Editing/deleting audio records
This commit is contained in:
parent
c7a2234b3f
commit
d2dcdbff9d
@ -23,6 +23,7 @@ struct AudioRecordView: View {
|
||||
Spacer(minLength: 0)
|
||||
Text(record.date)
|
||||
.font(.subheadline)
|
||||
.monospacedDigit()
|
||||
}
|
||||
.padding(.trailing)
|
||||
}
|
||||
@ -34,6 +35,7 @@ struct AudioRecordView: View {
|
||||
Image(systemName: record.isPlaying ? "pause.fill" : "play.fill")
|
||||
.padding()
|
||||
}
|
||||
.buttonStyle(.borderless)
|
||||
}
|
||||
|
||||
@ViewBuilder
|
||||
@ -41,6 +43,7 @@ struct AudioRecordView: View {
|
||||
if let duration = record.duration {
|
||||
Text(duration)
|
||||
.font(.subheadline)
|
||||
.monospacedDigit()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -11,7 +11,7 @@ import Foundation
|
||||
|
||||
struct AudioRecordViewModel: Identifiable {
|
||||
|
||||
var id: TimeInterval
|
||||
var id: String
|
||||
var isPlaying: Bool
|
||||
var duration: String?
|
||||
var number: String?
|
||||
|
||||
@ -7,20 +7,28 @@
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
import AutoCatCore
|
||||
|
||||
struct RecordsScreen: View {
|
||||
|
||||
@State var viewModel: RecordsViewModel
|
||||
|
||||
@State var showEditAlert = false
|
||||
@State var numberText = ""
|
||||
@State var selectedRecordId: String = ""
|
||||
|
||||
var body: some View {
|
||||
List {
|
||||
ForEach(viewModel.recordModels) { record in
|
||||
List(viewModel.recordModels) { record in
|
||||
AudioRecordView(record: record)
|
||||
.listRowInsets(EdgeInsets())
|
||||
.swipeActions(allowsFullSwipe: false) {
|
||||
makeActions(for: record)
|
||||
}
|
||||
.contextMenu {
|
||||
makeActions(for: record, useLabels: true)
|
||||
}
|
||||
}
|
||||
.buttonStyle(BorderlessButtonStyle())
|
||||
.listStyle(.plain)
|
||||
.listStyle(.inset)
|
||||
.hud($viewModel.hud)
|
||||
.navigationTitle("Voice records")
|
||||
.onAppear {
|
||||
@ -43,5 +51,30 @@ struct RecordsScreen: View {
|
||||
}
|
||||
}
|
||||
}
|
||||
.noteAlert(
|
||||
title: String(localized: "Edit plate number"),
|
||||
body: $numberText,
|
||||
isPresented: $showEditAlert
|
||||
) { text in
|
||||
Task { await viewModel.editRecord(id: selectedRecordId, number: numberText) }
|
||||
}
|
||||
}
|
||||
|
||||
@ViewBuilder
|
||||
func makeActions(for record: AudioRecordViewModel, useLabels: Bool = false) -> some View {
|
||||
|
||||
Button {
|
||||
selectedRecordId = record.id
|
||||
numberText = record.number ?? ""
|
||||
showEditAlert = true
|
||||
} label: {
|
||||
Label(useLabels ? "Edit plate number" : "", systemImage: "pencil")
|
||||
}
|
||||
|
||||
Button(role: .destructive) {
|
||||
Task { await viewModel.deleteRecord(id: record.id) }
|
||||
} label: {
|
||||
Label(useLabels ? "Delete" : "", systemImage: "trash")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -85,4 +85,22 @@ final class RecordsViewModel: ACHudContainer {
|
||||
hud = .error(error)
|
||||
}
|
||||
}
|
||||
|
||||
func deleteRecord(id: String) async {
|
||||
await wrapWithToast(showProgress: false) { [weak self] in
|
||||
guard let self else { return }
|
||||
try await storageService.deleteRecord(id: id)
|
||||
records.removeAll { $0.id == id }
|
||||
}
|
||||
}
|
||||
|
||||
func editRecord(id: String, number: String) async {
|
||||
await wrapWithToast(showProgress: false) { [weak self] in
|
||||
guard let self else { return }
|
||||
let updatedRecord = try await storageService.updateRecord(id: id, number: number)
|
||||
if let index = records.firstIndex(where: { $0.id == id }) {
|
||||
records[index] = updatedRecord
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -40,5 +40,5 @@ public struct AudioRecordDto: Decodable, Sendable {
|
||||
|
||||
extension AudioRecordDto: Identifiable {
|
||||
|
||||
public var id: TimeInterval { addedDate }
|
||||
public var id: String { path }
|
||||
}
|
||||
|
||||
@ -13,13 +13,8 @@ public final class RecordPlayerService: NSObject {
|
||||
var player: AVAudioPlayer?
|
||||
var record: AudioRecordDto?
|
||||
var onStop: ((AudioRecordDto, Error?) -> Void)?
|
||||
}
|
||||
|
||||
extension RecordPlayerService: RecordPlayerServiceProtocol {
|
||||
|
||||
public func play(record: AudioRecordDto, onStop: ((AudioRecordDto, Error?) -> Void)?) throws {
|
||||
|
||||
player?.stop()
|
||||
func playNewRecord(record: AudioRecordDto, onStop: ((AudioRecordDto, Error?) -> Void)?) throws {
|
||||
|
||||
let url = try FileManager.default.url(for: record.path, in: Constants.audioRecordsFolder)
|
||||
player = try AVAudioPlayer(contentsOf: url)
|
||||
@ -28,19 +23,51 @@ extension RecordPlayerService: RecordPlayerServiceProtocol {
|
||||
self.record = record
|
||||
self.onStop = onStop
|
||||
|
||||
// try AVAudioSession.sharedInstance().setCategory(.playback)
|
||||
// try AVAudioSession.sharedInstance().setActive(true)
|
||||
try activateSession()
|
||||
|
||||
print("==== playing started ====")
|
||||
player?.play()
|
||||
}
|
||||
|
||||
func activateSession() throws {
|
||||
try AVAudioSession.sharedInstance().setCategory(
|
||||
.playback,
|
||||
mode: .default,
|
||||
options: [.duckOthers]
|
||||
)
|
||||
try AVAudioSession.sharedInstance().setActive(true)
|
||||
}
|
||||
|
||||
func deactivateSession() throws {
|
||||
try AVAudioSession.sharedInstance().setActive(
|
||||
false,
|
||||
options: .notifyOthersOnDeactivation
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
extension RecordPlayerService: RecordPlayerServiceProtocol {
|
||||
|
||||
public func play(record: AudioRecordDto, onStop: ((AudioRecordDto, Error?) -> Void)?) throws {
|
||||
guard let player, self.record?.id == record.id else {
|
||||
try playNewRecord(record: record, onStop: onStop)
|
||||
return
|
||||
}
|
||||
|
||||
if player.isPlaying {
|
||||
player.pause()
|
||||
try deactivateSession()
|
||||
} else {
|
||||
try activateSession()
|
||||
player.play()
|
||||
}
|
||||
}
|
||||
|
||||
public func pause() {
|
||||
|
||||
player?.pause()
|
||||
}
|
||||
|
||||
public var currentPlayingId: TimeInterval? {
|
||||
public var currentPlayingId: String? {
|
||||
guard player?.isPlaying == true else {
|
||||
return nil
|
||||
}
|
||||
@ -56,7 +83,6 @@ extension RecordPlayerService: AVAudioPlayerDelegate {
|
||||
return
|
||||
}
|
||||
|
||||
print("==== playing stopped ====")
|
||||
onStop?(record, nil)
|
||||
reset()
|
||||
}
|
||||
@ -66,7 +92,6 @@ extension RecordPlayerService: AVAudioPlayerDelegate {
|
||||
return
|
||||
}
|
||||
|
||||
print("==== playing error ====")
|
||||
onStop?(record, error)
|
||||
reset()
|
||||
}
|
||||
@ -76,5 +101,7 @@ extension RecordPlayerService: AVAudioPlayerDelegate {
|
||||
onStop = nil
|
||||
player = nil
|
||||
record = nil
|
||||
|
||||
try? deactivateSession()
|
||||
}
|
||||
}
|
||||
|
||||
@ -10,7 +10,7 @@ import Foundation
|
||||
|
||||
public protocol RecordPlayerServiceProtocol {
|
||||
|
||||
var currentPlayingId: TimeInterval? { get }
|
||||
var currentPlayingId: String? { get }
|
||||
|
||||
func play(record: AudioRecordDto, onStop: ((AudioRecordDto, Error?) -> Void)?) throws
|
||||
func pause()
|
||||
|
||||
@ -24,7 +24,7 @@ public extension StorageService {
|
||||
.map(\.dto)
|
||||
}
|
||||
|
||||
func deleteRecord(id: TimeInterval) async throws {
|
||||
func deleteRecord(id: String) async throws {
|
||||
|
||||
guard let record = realm.object(ofType: AudioRecord.self, forPrimaryKey: id) else {
|
||||
throw StorageError.recordNotFound
|
||||
@ -35,14 +35,16 @@ public extension StorageService {
|
||||
}
|
||||
}
|
||||
|
||||
func updateRecord(_ record: AudioRecordDto) async throws {
|
||||
func updateRecord(id: String, number: String) async throws -> AudioRecordDto {
|
||||
|
||||
guard let record = realm.object(ofType: AudioRecord.self, forPrimaryKey: record.id) else {
|
||||
guard let record = realm.object(ofType: AudioRecord.self, forPrimaryKey: id) else {
|
||||
throw StorageError.recordNotFound
|
||||
}
|
||||
|
||||
try await realm.asyncWrite {
|
||||
realm.add(record, update: .modified)
|
||||
}
|
||||
record.number = number
|
||||
}
|
||||
|
||||
return record.dto
|
||||
}
|
||||
}
|
||||
|
||||
@ -35,6 +35,6 @@ public protocol StorageServiceProtocol: Sendable {
|
||||
// Audio records
|
||||
func add(record: AudioRecordDto) async throws
|
||||
func loadRecords() async throws -> [AudioRecordDto]
|
||||
func deleteRecord(id: TimeInterval) async throws
|
||||
func updateRecord(_ record: AudioRecordDto) async throws
|
||||
func deleteRecord(id: String) async throws
|
||||
func updateRecord(id: String, number: String) async throws -> AudioRecordDto
|
||||
}
|
||||
|
||||
@ -36,7 +36,7 @@ public struct Formatters {
|
||||
public static let time: DateComponentsFormatter = {
|
||||
|
||||
let formatter = DateComponentsFormatter()
|
||||
formatter.unitsStyle = .abbreviated
|
||||
formatter.unitsStyle = .positional
|
||||
formatter.allowedUnits = [.minute, .second]
|
||||
formatter.zeroFormattingBehavior = .pad
|
||||
return formatter
|
||||
|
||||
Loading…
Reference in New Issue
Block a user