mirror of
https://github.com/mastodon/mastodon-ios
synced 2025-04-11 22:58:02 +02:00
chore: make slider enable state change with isPlaying
This commit is contained in:
parent
2d4dbad535
commit
5a17b8a6ee
@ -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 */,
|
||||
|
@ -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
|
||||
}
|
||||
}
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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),
|
||||
])
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
}
|
||||
|
@ -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?
|
||||
|
Loading…
x
Reference in New Issue
Block a user