Compare commits
21 Commits
3fad13097a
...
main
Author | SHA1 | Date | |
---|---|---|---|
edfe97c6dc | |||
14c229175c | |||
99f2bd8a74 | |||
8bc7425e2a | |||
5946cd3ec0 | |||
9bfd85ec3d | |||
15fbe5895c | |||
13de6fa302 | |||
b719ab300d | |||
9c6515a2f5 | |||
bb268cc6ad | |||
9f717d83df | |||
06416138d9 | |||
4ee81cf2ea | |||
4a295b9b88 | |||
4c3a46d40e | |||
3eb5fb73eb | |||
b4704bd4fc | |||
e6b217aba4 | |||
8d973e7942 | |||
1eb574e682 |
@ -0,0 +1,22 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
||||||
|
<model type="com.apple.IDECoreDataModeler.DataModel" documentVersion="1.0" lastSavedToolsVersion="23605" systemVersion="24C101" minimumToolsVersion="Automatic" sourceLanguage="Swift" usedWithSwiftData="YES" userDefinedModelVersionIdentifier="">
|
||||||
|
<entity name="CoreDataClassModel" representedClassName=".CoreDataClassModel" syncable="YES">
|
||||||
|
<attribute name="auditory" optional="YES" attributeType="String"/>
|
||||||
|
<attribute name="comment" optional="YES" attributeType="String"/>
|
||||||
|
<attribute name="day" optional="YES" attributeType="Date" usesScalarValueType="NO"/>
|
||||||
|
<attribute name="endtime" optional="YES" attributeType="Date" usesScalarValueType="NO"/>
|
||||||
|
<attribute name="important" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
|
||||||
|
<attribute name="notification" optional="YES" attributeType="String"/>
|
||||||
|
<attribute name="online" optional="YES" attributeType="String"/>
|
||||||
|
<attribute name="professor" optional="YES" attributeType="String"/>
|
||||||
|
<attribute name="starttime" optional="YES" attributeType="Date" usesScalarValueType="NO"/>
|
||||||
|
<attribute name="subject" optional="YES" attributeType="String"/>
|
||||||
|
</entity>
|
||||||
|
<entity name="JsonClassModel" representedClassName="JsonClassModel" syncable="YES" codeGenerationType="class">
|
||||||
|
<attribute name="day" optional="YES" attributeType="Integer 16" defaultValueString="0" usesScalarValueType="YES"/>
|
||||||
|
<attribute name="group" optional="YES" attributeType="String"/>
|
||||||
|
<attribute name="name" optional="YES" attributeType="String"/>
|
||||||
|
<attribute name="time" optional="YES" attributeType="String"/>
|
||||||
|
<attribute name="week" optional="YES" attributeType="Integer 16" defaultValueString="0" usesScalarValueType="YES"/>
|
||||||
|
</entity>
|
||||||
|
</model>
|
@ -3,4 +3,22 @@
|
|||||||
uuid = "38CE0E1B-29C0-4785-BF18-FE1BA38F677F"
|
uuid = "38CE0E1B-29C0-4785-BF18-FE1BA38F677F"
|
||||||
type = "1"
|
type = "1"
|
||||||
version = "2.0">
|
version = "2.0">
|
||||||
|
<Breakpoints>
|
||||||
|
<BreakpointProxy
|
||||||
|
BreakpointExtensionID = "Xcode.Breakpoint.FileBreakpoint">
|
||||||
|
<BreakpointContent
|
||||||
|
uuid = "CF2C0E34-74B0-458B-AE66-E61DEB75A958"
|
||||||
|
shouldBeEnabled = "Yes"
|
||||||
|
ignoreCount = "0"
|
||||||
|
continueAfterRunningActions = "No"
|
||||||
|
filePath = "Schedule ICTIS/Main/Views/ProfessorAuditoryClassFieldView.swift"
|
||||||
|
startingColumnNumber = "9223372036854775807"
|
||||||
|
endingColumnNumber = "9223372036854775807"
|
||||||
|
startingLineNumber = "20"
|
||||||
|
endingLineNumber = "20"
|
||||||
|
landmarkName = "body"
|
||||||
|
landmarkType = "24">
|
||||||
|
</BreakpointContent>
|
||||||
|
</BreakpointProxy>
|
||||||
|
</Breakpoints>
|
||||||
</Bucket>
|
</Bucket>
|
||||||
|
Before Width: | Height: | Size: 221 B |
Before Width: | Height: | Size: 222 B |
32
Schedule ICTIS/ConnectingToNetworkView.swift
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
//
|
||||||
|
// LoadingView.swift
|
||||||
|
// Schedule ICTIS
|
||||||
|
//
|
||||||
|
// Created by Mironov Egor on 11.12.2024.
|
||||||
|
//
|
||||||
|
|
||||||
|
import SwiftUI
|
||||||
|
|
||||||
|
struct ConnectingToNetworkView: View {
|
||||||
|
@State private var isAnimating = false
|
||||||
|
var body: some View {
|
||||||
|
VStack {
|
||||||
|
Text("Ожидание сети")
|
||||||
|
.font(.custom("Montserrat-Medium", fixedSize: 18))
|
||||||
|
Circle()
|
||||||
|
.trim(from: 0.2, to: 1.0)
|
||||||
|
.stroke(Color("blueColor"), lineWidth: 3)
|
||||||
|
.frame(width: 30, height: 30)
|
||||||
|
.rotationEffect(Angle(degrees: isAnimating ? 360 : 0))
|
||||||
|
.animation(
|
||||||
|
Animation.linear(duration: 0.6).repeatForever(autoreverses: false),
|
||||||
|
value: isAnimating
|
||||||
|
)
|
||||||
|
.onAppear { isAnimating = true }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#Preview {
|
||||||
|
ConnectingToNetworkView()
|
||||||
|
}
|
@ -8,36 +8,70 @@
|
|||||||
import SwiftUI
|
import SwiftUI
|
||||||
|
|
||||||
struct ContentView: View {
|
struct ContentView: View {
|
||||||
@State private var selectedTab: Int = 1
|
@State private var selectedTab: TabBarModel = .schedule
|
||||||
@StateObject var vm = ViewModel()
|
@State private var isTabBarHidden = false
|
||||||
|
@ObservedObject var vm: ScheduleViewModel
|
||||||
|
@ObservedObject var networkMonitor: NetworkMonitor
|
||||||
var body: some View {
|
var body: some View {
|
||||||
TabView(selection: $selectedTab) {
|
ZStack (alignment: .bottom) {
|
||||||
Text("Tasks")
|
TabView(selection: $selectedTab) {
|
||||||
.tabItem {
|
Text("Tasks")
|
||||||
Image(systemName: "books.vertical")
|
.tag(TabBarModel.tasks)
|
||||||
Text("Задания")
|
|
||||||
}
|
|
||||||
.tag(0)
|
|
||||||
|
|
||||||
MainView(vm: vm)
|
MainView(vm: vm, networkMonitor: networkMonitor)
|
||||||
.tabItem {
|
.tag(TabBarModel.schedule)
|
||||||
Image(systemName: "house")
|
.background {
|
||||||
Text("Расписание")
|
if !isTabBarHidden {
|
||||||
}
|
HideTabBar {
|
||||||
.tag(1)
|
print("TabBar is hidden")
|
||||||
|
isTabBarHidden = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Text("Settings")
|
SettingsView(vm: vm, networkMonitor: networkMonitor)
|
||||||
.tabItem {
|
.tag(TabBarModel.settings)
|
||||||
Image(systemName: "gear")
|
}
|
||||||
Text("Настройки")
|
TabBarView(selectedTab: $selectedTab)
|
||||||
}
|
|
||||||
.tag(2)
|
|
||||||
}
|
}
|
||||||
.accentColor(Color("blueColor"))
|
.onAppear {
|
||||||
|
vm.fetchWeekSchedule()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct HideTabBar: UIViewRepresentable {
|
||||||
|
var result: () -> ()
|
||||||
|
func makeUIView(context: Context) -> UIView {
|
||||||
|
let view = UIView(frame: .zero)
|
||||||
|
view.backgroundColor = .clear
|
||||||
|
|
||||||
|
DispatchQueue.main.async {
|
||||||
|
if let tabController = view.tabController {
|
||||||
|
tabController.tabBar.isHidden = true
|
||||||
|
result()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return view
|
||||||
|
}
|
||||||
|
func updateUIView(_ uiView: UIView, context: Context) {
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extension UIView {
|
||||||
|
var tabController: UITabBarController? {
|
||||||
|
if let controller = sequence(first: self, next: {
|
||||||
|
$0.next
|
||||||
|
}).first(where: { $0 is UITabBarController}) as? UITabBarController {
|
||||||
|
return controller
|
||||||
|
}
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#Preview {
|
#Preview {
|
||||||
ContentView()
|
@Previewable @StateObject var vm1 = ScheduleViewModel()
|
||||||
|
@Previewable @StateObject var vm2 = NetworkMonitor()
|
||||||
|
ContentView(vm: vm1, networkMonitor: vm2)
|
||||||
}
|
}
|
||||||
|
31
Schedule ICTIS/ErrorsView/NetworkErrorView.swift
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
//
|
||||||
|
// NetworkErrorView.swift
|
||||||
|
// Schedule ICTIS
|
||||||
|
//
|
||||||
|
// Created by Mironov Egor on 26.03.2025.
|
||||||
|
//
|
||||||
|
|
||||||
|
import SwiftUI
|
||||||
|
|
||||||
|
struct NetworkErrorView: View {
|
||||||
|
var message: String
|
||||||
|
var body: some View {
|
||||||
|
VStack {
|
||||||
|
Spacer()
|
||||||
|
VStack {
|
||||||
|
Image(systemName: "wifi.slash")
|
||||||
|
.font(.system(size: 60, weight: .light))
|
||||||
|
.frame(width: 70, height: 70)
|
||||||
|
Text(message)
|
||||||
|
.font(.custom("Montserrat-Medium", fixedSize: 15))
|
||||||
|
.padding(.top, 5)
|
||||||
|
}
|
||||||
|
.padding(.horizontal, 30)
|
||||||
|
Spacer()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#Preview {
|
||||||
|
NetworkErrorView(message: "Восстановите подключение к интернету чтобы мы смогли загрузить расписание")
|
||||||
|
}
|
BIN
Schedule ICTIS/Fonts/Montserrat-Black.ttf
Executable file
BIN
Schedule ICTIS/Fonts/Montserrat-BlackItalic.ttf
Executable file
BIN
Schedule ICTIS/Fonts/Montserrat-Bold.ttf
Executable file
BIN
Schedule ICTIS/Fonts/Montserrat-BoldItalic.ttf
Executable file
BIN
Schedule ICTIS/Fonts/Montserrat-ExtraBold.ttf
Executable file
BIN
Schedule ICTIS/Fonts/Montserrat-ExtraBoldItalic.ttf
Executable file
BIN
Schedule ICTIS/Fonts/Montserrat-ExtraLight.ttf
Executable file
BIN
Schedule ICTIS/Fonts/Montserrat-ExtraLightItalic.ttf
Executable file
BIN
Schedule ICTIS/Fonts/Montserrat-Italic.ttf
Executable file
BIN
Schedule ICTIS/Fonts/Montserrat-Light.ttf
Executable file
BIN
Schedule ICTIS/Fonts/Montserrat-LightItalic.ttf
Executable file
BIN
Schedule ICTIS/Fonts/Montserrat-Medium.ttf
Executable file
BIN
Schedule ICTIS/Fonts/Montserrat-MediumItalic.ttf
Executable file
BIN
Schedule ICTIS/Fonts/Montserrat-Regular.ttf
Executable file
BIN
Schedule ICTIS/Fonts/Montserrat-SemiBold.ttf
Executable file
BIN
Schedule ICTIS/Fonts/Montserrat-SemiBoldItalic.ttf
Executable file
BIN
Schedule ICTIS/Fonts/Montserrat-Thin.ttf
Executable file
BIN
Schedule ICTIS/Fonts/Montserrat-ThinItalic.ttf
Executable file
53
Schedule ICTIS/LoadingScheduleView.swift
Normal file
@ -0,0 +1,53 @@
|
|||||||
|
import SwiftUI
|
||||||
|
|
||||||
|
struct LoadingScheduleView: View {
|
||||||
|
@State private var isAnimated = false
|
||||||
|
var body: some View {
|
||||||
|
ZStack {
|
||||||
|
ScrollView(.vertical, showsIndicators: false) {
|
||||||
|
VStack(spacing: 20) {
|
||||||
|
ForEach(0..<5, id: \.self) { _ in
|
||||||
|
VStack (alignment: .trailing) {
|
||||||
|
RoundedRectangle(cornerRadius: 20)
|
||||||
|
.fill(
|
||||||
|
LinearGradient(
|
||||||
|
gradient: Gradient(colors: [
|
||||||
|
isAnimated ? Color.gray.opacity(0.6) : Color.gray.opacity(0.3),
|
||||||
|
isAnimated ? Color.gray.opacity(0.3) : Color.gray.opacity(0.6)
|
||||||
|
]),
|
||||||
|
startPoint: .topLeading,
|
||||||
|
endPoint: .bottomTrailing
|
||||||
|
)
|
||||||
|
)
|
||||||
|
.frame(width: 45, height: 20)
|
||||||
|
.padding(.horizontal, 20)
|
||||||
|
.animation(.linear(duration: 0.8).repeatForever(autoreverses: true), value: isAnimated)
|
||||||
|
RoundedRectangle(cornerRadius: 20)
|
||||||
|
.fill(
|
||||||
|
LinearGradient(
|
||||||
|
gradient: Gradient(colors: [
|
||||||
|
isAnimated ? Color.gray.opacity(0.6) : Color.gray.opacity(0.3),
|
||||||
|
isAnimated ? Color.gray.opacity(0.3) : Color.gray.opacity(0.6)
|
||||||
|
]),
|
||||||
|
startPoint: .topLeading,
|
||||||
|
endPoint: .bottomTrailing
|
||||||
|
)
|
||||||
|
)
|
||||||
|
.frame(height: 70)
|
||||||
|
.padding(.horizontal, 20)
|
||||||
|
.animation(.linear(duration: 0.8).repeatForever(autoreverses: true), value: isAnimated)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.onAppear {
|
||||||
|
isAnimated.toggle()
|
||||||
|
}
|
||||||
|
.padding(.top, 10)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#Preview {
|
||||||
|
LoadingScheduleView()
|
||||||
|
}
|
29
Schedule ICTIS/LoadingView.swift
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
//
|
||||||
|
// LoadingView.swift
|
||||||
|
// Schedule ICTIS
|
||||||
|
//
|
||||||
|
// Created by Mironov Egor on 04.04.2025.
|
||||||
|
//
|
||||||
|
|
||||||
|
import SwiftUI
|
||||||
|
|
||||||
|
struct LoadingView: View {
|
||||||
|
@State private var isAnimating = false
|
||||||
|
|
||||||
|
var body: some View {
|
||||||
|
Circle()
|
||||||
|
.trim(from: 0.2, to: 1.0)
|
||||||
|
.stroke(Color("blueColor"), lineWidth: 3)
|
||||||
|
.frame(width: 30, height: 30)
|
||||||
|
.rotationEffect(Angle(degrees: isAnimating ? 360 : 0))
|
||||||
|
.animation(
|
||||||
|
Animation.linear(duration: 0.6).repeatForever(autoreverses: false),
|
||||||
|
value: isAnimating
|
||||||
|
)
|
||||||
|
.onAppear { isAnimating = true }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#Preview {
|
||||||
|
LoadingView()
|
||||||
|
}
|
@ -1,183 +0,0 @@
|
|||||||
//
|
|
||||||
// MonthTabView.swift
|
|
||||||
// Schedule ICTIS
|
|
||||||
//
|
|
||||||
// Created by G412 on 10.12.2024.
|
|
||||||
//
|
|
||||||
|
|
||||||
import SwiftUI
|
|
||||||
|
|
||||||
struct MonthTabView: View {
|
|
||||||
@State private var currentMonthIndex: Int = 1
|
|
||||||
@State private var monthSlider: [[Date.MonthWeek]] = []
|
|
||||||
@State private var createMonth: Bool = false
|
|
||||||
@State private var currentWeekIndex: Int = 0
|
|
||||||
@ObservedObject var vm: ViewModel
|
|
||||||
var body: some View {
|
|
||||||
VStack {
|
|
||||||
HStack (spacing: 34) {
|
|
||||||
ForEach(MockData.daysOfWeek.indices, id: \.self) { index in
|
|
||||||
Text(MockData.daysOfWeek[index])
|
|
||||||
.font(.system(size: 15, weight: .semibold))
|
|
||||||
.foregroundColor(MockData.daysOfWeek[index] == "Вс" ? Color(.red) : Color("customGray2"))
|
|
||||||
.padding(.top, 13)
|
|
||||||
.foregroundColor(.gray)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.padding(.top, 14)
|
|
||||||
//.background(Color.red)
|
|
||||||
TabView(selection: $currentMonthIndex) {
|
|
||||||
ForEach(monthSlider.indices, id: \.self) { index in
|
|
||||||
let month = monthSlider[index]
|
|
||||||
MonthView(month)
|
|
||||||
.tag(index)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.padding(.top, -25)
|
|
||||||
.padding(.bottom, -10)
|
|
||||||
.padding(.horizontal, -15)
|
|
||||||
.tabViewStyle(.page(indexDisplayMode: .never))
|
|
||||||
//.background(Color.green)
|
|
||||||
}
|
|
||||||
.onAppear(perform: {
|
|
||||||
vm.updateSelectedDayIndex()
|
|
||||||
if monthSlider.isEmpty {
|
|
||||||
let currentMonth = Date().fetchMonth(vm.selectedDay)
|
|
||||||
|
|
||||||
if let firstDate = currentMonth.first?.week[0].date {
|
|
||||||
monthSlider.append(firstDate.createPreviousMonth())
|
|
||||||
}
|
|
||||||
|
|
||||||
monthSlider.append(currentMonth)
|
|
||||||
|
|
||||||
if let lastDate = currentMonth.last?.week[6].date {
|
|
||||||
monthSlider.append(lastDate.createNextMonth())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.onChange(of: currentMonthIndex, initial: false) { oldValue, newValue in
|
|
||||||
if newValue == 0 || newValue == (monthSlider.count - 1) {
|
|
||||||
createMonth = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@ViewBuilder
|
|
||||||
func MonthView(_ month: [Date.MonthWeek]) -> some View {
|
|
||||||
VStack (spacing: 10) {
|
|
||||||
ForEach(month.indices, id: \.self) { index in
|
|
||||||
let week = month[index].week
|
|
||||||
WeekView(week)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.background {
|
|
||||||
GeometryReader {
|
|
||||||
let minX = $0.frame(in: .global).minX
|
|
||||||
|
|
||||||
Color.clear
|
|
||||||
.preference(key: OffsetKey.self, value: minX)
|
|
||||||
.onPreferenceChange(OffsetKey.self) { value in
|
|
||||||
if (abs(value.rounded()) - 20) < 5 && createMonth {
|
|
||||||
paginateMonth()
|
|
||||||
|
|
||||||
createMonth = false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
@ViewBuilder
|
|
||||||
func WeekView(_ week: [Date.WeekDay]) -> some View {
|
|
||||||
HStack (spacing: 23) {
|
|
||||||
ForEach(week) { day in
|
|
||||||
VStack {
|
|
||||||
Text(day.date.format("dd"))
|
|
||||||
.font(.system(size: 15, weight: .bold))
|
|
||||||
.foregroundStyle(isDateInCurrentMonth(day.date) ? isSameDate(day.date, vm.selectedDay) ? Color.white : Color.black: isSameDate(day.date, vm.selectedDay) ? Color.white : Color("greyForDaysInMonthTabView"))
|
|
||||||
}
|
|
||||||
.frame(width: 30, height: 30, alignment: .center)
|
|
||||||
.background( content: {
|
|
||||||
Group {
|
|
||||||
if isSameDate(day.date, vm.selectedDay) {
|
|
||||||
Color("blueColor")
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
Color("background")
|
|
||||||
}
|
|
||||||
if isSameDate(day.date, vm.selectedDay) {
|
|
||||||
Color("blueColor")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
)
|
|
||||||
.overlay (
|
|
||||||
Group {
|
|
||||||
if day.date.isToday && !isSameDate(day.date, vm.selectedDay) {
|
|
||||||
RoundedRectangle(cornerRadius: 100)
|
|
||||||
.stroke(Color("blueColor"), lineWidth: 2)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
)
|
|
||||||
.cornerRadius(15)
|
|
||||||
.onTapGesture {
|
|
||||||
if isSameWeek(day.date, vm.selectedDay) {
|
|
||||||
print("На одной неделе")
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
var difBetweenWeeks = weeksBetween(startDate: vm.selectedDay, endDate: day.date)
|
|
||||||
if day.date < vm.selectedDay {
|
|
||||||
difBetweenWeeks = difBetweenWeeks * -1
|
|
||||||
}
|
|
||||||
print(difBetweenWeeks)
|
|
||||||
vm.fetchWeekSchedule("", difBetweenWeeks)
|
|
||||||
}
|
|
||||||
vm.selectedDay = day.date
|
|
||||||
vm.updateSelectedDayIndex()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func paginateMonth(_ indexOfWeek: Int = 0) {
|
|
||||||
let calendar = Calendar.current
|
|
||||||
if monthSlider.indices.contains(currentMonthIndex) {
|
|
||||||
if let firstDate = monthSlider[currentMonthIndex].first?.week[0].date,
|
|
||||||
currentMonthIndex == 0 {
|
|
||||||
// switch (vm.numOfGroup) {
|
|
||||||
// case "":
|
|
||||||
// vm.week -= 1
|
|
||||||
// default:
|
|
||||||
// vm.fetchWeekSchedule("new week", -1)
|
|
||||||
// }
|
|
||||||
monthSlider.insert(firstDate.createPreviousMonth(), at: 0)
|
|
||||||
monthSlider.removeLast()
|
|
||||||
currentMonthIndex = 1
|
|
||||||
vm.selectedDay = calendar.date(byAdding: .weekOfYear, value: -5, to: vm.selectedDay) ?? Date.init()
|
|
||||||
vm.updateSelectedDayIndex()
|
|
||||||
vm.fetchWeekSchedule("", -5)
|
|
||||||
}
|
|
||||||
|
|
||||||
if let lastDate = monthSlider[currentMonthIndex].last?.week[6].date,
|
|
||||||
currentMonthIndex == (monthSlider.count - 1) {
|
|
||||||
// switch (vm.numOfGroup) {
|
|
||||||
// case "":
|
|
||||||
// vm.week += 1
|
|
||||||
// default:
|
|
||||||
// vm.fetchWeekSchedule("new week", 1)
|
|
||||||
// }
|
|
||||||
monthSlider.append(lastDate.createNextMonth())
|
|
||||||
monthSlider.removeFirst()
|
|
||||||
currentMonthIndex = monthSlider.count - 2
|
|
||||||
vm.selectedDay = calendar.date(byAdding: .weekOfYear, value: 5, to: vm.selectedDay) ?? Date.init()
|
|
||||||
vm.updateSelectedDayIndex()
|
|
||||||
vm.fetchWeekSchedule("", 5)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#Preview {
|
|
||||||
ContentView()
|
|
||||||
}
|
|
@ -1,152 +0,0 @@
|
|||||||
//
|
|
||||||
// WeekTabView.swift
|
|
||||||
// Schedule ICTIS
|
|
||||||
//
|
|
||||||
// Created by G412 on 10.12.2024.
|
|
||||||
//
|
|
||||||
|
|
||||||
import SwiftUI
|
|
||||||
|
|
||||||
struct WeekTabView: View {
|
|
||||||
@State private var currentWeekIndex: Int = 1
|
|
||||||
@State private var weekSlider: [[Date.WeekDay]] = []
|
|
||||||
@State private var createWeek: Bool = false
|
|
||||||
@ObservedObject var vm: ViewModel
|
|
||||||
var body: some View {
|
|
||||||
HStack {
|
|
||||||
TabView(selection: $currentWeekIndex) {
|
|
||||||
ForEach(weekSlider.indices, id: \.self) { index in
|
|
||||||
let week = weekSlider[index]
|
|
||||||
WeekView(week)
|
|
||||||
.padding(.horizontal, 15)
|
|
||||||
.tag(index)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.padding(.horizontal, -15)
|
|
||||||
.tabViewStyle(.page(indexDisplayMode: .never))
|
|
||||||
.frame(height: 90)
|
|
||||||
}
|
|
||||||
.onAppear(perform: {
|
|
||||||
vm.updateSelectedDayIndex()
|
|
||||||
if weekSlider.isEmpty {
|
|
||||||
let currentWeek = Date().fetchWeek(vm.selectedDay)
|
|
||||||
|
|
||||||
if let firstDate = currentWeek.first?.date {
|
|
||||||
weekSlider.append(firstDate.createPrevioustWeek())
|
|
||||||
}
|
|
||||||
|
|
||||||
weekSlider.append(currentWeek)
|
|
||||||
|
|
||||||
if let lastDate = currentWeek.last?.date {
|
|
||||||
weekSlider.append(lastDate.createNextWeek())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.onChange(of: currentWeekIndex, initial: false) { oldValue, newValue in
|
|
||||||
if newValue == 0 || newValue == (weekSlider.count - 1) {
|
|
||||||
createWeek = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@ViewBuilder
|
|
||||||
func WeekView(_ week: [Date.WeekDay]) -> some View {
|
|
||||||
HStack (spacing: 10) {
|
|
||||||
ForEach(week) { day in
|
|
||||||
VStack (spacing: 1) {
|
|
||||||
Text(day.date.format("E"))
|
|
||||||
.font(.system(size: 15, weight: .semibold))
|
|
||||||
.foregroundColor(day.date.format("E") == "Вс" ? Color(.red) : isSameDate(day.date, vm.selectedDay) ? Color("customGray1") : Color("customGray3"))
|
|
||||||
.padding(.top, 13)
|
|
||||||
.foregroundColor(.gray)
|
|
||||||
Text(day.date.format("dd"))
|
|
||||||
.font(.system(size: 15, weight: .bold))
|
|
||||||
.foregroundStyle(isSameDate(day.date, vm.selectedDay) ? .white : .black)
|
|
||||||
.padding(.bottom, 13)
|
|
||||||
}
|
|
||||||
.frame(width: 43, height: 55, alignment: .center)
|
|
||||||
.background( content: {
|
|
||||||
Group {
|
|
||||||
if isSameDate(day.date, vm.selectedDay) {
|
|
||||||
Color("blueColor")
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
Color(.white)
|
|
||||||
}
|
|
||||||
if isSameDate(day.date, vm.selectedDay) {
|
|
||||||
Color("blueColor")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
)
|
|
||||||
.overlay (
|
|
||||||
Group {
|
|
||||||
if day.date.isToday && !isSameDate(day.date, vm.selectedDay) {
|
|
||||||
RoundedRectangle(cornerRadius: 15)
|
|
||||||
.stroke(Color("blueColor"), lineWidth: 2)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
)
|
|
||||||
.cornerRadius(15)
|
|
||||||
.onTapGesture {
|
|
||||||
vm.selectedDay = day.date
|
|
||||||
vm.updateSelectedDayIndex()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.background {
|
|
||||||
GeometryReader {
|
|
||||||
let minX = $0.frame(in: .global).minX
|
|
||||||
|
|
||||||
Color.clear
|
|
||||||
.preference(key: OffsetKey.self, value: minX)
|
|
||||||
.onPreferenceChange(OffsetKey.self) { value in
|
|
||||||
if value.rounded() == 15 && createWeek {
|
|
||||||
paginateWeek()
|
|
||||||
|
|
||||||
createWeek = false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func paginateWeek() {
|
|
||||||
let calendar = Calendar.current
|
|
||||||
if weekSlider.indices.contains(currentWeekIndex) {
|
|
||||||
if let firstDate = weekSlider[currentWeekIndex].first?.date,
|
|
||||||
currentWeekIndex == 0 {
|
|
||||||
switch (vm.numOfGroup) {
|
|
||||||
case "":
|
|
||||||
vm.week -= 1
|
|
||||||
default:
|
|
||||||
vm.fetchWeekSchedule("new week", -1)
|
|
||||||
}
|
|
||||||
weekSlider.insert(firstDate.createPrevioustWeek(), at: 0)
|
|
||||||
weekSlider.removeLast()
|
|
||||||
currentWeekIndex = 1
|
|
||||||
vm.selectedDay = calendar.date(byAdding: .weekOfYear, value: -1, to: vm.selectedDay) ?? Date.init()
|
|
||||||
vm.updateSelectedDayIndex()
|
|
||||||
}
|
|
||||||
|
|
||||||
if let lastDate = weekSlider[currentWeekIndex].last?.date,
|
|
||||||
currentWeekIndex == (weekSlider.count - 1) {
|
|
||||||
switch (vm.numOfGroup) {
|
|
||||||
case "":
|
|
||||||
vm.week += 1
|
|
||||||
default:
|
|
||||||
vm.fetchWeekSchedule("new week", 1)
|
|
||||||
}
|
|
||||||
weekSlider.append(lastDate.createNextWeek())
|
|
||||||
weekSlider.removeFirst()
|
|
||||||
currentWeekIndex = weekSlider.count - 2
|
|
||||||
vm.selectedDay = calendar.date(byAdding: .weekOfYear, value: 1, to: vm.selectedDay) ?? Date.init()
|
|
||||||
vm.updateSelectedDayIndex()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#Preview {
|
|
||||||
ContentView()
|
|
||||||
}
|
|
52
Schedule ICTIS/Main/Views/CreatedClassView.swift
Normal file
@ -0,0 +1,52 @@
|
|||||||
|
//
|
||||||
|
// CreatedClassView.swift
|
||||||
|
// Schedule ICTIS
|
||||||
|
//
|
||||||
|
// Created by Mironov Egor on 23.12.2024.
|
||||||
|
//
|
||||||
|
|
||||||
|
import SwiftUI
|
||||||
|
|
||||||
|
struct CreatedClassView: View {
|
||||||
|
@ObservedObject var _class: CoreDataClassModel
|
||||||
|
var provider = ClassProvider.shared
|
||||||
|
var body: some View {
|
||||||
|
let existingCopy = try? provider.viewContext.existingObject(with: _class.objectID)
|
||||||
|
if existingCopy != nil {
|
||||||
|
HStack(spacing: 15) {
|
||||||
|
VStack {
|
||||||
|
Text(getTimeString(_class.starttime))
|
||||||
|
.font(.custom("Montserrat-Regular", fixedSize: 15))
|
||||||
|
.padding(.bottom, 1)
|
||||||
|
Text(getTimeString(_class.endtime))
|
||||||
|
.font(.custom("Montserrat-Regular", fixedSize: 15))
|
||||||
|
.padding(.top, 1)
|
||||||
|
}
|
||||||
|
.frame(width: 48)
|
||||||
|
.padding(.top, 7)
|
||||||
|
.padding(.bottom, 7)
|
||||||
|
.padding(.leading, 10)
|
||||||
|
Rectangle()
|
||||||
|
.frame(width: 2)
|
||||||
|
.frame(maxHeight: UIScreen.main.bounds.height - 18)
|
||||||
|
.padding(.top, 7)
|
||||||
|
.padding(.bottom, 7)
|
||||||
|
.foregroundColor(_class.important ? Color("redForImportant") : onlineOrNot(_class.online))
|
||||||
|
Text(getSubjectName(_class.subject, _class.professor, _class.auditory))
|
||||||
|
.font(.custom("Montserrat-Medium", fixedSize: 15))
|
||||||
|
.lineSpacing(3)
|
||||||
|
.padding(.top, 9)
|
||||||
|
.padding(.bottom, 9)
|
||||||
|
Spacer()
|
||||||
|
}
|
||||||
|
.frame(maxWidth: UIScreen.main.bounds.width - 40, maxHeight: 230)
|
||||||
|
.background(Color.white)
|
||||||
|
.cornerRadius(20)
|
||||||
|
.shadow(color: .black.opacity(0.25), radius: 4, x: 2, y: 2)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#Preview {
|
||||||
|
CreatedClassView(_class: .preview())
|
||||||
|
}
|
47
Schedule ICTIS/Main/Views/Fields/AuditoryFieldView.swift
Normal file
@ -0,0 +1,47 @@
|
|||||||
|
//
|
||||||
|
// AuditoryFieldView.swift
|
||||||
|
// Schedule ICTIS
|
||||||
|
//
|
||||||
|
// Created by G412 on 23.01.2025.
|
||||||
|
//
|
||||||
|
|
||||||
|
import SwiftUI
|
||||||
|
|
||||||
|
struct AuditoryFieldView: View {
|
||||||
|
@Binding var text: String
|
||||||
|
var labelForField: String
|
||||||
|
@FocusState var isFocused: Bool
|
||||||
|
var body: some View {
|
||||||
|
HStack(spacing: 0) {
|
||||||
|
Image(systemName: "mappin.and.ellipse")
|
||||||
|
.foregroundColor(Color.gray)
|
||||||
|
.padding(.leading, 12)
|
||||||
|
.padding(.trailing, 14)
|
||||||
|
TextField(labelForField, text: $text)
|
||||||
|
.font(.custom("Montserrat-Meduim", fixedSize: 17))
|
||||||
|
.disableAutocorrection(true)
|
||||||
|
.submitLabel(.done)
|
||||||
|
.focused($isFocused)
|
||||||
|
if isFocused {
|
||||||
|
Button {
|
||||||
|
self.text = ""
|
||||||
|
self.isFocused = false
|
||||||
|
} label: {
|
||||||
|
Image(systemName: "xmark.circle.fill")
|
||||||
|
.padding(.trailing, 20)
|
||||||
|
.offset(x: 10)
|
||||||
|
.foregroundColor(.gray)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.frame(height: 40)
|
||||||
|
.background(
|
||||||
|
RoundedRectangle(cornerRadius: 10)
|
||||||
|
.fill(.white)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#Preview {
|
||||||
|
AuditoryFieldView(text: .constant(""), labelForField: "Корпус-аудитория")
|
||||||
|
}
|
@ -7,13 +7,14 @@
|
|||||||
|
|
||||||
import SwiftUI
|
import SwiftUI
|
||||||
|
|
||||||
struct CommentView: View {
|
struct CommentFieldView: View {
|
||||||
@Binding var textForComment: String
|
@Binding var textForComment: String
|
||||||
@FocusState private var isFocused: Bool
|
@FocusState var isFocused: Bool
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
HStack {
|
HStack {
|
||||||
TextField("Комментарий", text: $textForComment)
|
TextField("Комментарий", text: $textForComment)
|
||||||
|
.font(.custom("Montserrat-Medium", fixedSize: 17))
|
||||||
.submitLabel(.done)
|
.submitLabel(.done)
|
||||||
.multilineTextAlignment(.leading)
|
.multilineTextAlignment(.leading)
|
||||||
.focused($isFocused)
|
.focused($isFocused)
|
||||||
@ -41,6 +42,3 @@ struct CommentView: View {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#Preview {
|
|
||||||
SheetCreateClassView(isShowingSheet: .constant(true))
|
|
||||||
}
|
|
@ -1,25 +1,24 @@
|
|||||||
//
|
//
|
||||||
// Field.swift
|
// ProfessorFieldView.swift
|
||||||
// Schedule ICTIS
|
// Schedule ICTIS
|
||||||
//
|
//
|
||||||
// Created by G412 on 16.12.2024.
|
// Created by G412 on 23.01.2025.
|
||||||
//
|
//
|
||||||
|
|
||||||
import SwiftUI
|
import SwiftUI
|
||||||
|
|
||||||
struct FieldView: View {
|
struct ProfessorFieldView: View {
|
||||||
@Binding var text: String
|
@Binding var text: String
|
||||||
var nameOfImage: String
|
|
||||||
var labelForField: String
|
var labelForField: String
|
||||||
@FocusState private var isFocused: Bool
|
@FocusState var isFocused: Bool
|
||||||
var body: some View {
|
var body: some View {
|
||||||
HStack(spacing: 0) {
|
HStack(spacing: 0) {
|
||||||
Image(systemName: nameOfImage)
|
Image(systemName: "graduationcap")
|
||||||
.foregroundColor(Color.gray)
|
.foregroundColor(Color.gray)
|
||||||
.padding(.leading, 12)
|
.padding(.leading, 12)
|
||||||
.padding(.trailing, 7)
|
.padding(.trailing, 7)
|
||||||
TextField(labelForField, text: $text)
|
TextField(labelForField, text: $text)
|
||||||
.font(.system(size: 18, weight: .regular))
|
.font(.custom("Montserrat-Meduim", fixedSize: 17))
|
||||||
.disableAutocorrection(true)
|
.disableAutocorrection(true)
|
||||||
.submitLabel(.done)
|
.submitLabel(.done)
|
||||||
.focused($isFocused)
|
.focused($isFocused)
|
||||||
@ -44,5 +43,5 @@ struct FieldView: View {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#Preview {
|
#Preview {
|
||||||
ContentView()
|
ProfessorFieldView(text: .constant(""), labelForField: "Преподаватель")
|
||||||
}
|
}
|
61
Schedule ICTIS/Main/Views/Fields/StartEndTimeFieldView.swift
Normal file
@ -0,0 +1,61 @@
|
|||||||
|
//
|
||||||
|
// StartEndTimeView.swift
|
||||||
|
// Schedule ICTIS
|
||||||
|
//
|
||||||
|
// Created by Mironov Egor on 17.12.2024.
|
||||||
|
//
|
||||||
|
|
||||||
|
import SwiftUI
|
||||||
|
|
||||||
|
struct StartEndTimeFieldView: View {
|
||||||
|
@Binding var isIncorrectDate: Bool
|
||||||
|
@Binding var selectedDay: Date
|
||||||
|
@Binding var selectedTime: Date
|
||||||
|
var imageName: String
|
||||||
|
var text: String
|
||||||
|
@Binding var isTimeSelected: Bool
|
||||||
|
var body: some View {
|
||||||
|
HStack {
|
||||||
|
Image(systemName: imageName)
|
||||||
|
.foregroundColor(isIncorrectDate ? .red : Color("grayForFields"))
|
||||||
|
.padding(.leading, 12)
|
||||||
|
.padding(.trailing, 5)
|
||||||
|
|
||||||
|
if !isTimeSelected || isIncorrectDate {
|
||||||
|
Text(text)
|
||||||
|
.font(.custom("Montserrat-Meduim", fixedSize: 17))
|
||||||
|
.foregroundColor(.gray.opacity(0.5))
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
Text("\(selectedTime, formatter: timeFormatter)")
|
||||||
|
.foregroundColor(isIncorrectDate ? .red : .black)
|
||||||
|
.font(.custom("Montserrat-Medium", fixedSize: 17))
|
||||||
|
.padding(.trailing, 10)
|
||||||
|
}
|
||||||
|
Spacer()
|
||||||
|
}
|
||||||
|
.frame(width: (UIScreen.main.bounds.width / 2) - 22, height: 40)
|
||||||
|
.background(
|
||||||
|
RoundedRectangle(cornerRadius: 10)
|
||||||
|
.fill(.white)
|
||||||
|
)
|
||||||
|
.overlay {
|
||||||
|
if selectedDay.isToday {
|
||||||
|
DatePicker("", selection: $selectedTime, in: Date()..., displayedComponents: .hourAndMinute)
|
||||||
|
.padding(.trailing, 35)
|
||||||
|
.blendMode(.destinationOver)
|
||||||
|
.onChange(of: selectedTime) { newValue, oldValue in
|
||||||
|
isTimeSelected = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
DatePicker("", selection: $selectedTime, displayedComponents: .hourAndMinute)
|
||||||
|
.padding(.trailing, 35)
|
||||||
|
.blendMode(.destinationOver)
|
||||||
|
.onChange(of: selectedTime) { newValue, oldValue in
|
||||||
|
isTimeSelected = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
65
Schedule ICTIS/Main/Views/Fields/SubjectFieldView.swift
Normal file
@ -0,0 +1,65 @@
|
|||||||
|
//
|
||||||
|
// Field.swift
|
||||||
|
// Schedule ICTIS
|
||||||
|
//
|
||||||
|
// Created by G412 on 16.12.2024.
|
||||||
|
// КТбо2-6
|
||||||
|
|
||||||
|
import SwiftUI
|
||||||
|
|
||||||
|
struct SubjectFieldView: View {
|
||||||
|
@Binding var text: String
|
||||||
|
@Binding var isShowingSubjectFieldRed: Bool
|
||||||
|
@Binding var labelForField: String
|
||||||
|
@FocusState var isFocused: Bool
|
||||||
|
var body: some View {
|
||||||
|
HStack(spacing: 0) {
|
||||||
|
Image(systemName: "book")
|
||||||
|
.foregroundColor(Color.gray)
|
||||||
|
.padding(.leading, 12)
|
||||||
|
.padding(.trailing, 9)
|
||||||
|
TextField(labelForField, text: $text)
|
||||||
|
.font(.custom("Montserrat-Meduim", fixedSize: 17))
|
||||||
|
.disableAutocorrection(true)
|
||||||
|
.submitLabel(.done)
|
||||||
|
.focused($isFocused)
|
||||||
|
.onChange(of: isFocused, initial: false) { oldValue, newValue in
|
||||||
|
if newValue {
|
||||||
|
self.isShowingSubjectFieldRed = false
|
||||||
|
self.labelForField = "Предмет"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.background {
|
||||||
|
Group {
|
||||||
|
if isShowingSubjectFieldRed {
|
||||||
|
Text("Поле должно быть заполнено!")
|
||||||
|
.font(.custom("Montserrat-Meduim", fixedSize: 17))
|
||||||
|
.foregroundColor(.red)
|
||||||
|
.frame(width: 290)
|
||||||
|
.padding(.leading, -38)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if isFocused {
|
||||||
|
Button {
|
||||||
|
self.text = ""
|
||||||
|
self.isFocused = false
|
||||||
|
} label: {
|
||||||
|
Image(systemName: "xmark.circle.fill")
|
||||||
|
.padding(.trailing, 20)
|
||||||
|
.offset(x: 10)
|
||||||
|
.foregroundColor(.gray)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.frame(height: 40)
|
||||||
|
.background(
|
||||||
|
RoundedRectangle(cornerRadius: 10)
|
||||||
|
.fill(.white)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#Preview {
|
||||||
|
SubjectFieldView(text: .constant(""), isShowingSubjectFieldRed: .constant(false), labelForField: .constant("Предмет"))
|
||||||
|
}
|
50
Schedule ICTIS/Main/Views/FilterGroupsView.swift
Normal file
@ -0,0 +1,50 @@
|
|||||||
|
//
|
||||||
|
// FilterGroupsView.swift
|
||||||
|
// Schedule ICTIS
|
||||||
|
//
|
||||||
|
// Created by Mironov Egor on 21.03.2025.
|
||||||
|
//
|
||||||
|
|
||||||
|
import SwiftUI
|
||||||
|
|
||||||
|
struct FilterGroupsView: View {
|
||||||
|
@ObservedObject var vm: ScheduleViewModel
|
||||||
|
var body: some View {
|
||||||
|
ScrollView(.horizontal, showsIndicators: false) {
|
||||||
|
HStack(spacing: 12) {
|
||||||
|
ForEach(vm.filteringGroups, id: \.self) { group in
|
||||||
|
VStack {
|
||||||
|
Text(group)
|
||||||
|
.foregroundColor(Color("customGray3"))
|
||||||
|
.font(.custom("Montserrat-Medium", fixedSize: 14))
|
||||||
|
.padding(.horizontal, 15)
|
||||||
|
.padding(.vertical, 7)
|
||||||
|
}
|
||||||
|
.background(Color.white)
|
||||||
|
.overlay (
|
||||||
|
Group {
|
||||||
|
if vm.showOnlyChoosenGroup == group {
|
||||||
|
RoundedRectangle(cornerRadius: 20)
|
||||||
|
.stroke(Color("blueColor"), lineWidth: 3)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
.cornerRadius(20)
|
||||||
|
.onTapGesture {
|
||||||
|
vm.showOnlyChoosenGroup = group
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.padding(.horizontal)
|
||||||
|
}
|
||||||
|
.onAppear {
|
||||||
|
vm.updateFilteringGroups()
|
||||||
|
}
|
||||||
|
.padding(.bottom, 10)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#Preview {
|
||||||
|
@Previewable @ObservedObject var vm = ScheduleViewModel()
|
||||||
|
FilterGroupsView(vm: vm)
|
||||||
|
}
|
@ -2,7 +2,7 @@
|
|||||||
// FirstLaunchScheduleView.swift
|
// FirstLaunchScheduleView.swift
|
||||||
// Schedule ICTIS
|
// Schedule ICTIS
|
||||||
//
|
//
|
||||||
// Created by G412 on 06.12.2024.
|
// Created by Mironov Egor on 06.12.2024.
|
||||||
//
|
//
|
||||||
|
|
||||||
import SwiftUI
|
import SwiftUI
|
||||||
|
@ -1,25 +0,0 @@
|
|||||||
//
|
|
||||||
// LoadingView.swift
|
|
||||||
// Schedule ICTIS
|
|
||||||
//
|
|
||||||
// Created by G412 on 11.12.2024.
|
|
||||||
//
|
|
||||||
|
|
||||||
import SwiftUI
|
|
||||||
|
|
||||||
struct LoadingView: View {
|
|
||||||
@Binding var isLoading: Bool
|
|
||||||
var body: some View {
|
|
||||||
ZStack {
|
|
||||||
Color("background")
|
|
||||||
.ignoresSafeArea()
|
|
||||||
ProgressView()
|
|
||||||
.progressViewStyle(CircularProgressViewStyle(tint: .secondary))
|
|
||||||
.scaleEffect(1.2)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#Preview {
|
|
||||||
LoadingView(isLoading: .constant(true))
|
|
||||||
}
|
|
@ -10,26 +10,33 @@ import SwiftUI
|
|||||||
struct MainView: View {
|
struct MainView: View {
|
||||||
@State private var searchText: String = ""
|
@State private var searchText: String = ""
|
||||||
@State private var isShowingMonthSlider: Bool = false
|
@State private var isShowingMonthSlider: Bool = false
|
||||||
@State private var isFirstAppearence = true
|
@ObservedObject var vm: ScheduleViewModel
|
||||||
@ObservedObject var vm: ViewModel
|
@ObservedObject var networkMonitor: NetworkMonitor
|
||||||
|
@FocusState private var isFocusedSearchBar: Bool
|
||||||
|
@State private var isScrolling: Bool = false
|
||||||
var body: some View {
|
var body: some View {
|
||||||
VStack {
|
VStack {
|
||||||
SearchBarView(text: $searchText, vm: vm)
|
SearchBarView(text: $searchText, isFocused: _isFocusedSearchBar, vm: vm, isShowingMonthSlider: $isShowingMonthSlider)
|
||||||
|
.onChange(of: isScrolling, initial: false) { oldValue, newValue in
|
||||||
if (vm.isFirstStartOffApp && vm.isLoading) {
|
if newValue && isScrolling {
|
||||||
LoadingView(isLoading: $vm.isLoading)
|
isFocusedSearchBar = false
|
||||||
}
|
}
|
||||||
else if (vm.isFirstStartOffApp) {
|
}
|
||||||
FirstLaunchScheduleView()
|
CurrentDateView()
|
||||||
|
FilterGroupsView(vm: vm)
|
||||||
|
if vm.isLoading {
|
||||||
|
LoadingScheduleView()
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
CurrentDateView()
|
ScheduleView(vm: vm, networkMonitor: networkMonitor, isScrolling: $isScrolling)
|
||||||
ScheduleView(vm: vm)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.alert(isPresented: $vm.isShowingAlertForIncorrectGroup, error: vm.errorInNetwork) { error in
|
.alert(isPresented: $vm.isShowingAlertForIncorrectGroup, error: vm.errorInNetwork) { error in
|
||||||
|
Button("ОК") {
|
||||||
|
print("This alert")
|
||||||
|
vm.isShowingAlertForIncorrectGroup = false
|
||||||
|
vm.errorInNetwork = nil
|
||||||
|
}
|
||||||
} message: { error in
|
} message: { error in
|
||||||
Text(error.failureReason)
|
Text(error.failureReason)
|
||||||
}
|
}
|
||||||
@ -42,14 +49,14 @@ struct MainView: View {
|
|||||||
HStack {
|
HStack {
|
||||||
VStack (alignment: .leading, spacing: 0) {
|
VStack (alignment: .leading, spacing: 0) {
|
||||||
Text(vm.selectedDay.format("EEEE"))
|
Text(vm.selectedDay.format("EEEE"))
|
||||||
.font(.system(size: 40, weight: .semibold))
|
.font(.custom("Montserrat-SemiBold", fixedSize: 30))
|
||||||
.foregroundStyle(.black)
|
.foregroundStyle(.black)
|
||||||
HStack (spacing: 5) {
|
HStack (spacing: 5) {
|
||||||
Text(vm.selectedDay.format("dd"))
|
Text(vm.selectedDay.format("dd"))
|
||||||
.font(.system(size: 20, weight: .bold))
|
.font(.custom("Montserrat-Bold", fixedSize: 17))
|
||||||
.foregroundStyle(Color("grayForDate"))
|
.foregroundStyle(Color("grayForDate"))
|
||||||
Text(vm.selectedDay.format("MMMM"))
|
Text(vm.selectedDay.format("MMMM"))
|
||||||
.font(.system(size: 20, weight: .bold))
|
.font(.custom("Montserrat-Bold", fixedSize: 17))
|
||||||
.foregroundStyle(Color("grayForDate"))
|
.foregroundStyle(Color("grayForDate"))
|
||||||
Spacer()
|
Spacer()
|
||||||
Button(action: {
|
Button(action: {
|
||||||
@ -59,12 +66,12 @@ struct MainView: View {
|
|||||||
}) {
|
}) {
|
||||||
HStack(spacing: 2) {
|
HStack(spacing: 2) {
|
||||||
Text(isShowingMonthSlider ? "Свернуть" : "Развернуть")
|
Text(isShowingMonthSlider ? "Свернуть" : "Развернуть")
|
||||||
.font(.system(size: 16, weight: .light))
|
.font(.custom("Montserrat-Regular", fixedSize: 15))
|
||||||
.foregroundStyle(Color.blue)
|
.foregroundStyle(Color.blue)
|
||||||
Image(isShowingMonthSlider ? "arrowup" : "arrowdown")
|
Image(isShowingMonthSlider ? "arrowup" : "arrowdown")
|
||||||
.resizable()
|
.resizable()
|
||||||
.scaledToFit()
|
.scaledToFit()
|
||||||
.frame(width: 15, height: 15) // Установите размер изображения
|
.frame(width: 15, height: 15)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -76,16 +83,15 @@ struct MainView: View {
|
|||||||
if (!isShowingMonthSlider) {
|
if (!isShowingMonthSlider) {
|
||||||
WeekTabView(vm: vm)
|
WeekTabView(vm: vm)
|
||||||
.transition(.opacity)
|
.transition(.opacity)
|
||||||
|
.animation(.easeInOut(duration: 0.25), value: isShowingMonthSlider)
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
MonthTabView(vm: vm)
|
MonthTabView(vm: vm)
|
||||||
.transition(.opacity)
|
.transition(.opacity)
|
||||||
|
.animation(.linear(duration: 0.5), value: isShowingMonthSlider)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.padding(.horizontal)
|
.padding(.horizontal)
|
||||||
.animation(.easeInOut(duration: 0.25), value: isShowingMonthSlider)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
#Preview {
|
|
||||||
ContentView()
|
|
||||||
}
|
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
// NoScheduleView.swift
|
// NoScheduleView.swift
|
||||||
// Schedule ICTIS
|
// Schedule ICTIS
|
||||||
//
|
//
|
||||||
// Created by G412 on 12.12.2024.
|
// Created by Mironov Egor on 12.12.2024.
|
||||||
//
|
//
|
||||||
|
|
||||||
import SwiftUI
|
import SwiftUI
|
||||||
@ -11,8 +11,9 @@ struct NoScheduleView: View {
|
|||||||
var body: some View {
|
var body: some View {
|
||||||
VStack {
|
VStack {
|
||||||
ScrollView (showsIndicators: false) {
|
ScrollView (showsIndicators: false) {
|
||||||
Text("Пока расписания нет")
|
Text("Пока что расписания нет😪")
|
||||||
.padding(.top, 20)
|
.padding(.top, 100)
|
||||||
|
.font(.custom("Montserrat-SemiBold", fixedSize: 17))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,84 +1,262 @@
|
|||||||
//
|
|
||||||
// ScheduleView.swift
|
|
||||||
// Schedule ICTIS
|
|
||||||
//
|
|
||||||
// Created by G412 on 05.12.2024.
|
|
||||||
//
|
|
||||||
|
|
||||||
import SwiftUI
|
import SwiftUI
|
||||||
|
import CoreData
|
||||||
|
|
||||||
struct ScheduleView: View {
|
struct ScheduleView: View {
|
||||||
@State private var isShowingSheet: Bool = false
|
@ObservedObject var vm: ScheduleViewModel
|
||||||
@ObservedObject var vm: ViewModel
|
@ObservedObject var networkMonitor: NetworkMonitor
|
||||||
var body: some View {
|
@FetchRequest(fetchRequest: CoreDataClassModel.all()) private var classes // Список пар добавленных пользователем
|
||||||
if vm.isLoading {
|
@FetchRequest(fetchRequest: JsonClassModel.all()) private var subjects // Список пар сохраненных в CoreData
|
||||||
LoadingView(isLoading: $vm.isLoading)
|
@State private var selectedClass: CoreDataClassModel? = nil
|
||||||
|
@State private var lastOffset: CGFloat = 0
|
||||||
|
@State private var scrollTimer: Timer? = nil
|
||||||
|
@Binding var isScrolling: Bool
|
||||||
|
var provider = ClassProvider.shared
|
||||||
|
|
||||||
|
private var hasSubjectsToShow: Bool {
|
||||||
|
subjects.contains { subject in
|
||||||
|
subject.week == vm.week
|
||||||
}
|
}
|
||||||
else {
|
}
|
||||||
if vm.errorInNetwork != .invalidResponse {
|
|
||||||
ZStack (alignment: .top) {
|
private var hasClassesToShow: Bool {
|
||||||
ScrollView(.vertical, showsIndicators: false) {
|
classes.contains { _class in
|
||||||
VStack (spacing: 20) {
|
_class.day == vm.selectedDay
|
||||||
ForEach(vm.classes.indices, id: \.self) { index in
|
}
|
||||||
if index != 0 && index != 1 && index == vm.selectedIndex {
|
}
|
||||||
let daySchedule = vm.classes[index] // Это массив строк для дня
|
|
||||||
ForEach(daySchedule.indices.dropFirst(), id: \.self) { lessonIndex in
|
var body: some View {
|
||||||
let lesson = daySchedule[lessonIndex] // Это строка с расписанием одной пары
|
ZStack(alignment: .top) {
|
||||||
if !lesson.isEmpty {
|
if networkMonitor.isConnected {
|
||||||
HStack(spacing: 10) {
|
onlineContent
|
||||||
VStack {
|
} else {
|
||||||
Text(convertTimeString(vm.classes[1][lessonIndex])[0])
|
offlineContent
|
||||||
.font(.system(size: 15, weight: .regular))
|
|
||||||
Text(convertTimeString(vm.classes[1][lessonIndex])[1])
|
|
||||||
.font(.system(size: 15, weight: .regular))
|
|
||||||
}
|
|
||||||
.padding(.top, 7)
|
|
||||||
.padding(.bottom, 7)
|
|
||||||
.padding(.leading, 10)
|
|
||||||
Rectangle()
|
|
||||||
.frame(width: 2)
|
|
||||||
.frame(maxHeight: UIScreen.main.bounds.height - 18)
|
|
||||||
.padding(.top, 7)
|
|
||||||
.padding(.bottom, 7)
|
|
||||||
.foregroundColor(getColorForClass(lesson))
|
|
||||||
Text(lesson)
|
|
||||||
.font(.system(size: 18, weight: .regular))
|
|
||||||
.padding(.top, 7)
|
|
||||||
.padding(.bottom, 7)
|
|
||||||
Spacer()
|
|
||||||
}
|
|
||||||
.frame(maxWidth: UIScreen.main.bounds.width - 40, maxHeight: 230)
|
|
||||||
.background(Color.white)
|
|
||||||
.cornerRadius(20)
|
|
||||||
.shadow(color: .black.opacity(0.25), radius: 4, x: 2, y: 2)
|
|
||||||
.onTapGesture {
|
|
||||||
isShowingSheet = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.frame(width: UIScreen.main.bounds.width)
|
|
||||||
.padding(.bottom, 100)
|
|
||||||
.padding(.top, 30)
|
|
||||||
}
|
|
||||||
VStack {
|
|
||||||
LinearGradient(gradient: Gradient(colors: [Color("background").opacity(0.95), Color.white.opacity(0.1)]), startPoint: .top, endPoint: .bottom)
|
|
||||||
}
|
|
||||||
.frame(width: UIScreen.main.bounds.width, height: 15)
|
|
||||||
}
|
|
||||||
.sheet(isPresented: $isShowingSheet) {
|
|
||||||
SheetChangeClassView(isShowingSheet: $isShowingSheet)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
else {
|
gradientOverlay
|
||||||
|
}
|
||||||
|
.onAppear {
|
||||||
|
deleteClassesFormCoreDataIfMonday()
|
||||||
|
if networkMonitor.isConnected {
|
||||||
|
checkSavingOncePerDay()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.sheet(item: $selectedClass, onDismiss: { selectedClass = nil }) { _class in
|
||||||
|
CreateEditClassView(vm: .init(provider: provider, _class: _class), day: vm.selectedDay)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Онлайн-контент (с интернетом)
|
||||||
|
private var onlineContent: some View {
|
||||||
|
Group {
|
||||||
|
if vm.errorInNetwork == .timeout {
|
||||||
|
NetworkErrorView(message: "Проверьте подключение к интернету")
|
||||||
|
} else if vm.isLoading {
|
||||||
|
LoadingScheduleView()
|
||||||
|
} else if vm.errorInNetwork != .invalidResponse {
|
||||||
|
scheduleScrollView(isOnline: true)
|
||||||
|
} else {
|
||||||
NoScheduleView()
|
NoScheduleView()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
.onAppear {
|
||||||
|
if vm.classesGroups.isEmpty {
|
||||||
|
vm.fetchWeekSchedule()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Оффлайн-контент (без интернета)
|
||||||
|
private var offlineContent: some View {
|
||||||
|
scheduleScrollView(isOnline: false)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Общий ScrollView для расписания
|
||||||
|
private func scheduleScrollView(isOnline: Bool) -> some View {
|
||||||
|
ScrollView(.vertical, showsIndicators: false) {
|
||||||
|
VStack(spacing: 30) {
|
||||||
|
subjectsSection(isOnline: isOnline)
|
||||||
|
myPairsSection
|
||||||
|
}
|
||||||
|
.frame(width: UIScreen.main.bounds.width)
|
||||||
|
.padding(.bottom, 100)
|
||||||
|
.padding(.top, 10)
|
||||||
|
.background(GeometryReader { geometry in
|
||||||
|
Color.clear.preference(key: ViewOffsetKey.self, value: geometry.frame(in: .global).minY)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
.onPreferenceChange(ViewOffsetKey.self) { offset in
|
||||||
|
if offset != lastOffset {
|
||||||
|
isScrolling = true
|
||||||
|
scrollTimer?.invalidate()
|
||||||
|
scrollTimer = Timer.scheduledTimer(withTimeInterval: 0.1, repeats: false) { _ in
|
||||||
|
isScrolling = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
lastOffset = offset
|
||||||
|
}
|
||||||
|
.onDisappear {
|
||||||
|
scrollTimer?.invalidate()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Секция с парами
|
||||||
|
private func subjectsSection(isOnline: Bool) -> some View {
|
||||||
|
VStack(alignment: .leading, spacing: 10) {
|
||||||
|
if isOnline {
|
||||||
|
ForEach(0..<vm.classesGroups.count, id: \.self) { dayIndex in
|
||||||
|
if dayIndex == vm.selectedIndex {
|
||||||
|
ForEach(vm.classesGroups[dayIndex]) { info in
|
||||||
|
if vm.showOnlyChoosenGroup == "Все" || info.group == vm.showOnlyChoosenGroup {
|
||||||
|
SubjectView(info: ClassInfo(subject: info.subject, group: info.group, time: info.time), vm: vm)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
let filteredSubjects = subjects.filter { $0.day == Int16(vm.selectedIndex) }
|
||||||
|
if (filteredSubjects.isEmpty || vm.week != 0) && !hasClassesToShow {
|
||||||
|
ConnectingToNetworkView()
|
||||||
|
.padding(.top, 100)
|
||||||
|
} else {
|
||||||
|
ForEach(filteredSubjects, id: \.self) { subject in
|
||||||
|
if (vm.showOnlyChoosenGroup == "Все" || subject.group == vm.showOnlyChoosenGroup) && vm.week == 0 {
|
||||||
|
SubjectView(info: ClassInfo(subject: subject.name!, group: subject.group!, time: subject.time!), vm: vm)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Секция "Мои пары"
|
||||||
|
private var myPairsSection: some View {
|
||||||
|
Group {
|
||||||
|
if classes.contains(where: { daysAreEqual($0.day, vm.selectedDay) }) {
|
||||||
|
VStack(alignment: .leading, spacing: 20) {
|
||||||
|
Text("Мои пары")
|
||||||
|
.font(.custom("Montserrat-Bold", fixedSize: 20))
|
||||||
|
ForEach(classes) { _class in
|
||||||
|
if daysAreEqual(_class.day, vm.selectedDay) {
|
||||||
|
CreatedClassView(_class: _class)
|
||||||
|
.onTapGesture {
|
||||||
|
selectedClass = _class
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Градиентный оверлей
|
||||||
|
private var gradientOverlay: some View {
|
||||||
|
VStack {
|
||||||
|
LinearGradient(
|
||||||
|
gradient: Gradient(colors: [Color("background").opacity(0.95), Color.white.opacity(0.1)]),
|
||||||
|
startPoint: .top,
|
||||||
|
endPoint: .bottom
|
||||||
|
)
|
||||||
|
}
|
||||||
|
.frame(width: UIScreen.main.bounds.width, height: 15)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extension ScheduleView {
|
||||||
|
private func deleteClassesFormCoreDataIfMonday() {
|
||||||
|
let today = Date()
|
||||||
|
let calendar = Calendar.current
|
||||||
|
let weekday = calendar.component(.weekday, from: today)
|
||||||
|
|
||||||
|
if weekday == 6 {
|
||||||
|
for _class in classes {
|
||||||
|
if _class.day < today {
|
||||||
|
do {
|
||||||
|
try provider.delete(_class, in: provider.viewContext)
|
||||||
|
} catch {
|
||||||
|
print("❌ - Ошибка при удалении: \(error)")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func saveGroupsToMemory() {
|
||||||
|
var indexOfTheDay: Int16 = 0
|
||||||
|
let context = provider.newContext // Создаем новый контекст
|
||||||
|
|
||||||
|
context.perform {
|
||||||
|
for dayIndex in 0..<self.vm.classesGroups.count {
|
||||||
|
let classesForDay = self.vm.classesGroups[dayIndex]
|
||||||
|
|
||||||
|
// Проходим по всем занятиям текущего дня
|
||||||
|
for classInfo in classesForDay {
|
||||||
|
let newClass = JsonClassModel(context: context)
|
||||||
|
|
||||||
|
// Заполняем атрибуты
|
||||||
|
newClass.name = classInfo.subject
|
||||||
|
newClass.group = classInfo.group
|
||||||
|
newClass.time = classInfo.time
|
||||||
|
newClass.day = indexOfTheDay
|
||||||
|
newClass.week = Int16(vm.week)
|
||||||
|
}
|
||||||
|
indexOfTheDay += 1
|
||||||
|
}
|
||||||
|
|
||||||
|
// Сохраняем изменения в CoreData
|
||||||
|
do {
|
||||||
|
try self.provider.persist(in: context)
|
||||||
|
print("✅ Успешно сохранено в CoreData")
|
||||||
|
} catch {
|
||||||
|
print("❌ Ошибка при сохранении в CoreData: \(error)")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@MainActor
|
||||||
|
func deleteAllJsonClassModelsSync() throws {
|
||||||
|
let context = provider.viewContext
|
||||||
|
|
||||||
|
let fetchRequest: NSFetchRequest<NSFetchRequestResult> = JsonClassModel.fetchRequest()
|
||||||
|
let deleteRequest = NSBatchDeleteRequest(fetchRequest: fetchRequest)
|
||||||
|
|
||||||
|
try context.execute(deleteRequest)
|
||||||
|
try context.save()
|
||||||
|
print("✅ Все объекты JsonClassModel успешно удалены")
|
||||||
|
}
|
||||||
|
|
||||||
|
func checkSavingOncePerDay() {
|
||||||
|
let today = Date()
|
||||||
|
let calendar = Calendar.current
|
||||||
|
let todayStart = calendar.startOfDay(for: today) // Начало текущего дня
|
||||||
|
|
||||||
|
// Получаем дату последнего выполнения из UserDefaults
|
||||||
|
let lastCheckDate = UserDefaults.standard.object(forKey: "LastSaving") as? Date ?? .distantPast
|
||||||
|
let lastCheckStart = calendar.startOfDay(for: lastCheckDate)
|
||||||
|
|
||||||
|
print("Дата последнего сохранения расписания в CoreData: \(lastCheckDate)")
|
||||||
|
|
||||||
|
// Проверяем, был ли уже выполнен код сегодня
|
||||||
|
if lastCheckStart < todayStart && networkMonitor.isConnected {
|
||||||
|
print("✅ Интернет есть, сохранение пар в CoreData")
|
||||||
|
vm.fillDictForVm()
|
||||||
|
vm.fetchWeekSchedule()
|
||||||
|
do {
|
||||||
|
try deleteAllJsonClassModelsSync()
|
||||||
|
} catch {
|
||||||
|
print("Ошибка при удалении: \(error)")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
saveGroupsToMemory()
|
||||||
|
|
||||||
|
// Сохраняем текущую дату как дату последнего выполнения
|
||||||
|
UserDefaults.standard.set(today, forKey: "LastSaving")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#Preview {
|
struct ViewOffsetKey: PreferenceKey {
|
||||||
ContentView()
|
typealias Value = CGFloat
|
||||||
|
static var defaultValue = CGFloat.zero
|
||||||
|
static func reduce(value: inout Value, nextValue: () -> Value) {
|
||||||
|
value += nextValue()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -9,9 +9,12 @@ import SwiftUI
|
|||||||
|
|
||||||
struct SearchBarView: View {
|
struct SearchBarView: View {
|
||||||
@Binding var text: String
|
@Binding var text: String
|
||||||
@State private var isEditing = false
|
@FocusState var isFocused: Bool
|
||||||
@State private var isShowingSheet: Bool = false
|
@State private var isShowingSheet: Bool = false
|
||||||
@ObservedObject var vm: ViewModel
|
@ObservedObject var vm: ScheduleViewModel
|
||||||
|
@Binding var isShowingMonthSlider: Bool
|
||||||
|
|
||||||
|
var provider = ClassProvider.shared
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
HStack (spacing: 11) {
|
HStack (spacing: 11) {
|
||||||
@ -22,40 +25,44 @@ struct SearchBarView: View {
|
|||||||
.padding(.trailing, 7)
|
.padding(.trailing, 7)
|
||||||
TextField("Поиск группы", text: $text)
|
TextField("Поиск группы", text: $text)
|
||||||
.disableAutocorrection(true)
|
.disableAutocorrection(true)
|
||||||
.onTapGesture {
|
.focused($isFocused)
|
||||||
self.isEditing = true
|
|
||||||
}
|
|
||||||
.onSubmit {
|
.onSubmit {
|
||||||
self.isEditing = false
|
self.isFocused = false
|
||||||
if (!text.isEmpty) {
|
if (!text.isEmpty) {
|
||||||
vm.fetchWeekSchedule(text)
|
vm.nameToHtml[vm.searchingGroup] = nil
|
||||||
vm.group = text
|
vm.removeFromSchedule(group: vm.searchingGroup)
|
||||||
|
text = transformStringToFormat(text)
|
||||||
|
vm.searchingGroup = text
|
||||||
|
vm.nameToHtml[text] = ""
|
||||||
|
vm.fetchWeekSchedule()
|
||||||
|
vm.updateFilteringGroups()
|
||||||
}
|
}
|
||||||
self.text = ""
|
self.text = ""
|
||||||
}
|
}
|
||||||
.submitLabel(.search)
|
.submitLabel(.search)
|
||||||
if isEditing {
|
if isFocused {
|
||||||
Button {
|
Button {
|
||||||
self.text = ""
|
self.text = ""
|
||||||
self.isEditing = false
|
self.isFocused = false
|
||||||
UIApplication.shared.sendAction(#selector(UIResponder.resignFirstResponder), to: nil, from: nil, for: nil)
|
} label: {
|
||||||
} label: {
|
Image(systemName: "xmark.circle.fill")
|
||||||
Image(systemName: "xmark.circle.fill")
|
.padding(.trailing, 20)
|
||||||
.padding(.trailing, 20)
|
.offset(x: 10)
|
||||||
.offset(x: 10)
|
.foregroundColor(.gray)
|
||||||
.foregroundColor(.gray)
|
.background(
|
||||||
.background(
|
)
|
||||||
)
|
|
||||||
}
|
}
|
||||||
.background(Color.red)
|
}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
.simultaneousGesture(TapGesture().onEnded {
|
||||||
|
self.isShowingMonthSlider = false
|
||||||
|
})
|
||||||
.frame(height: 40)
|
.frame(height: 40)
|
||||||
.background(
|
.background(
|
||||||
RoundedRectangle(cornerRadius: 10)
|
RoundedRectangle(cornerRadius: 10)
|
||||||
.fill(.white)
|
.fill(.white)
|
||||||
)
|
)
|
||||||
if (!vm.isFirstStartOffApp) {
|
if !isFocused {
|
||||||
Button {
|
Button {
|
||||||
isShowingSheet = true
|
isShowingSheet = true
|
||||||
} label: {
|
} label: {
|
||||||
@ -78,11 +85,8 @@ struct SearchBarView: View {
|
|||||||
.frame(height: 40)
|
.frame(height: 40)
|
||||||
.accentColor(.blue)
|
.accentColor(.blue)
|
||||||
.sheet(isPresented: $isShowingSheet) {
|
.sheet(isPresented: $isShowingSheet) {
|
||||||
SheetCreateClassView(isShowingSheet: $isShowingSheet)
|
CreateEditClassView(vm: .init(provider: provider), day: vm.selectedDay)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#Preview {
|
|
||||||
ContentView()
|
|
||||||
}
|
|
||||||
|
@ -1,37 +0,0 @@
|
|||||||
//
|
|
||||||
// SheetView.swift
|
|
||||||
// Schedule ICTIS
|
|
||||||
//
|
|
||||||
// Created by Mironov Egor on 12.12.2024.
|
|
||||||
//
|
|
||||||
|
|
||||||
import SwiftUI
|
|
||||||
|
|
||||||
struct SheetChangeClassView: View {
|
|
||||||
@Binding var isShowingSheet: Bool
|
|
||||||
var body: some View {
|
|
||||||
NavigationView {
|
|
||||||
VStack {
|
|
||||||
Spacer()
|
|
||||||
Text("Создание новой пары")
|
|
||||||
Spacer()
|
|
||||||
}
|
|
||||||
.toolbar {
|
|
||||||
ToolbarItem(placement: .navigationBarLeading) {
|
|
||||||
Button("Отменить") {
|
|
||||||
isShowingSheet = false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
ToolbarItem(placement: .navigationBarTrailing) {
|
|
||||||
Button("Сохранить") {
|
|
||||||
isShowingSheet = false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#Preview {
|
|
||||||
SheetChangeClassView(isShowingSheet: .constant(true))
|
|
||||||
}
|
|
@ -1,123 +0,0 @@
|
|||||||
//
|
|
||||||
// SheetCreateClassView.swift
|
|
||||||
// Schedule ICTIS
|
|
||||||
//
|
|
||||||
// Created by Mironov Egor on 12.12.2024.
|
|
||||||
//
|
|
||||||
|
|
||||||
import SwiftUI
|
|
||||||
|
|
||||||
struct SheetCreateClassView: View {
|
|
||||||
@Binding var isShowingSheet: Bool
|
|
||||||
@State private var textForNameOfClass = ""
|
|
||||||
@State private var textForNameOfAuditory = ""
|
|
||||||
@State private var textForNameOfProfessor = ""
|
|
||||||
@State private var isShowingDatePickerForDate: Bool = false
|
|
||||||
@State private var selectedDay: Date = Date()
|
|
||||||
@State private var selectedStartTime: Date = Date()
|
|
||||||
@State private var selectedEndTime: Date = Date()
|
|
||||||
@State private var isImportant: Bool = false
|
|
||||||
@State private var selectedOption: String = "Нет"
|
|
||||||
@State private var textForComment: String = ""
|
|
||||||
|
|
||||||
var body: some View {
|
|
||||||
NavigationView {
|
|
||||||
ScrollView(.vertical, showsIndicators: false) {
|
|
||||||
VStack {
|
|
||||||
FieldView(text: $textForNameOfClass, nameOfImage: "book", labelForField: "Предмет")
|
|
||||||
.padding(.bottom, 10)
|
|
||||||
FieldView(text: $textForNameOfAuditory, nameOfImage: "mappin.and.ellipse", labelForField: "Корпус-аудитория")
|
|
||||||
.padding(.bottom, 10)
|
|
||||||
FieldView(text: $textForNameOfProfessor, nameOfImage: "book", labelForField: "Преподаватель")
|
|
||||||
.padding(.bottom, 10)
|
|
||||||
HStack {
|
|
||||||
Image(systemName: "calendar")
|
|
||||||
.foregroundColor(Color.gray)
|
|
||||||
.padding(.leading, 12)
|
|
||||||
.padding(.trailing, 7)
|
|
||||||
Text("Дата")
|
|
||||||
.foregroundColor(Color("grayForFields").opacity(0.5))
|
|
||||||
.font(.system(size: 18, weight: .regular))
|
|
||||||
Spacer()
|
|
||||||
Text("\(selectedDay, formatter: dateFormatter)")
|
|
||||||
.foregroundColor(.black)
|
|
||||||
.font(.system(size: 18, weight: .medium))
|
|
||||||
.padding(.trailing, 20)
|
|
||||||
}
|
|
||||||
.frame(height: 40)
|
|
||||||
.background(
|
|
||||||
RoundedRectangle(cornerRadius: 10)
|
|
||||||
.fill(.white)
|
|
||||||
)
|
|
||||||
.overlay {
|
|
||||||
DatePicker("", selection: $selectedDay, in: Date()..., displayedComponents: .date)
|
|
||||||
.blendMode(.destinationOver)
|
|
||||||
}
|
|
||||||
.padding(.bottom, 10)
|
|
||||||
HStack {
|
|
||||||
StartEndTimeView(selectedTime: $selectedStartTime, imageName: "clock", text: "Начало")
|
|
||||||
Spacer()
|
|
||||||
StartEndTimeView(selectedTime: $selectedEndTime, imageName: "clock.badge.xmark", text: "Конец")
|
|
||||||
}
|
|
||||||
.frame(height: 40)
|
|
||||||
.padding(.bottom, 10)
|
|
||||||
Toggle("Пометить как важную", isOn: $isImportant)
|
|
||||||
.frame(height: 40)
|
|
||||||
.padding(.horizontal)
|
|
||||||
.background(
|
|
||||||
RoundedRectangle(cornerRadius: 10)
|
|
||||||
.fill(.white)
|
|
||||||
)
|
|
||||||
.padding(.bottom, 10)
|
|
||||||
|
|
||||||
HStack {
|
|
||||||
Text("Напоминанние")
|
|
||||||
Spacer()
|
|
||||||
Picker("Напоминание", selection: $selectedOption, content: {
|
|
||||||
ForEach(MockData.notifications, id: \.self) {
|
|
||||||
Text($0)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.accentColor(Color("grayForFields"))
|
|
||||||
}
|
|
||||||
.frame(height: 40)
|
|
||||||
.padding(.horizontal)
|
|
||||||
.background(
|
|
||||||
RoundedRectangle(cornerRadius: 10)
|
|
||||||
.fill(.white)
|
|
||||||
)
|
|
||||||
.padding(.bottom, 10)
|
|
||||||
|
|
||||||
CommentView(textForComment: $textForComment)
|
|
||||||
Spacer()
|
|
||||||
}
|
|
||||||
.padding(.horizontal)
|
|
||||||
.padding(.bottom, 60)
|
|
||||||
}
|
|
||||||
.toolbar {
|
|
||||||
ToolbarItem(placement: .navigationBarLeading) {
|
|
||||||
Button("Отменить") {
|
|
||||||
isShowingSheet = false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
ToolbarItem(placement: .navigationBarTrailing) {
|
|
||||||
Button("Сохранить") {
|
|
||||||
isShowingSheet = false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.navigationTitle("Новая пара")
|
|
||||||
.background(Color("background"))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
private var dateFormatter: DateFormatter {
|
|
||||||
let formatter = DateFormatter()
|
|
||||||
formatter.dateStyle = .medium
|
|
||||||
formatter.timeStyle = .none
|
|
||||||
return formatter
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#Preview {
|
|
||||||
SheetCreateClassView(isShowingSheet: .constant(true))
|
|
||||||
}
|
|
301
Schedule ICTIS/Main/Views/Sheets/CreateEditClassView.swift
Normal file
@ -0,0 +1,301 @@
|
|||||||
|
//
|
||||||
|
// SheetCreateClassView.swift
|
||||||
|
// Schedule ICTIS
|
||||||
|
//
|
||||||
|
// Created by Mironov Egor on 12.12.2024.
|
||||||
|
//
|
||||||
|
|
||||||
|
import SwiftUI
|
||||||
|
|
||||||
|
struct CreateEditClassView: View {
|
||||||
|
@Environment(\.dismiss) private var dismiss
|
||||||
|
@State private var isShowingDatePickerForDate: Bool = false
|
||||||
|
@ObservedObject var vm: EditClassViewModel
|
||||||
|
var day: Date
|
||||||
|
@State private var isIncorrectDate1: Bool = false
|
||||||
|
@State private var isIncorrectDate2: Bool = false
|
||||||
|
@State private var isShowingSubjectFieldRed: Bool = false
|
||||||
|
@State private var isSelectedTime1 = false
|
||||||
|
@State private var isSelectedTime2 = false
|
||||||
|
@State private var textForLabelInSubjectField: String = "Предмет"
|
||||||
|
@State private var selectedType: String = "Оффлайн"
|
||||||
|
@FocusState private var isFocusedSubject: Bool
|
||||||
|
@FocusState private var isFocusedAuditory: Bool
|
||||||
|
@FocusState private var isFocusedProfessor: Bool
|
||||||
|
@FocusState private var isFocusedComment: Bool
|
||||||
|
var provider = ClassProvider.shared
|
||||||
|
var body: some View {
|
||||||
|
NavigationView {
|
||||||
|
ScrollView(.vertical, showsIndicators: false) {
|
||||||
|
VStack {
|
||||||
|
SubjectFieldView(text: $vm._class.subject, isShowingSubjectFieldRed: $isShowingSubjectFieldRed, labelForField: $textForLabelInSubjectField, isFocused: _isFocusedSubject)
|
||||||
|
.padding(.bottom, 10)
|
||||||
|
|
||||||
|
HStack {
|
||||||
|
HStack {
|
||||||
|
Text("Тип")
|
||||||
|
.font(.custom("Montserrat-Medium", fixedSize: 17))
|
||||||
|
.foregroundColor(.black)
|
||||||
|
Spacer()
|
||||||
|
HStack {
|
||||||
|
Text(vm._class.online)
|
||||||
|
.font(.custom("Montserrat-Medium", fixedSize: 17))
|
||||||
|
.foregroundColor(Color("customGray3"))
|
||||||
|
Image("upDownArrows")
|
||||||
|
.resizable()
|
||||||
|
.scaledToFit()
|
||||||
|
.frame(width: 15, height: 15)
|
||||||
|
}
|
||||||
|
.padding(.horizontal)
|
||||||
|
}
|
||||||
|
.padding(.horizontal)
|
||||||
|
.padding(.top, 10)
|
||||||
|
.padding(.bottom, 10)
|
||||||
|
.background(
|
||||||
|
RoundedRectangle(cornerRadius: 10)
|
||||||
|
.fill(.white)
|
||||||
|
)
|
||||||
|
.overlay {
|
||||||
|
HStack {
|
||||||
|
Spacer()
|
||||||
|
Picker("Тип", selection: $vm._class.online, content: {
|
||||||
|
ForEach(MockData.onlineOrOffline, id: \.self) {
|
||||||
|
Text($0)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.accentColor(Color("grayForFields"))
|
||||||
|
.padding(.trailing, 35)
|
||||||
|
.blendMode(.destinationOver)
|
||||||
|
}
|
||||||
|
.frame(width: UIScreen.main.bounds.width)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.padding(.bottom, 10)
|
||||||
|
|
||||||
|
ZStack {
|
||||||
|
if vm._class.online == "Оффлайн" {
|
||||||
|
AuditoryFieldView(text: $vm._class.auditory, labelForField: "Корпус-аудитория", isFocused: _isFocusedAuditory)
|
||||||
|
.padding(.bottom, 10)
|
||||||
|
.transition(.asymmetric(
|
||||||
|
insertion: .offset(y: -50).combined(with: .identity),
|
||||||
|
removal: .offset(y: -50).combined(with: .opacity)
|
||||||
|
))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.animation(
|
||||||
|
vm._class.online == "Оффлайн" ?
|
||||||
|
.linear(duration: 0.3) : // Анимация для появления
|
||||||
|
.linear(duration: 0.2), // Анимация для исчезновения
|
||||||
|
value: vm._class.online
|
||||||
|
)
|
||||||
|
|
||||||
|
ProfessorFieldView(text: $vm._class.professor, labelForField: "Преподаватель", isFocused: _isFocusedProfessor)
|
||||||
|
.padding(.bottom, 10)
|
||||||
|
|
||||||
|
HStack {
|
||||||
|
Image(systemName: "calendar")
|
||||||
|
.foregroundColor(Color.gray)
|
||||||
|
.padding(.leading, 12)
|
||||||
|
.padding(.trailing, 5)
|
||||||
|
Text("Дата")
|
||||||
|
.foregroundColor(Color("grayForFields").opacity(0.5))
|
||||||
|
.font(.custom("Montserrat-Meduim", fixedSize: 17))
|
||||||
|
Spacer()
|
||||||
|
Text("\(vm._class.day, formatter: dateFormatter)")
|
||||||
|
.foregroundColor(.black)
|
||||||
|
.font(.custom("Montserrat-Medium", fixedSize: 17))
|
||||||
|
.padding(.trailing, 20)
|
||||||
|
}
|
||||||
|
.frame(height: 40)
|
||||||
|
.background(
|
||||||
|
RoundedRectangle(cornerRadius: 10)
|
||||||
|
.fill(.white)
|
||||||
|
)
|
||||||
|
.overlay {
|
||||||
|
DatePicker("", selection: $vm._class.day, in: Date()..., displayedComponents: .date)
|
||||||
|
.blendMode(.destinationOver)
|
||||||
|
|
||||||
|
}
|
||||||
|
.padding(.bottom, 10)
|
||||||
|
HStack {
|
||||||
|
StartEndTimeFieldView(isIncorrectDate: $isIncorrectDate1, selectedDay: $vm._class.day, selectedTime: $vm._class.starttime, imageName: "clock", text: "Начало", isTimeSelected: $isSelectedTime1)
|
||||||
|
.onChange(of: vm._class.starttime) { oldValue, newValue in
|
||||||
|
if !checkStartTimeLessThenEndTime(vm._class.starttime, vm._class.endtime) {
|
||||||
|
self.isIncorrectDate1 = true
|
||||||
|
self.isSelectedTime1 = false
|
||||||
|
print("Первый")
|
||||||
|
print(self.isSelectedTime1)
|
||||||
|
print(self.isSelectedTime2)
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
self.isIncorrectDate1 = false
|
||||||
|
self.isIncorrectDate2 = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Spacer()
|
||||||
|
StartEndTimeFieldView(isIncorrectDate: $isIncorrectDate2, selectedDay: $vm._class.day, selectedTime: $vm._class.endtime, imageName: "clock.badge.xmark", text: "Конец", isTimeSelected: $isSelectedTime2)
|
||||||
|
.onChange(of: vm._class.endtime) { oldValue, newValue in
|
||||||
|
if !checkStartTimeLessThenEndTime(vm._class.starttime, vm._class.endtime) {
|
||||||
|
self.isIncorrectDate2 = true
|
||||||
|
self.isSelectedTime2 = false
|
||||||
|
print("Второй")
|
||||||
|
print(self.isSelectedTime1)
|
||||||
|
print(self.isSelectedTime2)
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
self.isIncorrectDate1 = false
|
||||||
|
self.isIncorrectDate2 = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.frame(height: 40)
|
||||||
|
.padding(.bottom, 10)
|
||||||
|
Toggle("Пометить как важную", isOn: $vm._class.important)
|
||||||
|
.font(.custom("Montserrat-Medium", fixedSize: 17))
|
||||||
|
.frame(height: 40)
|
||||||
|
.padding(.horizontal)
|
||||||
|
.background(
|
||||||
|
RoundedRectangle(cornerRadius: 10)
|
||||||
|
.fill(.white)
|
||||||
|
)
|
||||||
|
.padding(.bottom, 10)
|
||||||
|
|
||||||
|
HStack {
|
||||||
|
HStack {
|
||||||
|
Text("Напоминание")
|
||||||
|
.font(.custom("Montserrat-Medium", fixedSize: 17))
|
||||||
|
.foregroundColor(.black)
|
||||||
|
Spacer()
|
||||||
|
HStack {
|
||||||
|
Text(vm._class.notification)
|
||||||
|
.font(.custom("Montserrat-Medium", fixedSize: 17))
|
||||||
|
.foregroundColor(Color("customGray3"))
|
||||||
|
Image("upDownArrows")
|
||||||
|
.resizable()
|
||||||
|
.scaledToFit()
|
||||||
|
.frame(width: 15, height: 15)
|
||||||
|
}
|
||||||
|
.padding(.horizontal)
|
||||||
|
}
|
||||||
|
.padding(.horizontal)
|
||||||
|
.padding(.top, 10)
|
||||||
|
.padding(.bottom, 10)
|
||||||
|
.background(
|
||||||
|
RoundedRectangle(cornerRadius: 10)
|
||||||
|
.fill(.white)
|
||||||
|
)
|
||||||
|
.overlay {
|
||||||
|
HStack {
|
||||||
|
Spacer()
|
||||||
|
Picker("", selection: $vm._class.notification , content: {
|
||||||
|
ForEach(MockData.notifications, id: \.self) {
|
||||||
|
Text($0)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.accentColor(Color("grayForFields"))
|
||||||
|
.padding(.trailing, 35)
|
||||||
|
.blendMode(.destinationOver)
|
||||||
|
}
|
||||||
|
.frame(width: UIScreen.main.bounds.width)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.padding(.bottom, 10)
|
||||||
|
|
||||||
|
CommentFieldView(textForComment: $vm._class.comment, isFocused: _isFocusedComment)
|
||||||
|
.padding(.bottom, 20)
|
||||||
|
|
||||||
|
|
||||||
|
if !vm.isNew {
|
||||||
|
Button {
|
||||||
|
do {
|
||||||
|
try provider.delete(vm._class, in: provider.viewContext)
|
||||||
|
dismiss()
|
||||||
|
} catch {
|
||||||
|
print(error)
|
||||||
|
}
|
||||||
|
} label: {
|
||||||
|
HStack {
|
||||||
|
Spacer()
|
||||||
|
Image(systemName: "trash")
|
||||||
|
Text("Удалить занятие")
|
||||||
|
.font(.custom("Montserrat-Medium", fixedSize: 17))
|
||||||
|
Spacer()
|
||||||
|
}
|
||||||
|
.frame(height: 40)
|
||||||
|
.background(Color.white)
|
||||||
|
.foregroundColor(Color.red)
|
||||||
|
.cornerRadius(10)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Spacer()
|
||||||
|
}
|
||||||
|
.padding(.horizontal)
|
||||||
|
.padding(.bottom, 60)
|
||||||
|
}
|
||||||
|
.toolbar {
|
||||||
|
ToolbarItem(placement: .navigationBarLeading) {
|
||||||
|
Button("Отменить") {
|
||||||
|
dismiss()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ToolbarItem(placement: .navigationBarTrailing) {
|
||||||
|
Button("Сохранить") {
|
||||||
|
isFocusedSubject = false
|
||||||
|
isFocusedProfessor = false
|
||||||
|
isFocusedAuditory = false
|
||||||
|
isFocusedComment = false
|
||||||
|
if (vm._class.subject.isEmpty || (isIncorrectDate1 || isIncorrectDate2) || (!isSelectedTime1 || !isSelectedTime2)) {
|
||||||
|
if (vm._class.subject.isEmpty) {
|
||||||
|
self.isShowingSubjectFieldRed = true
|
||||||
|
self.textForLabelInSubjectField = ""
|
||||||
|
}
|
||||||
|
if !isSelectedTime1 {
|
||||||
|
self.isIncorrectDate1 = true
|
||||||
|
}
|
||||||
|
if !isSelectedTime2 {
|
||||||
|
self.isIncorrectDate2 = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
do {
|
||||||
|
try vm.save()
|
||||||
|
dismiss()
|
||||||
|
} catch {
|
||||||
|
print(error)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.navigationTitle(vm.isNew ? "Новая пара" : "Изменить данные")
|
||||||
|
.background(Color("background"))
|
||||||
|
.onAppear {
|
||||||
|
let temp = Calendar.current.date(byAdding: .hour, value: 1, to: Date.init())
|
||||||
|
if let endTime = temp {
|
||||||
|
if (!hoursMinutesAreEqual(date1: vm._class.starttime, isEqualTo: Date()) && !hoursMinutesAreEqual(date1: vm._class.endtime, isEqualTo: endTime)) {
|
||||||
|
self.isSelectedTime1 = true
|
||||||
|
self.isSelectedTime2 = true
|
||||||
|
print(vm._class.starttime)
|
||||||
|
print(vm._class.endtime)
|
||||||
|
print(endTime)
|
||||||
|
print(Date())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if day > Calendar.current.startOfDay(for: Date()) {
|
||||||
|
vm._class.day = day
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.onTapGesture {
|
||||||
|
isFocusedSubject = false
|
||||||
|
isFocusedProfessor = false
|
||||||
|
isFocusedAuditory = false
|
||||||
|
isFocusedComment = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#Preview {
|
||||||
|
let day: Date = .init()
|
||||||
|
CreateEditClassView(vm: .init(provider: .shared), day: day)
|
||||||
|
}
|
@ -1,59 +0,0 @@
|
|||||||
//
|
|
||||||
// StartEndTimeView.swift
|
|
||||||
// Schedule ICTIS
|
|
||||||
//
|
|
||||||
// Created by Mironov Egor on 17.12.2024.
|
|
||||||
//
|
|
||||||
|
|
||||||
import SwiftUI
|
|
||||||
|
|
||||||
struct StartEndTimeView: View {
|
|
||||||
@Binding var selectedTime: Date
|
|
||||||
var imageName: String
|
|
||||||
var text: String
|
|
||||||
@State private var isTimeSelected: Bool = false
|
|
||||||
var body: some View {
|
|
||||||
HStack {
|
|
||||||
Image(systemName: imageName)
|
|
||||||
.foregroundColor(Color("grayForFields"))
|
|
||||||
.padding(.leading, 12)
|
|
||||||
|
|
||||||
if !isTimeSelected {
|
|
||||||
Text(text)
|
|
||||||
.font(.system(size: 17, weight: .regular))
|
|
||||||
.foregroundColor(.gray.opacity(0.5))
|
|
||||||
}
|
|
||||||
|
|
||||||
if isTimeSelected {
|
|
||||||
Text("\(selectedTime, formatter: timeFormatter)")
|
|
||||||
.foregroundColor(.black)
|
|
||||||
.font(.system(size: 17, weight: .medium))
|
|
||||||
.padding(.trailing, 10)
|
|
||||||
}
|
|
||||||
Spacer()
|
|
||||||
}
|
|
||||||
.frame(width: (UIScreen.main.bounds.width / 2) - 22, height: 40)
|
|
||||||
.background(
|
|
||||||
RoundedRectangle(cornerRadius: 10)
|
|
||||||
.fill(.white)
|
|
||||||
)
|
|
||||||
.overlay {
|
|
||||||
DatePicker("", selection: $selectedTime, in: Date()..., displayedComponents: .hourAndMinute)
|
|
||||||
.padding(.trailing, 35)
|
|
||||||
.blendMode(.destinationOver)
|
|
||||||
.onChange(of: selectedTime) { newValue, oldValue in
|
|
||||||
isTimeSelected = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private var timeFormatter: DateFormatter {
|
|
||||||
let formatter = DateFormatter()
|
|
||||||
formatter.dateFormat = "HH:mm"
|
|
||||||
return formatter
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#Preview {
|
|
||||||
StartEndTimeView(selectedTime: .constant(Date()), imageName: "clock", text: "Начало")
|
|
||||||
}
|
|
61
Schedule ICTIS/Main/Views/SubjectView.swift
Normal file
@ -0,0 +1,61 @@
|
|||||||
|
//
|
||||||
|
// SubjectView.swift
|
||||||
|
// Schedule ICTIS
|
||||||
|
//
|
||||||
|
// Created by Egor Mironov on 02.04.2025.
|
||||||
|
//
|
||||||
|
|
||||||
|
import SwiftUI
|
||||||
|
|
||||||
|
struct SubjectView: View {
|
||||||
|
let info: ClassInfo
|
||||||
|
@ObservedObject var vm: ScheduleViewModel
|
||||||
|
@State private var onlyOneGroup: Bool = false
|
||||||
|
|
||||||
|
var body: some View {
|
||||||
|
VStack(alignment: .trailing) {
|
||||||
|
if !onlyOneGroup {
|
||||||
|
Text(info.group)
|
||||||
|
.font(.custom("Montserrat-Regular", fixedSize: 11))
|
||||||
|
.foregroundColor(Color("grayForNameGroup"))
|
||||||
|
}
|
||||||
|
HStack(spacing: 15) {
|
||||||
|
VStack {
|
||||||
|
Text(convertTimeString(info.time)[0])
|
||||||
|
.font(.custom("Montserrat-Regular", fixedSize: 15))
|
||||||
|
.padding(.bottom, 1)
|
||||||
|
Text(convertTimeString(info.time)[1])
|
||||||
|
.font(.custom("Montserrat-Regular", fixedSize: 15))
|
||||||
|
.padding(.top, 1)
|
||||||
|
}
|
||||||
|
.frame(width: 48)
|
||||||
|
.padding(.top, 7)
|
||||||
|
.padding(.bottom, 7)
|
||||||
|
.padding(.leading, 10)
|
||||||
|
Rectangle()
|
||||||
|
.frame(width: 2)
|
||||||
|
.frame(maxHeight: UIScreen.main.bounds.height - 18)
|
||||||
|
.padding(.top, 7)
|
||||||
|
.padding(.bottom, 7)
|
||||||
|
.foregroundColor(getColorForClass(info.subject))
|
||||||
|
Text(info.subject)
|
||||||
|
.font(.custom("Montserrat-Medium", fixedSize: 16))
|
||||||
|
.lineSpacing(3)
|
||||||
|
.padding(.top, 9)
|
||||||
|
.padding(.bottom, 9)
|
||||||
|
Spacer()
|
||||||
|
}
|
||||||
|
.frame(maxWidth: UIScreen.main.bounds.width - 40, maxHeight: 230)
|
||||||
|
.background(Color.white)
|
||||||
|
.cornerRadius(20)
|
||||||
|
.shadow(color: .black.opacity(0.25), radius: 4, x: 2, y: 2)
|
||||||
|
}
|
||||||
|
.onAppear {
|
||||||
|
onlyOneGroup = (vm.showOnlyChoosenGroup != vm.filteringGroups[0])
|
||||||
|
}
|
||||||
|
.padding(.bottom, onlyOneGroup ? 17 : 0)
|
||||||
|
.onChange(of: vm.showOnlyChoosenGroup) { oldValue, newValue in
|
||||||
|
onlyOneGroup = (newValue != vm.filteringGroups[0])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
84
Schedule ICTIS/Main/Views/TabViews/MonthTabView.swift
Normal file
@ -0,0 +1,84 @@
|
|||||||
|
//
|
||||||
|
// MonthTabView.swift
|
||||||
|
// Schedule ICTIS
|
||||||
|
//
|
||||||
|
// Created by Mironov Egor on 10.12.2024.
|
||||||
|
//
|
||||||
|
|
||||||
|
import SwiftUI
|
||||||
|
|
||||||
|
struct MonthTabView: View {
|
||||||
|
@State var currentMonthIndex: Int = 1
|
||||||
|
@State var monthSlider: [[Date.MonthWeek]] = []
|
||||||
|
@State private var createMonth: Bool = false
|
||||||
|
@State private var currentWeekIndex: Int = 0
|
||||||
|
@ObservedObject var vm: ScheduleViewModel
|
||||||
|
var body: some View {
|
||||||
|
VStack {
|
||||||
|
HStack (spacing: 33) {
|
||||||
|
ForEach(MockData.daysOfWeek.indices, id: \.self) { index in
|
||||||
|
Text(MockData.daysOfWeek[index])
|
||||||
|
.font(.custom("Montserrat-SemiBold", fixedSize: 15))
|
||||||
|
.foregroundColor(MockData.daysOfWeek[index] == "Вс" ? Color(.red) : Color("customGray2"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
TabView(selection: $currentMonthIndex) {
|
||||||
|
ForEach(monthSlider.indices, id: \.self) { index in
|
||||||
|
let month = monthSlider[index]
|
||||||
|
MonthView(month)
|
||||||
|
.tag(index)
|
||||||
|
.transition(.slide)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.padding(.horizontal, -15)
|
||||||
|
.tabViewStyle(.page(indexDisplayMode: .never))
|
||||||
|
//.animation(.easeIn(duration: 0.3), value: currentMonthIndex)
|
||||||
|
}
|
||||||
|
.frame(height: 220)
|
||||||
|
.padding(.top, 26)
|
||||||
|
.padding(.bottom, 20)
|
||||||
|
.onAppear {
|
||||||
|
updateMonthScreenViewForNewGroup()
|
||||||
|
}
|
||||||
|
.onChange(of: currentMonthIndex, initial: false) { oldValue, newValue in
|
||||||
|
if newValue == 0 || newValue == (monthSlider.count - 1) {
|
||||||
|
createMonth = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.onChange(of: vm.isNewGroup, initial: false) { oldValue, newValue in
|
||||||
|
if newValue {
|
||||||
|
monthSlider.removeAll()
|
||||||
|
currentMonthIndex = 1
|
||||||
|
updateMonthScreenViewForNewGroup()
|
||||||
|
vm.isNewGroup = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@ViewBuilder
|
||||||
|
func MonthView(_ month: [Date.MonthWeek]) -> some View {
|
||||||
|
VStack (spacing: 10) {
|
||||||
|
ForEach(month.indices, id: \.self) { index in
|
||||||
|
let week = month[index].week
|
||||||
|
WeekViewForMonth(week: week, vm: vm)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.background {
|
||||||
|
GeometryReader {
|
||||||
|
let minX = $0.frame(in: .global).minX
|
||||||
|
|
||||||
|
Color.clear
|
||||||
|
.preference(key: OffsetKey.self, value: minX)
|
||||||
|
.onPreferenceChange(OffsetKey.self) { value in
|
||||||
|
if (abs(value.rounded()) - 20) < 5 && createMonth {
|
||||||
|
paginateMonth()
|
||||||
|
|
||||||
|
createMonth = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
46
Schedule ICTIS/Main/Views/TabViews/WeekTabView.swift
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
//
|
||||||
|
// WeekTabView.swift
|
||||||
|
// Schedule ICTIS
|
||||||
|
//
|
||||||
|
// Created by Mironov Egor on 10.12.2024.
|
||||||
|
//
|
||||||
|
|
||||||
|
import SwiftUI
|
||||||
|
|
||||||
|
struct WeekTabView: View {
|
||||||
|
@State private var currentWeekIndex: Int = 1
|
||||||
|
@State var weekSlider: [[Date.WeekDay]] = []
|
||||||
|
@State private var createWeek: Bool = false
|
||||||
|
@ObservedObject var vm: ScheduleViewModel
|
||||||
|
var body: some View {
|
||||||
|
HStack {
|
||||||
|
TabView(selection: $currentWeekIndex) {
|
||||||
|
ForEach(weekSlider.indices, id: \.self) { index in
|
||||||
|
let week = weekSlider[index]
|
||||||
|
WeekViewForWeek(weekSlider: $weekSlider, currentWeekIndex: $currentWeekIndex, createWeek: $createWeek, week: week, vm: vm)
|
||||||
|
.padding(.horizontal, 15)
|
||||||
|
.tag(index)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.padding(.horizontal, -15)
|
||||||
|
.tabViewStyle(.page(indexDisplayMode: .never))
|
||||||
|
.frame(height: 90)
|
||||||
|
}
|
||||||
|
.onAppear(perform: {
|
||||||
|
updateWeekScreenViewForNewGroup()
|
||||||
|
})
|
||||||
|
.onChange(of: currentWeekIndex, initial: false) { oldValue, newValue in
|
||||||
|
if newValue == 0 || newValue == (weekSlider.count - 1) {
|
||||||
|
createWeek = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.onChange(of: vm.isNewGroup, initial: false) { oldValue, newValue in
|
||||||
|
if newValue {
|
||||||
|
weekSlider.removeAll()
|
||||||
|
currentWeekIndex = 1
|
||||||
|
updateWeekScreenViewForNewGroup()
|
||||||
|
vm.isNewGroup = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
32
Schedule ICTIS/Main/Views/TabViews/WeekViewForMonth.swift
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
//
|
||||||
|
// WeekViewForMonth.swift
|
||||||
|
// Schedule ICTIS
|
||||||
|
//
|
||||||
|
// Created by Mironov Egor on 20.12.2024.
|
||||||
|
//
|
||||||
|
|
||||||
|
import SwiftUI
|
||||||
|
|
||||||
|
struct WeekViewForMonth: View {
|
||||||
|
let week: [Date.WeekDay]
|
||||||
|
@ObservedObject var vm: ScheduleViewModel
|
||||||
|
|
||||||
|
var body: some View {
|
||||||
|
HStack(spacing: 23) {
|
||||||
|
ForEach(week) { day in
|
||||||
|
VStack {
|
||||||
|
Text(day.date.format("dd"))
|
||||||
|
.font(.custom("Montserrat-SemiBold", fixedSize: 15))
|
||||||
|
.foregroundStyle(getForegroundColor(day: day))
|
||||||
|
}
|
||||||
|
.frame(width: 30, height: 30, alignment: .center)
|
||||||
|
.background(getBackgroundColor(day: day))
|
||||||
|
.overlay(overlay(day: day))
|
||||||
|
.cornerRadius(15)
|
||||||
|
.onTapGesture {
|
||||||
|
handleTap(day: day)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
76
Schedule ICTIS/Main/Views/TabViews/WeekViewForWeek.swift
Normal file
@ -0,0 +1,76 @@
|
|||||||
|
//
|
||||||
|
// WeekView.swift
|
||||||
|
// Schedule ICTIS
|
||||||
|
//
|
||||||
|
// Created by Mironov Egor on 20.12.2024.
|
||||||
|
//
|
||||||
|
|
||||||
|
import SwiftUI
|
||||||
|
|
||||||
|
struct WeekViewForWeek: View {
|
||||||
|
@Binding var weekSlider: [[Date.WeekDay]]
|
||||||
|
@Binding var currentWeekIndex: Int
|
||||||
|
@Binding var createWeek: Bool
|
||||||
|
let week: [Date.WeekDay]
|
||||||
|
@ObservedObject var vm: ScheduleViewModel
|
||||||
|
var body: some View {
|
||||||
|
HStack (spacing: 10) {
|
||||||
|
ForEach(week) { day in
|
||||||
|
VStack (spacing: 1) {
|
||||||
|
Text(day.date.format("E"))
|
||||||
|
.font(.custom("Montserrat-SemiBold", fixedSize: 15))
|
||||||
|
.foregroundColor(day.date.format("E") == "Вс" ? Color(.red) : isSameDate(day.date, vm.selectedDay) ? Color("customGray1") : Color("customGray3"))
|
||||||
|
.padding(.top, 13)
|
||||||
|
.foregroundColor(.gray)
|
||||||
|
Text(day.date.format("dd"))
|
||||||
|
.font(.custom("Montserrat-Semibold", fixedSize: 15))
|
||||||
|
.foregroundStyle(isSameDate(day.date, vm.selectedDay) ? .white : .black)
|
||||||
|
.padding(.bottom, 13)
|
||||||
|
}
|
||||||
|
.frame(width: 43, height: 55, alignment: .center)
|
||||||
|
.background( content: {
|
||||||
|
Group {
|
||||||
|
if isSameDate(day.date, vm.selectedDay) {
|
||||||
|
Color("blueColor")
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
Color(.white)
|
||||||
|
}
|
||||||
|
if isSameDate(day.date, vm.selectedDay) {
|
||||||
|
Color("blueColor")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
.overlay (
|
||||||
|
Group {
|
||||||
|
if day.date.isToday && !isSameDate(day.date, vm.selectedDay) {
|
||||||
|
RoundedRectangle(cornerRadius: 15)
|
||||||
|
.stroke(Color("blueColor"), lineWidth: 2)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
.cornerRadius(15)
|
||||||
|
.onTapGesture {
|
||||||
|
vm.selectedDay = day.date
|
||||||
|
vm.updateSelectedDayIndex()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.background {
|
||||||
|
GeometryReader {
|
||||||
|
let minX = $0.frame(in: .global).minX
|
||||||
|
|
||||||
|
Color.clear
|
||||||
|
.preference(key: OffsetKey.self, value: minX)
|
||||||
|
.onPreferenceChange(OffsetKey.self) { value in
|
||||||
|
if value.rounded() == 15 && createWeek {
|
||||||
|
paginateWeek()
|
||||||
|
|
||||||
|
createWeek = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,58 +0,0 @@
|
|||||||
//
|
|
||||||
// TextFiledView.swift
|
|
||||||
// Schedule ICTIS
|
|
||||||
//
|
|
||||||
// Created by G412 on 17.12.2024.
|
|
||||||
//
|
|
||||||
|
|
||||||
import SwiftUI
|
|
||||||
|
|
||||||
struct TextFiledView: View {
|
|
||||||
@State private var isEditing: Bool = false
|
|
||||||
@State private var text: String = ""
|
|
||||||
@State private var nameOfImage: String = "calendar"
|
|
||||||
@State private var labelForField: String = "Преподаватель"
|
|
||||||
|
|
||||||
@FocusState private var isTextFieldFocused: Bool
|
|
||||||
|
|
||||||
var body: some View {
|
|
||||||
HStack(spacing: 0) {
|
|
||||||
Image(systemName: nameOfImage)
|
|
||||||
.foregroundColor(Color.gray)
|
|
||||||
.padding(.leading, 12)
|
|
||||||
.padding(.trailing, 7)
|
|
||||||
|
|
||||||
TextField(labelForField, text: $text)
|
|
||||||
.font(.system(size: 18, weight: .regular))
|
|
||||||
.disableAutocorrection(true)
|
|
||||||
.focused($isTextFieldFocused)
|
|
||||||
.onChange(of: isTextFieldFocused) { newValue, oldValue in
|
|
||||||
isEditing = newValue
|
|
||||||
}
|
|
||||||
.submitLabel(.done)
|
|
||||||
|
|
||||||
if isTextFieldFocused {
|
|
||||||
Button {
|
|
||||||
self.text = ""
|
|
||||||
self.isEditing = false
|
|
||||||
isTextFieldFocused = false
|
|
||||||
} label: {
|
|
||||||
Image(systemName: "xmark.circle.fill")
|
|
||||||
.padding()
|
|
||||||
.padding(.trailing, 20)
|
|
||||||
.offset(x: 10)
|
|
||||||
.foregroundColor(.red)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.frame(height: 40)
|
|
||||||
.background(
|
|
||||||
RoundedRectangle(cornerRadius: 10)
|
|
||||||
.fill(.white)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#Preview {
|
|
||||||
TextFiledView()
|
|
||||||
}
|
|
@ -2,7 +2,7 @@
|
|||||||
// MockData.swift
|
// MockData.swift
|
||||||
// Schedule ICTIS
|
// Schedule ICTIS
|
||||||
//
|
//
|
||||||
// Created by G412 on 06.12.2024.
|
// Created by Mironov Egor on 06.12.2024.
|
||||||
//
|
//
|
||||||
|
|
||||||
import Foundation
|
import Foundation
|
||||||
@ -10,5 +10,15 @@ import Foundation
|
|||||||
|
|
||||||
struct MockData {
|
struct MockData {
|
||||||
static let daysOfWeek = ["Пн", "Вт", "Ср", "Чт", "Пт", "Сб", "Вс"]
|
static let daysOfWeek = ["Пн", "Вт", "Ср", "Чт", "Пт", "Сб", "Вс"]
|
||||||
|
|
||||||
|
// MARK: SheetCreateClassView
|
||||||
static let notifications = ["Нет", "За 10 минут", "За 30 миннут", "За 1 час"]
|
static let notifications = ["Нет", "За 10 минут", "За 30 миннут", "За 1 час"]
|
||||||
|
|
||||||
|
static let onlineOrOffline = ["Оффлайн", "Онлайн"]
|
||||||
|
|
||||||
|
static let themes = ["Светлая", "Темная", "Системная"]
|
||||||
|
|
||||||
|
static let languages = ["Русский", "Английский", "Китайский", "Испанский"]
|
||||||
|
|
||||||
|
static let groups = ["КТбо2-6", "КТбо1-9", "КТбо3-3", "ВУЦ", "КТао1-1", "КТсо2-2"]
|
||||||
}
|
}
|
||||||
|
91
Schedule ICTIS/Model/CoreDataClassModel.swift
Normal file
@ -0,0 +1,91 @@
|
|||||||
|
//
|
||||||
|
// Class.swift
|
||||||
|
// Schedule ICTIS
|
||||||
|
//
|
||||||
|
// Created by Mironov Egor on 18.12.2024.
|
||||||
|
//
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
import CoreData
|
||||||
|
|
||||||
|
final class CoreDataClassModel: NSManagedObject, Identifiable {
|
||||||
|
@NSManaged var auditory: String
|
||||||
|
@NSManaged var professor: String
|
||||||
|
@NSManaged var subject: String
|
||||||
|
@NSManaged var comment: String
|
||||||
|
@NSManaged var notification: String
|
||||||
|
@NSManaged var day: Date
|
||||||
|
@NSManaged var starttime: Date
|
||||||
|
@NSManaged var endtime: Date
|
||||||
|
@NSManaged var important: Bool
|
||||||
|
@NSManaged var online: String
|
||||||
|
|
||||||
|
static var dateNow: Date = .now
|
||||||
|
|
||||||
|
// Здесь мы выполняем дополнительную инициализацию, назначая значения по умолчанию
|
||||||
|
// Этот метод вызывается всякий раз, когда объект Core Data вставляется в контекст
|
||||||
|
override func awakeFromInsert() {
|
||||||
|
super.awakeFromInsert()
|
||||||
|
|
||||||
|
let moscowTimeZone = TimeZone(identifier: "Europe/Moscow")!
|
||||||
|
var calendar = Calendar.current
|
||||||
|
calendar.timeZone = moscowTimeZone
|
||||||
|
let startTime = Date()
|
||||||
|
let endTime = calendar.date(byAdding: .hour, value: 1, to: Date.init())
|
||||||
|
|
||||||
|
setPrimitiveValue("", forKey: "auditory")
|
||||||
|
setPrimitiveValue("", forKey: "professor")
|
||||||
|
setPrimitiveValue("", forKey: "subject")
|
||||||
|
setPrimitiveValue("", forKey: "comment")
|
||||||
|
setPrimitiveValue("Нет", forKey: "notification")
|
||||||
|
setPrimitiveValue(false, forKey: "important")
|
||||||
|
setPrimitiveValue("Оффлайн", forKey: "online")
|
||||||
|
setPrimitiveValue(startTime, forKey: "day")
|
||||||
|
setPrimitiveValue(startTime, forKey: "starttime")
|
||||||
|
setPrimitiveValue(endTime, forKey: "endtime")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Расширение для загрузки данных из памяти
|
||||||
|
extension CoreDataClassModel {
|
||||||
|
// Получаем все данные из памяти
|
||||||
|
private static var classesFetchRequest: NSFetchRequest<CoreDataClassModel> {
|
||||||
|
NSFetchRequest(entityName: "CoreDataClassModel")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Получаем все данные и сортируем их по дню
|
||||||
|
// Этот метод будет использоваться на View(ScheduleView), где отображаются пары
|
||||||
|
static func all() -> NSFetchRequest<CoreDataClassModel> {
|
||||||
|
let request: NSFetchRequest<CoreDataClassModel> = classesFetchRequest
|
||||||
|
request.sortDescriptors = [
|
||||||
|
NSSortDescriptor(keyPath: \CoreDataClassModel.day, ascending: true)
|
||||||
|
]
|
||||||
|
return request
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extension CoreDataClassModel {
|
||||||
|
@discardableResult
|
||||||
|
static func makePreview(count: Int, in context: NSManagedObjectContext) -> [CoreDataClassModel] {
|
||||||
|
var classes = [CoreDataClassModel]()
|
||||||
|
for i in 0..<count {
|
||||||
|
let _class = CoreDataClassModel(context: context)
|
||||||
|
_class.subject = "Предмет \(i)"
|
||||||
|
_class.auditory = "Аудитория \(i)"
|
||||||
|
_class.professor = "Преподаватель \(i)"
|
||||||
|
_class.day = Calendar.current.date(byAdding: .day, value: i, to: .now) ?? .now
|
||||||
|
_class.starttime = Date()
|
||||||
|
_class.endtime = Calendar.current.date(byAdding: .hour, value: i, to: .now) ?? .now
|
||||||
|
classes.append(_class)
|
||||||
|
}
|
||||||
|
return classes
|
||||||
|
}
|
||||||
|
|
||||||
|
static func preview(context: NSManagedObjectContext = ClassProvider.shared.viewContext) -> CoreDataClassModel {
|
||||||
|
return makePreview(count: 1, in: context)[0]
|
||||||
|
}
|
||||||
|
|
||||||
|
static func empty(context: NSManagedObjectContext = ClassProvider.shared.viewContext) -> CoreDataClassModel {
|
||||||
|
return CoreDataClassModel(context: context)
|
||||||
|
}
|
||||||
|
}
|
20
Schedule ICTIS/Model/GroupsModel.swift
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
//
|
||||||
|
// SubjectsModel.swift
|
||||||
|
// Schedule ICTIS
|
||||||
|
//
|
||||||
|
// Created by Mironov Egor on 19.02.2025.
|
||||||
|
//
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
|
||||||
|
// MARK: - Welcome
|
||||||
|
struct Welcome: Decodable {
|
||||||
|
let choices: [Subject]
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - Choice
|
||||||
|
struct Subject: Decodable, Identifiable {
|
||||||
|
let name: String
|
||||||
|
let id: String
|
||||||
|
let group: String
|
||||||
|
}
|
46
Schedule ICTIS/Model/JsonClassModel.swift
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
//
|
||||||
|
// JsonClassModel.swift
|
||||||
|
// Schedule ICTIS
|
||||||
|
//
|
||||||
|
// Created by Mironov Egor on 27.03.2025.
|
||||||
|
//
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
import CoreData
|
||||||
|
|
||||||
|
final class JsonDataClassModel: NSManagedObject, Identifiable {
|
||||||
|
@NSManaged var name: String
|
||||||
|
@NSManaged var group: String
|
||||||
|
@NSManaged var time: String
|
||||||
|
@NSManaged var day: Int16
|
||||||
|
|
||||||
|
// Здесь мы выполняем дополнительную инициализацию, назначая значения по умолчанию
|
||||||
|
// Этот метод вызывается всякий раз, когда объект Core Data вставляется в контекст
|
||||||
|
override func awakeFromInsert() {
|
||||||
|
super.awakeFromInsert()
|
||||||
|
|
||||||
|
setPrimitiveValue("", forKey: "name")
|
||||||
|
setPrimitiveValue("", forKey: "group")
|
||||||
|
setPrimitiveValue("", forKey: "time")
|
||||||
|
setPrimitiveValue(0, forKey: "day")
|
||||||
|
setPrimitiveValue(0, forKey: "week")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Расширение для загрузки данных из памяти
|
||||||
|
extension JsonClassModel {
|
||||||
|
// Получаем все данные из памяти
|
||||||
|
private static var subjectsFetchRequest: NSFetchRequest<JsonClassModel> {
|
||||||
|
NSFetchRequest(entityName: "JsonClassModel")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Получаем все данные и сортируем их по дню
|
||||||
|
// Этот метод будет использоваться на View(ScheduleView), где отображаются пары
|
||||||
|
static func all() -> NSFetchRequest<JsonClassModel> {
|
||||||
|
let request: NSFetchRequest<JsonClassModel> = subjectsFetchRequest
|
||||||
|
request.sortDescriptors = [
|
||||||
|
NSSortDescriptor(keyPath: \JsonClassModel.time, ascending: true)
|
||||||
|
]
|
||||||
|
return request
|
||||||
|
}
|
||||||
|
}
|
@ -21,3 +21,10 @@ struct Table: Decodable {
|
|||||||
let table: [[String]]
|
let table: [[String]]
|
||||||
let link: String
|
let link: String
|
||||||
}
|
}
|
||||||
|
|
||||||
|
struct ClassInfo: Identifiable {
|
||||||
|
let id = UUID()
|
||||||
|
let subject: String
|
||||||
|
let group: String
|
||||||
|
let time: String
|
||||||
|
}
|
@ -2,13 +2,13 @@
|
|||||||
// Tab.swift
|
// Tab.swift
|
||||||
// Schedule ICTIS
|
// Schedule ICTIS
|
||||||
//
|
//
|
||||||
// Created by G412 on 13.11.2024.
|
// Created by Mironov Egor on 13.11.2024.
|
||||||
//
|
//
|
||||||
|
|
||||||
import SwiftUI
|
import SwiftUI
|
||||||
|
|
||||||
enum TabBarModel: String, CaseIterable {
|
enum TabBarModel: String, CaseIterable {
|
||||||
case schedule = "house"
|
|
||||||
case tasks = "books.vertical"
|
case tasks = "books.vertical"
|
||||||
|
case schedule = "house"
|
||||||
case settings = "gear"
|
case settings = "gear"
|
||||||
}
|
}
|
||||||
|
Before Width: | Height: | Size: 24 KiB After Width: | Height: | Size: 24 KiB |
21
Schedule ICTIS/Preview Content/Assets.xcassets/arrowRight.imageset/Contents.json
vendored
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
{
|
||||||
|
"images" : [
|
||||||
|
{
|
||||||
|
"filename" : "arrowRight.svg",
|
||||||
|
"idiom" : "universal",
|
||||||
|
"scale" : "1x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"idiom" : "universal",
|
||||||
|
"scale" : "2x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"idiom" : "universal",
|
||||||
|
"scale" : "3x"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"info" : {
|
||||||
|
"author" : "xcode",
|
||||||
|
"version" : 1
|
||||||
|
}
|
||||||
|
}
|
3
Schedule ICTIS/Preview Content/Assets.xcassets/arrowRight.imageset/arrowRight.svg
vendored
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path d="M9 6L15 12L9 18" stroke="#8B8B8B" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 212 B |
@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"images" : [
|
"images" : [
|
||||||
{
|
{
|
||||||
"filename" : "Vector.png",
|
"filename" : "arrowdown.svg",
|
||||||
"idiom" : "universal",
|
"idiom" : "universal",
|
||||||
"scale" : "1x"
|
"scale" : "1x"
|
||||||
},
|
},
|
3
Schedule ICTIS/Preview Content/Assets.xcassets/arrowdown.imageset/arrowdown.svg
vendored
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
<svg width="14" height="8" viewBox="0 0 14 8" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path d="M13 1L7 7L1 1" stroke="#007AFF" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 191 B |
@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"images" : [
|
"images" : [
|
||||||
{
|
{
|
||||||
"filename" : "arrow.png",
|
"filename" : "arrowup.svg",
|
||||||
"idiom" : "universal",
|
"idiom" : "universal",
|
||||||
"scale" : "1x"
|
"scale" : "1x"
|
||||||
},
|
},
|
3
Schedule ICTIS/Preview Content/Assets.xcassets/arrowup.imageset/arrowup.svg
vendored
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
<svg width="14" height="8" viewBox="0 0 14 8" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path d="M1 7L7 1L13 7" stroke="#007AFF" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 191 B |
21
Schedule ICTIS/Preview Content/Assets.xcassets/auditoryImage.imageset/Contents.json
vendored
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
{
|
||||||
|
"images" : [
|
||||||
|
{
|
||||||
|
"filename" : "auditoryImage.png",
|
||||||
|
"idiom" : "universal",
|
||||||
|
"scale" : "1x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"idiom" : "universal",
|
||||||
|
"scale" : "2x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"idiom" : "universal",
|
||||||
|
"scale" : "3x"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"info" : {
|
||||||
|
"author" : "xcode",
|
||||||
|
"version" : 1
|
||||||
|
}
|
||||||
|
}
|
BIN
Schedule ICTIS/Preview Content/Assets.xcassets/auditoryImage.imageset/auditoryImage.png
vendored
Normal file
After Width: | Height: | Size: 549 B |
21
Schedule ICTIS/Preview Content/Assets.xcassets/bookImage.imageset/Contents.json
vendored
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
{
|
||||||
|
"images" : [
|
||||||
|
{
|
||||||
|
"filename" : "bookImage.png",
|
||||||
|
"idiom" : "universal",
|
||||||
|
"scale" : "1x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"idiom" : "universal",
|
||||||
|
"scale" : "2x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"idiom" : "universal",
|
||||||
|
"scale" : "3x"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"info" : {
|
||||||
|
"author" : "xcode",
|
||||||
|
"version" : 1
|
||||||
|
}
|
||||||
|
}
|
BIN
Schedule ICTIS/Preview Content/Assets.xcassets/bookImage.imageset/bookImage.png
vendored
Normal file
After Width: | Height: | Size: 466 B |
@ -0,0 +1,38 @@
|
|||||||
|
{
|
||||||
|
"colors" : [
|
||||||
|
{
|
||||||
|
"color" : {
|
||||||
|
"color-space" : "srgb",
|
||||||
|
"components" : {
|
||||||
|
"alpha" : "1.000",
|
||||||
|
"blue" : "0x89",
|
||||||
|
"green" : "0x89",
|
||||||
|
"red" : "0x89"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"idiom" : "universal"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"appearances" : [
|
||||||
|
{
|
||||||
|
"appearance" : "luminosity",
|
||||||
|
"value" : "dark"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"color" : {
|
||||||
|
"color-space" : "srgb",
|
||||||
|
"components" : {
|
||||||
|
"alpha" : "1.000",
|
||||||
|
"blue" : "0x89",
|
||||||
|
"green" : "0x89",
|
||||||
|
"red" : "0x89"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"idiom" : "universal"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"info" : {
|
||||||
|
"author" : "xcode",
|
||||||
|
"version" : 1
|
||||||
|
}
|
||||||
|
}
|
21
Schedule ICTIS/Preview Content/Assets.xcassets/professorHatImage.imageset/Contents.json
vendored
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
{
|
||||||
|
"images" : [
|
||||||
|
{
|
||||||
|
"filename" : "professorHatImage.png",
|
||||||
|
"idiom" : "universal",
|
||||||
|
"scale" : "1x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"idiom" : "universal",
|
||||||
|
"scale" : "2x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"idiom" : "universal",
|
||||||
|
"scale" : "3x"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"info" : {
|
||||||
|
"author" : "xcode",
|
||||||
|
"version" : 1
|
||||||
|
}
|
||||||
|
}
|
BIN
Schedule ICTIS/Preview Content/Assets.xcassets/professorHatImage.imageset/professorHatImage.png
vendored
Normal file
After Width: | Height: | Size: 716 B |
@ -0,0 +1,38 @@
|
|||||||
|
{
|
||||||
|
"colors" : [
|
||||||
|
{
|
||||||
|
"color" : {
|
||||||
|
"color-space" : "srgb",
|
||||||
|
"components" : {
|
||||||
|
"alpha" : "1.000",
|
||||||
|
"blue" : "0x00",
|
||||||
|
"green" : "0x00",
|
||||||
|
"red" : "0xFF"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"idiom" : "universal"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"appearances" : [
|
||||||
|
{
|
||||||
|
"appearance" : "luminosity",
|
||||||
|
"value" : "dark"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"color" : {
|
||||||
|
"color-space" : "srgb",
|
||||||
|
"components" : {
|
||||||
|
"alpha" : "1.000",
|
||||||
|
"blue" : "0x00",
|
||||||
|
"green" : "0x00",
|
||||||
|
"red" : "0xFF"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"idiom" : "universal"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"info" : {
|
||||||
|
"author" : "xcode",
|
||||||
|
"version" : 1
|
||||||
|
}
|
||||||
|
}
|
21
Schedule ICTIS/Preview Content/Assets.xcassets/upDownArrows.imageset/Contents.json
vendored
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
{
|
||||||
|
"images" : [
|
||||||
|
{
|
||||||
|
"filename" : "upDownArrows.svg",
|
||||||
|
"idiom" : "universal",
|
||||||
|
"scale" : "1x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"idiom" : "universal",
|
||||||
|
"scale" : "2x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"idiom" : "universal",
|
||||||
|
"scale" : "3x"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"info" : {
|
||||||
|
"author" : "xcode",
|
||||||
|
"version" : 1
|
||||||
|
}
|
||||||
|
}
|
4
Schedule ICTIS/Preview Content/Assets.xcassets/upDownArrows.imageset/upDownArrows.svg
vendored
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
<svg width="14" height="18" viewBox="0 0 14 18" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path d="M1 7L7 1L13 7" stroke="#878787" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
|
<path d="M13 11L7 17L1 11" stroke="#878787" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 320 B |
95
Schedule ICTIS/Provider/ClassProvider.swift
Normal file
@ -0,0 +1,95 @@
|
|||||||
|
//
|
||||||
|
// ClassProvider.swift
|
||||||
|
// Schedule ICTIS
|
||||||
|
//
|
||||||
|
// Created by Mironov Egor on 18.12.2024.
|
||||||
|
//
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
import CoreData
|
||||||
|
import SwiftUI
|
||||||
|
|
||||||
|
// Это класс служит посредником между View и моделью данных
|
||||||
|
// Он позволяет открыть наш файл данных чтобы записывать и извлекать значения
|
||||||
|
// Объект этого класса должен быть единственным за весь жизненный цикл приложения, чтобы не было рассинхронизации
|
||||||
|
// Для этого мы делаем его синглтоном
|
||||||
|
final class ClassProvider {
|
||||||
|
static let shared = ClassProvider()
|
||||||
|
|
||||||
|
// Это свойство для хранения открытого файла модели данных
|
||||||
|
private let persistentContainer: NSPersistentContainer
|
||||||
|
|
||||||
|
var viewContext: NSManagedObjectContext {
|
||||||
|
persistentContainer.viewContext
|
||||||
|
}
|
||||||
|
|
||||||
|
var newContext: NSManagedObjectContext {
|
||||||
|
//persistentContainer.newBackgroundContext()
|
||||||
|
//Можно использовать объявление newContext с помощью строки, которая написана выше, но вариант ниже потокобезопаснее
|
||||||
|
let context = NSManagedObjectContext(concurrencyType: .mainQueueConcurrencyType)
|
||||||
|
context.persistentStoreCoordinator = persistentContainer.persistentStoreCoordinator
|
||||||
|
return context
|
||||||
|
}
|
||||||
|
|
||||||
|
private init() {
|
||||||
|
// Открытие файла
|
||||||
|
persistentContainer = NSPersistentContainer(name: "ClassDataModel")
|
||||||
|
if EnvironmentValues.isPreview {
|
||||||
|
persistentContainer.persistentStoreDescriptions.first?.url = .init(filePath: "/dev/null")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Выставляем флаг для автоматического слияния данных из фонового контекста в основной
|
||||||
|
persistentContainer.viewContext.automaticallyMergesChangesFromParent = true
|
||||||
|
|
||||||
|
// Выполняем открытие файла с данными
|
||||||
|
persistentContainer.loadPersistentStores {_, error in
|
||||||
|
if let error {
|
||||||
|
fatalError("Unable to load store. Error: \(error)")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func exists(_ lesson: CoreDataClassModel, in context: NSManagedObjectContext) -> CoreDataClassModel? {
|
||||||
|
try? context.existingObject(with: lesson.objectID) as? CoreDataClassModel
|
||||||
|
}
|
||||||
|
|
||||||
|
func delete(_ lesson: CoreDataClassModel, in context: NSManagedObjectContext) throws {
|
||||||
|
if let existingClass = exists(lesson, in: context) {
|
||||||
|
context.delete(existingClass)
|
||||||
|
Task(priority: .background) {
|
||||||
|
try await context.perform {
|
||||||
|
try context.save()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func persist(in context: NSManagedObjectContext) throws {
|
||||||
|
if context.hasChanges {
|
||||||
|
try context.save()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extension EnvironmentValues {
|
||||||
|
static var isPreview: Bool {
|
||||||
|
return ProcessInfo.processInfo.environment["XCODE_RUNNING_FOR_PREVIEWS"] == "1"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extension ClassProvider {
|
||||||
|
func exists(_ jsonClass: JsonClassModel, in context: NSManagedObjectContext) -> JsonClassModel? {
|
||||||
|
try? context.existingObject(with: jsonClass.objectID) as? JsonClassModel
|
||||||
|
}
|
||||||
|
|
||||||
|
func delete(_ jsonClass: JsonClassModel, in context: NSManagedObjectContext) throws {
|
||||||
|
if let existingJsonClass = exists(jsonClass, in: context) {
|
||||||
|
context.delete(existingJsonClass)
|
||||||
|
Task(priority: .background) {
|
||||||
|
try await context.perform {
|
||||||
|
try context.save()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -9,9 +9,16 @@ import SwiftUI
|
|||||||
|
|
||||||
@main
|
@main
|
||||||
struct Schedule_ICTISApp: App {
|
struct Schedule_ICTISApp: App {
|
||||||
|
@StateObject private var networkMonitor = NetworkMonitor()
|
||||||
|
@StateObject var vm = ScheduleViewModel()
|
||||||
|
var provider = ClassProvider.shared
|
||||||
var body: some Scene {
|
var body: some Scene {
|
||||||
WindowGroup {
|
WindowGroup {
|
||||||
ContentView()
|
ContentView(vm: vm, networkMonitor: networkMonitor)
|
||||||
|
.environment(\.managedObjectContext, ClassProvider.shared.viewContext)
|
||||||
|
.onAppear {
|
||||||
|
vm.fillDictForVm()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
101
Schedule ICTIS/Settings/FavGroupsView.swift
Normal file
@ -0,0 +1,101 @@
|
|||||||
|
//
|
||||||
|
// FavGroupsView.swift
|
||||||
|
// Schedule ICTIS
|
||||||
|
//
|
||||||
|
// Created by G412 on 05.03.2025.
|
||||||
|
//
|
||||||
|
|
||||||
|
import SwiftUI
|
||||||
|
|
||||||
|
struct FavGroupsView: View {
|
||||||
|
@ObservedObject var vm: ScheduleViewModel
|
||||||
|
@ObservedObject var networkMonitor: NetworkMonitor
|
||||||
|
var firstFavGroup = (UserDefaults.standard.string(forKey: "group") ?? "")
|
||||||
|
var secondFavGroup = (UserDefaults.standard.string(forKey: "group2") ?? "")
|
||||||
|
var thirdFavGroup = (UserDefaults.standard.string(forKey: "group3") ?? "")
|
||||||
|
var body: some View {
|
||||||
|
VStack (spacing: 0) {
|
||||||
|
List {
|
||||||
|
if firstFavGroup != "" {
|
||||||
|
HStack {
|
||||||
|
Text(firstFavGroup)
|
||||||
|
.font(.custom("Montserrat-Medium", fixedSize: 17))
|
||||||
|
Spacer()
|
||||||
|
}
|
||||||
|
.background(Color.white)
|
||||||
|
.cornerRadius(10)
|
||||||
|
.swipeActions(edge: .trailing) {
|
||||||
|
Button(role: .destructive) {
|
||||||
|
vm.removeFromSchedule(group: firstFavGroup)
|
||||||
|
UserDefaults.standard.set("", forKey: "group")
|
||||||
|
} label: {
|
||||||
|
Label("Удалить", systemImage: "trash")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if secondFavGroup != "" {
|
||||||
|
HStack {
|
||||||
|
Text(secondFavGroup)
|
||||||
|
.font(.custom("Montserrat-Medium", fixedSize: 17))
|
||||||
|
Spacer()
|
||||||
|
}
|
||||||
|
.background(Color.white)
|
||||||
|
.cornerRadius(10)
|
||||||
|
.swipeActions(edge: .trailing) {
|
||||||
|
Button(role: .destructive) {
|
||||||
|
vm.removeFromSchedule(group: secondFavGroup)
|
||||||
|
UserDefaults.standard.set("", forKey: "group2")
|
||||||
|
} label: {
|
||||||
|
Label("Удалить", systemImage: "trash")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if thirdFavGroup != "" {
|
||||||
|
HStack {
|
||||||
|
Text(thirdFavGroup)
|
||||||
|
.font(.custom("Montserrat-Medium", fixedSize: 17))
|
||||||
|
Spacer()
|
||||||
|
}
|
||||||
|
.background(Color.white)
|
||||||
|
.cornerRadius(10)
|
||||||
|
.swipeActions(edge: .trailing) {
|
||||||
|
Button(role: .destructive) {
|
||||||
|
vm.removeFromSchedule(group: thirdFavGroup)
|
||||||
|
UserDefaults.standard.set("", forKey: "group3")
|
||||||
|
} label: {
|
||||||
|
Label("Удалить", systemImage: "trash")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.frame(maxHeight: 400)
|
||||||
|
|
||||||
|
Spacer()
|
||||||
|
|
||||||
|
HStack {
|
||||||
|
Spacer()
|
||||||
|
if firstFavGroup == "" || secondFavGroup == "" || thirdFavGroup == "" {
|
||||||
|
NavigationLink(destination: SelectingGroupView(vm: vm, networkMonitor: networkMonitor, firstFavGroup: firstFavGroup, secondFavGroup: secondFavGroup, thirdFavGroup: thirdFavGroup)) {
|
||||||
|
HStack {
|
||||||
|
Image(systemName: "plus")
|
||||||
|
.foregroundColor(.white)
|
||||||
|
.font(.system(size: 22))
|
||||||
|
.padding(EdgeInsets(top: 12, leading: 12, bottom: 12, trailing: 12))
|
||||||
|
}
|
||||||
|
.background(Color("blueColor"))
|
||||||
|
.cornerRadius(10)
|
||||||
|
.padding(.trailing, 20)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.padding(.bottom, 90)
|
||||||
|
}
|
||||||
|
.background(Color("background"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#Preview {
|
||||||
|
@Previewable @StateObject var vm = ScheduleViewModel()
|
||||||
|
@Previewable @StateObject var vm2 = NetworkMonitor()
|
||||||
|
FavGroupsView(vm: vm, networkMonitor: vm2)
|
||||||
|
}
|
101
Schedule ICTIS/Settings/FavVPKView.swift
Normal file
@ -0,0 +1,101 @@
|
|||||||
|
//
|
||||||
|
// FavGroupsView.swift
|
||||||
|
// Schedule ICTIS
|
||||||
|
//
|
||||||
|
// Created by Egor Mironov on 05.03.2025.
|
||||||
|
//
|
||||||
|
|
||||||
|
import SwiftUI
|
||||||
|
|
||||||
|
struct FavVPKView: View {
|
||||||
|
@ObservedObject var vm: ScheduleViewModel
|
||||||
|
@ObservedObject var networkMonitor: NetworkMonitor
|
||||||
|
var firstFavVPK = (UserDefaults.standard.string(forKey: "vpk1") ?? "")
|
||||||
|
var secondFavVPK = (UserDefaults.standard.string(forKey: "vpk2") ?? "")
|
||||||
|
var thirdFavVPK = (UserDefaults.standard.string(forKey: "vpk3") ?? "")
|
||||||
|
var body: some View {
|
||||||
|
VStack (spacing: 0) {
|
||||||
|
List {
|
||||||
|
if firstFavVPK != "" {
|
||||||
|
HStack {
|
||||||
|
Text(firstFavVPK)
|
||||||
|
.font(.custom("Montserrat-Medium", fixedSize: 17))
|
||||||
|
Spacer()
|
||||||
|
}
|
||||||
|
.background(Color.white)
|
||||||
|
.cornerRadius(10)
|
||||||
|
.swipeActions(edge: .trailing) {
|
||||||
|
Button(role: .destructive) {
|
||||||
|
vm.removeFromSchedule(group: firstFavVPK)
|
||||||
|
UserDefaults.standard.set("", forKey: "vpk1")
|
||||||
|
} label: {
|
||||||
|
Label("Удалить", systemImage: "trash")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if secondFavVPK != "" {
|
||||||
|
HStack {
|
||||||
|
Text(secondFavVPK)
|
||||||
|
.font(.custom("Montserrat-Medium", fixedSize: 17))
|
||||||
|
Spacer()
|
||||||
|
}
|
||||||
|
.background(Color.white)
|
||||||
|
.cornerRadius(10)
|
||||||
|
.swipeActions(edge: .trailing) {
|
||||||
|
Button(role: .destructive) {
|
||||||
|
vm.removeFromSchedule(group: secondFavVPK)
|
||||||
|
UserDefaults.standard.set("", forKey: "vpk2")
|
||||||
|
} label: {
|
||||||
|
Label("Удалить", systemImage: "trash")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if thirdFavVPK != "" {
|
||||||
|
HStack {
|
||||||
|
Text(thirdFavVPK)
|
||||||
|
.font(.custom("Montserrat-Medium", fixedSize: 17))
|
||||||
|
Spacer()
|
||||||
|
}
|
||||||
|
.background(Color.white)
|
||||||
|
.cornerRadius(10)
|
||||||
|
.swipeActions(edge: .trailing) {
|
||||||
|
Button(role: .destructive) {
|
||||||
|
vm.removeFromSchedule(group: thirdFavVPK)
|
||||||
|
UserDefaults.standard.set("", forKey: "vpk3")
|
||||||
|
} label: {
|
||||||
|
Label("Удалить", systemImage: "trash")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.frame(maxHeight: 400)
|
||||||
|
|
||||||
|
Spacer()
|
||||||
|
|
||||||
|
HStack {
|
||||||
|
Spacer()
|
||||||
|
if firstFavVPK == "" || secondFavVPK == "" || thirdFavVPK == "" {
|
||||||
|
NavigationLink(destination: SelectingVPKView(vm: vm, networkMonitor: networkMonitor, firstFavVPK: firstFavVPK, secondFavVPK: secondFavVPK, thirdFavVPK: thirdFavVPK)) {
|
||||||
|
HStack {
|
||||||
|
Image(systemName: "plus")
|
||||||
|
.foregroundColor(.white)
|
||||||
|
.font(.system(size: 22))
|
||||||
|
.padding(EdgeInsets(top: 12, leading: 12, bottom: 12, trailing: 12))
|
||||||
|
}
|
||||||
|
.background(Color("blueColor"))
|
||||||
|
.cornerRadius(10)
|
||||||
|
.padding(.trailing, 20)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.padding(.bottom, 90)
|
||||||
|
}
|
||||||
|
.background(Color("background"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#Preview {
|
||||||
|
@Previewable @StateObject var vm = ScheduleViewModel()
|
||||||
|
@Previewable @StateObject var vm2 = NetworkMonitor()
|
||||||
|
FavVPKView(vm: vm, networkMonitor: vm2)
|
||||||
|
}
|
91
Schedule ICTIS/Settings/GeneralGroupSettings.swift
Normal file
@ -0,0 +1,91 @@
|
|||||||
|
//
|
||||||
|
// GeneralGroupSettings.swift
|
||||||
|
// Schedule ICTIS
|
||||||
|
//
|
||||||
|
// Created by Mironov Egor on 25.02.2025.
|
||||||
|
//
|
||||||
|
|
||||||
|
import SwiftUI
|
||||||
|
|
||||||
|
struct GeneralGroupSettings: View {
|
||||||
|
@Binding var selectedTheme: String
|
||||||
|
@Binding var selectedLanguage: String
|
||||||
|
var body: some View {
|
||||||
|
VStack {
|
||||||
|
HStack {
|
||||||
|
Text("Тема")
|
||||||
|
.font(.custom("Montserrat-Medium", fixedSize: 17))
|
||||||
|
.foregroundColor(.black)
|
||||||
|
Spacer()
|
||||||
|
HStack {
|
||||||
|
Text(selectedTheme)
|
||||||
|
.font(.custom("Montserrat-Medium", fixedSize: 17))
|
||||||
|
.foregroundColor(Color("customGray3"))
|
||||||
|
Image("upDownArrows")
|
||||||
|
.resizable()
|
||||||
|
.scaledToFit()
|
||||||
|
.frame(width: 15, height: 15)
|
||||||
|
}
|
||||||
|
.padding(.horizontal)
|
||||||
|
}
|
||||||
|
.padding(.horizontal)
|
||||||
|
.padding(.top, 17)
|
||||||
|
.padding(.bottom, 7)
|
||||||
|
.overlay {
|
||||||
|
HStack {
|
||||||
|
Spacer()
|
||||||
|
Picker("", selection: $selectedTheme, content: {
|
||||||
|
ForEach(MockData.themes, id: \.self) {
|
||||||
|
Text($0)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.padding(.trailing, 35)
|
||||||
|
.blendMode(.destinationOver)
|
||||||
|
}
|
||||||
|
.frame(width: UIScreen.main.bounds.width)
|
||||||
|
}
|
||||||
|
Rectangle()
|
||||||
|
.foregroundColor(Color("customGray1"))
|
||||||
|
.frame(height: 1)
|
||||||
|
.padding(.horizontal)
|
||||||
|
HStack {
|
||||||
|
Text("Язык")
|
||||||
|
.font(.custom("Montserrat-Medium", fixedSize: 17))
|
||||||
|
.foregroundColor(.black)
|
||||||
|
Spacer()
|
||||||
|
HStack {
|
||||||
|
Text(selectedLanguage)
|
||||||
|
.font(.custom("Montserrat-Medium", fixedSize: 17))
|
||||||
|
.foregroundColor(Color("customGray3"))
|
||||||
|
Image("upDownArrows")
|
||||||
|
.resizable()
|
||||||
|
.scaledToFit()
|
||||||
|
.frame(width: 15, height: 15)
|
||||||
|
}
|
||||||
|
.padding(.horizontal)
|
||||||
|
}
|
||||||
|
.padding(.horizontal)
|
||||||
|
.padding(.top, 7)
|
||||||
|
.padding(.bottom, 17)
|
||||||
|
.overlay {
|
||||||
|
HStack {
|
||||||
|
Spacer()
|
||||||
|
Picker("", selection: $selectedLanguage, content: {
|
||||||
|
ForEach(MockData.languages, id: \.self) {
|
||||||
|
Text($0)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.padding(.trailing, 35)
|
||||||
|
.blendMode(.destinationOver)
|
||||||
|
}
|
||||||
|
.frame(width: UIScreen.main.bounds.width)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.background(Color.white)
|
||||||
|
.cornerRadius(20)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#Preview {
|
||||||
|
GeneralGroupSettings(selectedTheme: .constant("Темная"), selectedLanguage: .constant("Русский"))
|
||||||
|
}
|
55
Schedule ICTIS/Settings/ListOfGroupsView.swift
Normal file
@ -0,0 +1,55 @@
|
|||||||
|
//
|
||||||
|
// ListOfGroupsView.swift
|
||||||
|
// Schedule ICTIS
|
||||||
|
//
|
||||||
|
// Created by G412 on 13.03.2025.
|
||||||
|
//
|
||||||
|
|
||||||
|
import SwiftUI
|
||||||
|
|
||||||
|
struct ListOfGroupsView: View {
|
||||||
|
@Environment(\.dismiss) private var dismiss
|
||||||
|
@ObservedObject var vm: ScheduleViewModel
|
||||||
|
@ObservedObject var serchGroupsVM: SearchGroupsViewModel
|
||||||
|
var firstFavVPK: String
|
||||||
|
var secondFavVPK: String
|
||||||
|
var thirdFavVPK: String
|
||||||
|
var body: some View {
|
||||||
|
ScrollView(.vertical, showsIndicators: true) {
|
||||||
|
ForEach(serchGroupsVM.groups) { item in
|
||||||
|
if item.name.starts(with: "ВПК") || item.name.starts(with: "мВПК") {
|
||||||
|
VStack {
|
||||||
|
Rectangle()
|
||||||
|
.frame(height: 1)
|
||||||
|
.foregroundColor(Color("customGray1"))
|
||||||
|
.padding(.horizontal, 10)
|
||||||
|
HStack {
|
||||||
|
Text(item.name)
|
||||||
|
.foregroundColor(.black)
|
||||||
|
.font(.custom("Montserrat-SemiBold", fixedSize: 15))
|
||||||
|
Spacer()
|
||||||
|
}
|
||||||
|
.padding(.horizontal, 10)
|
||||||
|
.padding(.top, 2)
|
||||||
|
.padding(.bottom, 2)
|
||||||
|
.frame(width: UIScreen.main.bounds.width, height: 30)
|
||||||
|
.background(Color("background"))
|
||||||
|
.onTapGesture {
|
||||||
|
if firstFavVPK == "" {
|
||||||
|
UserDefaults.standard.set(item.name, forKey: "vpk1")
|
||||||
|
} else if secondFavVPK == "" {
|
||||||
|
UserDefaults.standard.set(item.name, forKey: "vpk2")
|
||||||
|
} else {
|
||||||
|
UserDefaults.standard.set(item.name, forKey: "vpk3")
|
||||||
|
}
|
||||||
|
vm.nameToHtml[item.name] = ""
|
||||||
|
vm.fetchWeekSchedule()
|
||||||
|
vm.updateFilteringGroups()
|
||||||
|
dismiss()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
47
Schedule ICTIS/Settings/ScheduleGroupSettings.swift
Normal file
@ -0,0 +1,47 @@
|
|||||||
|
//
|
||||||
|
// ScheduleGroupSettings.swift
|
||||||
|
// Schedule ICTIS
|
||||||
|
//
|
||||||
|
// Created by Mironov Egor on 25.02.2025.
|
||||||
|
//
|
||||||
|
|
||||||
|
import SwiftUI
|
||||||
|
|
||||||
|
struct ScheduleGroupSettings: View {
|
||||||
|
@ObservedObject var vm: ScheduleViewModel
|
||||||
|
@ObservedObject var networkMonitor: NetworkMonitor
|
||||||
|
var body: some View {
|
||||||
|
VStack {
|
||||||
|
NavigationLink(destination: FavGroupsView(vm: vm, networkMonitor: networkMonitor)) {
|
||||||
|
HStack {
|
||||||
|
Text("Избранное расписание")
|
||||||
|
.font(.custom("Montserrat-Medium", fixedSize: 17))
|
||||||
|
.foregroundColor(.black)
|
||||||
|
Spacer()
|
||||||
|
Image("arrowRight")
|
||||||
|
}
|
||||||
|
.padding(.horizontal)
|
||||||
|
.padding(.top, 12)
|
||||||
|
.padding(.bottom, 3)
|
||||||
|
}
|
||||||
|
Rectangle()
|
||||||
|
.foregroundColor(Color("customGray1"))
|
||||||
|
.frame(height: 1)
|
||||||
|
.padding(.horizontal)
|
||||||
|
NavigationLink(destination: FavVPKView(vm: vm, networkMonitor: networkMonitor)) {
|
||||||
|
HStack {
|
||||||
|
Text("ВПК")
|
||||||
|
.font(.custom("Montserrat-Medium", fixedSize: 17))
|
||||||
|
.foregroundColor(.black)
|
||||||
|
Spacer()
|
||||||
|
Image("arrowRight")
|
||||||
|
}
|
||||||
|
.padding(.horizontal)
|
||||||
|
.padding(.top, 3)
|
||||||
|
.padding(.bottom, 12)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.background(Color.white)
|
||||||
|
.cornerRadius(20)
|
||||||
|
}
|
||||||
|
}
|
154
Schedule ICTIS/Settings/SelectingGroupView.swift
Normal file
@ -0,0 +1,154 @@
|
|||||||
|
//
|
||||||
|
// SelectedGroupView.swift
|
||||||
|
// Schedule ICTIS
|
||||||
|
//
|
||||||
|
// Created by Mironov Egor on 30.01.2025.
|
||||||
|
//
|
||||||
|
|
||||||
|
import SwiftUI
|
||||||
|
|
||||||
|
struct SelectingGroupView: View {
|
||||||
|
@Environment(\.dismiss) private var dismiss
|
||||||
|
@FocusState private var isFocused: Bool
|
||||||
|
@State private var text: String = ""
|
||||||
|
@ObservedObject var vm: ScheduleViewModel
|
||||||
|
@ObservedObject var networkMonitor: NetworkMonitor
|
||||||
|
@State private var isLoading = false
|
||||||
|
@State private var searchTask: DispatchWorkItem?
|
||||||
|
@StateObject private var serchGroupsVM = SearchGroupsViewModel()
|
||||||
|
var firstFavGroup: String
|
||||||
|
var secondFavGroup: String
|
||||||
|
var thirdFavGroup: String
|
||||||
|
var body: some View {
|
||||||
|
VStack {
|
||||||
|
HStack (spacing: 0) {
|
||||||
|
Image(systemName: "magnifyingglass")
|
||||||
|
.foregroundColor(Color.gray)
|
||||||
|
.padding(.leading, 12)
|
||||||
|
.padding(.trailing, 7)
|
||||||
|
TextField("Поиск группы", text: $text)
|
||||||
|
.disableAutocorrection(true)
|
||||||
|
.focused($isFocused)
|
||||||
|
.onChange(of: text) { oldValue, newValue in
|
||||||
|
searchTask?.cancel()
|
||||||
|
let task = DispatchWorkItem {
|
||||||
|
if !text.isEmpty {
|
||||||
|
serchGroupsVM.fetchGroups(group: text)
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
serchGroupsVM.fetchGroups(group: "кт")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
searchTask = task
|
||||||
|
DispatchQueue.main.asyncAfter(deadline: .now() + 0.5, execute: task)
|
||||||
|
}
|
||||||
|
.onSubmit {
|
||||||
|
self.isFocused = false
|
||||||
|
if (!text.isEmpty) {
|
||||||
|
vm.fetchWeekSchedule(isOtherWeek: false)
|
||||||
|
self.isLoading = true
|
||||||
|
DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) {
|
||||||
|
if vm.errorInNetwork == .noError {
|
||||||
|
vm.errorInNetwork = nil
|
||||||
|
text = transformStringToFormat(text)
|
||||||
|
if firstFavGroup == "" {
|
||||||
|
UserDefaults.standard.set(text, forKey: "group")
|
||||||
|
} else if secondFavGroup == "" {
|
||||||
|
UserDefaults.standard.set(text, forKey: "group2")
|
||||||
|
} else {
|
||||||
|
UserDefaults.standard.set(text, forKey: "group3")
|
||||||
|
}
|
||||||
|
vm.nameToHtml[text] = ""
|
||||||
|
vm.fetchWeekSchedule()
|
||||||
|
vm.updateFilteringGroups()
|
||||||
|
self.isLoading = false
|
||||||
|
self.text = ""
|
||||||
|
dismiss()
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
vm.isShowingAlertForIncorrectGroup = true
|
||||||
|
vm.errorInNetwork = .invalidResponse
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.submitLabel(.done)
|
||||||
|
if isFocused {
|
||||||
|
Button {
|
||||||
|
self.text = ""
|
||||||
|
self.isFocused = false
|
||||||
|
} label: {
|
||||||
|
Image(systemName: "xmark.circle.fill")
|
||||||
|
.padding(.trailing, 20)
|
||||||
|
.offset(x: 10)
|
||||||
|
.foregroundColor(.gray)
|
||||||
|
.background(
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.frame(height: 40)
|
||||||
|
.background(
|
||||||
|
RoundedRectangle(cornerRadius: 15)
|
||||||
|
.fill(.white)
|
||||||
|
)
|
||||||
|
Spacer()
|
||||||
|
if isLoading {
|
||||||
|
LoadingView()
|
||||||
|
Spacer()
|
||||||
|
} else if networkMonitor.isConnected {
|
||||||
|
ScrollView(.vertical, showsIndicators: true) {
|
||||||
|
ForEach(serchGroupsVM.groups) { item in
|
||||||
|
if item.name.starts(with: "КТ") { //Отображаем только группы(без аудиторий и преподавателей)
|
||||||
|
VStack {
|
||||||
|
Rectangle()
|
||||||
|
.frame(height: 1)
|
||||||
|
.foregroundColor(Color("customGray1"))
|
||||||
|
.padding(.horizontal, 10)
|
||||||
|
HStack {
|
||||||
|
Text(item.name)
|
||||||
|
.foregroundColor(.black)
|
||||||
|
.font(.custom("Montserrat-SemiBold", fixedSize: 15))
|
||||||
|
Spacer()
|
||||||
|
}
|
||||||
|
.padding(.horizontal, 10)
|
||||||
|
.padding(.top, 2)
|
||||||
|
.padding(.bottom, 2)
|
||||||
|
.frame(width: UIScreen.main.bounds.width, height: 30)
|
||||||
|
.background(Color("background"))
|
||||||
|
.onTapGesture {
|
||||||
|
if firstFavGroup == "" {
|
||||||
|
UserDefaults.standard.set(item.name, forKey: "group")
|
||||||
|
vm.nameToHtml[item.name] = ""
|
||||||
|
} else if secondFavGroup == "" {
|
||||||
|
UserDefaults.standard.set(item.name, forKey: "group2")
|
||||||
|
vm.nameToHtml[item.name] = ""
|
||||||
|
} else {
|
||||||
|
UserDefaults.standard.set(item.name, forKey: "group3")
|
||||||
|
vm.nameToHtml[item.name] = ""
|
||||||
|
}
|
||||||
|
vm.updateFilteringGroups()
|
||||||
|
vm.fetchWeekSchedule()
|
||||||
|
dismiss()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
NetworkErrorView(message: "Восстановите подключение к интернету чтобы мы смогли загрузить список групп")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.padding(.horizontal, 10)
|
||||||
|
.background(Color("background"))
|
||||||
|
.onAppear {
|
||||||
|
serchGroupsVM.fetchGroups(group: "кт")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#Preview {
|
||||||
|
@Previewable @StateObject var vm = ScheduleViewModel()
|
||||||
|
@Previewable @StateObject var vm2 = NetworkMonitor()
|
||||||
|
SelectingGroupView(vm: vm, networkMonitor: vm2, firstFavGroup: "", secondFavGroup: "", thirdFavGroup: "")
|
||||||
|
}
|
118
Schedule ICTIS/Settings/SelectingVPKView.swift
Normal file
@ -0,0 +1,118 @@
|
|||||||
|
//
|
||||||
|
// SelectedGroupView.swift
|
||||||
|
// Schedule ICTIS
|
||||||
|
//
|
||||||
|
// Created by Mironov Egor on 30.01.2025.
|
||||||
|
//
|
||||||
|
|
||||||
|
import SwiftUI
|
||||||
|
|
||||||
|
struct SelectingVPKView: View {
|
||||||
|
@Environment(\.dismiss) private var dismiss
|
||||||
|
@FocusState private var isFocused: Bool
|
||||||
|
@State private var text: String = ""
|
||||||
|
@ObservedObject var vm: ScheduleViewModel
|
||||||
|
@ObservedObject var networkMonitor: NetworkMonitor
|
||||||
|
@State private var isLoading = false
|
||||||
|
@State private var searchTask: DispatchWorkItem?
|
||||||
|
@StateObject private var serchGroupsVM = SearchGroupsViewModel()
|
||||||
|
var firstFavVPK: String
|
||||||
|
var secondFavVPK: String
|
||||||
|
var thirdFavVPK: String
|
||||||
|
var body: some View {
|
||||||
|
VStack {
|
||||||
|
HStack (spacing: 0) {
|
||||||
|
Image(systemName: "magnifyingglass")
|
||||||
|
.foregroundColor(Color.gray)
|
||||||
|
.padding(.leading, 12)
|
||||||
|
.padding(.trailing, 7)
|
||||||
|
TextField("Поиск ВПК", text: $text)
|
||||||
|
.disableAutocorrection(true)
|
||||||
|
.focused($isFocused)
|
||||||
|
.onChange(of: text) { oldValue, newValue in
|
||||||
|
searchTask?.cancel()
|
||||||
|
let task = DispatchWorkItem {
|
||||||
|
if !text.isEmpty {
|
||||||
|
serchGroupsVM.fetchGroups(group: text)
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
serchGroupsVM.fetchGroups(group: "ВПК")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
searchTask = task
|
||||||
|
DispatchQueue.main.asyncAfter(deadline: .now() + 0.5, execute: task)
|
||||||
|
}
|
||||||
|
.onSubmit {
|
||||||
|
self.isFocused = false
|
||||||
|
if (!text.isEmpty) {
|
||||||
|
vm.fetchWeekSchedule(isOtherWeek: false)
|
||||||
|
self.isLoading = true
|
||||||
|
DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) {
|
||||||
|
if vm.errorInNetwork == .noError {
|
||||||
|
vm.errorInNetwork = nil
|
||||||
|
if firstFavVPK == "" {
|
||||||
|
UserDefaults.standard.set(text, forKey: "vpk1")
|
||||||
|
} else if secondFavVPK == "" {
|
||||||
|
UserDefaults.standard.set(text, forKey: "vpk2")
|
||||||
|
} else {
|
||||||
|
UserDefaults.standard.set(text, forKey: "vpk3")
|
||||||
|
}
|
||||||
|
text = transformStringToFormat(text)
|
||||||
|
vm.nameToHtml[text] = ""
|
||||||
|
vm.updateFilteringGroups()
|
||||||
|
vm.fetchWeekSchedule()
|
||||||
|
self.isLoading = false
|
||||||
|
self.text = ""
|
||||||
|
print("✅ - Избранный ВПК был установлен")
|
||||||
|
dismiss()
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
vm.isShowingAlertForIncorrectGroup = true
|
||||||
|
vm.errorInNetwork = .invalidResponse
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.submitLabel(.done)
|
||||||
|
if isFocused {
|
||||||
|
Button {
|
||||||
|
self.text = ""
|
||||||
|
self.isFocused = false
|
||||||
|
} label: {
|
||||||
|
Image(systemName: "xmark.circle.fill")
|
||||||
|
.padding(.trailing, 20)
|
||||||
|
.offset(x: 10)
|
||||||
|
.foregroundColor(.gray)
|
||||||
|
.background(
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.frame(height: 40)
|
||||||
|
.background(
|
||||||
|
RoundedRectangle(cornerRadius: 15)
|
||||||
|
.fill(.white)
|
||||||
|
)
|
||||||
|
Spacer()
|
||||||
|
if isLoading {
|
||||||
|
LoadingView()
|
||||||
|
Spacer()
|
||||||
|
} else if networkMonitor.isConnected {
|
||||||
|
ListOfGroupsView(vm: vm, serchGroupsVM: serchGroupsVM, firstFavVPK: firstFavVPK, secondFavVPK: secondFavVPK, thirdFavVPK: thirdFavVPK)
|
||||||
|
} else {
|
||||||
|
ConnectingToNetworkView()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.padding(.horizontal, 10)
|
||||||
|
.background(Color("background"))
|
||||||
|
.onAppear {
|
||||||
|
serchGroupsVM.fetchGroups(group: "ВПК")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#Preview {
|
||||||
|
@Previewable @StateObject var vm = ScheduleViewModel()
|
||||||
|
@Previewable @StateObject var vm2 = NetworkMonitor()
|
||||||
|
SelectingVPKView(vm: vm, networkMonitor: vm2, firstFavVPK: "", secondFavVPK: "", thirdFavVPK: "")
|
||||||
|
}
|
48
Schedule ICTIS/Settings/SettingsView.swift
Normal file
@ -0,0 +1,48 @@
|
|||||||
|
//
|
||||||
|
// SettingsView2.swift
|
||||||
|
// Schedule ICTIS
|
||||||
|
//
|
||||||
|
// Created by Mironov Egor on 25.02.2025.
|
||||||
|
//
|
||||||
|
|
||||||
|
import SwiftUI
|
||||||
|
|
||||||
|
struct SettingsView: View {
|
||||||
|
@ObservedObject var vm: ScheduleViewModel
|
||||||
|
@ObservedObject var networkMonitor: NetworkMonitor
|
||||||
|
@State private var selectedTheme = "Светлая"
|
||||||
|
@State private var selectedLanguage = "Русский"
|
||||||
|
var body: some View {
|
||||||
|
NavigationView {
|
||||||
|
VStack {
|
||||||
|
ScrollView (.vertical, showsIndicators: false) {
|
||||||
|
VStack (alignment: .leading) {
|
||||||
|
Text("Общие")
|
||||||
|
.font(.custom("Montserrat-Medium", fixedSize: 18))
|
||||||
|
.foregroundColor(Color("customGray3"))
|
||||||
|
.padding(.horizontal)
|
||||||
|
GeneralGroupSettings(selectedTheme: $selectedTheme, selectedLanguage: $selectedLanguage)
|
||||||
|
}
|
||||||
|
.padding(.top, 20)
|
||||||
|
VStack (alignment: .leading) {
|
||||||
|
Text("Расписание")
|
||||||
|
.font(.custom("Montserrat-Medium", fixedSize: 18))
|
||||||
|
.foregroundColor(Color("customGray3"))
|
||||||
|
.padding(.horizontal)
|
||||||
|
ScheduleGroupSettings(vm: vm, networkMonitor: networkMonitor)
|
||||||
|
}
|
||||||
|
.padding(.top, 20)
|
||||||
|
}
|
||||||
|
.padding(.horizontal)
|
||||||
|
}
|
||||||
|
.background(Color("background"))
|
||||||
|
.navigationTitle("Настройки")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#Preview {
|
||||||
|
@Previewable @StateObject var vm = ScheduleViewModel()
|
||||||
|
@Previewable @StateObject var vm2 = NetworkMonitor()
|
||||||
|
SettingsView(vm: vm, networkMonitor: vm2)
|
||||||
|
}
|
28
Schedule ICTIS/Settings/TestingView.swift
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
//
|
||||||
|
// TestingView.swift
|
||||||
|
// Schedule ICTIS
|
||||||
|
//
|
||||||
|
// Created by G412 on 05.03.2025.
|
||||||
|
//
|
||||||
|
|
||||||
|
import SwiftUI
|
||||||
|
|
||||||
|
struct TestingView: View {
|
||||||
|
var body: some View {
|
||||||
|
VStack {
|
||||||
|
Text("Hello")
|
||||||
|
Text("Hello")
|
||||||
|
Text("Hello")
|
||||||
|
Text("Hello")
|
||||||
|
Text("Hello")
|
||||||
|
Text("Hello")
|
||||||
|
Text("Hello")
|
||||||
|
Text("Hello")
|
||||||
|
Text("Hello")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#Preview {
|
||||||
|
TestingView()
|
||||||
|
}
|