[] 대시보드 공통, 일정, 관리 박스 생성

This commit is contained in:
Seonkyu_Kim 2025-02-11 08:57:42 +09:00
parent 5deb49efe9
commit a7b7797676
25 changed files with 484 additions and 84 deletions

View File

@ -324,7 +324,7 @@
SUPPORTED_PLATFORMS = "iphoneos iphonesimulator"; SUPPORTED_PLATFORMS = "iphoneos iphonesimulator";
SUPPORTS_MACCATALYST = NO; SUPPORTS_MACCATALYST = NO;
SUPPORTS_XR_DESIGNED_FOR_IPHONE_IPAD = NO; SUPPORTS_XR_DESIGNED_FOR_IPHONE_IPAD = NO;
SWIFT_ACTIVE_COMPILATION_CONDITIONS = "DEBUG $(inherited) DEV"; SWIFT_ACTIVE_COMPILATION_CONDITIONS = "DEBUG $(inherited) DEV LOCAL";
SWIFT_EMIT_LOC_STRINGS = YES; SWIFT_EMIT_LOC_STRINGS = YES;
SWIFT_VERSION = 5.0; SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = 1; TARGETED_DEVICE_FAMILY = 1;

View File

@ -7,9 +7,10 @@
import SwiftUI import SwiftUI
// MARK: - ACAMATE // MARK: - ACAMATE
// APPSTORE_URL : https://apps.apple.com/us/app/%EC%95%84%EC%B9%B4%EB%8D%B0%EB%AF%B8%EB%A9%94%EC%9D%B4%ED%8A%B8/id6739448113 // APPSTORE_URL : https://apps.apple.com/us/app/%EC%95%84%EC%B9%B4%EB%8D%B0%EB%AF%B8%EB%A9%94%EC%9D%B4%ED%8A%B8/id6739448113
#if DEV && LOCAL //#if DEV && LOCAL
public let API_URL: String = "http://localhost:5144" //public let API_URL: String = "http://localhost:5144"
#elseif DEV //#else
#if DEV
public let API_URL: String = "https://devacamate.ipstein.myds.me" public let API_URL: String = "https://devacamate.ipstein.myds.me"
#else #else
public let API_URL: String = "https://acamate.ipstein.myds.me" public let API_URL: String = "https://acamate.ipstein.myds.me"

View File

@ -0,0 +1,74 @@
//
// OffsetObservableScollView.swift
// AcaMate
//
// Created by TAnine on 2/10/25.
//
import SwiftUI
import Combine
struct OffsetObservableScrollView<Content: View>: View {
var axes: Axis.Set = .vertical
var showsIndicators: Bool = true
@Binding var scrollOffset: CGPoint
@ViewBuilder var content: (ScrollViewProxy) -> Content
@Namespace var coordinateSpaceName: Namespace.ID
init(
_ axes: Axis.Set = .vertical,
showsIndicators: Bool = true,
scrollOffset: Binding<CGPoint>,
@ViewBuilder content: @escaping (ScrollViewProxy) -> Content
) {
self.axes = axes
self.showsIndicators = showsIndicators
self._scrollOffset = scrollOffset
self.content = content
}
var body: some View {
ScrollView(axes, showsIndicators: showsIndicators) {
ScrollViewReader { scrollViewProxy in
VStack(spacing: 0) {
// anchor (id "TOP")
Color.clear
.frame(height: 1)
.id("TOP")
// . content scrollViewProxy ,
// .
content(scrollViewProxy)
.overlay {
GeometryReader { geometryProxy in
Color.clear
.preference(
key: ScrollOffsetPreferenceKey.self,
value: CGPoint (
x: -geometryProxy.frame(in: .named(coordinateSpaceName)).minX,
y: -geometryProxy.frame(in: .named(coordinateSpaceName)).minY
)
)
}
}
}
}
}
.coordinateSpace(name: coordinateSpaceName)
.onPreferenceChange(ScrollOffsetPreferenceKey.self) { value in
printLog("\(scrollOffset) -> \(value)")
scrollOffset = value
}
}
}
struct ScrollOffsetPreferenceKey: PreferenceKey {
static var defaultValue: CGPoint = .zero
static func reduce(value: inout CGPoint, nextValue: () -> CGPoint) {
value = nextValue()
}
}

View File

@ -39,6 +39,9 @@ struct IntroView: View {
.onAppear { .onAppear {
printLog("IntroView_onAppear") printLog("IntroView_onAppear")
#if LOCAL
naviState.set(act: .RESET, path: .Login)
#else
subscribeAlertAction() subscribeAlertAction()
loadVersion() loadVersion()
.sink { completion in .sink { completion in
@ -66,7 +69,7 @@ struct IntroView: View {
} }
} }
.store(in: &cancellables) .store(in: &cancellables)
#endif
} }
} }

View File

@ -7,49 +7,24 @@
import SwiftUI import SwiftUI
struct AttendanceView: View { struct AttendanceBoxView: View {
@StateObject var btnVM = ButtonViewModel()
@State private var attMoreBtnID = UUID()
@State var monthlyGroup: (Int,Int) = (5,10)//(0,0) @State var monthlyGroup: (Int,Int) = (5,10)//(0,0)
@State var dailyGroup: (Int,Int) = (3,4)//(0,0) @State var dailyGroup: (Int,Int) = (3,4)//(0,0)
var body: some View { var body: some View {
VStack(spacing: 0) { DashBoardView(image: Image(.Icon.attendance),
HStack(spacing: 0){ title: "출석")
Image(.Icon.attendance) {
.resizable()
.frame(width: 24, height: 24, alignment: .center)
.padding([.trailing],4)
Text("출석")
.font(.nps(font: .bold, size: 20))
.foregroundStyle(Color(.Text.detail))
Spacer()
SimpleBtnView(vm: btnVM, id: attMoreBtnID)
}
.padding([.bottom],2)
Rectangle()
.frame(maxWidth: .infinity, maxHeight: 2)
.foregroundStyle(Color(.Second.normal))
.padding([.bottom],12)
HStack(spacing: 4) { HStack(spacing: 4) {
AttCellView(isDaily: false, valueGroup: $monthlyGroup) AttCellView(isDaily: false, valueGroup: $monthlyGroup)
Spacer() Spacer()
// Spacer(minLength: 1)
AttCellView(isDaily: true, valueGroup: $dailyGroup) AttCellView(isDaily: true, valueGroup: $dailyGroup)
} }
.frame(maxWidth: .infinity) .frame(maxWidth: .infinity)
} } moreAction: {
.padding(24)
.onAppear {
btnVM.setSize(for: attMoreBtnID, newWidth: 40, newHeight: 24)
btnVM.setText(for: attMoreBtnID, newText: "더보기", newFont: .nps(size: 12))
btnVM.setTextColor(for: attMoreBtnID, newColor: .Text.disabled)
// MARK: TO-DO // MARK: TO-DO
// //
print("123")
} }
} }
} }
@ -71,7 +46,6 @@ struct AttCellView: View {
.foregroundStyle(Color(.Text.detail)) .foregroundStyle(Color(.Text.detail))
Text("출석").font(.nps(size: 12)) Text("출석").font(.nps(size: 12))
.foregroundStyle(Color(.Text.detail)) .foregroundStyle(Color(.Text.detail))
// Spacer()
} }
.padding(.top,4) .padding(.top,4)
HStack(alignment: .center, spacing: 2) { HStack(alignment: .center, spacing: 2) {
@ -110,7 +84,3 @@ struct AttCellView: View {
} }
} }
} }
#Preview {
AttendanceView()
}

View File

@ -0,0 +1,68 @@
//
// CalendarBoxView.swift
// AcaMate
//
// Created by TAnine on 2/10/25.
//
import SwiftUI
struct CalendarBoxView: View {
@State var summaryCalDataList: [SummaryCalendar]
var body: some View {
DashBoardView(image: Image(.Icon.calendar), title: "최근 일정") {
VStack(spacing: 12) {
ForEach(Array(summaryCalDataList.enumerated()), id: \.offset) { index, data in
CalCellView(summaryCalData: data)
}
}
} moreAction: {
printLog("최근일정의 더보기")
}
}
}
struct CalCellView: View {
var summaryCalData: SummaryCalendar
var body: some View {
VStack(spacing: 8) {
HStack (spacing: 12) {
Image(.Icon.clock)
.resizable()
.frame(width: 24, height: 24, alignment: .center)
Text("\(summaryCalData.date)")
.font(.nps(size: 20))
.foregroundStyle(Color(.Text.detail))
Spacer(minLength: 1)
}
HStack(spacing: 0) {
Spacer(minLength: 1)
Text("\(summaryCalData.summary)")
.font(.nps(size: 20))
.foregroundStyle(Color(.Text.black))
.lineLimit(1)
.minimumScaleFactor(0.5)
.truncationMode(.tail)
}
}
.padding(12)
.background {
RoundedRectangle(cornerRadius: 4)
.stroke(Color(.Second.normal), lineWidth: 2)
.fill(Color(.Second.light))
}
.onTapGesture {
printLog("캘린더 내부 셀 클릭")
// MARK: TO-DO
// Summary
// SummaryCalendar' id
//
}
}
}

View File

@ -0,0 +1,53 @@
//
// DashBoardView.swift
// AcaMate
//
// Created by TAnine on 2/10/25.
//
import SwiftUI
struct DashBoardView<Content: View>: View {
@StateObject var btnVM = ButtonViewModel()
@State private var attMoreBtnID = UUID()
let image: Image
let title: String
@ViewBuilder let content: Content
var moreAction: VOID_TO_VOID?
var body: some View {
VStack(spacing: 0) {
HStack(spacing: 0){
image
.resizable()
.frame(width: 24, height: 24, alignment: .center)
.padding([.trailing],4)
Text("\(title)")
.font(.nps(font: .bold, size: 20))
.foregroundStyle(Color(.Text.detail))
Spacer()
SimpleBtnView(vm: btnVM, id: attMoreBtnID)
}
.padding([.bottom],2)
Rectangle()
.frame(maxWidth: .infinity, maxHeight: 2)
.foregroundStyle(Color(.Second.normal))
.padding([.bottom],12)
content
}
.padding(24)
.onAppear {
btnVM.setSize(for: attMoreBtnID, newWidth: 40, newHeight: 24)
btnVM.setText(for: attMoreBtnID, newText: "더보기", newFont: .nps(size: 12))
btnVM.setTextColor(for: attMoreBtnID, newColor: .Text.disabled)
if let action = moreAction {
btnVM.setAction(for: attMoreBtnID, newAction: action)
}
}
}
}

View File

@ -0,0 +1,81 @@
//
// ManagementBoxView.swift
// AcaMate
//
// Created by TAnine on 2/10/25.
//
import SwiftUI
struct ManagementBoxView: View {
@StateObject var btnVM = ButtonViewModel()
@State private var leftBtnID = UUID()
@State private var rightBtnID = UUID()
var body: some View {
DashBoardView(image: Image(.Icon.edu), title: "학습 관리") {
} moreAction: {
printLog("학습 관리의 더보기")
}
}
}
struct MangCellView: View {
@StateObject var btnVM = ButtonViewModel()
let summaryMang: SummaryManagement
@State var ratioBtn = UUID()
@State var flagBtn = UUID()
var body: some View {
VStack(spacing: 8) {
HStack(spacing: 12) {
Image(.Icon.management)
.resizable()
.frame(width: 24, height: 24, alignment: .center)
Text("\(summaryMang.title)")
.font(.nps(size: 20))
.foregroundStyle(Color(.Text.title))
Spacer(minLength: 1)
}
HStack(spacing: 4) {
Spacer(minLength: 1)
Text("\(summaryMang.teacher)")
.font(.nps(size: 12))
.foregroundStyle(Color(.Text.detail))
Text("이름")
.font(.nps(size: 12))
.foregroundStyle(Color(.Text.detail))
}
}
//
HStack(spacing: 6) {
HStack(spacing: 10) {
CircleBtnView(vm: btnVM, id: ratioBtn)
VStack(spacing: 10) {
HStack(spacing: 4) {
Text("학습")
.font(.nps(size: 16))
.foregroundStyle(Color(.Text.detail))
Text("진도")
.font(.nps(font: .bold, size: 16))
.foregroundStyle(Color(.Text.detail))
Spacer(minLength: 1)
}
HStack(spacing: 4) {
Spacer(minLength: 1)
Text("\(summaryMang.ratio)")
.font(.nps(font: .bold, size: 24))
.foregroundStyle(summaryMang.ratio > 70 ? Color(.Other.blue):Color(.Other.red))
Text("%")
.font(.nps(font: .bold, size: 20))
.foregroundStyle(Color(.Text.detail))
}
}
}
}
}
}

View File

@ -13,43 +13,69 @@ struct MainView: View {
@State var cancellables: Set<AnyCancellable> = [] @State var cancellables: Set<AnyCancellable> = []
@Binding var naviState : NaviState @Binding var naviState : NaviState
@State private var scrollOffset: CGPoint = .zero
@State private var topViewState: Bool = false
var body: some View { var body: some View {
VStack(spacing: 0) { VStack(spacing: 0) {
TopView(titleName: "Name") ZStack {
// MARK: TO-DO OffsetObservableScrollView(scrollOffset: $scrollOffset) { proxy in
//
VStack(spacing: 0) { VStack(spacing: 0) {
TopProfileView() TopProfileView()
.padding(EdgeInsets(top: 0, leading: 0, bottom: 12, trailing: 0)) .padding(EdgeInsets(top: 0, leading: 0, bottom: 12, trailing: 0))
AttendanceView() AttendanceBoxView()
.background { .background {
RoundedRectangle(cornerRadius: 8) RoundedRectangle(cornerRadius: 8)
.foregroundStyle(Color(.Other.cell)) .foregroundStyle(Color(.Other.cell))
} }
.padding(EdgeInsets(top: 12, leading: 24, bottom: 12, trailing: 24)) .padding(EdgeInsets(top: 12, leading: 24, bottom: 12, trailing: 24))
AttendanceView() AttendanceBoxView()
.background { .background {
RoundedRectangle(cornerRadius: 8) RoundedRectangle(cornerRadius: 8)
.foregroundStyle(Color(.Other.cell)) .foregroundStyle(Color(.Other.cell))
} }
.padding(EdgeInsets(top: 12, leading: 24, bottom: 12, trailing: 24)) .padding(EdgeInsets(top: 12, leading: 24, bottom: 12, trailing: 24))
AttendanceView() AttendanceBoxView()
.background { .background {
RoundedRectangle(cornerRadius: 8) RoundedRectangle(cornerRadius: 8)
.foregroundStyle(Color(.Other.cell)) .foregroundStyle(Color(.Other.cell))
} }
.padding(EdgeInsets(top: 12, leading: 24, bottom: 12, trailing: 24)) .padding(EdgeInsets(top: 12, leading: 24, bottom: 12, trailing: 24))
AttendanceView() CalendarBoxView(summaryCalDataList: [
SummaryCalendar(id: "123", date: "2025-02-28", summary: "요약내용입니다."),
SummaryCalendar(id: "123", date: "2025-02-28", summary: "요약내용입니다.")])
.background { .background {
RoundedRectangle(cornerRadius: 8) RoundedRectangle(cornerRadius: 8)
.foregroundStyle(Color(.Other.cell)) .foregroundStyle(Color(.Other.cell))
} }
.padding(EdgeInsets(top: 12, leading: 24, bottom: 12, trailing: 24)) .padding(EdgeInsets(top: 12, leading: 24, bottom: 12, trailing: 24))
} }
}
if topViewState {
VStack(spacing: 0) {
TopView(titleName: "Name")
.transition(.move(edge: .top))
.animation(.easeInOut, value: scrollOffset)
Spacer(minLength: 1)
}
}
}
BottomView() BottomView()
.frame(maxWidth: .infinity) .frame(maxWidth: .infinity)
} }
.frame(maxWidth: .infinity, maxHeight: .infinity) .frame(maxWidth: .infinity, maxHeight: .infinity)
.onChange(of: scrollOffset.y) { oldValue, newValue in
if newValue > 200 && topViewState == false {
topViewState = true
} else if newValue < 20 && topViewState == true{
topViewState = false
}
}
} }
} }

View File

@ -0,0 +1,14 @@
//
// SimpleCalendar.swift
// AcaMate
//
// Created by TAnine on 2/10/25.
//
import Foundation
struct SummaryCalendar {
var id: String
var date: String
var summary: String
}

View File

@ -0,0 +1,16 @@
//
// ManagementData.swift
// AcaMate
//
// Created by TAnine on 2/10/25.
//
import Foundation
struct SummaryManagement {
var id: String
var title: String
var teacher: String
var ratio: Int
var homework: Int
}

View File

@ -72,6 +72,42 @@ struct LoadingModifier: ViewModifier {
} }
} }
struct PressEffect: ViewModifier {
@State private var isPressed = false
var scale: CGFloat
var opacity: CGFloat
var duration: Double
func body(content: Content) -> some View {
content
.scaleEffect(isPressed ? scale : 1.0)
.opacity(isPressed ? opacity : 1.0)
.animation(.easeOut(duration: duration), value: isPressed)
.simultaneousGesture(
LongPressGesture(minimumDuration: 0.01)
.onChanged { _ in isPressed = true }
.onEnded { _ in isPressed = false }
)
}
}
struct PressBackgroundEffect: ViewModifier {
@State private var isPressed = false
var backgroundColor: Color
var duration: Double
func body(content: Content) -> some View {
content
.background(isPressed ? backgroundColor : Color.clear)
.animation(.easeOut(duration: duration), value: isPressed)
.simultaneousGesture(
LongPressGesture(minimumDuration: 0.01)
.onChanged { _ in isPressed = true }
.onEnded { _ in isPressed = false }
)
}
}
extension View { extension View {
/// View /// View
func fullDrawView(_ backColor: Color) -> some View { func fullDrawView(_ backColor: Color) -> some View {
@ -105,15 +141,13 @@ extension View {
} }
} }
@ViewBuilder func pressAnimation(scale: CGFloat = 0.95, opacity: CGFloat = 0.85, duration: Double = 0.1) -> some View {
func switchButtonStyle(_ animate: Bool) -> some View { self.modifier(PressEffect(scale: scale, opacity: opacity, duration: duration))
if animate {
self.buttonStyle(DefaultButtonStyle())
} else {
self.buttonStyle(PlainButtonStyle())
// .allowsHitTesting(false)
}
}
}
//extension BUtton }
}
extension View {
func pressColorAnimation(backgroundColor: Color = Color.black.opacity(0.1), duration: Double = 0.1) -> some View {
self.modifier(PressBackgroundEffect(backgroundColor: backgroundColor, duration: duration))
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

View File

@ -0,0 +1,15 @@
{
"images" : [
{
"filename" : "Clock.png",
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
},
"properties" : {
"preserves-vector-representation" : true
}
}

View File

@ -0,0 +1,15 @@
{
"images" : [
{
"filename" : "Edu.png",
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
},
"properties" : {
"preserves-vector-representation" : true
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

View File

@ -0,0 +1,15 @@
{
"images" : [
{
"filename" : "Left.png",
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
},
"properties" : {
"preserves-vector-representation" : true
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 505 B

View File

@ -0,0 +1,15 @@
{
"images" : [
{
"filename" : "Right.png",
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
},
"properties" : {
"preserves-vector-representation" : true
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 470 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 505 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 470 B