Skip to content

Commit 158284e

Browse files
authored
Merge pull request #1221 from TortugaPower/watch-remoteplay
Add support for stand alone playback for the watch app
2 parents 5fe537b + 2412d5a commit 158284e

File tree

101 files changed

+3826
-293
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

101 files changed

+3826
-293
lines changed

BookPlayer.xcodeproj/project.pbxproj

+138-10
Large diffs are not rendered by default.

BookPlayer/Base.lproj/Localizable.strings

+2-1
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@
5555
"ok_button" = "OK";
5656
"siri_alert_description" = "Siri Shortcuts are available on iOS 12 and above";
5757
"support_bookplayer_title" = "Support BookPlayer";
58-
"support_bookplayer_description" = "Get BookPlayer Pro to support future development and get extra themes, app icons and cloud sync";
58+
"support_bookplayer_description" = "Support future development and get extra themes, app icons, cloud sync and stand-alone playback on the Apple Watch";
5959
"learn_more_title" = "LEARN MORE";
6060
"settings_appearance_title" = "Appearance";
6161
"settings_theme_title" = "Theme";
@@ -321,3 +321,4 @@ We're working hard on providing a seamless experience, if possible, please conta
321321
"more_title" = "More";
322322
"repeat_turn_on_title" = "Turn on Repeat for this book";
323323
"repeat_turn_off_title" = "Turn off Repeat for this book";
324+
"benefits_watchapp_description" = "Stream or download your books and listen on the go without your phone.";

BookPlayer/Player/PlayerLoaderService.swift

+6-4
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,11 @@
66
// Copyright © 2024 Tortuga Power. All rights reserved.
77
//
88

9-
import BookPlayerKit
9+
#if os(watchOS)
10+
import BookPlayerWatchKit
11+
#else
12+
import BookPlayerKit
13+
#endif
1014
import Foundation
1115

1216
final class PlayerLoaderService: @unchecked Sendable {
@@ -68,9 +72,7 @@ final class PlayerLoaderService: @unchecked Sendable {
6872
playerManager.load(item, autoplay: autoplay)
6973

7074
if recordAsLastBook {
71-
await MainActor.run {
72-
libraryService.setLibraryLastBook(with: item.relativePath)
73-
}
75+
libraryService.setLibraryLastBook(with: item.relativePath)
7476
}
7577
}
7678
}

BookPlayer/Player/PlayerManagerProtocol.swift

+5-1
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,11 @@
66
// Copyright © 2024 Tortuga Power. All rights reserved.
77
//
88

9-
import BookPlayerKit
9+
#if os(watchOS)
10+
import BookPlayerWatchKit
11+
#else
12+
import BookPlayerKit
13+
#endif
1014
import Combine
1115
import Foundation
1216

BookPlayer/Player/SleepTimer.swift

+13-6
Original file line numberDiff line numberDiff line change
@@ -6,12 +6,17 @@
66
// Copyright © 2018 Florian Pichler.
77
//
88

9-
import BookPlayerKit
109
import Combine
1110
import Foundation
12-
import IntentsUI
1311
import UIKit
1412

13+
#if os(watchOS)
14+
import BookPlayerWatchKit
15+
#else
16+
import BookPlayerKit
17+
import IntentsUI
18+
#endif
19+
1520
/// Available sleep timer states
1621
enum SleepTimerState: Equatable {
1722
case off
@@ -53,7 +58,7 @@ final class SleepTimer {
5358
600.0,
5459
900.0,
5560
1800.0,
56-
3600.0
61+
3600.0,
5762
]
5863

5964
/// Publisher when the countdown timer reaches the defined threshold
@@ -88,8 +93,10 @@ final class SleepTimer {
8893
let intent = SleepTimerIntent()
8994
intent.option = option
9095

91-
let interaction = INInteraction(intent: intent, response: nil)
92-
interaction.donate(completion: nil)
96+
#if os(iOS)
97+
let interaction = INInteraction(intent: intent, response: nil)
98+
interaction.donate(completion: nil)
99+
#endif
93100
}
94101

95102
/// Periodic function used for the `countdown` case of ``SleepTimerState``
@@ -116,7 +123,7 @@ final class SleepTimer {
116123
}
117124

118125
// MARK: Public methods
119-
126+
120127
public func setTimer(_ newState: SleepTimerState) {
121128
/// Always cancel any ongoing timer
122129
reset()

BookPlayer/Player/SpeedService.swift

+5-1
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,11 @@
66
// Copyright © 2021 Tortuga Power. All rights reserved.
77
//
88

9-
import BookPlayerKit
9+
#if os(watchOS)
10+
import BookPlayerWatchKit
11+
#else
12+
import BookPlayerKit
13+
#endif
1014
import Combine
1115
import Foundation
1216

BookPlayer/Player/WidgetReloadService.swift

+16-5
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,14 @@
77
//
88

99
import Foundation
10-
import BookPlayerKit
1110
import WidgetKit
1211

12+
#if os(watchOS)
13+
import BookPlayerWatchKit
14+
#else
15+
import BookPlayerKit
16+
#endif
17+
1318
protocol WidgetReloadServiceProtocol {
1419
/// Reload all the registered widgets
1520
func reloadAllWidgets()
@@ -31,15 +36,19 @@ class WidgetReloadService: WidgetReloadServiceProtocol {
3136
$0.cancel()
3237
})
3338
referenceWorkItems = [:]
34-
WidgetCenter.shared.reloadAllTimelines()
39+
if #available(watchOS 9.0, *) {
40+
WidgetCenter.shared.reloadAllTimelines()
41+
}
3542
}
3643

3744
func reloadWidget(_ type: Constants.Widgets) {
3845
let referenceWorkItem = referenceWorkItems[type]
3946

4047
referenceWorkItem?.cancel()
4148

42-
WidgetCenter.shared.reloadTimelines(ofKind: type.rawValue)
49+
if #available(watchOS 9.0, *) {
50+
WidgetCenter.shared.reloadTimelines(ofKind: type.rawValue)
51+
}
4352
}
4453

4554
func scheduleWidgetReload(of type: Constants.Widgets) {
@@ -48,9 +57,11 @@ class WidgetReloadService: WidgetReloadServiceProtocol {
4857
referenceWorkItem?.cancel()
4958

5059
let workItem = DispatchWorkItem {
51-
WidgetCenter.shared.reloadTimelines(ofKind: type.rawValue)
60+
if #available(watchOS 9.0, *) {
61+
WidgetCenter.shared.reloadTimelines(ofKind: type.rawValue)
62+
}
5263
}
53-
64+
5465
referenceWorkItems[type] = workItem
5566

5667
DispatchQueue.main.asyncAfter(deadline: .now() + .seconds(5), execute: workItem)

BookPlayer/Profile/Login Screen/LoginViewController.swift

+23-11
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,17 @@ class LoginViewController: UIViewController {
3838
return stackView
3939
}()
4040

41+
private lazy var watchAppBenefitStackView: UIStackView = {
42+
let stackView = LoginBenefitView(
43+
title: "Apple Watch (Beta)",
44+
description: "benefits_watchapp_description".localized,
45+
systemName: "applewatch.radiowaves.left.and.right",
46+
imageAlpha: 0.5
47+
)
48+
stackView.translatesAutoresizingMaskIntoConstraints = false
49+
return stackView
50+
}()
51+
4152
private lazy var cosmeticBenefitStackView: UIStackView = {
4253
let stackView = LoginBenefitView(
4354
title: "benefits_themesicons_title".localized,
@@ -66,7 +77,6 @@ class LoginViewController: UIViewController {
6677
disclaimers: [
6778
"benefits_disclaimer_account_description".localized,
6879
"benefits_disclaimer_subscription_description".localized,
69-
"benefits_disclaimer_watch_description".localized
7080
]
7181
)
7282
stackView.translatesAutoresizingMaskIntoConstraints = false
@@ -115,8 +125,8 @@ class LoginViewController: UIViewController {
115125
view.addSubview(scrollView)
116126
scrollView.addSubview(contentView)
117127
contentView.addSubview(cloudBenefitStackView)
128+
contentView.addSubview(watchAppBenefitStackView)
118129
contentView.addSubview(cosmeticBenefitStackView)
119-
contentView.addSubview(supportBenefitStackView)
120130
contentView.addSubview(disclaimerStackView)
121131
}
122132

@@ -145,19 +155,21 @@ class LoginViewController: UIViewController {
145155
cloudBenefitStackView.topAnchor.constraint(equalTo: contentView.topAnchor, constant: Spacing.M),
146156
cloudBenefitStackView.leadingAnchor.constraint(equalTo: contentView.leadingAnchor),
147157
cloudBenefitStackView.trailingAnchor.constraint(equalTo: contentView.trailingAnchor, constant: -Spacing.M),
148-
cosmeticBenefitStackView.topAnchor.constraint(equalTo: cloudBenefitStackView.bottomAnchor, constant: 30),
149-
cosmeticBenefitStackView.leadingAnchor.constraint(equalTo: cloudBenefitStackView.leadingAnchor),
150-
cosmeticBenefitStackView.trailingAnchor.constraint(equalTo: cloudBenefitStackView.trailingAnchor),
151-
supportBenefitStackView.topAnchor.constraint(equalTo: cosmeticBenefitStackView.bottomAnchor, constant: 30),
152-
supportBenefitStackView.leadingAnchor.constraint(equalTo: cosmeticBenefitStackView.leadingAnchor),
153-
supportBenefitStackView.trailingAnchor.constraint(equalTo: cosmeticBenefitStackView.trailingAnchor),
158+
watchAppBenefitStackView.topAnchor.constraint(equalTo: cloudBenefitStackView.bottomAnchor, constant: 30),
159+
watchAppBenefitStackView.leadingAnchor.constraint(equalTo: cloudBenefitStackView.leadingAnchor),
160+
watchAppBenefitStackView.trailingAnchor.constraint(equalTo: cloudBenefitStackView.trailingAnchor),
161+
162+
cosmeticBenefitStackView.topAnchor.constraint(equalTo: watchAppBenefitStackView.bottomAnchor, constant: 30),
163+
cosmeticBenefitStackView.leadingAnchor.constraint(equalTo: watchAppBenefitStackView.leadingAnchor),
164+
cosmeticBenefitStackView.trailingAnchor.constraint(equalTo: watchAppBenefitStackView.trailingAnchor),
165+
154166
// setup disclaimer
155167
disclaimerStackView.topAnchor.constraint(
156-
greaterThanOrEqualTo: supportBenefitStackView.bottomAnchor,
168+
greaterThanOrEqualTo: cosmeticBenefitStackView.bottomAnchor,
157169
constant: 45
158170
),
159-
disclaimerStackView.leadingAnchor.constraint(equalTo: supportBenefitStackView.leadingAnchor, constant: Spacing.M),
160-
disclaimerStackView.trailingAnchor.constraint(equalTo: supportBenefitStackView.trailingAnchor),
171+
disclaimerStackView.leadingAnchor.constraint(equalTo: cosmeticBenefitStackView.leadingAnchor, constant: Spacing.M),
172+
disclaimerStackView.trailingAnchor.constraint(equalTo: cosmeticBenefitStackView.trailingAnchor),
161173
disclaimerStackView.bottomAnchor.constraint(equalTo: contentView.bottomAnchor, constant: -Spacing.L),
162174
])
163175
}

BookPlayer/Services/UserActivityManager.swift

+5-1
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,11 @@
66
// Copyright © 2018 Tortuga Power. All rights reserved.
77
//
88

9-
import BookPlayerKit
9+
#if os(watchOS)
10+
import BookPlayerWatchKit
11+
#else
12+
import BookPlayerKit
13+
#endif
1014
import Foundation
1115
import Intents
1216

BookPlayer/Settings/Plus Screen/Views/Base.lproj/PlusBannerView.xib

+9-12
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
<?xml version="1.0" encoding="UTF-8"?>
2-
<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="21507" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES">
2+
<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="23504" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES">
33
<device id="retina4_7" orientation="portrait" appearance="light"/>
44
<dependencies>
55
<deployment identifier="iOS"/>
6-
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="21505"/>
6+
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="23506"/>
77
<capability name="Safe area layout guides" minToolsVersion="9.0"/>
88
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
99
</dependencies>
@@ -21,27 +21,24 @@
2121
<rect key="frame" x="0.0" y="0.0" width="391" height="149"/>
2222
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
2323
<subviews>
24-
<imageView userInteractionEnabled="NO" alpha="0.5" contentMode="scaleAspectFit" horizontalHuggingPriority="251" verticalHuggingPriority="251" image="icloud.and.arrow.up.fill" catalog="system" translatesAutoresizingMaskIntoConstraints="NO" id="q8b-g4-bTO">
25-
<rect key="frame" x="16" y="48.5" width="70" height="50"/>
24+
<imageView userInteractionEnabled="NO" alpha="0.5" contentMode="scaleAspectFit" horizontalHuggingPriority="251" verticalHuggingPriority="251" image="applewatch.radiowaves.left.and.right" catalog="system" translatesAutoresizingMaskIntoConstraints="NO" id="q8b-g4-bTO">
25+
<rect key="frame" x="16" y="68" width="70" height="50"/>
2626
<constraints>
2727
<constraint firstAttribute="height" constant="50" id="a71-tI-vwv"/>
2828
<constraint firstAttribute="width" constant="70" id="zuh-0P-o2j"/>
2929
</constraints>
3030
</imageView>
31-
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Support BookPlayer" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="rpT-kz-YBF" customClass="LocalizableLabel" customModule="BookPlayer" customModuleProvider="target">
32-
<rect key="frame" x="102" y="24" width="265" height="21"/>
31+
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="BookPlayer Pro" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="rpT-kz-YBF">
32+
<rect key="frame" x="102" y="44" width="265" height="21"/>
3333
<constraints>
3434
<constraint firstAttribute="height" constant="21" id="7qM-If-ztF"/>
3535
</constraints>
3636
<fontDescription key="fontDescription" type="system" pointSize="17"/>
3737
<color key="textColor" red="0.13728347420692444" green="0.13718283176422119" blue="0.14117720723152161" alpha="1" colorSpace="custom" customColorSpace="displayP3"/>
3838
<nil key="highlightedColor"/>
39-
<userDefinedRuntimeAttributes>
40-
<userDefinedRuntimeAttribute type="string" keyPath="localizedKey" value="support_bookplayer_title"/>
41-
</userDefinedRuntimeAttributes>
4239
</label>
43-
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Get BookPlayer Pro to support future development and get extra themes, app icons and cloud sync" textAlignment="natural" lineBreakMode="tailTruncation" numberOfLines="0" baselineAdjustment="alignBaselines" minimumScaleFactor="0.80000000000000004" translatesAutoresizingMaskIntoConstraints="NO" id="FWO-08-PTC" customClass="LocalizableLabel" customModule="BookPlayer" customModuleProvider="target">
44-
<rect key="frame" x="102" y="48" width="265" height="47"/>
40+
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Support future development and get extra themes, app icons, cloud sync and stand-alone playback on the Apple Watch" textAlignment="natural" lineBreakMode="tailTruncation" numberOfLines="0" baselineAdjustment="alignBaselines" minimumScaleFactor="0.80000000000000004" translatesAutoresizingMaskIntoConstraints="NO" id="FWO-08-PTC" customClass="LocalizableLabel" customModule="BookPlayer" customModuleProvider="target">
41+
<rect key="frame" x="102" y="68" width="265" height="27"/>
4542
<fontDescription key="fontDescription" type="system" pointSize="13"/>
4643
<color key="textColor" red="0.60795658826828003" green="0.61154496669769287" blue="0.6235319972038269" alpha="1" colorSpace="custom" customColorSpace="displayP3"/>
4744
<nil key="highlightedColor"/>
@@ -91,6 +88,6 @@
9188
</view>
9289
</objects>
9390
<resources>
94-
<image name="icloud.and.arrow.up.fill" catalog="system" width="128" height="108"/>
91+
<image name="applewatch.radiowaves.left.and.right" catalog="system" width="128" height="87"/>
9592
</resources>
9693
</document>

0 commit comments

Comments
 (0)