feat: 更新视图组件及数据模型

- 在yanaApp中为SplashPage添加忽略安全区域的设置,确保全屏显示。
- 在DynamicsModels中更新MyMomentInfo结构,添加可选字段以兼容不同版本的服务器返回数据。
- 在CommonComponents中将LoginBackgroundView的背景图替换为蓝色,简化视图。
- 在MainPage中为内容添加忽略安全区域的设置,提升布局一致性。
- 在MePage中新增MePageViewModel,优化用户信息管理逻辑,支持动态列表的加载和错误处理。
- 在SplashPage中调整过渡动画时长,提升用户体验。
This commit is contained in:
edwinQQQ
2025-09-26 14:57:34 +08:00
parent 6b960f53b4
commit 07265c01db
7 changed files with 266 additions and 41 deletions

View File

@@ -242,28 +242,39 @@ struct PublishFeedData: Codable, Equatable {
/// - /dynamic/getMyDynamic
struct MyMomentInfo: Codable, Equatable, Sendable {
let content: String
//
let dynamicId: Int?
let uid: Int
let publishTime: Int64
let nick: String?
let avatar: String?
let type: Int
let content: String
let likeCount: Int?
let isLike: Bool?
let commentCount: Int?
let publishTime: Int64
let worldId: Int?
let status: Int?
let playCount: Int?
let dynamicResList: [MomentsPicture]? // /
// MomentsInfo
func toMomentsInfo() -> MomentsInfo {
return MomentsInfo(
dynamicId: 0, // dynamicId
dynamicId: dynamicId ?? 0,
uid: uid,
nick: "", //
avatar: "", //
nick: nick ?? "",
avatar: avatar ?? "",
type: type,
content: content,
likeCount: 0, //
isLike: false, //
commentCount: 0, //
publishTime: Int(publishTime / 1000), //
worldId: 0, // worldId
status: 1, //
playCount: nil,
dynamicResList: nil,
likeCount: likeCount ?? 0,
isLike: isLike ?? false,
commentCount: commentCount ?? 0,
publishTime: Int(publishTime / 1000),
worldId: worldId ?? 0,
status: status ?? 1,
playCount: playCount,
dynamicResList: dynamicResList,
gender: nil,
squareTop: nil,
topicTop: nil,

View File

@@ -112,10 +112,11 @@ struct LiquidGlassBackground: View {
// MARK: -
struct LoginBackgroundView: View {
var body: some View {
Image("bg")
.resizable()
.aspectRatio(contentMode: .fill)
.ignoresSafeArea(.all)
Color.blue
// Image("bg")
// .resizable()
// .aspectRatio(contentMode: .fill)
// .ignoresSafeArea(.all)
}
}

View File

@@ -46,8 +46,9 @@ struct MainPage: View {
.padding(.horizontal, 24)
.padding(.bottom, 100)
}
}
}.ignoresSafeArea(.all)
}
.toolbar(.hidden)
}
.onAppear {
viewModel.onLogout = onLogout

View File

@@ -3,31 +3,146 @@ import SwiftUI
struct MePage: View {
let onLogout: () -> Void
@State private var isShowingSettings: Bool = false
@StateObject private var viewModel = MePageViewModel()
//
@State private var previewItem: PreviewItem? = nil
@State private var previewCurrentIndex: Int = 0
var body: some View {
ZStack(alignment: .topTrailing) {
VStack {
Text("Me View")
.font(.title)
.foregroundColor(.white)
Text("This is a simplified MeView")
.font(.body)
.foregroundColor(.white.opacity(0.8))
}
ZStack {
//
MomentListBackgroundView()
Button(action: {
isShowingSettings = true
}) {
Image(systemName: "gearshape")
.font(.system(size: 24, weight: .medium))
.foregroundColor(.white)
.frame(width: 40, height: 40)
.background(Color.black.opacity(0.3))
.clipShape(Circle())
VStack(spacing: 0) {
// + + ID +
ZStack(alignment: .topTrailing) {
VStack(spacing: 12) {
AsyncImage(url: URL(string: viewModel.avatarURL)) { image in
image.resizable().scaledToFill()
} placeholder: {
Image(systemName: "person.circle.fill")
.resizable()
.scaledToFill()
.foregroundColor(.gray)
}
.frame(width: 132, height: 132)
.clipShape(Circle())
.overlay(Circle().stroke(Color.white, lineWidth: 3))
.shadow(color: .black.opacity(0.25), radius: 10, x: 0, y: 6)
Text(viewModel.nickname.isEmpty ? "未知用户" : viewModel.nickname)
.font(.system(size: 34, weight: .semibold))
.foregroundColor(.white)
.lineLimit(1)
.minimumScaleFactor(0.6)
if viewModel.userId > 0 {
HStack(spacing: 6) {
Text("ID:\(viewModel.userId)")
.font(.system(size: 16))
.foregroundColor(.white.opacity(0.8))
Image(systemName: "doc.on.doc")
.foregroundColor(.white.opacity(0.8))
}
}
}
.frame(maxWidth: .infinity)
.padding(.top, 24)
Button(action: { isShowingSettings = true }) {
Image(systemName: "gearshape")
.font(.system(size: 24, weight: .medium))
.foregroundColor(.white)
.frame(width: 40, height: 40)
.background(Color.black.opacity(0.3))
.clipShape(Circle())
}
.padding(.trailing, 16)
.padding(.top, 8)
}
.padding(.bottom, 8)
//
if !viewModel.moments.isEmpty {
ScrollView {
LazyVStack(spacing: 16) {
ForEach(Array(viewModel.moments.enumerated()), id: \.offset) { index, moment in
MomentListItem(
moment: moment,
onImageTap: { images, tappedIndex in
previewCurrentIndex = tappedIndex
previewItem = PreviewItem(images: images, index: tappedIndex)
}
)
.padding(.horizontal, 16)
.onAppear {
if index == viewModel.moments.count - 3 {
viewModel.loadMoreData()
}
}
}
if viewModel.isLoadingMore {
HStack {
ProgressView()
.progressViewStyle(CircularProgressViewStyle(tint: .white))
.scaleEffect(0.8)
Text("加载更多...")
.font(.system(size: 14))
.foregroundColor(.white.opacity(0.8))
}
.padding(.vertical, 20)
}
if !viewModel.hasMore && !viewModel.moments.isEmpty {
Text("没有更多数据了")
.font(.system(size: 14))
.foregroundColor(.white.opacity(0.6))
.padding(.vertical, 20)
}
}
.padding(.bottom, 160)
}
.refreshable { await viewModel.refreshData() }
} else if viewModel.isLoading {
ProgressView()
.progressViewStyle(CircularProgressViewStyle(tint: .white))
.padding(.top, 20)
} else if let error = viewModel.errorMessage {
VStack(spacing: 16) {
Text(error)
.font(.system(size: 14))
.foregroundColor(.red)
.multilineTextAlignment(.center)
.padding(.horizontal, 20)
Button(action: { Task { await viewModel.refreshData() } }) {
Text("重试")
.font(.system(size: 14, weight: .medium))
.foregroundColor(.white)
.padding(.horizontal, 20)
.padding(.vertical, 8)
.background(Color.white.opacity(0.2))
.cornerRadius(8)
}
}
.padding(.top, 20)
} else {
VStack(spacing: 12) {
Image(systemName: "doc.text")
.font(.system(size: 32))
.foregroundColor(.white.opacity(0.5))
Text("暂无动态")
.font(.system(size: 16, weight: .medium))
.foregroundColor(.white.opacity(0.7))
}
.frame(maxWidth: .infinity, maxHeight: .infinity)
}
Spacer()
}
.padding(.trailing, 16)
.padding(.top, 8)
.safeAreaPadding(.top, 8)
}
.onAppear { viewModel.onAppear() }
.onReceive(NotificationCenter.default.publisher(for: .init("CreateFeedPublished"))) { _ in
Task { await viewModel.refreshData() }
}
.sheet(isPresented: $isShowingSettings) {
SettingPage(
@@ -39,6 +154,15 @@ struct MePage: View {
)
.navigationBarHidden(true)
}
//
.sheet(item: $previewItem) { item in
ImagePreviewPager(
images: item.images as [String],
currentIndex: $previewCurrentIndex
) {
previewItem = nil
}
}
}
}

View File

@@ -4,7 +4,7 @@ struct SplashPage: View {
@State private var showLogin = false
@State private var showMain = false
@State private var hasCheckedAuth = false
private let splashTransitionAnimation: Animation = .easeInOut(duration: 0.25)
private let splashTransitionAnimation: Animation = .easeInOut(duration: 0.5)
var body: some View {
Group {
@@ -56,7 +56,6 @@ struct SplashPage: View {
}
}
}
.ignoresSafeArea()
}
}

View File

@@ -0,0 +1,88 @@
import Foundation
import SwiftUI
@MainActor
final class MePageViewModel: ObservableObject {
@Published var userId: Int = 0
@Published var nickname: String = ""
@Published var avatarURL: String = ""
@Published var moments: [MomentsInfo] = []
@Published var isLoading: Bool = false
@Published var isLoadingMore: Bool = false
@Published var errorMessage: String? = nil
@Published var hasMore: Bool = true
private var page: Int = 1
private let pageSize: Int = 20
func onAppear() {
Task { @MainActor in
await loadCurrentUser()
// Tab
if moments.isEmpty {
await refreshData()
}
}
}
func refreshData() async {
page = 1
hasMore = true
errorMessage = nil
isLoading = true
moments.removeAll()
defer { isLoading = false }
await fetchMyMoments(page: page)
}
func loadMoreData() {
guard !isLoadingMore, hasMore else { return }
isLoadingMore = true
Task { @MainActor in
defer { isLoadingMore = false }
page += 1
await fetchMyMoments(page: page)
}
}
private func loadCurrentUser() async {
// /Keychain
if let account = await UserInfoManager.getAccountModel() {
if let uidString = account.uid, let uid = Int(uidString) {
userId = uid
}
// UserInfo
if let info = await UserInfoManager.getUserInfo() {
nickname = info.nick ?? nickname
avatarURL = info.avatar ?? avatarURL
}
}
//
if nickname.isEmpty { nickname = "未知用户" }
}
private func fetchMyMoments(page: Int) async {
guard userId > 0 else {
errorMessage = "未登录或用户ID无效"
return
}
let api: any APIServiceProtocol & Sendable = LiveAPIService()
let request = GetMyDynamicRequest(fromUid: userId, uid: userId, page: page, pageSize: pageSize)
do {
let response = try await api.request(request)
if let list = response.data {
let items = list.map { $0.toMomentsInfo() }
if items.isEmpty { hasMore = false }
moments.append(contentsOf: items)
} else {
hasMore = false
}
} catch {
errorMessage = error.localizedDescription
}
}
}

View File

@@ -24,6 +24,7 @@ struct yanaApp: App {
var body: some Scene {
WindowGroup {
SplashPage()
.ignoresSafeArea(.all)
}
}
}