@neoskola/auto-play 0.3.10 → 0.3.12

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?
@@ -69,15 +69,30 @@ class NowPlayingTemplate: NSObject, AutoPlayTemplate, CPMapTemplateDelegate, Now
69
69
  previousRootVC = window.rootViewController
70
70
 
71
71
  let customVC = NeoSkolaNowPlayingViewController()
72
- customVC.delegate = self
73
72
 
74
73
  let titleText = Parser.parseText(text: config.title) ?? "Now Playing"
75
74
  let subtitleText = config.subtitle.flatMap { Parser.parseText(text: $0) } ?? ""
76
75
  customVC.updateInfo(courseName: subtitleText, lessonName: titleText)
77
76
  customVC.updatePlaybackState(isPlaying: config.isPlaying)
78
77
 
79
- if let loadedImage = loadedImage {
80
- customVC.updateArtwork(image: loadedImage)
78
+ // Wire button callbacks
79
+ customVC.onPreviousTrack = { [weak self] in
80
+ self?.config.onPreviousTrack?()
81
+ }
82
+ customVC.onPlayPause = { [weak self] in
83
+ guard let self = self else { return }
84
+ DispatchQueue.main.async {
85
+ if self.config.isPlaying {
86
+ self.pauseAudio()
87
+ self.config.onPause?()
88
+ } else {
89
+ self.resumeAudio()
90
+ self.config.onPlay?()
91
+ }
92
+ }
93
+ }
94
+ customVC.onNextTrack = { [weak self] in
95
+ self?.config.onNextTrack?()
81
96
  }
82
97
 
83
98
  window.rootViewController = customVC
@@ -99,29 +114,6 @@ class NowPlayingTemplate: NSObject, AutoPlayTemplate, CPMapTemplateDelegate, Now
99
114
  self.customViewController = nil
100
115
  }
101
116
 
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
117
  // MARK: - Player UI
126
118
 
127
119
  private func updatePlayerUI() {
@@ -480,7 +472,6 @@ class NowPlayingTemplate: NSObject, AutoPlayTemplate, CPMapTemplateDelegate, Now
480
472
  DispatchQueue.main.async {
481
473
  self.loadedImage = uiImage
482
474
  self.updateNowPlayingInfo()
483
- self.customViewController?.updateArtwork(image: uiImage)
484
475
  }
485
476
  }.resume()
486
477
  }
@@ -1,34 +1,27 @@
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
5
  // MARK: - UI Elements
13
- // Left side: artwork + info
14
- private let leftColumn = UIView()
15
- private let artworkImageView = UIImageView()
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
15
+ // MARK: - Button Controls
16
+ private let buttonStack = UIStackView()
17
+ private let previousButton = UIButton(type: .system)
18
+ private let playPauseButton = UIButton(type: .system)
19
+ private let nextButton = UIButton(type: .system)
20
+
21
+ // MARK: - Callbacks
22
+ var onPreviousTrack: (() -> Void)?
23
+ var onPlayPause: (() -> Void)?
24
+ var onNextTrack: (() -> Void)?
32
25
 
33
26
  // MARK: - Lifecycle
34
27
 
@@ -42,171 +35,130 @@ class NeoSkolaNowPlayingViewController: UIViewController {
42
35
  // MARK: - Setup
43
36
 
44
37
  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)
38
+ // Course Name (subtitle - kurs adi)
39
+ courseNameLabel.font = .systemFont(ofSize: 13, weight: .medium)
40
+ courseNameLabel.textColor = UIColor(white: 0.55, alpha: 1.0)
64
41
  courseNameLabel.textAlignment = .center
65
42
  courseNameLabel.numberOfLines = 1
66
43
  courseNameLabel.adjustsFontSizeToFitWidth = true
67
- courseNameLabel.minimumScaleFactor = 0.8
68
- courseNameLabel.translatesAutoresizingMaskIntoConstraints = false
69
- leftColumn.addSubview(courseNameLabel)
44
+ courseNameLabel.minimumScaleFactor = 0.7
70
45
 
71
- // Lesson Name
72
- lessonNameLabel.font = .systemFont(ofSize: 13, weight: .bold)
46
+ // Lesson Name (title - ders adi, bold)
47
+ lessonNameLabel.font = .systemFont(ofSize: 17, weight: .bold)
73
48
  lessonNameLabel.textColor = .white
74
49
  lessonNameLabel.textAlignment = .center
75
50
  lessonNameLabel.numberOfLines = 2
76
51
  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)
52
+ lessonNameLabel.minimumScaleFactor = 0.6
84
53
 
85
- // Status Label
86
- statusLabel.font = .systemFont(ofSize: 10, weight: .semibold)
54
+ // Status Label ("OYNATILIYOR" / "DURAKLATILDI")
55
+ statusLabel.font = .systemFont(ofSize: 11, weight: .semibold)
87
56
  statusLabel.textColor = UIColor.systemGreen
88
57
  statusLabel.textAlignment = .center
89
58
  statusLabel.text = "OYNATILIYOR"
90
- statusLabel.translatesAutoresizingMaskIntoConstraints = false
91
- rightColumn.addSubview(statusLabel)
92
-
93
- // Controls
94
- setupControls()
95
59
 
96
60
  // Progress Bar
97
61
  progressView.progressTintColor = UIColor.systemGreen
98
62
  progressView.trackTintColor = UIColor(white: 0.2, alpha: 1.0)
99
63
  progressView.progress = 0
100
- progressView.translatesAutoresizingMaskIntoConstraints = false
101
- rightColumn.addSubview(progressView)
102
64
 
103
65
  // Time Labels
104
- currentTimeLabel.font = .monospacedDigitSystemFont(ofSize: 10, weight: .medium)
105
- currentTimeLabel.textColor = UIColor(white: 0.45, alpha: 1.0)
66
+ currentTimeLabel.font = .monospacedDigitSystemFont(ofSize: 11, weight: .medium)
67
+ currentTimeLabel.textColor = UIColor(white: 0.5, alpha: 1.0)
106
68
  currentTimeLabel.text = "0:00"
107
- currentTimeLabel.translatesAutoresizingMaskIntoConstraints = false
108
69
 
109
- durationLabel.font = .monospacedDigitSystemFont(ofSize: 10, weight: .medium)
110
- durationLabel.textColor = UIColor(white: 0.45, alpha: 1.0)
70
+ durationLabel.font = .monospacedDigitSystemFont(ofSize: 11, weight: .medium)
71
+ durationLabel.textColor = UIColor(white: 0.5, alpha: 1.0)
111
72
  durationLabel.text = "--:--"
112
73
  durationLabel.textAlignment = .right
113
- durationLabel.translatesAutoresizingMaskIntoConstraints = false
114
74
 
115
75
  timeStack.axis = .horizontal
116
76
  timeStack.distribution = .equalSpacing
117
77
  timeStack.addArrangedSubview(currentTimeLabel)
118
78
  timeStack.addArrangedSubview(durationLabel)
119
- timeStack.translatesAutoresizingMaskIntoConstraints = false
120
- rightColumn.addSubview(timeStack)
121
- }
122
79
 
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
129
-
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
80
+ // Previous Button
81
+ previousButton.setImage(
82
+ UIImage(systemName: "backward.end.fill",
83
+ withConfiguration: UIImage.SymbolConfiguration(pointSize: 28, weight: .medium))?
84
+ .withTintColor(.white, renderingMode: .alwaysOriginal),
85
+ for: .normal
86
+ )
87
+ previousButton.addTarget(self, action: #selector(previousTapped), for: .touchUpInside)
88
+
89
+ // Play/Pause Button
90
+ playPauseButton.setImage(
91
+ UIImage(systemName: "play.circle.fill",
92
+ withConfiguration: UIImage.SymbolConfiguration(pointSize: 44, weight: .medium))?
93
+ .withTintColor(.systemGreen, renderingMode: .alwaysOriginal),
94
+ for: .normal
95
+ )
133
96
  playPauseButton.addTarget(self, action: #selector(playPauseTapped), for: .touchUpInside)
134
- playPauseButton.translatesAutoresizingMaskIntoConstraints = false
135
97
 
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
98
+ // Next Button
99
+ nextButton.setImage(
100
+ UIImage(systemName: "forward.end.fill",
101
+ withConfiguration: UIImage.SymbolConfiguration(pointSize: 28, weight: .medium))?
102
+ .withTintColor(.white, renderingMode: .alwaysOriginal),
103
+ for: .normal
104
+ )
139
105
  nextButton.addTarget(self, action: #selector(nextTapped), for: .touchUpInside)
140
- nextButton.translatesAutoresizingMaskIntoConstraints = false
141
-
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)
106
+
107
+ // Button Stack (horizontal)
108
+ buttonStack.axis = .horizontal
109
+ buttonStack.alignment = .center
110
+ buttonStack.distribution = .equalSpacing
111
+ buttonStack.spacing = 40
112
+ buttonStack.addArrangedSubview(previousButton)
113
+ buttonStack.addArrangedSubview(playPauseButton)
114
+ buttonStack.addArrangedSubview(nextButton)
115
+
116
+ // Main container stack (vertical)
117
+ containerStack.axis = .vertical
118
+ containerStack.alignment = .fill
119
+ containerStack.spacing = 4
120
+ containerStack.translatesAutoresizingMaskIntoConstraints = false
121
+
122
+ containerStack.addArrangedSubview(courseNameLabel)
123
+ containerStack.addArrangedSubview(lessonNameLabel)
124
+
125
+ // Spacer between lesson name and status
126
+ let spacer = UIView()
127
+ spacer.translatesAutoresizingMaskIntoConstraints = false
128
+ spacer.heightAnchor.constraint(equalToConstant: 6).isActive = true
129
+ containerStack.addArrangedSubview(spacer)
130
+
131
+ containerStack.addArrangedSubview(statusLabel)
132
+
133
+ // Spacer between status and progress
134
+ let spacer2 = UIView()
135
+ spacer2.translatesAutoresizingMaskIntoConstraints = false
136
+ spacer2.heightAnchor.constraint(equalToConstant: 8).isActive = true
137
+ containerStack.addArrangedSubview(spacer2)
138
+
139
+ containerStack.addArrangedSubview(progressView)
140
+ containerStack.addArrangedSubview(timeStack)
141
+
142
+ // Spacer between time and buttons
143
+ let spacer3 = UIView()
144
+ spacer3.translatesAutoresizingMaskIntoConstraints = false
145
+ spacer3.heightAnchor.constraint(equalToConstant: 16).isActive = true
146
+ containerStack.addArrangedSubview(spacer3)
147
+
148
+ containerStack.addArrangedSubview(buttonStack)
149
+
150
+ view.addSubview(containerStack)
151
151
  }
152
152
 
153
153
  private func setupConstraints() {
154
154
  let safeArea = view.safeAreaLayoutGuide
155
155
 
156
156
  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),
178
-
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
- progressView.heightAnchor.constraint(equalToConstant: 3),
157
+ containerStack.centerYAnchor.constraint(equalTo: safeArea.centerYAnchor),
158
+ containerStack.leadingAnchor.constraint(equalTo: safeArea.leadingAnchor, constant: 24),
159
+ containerStack.trailingAnchor.constraint(equalTo: safeArea.trailingAnchor, constant: -24),
205
160
 
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),
161
+ progressView.heightAnchor.constraint(equalToConstant: 3),
210
162
  ])
211
163
  }
212
164
 
@@ -218,15 +170,17 @@ class NeoSkolaNowPlayingViewController: UIViewController {
218
170
  }
219
171
 
220
172
  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
173
  statusLabel.text = isPlaying ? "OYNATILIYOR" : "DURAKLATILDI"
229
174
  statusLabel.textColor = isPlaying ? UIColor.systemGreen : UIColor.systemGray
175
+
176
+ let iconName = isPlaying ? "pause.circle.fill" : "play.circle.fill"
177
+ let color: UIColor = isPlaying ? .systemOrange : .systemGreen
178
+ playPauseButton.setImage(
179
+ UIImage(systemName: iconName,
180
+ withConfiguration: UIImage.SymbolConfiguration(pointSize: 44, weight: .medium))?
181
+ .withTintColor(color, renderingMode: .alwaysOriginal),
182
+ for: .normal
183
+ )
230
184
  }
231
185
 
232
186
  func updateTime(elapsed: Double, duration: Double) {
@@ -239,11 +193,6 @@ class NeoSkolaNowPlayingViewController: UIViewController {
239
193
  }
240
194
  }
241
195
 
242
- func updateArtwork(image: UIImage) {
243
- artworkImageView.image = image
244
- artworkImageView.contentMode = .scaleAspectFill
245
- }
246
-
247
196
  // MARK: - Helpers
248
197
 
249
198
  private func formatTime(_ seconds: Double) -> String {
@@ -253,17 +202,17 @@ class NeoSkolaNowPlayingViewController: UIViewController {
253
202
  return String(format: "%d:%02d", mins, secs)
254
203
  }
255
204
 
256
- // MARK: - Actions
205
+ // MARK: - Button Actions
257
206
 
258
- @objc private func prevTapped() {
259
- delegate?.didTapPrevious()
207
+ @objc private func previousTapped() {
208
+ onPreviousTrack?()
260
209
  }
261
210
 
262
211
  @objc private func playPauseTapped() {
263
- delegate?.didTapPlayPause()
212
+ onPlayPause?()
264
213
  }
265
214
 
266
215
  @objc private func nextTapped() {
267
- delegate?.didTapNext()
216
+ onNextTrack?()
268
217
  }
269
218
  }
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.12",
4
4
  "description": "Android Auto and Apple CarPlay for react-native",
5
5
  "main": "lib/index",
6
6
  "module": "lib/index",