Skip to content
This repository was archived by the owner on Dec 11, 2023. It is now read-only.

Commit e62dd36

Browse files
authored
Merge pull request #190 from nnsnodnb/feature/todayExtension
TodayExtension実装
2 parents e600a80 + 60a1189 commit e62dd36

File tree

9 files changed

+343
-69
lines changed

9 files changed

+343
-69
lines changed

TodayExtension/Base.lproj/MainInterface.storyboard

Lines changed: 0 additions & 39 deletions
This file was deleted.
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
//
2+
// MPMediaItemArtwork+UIImage.swift
3+
// TodayExtension
4+
//
5+
// Created by Oka Yuya on 2020/05/17.
6+
// Copyright © 2020 Yuya Oka. All rights reserved.
7+
//
8+
9+
import MediaPlayer
10+
import UIKit
11+
12+
extension MPMediaItemArtwork {
13+
14+
var image: UIImage? {
15+
return image(at: bounds.size)
16+
}
17+
}
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
//
2+
// ScrollFlowLabel+Rx.swift
3+
// TodayExtension
4+
//
5+
// Created by Oka Yuya on 2020/05/17.
6+
// Copyright © 2020 Yuya Oka. All rights reserved.
7+
//
8+
9+
import RxCocoa
10+
import RxSwift
11+
import ScrollFlowLabel
12+
13+
extension Reactive where Base: ScrollFlowLabel {
14+
15+
var text: Binder<String?> {
16+
return .init(base) { (label, text) in
17+
label.text = text
18+
}
19+
}
20+
}
File renamed without changes.
Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
//
2+
// TodayViewController.swift
3+
// TodayExtension
4+
//
5+
// Created by Oka Yuya on 2020/01/23.
6+
// Copyright © 2020 Yuya Oka. All rights reserved.
7+
//
8+
9+
import NotificationCenter
10+
import RxCocoa
11+
import RxSwift
12+
import ScrollFlowLabel
13+
import UIKit
14+
15+
enum ViewType {
16+
case common
17+
case denied
18+
}
19+
20+
final class TodayViewController: UIViewController, NCWidgetProviding {
21+
22+
@IBOutlet private weak var commonView: UIView! {
23+
didSet {
24+
viewModel.outputs.viewType.map { $0 != .common }.bind(to: commonView.rx.isHidden).disposed(by: disposeBag)
25+
}
26+
}
27+
@IBOutlet private weak var artworkImageButton: UIButton! {
28+
didSet {
29+
artworkImageButton.imageView?.contentMode = .scaleAspectFit
30+
artworkImageButton.contentHorizontalAlignment = .fill
31+
artworkImageButton.contentVerticalAlignment = .fill
32+
artworkImageButton.rx.tap
33+
.subscribe(onNext: { [unowned self] in
34+
let url = URL(string: "nowplaying-ios-nnsnodnb")!
35+
self.extensionContext?.open(url, completionHandler: nil)
36+
})
37+
.disposed(by: disposeBag)
38+
}
39+
}
40+
@IBOutlet private weak var songNameScrollLabel: ScrollFlowLabel! {
41+
didSet {
42+
songNameScrollLabel.textColor = .black
43+
songNameScrollLabel.textAlignment = .left
44+
songNameScrollLabel.font = .boldSystemFont(ofSize: 20)
45+
songNameScrollLabel.pauseInterval = 2
46+
songNameScrollLabel.scrollDirection = .left
47+
songNameScrollLabel.observeApplicationState()
48+
}
49+
}
50+
@IBOutlet private weak var artistNameScrollLabel: ScrollFlowLabel! {
51+
didSet {
52+
artistNameScrollLabel.textColor = .black
53+
artistNameScrollLabel.textAlignment = .left
54+
artistNameScrollLabel.font = .systemFont(ofSize: 17)
55+
artistNameScrollLabel.pauseInterval = 2
56+
artistNameScrollLabel.scrollDirection = .left
57+
artistNameScrollLabel.observeApplicationState()
58+
}
59+
}
60+
@IBOutlet private weak var deniedView: UIView! {
61+
didSet {
62+
viewModel.outputs.viewType.map { $0 != .denied }.bind(to: deniedView.rx.isHidden).disposed(by: disposeBag)
63+
}
64+
}
65+
66+
private let disposeBag = DisposeBag()
67+
68+
private lazy var viewModel: TodayViewModelType = TodayViewModel()
69+
70+
// MARK: - Life cycle
71+
72+
override func viewDidLoad() {
73+
super.viewDidLoad()
74+
75+
viewModel.outputs.artworkImage.bind(to: artworkImageButton.rx.image()).disposed(by: disposeBag)
76+
viewModel.outputs.songName.bind(to: songNameScrollLabel.rx.text).disposed(by: disposeBag)
77+
viewModel.outputs.artistName.bind(to: artistNameScrollLabel.rx.text).disposed(by: disposeBag)
78+
}
79+
80+
override func viewWillAppear(_ animated: Bool) {
81+
super.viewWillAppear(animated)
82+
viewModel.inputs.fetchNowPlayingItem.accept(())
83+
}
84+
85+
func widgetPerformUpdate(completionHandler: (@escaping (NCUpdateResult) -> Void)) {
86+
completionHandler(NCUpdateResult.newData)
87+
}
88+
}
Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
//
2+
// TodayViewModel.swift
3+
// TodayExtension
4+
//
5+
// Created by Oka Yuya on 2020/05/17.
6+
// Copyright © 2020 Yuya Oka. All rights reserved.
7+
//
8+
9+
import Foundation
10+
import MediaPlayer
11+
import RxCocoa
12+
import RxSwift
13+
14+
protocol TodayViewModelInput {
15+
16+
var fetchNowPlayingItem: PublishRelay<Void> { get }
17+
}
18+
19+
protocol TodayViewModelOutput {
20+
21+
var artworkImage: Observable<UIImage?> { get }
22+
var songName: Observable<String> { get }
23+
var artistName: Observable<String> { get }
24+
var viewType: Observable<ViewType> { get }
25+
}
26+
27+
protocol TodayViewModelType {
28+
29+
var inputs: TodayViewModelInput { get }
30+
var outputs: TodayViewModelOutput { get }
31+
}
32+
33+
final class TodayViewModel: TodayViewModelType {
34+
35+
let fetchNowPlayingItem: PublishRelay<Void> = .init()
36+
37+
var inputs: TodayViewModelInput { return self }
38+
var outputs: TodayViewModelOutput { return self }
39+
var artworkImage: Observable<UIImage?> {
40+
return nowPlayingItem.map { $0.artwork?.image }
41+
}
42+
var songName: Observable<String> {
43+
return nowPlayingItem.map { $0.title ?? "" }
44+
}
45+
var artistName: Observable<String> {
46+
return nowPlayingItem.map { $0.artist ?? "" }
47+
}
48+
var viewType: Observable<ViewType> {
49+
return libraryAuthorizationStatus.map { $0 == .authorized ? .common : .denied }.share(replay: 1, scope: .whileConnected)
50+
}
51+
52+
private let disposeBag = DisposeBag()
53+
private let nowPlayingItem: PublishRelay<MPMediaItem> = .init()
54+
private let libraryAuthorizationStatus: PublishRelay<MPMediaLibraryAuthorizationStatus> = .init()
55+
56+
init() {
57+
MPMusicPlayerController.systemMusicPlayer.beginGeneratingPlaybackNotifications()
58+
59+
fetchNowPlayingItem
60+
.filter { MPMediaLibrary.authorizationStatus() != .authorized }
61+
.compactMap { MPMusicPlayerController.systemMusicPlayer.nowPlayingItem }
62+
.bind(to: nowPlayingItem)
63+
.disposed(by: disposeBag)
64+
65+
MPMediaLibrary.requestAuthorization { [weak self] (status) in
66+
if status == .authorized {
67+
DispatchQueue.main.async {
68+
guard let nowPlayingItem = MPMusicPlayerController.systemMusicPlayer.nowPlayingItem else { return }
69+
self?.nowPlayingItem.accept(nowPlayingItem)
70+
}
71+
}
72+
self?.libraryAuthorizationStatus.accept(status)
73+
}
74+
75+
// 曲が変更されたら通知される
76+
NotificationCenter.default.rx.notification(.MPMusicPlayerControllerNowPlayingItemDidChange, object: nil)
77+
.compactMap { $0.object as? MPMusicPlayerController }
78+
.compactMap { $0.nowPlayingItem }
79+
.share()
80+
.bind(to: nowPlayingItem)
81+
.disposed(by: disposeBag)
82+
}
83+
84+
deinit {
85+
MPMusicPlayerController.systemMusicPlayer.endGeneratingPlaybackNotifications()
86+
}
87+
}
88+
89+
// MARK: - TodayViewModelInput
90+
91+
extension TodayViewModel: TodayViewModelInput {}
92+
93+
// MARK: - TodayViewModelOutput
94+
95+
extension TodayViewModel: TodayViewModelOutput {}

0 commit comments

Comments
 (0)