导入 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。
This commit is contained in:
@@ -0,0 +1,257 @@
|
||||
//
|
||||
// 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()
|
||||
}
|
||||
Reference in New Issue
Block a user