Merge pull request '[] 홈 화면' 대시보드 그림 그리기 완료' (#11) from seonkyu.kim/AcaMate_iOS:main into main

Reviewed-on: https://git.ipstein.myds.me/AcaMate/AcaMate_iOS/pulls/11
This commit is contained in:
김선규 2025-02-12 08:23:06 +00:00
commit 9036502207
15 changed files with 284 additions and 136 deletions

View File

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

View File

@ -27,7 +27,7 @@ struct NavigationView: View {
case .Login :
LoginView(naviState: $naviState)
case .Main:
MainView(naviState: $naviState, menuName: .Home)
MainView(naviState: $naviState)
}
}
}

View File

@ -12,26 +12,14 @@ struct CalendarBoxView: View {
var body: some View {
DashBoardView(image: Image(.Icon.calendar), title: "최근 일정") {
if !summaryCalDataList.isEmpty {
if summaryCalDataList.isEmpty {
EmptyBoxView(title: "최근 일정이 없습니다.")
} else {
VStack(spacing: 12) {
ForEach(Array(summaryCalDataList.enumerated()), id: \.offset) { index, data in
CalCellView(summaryCalData: data)
}
}
} else {
Text("최근 일정이 없습니다.")
.font(.nps(size: 20))
.foregroundStyle(Color(.Text.detail))
.lineLimit(1)
.minimumScaleFactor(0.5)
.truncationMode(.tail)
.frame(maxWidth: .infinity)
.padding([.top,.bottom],12)
.background {
RoundedRectangle(cornerRadius: 4)
.stroke(Color(.Second.normal), lineWidth: 2)
.fill(Color(.Second.light))
}
}
} moreAction: {

View File

@ -0,0 +1,130 @@
//
// DriveBoxView.swift
// AcaMate
//
// Created by TAnine on 2/12/25.
//
import SwiftUI
struct DriveBoxView: View {
@EnvironmentObject var appVM: AppViewModel
@State var summaryDriveList: [SummaryDrive]
var body: some View {
DashBoardView(image: Image(.Icon.drive), title: "배차 현황") {
if summaryDriveList.isEmpty {
EmptyBoxView(title: "등록된 차량이 없습니다.")
} else {
VStack(spacing: 12) {
ForEach(Array(summaryDriveList.enumerated()),id: \.offset) { index, drive in
DriveCellView(summaryDrive: $summaryDriveList[index])
}
}
}
} moreAction: {
appVM.menuName = .Management
}
}
}
struct DriveCellView: View {
@EnvironmentObject var appVM: AppViewModel
@StateObject var btnVM = ButtonViewModel()
@Binding var summaryDrive: SummaryDrive
@State var setNotiBtn = UUID()
var body: some View {
VStack(spacing:0) {
HStack(spacing: 0) {
Image(summaryDrive.isUsable ? .Icon.driveON : .Icon.driveOFF)
.resizable()
.frame(width: 24, height: 24, alignment: .center)
Spacer(minLength: 1)
HStack(alignment: .bottom, spacing: 4) {
Text("[\(summaryDrive.title)]")
.font(.nps(size: 14))
Text("\(summaryDrive.isIn ? "등원" : "하원")")
.font(.nps(font: .bold, size: 20))
Text("차량")
.font(.nps(size: 18))
}
.foregroundStyle(Color(.Text.detail))
Spacer(minLength: 1)
SimpleBtnView(vm: btnVM, id: setNotiBtn)
}
HStack(spacing: 0) {
Spacer(minLength: 1)
Text("\(summaryDrive.driver) 기사님")
.font(.nps(size: 12))
.foregroundStyle(Color(.Text.detail))
}
HStack(spacing: 0) {
Text("\(summaryDrive.isUsable ? (summaryDrive.isDrive ? "운행": "") : "정지" )")
.font(.nps(font: .bold, size: 20))
.foregroundStyle(summaryDrive.isUsable ? (summaryDrive.isDrive ? Color(.Other.red): Color(.Text.detail)) : Color(.Text.disabled))
Spacer(minLength: 1)
}
HStack(alignment: .bottom, spacing: 4) {
Text("출발")
.font(.nps(size: 16))
Text("\(summaryDrive.time)")
.font(.nps(font: .bold, size: 20))
Spacer(minLength: 1)
Text("\(summaryDrive.number)")
.font(.nps(font: .bold, size: 20))
Text("차량")
.font(.nps(size: 16))
}
.foregroundStyle(Color(.Text.detail))
}
.padding(12)
.background {
RoundedRectangle(cornerRadius: 4)
.stroke(summaryDrive.isUsable ? (summaryDrive.isDrive ? Color(.Other.red): Color(.Second.normal)) : Color(.Disable.dark),
lineWidth: 2)
.fill(summaryDrive.isUsable ? (summaryDrive.isDrive ? Color(.Danger.normal): Color(.Second.light)) : Color(.Disable.normal))
}
.onTapGesture {
// MARK: TO-DO
//
printLog(summaryDrive)
}
.onAppear {
btnVM.setImage(for: setNotiBtn, newImage: summaryDrive.isAlarm ? Image(.Icon.notificationON) : Image(.Icon.notificationOFF))
btnVM.setSize(for: setNotiBtn, newWidth: 24, newHeight: 24)
btnVM.setAction(for: setNotiBtn) {
if summaryDrive.isUsable {
if summaryDrive.isAlarm{
appVM.alertData = AlertData(body: "알람이 해제되었습니다.",
button: [
ButtonType(name: "확인", role: .cancel) { printLog("차량 알림 변경 알럿") }
])
} else {
appVM.alertData = AlertData(body: "알람이 설정되었습니다.",
button: [
ButtonType(name: "확인", role: .cancel) { printLog("차량 알림 변경 알럿") }
])
}
appVM.showAlert.toggle()
summaryDrive.isAlarm.toggle()
}
}
}
.onChange(of: summaryDrive.isAlarm) { _, _ in
btnVM.setImage(for: setNotiBtn, newImage: summaryDrive.isAlarm ? Image(.Icon.notificationON) : Image(.Icon.notificationOFF))
}
}
}

View File

@ -10,7 +10,6 @@ import SwiftUI
struct HomeView: View {
@State private var scrollOffset: CGPoint = .zero
@State private var topViewState: Bool = false
var body: some View {
VStack(spacing: 0) {
ZStack {
@ -18,27 +17,40 @@ struct HomeView: View {
VStack(spacing: 0) {
TopProfileView(userType: .Student)
// .padding(EdgeInsets(top: 0, leading: 0, bottom: 12, trailing: 0))s
.padding(.bottom,12)
Group {
AttendanceBoxView()
CalendarBoxView(summaryCalDataList: [])
// CalendarBoxView(summaryCalDataList: [])
CalendarBoxView(summaryCalDataList: [
SummaryCalendar(id: "123", date: "2025-02-28", summary: "요약내용입니다."),
SummaryCalendar(id: "123", date: "2025-02-28", summary: "요약내용입니다.")])
ManagementBoxView(managementList: [])
// ManagementBoxView(managementList: [])
ManagementBoxView(managementList: [
SummaryManagement(id: "01", title: "과목 명1", teacher: "A", ratio: 27, homework: 3),
SummaryManagement(id: "02", title: "과목 명2", teacher: "B", ratio: 80, homework: 10),
SummaryManagement(id: "03", title: "과목 명3", teacher: "C", ratio: 13, homework: 2),
SummaryManagement(id: "04", title: "과목 명4", teacher: "D", ratio: 72, homework: 0),
])
// NoticeBoxView(noticeList: [])
NoticeBoxView(noticeList: [
SummaryNotice(id: "00", title: "공지사항1", date: "2025-02-11", new: true),
SummaryNotice(id: "01", title: "공지사항2", date: "2025-01-11", new: false),
SummaryNotice(id: "02", title: "공지사항3", date: "2025-01-01", new: false)
])
DriveBoxView(summaryDriveList: [
SummaryDrive(id: "00", title: "강남행", driver: "A", time: "08:00", number: "1", isUsable: true, isAlarm: true, isDrive: false, isIn: true),
SummaryDrive(id: "01", title: "강남행", driver: "A", time: "12:00", number: "2", isUsable: true, isAlarm: true, isDrive: true, isIn: true),
SummaryDrive(id: "02", title: "강남행", driver: "A", time: "18:00", number: "3", isUsable: false, isAlarm: true, isDrive: false, isIn: true),
SummaryDrive(id: "02", title: "강남행", driver: "A", time: "18:00", number: "3", isUsable: true, isAlarm: true, isDrive: false, isIn: false)
])
}
.background {
RoundedRectangle(cornerRadius: 8)
@ -70,3 +82,21 @@ struct HomeView: View {
}
}
struct EmptyBoxView: View {
let title: String
var body: some View {
Text("\(title)")
.font(.nps(size: 20))
.foregroundStyle(Color(.Text.detail))
.lineLimit(1)
.minimumScaleFactor(0.5)
.truncationMode(.tail)
.frame(maxWidth: .infinity)
.padding([.top,.bottom],12)
.background {
RoundedRectangle(cornerRadius: 4)
.stroke(Color(.Second.normal), lineWidth: 2)
.fill(Color(.Second.light))
}
}
}

View File

@ -16,7 +16,9 @@ struct ManagementBoxView: View {
var body: some View {
DashBoardView(image: Image(.Icon.edu), title: "학습 관리") {
if managementList.count >= 1 {
if managementList.isEmpty {
EmptyBoxView(title: "학습하고 있는 강의가 없습니다.")
} else {
HStack {
SimpleBtnView(vm: btnVM, id: leftBtnID)
Spacer(minLength: 1)
@ -25,21 +27,6 @@ struct ManagementBoxView: View {
.padding([.bottom],12)
MangCellView(summaryMang: $managementList[countNum])
} else {
Text("학습하고 있는 강의가 없습니다.")
.font(.nps(size: 20))
.foregroundStyle(Color(.Text.detail))
.lineLimit(1)
.minimumScaleFactor(0.5)
.truncationMode(.tail)
.frame(maxWidth: .infinity)
.padding([.top,.bottom],12)
.background {
RoundedRectangle(cornerRadius: 4)
.stroke(Color(.Second.normal), lineWidth: 2)
.fill(Color(.Second.light))
}
}
} moreAction: {
@ -93,12 +80,9 @@ struct MangCellView: View {
.foregroundStyle(Color(.Text.title))
Spacer(minLength: 1)
}
HStack(spacing: 4) {
HStack(spacing: 0) {
Spacer(minLength: 1)
Text("\(summaryMang.teacher)")
.font(.nps(size: 12))
.foregroundStyle(Color(.Text.detail))
Text("선생님")
Text("\(summaryMang.teacher) 선생님")
.font(.nps(size: 12))
.foregroundStyle(Color(.Text.detail))
}

View File

@ -12,14 +12,18 @@ struct NoticeBoxView: View {
var body: some View {
DashBoardView(image: Image(.Icon.notice), title: "공지사항") {
ForEach(Array(noticeList.enumerated()), id: \.offset) { index, notice in
if index < 3 {
NotiCellView(summaryNoti: notice)
if noticeList.isEmpty {
EmptyBoxView(title: "공지가 없습니다.")
} else {
VStack(spacing: 12) {
ForEach(Array(noticeList.enumerated()), id: \.offset) { index, notice in
if index < 3 {
NotiCellView(summaryNoti: notice)
}
}
}
}
//
} moreAction: {
// MARK: TO-DO
@ -51,8 +55,19 @@ struct NotiCellView: View {
.foregroundStyle(Color(.Text.detail))
Text("\(summaryNoti.date)")
.font(.nps(size: 12))
.frame(width: 80)
.foregroundStyle(Color(.Text.detail))
}
}
.padding(12)
.background {
RoundedRectangle(cornerRadius: 4)
.stroke(Color(.Second.normal), lineWidth: 2)
.fill(Color(.Second.light))
}
.onTapGesture {
printLog("공지사항 내부 셀 클릭")
// MARK: TO-DO
}
}
}

View File

@ -8,8 +8,8 @@
import SwiftUI
struct BottomView: View {
@EnvironmentObject var appVM: AppViewModel
@StateObject var btnVM = ButtonViewModel()
@State var pageType: PageType = .Home
@State private var homeID = UUID()
@State private var managementID = UUID()
@ -17,7 +17,10 @@ struct BottomView: View {
@State private var calendarID = UUID()
@State private var etcID = UUID()
@Binding var menuName: MenuName
let btnType: [MenuName] = [.Home, .Management, .Chatting, .Calendar, .Etc]
// var idList: [UUID] = []
var body: some View {
let idList: [UUID] = [homeID,managementID,chattingID,calendarID,etcID]
@ -27,7 +30,6 @@ struct BottomView: View {
if index != idList.count-1 {
Spacer(minLength: 1)
}
}
}
.padding(EdgeInsets(top: 12, leading: 24, bottom: 0, trailing: 24))
@ -39,9 +41,9 @@ struct BottomView: View {
}
.frame(maxWidth: .infinity)
.onAppear {
let idList: [UUID] = [homeID,managementID,chattingID,calendarID,etcID]
//["", " ", "", "", ""]
let btnType: [MenuName] = [.Home, .Management, .Chatting, .Calendar, .Etc]
// idList = [homeID,managementID,chattingID,calendarID,etcID]
let btnImage: [Image] = [Image(.Icon.home),Image(.Icon.management),Image(.Icon.chatting),Image(.Icon.calendar),Image(.Icon.etc)]
idList.enumerated().forEach { (index, id) in
@ -50,19 +52,24 @@ struct BottomView: View {
btnVM.setText(for: id, newText: btnType[index].rawValue,
newFont: .nps(font: .bold, size: 6))
btnVM.setImage(for: id, newImage: btnImage[index])
btnVM.setAction(for: id) {
if btnType[index] == appVM.menuName {
btnVM.setIsSelected(for: id, newValue: true)
idList.forEach {
if $0 != id {
btnVM.setIsSelected(for: $0, newValue: false)
}
}
menuName = btnType[index]
}
btnVM.setAction(for: id) {
appVM.menuName = btnType[index]
}
}
}
.onChange(of: appVM.menuName) { old, new in
// let idList: [UUID] = [homeID,managementID,chattingID,calendarID,etcID]
if let oldID = btnType.firstIndex(of: old) {
btnVM.setIsSelected(for: idList[oldID], newValue: false)
}
if let newID = btnType.firstIndex(of: new) {
btnVM.setIsSelected(for: idList[newID], newValue: true)
}
btnVM.setIsSelected(for: idList[pageType.rawValue], newValue: true)
}
}

View File

@ -9,20 +9,16 @@ import SwiftUI
import Combine
struct MainView: View {
@EnvironmentObject var appVM: AppViewModel
@EnvironmentObject var alertController: AlertController
@State var cancellables: Set<AnyCancellable> = []
@Binding var naviState : NaviState
@State var menuName: MenuName
// @State private var scrollOffset: CGPoint = .zero
// @State private var topViewState: Bool = false
var body: some View {
VStack(spacing: 0) {
Group {
switch menuName {
switch appVM.menuName {
case .Home:
HomeView()
case .Management:
@ -37,19 +33,11 @@ struct MainView: View {
}
Spacer(minLength: 1)
BottomView(menuName: $menuName)
BottomView()
.frame(maxWidth: .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

@ -93,5 +93,6 @@ struct SetAlertData {
ButtonType(name: "확인", role: .cancel, function: nil)
])
}
}

View File

@ -0,0 +1,21 @@
//
// Drive Data.swift
// AcaMate
//
// Created by TAnine on 2/12/25.
//
import Foundation
struct SummaryDrive : Hashable {
var id: String
var title: String
var driver: String
var time: String
var number: String
var isUsable: Bool
var isAlarm: Bool
var isDrive: Bool
var isIn: Bool
}

View File

@ -1,16 +0,0 @@
//
// Page Type.swift
// AcaMate
//
// Created by TAnine on 2/6/25.
//
import Foundation
enum PageType: Int{
case Home = 0
case Management
case Chatting
case Calendar
case Etc
}

View File

@ -11,6 +11,7 @@ import Combine
class AppViewModel: ObservableObject {
@Published var isLoading: Bool = false
@Published var showAlert: Bool = false
@Published var menuName: MenuName = .Home
var alertData: AlertData = .init(body: "")

View File

@ -71,41 +71,41 @@ 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 }
)
}
}
//
//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 {
@ -141,13 +141,12 @@ extension View {
}
}
func pressAnimation(scale: CGFloat = 0.95, opacity: CGFloat = 0.85, duration: Double = 0.1) -> some View {
self.modifier(PressEffect(scale: scale, opacity: opacity, duration: duration))
}
// func pressAnimation(scale: CGFloat = 0.95, opacity: CGFloat = 0.85, duration: Double = 0.1) -> some View {
// self.modifier(PressEffect(scale: scale, opacity: opacity, duration: duration))
// }
}
extension View {
func pressColorAnimation(backgroundColor: Color = Color.black.opacity(0.1), duration: Double = 0.1) -> some View {
self.modifier(PressBackgroundEffect(backgroundColor: backgroundColor, duration: duration))
}
// func pressColorAnimation(backgroundColor: Color = Color.black.opacity(0.1), duration: Double = 0.1) -> some View {
// self.modifier(PressBackgroundEffect(backgroundColor: backgroundColor, duration: duration))
// }
}