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\"";
ENABLE_PREVIEWS = YES;
GENERATE_INFOPLIST_FILE = YES;
INFOPLIST_KEY_CFBundleDisplayName = "小助教";
"INFOPLIST_KEY_UIApplicationSceneManifest_Generation[sdk=iphoneos*]" = YES;
"INFOPLIST_KEY_UIApplicationSceneManifest_Generation[sdk=iphonesimulator*]" = YES;
"INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents[sdk=iphoneos*]" = YES;
@ -431,6 +432,7 @@
DEVELOPMENT_ASSET_PATHS = "\"TeachMate/Preview Content\"";
ENABLE_PREVIEWS = YES;
GENERATE_INFOPLIST_FILE = YES;
INFOPLIST_KEY_CFBundleDisplayName = "小助教";
"INFOPLIST_KEY_UIApplicationSceneManifest_Generation[sdk=iphoneos*]" = YES;
"INFOPLIST_KEY_UIApplicationSceneManifest_Generation[sdk=iphonesimulator*]" = 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" : [
{
"filename" : "1024x1024.png",
"idiom" : "universal",
"platform" : "ios",
"size" : "1024x1024"
@ -12,6 +13,7 @@
"value" : "dark"
}
],
"filename" : "1024x1024 3.png",
"idiom" : "universal",
"platform" : "ios",
"size" : "1024x1024"
@ -23,56 +25,67 @@
"value" : "tinted"
}
],
"filename" : "1024x1024 2.png",
"idiom" : "universal",
"platform" : "ios",
"size" : "1024x1024"
},
{
"filename" : "16x16.png",
"idiom" : "mac",
"scale" : "1x",
"size" : "16x16"
},
{
"filename" : "32x32.png",
"idiom" : "mac",
"scale" : "2x",
"size" : "16x16"
},
{
"filename" : "32x32 1.png",
"idiom" : "mac",
"scale" : "1x",
"size" : "32x32"
},
{
"filename" : "64x64.png",
"idiom" : "mac",
"scale" : "2x",
"size" : "32x32"
},
{
"filename" : "128x128.png",
"idiom" : "mac",
"scale" : "1x",
"size" : "128x128"
},
{
"filename" : "256x256.png",
"idiom" : "mac",
"scale" : "2x",
"size" : "128x128"
},
{
"filename" : "256x256 1.png",
"idiom" : "mac",
"scale" : "1x",
"size" : "256x256"
},
{
"filename" : "512x512.png",
"idiom" : "mac",
"scale" : "2x",
"size" : "256x256"
},
{
"filename" : "512x512 1.png",
"idiom" : "mac",
"scale" : "1x",
"size" : "512x512"
},
{
"filename" : "1024x1024 1.png",
"idiom" : "mac",
"scale" : "2x",
"size" : "512x512"

View File

@ -15,7 +15,7 @@ enum PreviewData {
// MARK: -
/// 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)!
@ -59,7 +59,7 @@ enum PreviewData {
///
static let classes = [
"计算机1班", "计算机2班", "软件工程1班", "软件工程2班",
"云计算G23-1", "软件G22-3", "软件G22-4", "软件工程2班",
"网络工程1班", "人工智能1班", "数据科学1班", "信息安全1班"
]

View File

@ -7,9 +7,23 @@
import SwiftUI
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
struct TeachMateApp: App {
@StateObject private var windowTitleManager = WindowTitleManager()
var sharedModelContainer: ModelContainer = {
let schema = Schema([
Semester.self,
@ -28,7 +42,17 @@ struct TeachMateApp: App {
var body: some Scene {
WindowGroup {
ContentView()
.environmentObject(windowTitleManager)
.onReceive(windowTitleManager.$title) { newTitle in
#if os(macOS)
NSApp.windows.first?.title = newTitle
#endif
}
}
.modelContainer(sharedModelContainer)
#if os(macOS)
.windowResizability(.contentSize)
.defaultSize(WindowSizeManager.WindowSizePreset.schedule.size)
#endif
}
}

View File

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

View File

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

View File

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

View File

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

View File

@ -39,6 +39,34 @@ struct CourseScheduleView: View {
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] {
return filteredCourses.filter { course in
@ -62,7 +90,7 @@ struct CourseScheduleView: View {
headerRow
//
ForEach(TimeSlot.defaultSlots) { timeSlot in
ForEach(displayTimeSlots) { timeSlot in
courseRow(timeSlot: timeSlot)
}
}
@ -134,8 +162,8 @@ struct CourseScheduleView: View {
.border(Color.gray.opacity(0.5), width: 0.5)
//
ForEach(0..<7, id: \.self) { index in
Text(weekdays[index])
ForEach(displayWeekdays, id: \.self) { weekday in
Text(weekdays[weekday - 1])
.font(.headline)
.frame(width: dayCellWidth, height: headerHeight)
.background(Color.gray.opacity(0.3))
@ -160,7 +188,7 @@ struct CourseScheduleView: View {
.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)
}
}
@ -222,31 +250,31 @@ struct CourseScheduleView: View {
//
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")
aiCourse.addSession(weekday: 5, timeSlot: 5, location: "CMA101陈栋教室", schoolClass: "视传G24-1,视传G24-2,视传G24-3")
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")
let networkCourse = CourseModel(name: "网络组建与维护", colorHex: "#FCF3CF", isNew: true)
networkCourse.addSession(weekday: 1, timeSlot: 3, location: "XXA2305", schoolClass: "软件G23-3")
networkCourse.addSession(weekday: 2, timeSlot: 3, location: "XXA2401", schoolClass: "软件G23-4")
networkCourse.addSession(weekday: 3, timeSlot: 2, location: "XXA2303", schoolClass: "软件G23-3")
networkCourse.addSession(weekday: 3, timeSlot: 4, location: "XXA2504", schoolClass: "软件G23-4")
sampleCourses.append(aiCourse)
sampleCourses.append(dockerCourse)
sampleCourses.append(networkCourse)
for course in sampleCourses {
course.semester = semester
modelContext.insert(course)
for course in sampleCourses ?? [] {
//
let newCourse = CourseModel(name: course.name, colorHex: course.colorHex)
//
for session in course.sessions {
newCourse.addSession(
weekday: session.weekday,
timeSlot: session.timeSlot,
location: session.location,
schoolClass: session.schoolCalss
)
}
//
newCourse.semester = semester
modelContext.insert(newCourse)
}
}

View File

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