Files
admin d6aeede2bc 导入 XuXuCheck 工程(iOS 干化学尿液试纸分析 App)
从桌面工程复制而来,作为本仓库的 Xuxucheck/ 子目录。

为控制仓库体积做了两处删减:
- 移除 Frameworks/opencv2.xcframework 下的两个 visionOS 切片
  (xros-arm64、xros-arm64-simulator,合计约 106 MB),并同步删掉该
  xcframework 内 Info.plist 里对应的两条 AvailableLibraries 记录,
  只保留 ios-arm64 与 ios-arm64_x86_64-simulator。
- 移除全部 .DS_Store 与 xcuserdata/。

已用 xcodebuild 从本目录完整编译验证:BUILD SUCCEEDED,
ProcessXCFramework 以 --platform ios 成功选中框架,产物含 3699 个 cv:: 符号
(OpenCV 确实链入),嵌入 framework 3 个,与移除无用 pod 之后的状态一致。

仓库约 206 MB,其中 OpenCV 203 MB、其余源码与依赖约 3 MB。
2026-09-23 14:17:31 +08:00

258 lines
8.1 KiB
Swift

//
// HomeView.swift
// XuXuCheck
//
// Created by henseycai on 2025/6/11.
//
import SwiftUI
struct HomeView: View {
@State var hasScroll = false
@Namespace var namespace
@State var showMods: Bool = false
@State var showStatusBar = true
@State var selectedID = UUID()
@Environment(Status.self) var status
@Environment(LoginStatus.self) var loginStatus
var body: some View {
ZStack {
Color("background").ignoresSafeArea()
ScrollView {
scrollDetection
//featured
HomeScanView().padding(20)
LazyVGrid(columns: [GridItem(.adaptive(minimum: 140), spacing: 20)]) {
homeModCards
}
.padding(.horizontal, 20)
}
.coordinateSpace(name: "scroll")
.safeAreaInset(edge: .top, content: {
Color.clear.frame(height: 70)
})
.overlay(
NavigationBar(title: Storage.shared.member_name, hasScrolled: $hasScroll)
)
}
.statusBar(hidden: !showStatusBar)
.fullScreenCover(isPresented: $showMods) {
if let idx = homeFuncblocks.firstIndex(where: {$0.id == selectedID}){
if let url = homeFuncblocks[idx].link {
if(url.contains(API.webrecordUrl) && loginStatus.isLogin == false){
LoginView()
}else{
XuXuWebView(currentUrl: url)
}
}
}
}
}
var scrollDetection: some View {
GeometryReader { proxy in
Color.clear
.preference(key: ScrollPreferenceKey.self, value: proxy.frame(in: .named("scroll")).minY)
}
.frame(height: 0)
.onPreferenceChange(ScrollPreferenceKey.self, perform: { value in
withAnimation(.easeInOut) {
if value < 0 {
hasScroll = true
} else {
hasScroll = false
}
}
})
}
struct HomeScanView: View {
let bgcolor = Color(hex: 0x2449f1)
let bgcolor2 = Color(hex: 0x1638d9)
let scale = 9.0
@State private var moveOnCircularPath: Bool = false
@State var showModal = false
@Environment(LoginStatus.self) var loginStatus
var body: some View {
VStack {
ZStack {
Circle()
.stroke(bgcolor,lineWidth: scale * 2)
.frame(width: 26*scale, height: 26*scale)
Circle()
.stroke(bgcolor2,lineWidth: scale)
.frame(width: 23.5*scale, height: 23.5*scale)
Image("lay_cycle1")
.resizable()
.aspectRatio(contentMode: .fit)
.frame(width: 22.5*scale,height: 22.5*scale)
Image("ball2")
.resizable()
.aspectRatio(contentMode: .fit)
.frame(width: 18*scale,height: 18*scale)
Image("lay3")
.resizable()
.aspectRatio(contentMode: .fit)
.frame(width: 18*scale,height: 18*scale)
Image("lay5")
.resizable()
.aspectRatio(contentMode: .fit)
.frame(width: 18*scale,height: 18*scale)
Image("lay4")
.resizable()
.aspectRatio(contentMode: .fit)
.frame(width: 18*scale,height: 18*scale)
Image("lay6")
.resizable()
.aspectRatio(contentMode: .fit)
.frame(width: 4*scale,height: 4*scale)
Image("ball")
.resizable()
.aspectRatio(contentMode: .fit)
.frame(width: 2.2*scale,height: 2.2*scale).offset(x:0,y:10*scale)
.rotationEffect(.degrees(moveOnCircularPath ? 0 : -360))
.task {
withAnimation(.myLinear.delay(0).repeatForever(autoreverses: false)) {
moveOnCircularPath.toggle()
}
}
}.overlay(txtOverlay.offset(x:0,y: -(6*scale)),alignment: .bottom)
}
.onTapGesture {
showModal.toggle()
}
.fullScreenCover(isPresented: $showModal) {
if(loginStatus.isLogin){
ScanView()
}else{
LoginView()
}
}
}
private var txtOverlay: some View {
ZStack{
// Capsule()
// .fill(Color.white.opacity(0.5))
// .frame(width: 120, height: 50)
Text("SCAN").font(.title)
.foregroundColor(.yellow)
}
}
}
var homeModCards : some View {
ForEach(homeFuncblocks ) { block in
Button(action: {
showMods.toggle()
//status.showNavDetail.toggle()
//status.showNavDetail = true
showStatusBar = false
selectedID = block.id
}, label: {
HomeCardItem(namespace: namespace,
block: block,
showMods: $showMods)
})
}
}
struct HomeCardItem: View {
var namespace: Namespace.ID
var block: BlockItem = homeFuncblocks[0]
@Binding var showMods: Bool
var body: some View {
VStack {
Spacer()
VStack(alignment: .center) {
Image(block.image)
.resizable().frame(width: 56,height: 56)
.aspectRatio(contentMode: .fit)
.matchedGeometryEffect(id: "image\(block.id)", in: namespace)
Text(block.title)
.font(.headline.weight(.bold))
.matchedGeometryEffect(id: "title\(block.id)", in: namespace)
.frame(maxWidth: .infinity, alignment: .center)
}
.padding(16)
.background(
Rectangle()
.fill(.ultraThinMaterial)
.mask(RoundedRectangle(cornerRadius: 30, style: .continuous))
.blur(radius: 30)
.matchedGeometryEffect(id: "blur\(block.id)", in: namespace)
)
}
.foregroundStyle(.white)
.background(
Image(block.background)
.resizable()
.aspectRatio(contentMode: .fill)
.matchedGeometryEffect(id: "background\(block.id)", in: namespace)
)
.mask(RoundedRectangle(cornerRadius: 30, style: .continuous)
.matchedGeometryEffect(id: "mask\(block.id)", in: namespace))
.frame(height: 132)
}
}
struct HomeCardItem_Previews: PreviewProvider {
@Namespace static var namespace
static var previews: some View {
HomeCardItem(namespace: namespace, showMods: .constant(true))
}
}
}
#Preview {
HomeView()
}