feat: 更新日志级别管理及底部导航栏组件化

- 在ContentView中根据编译模式初始化日志级别,确保调试信息的灵活性。
- 在APILogger中使用actor封装日志级别,增强并发安全性。
- 新增通用底部Tab栏组件,优化MainPage中的底部导航逻辑,提升代码可维护性。
- 移除冗余的AppRootView和MainView,简化视图结构,提升代码整洁性。
This commit is contained in:
edwinQQQ
2025-09-18 16:12:18 +08:00
parent 8b4eb9cb7e
commit 90a840c5f3
7 changed files with 295 additions and 297 deletions

View File

@@ -6,17 +6,31 @@ final class CreateFeedViewModel: ObservableObject {
@Published var selectedImages: [UIImage] = []
@Published var isPublishing: Bool = false
@Published var errorMessage: String? = nil
var canPublish: Bool { !content.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty || !selectedImages.isEmpty }
//
var canPublish: Bool { !content.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty }
}
struct CreateFeedPage: View {
@StateObject private var viewModel = CreateFeedViewModel()
let onDismiss: () -> Void
// MARK: - UI State
@FocusState private var isTextEditorFocused: Bool
@State private var isShowingSourceSheet: Bool = false
@State private var isShowingImagePicker: Bool = false
@State private var imagePickerSource: UIImagePickerController.SourceType = .photoLibrary
private let maxCharacters: Int = 500
var body: some View {
GeometryReader { _ in
ZStack {
Color(hex: 0x0C0527).ignoresSafeArea()
Color(hex: 0x0C0527)
.ignoresSafeArea()
.onTapGesture {
//
isTextEditorFocused = false
}
VStack(spacing: 16) {
HStack {
Button(action: onDismiss) {
@@ -58,10 +72,80 @@ struct CreateFeedPage: View {
.padding(.horizontal, 12)
.padding(.vertical, 8)
.scrollContentBackground(.hidden)
.focused($isTextEditorFocused)
.frame(height: 200)
//
VStack { Spacer() }
.overlay(alignment: .bottomTrailing) {
Text("\(viewModel.content.count)/\(maxCharacters)")
.foregroundColor(.white.opacity(0.6))
.font(.system(size: 14))
.padding(.trailing, 8)
.padding(.bottom, 8)
}
}
.frame(height: 200)
.padding(.horizontal, 20)
.onChange(of: viewModel.content) { newValue in
//
if newValue.count > maxCharacters {
viewModel.content = String(newValue.prefix(maxCharacters))
}
}
//
HStack(alignment: .top, spacing: 12) {
Button {
isShowingSourceSheet = true
} label: {
ZStack {
RoundedRectangle(cornerRadius: 16)
.fill(Color(hex: 0x1C143A))
.frame(width: 180, height: 180)
Image(systemName: "plus")
.foregroundColor(.white.opacity(0.6))
.font(.system(size: 36, weight: .semibold))
}
}
.buttonStyle(.plain)
//
if !viewModel.selectedImages.isEmpty {
ScrollView(.horizontal, showsIndicators: false) {
HStack(spacing: 8) {
ForEach(viewModel.selectedImages.indices, id: \.self) { index in
Image(uiImage: viewModel.selectedImages[index])
.resizable()
.scaledToFill()
.frame(width: 100, height: 100)
.clipShape(RoundedRectangle(cornerRadius: 12))
.clipped()
}
}
}
.frame(height: 180)
}
}
.padding(.horizontal, 20)
.confirmationDialog(LocalizedString("createFeed.chooseSource", comment: "Choose Source"), isPresented: $isShowingSourceSheet, titleVisibility: .visible) {
Button(LocalizedString("createFeed.source.album", comment: "Photo Library")) {
imagePickerSource = .photoLibrary
isShowingImagePicker = true
}
Button(LocalizedString("createFeed.source.camera", comment: "Camera")) {
if UIImagePickerController.isSourceTypeAvailable(.camera) {
imagePickerSource = .camera
isShowingImagePicker = true
}
}
Button(LocalizedString("common.cancel", comment: "Cancel"), role: .cancel) {}
}
.sheet(isPresented: $isShowingImagePicker) {
ImagePicker(sourceType: imagePickerSource) { image in
viewModel.selectedImages.append(image)
}
}
if let error = viewModel.errorMessage {
Text(error)
@@ -86,4 +170,41 @@ struct CreateFeedPage: View {
}
}
// MARK: - UIKit Image Picker Wrapper
private struct ImagePicker: UIViewControllerRepresentable {
let sourceType: UIImagePickerController.SourceType
let onImagePicked: (UIImage) -> Void
func makeCoordinator() -> Coordinator {
Coordinator(onImagePicked: onImagePicked)
}
func makeUIViewController(context: Context) -> UIImagePickerController {
let picker = UIImagePickerController()
picker.sourceType = sourceType
picker.allowsEditing = false
picker.delegate = context.coordinator
return picker
}
func updateUIViewController(_ uiViewController: UIImagePickerController, context: Context) {}
final class Coordinator: NSObject, UIImagePickerControllerDelegate, UINavigationControllerDelegate {
let onImagePicked: (UIImage) -> Void
init(onImagePicked: @escaping (UIImage) -> Void) {
self.onImagePicked = onImagePicked
}
func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey : Any]) {
if let image = (info[.originalImage] as? UIImage) ?? (info[.editedImage] as? UIImage) {
onImagePicked(image)
}
picker.dismiss(animated: true)
}
func imagePickerControllerDidCancel(_ picker: UIImagePickerController) {
picker.dismiss(animated: true)
}
}
}