@neoskola/auto-play 0.3.9 → 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,32 +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
- private let containerView = UIView()
5
+ // MARK: - UI Elements (info-only, no buttons)
6
+ private let containerStack = UIStackView()
14
7
  private let courseNameLabel = UILabel()
15
8
  private let lessonNameLabel = UILabel()
16
- private let artworkImageView = UIImageView()
17
- private let artworkContainer = UIView()
18
9
  private let statusLabel = UILabel()
19
- private let controlsStack = UIStackView()
20
- private let prevButton = UIButton(type: .system)
21
- private let playPauseButton = UIButton(type: .system)
22
- private let nextButton = UIButton(type: .system)
23
10
  private let progressView = UIProgressView(progressViewStyle: .default)
24
11
  private let currentTimeLabel = UILabel()
25
12
  private let durationLabel = UILabel()
26
13
  private let timeStack = UIStackView()
27
14
 
28
- private var isPlaying = false
29
-
30
15
  // MARK: - Lifecycle
31
16
 
32
17
  override func viewDidLoad() {
@@ -39,180 +24,86 @@ class NeoSkolaNowPlayingViewController: UIViewController {
39
24
  // MARK: - Setup
40
25
 
41
26
  private func setupUI() {
42
- containerView.translatesAutoresizingMaskIntoConstraints = false
43
- view.addSubview(containerView)
44
-
45
- // Course Name (kucuk, gri)
27
+ // Course Name (subtitle - kurs adi)
46
28
  courseNameLabel.font = .systemFont(ofSize: 13, weight: .medium)
47
29
  courseNameLabel.textColor = UIColor(white: 0.55, alpha: 1.0)
48
30
  courseNameLabel.textAlignment = .center
49
31
  courseNameLabel.numberOfLines = 1
50
- courseNameLabel.translatesAutoresizingMaskIntoConstraints = false
51
- containerView.addSubview(courseNameLabel)
32
+ courseNameLabel.adjustsFontSizeToFitWidth = true
33
+ courseNameLabel.minimumScaleFactor = 0.7
52
34
 
53
- // Lesson Name (buyuk, beyaz, bold)
54
- lessonNameLabel.font = .systemFont(ofSize: 19, weight: .bold)
35
+ // Lesson Name (title - ders adi, bold)
36
+ lessonNameLabel.font = .systemFont(ofSize: 17, weight: .bold)
55
37
  lessonNameLabel.textColor = .white
56
38
  lessonNameLabel.textAlignment = .center
57
39
  lessonNameLabel.numberOfLines = 2
58
40
  lessonNameLabel.adjustsFontSizeToFitWidth = true
59
- lessonNameLabel.minimumScaleFactor = 0.7
60
- lessonNameLabel.translatesAutoresizingMaskIntoConstraints = false
61
- containerView.addSubview(lessonNameLabel)
41
+ lessonNameLabel.minimumScaleFactor = 0.6
62
42
 
63
- // Artwork Container (golge ve yuvarlak koseler)
64
- artworkContainer.translatesAutoresizingMaskIntoConstraints = false
65
- artworkContainer.layer.cornerRadius = 14
66
- artworkContainer.layer.shadowColor = UIColor.black.cgColor
67
- artworkContainer.layer.shadowOpacity = 0.5
68
- artworkContainer.layer.shadowOffset = CGSize(width: 0, height: 4)
69
- artworkContainer.layer.shadowRadius = 12
70
- containerView.addSubview(artworkContainer)
71
-
72
- // Artwork Image
73
- artworkImageView.contentMode = .scaleAspectFill
74
- artworkImageView.clipsToBounds = true
75
- artworkImageView.layer.cornerRadius = 14
76
- artworkImageView.backgroundColor = UIColor(white: 0.15, alpha: 1.0)
77
- artworkImageView.translatesAutoresizingMaskIntoConstraints = false
78
- artworkContainer.addSubview(artworkImageView)
79
-
80
- // Placeholder icon
81
- let placeholderConfig = UIImage.SymbolConfiguration(pointSize: 32, weight: .light)
82
- artworkImageView.image = UIImage(systemName: "music.note", withConfiguration: placeholderConfig)
83
- artworkImageView.tintColor = UIColor(white: 0.3, alpha: 1.0)
84
-
85
- // Status Label
43
+ // Status Label ("OYNATILIYOR" / "DURAKLATILDI")
86
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
- containerView.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
- containerView.addSubview(progressView)
102
53
 
103
54
  // Time Labels
104
55
  currentTimeLabel.font = .monospacedDigitSystemFont(ofSize: 11, weight: .medium)
105
- currentTimeLabel.textColor = UIColor(white: 0.45, alpha: 1.0)
56
+ currentTimeLabel.textColor = UIColor(white: 0.5, alpha: 1.0)
106
57
  currentTimeLabel.text = "0:00"
107
- currentTimeLabel.translatesAutoresizingMaskIntoConstraints = false
108
58
 
109
59
  durationLabel.font = .monospacedDigitSystemFont(ofSize: 11, weight: .medium)
110
- durationLabel.textColor = UIColor(white: 0.45, alpha: 1.0)
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
- containerView.addSubview(timeStack)
121
- }
122
68
 
123
- private func setupControls() {
124
- // Previous Button
125
- let prevConfig = UIImage.SymbolConfiguration(pointSize: 24, weight: .medium)
126
- prevButton.setImage(UIImage(systemName: "backward.end.fill", withConfiguration: prevConfig), for: .normal)
127
- prevButton.tintColor = .white
128
- prevButton.addTarget(self, action: #selector(prevTapped), for: .touchUpInside)
129
- 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)
83
+
84
+ containerStack.addArrangedSubview(statusLabel)
130
85
 
131
- // Play/Pause Button (buyuk)
132
- let playConfig = UIImage.SymbolConfiguration(pointSize: 42, weight: .medium)
133
- playPauseButton.setImage(UIImage(systemName: "play.circle.fill", withConfiguration: playConfig), for: .normal)
134
- playPauseButton.tintColor = UIColor.systemGreen
135
- playPauseButton.addTarget(self, action: #selector(playPauseTapped), for: .touchUpInside)
136
- playPauseButton.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)
137
91
 
138
- // Next Button
139
- let nextConfig = UIImage.SymbolConfiguration(pointSize: 24, weight: .medium)
140
- nextButton.setImage(UIImage(systemName: "forward.end.fill", withConfiguration: nextConfig), for: .normal)
141
- nextButton.tintColor = .white
142
- nextButton.addTarget(self, action: #selector(nextTapped), for: .touchUpInside)
143
- nextButton.translatesAutoresizingMaskIntoConstraints = false
92
+ containerStack.addArrangedSubview(progressView)
93
+ containerStack.addArrangedSubview(timeStack)
144
94
 
145
- // Stack
146
- controlsStack.axis = .horizontal
147
- controlsStack.alignment = .center
148
- controlsStack.distribution = .equalCentering
149
- controlsStack.spacing = 40
150
- controlsStack.addArrangedSubview(prevButton)
151
- controlsStack.addArrangedSubview(playPauseButton)
152
- controlsStack.addArrangedSubview(nextButton)
153
- controlsStack.translatesAutoresizingMaskIntoConstraints = false
154
- containerView.addSubview(controlsStack)
95
+ view.addSubview(containerStack)
155
96
  }
156
97
 
157
98
  private func setupConstraints() {
158
99
  let safeArea = view.safeAreaLayoutGuide
159
100
 
160
101
  NSLayoutConstraint.activate([
161
- // Container — tam ekran, safe area icinde
162
- containerView.topAnchor.constraint(equalTo: safeArea.topAnchor, constant: 8),
163
- containerView.leadingAnchor.constraint(equalTo: safeArea.leadingAnchor, constant: 20),
164
- containerView.trailingAnchor.constraint(equalTo: safeArea.trailingAnchor, constant: -20),
165
- containerView.bottomAnchor.constraint(equalTo: safeArea.bottomAnchor, constant: -8),
166
-
167
- // Course Name — top
168
- courseNameLabel.topAnchor.constraint(equalTo: containerView.topAnchor, constant: 4),
169
- courseNameLabel.leadingAnchor.constraint(equalTo: containerView.leadingAnchor),
170
- courseNameLabel.trailingAnchor.constraint(equalTo: containerView.trailingAnchor),
171
-
172
- // Lesson Name — course name altinda
173
- lessonNameLabel.topAnchor.constraint(equalTo: courseNameLabel.bottomAnchor, constant: 4),
174
- lessonNameLabel.leadingAnchor.constraint(equalTo: containerView.leadingAnchor),
175
- lessonNameLabel.trailingAnchor.constraint(equalTo: containerView.trailingAnchor),
176
-
177
- // Artwork Container — merkez
178
- artworkContainer.topAnchor.constraint(equalTo: lessonNameLabel.bottomAnchor, constant: 12),
179
- artworkContainer.centerXAnchor.constraint(equalTo: containerView.centerXAnchor),
180
- artworkContainer.widthAnchor.constraint(equalToConstant: 110),
181
- artworkContainer.heightAnchor.constraint(equalToConstant: 110),
182
-
183
- // Artwork Image — container'i doldur
184
- artworkImageView.topAnchor.constraint(equalTo: artworkContainer.topAnchor),
185
- artworkImageView.leadingAnchor.constraint(equalTo: artworkContainer.leadingAnchor),
186
- artworkImageView.trailingAnchor.constraint(equalTo: artworkContainer.trailingAnchor),
187
- artworkImageView.bottomAnchor.constraint(equalTo: artworkContainer.bottomAnchor),
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),
188
105
 
189
- // Status Label
190
- statusLabel.topAnchor.constraint(equalTo: artworkContainer.bottomAnchor, constant: 10),
191
- statusLabel.centerXAnchor.constraint(equalTo: containerView.centerXAnchor),
192
-
193
- // Controls Stack — status altinda
194
- controlsStack.topAnchor.constraint(equalTo: statusLabel.bottomAnchor, constant: 10),
195
- controlsStack.centerXAnchor.constraint(equalTo: containerView.centerXAnchor),
196
- controlsStack.widthAnchor.constraint(lessThanOrEqualTo: containerView.widthAnchor, multiplier: 0.7),
197
-
198
- // Button min sizes
199
- prevButton.widthAnchor.constraint(greaterThanOrEqualToConstant: 44),
200
- prevButton.heightAnchor.constraint(greaterThanOrEqualToConstant: 44),
201
- playPauseButton.widthAnchor.constraint(greaterThanOrEqualToConstant: 54),
202
- playPauseButton.heightAnchor.constraint(greaterThanOrEqualToConstant: 54),
203
- nextButton.widthAnchor.constraint(greaterThanOrEqualToConstant: 44),
204
- nextButton.heightAnchor.constraint(greaterThanOrEqualToConstant: 44),
205
-
206
- // Progress Bar
207
- progressView.topAnchor.constraint(equalTo: controlsStack.bottomAnchor, constant: 14),
208
- progressView.leadingAnchor.constraint(equalTo: containerView.leadingAnchor),
209
- progressView.trailingAnchor.constraint(equalTo: containerView.trailingAnchor),
210
106
  progressView.heightAnchor.constraint(equalToConstant: 3),
211
-
212
- // Time Stack
213
- timeStack.topAnchor.constraint(equalTo: progressView.bottomAnchor, constant: 4),
214
- timeStack.leadingAnchor.constraint(equalTo: containerView.leadingAnchor),
215
- timeStack.trailingAnchor.constraint(equalTo: containerView.trailingAnchor),
216
107
  ])
217
108
  }
218
109
 
@@ -224,13 +115,6 @@ class NeoSkolaNowPlayingViewController: UIViewController {
224
115
  }
225
116
 
226
117
  func updatePlaybackState(isPlaying: Bool) {
227
- self.isPlaying = isPlaying
228
-
229
- let config = UIImage.SymbolConfiguration(pointSize: 42, weight: .medium)
230
- let iconName = isPlaying ? "pause.circle.fill" : "play.circle.fill"
231
- playPauseButton.setImage(UIImage(systemName: iconName, withConfiguration: config), for: .normal)
232
- playPauseButton.tintColor = isPlaying ? UIColor.systemOrange : UIColor.systemGreen
233
-
234
118
  statusLabel.text = isPlaying ? "OYNATILIYOR" : "DURAKLATILDI"
235
119
  statusLabel.textColor = isPlaying ? UIColor.systemGreen : UIColor.systemGray
236
120
  }
@@ -245,11 +129,6 @@ class NeoSkolaNowPlayingViewController: UIViewController {
245
129
  }
246
130
  }
247
131
 
248
- func updateArtwork(image: UIImage) {
249
- artworkImageView.image = image
250
- artworkImageView.contentMode = .scaleAspectFill
251
- }
252
-
253
132
  // MARK: - Helpers
254
133
 
255
134
  private func formatTime(_ seconds: Double) -> String {
@@ -258,18 +137,4 @@ class NeoSkolaNowPlayingViewController: UIViewController {
258
137
  let secs = Int(seconds) % 60
259
138
  return String(format: "%d:%02d", mins, secs)
260
139
  }
261
-
262
- // MARK: - Actions
263
-
264
- @objc private func prevTapped() {
265
- delegate?.didTapPrevious()
266
- }
267
-
268
- @objc private func playPauseTapped() {
269
- delegate?.didTapPlayPause()
270
- }
271
-
272
- @objc private func nextTapped() {
273
- delegate?.didTapNext()
274
- }
275
140
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@neoskola/auto-play",
3
- "version": "0.3.9",
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",