chore: make slider enable state change with isPlaying

This commit is contained in:
sunxiaojian 2021-03-09 16:25:47 +08:00
parent 2d4dbad535
commit 5a17b8a6ee
6 changed files with 82 additions and 108 deletions

View File

@ -49,7 +49,6 @@
2D42FF8F25C8228A004A627A /* UIButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D42FF8E25C8228A004A627A /* UIButton.swift */; };
2D45E5BF25C9549700A6D639 /* PublicTimelineViewModel+State.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D45E5BE25C9549700A6D639 /* PublicTimelineViewModel+State.swift */; };
2D46975E25C2A54100CF4AA9 /* NSLayoutConstraint.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D46975D25C2A54100CF4AA9 /* NSLayoutConstraint.swift */; };
2D46976425C2A71500CF4AA9 /* UIIamge.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D46976325C2A71500CF4AA9 /* UIIamge.swift */; };
2D59819B25E4A581000FB903 /* MastodonConfirmEmailViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D59819A25E4A581000FB903 /* MastodonConfirmEmailViewController.swift */; };
2D5981A125E4A593000FB903 /* MastodonConfirmEmailViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D5981A025E4A593000FB903 /* MastodonConfirmEmailViewModel.swift */; };
2D5981BA25E4D7F8000FB903 /* ThirdPartyMailer in Frameworks */ = {isa = PBXBuildFile; productRef = 2D5981B925E4D7F8000FB903 /* ThirdPartyMailer */; };
@ -288,7 +287,6 @@
2D42FF8E25C8228A004A627A /* UIButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UIButton.swift; sourceTree = "<group>"; };
2D45E5BE25C9549700A6D639 /* PublicTimelineViewModel+State.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "PublicTimelineViewModel+State.swift"; sourceTree = "<group>"; };
2D46975D25C2A54100CF4AA9 /* NSLayoutConstraint.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NSLayoutConstraint.swift; sourceTree = "<group>"; };
2D46976325C2A71500CF4AA9 /* UIIamge.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UIIamge.swift; sourceTree = "<group>"; };
2D59819A25E4A581000FB903 /* MastodonConfirmEmailViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MastodonConfirmEmailViewController.swift; sourceTree = "<group>"; };
2D5981A025E4A593000FB903 /* MastodonConfirmEmailViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MastodonConfirmEmailViewModel.swift; sourceTree = "<group>"; };
2D5A3D0225CF8742002347D6 /* ControlContainableScrollViews.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ControlContainableScrollViews.swift; sourceTree = "<group>"; };
@ -1101,7 +1099,6 @@
2D3F9E0325DFA133004262D9 /* UITapGestureRecognizer.swift */,
DB8AF55C25C138B7002E6C99 /* UIViewController.swift */,
2D46975D25C2A54100CF4AA9 /* NSLayoutConstraint.swift */,
2D46976325C2A71500CF4AA9 /* UIIamge.swift */,
2DF123A625C3B0210020F248 /* ActiveLabel.swift */,
DB68586325E619B700F0A850 /* NSKeyValueObservation.swift */,
2D42FF6A25C817D2004A627A /* MastodonContent.swift */,
@ -1614,7 +1611,6 @@
2D206B9225F60EA700143C56 /* UIControl.swift in Sources */,
2D46975E25C2A54100CF4AA9 /* NSLayoutConstraint.swift in Sources */,
2D45E5BF25C9549700A6D639 /* PublicTimelineViewModel+State.swift in Sources */,
2D46976425C2A71500CF4AA9 /* UIIamge.swift in Sources */,
DB8AF55D25C138B7002E6C99 /* UIViewController.swift in Sources */,
2D3F9E0425DFA133004262D9 /* UITapGestureRecognizer.swift in Sources */,
DB4481CC25EE2AFE00BEFB67 /* PollItem.swift in Sources */,

View File

@ -1,55 +0,0 @@
//
// UIIamge.swift
// Mastodon
//
// Created by sxiaojian on 2021/1/28.
//
import UIKit
import CoreImage
import CoreImage.CIFilterBuiltins
extension UIImage {
static func placeholder(size: CGSize = CGSize(width: 1, height: 1), color: UIColor) -> UIImage {
let render = UIGraphicsImageRenderer(size: size)
return render.image { (context: UIGraphicsImageRendererContext) in
context.cgContext.setFillColor(color.cgColor)
context.fill(CGRect(origin: .zero, size: size))
}
}
}
// refs: https://www.hackingwithswift.com/example-code/media/how-to-read-the-average-color-of-a-uiimage-using-ciareaaverage
extension UIImage {
@available(iOS 14.0, *)
var dominantColor: UIColor? {
guard let inputImage = CIImage(image: self) else { return nil }
let filter = CIFilter.areaAverage()
filter.inputImage = inputImage
filter.extent = inputImage.extent
guard let outputImage = filter.outputImage else { return nil }
var bitmap = [UInt8](repeating: 0, count: 4)
let context = CIContext(options: [.workingColorSpace: kCFNull])
context.render(outputImage, toBitmap: &bitmap, rowBytes: 4, bounds: CGRect(x: 0, y: 0, width: 1, height: 1), format: .RGBA8, colorSpace: nil)
return UIColor(red: CGFloat(bitmap[0]) / 255, green: CGFloat(bitmap[1]) / 255, blue: CGFloat(bitmap[2]) / 255, alpha: CGFloat(bitmap[3]) / 255)
}
}
extension UIImage {
func blur(radius: CGFloat) -> UIImage? {
guard let inputImage = CIImage(image: self) else { return nil }
let blurFilter = CIFilter.gaussianBlur()
blurFilter.inputImage = inputImage
blurFilter.radius = Float(radius)
guard let outputImage = blurFilter.outputImage else { return nil }
guard let cgImage = CIContext().createCGImage(outputImage, from: outputImage.extent) else { return nil }
let image = UIImage(cgImage: cgImage, scale: scale, orientation: imageOrientation)
return image
}
}

View File

@ -5,31 +5,66 @@
// Created by sxiaojian on 2021/3/8.
//
import CoreImage
import CoreImage.CIFilterBuiltins
import UIKit
extension UIImage {
class func imageWithColor(color: UIColor, size: CGSize=CGSize(width: 1, height: 1)) -> UIImage? {
UIGraphicsBeginImageContextWithOptions(size, false, 0)
color.setFill()
UIRectFill(CGRect(origin: CGPoint.zero, size: size))
let image = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
return image
}
public func withRoundedCorners(radius: CGFloat? = nil) -> UIImage? {
let maxRadius = min(size.width, size.height) / 2
let cornerRadius: CGFloat
if let radius = radius, radius > 0 && radius <= maxRadius {
cornerRadius = radius
} else {
cornerRadius = maxRadius
static func placeholder(size: CGSize = CGSize(width: 1, height: 1), color: UIColor) -> UIImage {
let render = UIGraphicsImageRenderer(size: size)
return render.image { (context: UIGraphicsImageRendererContext) in
context.cgContext.setFillColor(color.cgColor)
context.fill(CGRect(origin: .zero, size: size))
}
UIGraphicsBeginImageContextWithOptions(size, false, scale)
let rect = CGRect(origin: .zero, size: size)
UIBezierPath(roundedRect: rect, cornerRadius: cornerRadius).addClip()
draw(in: rect)
let image = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
}
}
// refs: https://www.hackingwithswift.com/example-code/media/how-to-read-the-average-color-of-a-uiimage-using-ciareaaverage
extension UIImage {
@available(iOS 14.0, *)
var dominantColor: UIColor? {
guard let inputImage = CIImage(image: self) else { return nil }
let filter = CIFilter.areaAverage()
filter.inputImage = inputImage
filter.extent = inputImage.extent
guard let outputImage = filter.outputImage else { return nil }
var bitmap = [UInt8](repeating: 0, count: 4)
let context = CIContext(options: [.workingColorSpace: kCFNull])
context.render(outputImage, toBitmap: &bitmap, rowBytes: 4, bounds: CGRect(x: 0, y: 0, width: 1, height: 1), format: .RGBA8, colorSpace: nil)
return UIColor(red: CGFloat(bitmap[0]) / 255, green: CGFloat(bitmap[1]) / 255, blue: CGFloat(bitmap[2]) / 255, alpha: CGFloat(bitmap[3]) / 255)
}
}
extension UIImage {
func blur(radius: CGFloat) -> UIImage? {
guard let inputImage = CIImage(image: self) else { return nil }
let blurFilter = CIFilter.gaussianBlur()
blurFilter.inputImage = inputImage
blurFilter.radius = Float(radius)
guard let outputImage = blurFilter.outputImage else { return nil }
guard let cgImage = CIContext().createCGImage(outputImage, from: outputImage.extent) else { return nil }
let image = UIImage(cgImage: cgImage, scale: scale, orientation: imageOrientation)
return image
}
}
public extension UIImage {
func withRoundedCorners(radius: CGFloat? = nil) -> UIImage? {
let maxRadius = min(size.width, size.height) / 2
let cornerRadius: CGFloat = {
guard let radius = radius, radius > 0 else { return maxRadius }
return min(radius, maxRadius)
}()
let render = UIGraphicsImageRenderer(size: size)
return render.image { (_: UIGraphicsImageRendererContext) in
let rect = CGRect(origin: .zero, size: size)
UIBezierPath(roundedRect: rect, cornerRadius: cornerRadius).addClip()
draw(in: rect)
}
}
}

View File

@ -5,13 +5,11 @@
// Created by sxiaojian on 2021/3/8.
//
import os.log
import CoreDataStack
import os.log
import UIKit
final class AudioContainerView: UIView {
static let cornerRadius: CGFloat = 22
let container: UIStackView = {
@ -20,7 +18,7 @@ final class AudioContainerView: UIView {
stackView.distribution = .fill
stackView.alignment = .center
stackView.spacing = 11
stackView.layoutMargins = UIEdgeInsets(top: 0, left: 9, bottom: 0, right: 9)
stackView.layoutMargins = UIEdgeInsets(top: 0, left: 8, bottom: 0, right: 8)
stackView.isLayoutMarginsRelativeArrangement = true
stackView.layer.cornerRadius = AudioContainerView.cornerRadius
stackView.clipsToBounds = true
@ -29,7 +27,7 @@ final class AudioContainerView: UIView {
return stackView
}()
let checkmarkBackgroundView: UIView = {
let playButtonBackgroundView: UIView = {
let view = UIView()
view.layer.cornerRadius = 16
view.clipsToBounds = true
@ -39,7 +37,7 @@ final class AudioContainerView: UIView {
}()
let playButton: UIButton = {
let button = UIButton(type: .custom)
let button = HighlightDimmableButton(type: .custom)
let image = UIImage(systemName: "play.circle.fill", withConfiguration: UIImage.SymbolConfiguration(pointSize: 32, weight: .bold))!
button.setImage(image.withRenderingMode(.alwaysTemplate), for: .normal)
@ -57,7 +55,7 @@ final class AudioContainerView: UIView {
slider.translatesAutoresizingMaskIntoConstraints = false
slider.minimumTrackTintColor = Asset.Colors.Slider.bar.color
slider.maximumTrackTintColor = Asset.Colors.Slider.bar.color
if let image = UIImage.imageWithColor(color: .white, size: CGSize(width: 22, height: 22))?.withRoundedCorners(radius: 11) {
if let image = UIImage.placeholder(size: CGSize(width: 22, height: 22), color: .white).withRoundedCorners(radius: 11) {
slider.setThumbImage(image, for: .normal)
}
return slider
@ -81,13 +79,10 @@ final class AudioContainerView: UIView {
super.init(coder: coder)
_init()
}
}
extension AudioContainerView {
private func _init() {
addSubview(container)
NSLayoutConstraint.activate([
container.topAnchor.constraint(equalTo: topAnchor),
@ -96,14 +91,14 @@ extension AudioContainerView {
bottomAnchor.constraint(equalTo: container.bottomAnchor),
])
//checkmark
checkmarkBackgroundView.addSubview(playButton)
container.addArrangedSubview(checkmarkBackgroundView)
// checkmark
playButtonBackgroundView.addSubview(playButton)
container.addArrangedSubview(playButtonBackgroundView)
NSLayoutConstraint.activate([
playButton.centerXAnchor.constraint(equalTo: checkmarkBackgroundView.centerXAnchor),
playButton.centerYAnchor.constraint(equalTo: checkmarkBackgroundView.centerYAnchor),
checkmarkBackgroundView.heightAnchor.constraint(equalToConstant: 32),
checkmarkBackgroundView.widthAnchor.constraint(equalToConstant: 32),
playButton.centerXAnchor.constraint(equalTo: playButtonBackgroundView.centerXAnchor),
playButton.centerYAnchor.constraint(equalTo: playButtonBackgroundView.centerYAnchor),
playButtonBackgroundView.heightAnchor.constraint(equalToConstant: 32),
playButtonBackgroundView.widthAnchor.constraint(equalToConstant: 32),
])
container.addArrangedSubview(slider)
@ -113,5 +108,4 @@ extension AudioContainerView {
timeLabel.widthAnchor.constraint(equalToConstant: 40),
])
}
}

View File

@ -5,8 +5,8 @@
// Created by sxiaojian on 2021/3/9.
//
import Foundation
import CoreDataStack
import Foundation
import UIKit
class AudioContainerViewModel {
@ -17,10 +17,11 @@ class AudioContainerViewModel {
guard let duration = audioAttachment.meta?.original?.duration else { return }
let audioView = cell.statusView.audioView
audioView.timeLabel.text = duration.asString(style: .positional)
audioView.playButton.publisher(for: .touchUpInside)
.sink { button in
if (button.isSelected) {
.sink { _ in
let isPlaying = AudioPlayer.shared.playbackState.value == .readyToPlay || AudioPlayer.shared.playbackState.value == .playing
if isPlaying {
AudioPlayer.shared.pause()
} else {
if audioAttachment === AudioPlayer.shared.attachment {
@ -39,15 +40,15 @@ class AudioContainerViewModel {
.sink { slider in
let slider = slider as! UISlider
let time = Double(slider.value) * duration
AudioPlayer.shared.seekToTime(time: time)
}
.store(in: &cell.disposeBag)
self.observePlayer(cell:cell, audioAttachment: audioAttachment)
self.observePlayer(cell: cell, audioAttachment: audioAttachment)
if audioAttachment != AudioPlayer.shared.attachment {
self.resetAudioView(audioView: audioView)
}
}
static func observePlayer(
cell: StatusTableViewCell,
audioAttachment: Attachment
@ -61,16 +62,17 @@ class AudioContainerViewModel {
.sink(receiveValue: { time in
audioView.timeLabel.text = time.asString(style: .positional)
if let duration = audioAttachment.meta?.original?.duration, !audioView.slider.isTracking {
audioView.slider.setValue(Float(time/duration), animated: true)
audioView.slider.setValue(Float(time / duration), animated: true)
}
})
.store(in: &cell.disposeBag)
AudioPlayer.shared.playbackState
.receive(on: DispatchQueue.main)
.sink(receiveValue: { playbackState in
if (audioAttachment === AudioPlayer.shared.attachment) {
if audioAttachment === AudioPlayer.shared.attachment {
let isPlaying = playbackState == .playing || playbackState == .readyToPlay
audioView.playButton.isSelected = isPlaying
audioView.slider.isEnabled = isPlaying
if playbackState == .stopped {
self.resetAudioView(audioView: audioView)
}
@ -80,8 +82,10 @@ class AudioContainerViewModel {
})
.store(in: &cell.disposeBag)
}
static func resetAudioView(audioView:AudioContainerView) {
static func resetAudioView(audioView: AudioContainerView) {
audioView.playButton.isSelected = false
audioView.slider.setValue(0, animated: false)
audioView.slider.isEnabled = false
}
}

View File

@ -23,7 +23,7 @@ extension Mastodon.Entity {
public let id: ID
public let type: Type
public let url: String
public let previewURL: String?
public let previewURL: String? // could be nil when attachement is audio
public let remoteURL: String?
public let textURL: String?