Skip to content

Commit ed57d44

Browse files
authored
Merge pull request #201 from analogcode/dev
Fix popup interactions, live stream controls, and add buffering feedback
2 parents fddd10a + 0e6844b commit ed57d44

8 files changed

Lines changed: 323 additions & 139 deletions

File tree

SwiftRadio.xcodeproj/project.pbxproj

Lines changed: 31 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,8 @@
4343
CA2511BE2D45CD13006D1A99 /* ContributorsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = CA2511BD2D45CD11006D1A99 /* ContributorsViewController.swift */; };
4444
CA2511BF2D45CD13006D1A99 /* ContributorsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = CA2511BD2D45CD11006D1A99 /* ContributorsViewController.swift */; };
4545
CA2B6C6F2F31B769000CB4D4 /* MarqueeLabel in Frameworks */ = {isa = PBXBuildFile; productRef = CA2B6C6E2F31B769000CB4D4 /* MarqueeLabel */; };
46+
CA2B6C702F327016000CB4D4 /* LNPopupController in Embed Frameworks */ = {isa = PBXBuildFile; productRef = AA0000131ACC9DEE005B7C26 /* LNPopupController */; settings = {ATTRIBUTES = (CodeSignOnCopy, ); }; };
47+
CA2B6C722F327107000CB4D4 /* LNPopupController in Embed Frameworks */ = {isa = PBXBuildFile; productRef = AA0000111ACC9DEE005B7C26 /* LNPopupController */; settings = {ATTRIBUTES = (CodeSignOnCopy, ); }; };
4648
CA3A11012D4E000000111001 /* InfoSection.swift in Sources */ = {isa = PBXBuildFile; fileRef = CA3A11002D4E000000111001 /* InfoSection.swift */; };
4749
CA3A11022D4E000000111001 /* InfoSection.swift in Sources */ = {isa = PBXBuildFile; fileRef = CA3A11002D4E000000111001 /* InfoSection.swift */; };
4850
CA3A11042D4E000000111002 /* Content.swift in Sources */ = {isa = PBXBuildFile; fileRef = CA3A11032D4E000000111002 /* Content.swift */; };
@@ -118,6 +120,31 @@
118120
};
119121
/* End PBXContainerItemProxy section */
120122

123+
/* Begin PBXCopyFilesBuildPhase section */
124+
CA2B6C712F327016000CB4D4 /* Embed Frameworks */ = {
125+
isa = PBXCopyFilesBuildPhase;
126+
buildActionMask = 2147483647;
127+
dstPath = "";
128+
dstSubfolderSpec = 10;
129+
files = (
130+
CA2B6C702F327016000CB4D4 /* LNPopupController in Embed Frameworks */,
131+
);
132+
name = "Embed Frameworks";
133+
runOnlyForDeploymentPostprocessing = 0;
134+
};
135+
CA2B6C732F327107000CB4D4 /* Embed Frameworks */ = {
136+
isa = PBXCopyFilesBuildPhase;
137+
buildActionMask = 2147483647;
138+
dstPath = "";
139+
dstSubfolderSpec = 10;
140+
files = (
141+
CA2B6C722F327107000CB4D4 /* LNPopupController in Embed Frameworks */,
142+
);
143+
name = "Embed Frameworks";
144+
runOnlyForDeploymentPostprocessing = 0;
145+
};
146+
/* End PBXCopyFilesBuildPhase section */
147+
121148
/* Begin PBXFileReference section */
122149
2C5545BA1C1124DE00728469 /* SwiftRadioUITests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = SwiftRadioUITests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
123150
2C5545BC1C1124DE00728469 /* SwiftRadioUITests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SwiftRadioUITests.swift; sourceTree = "<group>"; };
@@ -422,6 +449,7 @@
422449
9409E1121ABF6FEA00312E2B /* Sources */,
423450
9409E1131ABF6FEA00312E2B /* Frameworks */,
424451
9409E1141ABF6FEA00312E2B /* Resources */,
452+
CA2B6C732F327107000CB4D4 /* Embed Frameworks */,
425453
);
426454
buildRules = (
427455
);
@@ -447,6 +475,7 @@
447475
CE6A3E30291F376D0058C82A /* Sources */,
448476
CE6A3E42291F376D0058C82A /* Frameworks */,
449477
CE6A3E45291F376D0058C82A /* Resources */,
478+
CA2B6C712F327016000CB4D4 /* Embed Frameworks */,
450479
);
451480
buildRules = (
452481
);
@@ -897,7 +926,7 @@
897926
PRODUCT_NAME = "$(TARGET_NAME)";
898927
PROVISIONING_PROFILE = "";
899928
PROVISIONING_PROFILE_SPECIFIER = "";
900-
"PROVISIONING_PROFILE_SPECIFIER[sdk=iphoneos*]" = swiftradiocarplay2;
929+
"PROVISIONING_PROFILE_SPECIFIER[sdk=iphoneos*]" = swiftradiocarplay3;
901930
SWIFT_INSTALL_OBJC_HEADER = NO;
902931
SWIFT_VERSION = 5.0;
903932
TARGETED_DEVICE_FAMILY = "1,2";
@@ -932,7 +961,7 @@
932961
PRODUCT_NAME = "$(TARGET_NAME)";
933962
PROVISIONING_PROFILE = "";
934963
PROVISIONING_PROFILE_SPECIFIER = "";
935-
"PROVISIONING_PROFILE_SPECIFIER[sdk=iphoneos*]" = swiftradiocarplay2;
964+
"PROVISIONING_PROFILE_SPECIFIER[sdk=iphoneos*]" = swiftradiocarplay3;
936965
SWIFT_INSTALL_OBJC_HEADER = NO;
937966
SWIFT_VERSION = 5.0;
938967
TARGETED_DEVICE_FAMILY = "1,2";

SwiftRadio/Cells/StationTableViewCell.swift

Lines changed: 53 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import NVActivityIndicatorView
1212
class StationTableViewCell: UITableViewCell {
1313

1414
// MARK: - UI
15+
private var representedStation: RadioStation?
1516

1617
private let cardBlurView: UIVisualEffectView = {
1718
let blur = UIBlurEffect(style: .systemUltraThinMaterialDark)
@@ -107,6 +108,22 @@ class StationTableViewCell: UITableViewCell {
107108
return view
108109
}()
109110

111+
private let bufferingOverlay: UIView = {
112+
let view = UIView()
113+
view.backgroundColor = UIColor.black.withAlphaComponent(0.5)
114+
view.layer.cornerRadius = 12
115+
view.clipsToBounds = true
116+
view.alpha = 0
117+
view.translatesAutoresizingMaskIntoConstraints = false
118+
return view
119+
}()
120+
121+
private let bufferingIndicator: NVActivityIndicatorView = {
122+
let view = NVActivityIndicatorView(frame: .zero, type: .ballPulse, color: .white, padding: nil)
123+
view.translatesAutoresizingMaskIntoConstraints = false
124+
return view
125+
}()
126+
110127
private var isAnimatingPulse = false
111128

112129
// MARK: - Init
@@ -122,13 +139,16 @@ class StationTableViewCell: UITableViewCell {
122139

123140
override func prepareForReuse() {
124141
super.prepareForReuse()
142+
representedStation = nil
125143
titleLabel.text = nil
126144
subtitleLabel.text = nil
127145
stationImageView.image = nil
128146
stopPulseAnimation()
129147
pulseRingView.alpha = 0
130148
equalizerView.stopAnimating()
131149
equalizerView.alpha = 0
150+
bufferingIndicator.stopAnimating()
151+
bufferingOverlay.alpha = 0
132152
}
133153

134154
// MARK: - Highlight / Tap Feedback
@@ -160,6 +180,8 @@ class StationTableViewCell: UITableViewCell {
160180
artworkContainer.addSubview(artworkShadowView)
161181
artworkContainer.addSubview(pulseRingView)
162182
artworkContainer.addSubview(stationImageView)
183+
artworkContainer.addSubview(bufferingOverlay)
184+
bufferingOverlay.addSubview(bufferingIndicator)
163185

164186
// Subtitle stack: label + equalizer
165187
subtitleStack.addArrangedSubview(equalizerView)
@@ -223,25 +245,51 @@ class StationTableViewCell: UITableViewCell {
223245
pulseRingView.bottomAnchor.constraint(equalTo: stationImageView.bottomAnchor, constant: -pulseInset),
224246
pulseRingView.leadingAnchor.constraint(equalTo: stationImageView.leadingAnchor, constant: pulseInset),
225247
pulseRingView.trailingAnchor.constraint(equalTo: stationImageView.trailingAnchor, constant: -pulseInset),
248+
249+
// Buffering overlay on top of image
250+
bufferingOverlay.topAnchor.constraint(equalTo: stationImageView.topAnchor),
251+
bufferingOverlay.bottomAnchor.constraint(equalTo: stationImageView.bottomAnchor),
252+
bufferingOverlay.leadingAnchor.constraint(equalTo: stationImageView.leadingAnchor),
253+
bufferingOverlay.trailingAnchor.constraint(equalTo: stationImageView.trailingAnchor),
254+
bufferingIndicator.centerXAnchor.constraint(equalTo: bufferingOverlay.centerXAnchor),
255+
bufferingIndicator.centerYAnchor.constraint(equalTo: bufferingOverlay.centerYAnchor),
256+
bufferingIndicator.widthAnchor.constraint(equalToConstant: 30),
257+
bufferingIndicator.heightAnchor.constraint(equalToConstant: 20),
226258
])
227259
}
228260

229261
// MARK: - Now Playing
230262

231-
func setNowPlaying(isPlaying: Bool, isCurrentStation: Bool) {
263+
func setNowPlaying(isPlaying: Bool, isBuffering: Bool, isCurrentStation: Bool) {
232264
guard isCurrentStation else {
233265
stopPulseAnimation()
234266
pulseRingView.alpha = 0
235267
equalizerView.stopAnimating()
236268
equalizerView.alpha = 0
269+
bufferingIndicator.stopAnimating()
270+
bufferingOverlay.alpha = 0
237271
return
238272
}
239273

240-
if isPlaying {
274+
if isBuffering {
275+
// Dark overlay + buffering indicator on artwork
276+
stopPulseAnimation()
277+
pulseRingView.alpha = 0
278+
equalizerView.stopAnimating()
279+
equalizerView.alpha = 0
280+
bufferingIndicator.startAnimating()
281+
bufferingOverlay.alpha = 1
282+
} else if isPlaying {
283+
// Pulse ring + equalizer
284+
bufferingIndicator.stopAnimating()
285+
bufferingOverlay.alpha = 0
241286
startPulseAnimation()
242287
equalizerView.startAnimating()
243288
equalizerView.alpha = 0.7
244289
} else {
290+
// Stopped — subtle indicators
291+
bufferingIndicator.stopAnimating()
292+
bufferingOverlay.alpha = 0
245293
stopPulseAnimation()
246294
pulseRingView.alpha = 0.25
247295
pulseRingView.transform = .identity
@@ -277,11 +325,13 @@ class StationTableViewCell: UITableViewCell {
277325

278326
extension StationTableViewCell {
279327
func configureStationCell(station: RadioStation) {
328+
representedStation = station
280329
titleLabel.text = station.name
281330
subtitleLabel.text = station.desc
282331

283332
station.getImage { [weak self] image in
284-
self?.stationImageView.image = image
333+
guard let self = self, self.representedStation == station else { return }
334+
self.stationImageView.image = image
285335
}
286336
}
287337
}

SwiftRadio/Coordinators/MainCoordinator.swift

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88

99
import UIKit
1010
import MessageUI
11+
import SafariServices
1112
import LNPopupController
1213

1314
class MainCoordinator: NavigationCoordinator {
@@ -70,10 +71,6 @@ class MainCoordinator: NavigationCoordinator {
7071
navigationController.present(modalNav, animated: true)
7172
}
7273

73-
func openWebsite(url: URL, from viewController: UIViewController) {
74-
UIApplication.shared.open(url, options: [:], completionHandler: nil)
75-
}
76-
7774
func share(_ text: String, from viewController: UIViewController) {
7875
let activityViewController = UIActivityViewController(activityItems: [text], applicationActivities: nil)
7976
if let popoverController = activityViewController.popoverPresentationController {
@@ -128,6 +125,13 @@ extension MainCoordinator: NowPlayingViewControllerDelegate {
128125
let infoController = InfoDetailViewController(station: station)
129126
navigationController.pushViewController(infoController, animated: true)
130127
navigationController.closePopup(animated: true)
128+
case .website:
129+
if let website = station.website, let url = URL(string: website) {
130+
let safariVC = SFSafariViewController(url: url)
131+
navigationController.closePopup(animated: true, completion: { [weak self] in
132+
self?.navigationController.present(safariVC, animated: true)
133+
})
134+
}
131135
default:
132136
BottomSheetHandler.handle(option, station: station, from: controller)
133137
}

0 commit comments

Comments
 (0)