Compare commits
No commits in common. "3b8cbe680caa48d94833cba048670c62414d4146" and "e17871e53d7d5053638ee521a51ad4f4821ff258" have entirely different histories.
3b8cbe680c
...
e17871e53d
Before Width: | Height: | Size: 30 KiB |
Before Width: | Height: | Size: 7.2 KiB |
Before Width: | Height: | Size: 3.2 KiB |
Before Width: | Height: | Size: 13 KiB |
Before Width: | Height: | Size: 3.8 KiB |
Before Width: | Height: | Size: 20 KiB |
Before Width: | Height: | Size: 4.9 KiB |
@ -394,7 +394,6 @@
|
||||
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;
|
||||
@ -432,7 +431,6 @@
|
||||
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;
|
||||
|
Before Width: | Height: | Size: 30 KiB |
Before Width: | Height: | Size: 30 KiB |
Before Width: | Height: | Size: 30 KiB |
Before Width: | Height: | Size: 30 KiB |
Before Width: | Height: | Size: 7.2 KiB |
Before Width: | Height: | Size: 3.2 KiB |
Before Width: | Height: | Size: 13 KiB |
Before Width: | Height: | Size: 13 KiB |
Before Width: | Height: | Size: 3.8 KiB |
Before Width: | Height: | Size: 3.8 KiB |
Before Width: | Height: | Size: 20 KiB |
Before Width: | Height: | Size: 20 KiB |
Before Width: | Height: | Size: 4.9 KiB |
@ -1,7 +1,6 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "1024x1024.png",
|
||||
"idiom" : "universal",
|
||||
"platform" : "ios",
|
||||
"size" : "1024x1024"
|
||||
@ -13,7 +12,6 @@
|
||||
"value" : "dark"
|
||||
}
|
||||
],
|
||||
"filename" : "1024x1024 3.png",
|
||||
"idiom" : "universal",
|
||||
"platform" : "ios",
|
||||
"size" : "1024x1024"
|
||||
@ -25,67 +23,56 @@
|
||||
"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"
|
||||
|
@ -15,7 +15,7 @@ enum PreviewData {
|
||||
// MARK: - 共享的日期和时间
|
||||
|
||||
/// 当前学期开始日期(2025年春季学期)
|
||||
static let currentSemesterStartDate = Calendar.current.date(from: DateComponents(year: 2025, month: 2, day: 24))!
|
||||
static let currentSemesterStartDate = Calendar.current.date(from: DateComponents(year: 2025, month: 2, day: 17))!
|
||||
|
||||
/// 当前学期结束日期
|
||||
static let currentSemesterEndDate = Calendar.current.date(byAdding: .day, value: 18 * 7, to: currentSemesterStartDate)!
|
||||
@ -59,7 +59,7 @@ enum PreviewData {
|
||||
|
||||
/// 常见班级
|
||||
static let classes = [
|
||||
"云计算G23-1", "软件G22-3", "软件G22-4", "软件工程2班",
|
||||
"计算机1班", "计算机2班", "软件工程1班", "软件工程2班",
|
||||
"网络工程1班", "人工智能1班", "数据科学1班", "信息安全1班"
|
||||
]
|
||||
|
||||
|
@ -7,23 +7,9 @@
|
||||
|
||||
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,
|
||||
@ -42,17 +28,7 @@ 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
|
||||
}
|
||||
}
|
||||
|
@ -8,7 +8,7 @@
|
||||
import SwiftUI
|
||||
|
||||
#if os(macOS)
|
||||
// 窗口尺寸预设
|
||||
// 窗口尺寸管理器
|
||||
struct WindowSizeManager {
|
||||
enum WindowSizePreset {
|
||||
case calendar // 教学历视图 - 竖长
|
||||
@ -17,29 +17,52 @@ 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) // 横长窗口
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 扩展Scene以便于使用
|
||||
extension Scene {
|
||||
func windowIdealSize(_ preset: WindowSizeManager.WindowSizePreset) -> some Scene {
|
||||
self.windowResizability(.contentSize)
|
||||
.defaultSize(preset.size)
|
||||
}
|
||||
}
|
||||
// 调整窗口大小
|
||||
static func resizeWindow(to preset: WindowSizePreset) {
|
||||
guard let window = NSApplication.shared.windows.first else { return }
|
||||
|
||||
// 扩展View以便于在视图级别设置理想尺寸
|
||||
extension View {
|
||||
func idealFrame(for preset: WindowSizeManager.WindowSizePreset) -> some View {
|
||||
self.frame(
|
||||
idealWidth: preset.size.width,
|
||||
idealHeight: preset.size.height
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
// 视图修饰符,用于调整窗口大小
|
||||
struct WindowSizeModifier: ViewModifier {
|
||||
let preset: WindowSizeManager.WindowSizePreset
|
||||
|
||||
func body(content: Content) -> some View {
|
||||
content
|
||||
.onAppear {
|
||||
WindowSizeManager.resizeWindow(to: preset)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 扩展View以便于使用
|
||||
extension View {
|
||||
func adjustWindowSize(to preset: WindowSizeManager.WindowSizePreset) -> some View {
|
||||
modifier(WindowSizeModifier(preset: preset))
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
@ -11,7 +11,6 @@ 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
|
||||
@ -121,20 +120,14 @@ struct ContentView: View {
|
||||
CourseScheduleView(semester: currentSemester)
|
||||
.frame(maxWidth: .infinity, maxHeight: .infinity)
|
||||
#if os(macOS)
|
||||
.idealFrame(for: .schedule)
|
||||
.adjustWindowSize(to: .schedule)
|
||||
#endif
|
||||
.onAppear {
|
||||
windowTitleManager.updateTitle(selectedNavItem.rawValue)
|
||||
}
|
||||
} else if !semesters.isEmpty {
|
||||
CourseScheduleView(semester: semesters[0])
|
||||
.frame(maxWidth: .infinity, maxHeight: .infinity)
|
||||
#if os(macOS)
|
||||
.idealFrame(for: .schedule)
|
||||
.adjustWindowSize(to: .schedule)
|
||||
#endif
|
||||
.onAppear {
|
||||
windowTitleManager.updateTitle(selectedNavItem.rawValue)
|
||||
}
|
||||
} else {
|
||||
VStack {
|
||||
Text("请先添加学期")
|
||||
@ -142,29 +135,20 @@ 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)
|
||||
.idealFrame(for: .calendar)
|
||||
.adjustWindowSize(to: .calendar)
|
||||
#endif
|
||||
.onAppear {
|
||||
windowTitleManager.updateTitle(selectedNavItem.rawValue)
|
||||
}
|
||||
} else if !semesters.isEmpty {
|
||||
AcademicCalendarView(semester: semesters[0])
|
||||
.frame(maxWidth: .infinity, maxHeight: .infinity)
|
||||
#if os(macOS)
|
||||
.idealFrame(for: .calendar)
|
||||
.adjustWindowSize(to: .calendar)
|
||||
#endif
|
||||
.onAppear {
|
||||
windowTitleManager.updateTitle(selectedNavItem.rawValue)
|
||||
}
|
||||
} else {
|
||||
VStack {
|
||||
Text("请先添加学期")
|
||||
@ -172,20 +156,11 @@ 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
|
||||
@ -224,14 +199,9 @@ struct ContentView: View {
|
||||
Spacer()
|
||||
}
|
||||
.padding()
|
||||
.onAppear {
|
||||
windowTitleManager.updateTitle(semester.title)
|
||||
}
|
||||
.frame(maxWidth: .infinity, alignment: .leading)
|
||||
} else {
|
||||
Text("选择一个项目以查看详情")
|
||||
.onAppear {
|
||||
windowTitleManager.updateTitle("TeachMate")
|
||||
}
|
||||
Text("请选择一个项目")
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -258,5 +228,4 @@ struct ContentView: View {
|
||||
#Preview {
|
||||
ContentView()
|
||||
.modelContainer(PreviewData.createSemesterContainer())
|
||||
.environmentObject(WindowTitleManager())
|
||||
}
|
||||
|
@ -64,7 +64,6 @@ struct CourseFormView: View {
|
||||
setupInitialValues()
|
||||
}
|
||||
}
|
||||
.frame(idealWidth: 600, idealHeight: 400)
|
||||
}
|
||||
|
||||
// Main content view
|
||||
@ -79,6 +78,7 @@ struct CourseFormView: View {
|
||||
// Left side - Form content
|
||||
formFieldsSection
|
||||
}
|
||||
.frame(minWidth: 600, idealWidth: 700, maxHeight: .infinity)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -87,7 +87,7 @@ struct CourseManagerView: View {
|
||||
mode: .edit(course),
|
||||
onSave: { _ in }
|
||||
)
|
||||
.frame(idealWidth: 600, idealHeight: 400)
|
||||
.frame(width: 600)
|
||||
} else {
|
||||
CourseFormView(
|
||||
mode: .add,
|
||||
@ -96,7 +96,7 @@ struct CourseManagerView: View {
|
||||
selectedCourse = newCourse
|
||||
}
|
||||
)
|
||||
.frame(idealWidth: 600, idealHeight: 400)
|
||||
.frame(width: 600)
|
||||
}
|
||||
}
|
||||
.onAppear {
|
||||
|
@ -39,34 +39,6 @@ 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
|
||||
@ -90,7 +62,7 @@ struct CourseScheduleView: View {
|
||||
headerRow
|
||||
|
||||
// 课程行
|
||||
ForEach(displayTimeSlots) { timeSlot in
|
||||
ForEach(TimeSlot.defaultSlots) { timeSlot in
|
||||
courseRow(timeSlot: timeSlot)
|
||||
}
|
||||
}
|
||||
@ -162,8 +134,8 @@ struct CourseScheduleView: View {
|
||||
.border(Color.gray.opacity(0.5), width: 0.5)
|
||||
|
||||
// 星期表头
|
||||
ForEach(displayWeekdays, id: \.self) { weekday in
|
||||
Text(weekdays[weekday - 1])
|
||||
ForEach(0..<7, id: \.self) { index in
|
||||
Text(weekdays[index])
|
||||
.font(.headline)
|
||||
.frame(width: dayCellWidth, height: headerHeight)
|
||||
.background(Color.gray.opacity(0.3))
|
||||
@ -188,7 +160,7 @@ struct CourseScheduleView: View {
|
||||
.border(Color.gray.opacity(0.5), width: 0.5)
|
||||
|
||||
// 每天的课程单元格
|
||||
ForEach(displayWeekdays, id: \.self) { weekday in
|
||||
ForEach(1...7, id: \.self) { weekday in
|
||||
courseCell(weekday: weekday, timeSlot: timeSlot.id)
|
||||
}
|
||||
}
|
||||
@ -250,31 +222,31 @@ struct CourseScheduleView: View {
|
||||
|
||||
// 添加示例课程数据
|
||||
private func addSampleCourses() {
|
||||
// 使用 PreviewData 中的示例数据
|
||||
let container = PreviewData.createContainer()
|
||||
let fetchDescriptor = FetchDescriptor<CourseModel>()
|
||||
let sampleCourses = try? container.mainContext.fetch(fetchDescriptor)
|
||||
.filter { $0.semester?.id != semester.id } // 过滤掉已经属于当前学期的课程
|
||||
var sampleCourses: [CourseModel] = []
|
||||
|
||||
// 创建有多个课时的课程
|
||||
let aiCourse = CourseModel(name: "人工智能技术与应用", colorHex: "#FADBD8", isNew: false)
|
||||
|
||||
for course in sampleCourses ?? [] {
|
||||
// 创建新课程,复制原课程的属性
|
||||
let newCourse = CourseModel(name: course.name, colorHex: course.colorHex)
|
||||
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")
|
||||
|
||||
// 复制课时信息
|
||||
for session in course.sessions {
|
||||
newCourse.addSession(
|
||||
weekday: session.weekday,
|
||||
timeSlot: session.timeSlot,
|
||||
location: session.location,
|
||||
schoolClass: session.schoolCalss
|
||||
)
|
||||
}
|
||||
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")
|
||||
|
||||
// 关联到当前学期并插入
|
||||
newCourse.semester = semester
|
||||
modelContext.insert(newCourse)
|
||||
sampleCourses.append(aiCourse)
|
||||
sampleCourses.append(dockerCourse)
|
||||
sampleCourses.append(networkCourse)
|
||||
|
||||
for course in sampleCourses {
|
||||
course.semester = semester
|
||||
modelContext.insert(course)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -58,7 +58,6 @@ struct SessionFormView: View {
|
||||
setupInitialValues()
|
||||
}
|
||||
}
|
||||
.frame(idealWidth: 600, idealHeight: 400)
|
||||
}
|
||||
|
||||
// Main content view
|
||||
@ -77,6 +76,7 @@ struct SessionFormView: View {
|
||||
Divider()
|
||||
.padding(.vertical)
|
||||
}
|
||||
.frame(minWidth: 600, idealWidth: 700, maxHeight: .infinity)
|
||||
}
|
||||
}
|
||||
|
||||
|