From d34aaf7a385bc928935d2961cb79fa5f2f8909c4 Mon Sep 17 00:00:00 2001 From: Fethi El Hassasna Date: Sun, 25 Jun 2023 22:52:03 -0400 Subject: [PATCH] Refactor StationViewController to remove storyboard dependency --- SwiftRadio.xcodeproj/project.pbxproj | 115 ++++++++--- .../xcshareddata/swiftpm/Package.resolved | 9 + SwiftRadio/Cells/StationTableViewCell.swift | 95 ++++++--- SwiftRadio/Config.swift | 2 - SwiftRadio/Coordinators/MainCoordinator.swift | 2 +- .../UITableViewCell+reuseIdentifier.swift | 28 +++ SwiftRadio/Main.storyboard | 143 +------------ .../StationsViewController.swift | 191 +++++++++--------- .../{Helpers => Views}/LogoShareView.swift | 0 .../{Helpers => Views}/LogoShareView.xib | 0 SwiftRadio/Views/NowPlayingView.swift | 140 +++++++++++++ 11 files changed, 439 insertions(+), 286 deletions(-) mode change 100755 => 100644 SwiftRadio/Cells/StationTableViewCell.swift create mode 100644 SwiftRadio/Helpers/UITableViewCell+reuseIdentifier.swift mode change 100755 => 100644 SwiftRadio/ViewControllers/StationsViewController.swift rename SwiftRadio/{Helpers => Views}/LogoShareView.swift (100%) rename SwiftRadio/{Helpers => Views}/LogoShareView.xib (100%) create mode 100644 SwiftRadio/Views/NowPlayingView.swift diff --git a/SwiftRadio.xcodeproj/project.pbxproj b/SwiftRadio.xcodeproj/project.pbxproj index 7422fdb1..7d661445 100644 --- a/SwiftRadio.xcodeproj/project.pbxproj +++ b/SwiftRadio.xcodeproj/project.pbxproj @@ -15,7 +15,6 @@ 9409E11C1ABF6FEA00312E2B /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9409E11B1ABF6FEA00312E2B /* AppDelegate.swift */; }; 9409E1261ABF6FEA00312E2B /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 9409E1251ABF6FEA00312E2B /* Images.xcassets */; }; 9409E1401ABF78B000312E2B /* NowPlayingViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9409E13F1ABF78B000312E2B /* NowPlayingViewController.swift */; }; - 942A3F371AE43DF80011396E /* StationsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 942A3F361AE43DF80011396E /* StationsViewController.swift */; }; 94452E4F1AD6F24700BFE7A5 /* PopUpMenuViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 94452E4E1AD6F24700BFE7A5 /* PopUpMenuViewController.swift */; }; 94452E551AD7086800BFE7A5 /* AboutViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 94452E541AD7086800BFE7A5 /* AboutViewController.swift */; }; 945DB3C21AD58E3A00495EBB /* stations.json in Resources */ = {isa = PBXBuildFile; fileRef = 945DB3C11AD58E3A00495EBB /* stations.json */; }; @@ -25,7 +24,6 @@ 94D1D0A51AD6D6230022CA11 /* InfoDetailViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 94D1D0A41AD6D6230022CA11 /* InfoDetailViewController.swift */; }; 94D260911B45D20000DE671C /* Config.swift in Sources */ = {isa = PBXBuildFile; fileRef = 94D260901B45D20000DE671C /* Config.swift */; }; 94D260961B45E3FA00DE671C /* AnimationFrames.swift in Sources */ = {isa = PBXBuildFile; fileRef = 94D260951B45E3FA00DE671C /* AnimationFrames.swift */; }; - 94D30EA71AD07A880024FE96 /* StationTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 94D30EA61AD07A880024FE96 /* StationTableViewCell.swift */; }; 94E9761C1B1A8F3200F52B1E /* UIImage+DropShadow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 94E9761B1B1A8F3200F52B1E /* UIImage+DropShadow.swift */; }; CE0A4996291F3AD40071C0CC /* AppDelegate+CarPlay.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE0A4995291F3AD40071C0CC /* AppDelegate+CarPlay.swift */; }; CE0A4997291F3B080071C0CC /* AppDelegate+CarPlay.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE0A4995291F3AD40071C0CC /* AppDelegate+CarPlay.swift */; }; @@ -33,8 +31,19 @@ CE321FF429371140001572BD /* Bundle+appName.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE321FF229371140001572BD /* Bundle+appName.swift */; }; CE37D7B4290F47E000B0933B /* Spring in Frameworks */ = {isa = PBXBuildFile; productRef = CE37D7B3290F47E000B0933B /* Spring */; }; CE37D7B7290F4A9700B0933B /* FRadioPlayer in Frameworks */ = {isa = PBXBuildFile; productRef = CE37D7B6290F4A9700B0933B /* FRadioPlayer */; }; + CE6036132A47A87A00E15E15 /* StationsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE6036122A47A87A00E15E15 /* StationsViewController.swift */; }; + CE6036142A47A87A00E15E15 /* StationsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE6036122A47A87A00E15E15 /* StationsViewController.swift */; }; + CE6036162A47B88600E15E15 /* StationTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE6036152A47B88600E15E15 /* StationTableViewCell.swift */; }; + CE6036172A47B88600E15E15 /* StationTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE6036152A47B88600E15E15 /* StationTableViewCell.swift */; }; + CE6036192A48BA2500E15E15 /* UITableViewCell+reuseIdentifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE6036182A48BA2500E15E15 /* UITableViewCell+reuseIdentifier.swift */; }; + CE60361A2A48BA2500E15E15 /* UITableViewCell+reuseIdentifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE6036182A48BA2500E15E15 /* UITableViewCell+reuseIdentifier.swift */; }; + CE6036212A48C51A00E15E15 /* NVActivityIndicatorView in Frameworks */ = {isa = PBXBuildFile; productRef = CE6036202A48C51A00E15E15 /* NVActivityIndicatorView */; }; + CE6036232A48C51A00E15E15 /* NVActivityIndicatorViewExtended in Frameworks */ = {isa = PBXBuildFile; productRef = CE6036222A48C51A00E15E15 /* NVActivityIndicatorViewExtended */; }; + CE6036242A48F68D00E15E15 /* NowPlayingView.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE60361C2A48C4A400E15E15 /* NowPlayingView.swift */; }; + CE6036252A48F68D00E15E15 /* NowPlayingView.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE60361C2A48C4A400E15E15 /* NowPlayingView.swift */; }; + CE6036282A48F70A00E15E15 /* NVActivityIndicatorView in Frameworks */ = {isa = PBXBuildFile; productRef = CE6036272A48F70A00E15E15 /* NVActivityIndicatorView */; }; + CE60362A2A48F70A00E15E15 /* NVActivityIndicatorViewExtended in Frameworks */ = {isa = PBXBuildFile; productRef = CE6036292A48F70A00E15E15 /* NVActivityIndicatorViewExtended */; }; CE6A3E31291F376D0058C82A /* Config.swift in Sources */ = {isa = PBXBuildFile; fileRef = 94D260901B45D20000DE671C /* Config.swift */; }; - CE6A3E32291F376D0058C82A /* StationTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 94D30EA61AD07A880024FE96 /* StationTableViewCell.swift */; }; CE6A3E33291F376D0058C82A /* ShareActivity.swift in Sources */ = {isa = PBXBuildFile; fileRef = 53113F38230C720900462C0E /* ShareActivity.swift */; }; CE6A3E34291F376D0058C82A /* NowPlayingViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9409E13F1ABF78B000312E2B /* NowPlayingViewController.swift */; }; CE6A3E35291F376D0058C82A /* StationsManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE963ECB29135A6F004F299E /* StationsManager.swift */; }; @@ -47,7 +56,6 @@ CE6A3E3C291F376D0058C82A /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9409E11B1ABF6FEA00312E2B /* AppDelegate.swift */; }; CE6A3E3D291F376D0058C82A /* AnimationFrames.swift in Sources */ = {isa = PBXBuildFile; fileRef = 94D260951B45E3FA00DE671C /* AnimationFrames.swift */; }; CE6A3E3E291F376D0058C82A /* UIImage+Cache.swift in Sources */ = {isa = PBXBuildFile; fileRef = CEDABBEC29121BBA00C0367F /* UIImage+Cache.swift */; }; - CE6A3E3F291F376D0058C82A /* StationsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 942A3F361AE43DF80011396E /* StationsViewController.swift */; }; CE6A3E40291F376D0058C82A /* RadioStation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 94AC70AD1AD05C6200652982 /* RadioStation.swift */; }; CE6A3E41291F376D0058C82A /* UIImageView+Cache.swift in Sources */ = {isa = PBXBuildFile; fileRef = CEDABBEA291217AF00C0367F /* UIImageView+Cache.swift */; }; CE6A3E43291F376D0058C82A /* FRadioPlayer in Frameworks */ = {isa = PBXBuildFile; productRef = CE6A3E2E291F376D0058C82A /* FRadioPlayer */; }; @@ -102,7 +110,6 @@ 9409E11B1ABF6FEA00312E2B /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 9409E1251ABF6FEA00312E2B /* Images.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Images.xcassets; sourceTree = ""; }; 9409E13F1ABF78B000312E2B /* NowPlayingViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NowPlayingViewController.swift; sourceTree = ""; }; - 942A3F361AE43DF80011396E /* StationsViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = StationsViewController.swift; sourceTree = ""; }; 94452E4E1AD6F24700BFE7A5 /* PopUpMenuViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PopUpMenuViewController.swift; sourceTree = ""; }; 94452E541AD7086800BFE7A5 /* AboutViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AboutViewController.swift; sourceTree = ""; }; 945DB3C11AD58E3A00495EBB /* stations.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = stations.json; sourceTree = ""; }; @@ -112,11 +119,14 @@ 94D1D0A41AD6D6230022CA11 /* InfoDetailViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = InfoDetailViewController.swift; sourceTree = ""; }; 94D260901B45D20000DE671C /* Config.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Config.swift; sourceTree = ""; }; 94D260951B45E3FA00DE671C /* AnimationFrames.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AnimationFrames.swift; sourceTree = ""; }; - 94D30EA61AD07A880024FE96 /* StationTableViewCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = StationTableViewCell.swift; sourceTree = ""; }; 94E9761B1B1A8F3200F52B1E /* UIImage+DropShadow.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "UIImage+DropShadow.swift"; sourceTree = ""; }; CE0A4994291F3A220071C0CC /* SwiftRadio.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = SwiftRadio.entitlements; sourceTree = ""; }; CE0A4995291F3AD40071C0CC /* AppDelegate+CarPlay.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "AppDelegate+CarPlay.swift"; sourceTree = ""; }; CE321FF229371140001572BD /* Bundle+appName.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Bundle+appName.swift"; sourceTree = ""; }; + CE6036122A47A87A00E15E15 /* StationsViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StationsViewController.swift; sourceTree = ""; }; + CE6036152A47B88600E15E15 /* StationTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StationTableViewCell.swift; sourceTree = ""; }; + CE6036182A48BA2500E15E15 /* UITableViewCell+reuseIdentifier.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UITableViewCell+reuseIdentifier.swift"; sourceTree = ""; }; + CE60361C2A48C4A400E15E15 /* NowPlayingView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NowPlayingView.swift; sourceTree = ""; }; CE6A3E4F291F376D0058C82A /* SwiftRadio-CarPlay.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "SwiftRadio-CarPlay.app"; sourceTree = BUILT_PRODUCTS_DIR; }; CE6ECCF9292F13DF008B3C16 /* Coordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Coordinator.swift; sourceTree = ""; }; CE6ECCFC292F1445008B3C16 /* MainCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MainCoordinator.swift; sourceTree = ""; }; @@ -145,7 +155,9 @@ buildActionMask = 2147483647; files = ( CE37D7B7290F4A9700B0933B /* FRadioPlayer in Frameworks */, + CE6036232A48C51A00E15E15 /* NVActivityIndicatorViewExtended in Frameworks */, CE37D7B4290F47E000B0933B /* Spring in Frameworks */, + CE6036212A48C51A00E15E15 /* NVActivityIndicatorView in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -154,7 +166,9 @@ buildActionMask = 2147483647; files = ( CE6A3E43291F376D0058C82A /* FRadioPlayer in Frameworks */, + CE60362A2A48F70A00E15E15 /* NVActivityIndicatorViewExtended in Frameworks */, CE6A3E44291F376D0058C82A /* Spring in Frameworks */, + CE6036282A48F70A00E15E15 /* NVActivityIndicatorView in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -176,6 +190,7 @@ 9409E1181ABF6FEA00312E2B /* SwiftRadio */, 2C5545BB1C1124DE00728469 /* SwiftRadioUITests */, 9409E1171ABF6FEA00312E2B /* Products */, + CE6036262A48F70A00E15E15 /* Frameworks */, ); sourceTree = ""; }; @@ -196,13 +211,14 @@ CE0A4993291F39660071C0CC /* CarPlay */, CE8062F7291EF5D9008BD097 /* Helpers */, CE6ECCF8292F139D008B3C16 /* Coordinators */, + CE60361B2A48C43D00E15E15 /* Views */, CE8062F6291EF5B3008BD097 /* ViewControllers */, CE8062F5291EF598008BD097 /* Model */, CE8062F4291EF57D008BD097 /* Data */, CE8062F3291EF566008BD097 /* Networking */, CE8062F2291EF545008BD097 /* Cells */, - 9409E11B1ABF6FEA00312E2B /* AppDelegate.swift */, 94D260901B45D20000DE671C /* Config.swift */, + 9409E11B1ABF6FEA00312E2B /* AppDelegate.swift */, CF72ACE621F714D000461EED /* Main.storyboard */, 9409E1251ABF6FEA00312E2B /* Images.xcassets */, 9409E11A1ABF6FEA00312E2B /* Info.plist */, @@ -220,6 +236,23 @@ path = CarPlay; sourceTree = ""; }; + CE60361B2A48C43D00E15E15 /* Views */ = { + isa = PBXGroup; + children = ( + 6258DCD722D93A3500166C65 /* LogoShareView.swift */, + 6258DCD922D93A5400166C65 /* LogoShareView.xib */, + CE60361C2A48C4A400E15E15 /* NowPlayingView.swift */, + ); + path = Views; + sourceTree = ""; + }; + CE6036262A48F70A00E15E15 /* Frameworks */ = { + isa = PBXGroup; + children = ( + ); + name = Frameworks; + sourceTree = ""; + }; CE6ECCF8292F139D008B3C16 /* Coordinators */ = { isa = PBXGroup; children = ( @@ -233,7 +266,7 @@ isa = PBXGroup; children = ( 945DB3C41AD5A6E200495EBB /* NothingFoundCell.xib */, - 94D30EA61AD07A880024FE96 /* StationTableViewCell.swift */, + CE6036152A47B88600E15E15 /* StationTableViewCell.swift */, ); path = Cells; sourceTree = ""; @@ -272,7 +305,7 @@ 94D1D0A41AD6D6230022CA11 /* InfoDetailViewController.swift */, 94452E4E1AD6F24700BFE7A5 /* PopUpMenuViewController.swift */, 9409E13F1ABF78B000312E2B /* NowPlayingViewController.swift */, - 942A3F361AE43DF80011396E /* StationsViewController.swift */, + CE6036122A47A87A00E15E15 /* StationsViewController.swift */, ); path = ViewControllers; sourceTree = ""; @@ -289,8 +322,7 @@ 53113F38230C720900462C0E /* ShareActivity.swift */, CED6353A293081ED002B216F /* Handoffable.swift */, CE321FF229371140001572BD /* Bundle+appName.swift */, - 6258DCD722D93A3500166C65 /* LogoShareView.swift */, - 6258DCD922D93A5400166C65 /* LogoShareView.xib */, + CE6036182A48BA2500E15E15 /* UITableViewCell+reuseIdentifier.swift */, ); path = Helpers; sourceTree = ""; @@ -332,6 +364,8 @@ packageProductDependencies = ( CE37D7B3290F47E000B0933B /* Spring */, CE37D7B6290F4A9700B0933B /* FRadioPlayer */, + CE6036202A48C51A00E15E15 /* NVActivityIndicatorView */, + CE6036222A48C51A00E15E15 /* NVActivityIndicatorViewExtended */, ); productName = RadioPro; productReference = 9409E1161ABF6FEA00312E2B /* SwiftRadio.app */; @@ -353,6 +387,8 @@ packageProductDependencies = ( CE6A3E2C291F376D0058C82A /* Spring */, CE6A3E2E291F376D0058C82A /* FRadioPlayer */, + CE6036272A48F70A00E15E15 /* NVActivityIndicatorView */, + CE6036292A48F70A00E15E15 /* NVActivityIndicatorViewExtended */, ); productName = RadioPro; productReference = CE6A3E4F291F376D0058C82A /* SwiftRadio-CarPlay.app */; @@ -397,6 +433,7 @@ packageReferences = ( CE37D7B2290F47E000B0933B /* XCRemoteSwiftPackageReference "Spring" */, CE37D7B5290F4A9700B0933B /* XCRemoteSwiftPackageReference "FRadioPlayer" */, + CE60361F2A48C51A00E15E15 /* XCRemoteSwiftPackageReference "NVActivityIndicatorView" */, ); productRefGroup = 9409E1171ABF6FEA00312E2B /* Products */; projectDirPath = ""; @@ -460,7 +497,8 @@ buildActionMask = 2147483647; files = ( 94D260911B45D20000DE671C /* Config.swift in Sources */, - 94D30EA71AD07A880024FE96 /* StationTableViewCell.swift in Sources */, + CE6036192A48BA2500E15E15 /* UITableViewCell+reuseIdentifier.swift in Sources */, + CE6036162A47B88600E15E15 /* StationTableViewCell.swift in Sources */, CE9EE8DE293BB41300F62041 /* BaseController.swift in Sources */, 53113F39230C720900462C0E /* ShareActivity.swift in Sources */, 9409E1401ABF78B000312E2B /* NowPlayingViewController.swift in Sources */, @@ -481,10 +519,11 @@ 94D260961B45E3FA00DE671C /* AnimationFrames.swift in Sources */, CE6ECD03292F358C008B3C16 /* UIViewController+Email.swift in Sources */, CEDABBED29121BBA00C0367F /* UIImage+Cache.swift in Sources */, - 942A3F371AE43DF80011396E /* StationsViewController.swift in Sources */, + CE6036252A48F68D00E15E15 /* NowPlayingView.swift in Sources */, 94AC70AE1AD05C6200652982 /* RadioStation.swift in Sources */, CE9EE8E1293C048A00F62041 /* LoaderController.swift in Sources */, CEDABBEB291217AF00C0367F /* UIImageView+Cache.swift in Sources */, + CE6036132A47A87A00E15E15 /* StationsViewController.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -493,7 +532,8 @@ buildActionMask = 2147483647; files = ( CE6A3E31291F376D0058C82A /* Config.swift in Sources */, - CE6A3E32291F376D0058C82A /* StationTableViewCell.swift in Sources */, + CE60361A2A48BA2500E15E15 /* UITableViewCell+reuseIdentifier.swift in Sources */, + CE6036172A47B88600E15E15 /* StationTableViewCell.swift in Sources */, CE9EE8DF293BB41300F62041 /* BaseController.swift in Sources */, CE6A3E33291F376D0058C82A /* ShareActivity.swift in Sources */, CE6A3E34291F376D0058C82A /* NowPlayingViewController.swift in Sources */, @@ -514,10 +554,11 @@ CE6A3E3D291F376D0058C82A /* AnimationFrames.swift in Sources */, CE6ECD04292F358C008B3C16 /* UIViewController+Email.swift in Sources */, CE6A3E3E291F376D0058C82A /* UIImage+Cache.swift in Sources */, - CE6A3E3F291F376D0058C82A /* StationsViewController.swift in Sources */, + CE6036242A48F68D00E15E15 /* NowPlayingView.swift in Sources */, CE6A3E40291F376D0058C82A /* RadioStation.swift in Sources */, CE9EE8E2293C048A00F62041 /* LoaderController.swift in Sources */, CE6A3E41291F376D0058C82A /* UIImageView+Cache.swift in Sources */, + CE6036142A47A87A00E15E15 /* StationsViewController.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -538,7 +579,7 @@ DEBUG_INFORMATION_FORMAT = dwarf; GCC_NO_COMMON_BLOCKS = YES; INFOPLIST_FILE = SwiftRadioUITests/Info.plist; - IPHONEOS_DEPLOYMENT_TARGET = 12.0; + IPHONEOS_DEPLOYMENT_TARGET = 13.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", @@ -558,7 +599,7 @@ DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; GCC_NO_COMMON_BLOCKS = YES; INFOPLIST_FILE = SwiftRadioUITests/Info.plist; - IPHONEOS_DEPLOYMENT_TARGET = 12.0; + IPHONEOS_DEPLOYMENT_TARGET = 13.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", @@ -620,7 +661,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 12.0; + IPHONEOS_DEPLOYMENT_TARGET = 13.0; MTL_ENABLE_DEBUG_INFO = YES; ONLY_ACTIVE_ARCH = YES; SDKROOT = iphoneos; @@ -669,7 +710,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 12.0; + IPHONEOS_DEPLOYMENT_TARGET = 13.0; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = iphoneos; SWIFT_COMPILATION_MODE = wholemodule; @@ -692,7 +733,7 @@ "$(PROJECT_DIR)/SwiftRadio", ); INFOPLIST_FILE = SwiftRadio/Info.plist; - IPHONEOS_DEPLOYMENT_TARGET = 12.0; + IPHONEOS_DEPLOYMENT_TARGET = 13.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", @@ -722,7 +763,7 @@ "$(PROJECT_DIR)/SwiftRadio", ); INFOPLIST_FILE = SwiftRadio/Info.plist; - IPHONEOS_DEPLOYMENT_TARGET = 12.0; + IPHONEOS_DEPLOYMENT_TARGET = 13.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", @@ -755,7 +796,7 @@ "$(PROJECT_DIR)/SwiftRadio", ); INFOPLIST_FILE = "SwiftRadio/Info-CarPlay.plist"; - IPHONEOS_DEPLOYMENT_TARGET = 12.0; + IPHONEOS_DEPLOYMENT_TARGET = 13.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", @@ -790,7 +831,7 @@ "$(PROJECT_DIR)/SwiftRadio", ); INFOPLIST_FILE = "SwiftRadio/Info-CarPlay.plist"; - IPHONEOS_DEPLOYMENT_TARGET = 12.0; + IPHONEOS_DEPLOYMENT_TARGET = 13.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", @@ -866,6 +907,14 @@ kind = branch; }; }; + CE60361F2A48C51A00E15E15 /* XCRemoteSwiftPackageReference "NVActivityIndicatorView" */ = { + isa = XCRemoteSwiftPackageReference; + repositoryURL = "https://github.com/ninjaprox/NVActivityIndicatorView.git"; + requirement = { + kind = upToNextMajorVersion; + minimumVersion = 5.0.0; + }; + }; CE6A3E2D291F376D0058C82A /* XCRemoteSwiftPackageReference "Spring" */ = { isa = XCRemoteSwiftPackageReference; repositoryURL = "https://github.com/fethica/Spring.git"; @@ -895,6 +944,26 @@ package = CE37D7B5290F4A9700B0933B /* XCRemoteSwiftPackageReference "FRadioPlayer" */; productName = FRadioPlayer; }; + CE6036202A48C51A00E15E15 /* NVActivityIndicatorView */ = { + isa = XCSwiftPackageProductDependency; + package = CE60361F2A48C51A00E15E15 /* XCRemoteSwiftPackageReference "NVActivityIndicatorView" */; + productName = NVActivityIndicatorView; + }; + CE6036222A48C51A00E15E15 /* NVActivityIndicatorViewExtended */ = { + isa = XCSwiftPackageProductDependency; + package = CE60361F2A48C51A00E15E15 /* XCRemoteSwiftPackageReference "NVActivityIndicatorView" */; + productName = NVActivityIndicatorViewExtended; + }; + CE6036272A48F70A00E15E15 /* NVActivityIndicatorView */ = { + isa = XCSwiftPackageProductDependency; + package = CE60361F2A48C51A00E15E15 /* XCRemoteSwiftPackageReference "NVActivityIndicatorView" */; + productName = NVActivityIndicatorView; + }; + CE6036292A48F70A00E15E15 /* NVActivityIndicatorViewExtended */ = { + isa = XCSwiftPackageProductDependency; + package = CE60361F2A48C51A00E15E15 /* XCRemoteSwiftPackageReference "NVActivityIndicatorView" */; + productName = NVActivityIndicatorViewExtended; + }; CE6A3E2C291F376D0058C82A /* Spring */ = { isa = XCSwiftPackageProductDependency; package = CE6A3E2D291F376D0058C82A /* XCRemoteSwiftPackageReference "Spring" */; diff --git a/SwiftRadio.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/SwiftRadio.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index 16ececdf..4645fed8 100644 --- a/SwiftRadio.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/SwiftRadio.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -9,6 +9,15 @@ "revision" : "c3f472ed8cb59b442312362bca363863962c88b3" } }, + { + "identity" : "nvactivityindicatorview", + "kind" : "remoteSourceControl", + "location" : "https://github.com/ninjaprox/NVActivityIndicatorView.git", + "state" : { + "revision" : "bcb52371f2259254bac6690f92bb474a61768c47", + "version" : "5.1.1" + } + }, { "identity" : "spring", "kind" : "remoteSourceControl", diff --git a/SwiftRadio/Cells/StationTableViewCell.swift b/SwiftRadio/Cells/StationTableViewCell.swift old mode 100755 new mode 100644 index dc219de3..1b2c0e40 --- a/SwiftRadio/Cells/StationTableViewCell.swift +++ b/SwiftRadio/Cells/StationTableViewCell.swift @@ -1,46 +1,93 @@ // // StationTableViewCell.swift -// Swift Radio +// SwiftRadio // -// Created by Matthew Fecher on 4/4/15. -// Copyright (c) 2015 MatthewFecher.com. All rights reserved. +// Created by Fethi El Hassasna on 2023-06-24. +// Copyright © 2023 matthewfecher.com. All rights reserved. // import UIKit +import NVActivityIndicatorView class StationTableViewCell: UITableViewCell { - - @IBOutlet weak var stationNameLabel: UILabel! - @IBOutlet weak var stationDescLabel: UILabel! - @IBOutlet weak var stationImageView: UIImageView! + + let stationImageView: UIImageView = { + let imageView = UIImageView() + imageView.contentMode = .scaleAspectFill + imageView.clipsToBounds = true + NSLayoutConstraint.activate([ + imageView.heightAnchor.constraint(equalToConstant: 75), + imageView.widthAnchor.constraint(equalToConstant: 110) + ]) + return imageView + }() + + let titleLabel: UILabel = { + let label = UILabel() + label.font = .preferredFont(forTextStyle: .title3) + label.numberOfLines = 2 + label.translatesAutoresizingMaskIntoConstraints = false + return label + }() + + let subtitleLabel: UILabel = { + let label = UILabel() + label.font = .preferredFont(forTextStyle: .footnote) + label.translatesAutoresizingMaskIntoConstraints = false + return label + }() + + override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) { + super.init(style: style, reuseIdentifier: reuseIdentifier) + setupViews() + } + + override func prepareForReuse() { + super.prepareForReuse() + titleLabel.text = nil + subtitleLabel.text = nil + stationImageView.image = nil + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + private func setupViews() { - override func awakeFromNib() { - super.awakeFromNib() + selectionStyle = .default - // set ImageView shadow - stationImageView.applyShadow() + let vStackView = UIStackView(arrangedSubviews: [titleLabel, subtitleLabel]) + vStackView.spacing = 8 + vStackView.axis = .vertical + vStackView.translatesAutoresizingMaskIntoConstraints = false - // set table selection color - let selectedView = UIView(frame: CGRect.zero) - selectedView.backgroundColor = UIColor(red: 78/255, green: 82/255, blue: 93/255, alpha: 0.6) - selectedBackgroundView = selectedView + let hStackView = UIStackView(arrangedSubviews: [stationImageView, vStackView]) + hStackView.spacing = 8 + hStackView.axis = .horizontal + hStackView.alignment = .center + hStackView.translatesAutoresizingMaskIntoConstraints = false + + contentView.addSubview(hStackView) + + NSLayoutConstraint.activate([ + hStackView.topAnchor.constraint(equalTo: contentView.layoutMarginsGuide.topAnchor), + hStackView.trailingAnchor.constraint(equalTo: contentView.layoutMarginsGuide.trailingAnchor), + hStackView.bottomAnchor.constraint(equalTo: contentView.layoutMarginsGuide.bottomAnchor), + hStackView.leadingAnchor.constraint(equalTo: contentView.layoutMarginsGuide.leadingAnchor) + ]) } +} +extension StationTableViewCell { func configureStationCell(station: RadioStation) { // Configure the cell... - stationNameLabel.text = station.name - stationDescLabel.text = station.desc + titleLabel.text = station.name + subtitleLabel.text = station.desc station.getImage { [weak self] image in self?.stationImageView.image = image } } - - override func prepareForReuse() { - super.prepareForReuse() - stationNameLabel.text = nil - stationDescLabel.text = nil - stationImageView.image = nil - } } diff --git a/SwiftRadio/Config.swift b/SwiftRadio/Config.swift index ade0b5ab..b48eed40 100755 --- a/SwiftRadio/Config.swift +++ b/SwiftRadio/Config.swift @@ -8,8 +8,6 @@ import UIKit -import UIKit - struct Config { static let debugLog = true diff --git a/SwiftRadio/Coordinators/MainCoordinator.swift b/SwiftRadio/Coordinators/MainCoordinator.swift index b06fc4ce..3b957572 100644 --- a/SwiftRadio/Coordinators/MainCoordinator.swift +++ b/SwiftRadio/Coordinators/MainCoordinator.swift @@ -55,7 +55,7 @@ class MainCoordinator: NavigationCoordinator { extension MainCoordinator: LoaderControllerDelegate { func didFinishLoading(_ controller: LoaderController, stations: [RadioStation]) { - let stationsVC = Storyboard.viewController as StationsViewController + let stationsVC = StationsViewController() stationsVC.delegate = self navigationController.setViewControllers([stationsVC], animated: false) } diff --git a/SwiftRadio/Helpers/UITableViewCell+reuseIdentifier.swift b/SwiftRadio/Helpers/UITableViewCell+reuseIdentifier.swift new file mode 100644 index 00000000..538c4037 --- /dev/null +++ b/SwiftRadio/Helpers/UITableViewCell+reuseIdentifier.swift @@ -0,0 +1,28 @@ +// +// UITableViewCell+reuseIdentifier.swift +// SwiftRadio +// +// Created by Fethi El Hassasna on 2023-06-25. +// Copyright © 2023 matthewfecher.com. All rights reserved. +// + +import UIKit + +extension UITableViewCell { + static var reuseIdentifier: String { + return String(describing: self) + } +} + +extension UITableView { + func register(_: T.Type) { + register(T.self, forCellReuseIdentifier: T.reuseIdentifier) + } + + func dequeueReusableCell(for indexPath: IndexPath) -> T { + guard let cell = dequeueReusableCell(withIdentifier: T.reuseIdentifier, for: indexPath) as? T else { + fatalError("Could not dequeue cell with identifier: \(T.reuseIdentifier)") + } + return cell + } +} diff --git a/SwiftRadio/Main.storyboard b/SwiftRadio/Main.storyboard index 0ce89ac1..21875fe4 100755 --- a/SwiftRadio/Main.storyboard +++ b/SwiftRadio/Main.storyboard @@ -1,148 +1,14 @@ - + - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - @@ -529,7 +395,7 @@ - + @@ -626,7 +492,7 @@ - + @@ -762,7 +628,6 @@ - diff --git a/SwiftRadio/ViewControllers/StationsViewController.swift b/SwiftRadio/ViewControllers/StationsViewController.swift old mode 100755 new mode 100644 index 42301068..1fb35b24 --- a/SwiftRadio/ViewControllers/StationsViewController.swift +++ b/SwiftRadio/ViewControllers/StationsViewController.swift @@ -1,39 +1,36 @@ // // StationsViewController.swift -// Swift Radio +// SwiftRadio // -// Created by Matthew Fecher on 7/19/15. -// Copyright (c) 2015 MatthewFecher.com. All rights reserved. +// Created by Fethi El Hassasna on 2023-06-24. +// Copyright © 2023 matthewfecher.com. All rights reserved. // import UIKit -import AVFoundation import FRadioPlayer -import Spring protocol StationsViewControllerDelegate: AnyObject { func pushNowPlayingController(_ stationsViewController: StationsViewController, newStation: Bool) func presentPopUpMenuController(_ stationsViewController: StationsViewController) } -class StationsViewController: UIViewController, Handoffable { +class StationsViewController: BaseController, Handoffable { // MARK: - Delegate weak var delegate: StationsViewControllerDelegate? - // MARK: - IB UI - - @IBOutlet weak var tableView: UITableView! - @IBOutlet weak var stationNowPlayingButton: UIButton! - @IBOutlet weak var nowPlayingAnimationImageView: UIImageView! - // MARK: - Properties - private let player = FRadioPlayer.shared private let manager = StationsManager.shared // MARK: - UI + private lazy var refreshControl: UIRefreshControl = { + let refreshControl = UIRefreshControl() + refreshControl.addTarget(self, action: #selector(refresh), for: .valueChanged) + return refreshControl + }() + private let searchController: UISearchController = { let controller = UISearchController(searchResultsController: nil) controller.obscuresBackgroundDuringPresentation = false @@ -41,22 +38,32 @@ class StationsViewController: UIViewController, Handoffable { return controller }() - private let refreshControl: UIRefreshControl = { - return UIRefreshControl() + private lazy var tableView: UITableView = { + let tableView = UITableView() + tableView.backgroundColor = .clear + tableView.backgroundView = nil + tableView.separatorStyle = .none + let cellNib = UINib(nibName: "NothingFoundCell", bundle: nil) + tableView.register(cellNib, forCellReuseIdentifier: "NothingFound") + tableView.register(StationTableViewCell.self) + tableView.dataSource = self + tableView.delegate = self + tableView.translatesAutoresizingMaskIntoConstraints = false + return tableView }() - // MARK: - ViewDidLoad + private let nowPlayingView: NowPlayingView = { + return NowPlayingView() + }() - @objc func handleMenuTap() { - delegate?.presentPopUpMenuController(self) + override func loadView() { + super.loadView() + setupViews() } override func viewDidLoad() { super.viewDidLoad() - - // Register 'Nothing Found' cell xib - let cellNib = UINib(nibName: "NothingFoundCell", bundle: nil) - tableView.register(cellNib, forCellReuseIdentifier: "NothingFound") + navigationController?.navigationBar.prefersLargeTitles = true // NavigationBar items navigationItem.leftBarButtonItem = UIBarButtonItem(image: #imageLiteral(resourceName: "icon-hamburger"), style: .plain, target: self, action: #selector(handleMenuTap)) @@ -65,42 +72,60 @@ class StationsViewController: UIViewController, Handoffable { player.addObserver(self) manager.addObserver(self) - // Setup TableView - tableView.backgroundColor = .clear - tableView.backgroundView = nil - tableView.separatorStyle = .none - - // Setup Pull to Refresh - setupPullToRefresh() - - // Create NowPlaying Animation - createNowPlayingAnimation() + // Setup Handoff User Activity + setupHandoffUserActivity() // Setup Search Bar setupSearchController() - // Setup Handoff User Activity - setupHandoffUserActivity() + // Now Playing View + nowPlayingView.tapHandler = { [weak self] in + self?.nowPlayingBarButtonPressed() + } } override func viewWillAppear(_ animated: Bool) { super.viewWillAppear(animated) title = "Swift Radio" } - - // MARK: - Setup UI Elements - private func setupPullToRefresh() { - refreshControl.attributedTitle = NSAttributedString(string: "Pull to refresh", attributes: [.foregroundColor: UIColor.white]) - refreshControl.backgroundColor = .black - refreshControl.tintColor = .white - refreshControl.addTarget(self, action: #selector(refresh), for: .valueChanged) - tableView.addSubview(refreshControl) + @objc func refresh(sender: AnyObject) { + // Pull to Refresh + manager.fetch() + + // Wait 2 seconds then refresh screen + DispatchQueue.main.asyncAfter(deadline: .now() + 2) { [weak self] in + self?.refreshControl.endRefreshing() + self?.view.setNeedsDisplay() + } } - private func createNowPlayingAnimation() { - nowPlayingAnimationImageView.animationImages = AnimationFrames.createFrames() - nowPlayingAnimationImageView.animationDuration = 0.7 + // Reset all properties to default + private func resetCurrentStation() { + nowPlayingView.reset() + navigationItem.rightBarButtonItem = nil + } + + // Update the now playing button title + private func updateNowPlayingButton(station: RadioStation?) { + + guard let station = station else { + nowPlayingView.reset() + return + } + + var playingTitle: String? + + if player.currentMetadata != nil { + playingTitle = station.trackName + " - " + station.artistName + } + + nowPlayingView.update(with: playingTitle, subtitle: station.name) + createNowPlayingBarButton() + } + + func startNowPlayingAnimation(_ animate: Bool) { + animate ? nowPlayingView.startAnimating() : nowPlayingView.stopAnimating() } private func createNowPlayingBarButton() { @@ -108,29 +133,18 @@ class StationsViewController: UIViewController, Handoffable { navigationItem.rightBarButtonItem = UIBarButtonItem(image: #imageLiteral(resourceName: "btn-nowPlaying"), style: .plain, target: self, action: #selector(nowPlayingBarButtonPressed)) } - // MARK: - Actions - @objc func nowPlayingBarButtonPressed() { pushNowPlayingController() } - @IBAction func nowPlayingPressed(_ sender: UIButton) { - pushNowPlayingController() + @objc func handleMenuTap() { + delegate?.presentPopUpMenuController(self) } - @objc func refresh(sender: AnyObject) { - // Pull to Refresh - manager.fetch() - - // Wait 2 seconds then refresh screen - DispatchQueue.main.asyncAfter(deadline: .now() + 2) { [weak self] in - self?.refreshControl.endRefreshing() - self?.view.setNeedsDisplay() - } + func nowPlayingPressed(_ sender: UIButton) { + pushNowPlayingController() } - // MARK: - Segue - func pushNowPlayingController(with station: RadioStation? = nil) { title = "" @@ -150,36 +164,22 @@ class StationsViewController: UIViewController, Handoffable { delegate?.pushNowPlayingController(self, newStation: newStation) } - // Reset all properties to default - private func resetCurrentStation() { - nowPlayingAnimationImageView.stopAnimating() - stationNowPlayingButton.setTitle("Choose a station above to begin", for: .normal) - stationNowPlayingButton.isEnabled = false - navigationItem.rightBarButtonItem = nil - } - - // Update the now playing button title - private func updateNowPlayingButton(station: RadioStation?) { - - guard let station = station else { - return - } + override func setupViews() { + super.setupViews() - var playingTitle = station.name + ": " + let stackView = UIStackView(arrangedSubviews: [tableView, nowPlayingView]) + stackView.axis = .vertical + stackView.translatesAutoresizingMaskIntoConstraints = false - if player.currentMetadata == nil { - playingTitle += "Now playing ..." - } else { - playingTitle += station.trackName + " - " + station.artistName - } + tableView.addSubview(refreshControl) + view.addSubview(stackView) - stationNowPlayingButton.setTitle(playingTitle, for: .normal) - stationNowPlayingButton.isEnabled = true - createNowPlayingBarButton() - } - - func startNowPlayingAnimation(_ animate: Bool) { - animate ? nowPlayingAnimationImageView.startAnimating() : nowPlayingAnimationImageView.stopAnimating() + NSLayoutConstraint.activate([ + stackView.topAnchor.constraint(equalTo: view.layoutMarginsGuide.topAnchor), + stackView.trailingAnchor.constraint(equalTo: view.trailingAnchor), + stackView.bottomAnchor.constraint(equalTo: view.layoutMarginsGuide.bottomAnchor), + stackView.leadingAnchor.constraint(equalTo: view.leadingAnchor) + ]) } } @@ -188,15 +188,14 @@ class StationsViewController: UIViewController, Handoffable { extension StationsViewController: UITableViewDataSource { func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat { - return 90.0 + 90.0 } func numberOfSections(in tableView: UITableView) -> Int { - return 1 + 1 } func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { - if searchController.isActive { return manager.searchedStations.count } else { @@ -207,20 +206,18 @@ extension StationsViewController: UITableViewDataSource { func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { if manager.stations.isEmpty { - let cell = tableView.dequeueReusableCell(withIdentifier: "NothingFound", for: indexPath) + let cell = tableView.dequeueReusableCell(withIdentifier: "NothingFound", for: indexPath) cell.backgroundColor = .clear cell.selectionStyle = .none return cell - } else { - let cell = tableView.dequeueReusableCell(withIdentifier: "StationCell", for: indexPath) as! StationTableViewCell + let cell = tableView.dequeueReusableCell(for: indexPath) as StationTableViewCell // alternate background color - cell.backgroundColor = (indexPath.row % 2 == 0) ? UIColor.clear : UIColor.black.withAlphaComponent(0.2) + cell.backgroundColor = (indexPath.row % 2 == 0) ? .clear : .black.withAlphaComponent(0.2) let station = searchController.isActive ? manager.searchedStations[indexPath.row] : manager.stations[indexPath.row] cell.configureStationCell(station: station) - return cell } } @@ -250,7 +247,7 @@ extension StationsViewController: UISearchResultsUpdating { navigationItem.searchController = searchController navigationItem.hidesSearchBarWhenScrolling = true } - + func updateSearchResults(for searchController: UISearchController) { guard let filter = searchController.searchBar.text else { return } manager.updateSearch(with: filter) @@ -275,7 +272,7 @@ extension StationsViewController: FRadioPlayerObserver { extension StationsViewController: StationsManagerObserver { func stationsManager(_ manager: StationsManager, stationsDidUpdate stations: [RadioStation]) { - self.tableView.reloadData() + tableView.reloadData() } func stationsManager(_ manager: StationsManager, stationDidChange station: RadioStation?) { diff --git a/SwiftRadio/Helpers/LogoShareView.swift b/SwiftRadio/Views/LogoShareView.swift similarity index 100% rename from SwiftRadio/Helpers/LogoShareView.swift rename to SwiftRadio/Views/LogoShareView.swift diff --git a/SwiftRadio/Helpers/LogoShareView.xib b/SwiftRadio/Views/LogoShareView.xib similarity index 100% rename from SwiftRadio/Helpers/LogoShareView.xib rename to SwiftRadio/Views/LogoShareView.xib diff --git a/SwiftRadio/Views/NowPlayingView.swift b/SwiftRadio/Views/NowPlayingView.swift new file mode 100644 index 00000000..751403ed --- /dev/null +++ b/SwiftRadio/Views/NowPlayingView.swift @@ -0,0 +1,140 @@ +// +// NowPlayingView.swift +// SwiftRadio +// +// Created by Fethi El Hassasna on 2023-06-25. +// Copyright © 2023 matthewfecher.com. All rights reserved. +// + +import UIKit +import NVActivityIndicatorView + +class NowPlayingView: UIView { + + var tapHandler: (() -> Void)? + + private static let resetTitle = "Choose a station above to begin..." + + private let animationView: NVActivityIndicatorView = { + let activityIndicatorView = NVActivityIndicatorView(frame: .zero, type: .audioEqualizer, color: .white, padding: nil) + NSLayoutConstraint.activate([ + activityIndicatorView.widthAnchor.constraint(equalToConstant: 30), + activityIndicatorView.heightAnchor.constraint(equalToConstant: 20) + ]) + return activityIndicatorView + }() + + private let nowPlayingButton: UIButton = { + let button = UIButton() + button.isEnabled = false + button.contentHorizontalAlignment = .left + button.translatesAutoresizingMaskIntoConstraints = false + return button + }() + + private let titleLabel: UILabel = { + let label = UILabel() + label.text = NowPlayingView.resetTitle + label.font = .preferredFont(forTextStyle: .callout) + label.textColor = .lightText + return label + }() + + private let subtitleLabel: UILabel = { + let label = UILabel() + label.font = .preferredFont(forTextStyle: .caption2) + label.textColor = .lightText + return label + }() + + init() { + super.init(frame: .zero) + setupViews() + } + + required init?(coder aDecoder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + private func setupViews() { + backgroundColor = .black.withAlphaComponent(0.1) + NSLayoutConstraint.activate([ + heightAnchor.constraint(greaterThanOrEqualToConstant: 50) + ]) + + nowPlayingButton.addTarget(self, action: #selector(handleTap), for: .touchUpInside) + + let dividerView = UIView() + dividerView.backgroundColor = UIColor.darkGray.withAlphaComponent(0.8) + dividerView.translatesAutoresizingMaskIntoConstraints = false + addSubview(dividerView) + + NSLayoutConstraint.activate([ + dividerView.topAnchor.constraint(equalTo: topAnchor), + dividerView.leadingAnchor.constraint(equalTo: leadingAnchor), + dividerView.trailingAnchor.constraint(equalTo: trailingAnchor), + dividerView.heightAnchor.constraint(equalToConstant: 0.5) + ]) + + let titleStackView = UIStackView(arrangedSubviews: [titleLabel, subtitleLabel]) + titleStackView.translatesAutoresizingMaskIntoConstraints = false + titleStackView.axis = .vertical + titleStackView.spacing = 4 + + let stackView = UIStackView(arrangedSubviews: [titleStackView, animationView]) + stackView.translatesAutoresizingMaskIntoConstraints = false + stackView.axis = .horizontal + stackView.alignment = .center + + addSubview(stackView) + + NSLayoutConstraint.activate([ + stackView.topAnchor.constraint(equalTo: dividerView.bottomAnchor, constant: 8), + stackView.rightAnchor.constraint(equalTo: layoutMarginsGuide.rightAnchor, constant: -8), + stackView.bottomAnchor.constraint(equalTo: layoutMarginsGuide.bottomAnchor, constant: 8), + stackView.leftAnchor.constraint(equalTo: layoutMarginsGuide.leftAnchor, constant: 8) + ]) + + addSubview(nowPlayingButton) + + NSLayoutConstraint.activate([ + nowPlayingButton.topAnchor.constraint(equalTo: topAnchor), + nowPlayingButton.rightAnchor.constraint(equalTo: rightAnchor), + nowPlayingButton.bottomAnchor.constraint(equalTo: bottomAnchor), + nowPlayingButton.leftAnchor.constraint(equalTo: leftAnchor) + ]) + + bringSubviewToFront(nowPlayingButton) + } + + func startAnimating() { + animationView.startAnimating() + } + + func stopAnimating() { + animationView.stopAnimating() + } + + func reset() { + animationView.stopAnimating() + titleLabel.text = NowPlayingView.resetTitle + subtitleLabel.text = nil + nowPlayingButton.isEnabled = false + } + + func update(with title: String?, subtitle: String) { + nowPlayingButton.isEnabled = true + + if let title { + titleLabel.text = title + subtitleLabel.text = subtitle + } else { + titleLabel.text = subtitle + subtitleLabel.text = "Now playing ..." + } + } + + @objc private func handleTap() { + tapHandler?() + } +}