first commit
This commit is contained in:
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,7 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<Workspace
|
||||||
|
version = "1.0">
|
||||||
|
<FileRef
|
||||||
|
location = "self:">
|
||||||
|
</FileRef>
|
||||||
|
</Workspace>
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||||
|
<plist version="1.0">
|
||||||
|
<dict>
|
||||||
|
<key>IDEDidComputeMac32BitWarning</key>
|
||||||
|
<true/>
|
||||||
|
</dict>
|
||||||
|
</plist>
|
||||||
BIN
Binary file not shown.
+19
@@ -0,0 +1,19 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||||
|
<plist version="1.0">
|
||||||
|
<dict>
|
||||||
|
<key>SchemeUserState</key>
|
||||||
|
<dict>
|
||||||
|
<key>music player.xcscheme_^#shared#^_</key>
|
||||||
|
<dict>
|
||||||
|
<key>orderHint</key>
|
||||||
|
<integer>1</integer>
|
||||||
|
</dict>
|
||||||
|
<key>music playerWatch Watch App.xcscheme_^#shared#^_</key>
|
||||||
|
<dict>
|
||||||
|
<key>orderHint</key>
|
||||||
|
<integer>0</integer>
|
||||||
|
</dict>
|
||||||
|
</dict>
|
||||||
|
</dict>
|
||||||
|
</plist>
|
||||||
Vendored
BIN
Binary file not shown.
@@ -0,0 +1,11 @@
|
|||||||
|
{
|
||||||
|
"colors" : [
|
||||||
|
{
|
||||||
|
"idiom" : "universal"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"info" : {
|
||||||
|
"author" : "xcode",
|
||||||
|
"version" : 1
|
||||||
|
}
|
||||||
|
}
|
||||||
Binary file not shown.
|
After Width: | Height: | Size: 290 KiB |
@@ -0,0 +1,14 @@
|
|||||||
|
{
|
||||||
|
"images" : [
|
||||||
|
{
|
||||||
|
"filename" : "AppIcon 1.jpg",
|
||||||
|
"idiom" : "universal",
|
||||||
|
"platform" : "ios",
|
||||||
|
"size" : "1024x1024"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"info" : {
|
||||||
|
"author" : "xcode",
|
||||||
|
"version" : 1
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
{
|
||||||
|
"info" : {
|
||||||
|
"author" : "xcode",
|
||||||
|
"version" : 1
|
||||||
|
}
|
||||||
|
}
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,117 @@
|
|||||||
|
import Foundation
|
||||||
|
import WatchConnectivity
|
||||||
|
import Combine
|
||||||
|
|
||||||
|
// MARK: - Лёгкая модель трека для передачи на часы
|
||||||
|
struct WatchTrackInfo: Codable {
|
||||||
|
let id: String // используем имя файла как стабильный идентификатор
|
||||||
|
let title: String
|
||||||
|
let artist: String
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - Связь iPhone <-> Apple Watch
|
||||||
|
// Добавь этот файл в таргет "music player" (основное приложение).
|
||||||
|
class PhoneConnectivityManager: NSObject, WCSessionDelegate, ObservableObject {
|
||||||
|
static let shared = PhoneConnectivityManager()
|
||||||
|
|
||||||
|
private var cancellables = Set<AnyCancellable>()
|
||||||
|
|
||||||
|
private override init() {
|
||||||
|
super.init()
|
||||||
|
activateSession()
|
||||||
|
// Откладываем обращение к MusicManager.shared на следующий "тик" —
|
||||||
|
// иначе при первом запуске получится циклическая инициализация
|
||||||
|
// двух синглтонов друг через друга и приложение упадёт.
|
||||||
|
DispatchQueue.main.async { [weak self] in
|
||||||
|
self?.observeMusicManager()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private func activateSession() {
|
||||||
|
guard WCSession.isSupported() else { return }
|
||||||
|
let session = WCSession.default
|
||||||
|
session.delegate = self
|
||||||
|
session.activate()
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - Отправка состояния плеера на часы
|
||||||
|
|
||||||
|
private func observeMusicManager() {
|
||||||
|
let manager = MusicManager.shared
|
||||||
|
Publishers.CombineLatest4(
|
||||||
|
manager.$tracks,
|
||||||
|
manager.$currentTrackIndex,
|
||||||
|
manager.$isPlaying,
|
||||||
|
manager.$currentTitle
|
||||||
|
)
|
||||||
|
.debounce(for: .milliseconds(250), scheduler: DispatchQueue.main)
|
||||||
|
.sink { [weak self] _ in
|
||||||
|
self?.pushStateToWatch()
|
||||||
|
}
|
||||||
|
.store(in: &cancellables)
|
||||||
|
}
|
||||||
|
|
||||||
|
func pushStateToWatch() {
|
||||||
|
guard WCSession.default.activationState == .activated else { return }
|
||||||
|
let manager = MusicManager.shared
|
||||||
|
|
||||||
|
let trackInfos = manager.tracks.map {
|
||||||
|
WatchTrackInfo(id: $0.url.lastPathComponent, title: $0.title, artist: $0.artist)
|
||||||
|
}
|
||||||
|
guard let tracksData = try? JSONEncoder().encode(trackInfos) else { return }
|
||||||
|
|
||||||
|
let context: [String: Any] = [
|
||||||
|
"tracks": tracksData,
|
||||||
|
"isPlaying": manager.isPlaying,
|
||||||
|
"currentTitle": manager.currentTitle,
|
||||||
|
"currentArtist": manager.currentArtist,
|
||||||
|
"currentId": manager.currentTrack?.url.lastPathComponent ?? ""
|
||||||
|
]
|
||||||
|
|
||||||
|
try? WCSession.default.updateApplicationContext(context)
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - WCSessionDelegate
|
||||||
|
|
||||||
|
func session(_ session: WCSession, activationDidCompleteWith activationState: WCSessionActivationState, error: Error?) {
|
||||||
|
DispatchQueue.main.async { self.pushStateToWatch() }
|
||||||
|
}
|
||||||
|
|
||||||
|
func sessionDidBecomeInactive(_ session: WCSession) {}
|
||||||
|
func sessionDidDeactivate(_ session: WCSession) { WCSession.default.activate() }
|
||||||
|
|
||||||
|
// Команды, пришедшие пока приложение на телефоне активно
|
||||||
|
func session(_ session: WCSession, didReceiveMessage message: [String: Any]) {
|
||||||
|
handleCommand(message)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Команды, пришедшие "в очереди" (если телефон был недоступен)
|
||||||
|
func session(_ session: WCSession, didReceiveUserInfo userInfo: [String: Any]) {
|
||||||
|
handleCommand(userInfo)
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - Обработка команд с часов
|
||||||
|
|
||||||
|
private func handleCommand(_ command: [String: Any]) {
|
||||||
|
guard let action = command["action"] as? String else { return }
|
||||||
|
DispatchQueue.main.async {
|
||||||
|
let manager = MusicManager.shared
|
||||||
|
switch action {
|
||||||
|
case "playPause":
|
||||||
|
manager.playPause()
|
||||||
|
case "next":
|
||||||
|
manager.nextTrack()
|
||||||
|
case "previous":
|
||||||
|
manager.prevTrack()
|
||||||
|
case "playTrack":
|
||||||
|
if let id = command["id"] as? String,
|
||||||
|
let index = manager.currentList.firstIndex(where: { $0.url.lastPathComponent == id }) {
|
||||||
|
manager.playTrack(at: index)
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
break
|
||||||
|
}
|
||||||
|
self.pushStateToWatch()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
BIN
Binary file not shown.
@@ -0,0 +1,6 @@
|
|||||||
|
{
|
||||||
|
"info" : {
|
||||||
|
"author" : "xcode",
|
||||||
|
"version" : 1
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||||
|
<plist version="1.0">
|
||||||
|
<dict>
|
||||||
|
<key>com.apple.security.app-sandbox</key>
|
||||||
|
<true/>
|
||||||
|
<key>com.apple.security.network.client</key>
|
||||||
|
<true/>
|
||||||
|
</dict>
|
||||||
|
</plist>
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
//
|
||||||
|
// music_playerApp.swift
|
||||||
|
// music player
|
||||||
|
//
|
||||||
|
// Created by aswer on 23.06.2026.
|
||||||
|
//
|
||||||
|
|
||||||
|
import SwiftUI
|
||||||
|
|
||||||
|
@main
|
||||||
|
struct music_playerApp: App {
|
||||||
|
var body: some Scene {
|
||||||
|
WindowGroup {
|
||||||
|
ContentView()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,36 @@
|
|||||||
|
//
|
||||||
|
// music_playerTests.swift
|
||||||
|
// music playerTests
|
||||||
|
//
|
||||||
|
// Created by aswer on 23.06.2026.
|
||||||
|
//
|
||||||
|
|
||||||
|
import XCTest
|
||||||
|
@testable import music_player
|
||||||
|
|
||||||
|
final class music_playerTests: XCTestCase {
|
||||||
|
|
||||||
|
override func setUpWithError() throws {
|
||||||
|
// Put setup code here. This method is called before the invocation of each test method in the class.
|
||||||
|
}
|
||||||
|
|
||||||
|
override func tearDownWithError() throws {
|
||||||
|
// Put teardown code here. This method is called after the invocation of each test method in the class.
|
||||||
|
}
|
||||||
|
|
||||||
|
func testExample() throws {
|
||||||
|
// This is an example of a functional test case.
|
||||||
|
// Use XCTAssert and related functions to verify your tests produce the correct results.
|
||||||
|
// Any test you write for XCTest can be annotated as throws and async.
|
||||||
|
// Mark your test throws to produce an unexpected failure when your test encounters an uncaught error.
|
||||||
|
// Mark your test async to allow awaiting for asynchronous code to complete. Check the results with assertions afterwards.
|
||||||
|
}
|
||||||
|
|
||||||
|
func testPerformanceExample() throws {
|
||||||
|
// This is an example of a performance test case.
|
||||||
|
self.measure {
|
||||||
|
// Put the code you want to measure the time of here.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,41 @@
|
|||||||
|
//
|
||||||
|
// music_playerUITests.swift
|
||||||
|
// music playerUITests
|
||||||
|
//
|
||||||
|
// Created by aswer on 23.06.2026.
|
||||||
|
//
|
||||||
|
|
||||||
|
import XCTest
|
||||||
|
|
||||||
|
final class music_playerUITests: XCTestCase {
|
||||||
|
|
||||||
|
override func setUpWithError() throws {
|
||||||
|
// Put setup code here. This method is called before the invocation of each test method in the class.
|
||||||
|
|
||||||
|
// In UI tests it is usually best to stop immediately when a failure occurs.
|
||||||
|
continueAfterFailure = false
|
||||||
|
|
||||||
|
// In UI tests it’s important to set the initial state - such as interface orientation - required for your tests before they run. The setUp method is a good place to do this.
|
||||||
|
}
|
||||||
|
|
||||||
|
override func tearDownWithError() throws {
|
||||||
|
// Put teardown code here. This method is called after the invocation of each test method in the class.
|
||||||
|
}
|
||||||
|
|
||||||
|
func testExample() throws {
|
||||||
|
// UI tests must launch the application that they test.
|
||||||
|
let app = XCUIApplication()
|
||||||
|
app.launch()
|
||||||
|
|
||||||
|
// Use XCTAssert and related functions to verify your tests produce the correct results.
|
||||||
|
}
|
||||||
|
|
||||||
|
func testLaunchPerformance() throws {
|
||||||
|
if #available(macOS 10.15, iOS 13.0, tvOS 13.0, watchOS 7.0, *) {
|
||||||
|
// This measures how long it takes to launch your application.
|
||||||
|
measure(metrics: [XCTApplicationLaunchMetric()]) {
|
||||||
|
XCUIApplication().launch()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
//
|
||||||
|
// music_playerUITestsLaunchTests.swift
|
||||||
|
// music playerUITests
|
||||||
|
//
|
||||||
|
// Created by aswer on 23.06.2026.
|
||||||
|
//
|
||||||
|
|
||||||
|
import XCTest
|
||||||
|
|
||||||
|
final class music_playerUITestsLaunchTests: XCTestCase {
|
||||||
|
|
||||||
|
override class var runsForEachTargetApplicationUIConfiguration: Bool {
|
||||||
|
true
|
||||||
|
}
|
||||||
|
|
||||||
|
override func setUpWithError() throws {
|
||||||
|
continueAfterFailure = false
|
||||||
|
}
|
||||||
|
|
||||||
|
func testLaunch() throws {
|
||||||
|
let app = XCUIApplication()
|
||||||
|
app.launch()
|
||||||
|
|
||||||
|
// Insert steps here to perform after app launch but before taking a screenshot,
|
||||||
|
// such as logging into a test account or navigating somewhere in the app
|
||||||
|
|
||||||
|
let attachment = XCTAttachment(screenshot: app.screenshot())
|
||||||
|
attachment.name = "Launch Screen"
|
||||||
|
attachment.lifetime = .keepAlways
|
||||||
|
add(attachment)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
{
|
||||||
|
"colors" : [
|
||||||
|
{
|
||||||
|
"idiom" : "universal"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"info" : {
|
||||||
|
"author" : "xcode",
|
||||||
|
"version" : 1
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
{
|
||||||
|
"images" : [
|
||||||
|
{
|
||||||
|
"idiom" : "universal",
|
||||||
|
"platform" : "watchos",
|
||||||
|
"size" : "1024x1024"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"info" : {
|
||||||
|
"author" : "xcode",
|
||||||
|
"version" : 1
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
{
|
||||||
|
"info" : {
|
||||||
|
"author" : "xcode",
|
||||||
|
"version" : 1
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,62 @@
|
|||||||
|
//
|
||||||
|
// ContentView.swift
|
||||||
|
// music playerWatch Watch App
|
||||||
|
//
|
||||||
|
// Created by aswer on 30.06.2026.
|
||||||
|
//
|
||||||
|
|
||||||
|
import SwiftUI
|
||||||
|
|
||||||
|
// MARK: - Главный экран часов
|
||||||
|
// Замени этим содержимым ContentView.swift, который Xcode сгенерировал
|
||||||
|
// в таргете часов (структура должна остаться по имени ContentView,
|
||||||
|
// чтобы App.swift на часах продолжил работать без изменений).
|
||||||
|
struct ContentView: View {
|
||||||
|
@StateObject private var connectivity = WatchConnectivityManager.shared
|
||||||
|
|
||||||
|
var body: some View {
|
||||||
|
NavigationView {
|
||||||
|
List {
|
||||||
|
if !connectivity.currentTitle.isEmpty {
|
||||||
|
NavigationLink(destination: NowPlayingView()) {
|
||||||
|
HStack {
|
||||||
|
Image(systemName: connectivity.isPlaying ? "pause.circle.fill" : "play.circle.fill")
|
||||||
|
.foregroundColor(.green)
|
||||||
|
VStack(alignment: .leading) {
|
||||||
|
Text(connectivity.currentTitle)
|
||||||
|
.font(.headline)
|
||||||
|
.lineLimit(1)
|
||||||
|
Text(connectivity.currentArtist)
|
||||||
|
.font(.caption)
|
||||||
|
.foregroundColor(.gray)
|
||||||
|
.lineLimit(1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if connectivity.tracks.isEmpty {
|
||||||
|
Text("Открой приложение на iPhone, чтобы список треков появился здесь")
|
||||||
|
.font(.caption2)
|
||||||
|
.foregroundColor(.gray)
|
||||||
|
} else {
|
||||||
|
ForEach(connectivity.tracks) { track in
|
||||||
|
Button(action: { connectivity.playTrack(id: track.id) }) {
|
||||||
|
VStack(alignment: .leading) {
|
||||||
|
Text(track.title)
|
||||||
|
.font(.body)
|
||||||
|
.foregroundColor(track.id == connectivity.currentId ? .green : .primary)
|
||||||
|
.lineLimit(1)
|
||||||
|
Text(track.artist)
|
||||||
|
.font(.caption2)
|
||||||
|
.foregroundColor(.gray)
|
||||||
|
.lineLimit(1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.navigationTitle("Музыка")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,41 @@
|
|||||||
|
import SwiftUI
|
||||||
|
|
||||||
|
// MARK: - Экран "Сейчас играет" на часах
|
||||||
|
struct NowPlayingView: View {
|
||||||
|
@StateObject private var connectivity = WatchConnectivityManager.shared
|
||||||
|
|
||||||
|
var body: some View {
|
||||||
|
VStack(spacing: 10) {
|
||||||
|
Text(connectivity.currentTitle)
|
||||||
|
.font(.headline)
|
||||||
|
.multilineTextAlignment(.center)
|
||||||
|
.lineLimit(2)
|
||||||
|
|
||||||
|
Text(connectivity.currentArtist)
|
||||||
|
.font(.subheadline)
|
||||||
|
.foregroundColor(.gray)
|
||||||
|
.lineLimit(1)
|
||||||
|
|
||||||
|
HStack(spacing: 22) {
|
||||||
|
Button(action: connectivity.previous) {
|
||||||
|
Image(systemName: "backward.fill")
|
||||||
|
.font(.title2)
|
||||||
|
}
|
||||||
|
|
||||||
|
Button(action: connectivity.playPause) {
|
||||||
|
Image(systemName: connectivity.isPlaying ? "pause.fill" : "play.fill")
|
||||||
|
.font(.largeTitle)
|
||||||
|
}
|
||||||
|
|
||||||
|
Button(action: connectivity.next) {
|
||||||
|
Image(systemName: "forward.fill")
|
||||||
|
.font(.title2)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.buttonStyle(.plain)
|
||||||
|
.padding(.top, 6)
|
||||||
|
}
|
||||||
|
.padding()
|
||||||
|
.navigationTitle("Сейчас играет")
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
{
|
||||||
|
"info" : {
|
||||||
|
"author" : "xcode",
|
||||||
|
"version" : 1
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,73 @@
|
|||||||
|
import Foundation
|
||||||
|
import WatchConnectivity
|
||||||
|
import Combine
|
||||||
|
|
||||||
|
// MARK: - Та же лёгкая модель трека, что и на телефоне
|
||||||
|
struct WatchTrackInfo: Codable, Identifiable {
|
||||||
|
let id: String
|
||||||
|
let title: String
|
||||||
|
let artist: String
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - Связь Apple Watch <-> iPhone
|
||||||
|
// Добавь этот файл в таргет часов (например "music playerWatch").
|
||||||
|
class WatchConnectivityManager: NSObject, WCSessionDelegate, ObservableObject {
|
||||||
|
static let shared = WatchConnectivityManager()
|
||||||
|
|
||||||
|
@Published var tracks: [WatchTrackInfo] = []
|
||||||
|
@Published var isPlaying: Bool = false
|
||||||
|
@Published var currentTitle: String = ""
|
||||||
|
@Published var currentArtist: String = ""
|
||||||
|
@Published var currentId: String = ""
|
||||||
|
|
||||||
|
private override init() {
|
||||||
|
super.init()
|
||||||
|
guard WCSession.isSupported() else { return }
|
||||||
|
let session = WCSession.default
|
||||||
|
session.delegate = self
|
||||||
|
session.activate()
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - WCSessionDelegate
|
||||||
|
|
||||||
|
func session(_ session: WCSession, activationDidCompleteWith activationState: WCSessionActivationState, error: Error?) {}
|
||||||
|
|
||||||
|
func session(_ session: WCSession, didReceiveApplicationContext applicationContext: [String: Any]) {
|
||||||
|
DispatchQueue.main.async {
|
||||||
|
self.apply(applicationContext)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private func apply(_ context: [String: Any]) {
|
||||||
|
if let data = context["tracks"] as? Data,
|
||||||
|
let decoded = try? JSONDecoder().decode([WatchTrackInfo].self, from: data) {
|
||||||
|
self.tracks = decoded
|
||||||
|
}
|
||||||
|
self.isPlaying = context["isPlaying"] as? Bool ?? false
|
||||||
|
self.currentTitle = context["currentTitle"] as? String ?? ""
|
||||||
|
self.currentArtist = context["currentArtist"] as? String ?? ""
|
||||||
|
self.currentId = context["currentId"] as? String ?? ""
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - Отправка команд на телефон
|
||||||
|
|
||||||
|
private func send(action: String, extra: [String: Any] = [:]) {
|
||||||
|
guard WCSession.isSupported() else { return }
|
||||||
|
var payload = extra
|
||||||
|
payload["action"] = action
|
||||||
|
|
||||||
|
let session = WCSession.default
|
||||||
|
if session.isReachable {
|
||||||
|
session.sendMessage(payload, replyHandler: nil) { _ in
|
||||||
|
session.transferUserInfo(payload)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
session.transferUserInfo(payload)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func playPause() { send(action: "playPause") }
|
||||||
|
func next() { send(action: "next") }
|
||||||
|
func previous() { send(action: "previous") }
|
||||||
|
func playTrack(id: String) { send(action: "playTrack", extra: ["id": id]) }
|
||||||
|
}
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
//
|
||||||
|
// music_playerWatchApp.swift
|
||||||
|
// music playerWatch Watch App
|
||||||
|
//
|
||||||
|
// Created by aswer on 30.06.2026.
|
||||||
|
//
|
||||||
|
|
||||||
|
import SwiftUI
|
||||||
|
|
||||||
|
@main
|
||||||
|
struct music_playerWatch_Watch_AppApp: App {
|
||||||
|
var body: some Scene {
|
||||||
|
WindowGroup {
|
||||||
|
ContentView()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,36 @@
|
|||||||
|
//
|
||||||
|
// music_playerWatch_Watch_AppTests.swift
|
||||||
|
// music playerWatch Watch AppTests
|
||||||
|
//
|
||||||
|
// Created by aswer on 30.06.2026.
|
||||||
|
//
|
||||||
|
|
||||||
|
import XCTest
|
||||||
|
@testable import music_playerWatch_Watch_App
|
||||||
|
|
||||||
|
final class music_playerWatch_Watch_AppTests: XCTestCase {
|
||||||
|
|
||||||
|
override func setUpWithError() throws {
|
||||||
|
// Put setup code here. This method is called before the invocation of each test method in the class.
|
||||||
|
}
|
||||||
|
|
||||||
|
override func tearDownWithError() throws {
|
||||||
|
// Put teardown code here. This method is called after the invocation of each test method in the class.
|
||||||
|
}
|
||||||
|
|
||||||
|
func testExample() throws {
|
||||||
|
// This is an example of a functional test case.
|
||||||
|
// Use XCTAssert and related functions to verify your tests produce the correct results.
|
||||||
|
// Any test you write for XCTest can be annotated as throws and async.
|
||||||
|
// Mark your test throws to produce an unexpected failure when your test encounters an uncaught error.
|
||||||
|
// Tests marked async will run the test method on an arbitrary thread managed by the Swift runtime.
|
||||||
|
}
|
||||||
|
|
||||||
|
func testPerformanceExample() throws {
|
||||||
|
// This is an example of a performance test case.
|
||||||
|
self.measure {
|
||||||
|
// Put the code you want to measure the time of here.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,41 @@
|
|||||||
|
//
|
||||||
|
// music_playerWatch_Watch_AppUITests.swift
|
||||||
|
// music playerWatch Watch AppUITests
|
||||||
|
//
|
||||||
|
// Created by aswer on 30.06.2026.
|
||||||
|
//
|
||||||
|
|
||||||
|
import XCTest
|
||||||
|
|
||||||
|
final class music_playerWatch_Watch_AppUITests: XCTestCase {
|
||||||
|
|
||||||
|
override func setUpWithError() throws {
|
||||||
|
// Put setup code here. This method is called before the invocation of each test method in the class.
|
||||||
|
|
||||||
|
// In UI tests it is usually best to stop immediately when a failure occurs.
|
||||||
|
continueAfterFailure = false
|
||||||
|
|
||||||
|
// In UI tests it’s important to set the initial state - such as interface orientation - required for your tests before they run. The setUp method is a good place to do this.
|
||||||
|
}
|
||||||
|
|
||||||
|
override func tearDownWithError() throws {
|
||||||
|
// Put teardown code here. This method is called after the invocation of each test method in the class.
|
||||||
|
}
|
||||||
|
|
||||||
|
func testExample() throws {
|
||||||
|
// UI tests must launch the application that they test.
|
||||||
|
let app = XCUIApplication()
|
||||||
|
app.launch()
|
||||||
|
|
||||||
|
// Use XCTAssert and related functions to verify your tests produce the correct results.
|
||||||
|
}
|
||||||
|
|
||||||
|
func testLaunchPerformance() throws {
|
||||||
|
if #available(macOS 10.15, iOS 13.0, tvOS 13.0, watchOS 7.0, *) {
|
||||||
|
// This measures how long it takes to launch your application.
|
||||||
|
measure(metrics: [XCTApplicationLaunchMetric()]) {
|
||||||
|
XCUIApplication().launch()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+32
@@ -0,0 +1,32 @@
|
|||||||
|
//
|
||||||
|
// music_playerWatch_Watch_AppUITestsLaunchTests.swift
|
||||||
|
// music playerWatch Watch AppUITests
|
||||||
|
//
|
||||||
|
// Created by aswer on 30.06.2026.
|
||||||
|
//
|
||||||
|
|
||||||
|
import XCTest
|
||||||
|
|
||||||
|
final class music_playerWatch_Watch_AppUITestsLaunchTests: XCTestCase {
|
||||||
|
|
||||||
|
override class var runsForEachTargetApplicationUIConfiguration: Bool {
|
||||||
|
true
|
||||||
|
}
|
||||||
|
|
||||||
|
override func setUpWithError() throws {
|
||||||
|
continueAfterFailure = false
|
||||||
|
}
|
||||||
|
|
||||||
|
func testLaunch() throws {
|
||||||
|
let app = XCUIApplication()
|
||||||
|
app.launch()
|
||||||
|
|
||||||
|
// Insert steps here to perform after app launch but before taking a screenshot,
|
||||||
|
// such as logging into a test account or navigating somewhere in the app
|
||||||
|
|
||||||
|
let attachment = XCTAttachment(screenshot: app.screenshot())
|
||||||
|
attachment.name = "Launch Screen"
|
||||||
|
attachment.lifetime = .keepAlways
|
||||||
|
add(attachment)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||||
|
<plist version="1.0">
|
||||||
|
<dict>
|
||||||
|
<key>UIBackgroundModes</key>
|
||||||
|
<array>
|
||||||
|
<string>audio</string>
|
||||||
|
</array>
|
||||||
|
</dict>
|
||||||
|
</plist>
|
||||||
Reference in New Issue
Block a user