Adding stubs for SwiftUI version of records screen

This commit is contained in:
Selim Mustafaev 2025-03-11 22:10:19 +03:00
parent 593deaf6f6
commit c63ff50b00
9 changed files with 269 additions and 1 deletions

View File

@ -134,6 +134,12 @@
7A8AB76525A0DB8F00ECF2C1 /* BundleVersion.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A8AB76425A0DB8F00ECF2C1 /* BundleVersion.swift */; };
7A912F372D381B7400002938 /* LicensePlateView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A912F362D381B7400002938 /* LicensePlateView.swift */; };
7A91894F29A2BD8700519C74 /* GestureRecognizers.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A91894E29A2BD8700519C74 /* GestureRecognizers.swift */; };
7A9519792D80B3E800E69883 /* AudioRecordService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A9519782D80B3E800E69883 /* AudioRecordService.swift */; };
7A95197B2D80B41600E69883 /* AudioRecordServiceProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A95197A2D80B41600E69883 /* AudioRecordServiceProtocol.swift */; };
7A95197D2D80B43D00E69883 /* AudioRecordError.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A95197C2D80B43D00E69883 /* AudioRecordError.swift */; };
7A9519802D80B6C100E69883 /* RecordsScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A95197F2D80B6C100E69883 /* RecordsScreen.swift */; };
7A9519822D80B6E500E69883 /* RecordsViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A9519812D80B6E500E69883 /* RecordsViewModel.swift */; };
7A9519842D80B72B00E69883 /* RecordsCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A9519832D80B72B00E69883 /* RecordsCoordinator.swift */; };
7A961C6C2C4C3C8600CE2211 /* TextRowView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A961C6B2C4C3C8600CE2211 /* TextRowView.swift */; };
7A961C6E2C4C3C9E00CE2211 /* LinkRowView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A961C6D2C4C3C9E00CE2211 /* LinkRowView.swift */; };
7A96AE2D246B2B7400297C33 /* GoogleSignInController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A96AE2C246B2B7400297C33 /* GoogleSignInController.swift */; };
@ -419,6 +425,12 @@
7A912F362D381B7400002938 /* LicensePlateView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LicensePlateView.swift; sourceTree = "<group>"; };
7A91894E29A2BD8700519C74 /* GestureRecognizers.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GestureRecognizers.swift; sourceTree = "<group>"; };
7A92D0AB240425B100EF3B77 /* ATGMediaBrowser.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = ATGMediaBrowser.framework; path = Carthage/Build/iOS/ATGMediaBrowser.framework; sourceTree = "<group>"; };
7A9519782D80B3E800E69883 /* AudioRecordService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AudioRecordService.swift; sourceTree = "<group>"; };
7A95197A2D80B41600E69883 /* AudioRecordServiceProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AudioRecordServiceProtocol.swift; sourceTree = "<group>"; };
7A95197C2D80B43D00E69883 /* AudioRecordError.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AudioRecordError.swift; sourceTree = "<group>"; };
7A95197F2D80B6C100E69883 /* RecordsScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RecordsScreen.swift; sourceTree = "<group>"; };
7A9519812D80B6E500E69883 /* RecordsViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RecordsViewModel.swift; sourceTree = "<group>"; };
7A9519832D80B72B00E69883 /* RecordsCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RecordsCoordinator.swift; sourceTree = "<group>"; };
7A961C6B2C4C3C8600CE2211 /* TextRowView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TextRowView.swift; sourceTree = "<group>"; };
7A961C6D2C4C3C9E00CE2211 /* LinkRowView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LinkRowView.swift; sourceTree = "<group>"; };
7A96AE2C246B2B7400297C33 /* GoogleSignInController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GoogleSignInController.swift; sourceTree = "<group>"; };
@ -726,6 +738,7 @@
7A1441632C297E9800E79018 /* Screens */ = {
isa = PBXGroup;
children = (
7A95197E2D80B69800E69883 /* RecordsScreen */,
7A5911EC2D63225500EC51BA /* SearchScreen */,
7A131FD12D37B74100DC7755 /* HistoryScreen */,
7AB9FE202D08C28E005DE374 /* EventsScreen */,
@ -802,6 +815,7 @@
7A45FB362C2706D000618694 /* Services */ = {
isa = PBXGroup;
children = (
7A9519772D80B3B200E69883 /* AudioRecordService */,
7AB4E4392D3D3F390006D052 /* VehicleService */,
7A06E0B12C707DD7005731AC /* SettingsService */,
7A60D24B2C5A9D2700D13F7B /* LocationService */,
@ -990,6 +1004,26 @@
path = Location;
sourceTree = "<group>";
};
7A9519772D80B3B200E69883 /* AudioRecordService */ = {
isa = PBXGroup;
children = (
7A9519782D80B3E800E69883 /* AudioRecordService.swift */,
7A95197A2D80B41600E69883 /* AudioRecordServiceProtocol.swift */,
7A95197C2D80B43D00E69883 /* AudioRecordError.swift */,
);
path = AudioRecordService;
sourceTree = "<group>";
};
7A95197E2D80B69800E69883 /* RecordsScreen */ = {
isa = PBXGroup;
children = (
7A95197F2D80B6C100E69883 /* RecordsScreen.swift */,
7A9519812D80B6E500E69883 /* RecordsViewModel.swift */,
7A9519832D80B72B00E69883 /* RecordsCoordinator.swift */,
);
path = RecordsScreen;
sourceTree = "<group>";
};
7AAAFAD12C4D0FB00050410D /* ACImageSlider */ = {
isa = PBXGroup;
children = (
@ -1398,6 +1432,7 @@
7A813DC32508EE4F00CC93B9 /* EventCell.swift in Sources */,
7A1441682C297EFD00E79018 /* NotesViewModel.swift in Sources */,
7AFBE8C02C3024E5003C491D /* ACHud.swift in Sources */,
7A9519842D80B72B00E69883 /* RecordsCoordinator.swift in Sources */,
7AABDE26253350C30041AFC6 /* RxSectionedDataSource.swift in Sources */,
7AAAFADA2C4D1AFE0050410D /* Zoomable.swift in Sources */,
7AC8B2762D6A01C700190706 /* UISearchTextField+Dumb.swift in Sources */,
@ -1468,6 +1503,7 @@
7AAAFADE2C4D23620050410D /* ACImageSliderModel.swift in Sources */,
7A8AB76525A0DB8F00ECF2C1 /* BundleVersion.swift in Sources */,
7AC3555229696E3F00889457 /* UIView+layout.swift in Sources */,
7A9519822D80B6E500E69883 /* RecordsViewModel.swift in Sources */,
7AC355592969746600889457 /* UIControl.swift in Sources */,
7AB67E8E2435D1A000258F61 /* CustomButton.swift in Sources */,
7AC35554296973E100889457 /* ACButton.swift in Sources */,
@ -1500,6 +1536,7 @@
7A1022702C551EFD00B84627 /* LocationEditCoordinator.swift in Sources */,
7A7158042C43EAA200852088 /* OwnersCoordinator.swift in Sources */,
7A17CE4C2A2E850200626A6E /* UISegmentedControl.swift in Sources */,
7A9519802D80B6C100E69883 /* RecordsScreen.swift in Sources */,
7A131FD52D37B76A00DC7755 /* HistoryViewModel.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
@ -1544,6 +1581,7 @@
7A64A21E2C19E8D500284124 /* VehicleAdDto.swift in Sources */,
7AF6D2202677C1680086EA64 /* Filter.swift in Sources */,
7A761C042677F18E0005F28F /* ApiService.swift in Sources */,
7A95197B2D80B41600E69883 /* AudioRecordServiceProtocol.swift in Sources */,
7AF6D21C2677C1680086EA64 /* DebugInfo.swift in Sources */,
7AF6D2122677C12E0086EA64 /* Location.swift in Sources */,
7AF6D2142677C1680086EA64 /* VehicleEvent.swift in Sources */,
@ -1562,6 +1600,7 @@
7A761C07267E8E7F0005F28F /* AnyEncodable.swift in Sources */,
7A64A2032C19DA1000284124 /* VehicleDto.swift in Sources */,
7AB4E4332D3C21C00006D052 /* FileManagerExt.swift in Sources */,
7A9519792D80B3E800E69883 /* AudioRecordService.swift in Sources */,
7AB587322C42D38E00FA7B66 /* StorageServiceProtocol.swift in Sources */,
7A3E12D72C7B42B700EE710D /* UserDefaults+Settings.swift in Sources */,
7AB4E43B2D3D3F4F0006D052 /* VehicleServiceProtocol.swift in Sources */,
@ -1587,6 +1626,7 @@
7A599C362C18AC7F00D47C18 /* ApiError.swift in Sources */,
7A45FB382C27073700618694 /* StorageService.swift in Sources */,
7A64A20A2C19E07100284124 /* VehicleNameDto.swift in Sources */,
7A95197D2D80B43D00E69883 /* AudioRecordError.swift in Sources */,
7AF6D22A2677C3AD0086EA64 /* Exportable.swift in Sources */,
7AF6D2212677C1680086EA64 /* PagedResponse.swift in Sources */,
7AF6D2192677C1680086EA64 /* DateSection.swift in Sources */,

View File

@ -18,6 +18,7 @@ class MainTabController: UITabBarController, UITabBarControllerDelegate {
}
addHistoryTab()
//addRecordsTab()
#if !targetEnvironment(macCatalyst)
addDummyTab()
@ -36,11 +37,20 @@ class MainTabController: UITabBarController, UITabBarControllerDelegate {
historyViewModel = viewModel
}
func addRecordsTab() {
let coordinator = RecordsCoordinator()
let controller = coordinator.start()
controller.tabBarItem = UITabBarItem(title: NSLocalizedString("Records", comment: ""),
image: UIImage(named: "record"), tag: 0)
viewControllers?[1] = controller
}
func addDummyTab() {
let controller = DummyNewController()
controller.tabBarItem = UITabBarItem(title: "", image: UIImage(systemName: "plus"), tag: 0)
viewControllers?.insert(controller, at: 2)
viewControllers?.append(controller)
}
func addSearchTab() {

View File

@ -60,6 +60,8 @@ class SceneDelegate: UIResponder, UIWindowSceneDelegate {
storageService: storageService,
locationService: locationService)
container.register(VehicleServiceProtocol.self, instance: vehicleService)
container.register(AudioRecordServiceProtocol.self, instance: AudioRecordService())
}
func setupRootController(scene: UIScene, openReport number: String?) {

View File

@ -0,0 +1,34 @@
//
// RecordsCoordinator.swift
// AutoCat
//
// Created by Selim Mustafaev on 11.03.2025.
// Copyright © 2025 Selim Mustafaev. All rights reserved.
//
import UIKit
import SwiftUI
import AutoCatCore
@MainActor
final class RecordsCoordinator {
var navController = UINavigationController()
func start() -> UIViewController {
let resolver = ServiceContainer.shared
let viewModel = RecordsViewModel(
recordService: resolver.resolve(AudioRecordServiceProtocol.self),
storageService: resolver.resolve(StorageServiceProtocol.self),
locationService: resolver.resolve(LocationServiceProtocol.self)
)
let view = RecordsScreen(viewModel: viewModel)
let controller = UIHostingController(rootView: view)
let navController = UINavigationController(rootViewController: controller)
self.navController = navController
return navController
}
}

View File

@ -0,0 +1,18 @@
//
// RecordsScreen.swift
// AutoCat
//
// Created by Selim Mustafaev on 11.03.2025.
// Copyright © 2025 Selim Mustafaev. All rights reserved.
//
import SwiftUI
struct RecordsScreen: View {
@State var viewModel: RecordsViewModel
var body: some View {
Text("Hello, World!")
}
}

View File

@ -0,0 +1,28 @@
//
// RecordsViewModel.swift
// AutoCat
//
// Created by Selim Mustafaev on 11.03.2025.
// Copyright © 2025 Selim Mustafaev. All rights reserved.
//
import SwiftUI
import AutoCatCore
@MainActor
@Observable
final class RecordsViewModel {
let recordService: AudioRecordServiceProtocol
let storageService: StorageServiceProtocol
let locationService: LocationServiceProtocol
init(recordService: AudioRecordServiceProtocol,
storageService: StorageServiceProtocol,
locationService: LocationServiceProtocol) {
self.recordService = recordService
self.storageService = storageService
self.locationService = locationService
}
}

View File

@ -0,0 +1,22 @@
//
// AudioRecordError.swift
// AutoCatCore
//
// Created by Selim Mustafaev on 11.03.2025.
// Copyright © 2025 Selim Mustafaev. All rights reserved.
//
import Foundation
public enum AudioRecordError: LocalizedError {
case permissionDenied
case unknown
public var errorDescription: String? {
switch self {
case .permissionDenied: "Permission denied to record audio"
case .unknown: "Unknown error"
}
}
}

View File

@ -0,0 +1,97 @@
//
// AudioRecordService.swift
// AutoCatCore
//
// Created by Selim Mustafaev on 11.03.2025.
// Copyright © 2025 Selim Mustafaev. All rights reserved.
//
import AVFoundation
import Speech
public final class AudioRecordService {
let audioFileSettings: [String : Any] = [
AVFormatIDKey: kAudioFormatMPEG4AAC,
AVSampleRateKey: 44100,
AVNumberOfChannelsKey: 2,
]
var recorder: AVAudioRecorder?
public init() {
}
@discardableResult
func requestRecognitionAuthorization() async -> SFSpeechRecognizerAuthorizationStatus {
let status = SFSpeechRecognizer.authorizationStatus()
guard status == .notDetermined else {
return status
}
return await withCheckedContinuation { continuation in
SFSpeechRecognizer.requestAuthorization { status in
continuation.resume(returning: status)
}
}
}
}
extension AudioRecordService: AudioRecordServiceProtocol {
public func requestPermissions() async -> Bool {
await requestRecognitionAuthorization()
return await AVAudioApplication.requestRecordPermission()
}
public func startRecording(to url: URL) async throws {
guard AVAudioApplication.shared.recordPermission != .denied else {
throw AudioRecordError.permissionDenied
}
switch AVAudioApplication.shared.recordPermission {
case .denied:
throw AudioRecordError.permissionDenied
case .undetermined:
if await AVAudioApplication.requestRecordPermission() {
break
} else {
return
}
case .granted:
break
@unknown default:
throw AudioRecordError.unknown
}
try AVAudioSession.sharedInstance().setCategory(.playAndRecord)
try AVAudioSession.sharedInstance().setActive(true)
recorder = try AVAudioRecorder(url: url, settings: audioFileSettings)
recorder?.record()
}
public func stopRecording() {
recorder?.stop()
recorder = nil
}
public func recognizeText(from url: URL) async -> String? {
guard let recognizer = SFSpeechRecognizer(locale: Locale(identifier: "ru-RU")), recognizer.isAvailable else {
return nil
}
let request = SFSpeechURLRecognitionRequest(url: url)
return await withCheckedContinuation { continuation in
recognizer.recognitionTask(with: request) { result, error in
continuation.resume(returning: result?.bestTranscription.formattedString)
}
}
}
}

View File

@ -0,0 +1,17 @@
//
// AudioRecordServiceProtocol.swift
// AutoCatCore
//
// Created by Selim Mustafaev on 11.03.2025.
// Copyright © 2025 Selim Mustafaev. All rights reserved.
//
import Foundation
public protocol AudioRecordServiceProtocol {
func requestPermissions() async -> Bool
func startRecording(to url: URL) async throws
func stopRecording()
func recognizeText(from url: URL) async -> String?
}