Merge branch 'main' into theme-setting

This commit is contained in:
Ethan Pippin 2021-07-26 13:44:22 -06:00
commit 1398d6cac0
11 changed files with 113 additions and 63 deletions

View File

@ -111,6 +111,7 @@ struct ConnectToServerView: View {
TextField(NSLocalizedString("Server URL", comment: ""), text: $uri) TextField(NSLocalizedString("Server URL", comment: ""), text: $uri)
.disableAutocorrection(true) .disableAutocorrection(true)
.autocapitalization(.none) .autocapitalization(.none)
.keyboardType(.URL)
Button { Button {
viewModel.connectToServer() viewModel.connectToServer()
} label: { } label: {

View File

@ -22,6 +22,11 @@
<string>$(CURRENT_PROJECT_VERSION)</string> <string>$(CURRENT_PROJECT_VERSION)</string>
<key>LSRequiresIPhoneOS</key> <key>LSRequiresIPhoneOS</key>
<true/> <true/>
<key>NSAppTransportSecurity</key>
<dict>
<key>NSAllowsArbitraryLoads</key>
<true/>
</dict>
<key>UILaunchScreen</key> <key>UILaunchScreen</key>
<dict/> <dict/>
<key>UIRequiredDeviceCapabilities</key> <key>UIRequiredDeviceCapabilities</key>

View File

@ -109,6 +109,7 @@ struct ConnectToServerView: View {
TextField(NSLocalizedString("Server URL", comment: ""), text: $uri) TextField(NSLocalizedString("Server URL", comment: ""), text: $uri)
.disableAutocorrection(true) .disableAutocorrection(true)
.autocapitalization(.none) .autocapitalization(.none)
.keyboardType(.URL)
Button { Button {
viewModel.connectToServer() viewModel.connectToServer()
} label: { } label: {

View File

@ -30,8 +30,6 @@
<dict> <dict>
<key>NSAllowsArbitraryLoads</key> <key>NSAllowsArbitraryLoads</key>
<true/> <true/>
<key>NSAllowsLocalNetworking</key>
<true/>
</dict> </dict>
<key>NSBluetoothAlwaysUsageDescription</key> <key>NSBluetoothAlwaysUsageDescription</key>
<string>${PRODUCT_NAME} uses Bluetooth to discover nearby Cast devices.</string> <string>${PRODUCT_NAME} uses Bluetooth to discover nearby Cast devices.</string>

View File

@ -34,7 +34,7 @@ struct ItemView: View {
.statusBar(hidden: true) .statusBar(hidden: true)
.edgesIgnoringSafeArea(.all) .edgesIgnoringSafeArea(.all)
.prefersHomeIndicatorAutoHidden(true) .prefersHomeIndicatorAutoHidden(true)
}.supportedOrientations(.landscape), isActive: $videoPlayerItem.shouldShowPlayer) { }, isActive: $videoPlayerItem.shouldShowPlayer) {
EmptyView() EmptyView()
} }
VStack { VStack {
@ -56,7 +56,6 @@ struct ItemView: View {
.navigationBarHidden(false) .navigationBarHidden(false)
.navigationBarBackButtonHidden(false) .navigationBarBackButtonHidden(false)
.environmentObject(videoPlayerItem) .environmentObject(videoPlayerItem)
.supportedOrientations(.all)
} }
} }
} }

View File

@ -209,6 +209,8 @@ class EmailHelper: NSObject, MFMailComposeViewControllerDelegate {
@main @main
struct JellyfinPlayerApp: App { struct JellyfinPlayerApp: App {
@UIApplicationDelegateAdaptor(AppDelegate.self) var appDelegate
let persistenceController = PersistenceController.shared let persistenceController = PersistenceController.shared
var body: some Scene { var body: some Scene {
@ -224,3 +226,12 @@ struct JellyfinPlayerApp: App {
} }
} }
} }
class AppDelegate: NSObject, UIApplicationDelegate {
static var orientationLock = UIInterfaceOrientationMask.all
func application(_ application: UIApplication, supportedInterfaceOrientationsFor window: UIWindow?) -> UIInterfaceOrientationMask {
AppDelegate.orientationLock
}
}

View File

@ -12,13 +12,13 @@ import SwiftUI
struct LibrarySearchView: View { struct LibrarySearchView: View {
@StateObject var viewModel: LibrarySearchViewModel @StateObject var viewModel: LibrarySearchViewModel
@State var searchQuery = "" @State var searchQuery = ""
@State private var tracks: [GridItem] = Array(repeating: .init(.flexible()), count: Int(UIScreen.main.bounds.size.width) / 125) @State private var tracks: [GridItem] = Array(repeating: .init(.flexible()), count: Int(UIScreen.main.bounds.size.width) / 125)
func recalcTracks() { func recalcTracks() {
tracks = Array(repeating: .init(.flexible()), count: Int(UIScreen.main.bounds.size.width) / 125) tracks = Array(repeating: .init(.flexible()), count: Int(UIScreen.main.bounds.size.width) / 125)
} }
var body: some View { var body: some View {
ZStack { ZStack {
VStack { VStack {
@ -40,7 +40,7 @@ struct LibrarySearchView: View {
} }
.navigationBarTitle("Search", displayMode: .inline) .navigationBarTitle("Search", displayMode: .inline)
} }
var suggestionsListView: some View { var suggestionsListView: some View {
ScrollView { ScrollView {
LazyVStack(spacing: 8) { LazyVStack(spacing: 8) {
@ -61,7 +61,7 @@ struct LibrarySearchView: View {
.padding(.horizontal, 16) .padding(.horizontal, 16)
} }
} }
var resultView: some View { var resultView: some View {
let items = items(for: viewModel.selectedItemType) let items = items(for: viewModel.selectedItemType)
return VStack(alignment: .leading, spacing: 16) { return VStack(alignment: .leading, spacing: 16) {
@ -90,7 +90,7 @@ struct LibrarySearchView: View {
recalcTracks() recalcTracks()
} }
} }
func items(for type: ItemType) -> [BaseItemDto] { func items(for type: ItemType) -> [BaseItemDto] {
switch type { switch type {
case .episode: case .episode:
@ -106,7 +106,7 @@ struct LibrarySearchView: View {
} }
private extension ItemType { private extension ItemType {
var localized: String { var localized: String {
switch self { switch self {
case .episode: case .episode:

View File

@ -73,7 +73,7 @@ struct SettingsView: View {
}) })
} }
Section { Section(header: Text(ServerEnvironment.current.server.name ?? "")) {
HStack { HStack {
Text("Signed in as \(username)").foregroundColor(.primary) Text("Signed in as \(username)").foregroundColor(.primary)
Spacer() Spacer()

View File

@ -81,7 +81,7 @@ class PlayerViewController: UIViewController, GCKDiscoveryManagerListener, GCKRe
var playbackItem = PlaybackItem() var playbackItem = PlaybackItem()
var remoteTimeUpdateTimer: Timer? var remoteTimeUpdateTimer: Timer?
var upNextViewModel: UpNextViewModel = UpNextViewModel() var upNextViewModel: UpNextViewModel = UpNextViewModel()
var lastOri: UIDeviceOrientation! var lastOri: UIInterfaceOrientation!
// MARK: IBActions // MARK: IBActions
@IBAction func seekSliderStart(_ sender: Any) { @IBAction func seekSliderStart(_ sender: Any) {
@ -387,6 +387,7 @@ class PlayerViewController: UIViewController, GCKDiscoveryManagerListener, GCKRe
// MARK: viewDidLoad // MARK: viewDidLoad
override func viewDidLoad() { override func viewDidLoad() {
super.viewDidLoad()
if manifest.type == "Movie" { if manifest.type == "Movie" {
titleLabel.text = manifest.name ?? "" titleLabel.text = manifest.name ?? ""
} else { } else {
@ -396,14 +397,21 @@ class PlayerViewController: UIViewController, GCKDiscoveryManagerListener, GCKRe
upNextViewModel.delegate = self upNextViewModel.delegate = self
} }
lastOri = UIDevice.current.orientation DispatchQueue.main.async {
self.lastOri = UIApplication.shared.windows.first?.windowScene?.interfaceOrientation
AppDelegate.orientationLock = .landscape
if !UIDevice.current.orientation.isLandscape || UIDevice.current.orientation.isFlat { if !self.lastOri.isLandscape {
let value = UIInterfaceOrientation.landscapeRight.rawValue UIDevice.current.setValue(UIInterfaceOrientation.landscapeRight.rawValue, forKey: "orientation")
UIDevice.current.setValue(value, forKey: "orientation") UIViewController.attemptRotationToDeviceOrientation()
UIViewController.attemptRotationToDeviceOrientation() }
} }
super.viewDidLoad()
NotificationCenter.default.addObserver(self, selector: #selector(didChangedOrientation), name: UIDevice.orientationDidChangeNotification, object: nil)
}
@objc func didChangedOrientation() {
lastOri = UIApplication.shared.windows.first?.windowScene?.interfaceOrientation
} }
func mediaHasStartedPlaying() { func mediaHasStartedPlaying() {
@ -438,12 +446,15 @@ class PlayerViewController: UIViewController, GCKDiscoveryManagerListener, GCKRe
} }
override func viewWillDisappear(_ animated: Bool) { override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
self.tabBarController?.tabBar.isHidden = false self.tabBarController?.tabBar.isHidden = false
self.navigationController?.isNavigationBarHidden = false self.navigationController?.isNavigationBarHidden = false
overrideUserInterfaceStyle = .unspecified overrideUserInterfaceStyle = .unspecified
UIDevice.current.setValue(lastOri.rawValue, forKey: "orientation") DispatchQueue.main.async {
UIViewController.attemptRotationToDeviceOrientation() AppDelegate.orientationLock = .all
super.viewWillDisappear(animated) UIDevice.current.setValue(self.lastOri.rawValue, forKey: "orientation")
UIViewController.attemptRotationToDeviceOrientation()
}
} }
// MARK: viewDidAppear // MARK: viewDidAppear
@ -479,19 +490,19 @@ class PlayerViewController: UIViewController, GCKDiscoveryManagerListener, GCKRe
MediaInfoAPI.getPostedPlaybackInfo(itemId: manifest.id!, userId: SessionManager.current.user.user_id!, maxStreamingBitrate: Int(maxBitrate), startTimeTicks: manifest.userData?.playbackPositionTicks ?? 0, autoOpenLiveStream: true, playbackInfoDto: playbackInfo) MediaInfoAPI.getPostedPlaybackInfo(itemId: manifest.id!, userId: SessionManager.current.user.user_id!, maxStreamingBitrate: Int(maxBitrate), startTimeTicks: manifest.userData?.playbackPositionTicks ?? 0, autoOpenLiveStream: true, playbackInfoDto: playbackInfo)
.sink(receiveCompletion: { completion in .sink(receiveCompletion: { completion in
switch completion { switch completion {
case .finished: case .finished:
break break
case .failure(let error): case .failure(let error):
if let err = error as? ErrorResponse { if let err = error as? ErrorResponse {
switch err { switch err {
case .error(401, _, _, _): case .error(401, _, _, _):
self.delegate?.exitPlayer(self) self.delegate?.exitPlayer(self)
SessionManager.current.logout() SessionManager.current.logout()
case .error: case .error:
self.delegate?.exitPlayer(self) self.delegate?.exitPlayer(self)
}
} }
break }
break
} }
}, receiveValue: { [self] response in }, receiveValue: { [self] response in
playSessionId = response.playSessionId ?? "" playSessionId = response.playSessionId ?? ""
@ -906,35 +917,35 @@ extension PlayerViewController: GCKSessionManagerListener {
// MARK: - VLCMediaPlayer Delegates // MARK: - VLCMediaPlayer Delegates
extension PlayerViewController: VLCMediaPlayerDelegate { extension PlayerViewController: VLCMediaPlayerDelegate {
func mediaPlayerStateChanged(_ aNotification: Notification!) { func mediaPlayerStateChanged(_ aNotification: Notification!) {
let currentState: VLCMediaPlayerState = mediaPlayer.state let currentState: VLCMediaPlayerState = mediaPlayer.state
switch currentState { switch currentState {
case .stopped : case .stopped :
LogManager.shared.log.debug("Player state changed: STOPPED") LogManager.shared.log.debug("Player state changed: STOPPED")
break break
case .ended : case .ended :
LogManager.shared.log.debug("Player state changed: ENDED") LogManager.shared.log.debug("Player state changed: ENDED")
break break
case .playing : case .playing :
LogManager.shared.log.debug("Player state changed: PLAYING") LogManager.shared.log.debug("Player state changed: PLAYING")
sendProgressReport(eventName: "unpause") sendProgressReport(eventName: "unpause")
delegate?.hideLoadingView(self) delegate?.hideLoadingView(self)
paused = false paused = false
case .paused : case .paused :
LogManager.shared.log.debug("Player state changed: PAUSED") LogManager.shared.log.debug("Player state changed: PAUSED")
paused = true paused = true
case .opening : case .opening :
LogManager.shared.log.debug("Player state changed: OPENING") LogManager.shared.log.debug("Player state changed: OPENING")
case .buffering : case .buffering :
LogManager.shared.log.debug("Player state changed: BUFFERING") LogManager.shared.log.debug("Player state changed: BUFFERING")
delegate?.showLoadingView(self) delegate?.showLoadingView(self)
case .error : case .error :
LogManager.shared.log.error("Video had error.") LogManager.shared.log.error("Video had error.")
sendStopReport() sendStopReport()
case .esAdded: case .esAdded:
mainActionButton.setImage(UIImage(systemName: "pause"), for: .normal) mainActionButton.setImage(UIImage(systemName: "pause"), for: .normal)
@unknown default: @unknown default:
break break
} }
} }
func mediaPlayerTimeChanged(_ aNotification: Notification!) { func mediaPlayerTimeChanged(_ aNotification: Notification!) {

View File

@ -1,6 +1,9 @@
<p align="center"> <p align="center">
<img alt="SwiftFin" height="125" src="https://github.com/jellyfin/SwiftFin/raw/main/JellyfinPlayer/Assets.xcassets/AppIcon.appiconset/152.png"> <img alt="SwiftFin" height="125" src="https://github.com/jellyfin/SwiftFin/raw/main/JellyfinPlayer/Assets.xcassets/AppIcon.appiconset/152.png">
<h2 align="center">SwiftFin</h2> <h2 align="center">SwiftFin</h2>
<a href="https://translate.jellyfin.org/engage/swiftfin/">
<img src="https://translate.jellyfin.org/widgets/swiftfin/-/svg-badge.svg"/>
</a>
<a href="https://matrix.to/#/+jellyfin:matrix.org"> <a href="https://matrix.to/#/+jellyfin:matrix.org">
<img src="https://img.shields.io/matrix/jellyfin:matrix.org"> <img src="https://img.shields.io/matrix/jellyfin:matrix.org">
</a> </a>
@ -19,6 +22,27 @@
<a href='https://testflight.apple.com/join/WiN0G62Q'><img height='70' alt='Join the Beta on TestFlight' src='https://anotherlens.app/testflight-badge.png'/></a> <a href='https://testflight.apple.com/join/WiN0G62Q'><img height='70' alt='Join the Beta on TestFlight' src='https://anotherlens.app/testflight-badge.png'/></a>
**Don't see SwiftFin in your language?**
Check out our [Weblate instance](https://translate.jellyfin.org/projects/swiftfin/) to help translate SwiftFin and other projects.
<a href="https://translate.jellyfin.org/engage/swiftfin/">
<img src="https://translate.jellyfin.org/widgets/swiftfin/-/multi-auto.svg"/>
</a>
## ⚙️ Development ## ⚙️ Development
Xcode 13.0 with command line tools. Xcode 13.0 with command line tools.
### Build Process
```bash
# install Cocoapods (if not installed)
$ sudo gem install cocoapods
# install dependencies
$ pod install
# open workspace and build it
$ open JellyfinPlayer.xcworkspace
```