Compare commits

...

4 Commits

31 changed files with 153 additions and 78 deletions

BIN
Resources/1024x1024.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

BIN
Resources/128x128.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.2 KiB

BIN
Resources/16x16.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.2 KiB

BIN
Resources/256x256.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

BIN
Resources/32x32.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.8 KiB

BIN
Resources/512x512.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

BIN
Resources/64x64.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.9 KiB

BIN
Resources/logo.psd Normal file

Binary file not shown.

View File

@ -394,6 +394,7 @@
DEVELOPMENT_ASSET_PATHS = "\"TeachMate/Preview Content\""; DEVELOPMENT_ASSET_PATHS = "\"TeachMate/Preview Content\"";
ENABLE_PREVIEWS = YES; ENABLE_PREVIEWS = YES;
GENERATE_INFOPLIST_FILE = YES; GENERATE_INFOPLIST_FILE = YES;
INFOPLIST_KEY_CFBundleDisplayName = "小助教";
"INFOPLIST_KEY_UIApplicationSceneManifest_Generation[sdk=iphoneos*]" = YES; "INFOPLIST_KEY_UIApplicationSceneManifest_Generation[sdk=iphoneos*]" = YES;
"INFOPLIST_KEY_UIApplicationSceneManifest_Generation[sdk=iphonesimulator*]" = YES; "INFOPLIST_KEY_UIApplicationSceneManifest_Generation[sdk=iphonesimulator*]" = YES;
"INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents[sdk=iphoneos*]" = YES; "INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents[sdk=iphoneos*]" = YES;
@ -431,6 +432,7 @@
DEVELOPMENT_ASSET_PATHS = "\"TeachMate/Preview Content\""; DEVELOPMENT_ASSET_PATHS = "\"TeachMate/Preview Content\"";
ENABLE_PREVIEWS = YES; ENABLE_PREVIEWS = YES;
GENERATE_INFOPLIST_FILE = YES; GENERATE_INFOPLIST_FILE = YES;
INFOPLIST_KEY_CFBundleDisplayName = "小助教";
"INFOPLIST_KEY_UIApplicationSceneManifest_Generation[sdk=iphoneos*]" = YES; "INFOPLIST_KEY_UIApplicationSceneManifest_Generation[sdk=iphoneos*]" = YES;
"INFOPLIST_KEY_UIApplicationSceneManifest_Generation[sdk=iphonesimulator*]" = YES; "INFOPLIST_KEY_UIApplicationSceneManifest_Generation[sdk=iphonesimulator*]" = YES;
"INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents[sdk=iphoneos*]" = YES; "INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents[sdk=iphoneos*]" = YES;

Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.9 KiB

View File

@ -1,6 +1,7 @@
{ {
"images" : [ "images" : [
{ {
"filename" : "1024x1024.png",
"idiom" : "universal", "idiom" : "universal",
"platform" : "ios", "platform" : "ios",
"size" : "1024x1024" "size" : "1024x1024"
@ -12,6 +13,7 @@
"value" : "dark" "value" : "dark"
} }
], ],
"filename" : "1024x1024 3.png",
"idiom" : "universal", "idiom" : "universal",
"platform" : "ios", "platform" : "ios",
"size" : "1024x1024" "size" : "1024x1024"
@ -23,56 +25,67 @@
"value" : "tinted" "value" : "tinted"
} }
], ],
"filename" : "1024x1024 2.png",
"idiom" : "universal", "idiom" : "universal",
"platform" : "ios", "platform" : "ios",
"size" : "1024x1024" "size" : "1024x1024"
}, },
{ {
"filename" : "16x16.png",
"idiom" : "mac", "idiom" : "mac",
"scale" : "1x", "scale" : "1x",
"size" : "16x16" "size" : "16x16"
}, },
{ {
"filename" : "32x32.png",
"idiom" : "mac", "idiom" : "mac",
"scale" : "2x", "scale" : "2x",
"size" : "16x16" "size" : "16x16"
}, },
{ {
"filename" : "32x32 1.png",
"idiom" : "mac", "idiom" : "mac",
"scale" : "1x", "scale" : "1x",
"size" : "32x32" "size" : "32x32"
}, },
{ {
"filename" : "64x64.png",
"idiom" : "mac", "idiom" : "mac",
"scale" : "2x", "scale" : "2x",
"size" : "32x32" "size" : "32x32"
}, },
{ {
"filename" : "128x128.png",
"idiom" : "mac", "idiom" : "mac",
"scale" : "1x", "scale" : "1x",
"size" : "128x128" "size" : "128x128"
}, },
{ {
"filename" : "256x256.png",
"idiom" : "mac", "idiom" : "mac",
"scale" : "2x", "scale" : "2x",
"size" : "128x128" "size" : "128x128"
}, },
{ {
"filename" : "256x256 1.png",
"idiom" : "mac", "idiom" : "mac",
"scale" : "1x", "scale" : "1x",
"size" : "256x256" "size" : "256x256"
}, },
{ {
"filename" : "512x512.png",
"idiom" : "mac", "idiom" : "mac",
"scale" : "2x", "scale" : "2x",
"size" : "256x256" "size" : "256x256"
}, },
{ {
"filename" : "512x512 1.png",
"idiom" : "mac", "idiom" : "mac",
"scale" : "1x", "scale" : "1x",
"size" : "512x512" "size" : "512x512"
}, },
{ {
"filename" : "1024x1024 1.png",
"idiom" : "mac", "idiom" : "mac",
"scale" : "2x", "scale" : "2x",
"size" : "512x512" "size" : "512x512"

View File

@ -15,7 +15,7 @@ enum PreviewData {
// MARK: - // MARK: -
/// 2025 /// 2025
static let currentSemesterStartDate = Calendar.current.date(from: DateComponents(year: 2025, month: 2, day: 17))! static let currentSemesterStartDate = Calendar.current.date(from: DateComponents(year: 2025, month: 2, day: 24))!
/// ///
static let currentSemesterEndDate = Calendar.current.date(byAdding: .day, value: 18 * 7, to: currentSemesterStartDate)! static let currentSemesterEndDate = Calendar.current.date(byAdding: .day, value: 18 * 7, to: currentSemesterStartDate)!
@ -59,7 +59,7 @@ enum PreviewData {
/// ///
static let classes = [ static let classes = [
"计算机1班", "计算机2班", "软件工程1班", "软件工程2班", "云计算G23-1", "软件G22-3", "软件G22-4", "软件工程2班",
"网络工程1班", "人工智能1班", "数据科学1班", "信息安全1班" "网络工程1班", "人工智能1班", "数据科学1班", "信息安全1班"
] ]

View File

@ -7,9 +7,23 @@
import SwiftUI import SwiftUI
import SwiftData import SwiftData
#if os(macOS)
import AppKit
#endif
// Class to manage window title across the app
class WindowTitleManager: ObservableObject {
@Published var title: String = "TeachMate"
func updateTitle(_ newTitle: String) {
self.title = newTitle
}
}
@main @main
struct TeachMateApp: App { struct TeachMateApp: App {
@StateObject private var windowTitleManager = WindowTitleManager()
var sharedModelContainer: ModelContainer = { var sharedModelContainer: ModelContainer = {
let schema = Schema([ let schema = Schema([
Semester.self, Semester.self,
@ -28,7 +42,17 @@ struct TeachMateApp: App {
var body: some Scene { var body: some Scene {
WindowGroup { WindowGroup {
ContentView() ContentView()
.environmentObject(windowTitleManager)
.onReceive(windowTitleManager.$title) { newTitle in
#if os(macOS)
NSApp.windows.first?.title = newTitle
#endif
}
} }
.modelContainer(sharedModelContainer) .modelContainer(sharedModelContainer)
#if os(macOS)
.windowResizability(.contentSize)
.defaultSize(WindowSizeManager.WindowSizePreset.schedule.size)
#endif
} }
} }

View File

@ -8,7 +8,7 @@
import SwiftUI import SwiftUI
#if os(macOS) #if os(macOS)
// //
struct WindowSizeManager { struct WindowSizeManager {
enum WindowSizePreset { enum WindowSizePreset {
case calendar // - case calendar // -
@ -17,52 +17,29 @@ struct WindowSizeManager {
var size: CGSize { var size: CGSize {
switch self { switch self {
case .calendar: case .calendar:
return CGSize(width: 500, height: 800) // return CGSize(width: 500, height: 800)
case .schedule: case .schedule:
return CGSize(width: 1200, height: 680) // return CGSize(width: 1200, height: 680)
} }
} }
} }
}
//
static func resizeWindow(to preset: WindowSizePreset) { // Scene便使
guard let window = NSApplication.shared.windows.first else { return } extension Scene {
func windowIdealSize(_ preset: WindowSizeManager.WindowSizePreset) -> some Scene {
let newSize = preset.size self.windowResizability(.contentSize)
let currentFrame = window.frame .defaultSize(preset.size)
//
let newOriginX = currentFrame.origin.x + (currentFrame.width - newSize.width) / 2
let newOriginY = currentFrame.origin.y + (currentFrame.height - newSize.height) / 2
let newFrame = NSRect(
x: newOriginX,
y: newOriginY,
width: newSize.width,
height: newSize.height
)
//
window.animator().setFrame(newFrame, display: true, animate: true)
} }
} }
// // View便
struct WindowSizeModifier: ViewModifier {
let preset: WindowSizeManager.WindowSizePreset
func body(content: Content) -> some View {
content
.onAppear {
WindowSizeManager.resizeWindow(to: preset)
}
}
}
// View便使
extension View { extension View {
func adjustWindowSize(to preset: WindowSizeManager.WindowSizePreset) -> some View { func idealFrame(for preset: WindowSizeManager.WindowSizePreset) -> some View {
modifier(WindowSizeModifier(preset: preset)) self.frame(
idealWidth: preset.size.width,
idealHeight: preset.size.height
)
} }
} }
#endif #endif

View File

@ -11,6 +11,7 @@ import SwiftData
struct ContentView: View { struct ContentView: View {
@Environment(\.modelContext) private var modelContext @Environment(\.modelContext) private var modelContext
@Query private var semesters: [Semester] @Query private var semesters: [Semester]
@EnvironmentObject private var windowTitleManager: WindowTitleManager
// Add a state to track the selected navigation item // Add a state to track the selected navigation item
@State private var selectedNavItem: NavItem? = .schedule @State private var selectedNavItem: NavItem? = .schedule
@ -120,14 +121,20 @@ struct ContentView: View {
CourseScheduleView(semester: currentSemester) CourseScheduleView(semester: currentSemester)
.frame(maxWidth: .infinity, maxHeight: .infinity) .frame(maxWidth: .infinity, maxHeight: .infinity)
#if os(macOS) #if os(macOS)
.adjustWindowSize(to: .schedule) .idealFrame(for: .schedule)
#endif #endif
.onAppear {
windowTitleManager.updateTitle(selectedNavItem.rawValue)
}
} else if !semesters.isEmpty { } else if !semesters.isEmpty {
CourseScheduleView(semester: semesters[0]) CourseScheduleView(semester: semesters[0])
.frame(maxWidth: .infinity, maxHeight: .infinity) .frame(maxWidth: .infinity, maxHeight: .infinity)
#if os(macOS) #if os(macOS)
.adjustWindowSize(to: .schedule) .idealFrame(for: .schedule)
#endif #endif
.onAppear {
windowTitleManager.updateTitle(selectedNavItem.rawValue)
}
} else { } else {
VStack { VStack {
Text("请先添加学期") Text("请先添加学期")
@ -135,20 +142,29 @@ struct ContentView: View {
Text("在添加学期后,将自动显示课程表") Text("在添加学期后,将自动显示课程表")
.foregroundColor(.secondary) .foregroundColor(.secondary)
} }
.onAppear {
windowTitleManager.updateTitle(selectedNavItem.rawValue)
}
} }
case .calendar: case .calendar:
if let currentSemester = semesters.first(where: { $0.isCurrent }) { if let currentSemester = semesters.first(where: { $0.isCurrent }) {
AcademicCalendarView(semester: currentSemester) AcademicCalendarView(semester: currentSemester)
.frame(maxWidth: .infinity, maxHeight: .infinity) .frame(maxWidth: .infinity, maxHeight: .infinity)
#if os(macOS) #if os(macOS)
.adjustWindowSize(to: .calendar) .idealFrame(for: .calendar)
#endif #endif
.onAppear {
windowTitleManager.updateTitle(selectedNavItem.rawValue)
}
} else if !semesters.isEmpty { } else if !semesters.isEmpty {
AcademicCalendarView(semester: semesters[0]) AcademicCalendarView(semester: semesters[0])
.frame(maxWidth: .infinity, maxHeight: .infinity) .frame(maxWidth: .infinity, maxHeight: .infinity)
#if os(macOS) #if os(macOS)
.adjustWindowSize(to: .calendar) .idealFrame(for: .calendar)
#endif #endif
.onAppear {
windowTitleManager.updateTitle(selectedNavItem.rawValue)
}
} else { } else {
VStack { VStack {
Text("请先添加学期") Text("请先添加学期")
@ -156,11 +172,20 @@ struct ContentView: View {
Text("在添加学期后,将自动显示教学日历") Text("在添加学期后,将自动显示教学日历")
.foregroundColor(.secondary) .foregroundColor(.secondary)
} }
.onAppear {
windowTitleManager.updateTitle(selectedNavItem.rawValue)
}
} }
case .reminders: case .reminders:
Text("提醒事项内容") Text("提醒事项内容")
.onAppear {
windowTitleManager.updateTitle(selectedNavItem.rawValue)
}
case .courseManager: case .courseManager:
CourseManagerView() CourseManagerView()
.onAppear {
windowTitleManager.updateTitle(selectedNavItem.rawValue)
}
} }
} else if let semester = selectedSemester { } else if let semester = selectedSemester {
// Show semester detail // Show semester detail
@ -199,9 +224,14 @@ struct ContentView: View {
Spacer() Spacer()
} }
.padding() .padding()
.frame(maxWidth: .infinity, alignment: .leading) .onAppear {
windowTitleManager.updateTitle(semester.title)
}
} else { } else {
Text("请选择一个项目") Text("选择一个项目以查看详情")
.onAppear {
windowTitleManager.updateTitle("TeachMate")
}
} }
} }
} }
@ -228,4 +258,5 @@ struct ContentView: View {
#Preview { #Preview {
ContentView() ContentView()
.modelContainer(PreviewData.createSemesterContainer()) .modelContainer(PreviewData.createSemesterContainer())
.environmentObject(WindowTitleManager())
} }

View File

@ -64,6 +64,7 @@ struct CourseFormView: View {
setupInitialValues() setupInitialValues()
} }
} }
.frame(idealWidth: 600, idealHeight: 400)
} }
// Main content view // Main content view
@ -78,7 +79,6 @@ struct CourseFormView: View {
// Left side - Form content // Left side - Form content
formFieldsSection formFieldsSection
} }
.frame(minWidth: 600, idealWidth: 700, maxHeight: .infinity)
} }
} }

View File

@ -87,7 +87,7 @@ struct CourseManagerView: View {
mode: .edit(course), mode: .edit(course),
onSave: { _ in } onSave: { _ in }
) )
.frame(width: 600) .frame(idealWidth: 600, idealHeight: 400)
} else { } else {
CourseFormView( CourseFormView(
mode: .add, mode: .add,
@ -96,7 +96,7 @@ struct CourseManagerView: View {
selectedCourse = newCourse selectedCourse = newCourse
} }
) )
.frame(width: 600) .frame(idealWidth: 600, idealHeight: 400)
} }
} }
.onAppear { .onAppear {

View File

@ -39,6 +39,34 @@ struct CourseScheduleView: View {
courses.filter { $0.semester?.id == semester.id } courses.filter { $0.semester?.id == semester.id }
} }
//
private var hasWeekendCourses: Bool {
filteredCourses.contains { course in
course.sessions.contains { session in
session.weekday == 6 || session.weekday == 7
}
}
}
// 9/10
private var hasLateTimeslotCourses: Bool {
filteredCourses.contains { course in
course.sessions.contains { session in
session.timeSlot == 5 // 5 9/10
}
}
}
//
private var displayWeekdays: [Int] {
hasWeekendCourses ? Array(1...7) : Array(1...5)
}
//
private var displayTimeSlots: [TimeSlot] {
hasLateTimeslotCourses ? TimeSlot.defaultSlots : TimeSlot.defaultSlots.filter { $0.id != 5 }
}
// //
private func getCoursesForCell(weekday: Int, timeSlot: Int) -> [CourseModel] { private func getCoursesForCell(weekday: Int, timeSlot: Int) -> [CourseModel] {
return filteredCourses.filter { course in return filteredCourses.filter { course in
@ -62,7 +90,7 @@ struct CourseScheduleView: View {
headerRow headerRow
// //
ForEach(TimeSlot.defaultSlots) { timeSlot in ForEach(displayTimeSlots) { timeSlot in
courseRow(timeSlot: timeSlot) courseRow(timeSlot: timeSlot)
} }
} }
@ -134,8 +162,8 @@ struct CourseScheduleView: View {
.border(Color.gray.opacity(0.5), width: 0.5) .border(Color.gray.opacity(0.5), width: 0.5)
// //
ForEach(0..<7, id: \.self) { index in ForEach(displayWeekdays, id: \.self) { weekday in
Text(weekdays[index]) Text(weekdays[weekday - 1])
.font(.headline) .font(.headline)
.frame(width: dayCellWidth, height: headerHeight) .frame(width: dayCellWidth, height: headerHeight)
.background(Color.gray.opacity(0.3)) .background(Color.gray.opacity(0.3))
@ -160,7 +188,7 @@ struct CourseScheduleView: View {
.border(Color.gray.opacity(0.5), width: 0.5) .border(Color.gray.opacity(0.5), width: 0.5)
// //
ForEach(1...7, id: \.self) { weekday in ForEach(displayWeekdays, id: \.self) { weekday in
courseCell(weekday: weekday, timeSlot: timeSlot.id) courseCell(weekday: weekday, timeSlot: timeSlot.id)
} }
} }
@ -222,31 +250,31 @@ struct CourseScheduleView: View {
// //
private func addSampleCourses() { private func addSampleCourses() {
var sampleCourses: [CourseModel] = [] // 使 PreviewData
let container = PreviewData.createContainer()
let fetchDescriptor = FetchDescriptor<CourseModel>()
let sampleCourses = try? container.mainContext.fetch(fetchDescriptor)
.filter { $0.semester?.id != semester.id } //
//
let aiCourse = CourseModel(name: "人工智能技术与应用", colorHex: "#FADBD8", isNew: false)
aiCourse.addSession(weekday: 4, timeSlot: 5, location: "CMA101陈栋教室", schoolClass: "环艺G24-1,环艺G24-2,电竞G24-1") for course in sampleCourses ?? [] {
aiCourse.addSession(weekday: 5, timeSlot: 5, location: "CMA101陈栋教室", schoolClass: "视传G24-1,视传G24-2,视传G24-3") //
let newCourse = CourseModel(name: course.name, colorHex: course.colorHex)
let dockerCourse = CourseModel(name: "容器云架构与运维", colorHex: "#D6EAF8", isNew: true)
dockerCourse.addSession(weekday: 3, timeSlot: 3, location: "XXGY402", schoolClass: "云计算G23-1")
dockerCourse.addSession(weekday: 5, timeSlot: 1, location: "XXGY404", schoolClass: "云计算G23-1") //
for session in course.sessions {
let networkCourse = CourseModel(name: "网络组建与维护", colorHex: "#FCF3CF", isNew: true) newCourse.addSession(
networkCourse.addSession(weekday: 1, timeSlot: 3, location: "XXA2305", schoolClass: "软件G23-3") weekday: session.weekday,
networkCourse.addSession(weekday: 2, timeSlot: 3, location: "XXA2401", schoolClass: "软件G23-4") timeSlot: session.timeSlot,
networkCourse.addSession(weekday: 3, timeSlot: 2, location: "XXA2303", schoolClass: "软件G23-3") location: session.location,
networkCourse.addSession(weekday: 3, timeSlot: 4, location: "XXA2504", schoolClass: "软件G23-4") schoolClass: session.schoolCalss
)
sampleCourses.append(aiCourse) }
sampleCourses.append(dockerCourse)
sampleCourses.append(networkCourse) //
newCourse.semester = semester
for course in sampleCourses { modelContext.insert(newCourse)
course.semester = semester
modelContext.insert(course)
} }
} }

View File

@ -58,6 +58,7 @@ struct SessionFormView: View {
setupInitialValues() setupInitialValues()
} }
} }
.frame(idealWidth: 600, idealHeight: 400)
} }
// Main content view // Main content view
@ -76,7 +77,6 @@ struct SessionFormView: View {
Divider() Divider()
.padding(.vertical) .padding(.vertical)
} }
.frame(minWidth: 600, idealWidth: 700, maxHeight: .infinity)
} }
} }