@neoskola/auto-play 0.3.10 → 0.3.11

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -55,4 +55,11 @@ extension UIImage {
55
55
  thumbPath.fill()
56
56
  }
57
57
  }
58
+
59
+ func resized(to targetSize: CGSize) -> UIImage {
60
+ let renderer = UIGraphicsImageRenderer(size: targetSize)
61
+ return renderer.image { _ in
62
+ self.draw(in: CGRect(origin: .zero, size: targetSize))
63
+ }
64
+ }
58
65
  }
@@ -2,7 +2,7 @@ import CarPlay
2
2
  import MediaPlayer
3
3
  import AVFoundation
4
4
 
5
- class NowPlayingTemplate: NSObject, AutoPlayTemplate, CPMapTemplateDelegate, NowPlayingViewDelegate {
5
+ class NowPlayingTemplate: NSObject, AutoPlayTemplate, CPMapTemplateDelegate {
6
6
  var template: CPMapTemplate
7
7
  var config: NowPlayingTemplateConfig
8
8
  private var loadedImage: UIImage?
@@ -42,7 +42,7 @@ class NowPlayingTemplate: NSObject, AutoPlayTemplate, CPMapTemplateDelegate, Now
42
42
  super.init()
43
43
 
44
44
  template.mapDelegate = self
45
- template.mapButtons = []
45
+ template.mapButtons = Self.buildMapButtons(isPlaying: config.isPlaying, owner: self)
46
46
 
47
47
  DispatchQueue.main.async { [weak self] in
48
48
  guard let self = self else { return }
@@ -57,6 +57,53 @@ class NowPlayingTemplate: NSObject, AutoPlayTemplate, CPMapTemplateDelegate, Now
57
57
  }
58
58
  }
59
59
 
60
+ // MARK: - CPMapButton Controls
61
+
62
+ private static func buildMapButtons(isPlaying: Bool, owner: NowPlayingTemplate) -> [CPMapButton] {
63
+ let buttonSize = CPButtonMaximumImageSize
64
+
65
+ // Previous track button
66
+ let prevImage = UIImage(systemName: "backward.end.fill")?
67
+ .withTintColor(.systemBlue, renderingMode: .alwaysOriginal)
68
+ .resized(to: buttonSize)
69
+ let prevButton = CPMapButton(image: prevImage ?? UIImage()) { [weak owner] _ in
70
+ owner?.config.onPreviousTrack?()
71
+ }
72
+
73
+ // Play/Pause button
74
+ let playPauseIconName = isPlaying ? "pause.circle.fill" : "play.circle.fill"
75
+ let playPauseColor: UIColor = isPlaying ? .systemOrange : .systemGreen
76
+ let playPauseImage = UIImage(systemName: playPauseIconName)?
77
+ .withTintColor(playPauseColor, renderingMode: .alwaysOriginal)
78
+ .resized(to: buttonSize)
79
+ let playPauseButton = CPMapButton(image: playPauseImage ?? UIImage()) { [weak owner] _ in
80
+ guard let owner = owner else { return }
81
+ DispatchQueue.main.async {
82
+ if owner.config.isPlaying {
83
+ owner.pauseAudio()
84
+ owner.config.onPause?()
85
+ } else {
86
+ owner.resumeAudio()
87
+ owner.config.onPlay?()
88
+ }
89
+ }
90
+ }
91
+
92
+ // Next track button
93
+ let nextImage = UIImage(systemName: "forward.end.fill")?
94
+ .withTintColor(.systemBlue, renderingMode: .alwaysOriginal)
95
+ .resized(to: buttonSize)
96
+ let nextButton = CPMapButton(image: nextImage ?? UIImage()) { [weak owner] _ in
97
+ owner?.config.onNextTrack?()
98
+ }
99
+
100
+ return [prevButton, playPauseButton, nextButton]
101
+ }
102
+
103
+ private func updateMapButtons() {
104
+ template.mapButtons = Self.buildMapButtons(isPlaying: config.isPlaying, owner: self)
105
+ }
106
+
60
107
  // MARK: - Custom View Injection
61
108
 
62
109
  @MainActor
@@ -69,17 +116,12 @@ class NowPlayingTemplate: NSObject, AutoPlayTemplate, CPMapTemplateDelegate, Now
69
116
  previousRootVC = window.rootViewController
70
117
 
71
118
  let customVC = NeoSkolaNowPlayingViewController()
72
- customVC.delegate = self
73
119
 
74
120
  let titleText = Parser.parseText(text: config.title) ?? "Now Playing"
75
121
  let subtitleText = config.subtitle.flatMap { Parser.parseText(text: $0) } ?? ""
76
122
  customVC.updateInfo(courseName: subtitleText, lessonName: titleText)
77
123
  customVC.updatePlaybackState(isPlaying: config.isPlaying)
78
124
 
79
- if let loadedImage = loadedImage {
80
- customVC.updateArtwork(image: loadedImage)
81
- }
82
-
83
125
  window.rootViewController = customVC
84
126
  window.makeKeyAndVisible()
85
127
 
@@ -99,29 +141,6 @@ class NowPlayingTemplate: NSObject, AutoPlayTemplate, CPMapTemplateDelegate, Now
99
141
  self.customViewController = nil
100
142
  }
101
143
 
102
- // MARK: - NowPlayingViewDelegate
103
-
104
- func didTapPlayPause() {
105
- DispatchQueue.main.async { [weak self] in
106
- guard let self = self else { return }
107
- if self.config.isPlaying {
108
- self.pauseAudio()
109
- self.config.onPause?()
110
- } else {
111
- self.resumeAudio()
112
- self.config.onPlay?()
113
- }
114
- }
115
- }
116
-
117
- func didTapPrevious() {
118
- config.onPreviousTrack?()
119
- }
120
-
121
- func didTapNext() {
122
- config.onNextTrack?()
123
- }
124
-
125
144
  // MARK: - Player UI
126
145
 
127
146
  private func updatePlayerUI() {
@@ -132,6 +151,7 @@ class NowPlayingTemplate: NSObject, AutoPlayTemplate, CPMapTemplateDelegate, Now
132
151
  customVC.updateInfo(courseName: subtitleText, lessonName: titleText)
133
152
  customVC.updatePlaybackState(isPlaying: config.isPlaying)
134
153
  customVC.updateTime(elapsed: currentElapsedTime, duration: currentDuration)
154
+ updateMapButtons()
135
155
  }
136
156
 
137
157
  // MARK: - Native Audio Playback
@@ -480,7 +500,6 @@ class NowPlayingTemplate: NSObject, AutoPlayTemplate, CPMapTemplateDelegate, Now
480
500
  DispatchQueue.main.async {
481
501
  self.loadedImage = uiImage
482
502
  self.updateNowPlayingInfo()
483
- self.customViewController?.updateArtwork(image: uiImage)
484
503
  }
485
504
  }.resume()
486
505
  }
@@ -1,35 +1,17 @@
1
1
  import UIKit
2
2
 
3
- protocol NowPlayingViewDelegate: AnyObject {
4
- func didTapPlayPause()
5
- func didTapPrevious()
6
- func didTapNext()
7
- }
8
-
9
3
  class NeoSkolaNowPlayingViewController: UIViewController {
10
- weak var delegate: NowPlayingViewDelegate?
11
4
 
12
- // MARK: - UI Elements
13
- // Left side: artwork + info
14
- private let leftColumn = UIView()
15
- private let artworkImageView = UIImageView()
5
+ // MARK: - UI Elements (info-only, no buttons)
6
+ private let containerStack = UIStackView()
16
7
  private let courseNameLabel = UILabel()
17
8
  private let lessonNameLabel = UILabel()
18
-
19
- // Right side: controls + progress
20
- private let rightColumn = UIView()
21
9
  private let statusLabel = UILabel()
22
- private let controlsStack = UIStackView()
23
- private let prevButton = UIButton(type: .system)
24
- private let playPauseButton = UIButton(type: .system)
25
- private let nextButton = UIButton(type: .system)
26
10
  private let progressView = UIProgressView(progressViewStyle: .default)
27
11
  private let currentTimeLabel = UILabel()
28
12
  private let durationLabel = UILabel()
29
13
  private let timeStack = UIStackView()
30
14
 
31
- private var isPlaying = false
32
-
33
15
  // MARK: - Lifecycle
34
16
 
35
17
  override func viewDidLoad() {
@@ -42,171 +24,86 @@ class NeoSkolaNowPlayingViewController: UIViewController {
42
24
  // MARK: - Setup
43
25
 
44
26
  private func setupUI() {
45
- // Left Column
46
- leftColumn.translatesAutoresizingMaskIntoConstraints = false
47
- view.addSubview(leftColumn)
48
-
49
- // Artwork
50
- artworkImageView.contentMode = .scaleAspectFill
51
- artworkImageView.clipsToBounds = true
52
- artworkImageView.layer.cornerRadius = 10
53
- artworkImageView.backgroundColor = UIColor(white: 0.15, alpha: 1.0)
54
- artworkImageView.translatesAutoresizingMaskIntoConstraints = false
55
- leftColumn.addSubview(artworkImageView)
56
-
57
- let placeholderConfig = UIImage.SymbolConfiguration(pointSize: 28, weight: .light)
58
- artworkImageView.image = UIImage(systemName: "music.note", withConfiguration: placeholderConfig)
59
- artworkImageView.tintColor = UIColor(white: 0.3, alpha: 1.0)
60
-
61
- // Course Name
62
- courseNameLabel.font = .systemFont(ofSize: 11, weight: .medium)
63
- courseNameLabel.textColor = UIColor(white: 0.5, alpha: 1.0)
27
+ // Course Name (subtitle - kurs adi)
28
+ courseNameLabel.font = .systemFont(ofSize: 13, weight: .medium)
29
+ courseNameLabel.textColor = UIColor(white: 0.55, alpha: 1.0)
64
30
  courseNameLabel.textAlignment = .center
65
31
  courseNameLabel.numberOfLines = 1
66
32
  courseNameLabel.adjustsFontSizeToFitWidth = true
67
- courseNameLabel.minimumScaleFactor = 0.8
68
- courseNameLabel.translatesAutoresizingMaskIntoConstraints = false
69
- leftColumn.addSubview(courseNameLabel)
33
+ courseNameLabel.minimumScaleFactor = 0.7
70
34
 
71
- // Lesson Name
72
- lessonNameLabel.font = .systemFont(ofSize: 13, weight: .bold)
35
+ // Lesson Name (title - ders adi, bold)
36
+ lessonNameLabel.font = .systemFont(ofSize: 17, weight: .bold)
73
37
  lessonNameLabel.textColor = .white
74
38
  lessonNameLabel.textAlignment = .center
75
39
  lessonNameLabel.numberOfLines = 2
76
40
  lessonNameLabel.adjustsFontSizeToFitWidth = true
77
- lessonNameLabel.minimumScaleFactor = 0.7
78
- lessonNameLabel.translatesAutoresizingMaskIntoConstraints = false
79
- leftColumn.addSubview(lessonNameLabel)
80
-
81
- // Right Column
82
- rightColumn.translatesAutoresizingMaskIntoConstraints = false
83
- view.addSubview(rightColumn)
41
+ lessonNameLabel.minimumScaleFactor = 0.6
84
42
 
85
- // Status Label
86
- statusLabel.font = .systemFont(ofSize: 10, weight: .semibold)
43
+ // Status Label ("OYNATILIYOR" / "DURAKLATILDI")
44
+ statusLabel.font = .systemFont(ofSize: 11, weight: .semibold)
87
45
  statusLabel.textColor = UIColor.systemGreen
88
46
  statusLabel.textAlignment = .center
89
47
  statusLabel.text = "OYNATILIYOR"
90
- statusLabel.translatesAutoresizingMaskIntoConstraints = false
91
- rightColumn.addSubview(statusLabel)
92
-
93
- // Controls
94
- setupControls()
95
48
 
96
49
  // Progress Bar
97
50
  progressView.progressTintColor = UIColor.systemGreen
98
51
  progressView.trackTintColor = UIColor(white: 0.2, alpha: 1.0)
99
52
  progressView.progress = 0
100
- progressView.translatesAutoresizingMaskIntoConstraints = false
101
- rightColumn.addSubview(progressView)
102
53
 
103
54
  // Time Labels
104
- currentTimeLabel.font = .monospacedDigitSystemFont(ofSize: 10, weight: .medium)
105
- currentTimeLabel.textColor = UIColor(white: 0.45, alpha: 1.0)
55
+ currentTimeLabel.font = .monospacedDigitSystemFont(ofSize: 11, weight: .medium)
56
+ currentTimeLabel.textColor = UIColor(white: 0.5, alpha: 1.0)
106
57
  currentTimeLabel.text = "0:00"
107
- currentTimeLabel.translatesAutoresizingMaskIntoConstraints = false
108
58
 
109
- durationLabel.font = .monospacedDigitSystemFont(ofSize: 10, weight: .medium)
110
- durationLabel.textColor = UIColor(white: 0.45, alpha: 1.0)
59
+ durationLabel.font = .monospacedDigitSystemFont(ofSize: 11, weight: .medium)
60
+ durationLabel.textColor = UIColor(white: 0.5, alpha: 1.0)
111
61
  durationLabel.text = "--:--"
112
62
  durationLabel.textAlignment = .right
113
- durationLabel.translatesAutoresizingMaskIntoConstraints = false
114
63
 
115
64
  timeStack.axis = .horizontal
116
65
  timeStack.distribution = .equalSpacing
117
66
  timeStack.addArrangedSubview(currentTimeLabel)
118
67
  timeStack.addArrangedSubview(durationLabel)
119
- timeStack.translatesAutoresizingMaskIntoConstraints = false
120
- rightColumn.addSubview(timeStack)
121
- }
122
68
 
123
- private func setupControls() {
124
- let prevConfig = UIImage.SymbolConfiguration(pointSize: 20, weight: .medium)
125
- prevButton.setImage(UIImage(systemName: "backward.end.fill", withConfiguration: prevConfig), for: .normal)
126
- prevButton.tintColor = .white
127
- prevButton.addTarget(self, action: #selector(prevTapped), for: .touchUpInside)
128
- prevButton.translatesAutoresizingMaskIntoConstraints = false
69
+ // Main container stack (vertical)
70
+ containerStack.axis = .vertical
71
+ containerStack.alignment = .fill
72
+ containerStack.spacing = 4
73
+ containerStack.translatesAutoresizingMaskIntoConstraints = false
74
+
75
+ containerStack.addArrangedSubview(courseNameLabel)
76
+ containerStack.addArrangedSubview(lessonNameLabel)
77
+
78
+ // Spacer between lesson name and status
79
+ let spacer = UIView()
80
+ spacer.translatesAutoresizingMaskIntoConstraints = false
81
+ spacer.heightAnchor.constraint(equalToConstant: 6).isActive = true
82
+ containerStack.addArrangedSubview(spacer)
129
83
 
130
- let playConfig = UIImage.SymbolConfiguration(pointSize: 36, weight: .medium)
131
- playPauseButton.setImage(UIImage(systemName: "play.circle.fill", withConfiguration: playConfig), for: .normal)
132
- playPauseButton.tintColor = UIColor.systemGreen
133
- playPauseButton.addTarget(self, action: #selector(playPauseTapped), for: .touchUpInside)
134
- playPauseButton.translatesAutoresizingMaskIntoConstraints = false
84
+ containerStack.addArrangedSubview(statusLabel)
135
85
 
136
- let nextConfig = UIImage.SymbolConfiguration(pointSize: 20, weight: .medium)
137
- nextButton.setImage(UIImage(systemName: "forward.end.fill", withConfiguration: nextConfig), for: .normal)
138
- nextButton.tintColor = .white
139
- nextButton.addTarget(self, action: #selector(nextTapped), for: .touchUpInside)
140
- nextButton.translatesAutoresizingMaskIntoConstraints = false
86
+ // Spacer between status and progress
87
+ let spacer2 = UIView()
88
+ spacer2.translatesAutoresizingMaskIntoConstraints = false
89
+ spacer2.heightAnchor.constraint(equalToConstant: 8).isActive = true
90
+ containerStack.addArrangedSubview(spacer2)
141
91
 
142
- controlsStack.axis = .horizontal
143
- controlsStack.alignment = .center
144
- controlsStack.distribution = .equalCentering
145
- controlsStack.spacing = 28
146
- controlsStack.addArrangedSubview(prevButton)
147
- controlsStack.addArrangedSubview(playPauseButton)
148
- controlsStack.addArrangedSubview(nextButton)
149
- controlsStack.translatesAutoresizingMaskIntoConstraints = false
150
- rightColumn.addSubview(controlsStack)
92
+ containerStack.addArrangedSubview(progressView)
93
+ containerStack.addArrangedSubview(timeStack)
94
+
95
+ view.addSubview(containerStack)
151
96
  }
152
97
 
153
98
  private func setupConstraints() {
154
99
  let safeArea = view.safeAreaLayoutGuide
155
100
 
156
101
  NSLayoutConstraint.activate([
157
- // Left Column — sol taraf, artwork + text
158
- leftColumn.topAnchor.constraint(equalTo: safeArea.topAnchor, constant: 8),
159
- leftColumn.leadingAnchor.constraint(equalTo: safeArea.leadingAnchor, constant: 16),
160
- leftColumn.bottomAnchor.constraint(lessThanOrEqualTo: safeArea.bottomAnchor, constant: -8),
161
- leftColumn.widthAnchor.constraint(equalTo: safeArea.widthAnchor, multiplier: 0.35),
162
-
163
- // Artwork — sol column ustunde, kare
164
- artworkImageView.topAnchor.constraint(equalTo: leftColumn.topAnchor),
165
- artworkImageView.centerXAnchor.constraint(equalTo: leftColumn.centerXAnchor),
166
- artworkImageView.widthAnchor.constraint(equalTo: leftColumn.widthAnchor, constant: -8),
167
- artworkImageView.heightAnchor.constraint(equalTo: artworkImageView.widthAnchor),
168
-
169
- // Course Name — artwork altinda
170
- courseNameLabel.topAnchor.constraint(equalTo: artworkImageView.bottomAnchor, constant: 6),
171
- courseNameLabel.leadingAnchor.constraint(equalTo: leftColumn.leadingAnchor),
172
- courseNameLabel.trailingAnchor.constraint(equalTo: leftColumn.trailingAnchor),
173
-
174
- // Lesson Name — course altinda
175
- lessonNameLabel.topAnchor.constraint(equalTo: courseNameLabel.bottomAnchor, constant: 2),
176
- lessonNameLabel.leadingAnchor.constraint(equalTo: leftColumn.leadingAnchor),
177
- lessonNameLabel.trailingAnchor.constraint(equalTo: leftColumn.trailingAnchor),
102
+ containerStack.centerYAnchor.constraint(equalTo: safeArea.centerYAnchor),
103
+ containerStack.leadingAnchor.constraint(equalTo: safeArea.leadingAnchor, constant: 24),
104
+ containerStack.trailingAnchor.constraint(equalTo: safeArea.trailingAnchor, constant: -24),
178
105
 
179
- // Right Column — sag taraf, kontroller + progress
180
- rightColumn.topAnchor.constraint(equalTo: safeArea.topAnchor, constant: 8),
181
- rightColumn.leadingAnchor.constraint(equalTo: leftColumn.trailingAnchor, constant: 12),
182
- rightColumn.trailingAnchor.constraint(equalTo: safeArea.trailingAnchor, constant: -16),
183
- rightColumn.bottomAnchor.constraint(lessThanOrEqualTo: safeArea.bottomAnchor, constant: -8),
184
-
185
- // Status — sag column ust
186
- statusLabel.topAnchor.constraint(equalTo: rightColumn.topAnchor, constant: 8),
187
- statusLabel.centerXAnchor.constraint(equalTo: rightColumn.centerXAnchor),
188
-
189
- // Controls — dikey merkez
190
- controlsStack.centerYAnchor.constraint(equalTo: rightColumn.centerYAnchor, constant: -4),
191
- controlsStack.centerXAnchor.constraint(equalTo: rightColumn.centerXAnchor),
192
-
193
- prevButton.widthAnchor.constraint(greaterThanOrEqualToConstant: 40),
194
- prevButton.heightAnchor.constraint(greaterThanOrEqualToConstant: 40),
195
- playPauseButton.widthAnchor.constraint(greaterThanOrEqualToConstant: 48),
196
- playPauseButton.heightAnchor.constraint(greaterThanOrEqualToConstant: 48),
197
- nextButton.widthAnchor.constraint(greaterThanOrEqualToConstant: 40),
198
- nextButton.heightAnchor.constraint(greaterThanOrEqualToConstant: 40),
199
-
200
- // Progress Bar — controls altinda
201
- progressView.topAnchor.constraint(equalTo: controlsStack.bottomAnchor, constant: 10),
202
- progressView.leadingAnchor.constraint(equalTo: rightColumn.leadingAnchor),
203
- progressView.trailingAnchor.constraint(equalTo: rightColumn.trailingAnchor),
204
106
  progressView.heightAnchor.constraint(equalToConstant: 3),
205
-
206
- // Time Stack — progress altinda
207
- timeStack.topAnchor.constraint(equalTo: progressView.bottomAnchor, constant: 3),
208
- timeStack.leadingAnchor.constraint(equalTo: rightColumn.leadingAnchor),
209
- timeStack.trailingAnchor.constraint(equalTo: rightColumn.trailingAnchor),
210
107
  ])
211
108
  }
212
109
 
@@ -218,13 +115,6 @@ class NeoSkolaNowPlayingViewController: UIViewController {
218
115
  }
219
116
 
220
117
  func updatePlaybackState(isPlaying: Bool) {
221
- self.isPlaying = isPlaying
222
-
223
- let config = UIImage.SymbolConfiguration(pointSize: 36, weight: .medium)
224
- let iconName = isPlaying ? "pause.circle.fill" : "play.circle.fill"
225
- playPauseButton.setImage(UIImage(systemName: iconName, withConfiguration: config), for: .normal)
226
- playPauseButton.tintColor = isPlaying ? UIColor.systemOrange : UIColor.systemGreen
227
-
228
118
  statusLabel.text = isPlaying ? "OYNATILIYOR" : "DURAKLATILDI"
229
119
  statusLabel.textColor = isPlaying ? UIColor.systemGreen : UIColor.systemGray
230
120
  }
@@ -239,11 +129,6 @@ class NeoSkolaNowPlayingViewController: UIViewController {
239
129
  }
240
130
  }
241
131
 
242
- func updateArtwork(image: UIImage) {
243
- artworkImageView.image = image
244
- artworkImageView.contentMode = .scaleAspectFill
245
- }
246
-
247
132
  // MARK: - Helpers
248
133
 
249
134
  private func formatTime(_ seconds: Double) -> String {
@@ -252,18 +137,4 @@ class NeoSkolaNowPlayingViewController: UIViewController {
252
137
  let secs = Int(seconds) % 60
253
138
  return String(format: "%d:%02d", mins, secs)
254
139
  }
255
-
256
- // MARK: - Actions
257
-
258
- @objc private func prevTapped() {
259
- delegate?.didTapPrevious()
260
- }
261
-
262
- @objc private func playPauseTapped() {
263
- delegate?.didTapPlayPause()
264
- }
265
-
266
- @objc private func nextTapped() {
267
- delegate?.didTapNext()
268
- }
269
140
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@neoskola/auto-play",
3
- "version": "0.3.10",
3
+ "version": "0.3.11",
4
4
  "description": "Android Auto and Apple CarPlay for react-native",
5
5
  "main": "lib/index",
6
6
  "module": "lib/index",