From 1de531abc8008cd336202d4d5dadd17712cc70d6 Mon Sep 17 00:00:00 2001 From: Vladimir Dubovik Date: Wed, 11 Dec 2024 13:33:35 +0300 Subject: [PATCH] Commit --- .../AppIcon.appiconset/Contents.json | 1 + .../AppIcon.appiconset/ICTIS_logo.png | Bin 0 -> 24056 bytes .../turquoise.colorset/Contents.json | 38 +++++++ Schedule ICTIS/ContentView.swift | 35 ++++-- Schedule ICTIS/Helpers/Date+Extensions.swift | 18 +++ Schedule ICTIS/Helpers/View+Extensions.swift | 46 ++++++++ Schedule ICTIS/Main/LoadingView.swift | 25 +++++ Schedule ICTIS/Main/MainView.swift | 34 ++---- Schedule ICTIS/Main/ScheduleView.swift | 106 ++++++++---------- .../Main/TabViews/MonthTabView.swift | 104 ++++++++++++++--- .../Main/TabViews/WeekTabView.swift | 40 ++++--- .../{Main/TabBar => Model}/TabBarModel.swift | 0 Schedule ICTIS/ViewModel/ViewModel.swift | 10 +- 13 files changed, 325 insertions(+), 132 deletions(-) create mode 100644 Schedule ICTIS/Assets.xcassets/AppIcon.appiconset/ICTIS_logo.png create mode 100644 Schedule ICTIS/Assets.xcassets/turquoise.colorset/Contents.json create mode 100644 Schedule ICTIS/Main/LoadingView.swift rename Schedule ICTIS/{Main/TabBar => Model}/TabBarModel.swift (100%) diff --git a/Schedule ICTIS/Assets.xcassets/AppIcon.appiconset/Contents.json b/Schedule ICTIS/Assets.xcassets/AppIcon.appiconset/Contents.json index 13613e3..080ba9c 100644 --- a/Schedule ICTIS/Assets.xcassets/AppIcon.appiconset/Contents.json +++ b/Schedule ICTIS/Assets.xcassets/AppIcon.appiconset/Contents.json @@ -1,6 +1,7 @@ { "images" : [ { + "filename" : "ICTIS_logo.png", "idiom" : "universal", "platform" : "ios", "size" : "1024x1024" diff --git a/Schedule ICTIS/Assets.xcassets/AppIcon.appiconset/ICTIS_logo.png b/Schedule ICTIS/Assets.xcassets/AppIcon.appiconset/ICTIS_logo.png new file mode 100644 index 0000000000000000000000000000000000000000..44b05e5df1ccc84f95ea5ced38eba6e6b29dbe14 GIT binary patch literal 24056 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 JtaD0e0sub}0Wtsp literal 0 HcmV?d00001 diff --git a/Schedule ICTIS/Assets.xcassets/turquoise.colorset/Contents.json b/Schedule ICTIS/Assets.xcassets/turquoise.colorset/Contents.json new file mode 100644 index 0000000..ba13876 --- /dev/null +++ b/Schedule ICTIS/Assets.xcassets/turquoise.colorset/Contents.json @@ -0,0 +1,38 @@ +{ + "colors" : [ + { + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0xDE", + "green" : "0xE4", + "red" : "0x22" + } + }, + "idiom" : "universal" + }, + { + "appearances" : [ + { + "appearance" : "luminosity", + "value" : "dark" + } + ], + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0xDE", + "green" : "0xE4", + "red" : "0x22" + } + }, + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Schedule ICTIS/ContentView.swift b/Schedule ICTIS/ContentView.swift index dd68dca..789de6c 100644 --- a/Schedule ICTIS/ContentView.swift +++ b/Schedule ICTIS/ContentView.swift @@ -8,20 +8,33 @@ import SwiftUI struct ContentView: View { - @State private var selectedTab: TabBarModel = .schedule + @State private var selectedTab: Int = 1 @StateObject var vm = ViewModel() + var body: some View { - ZStack { - switch selectedTab { - case .schedule: - MainView(vm: vm) - case .tasks: - Text("Tasks") - case .settings: - Text("Settings") - } - TabBarView(selectedTab: $selectedTab) + TabView(selection: $selectedTab) { + Text("Tasks") + .tabItem { + Image(systemName: "books.vertical") + Text("Задания") + } + .tag(0) + + MainView(vm: vm) + .tabItem { + Image(systemName: "house") + Text("Расписание") + } + .tag(1) + + Text("Settings") + .tabItem { + Image(systemName: "gear") + Text("Настройки") + } + .tag(2) } + .accentColor(Color("blueColor")) } } diff --git a/Schedule ICTIS/Helpers/Date+Extensions.swift b/Schedule ICTIS/Helpers/Date+Extensions.swift index c4256ca..135fd62 100644 --- a/Schedule ICTIS/Helpers/Date+Extensions.swift +++ b/Schedule ICTIS/Helpers/Date+Extensions.swift @@ -75,6 +75,24 @@ extension Date { return month } + func createNextMonth() -> [MonthWeek] { + let calendar = Calendar.current + let startOfLastDate = calendar.startOfDay(for: self) + guard let nextDate = calendar.date(byAdding: .day, value: 1, to: startOfLastDate) else { + return [] + } + return fetchMonth(nextDate) + } + + func createPreviousMonth() -> [MonthWeek] { + let calendar = Calendar.current + let startOfFirstDate = calendar.startOfDay(for: self) + guard let previousDate = calendar.date(byAdding: .month, value: -1, to: startOfFirstDate) else { + return [] + } + return fetchMonth(previousDate) + } + func createNextWeek() -> [WeekDay] { let calendar = Calendar.current let startOfLastDate = calendar.startOfDay(for: self) diff --git a/Schedule ICTIS/Helpers/View+Extensions.swift b/Schedule ICTIS/Helpers/View+Extensions.swift index 07bd8e9..7b883dc 100644 --- a/Schedule ICTIS/Helpers/View+Extensions.swift +++ b/Schedule ICTIS/Helpers/View+Extensions.swift @@ -24,4 +24,50 @@ extension View { return currentMonth == dateMonth && currentYear == dateYear } + + func isSameWeek(_ date1: Date, _ date2: Date) -> Bool { + return Calendar.current.compare(date1, to: date2, toGranularity: .weekOfYear) == .orderedSame + } + + func weeksBetween(startDate: Date, endDate: Date) -> Int { + let calendar = Calendar.current + let startOfFirstDate = calendar.startOfDay(for: startDate) + let startOfEndDate = calendar.startOfDay(for: endDate) + + let weekForDate1 = calendar.dateInterval(of: .weekOfMonth, for: startOfFirstDate) + let weekForDate2 = calendar.dateInterval(of: .weekOfMonth, for: startOfEndDate) + + guard let startOfWeek1 = weekForDate1?.start else { + return 0 + } + + guard let startOfWeek2 = weekForDate2?.start else { + return 0 + } + + let components = calendar.dateComponents([.day], from: startOfWeek1, to: startOfWeek2) + let daysDifference = components.day ?? 0 + return Int(ceil(Double(abs(daysDifference)) / 7.0)) + } + + func convertTimeString(_ input: String) -> [String] { + let parts = input.split(separator: "-") + if let firstPart = parts.first, let lastPart = parts.last { + return [String(firstPart), String(lastPart)] + } else { + return [] + } + } + + func getColorForClass(_ str: String) -> Color { + if (str.contains("LMS")) { + return Color("blueForOnline") + } + else if (str.contains("ВПК")) { + return Color("turquoise") + } + else { + return Color("greenForOffline") + } + } } diff --git a/Schedule ICTIS/Main/LoadingView.swift b/Schedule ICTIS/Main/LoadingView.swift new file mode 100644 index 0000000..372f54e --- /dev/null +++ b/Schedule ICTIS/Main/LoadingView.swift @@ -0,0 +1,25 @@ +// +// 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.5) + } + } +} + +#Preview { + LoadingView(isLoading: .constant(true)) +} diff --git a/Schedule ICTIS/Main/MainView.swift b/Schedule ICTIS/Main/MainView.swift index e44e294..047bba4 100644 --- a/Schedule ICTIS/Main/MainView.swift +++ b/Schedule ICTIS/Main/MainView.swift @@ -9,10 +9,6 @@ import SwiftUI struct MainView: View { @State private var searchText: String = "" - @State private var currentDate: Date = Date() - @State private var weekSlider: [[Date.WeekDay]] = [] - @State private var currentWeekIndex: Int = 1 - @State private var createWeek: Bool = false @State private var isShowingMonthSlider: Bool = false @State private var isFirstAppearence = true @ObservedObject var vm: ViewModel @@ -20,7 +16,10 @@ struct MainView: View { var body: some View { VStack { SearchBarView(text: $searchText, vm: vm) - if (vm.isFirstStartOffApp) { + if (vm.isFirstStartOffApp && vm.isLoading) { + LoadingView(isLoading: $vm.isLoading) + } + else if (vm.isFirstStartOffApp) { FirstLaunchScheduleView() } else { @@ -34,23 +33,6 @@ struct MainView: View { Text(error.failureReason) } .background(Color("background")) - .onAppear(perform: { - currentDate = vm.selectedDay - vm.updateSelectedDayIndex(currentDate) - 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()) - } - } - }) } @ViewBuilder @@ -58,14 +40,14 @@ struct MainView: View { VStack (alignment: .leading, spacing: 6) { HStack { VStack (alignment: .leading, spacing: 0) { - Text(currentDate.format("EEEE")) + Text(vm.selectedDay.format("EEEE")) .font(.system(size: 40, weight: .semibold)) .foregroundStyle(.black) HStack (spacing: 5) { - Text(currentDate.format("dd")) + Text(vm.selectedDay.format("dd")) .font(.system(size: 20, weight: .bold)) .foregroundStyle(Color("grayForDate")) - Text(currentDate.format("MMMM")) + Text(vm.selectedDay.format("MMMM")) .font(.system(size: 20, weight: .bold)) .foregroundStyle(Color("grayForDate")) Spacer() @@ -91,7 +73,7 @@ struct MainView: View { Spacer() } if (!isShowingMonthSlider) { - WeekTabView(currentWeekIndex: $currentWeekIndex, weekSlider: $weekSlider, currentDate: $currentDate, vm: vm) + WeekTabView(vm: vm) .transition(.opacity) } else { diff --git a/Schedule ICTIS/Main/ScheduleView.swift b/Schedule ICTIS/Main/ScheduleView.swift index 212ae75..2e319eb 100644 --- a/Schedule ICTIS/Main/ScheduleView.swift +++ b/Schedule ICTIS/Main/ScheduleView.swift @@ -10,75 +10,59 @@ import SwiftUI struct ScheduleView: View { @ObservedObject var vm: ViewModel var body: some View { - ZStack (alignment: .top) { - ScrollView(.vertical, showsIndicators: false) { - VStack (spacing: 20) { - 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 - let lesson = daySchedule[lessonIndex] // Это строка с расписанием одной пары - if !lesson.isEmpty { - HStack(spacing: 10) { - VStack { - Text(convertTimeString(vm.classes[1][lessonIndex])[0]) - .font(.system(size: 15, weight: .regular)) - Text(convertTimeString(vm.classes[1][lessonIndex])[1]) - .font(.system(size: 15, weight: .regular)) + if vm.isLoading { + LoadingView(isLoading: $vm.isLoading) + } + else { + ZStack (alignment: .top) { + ScrollView(.vertical, showsIndicators: false) { + VStack (spacing: 20) { + 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 + let lesson = daySchedule[lessonIndex] // Это строка с расписанием одной пары + if !lesson.isEmpty { + HStack(spacing: 10) { + VStack { + Text(convertTimeString(vm.classes[1][lessonIndex])[0]) + .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() } - .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(onlineOrOffline(lesson) ? Color("greenForOffline") : Color("blueForOnline")) - 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) } - .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) } } } } + .frame(width: UIScreen.main.bounds.width) + .padding(.bottom, 100) + .padding(.top, 30) } - .frame(width: UIScreen.main.bounds.width) - .padding(.bottom, 100) - .padding(.top, 30) + VStack { + LinearGradient(gradient: Gradient(colors: [Color("background").opacity(0.9), Color("background").opacity(0.89)]), startPoint: .top, endPoint: .bottom) + } + .frame(width: UIScreen.main.bounds.width, height: 15) } - VStack { - LinearGradient(gradient: Gradient(colors: [Color("background").opacity(0.9), Color("background").opacity(0.89)]), startPoint: .top, endPoint: .bottom) -// Rectangle() -// .frame(width: UIScreen.main.bounds.width, height: 25) -// .foregroundColor(Color("background").opacity(0.9)) - } - .frame(width: UIScreen.main.bounds.width, height: 15) - } - } - - func convertTimeString(_ input: String) -> [String] { - let parts = input.split(separator: "-") - if let firstPart = parts.first, let lastPart = parts.last { - return [String(firstPart), String(lastPart)] - } else { - return [] - } - } - - func onlineOrOffline(_ str: String) -> Bool { - if (MockData.onlineClasses.contains(str)) { - return false - } - else { - return true } } } diff --git a/Schedule ICTIS/Main/TabViews/MonthTabView.swift b/Schedule ICTIS/Main/TabViews/MonthTabView.swift index 539d24d..ed4ca89 100644 --- a/Schedule ICTIS/Main/TabViews/MonthTabView.swift +++ b/Schedule ICTIS/Main/TabViews/MonthTabView.swift @@ -11,7 +11,6 @@ struct MonthTabView: View { @State private var currentMonthIndex: Int = 1 @State private var monthSlider: [[Date.MonthWeek]] = [] @State private var createMonth: Bool = false - @State private var currentDate: Date = .init() @ObservedObject var vm: ViewModel var body: some View { VStack { @@ -39,32 +38,55 @@ struct MonthTabView: View { .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 } } - .onAppear(perform: { - currentDate = vm.selectedDay - vm.updateSelectedDayIndex(currentDate) - if monthSlider.isEmpty { - let currentMonth = Date().fetchMonth(vm.selectedDay) - - monthSlider.append(currentMonth) - } - }) } @ViewBuilder func MonthView(_ month: [Date.MonthWeek]) -> some View { - VStack { + 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) { @@ -72,18 +94,18 @@ struct MonthTabView: View { VStack { Text(day.date.format("dd")) .font(.system(size: 15, weight: .bold)) - .foregroundStyle(isDateInCurrentMonth(day.date) ? isSameDate(day.date, currentDate) ? Color.white : Color.black: isSameDate(day.date, currentDate) ? Color.white : Color("greyForDaysInMonthTabView")) + .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, currentDate) { + if isSameDate(day.date, vm.selectedDay) { Color("blueColor") } else { Color("background") } - if isSameDate(day.date, currentDate) { + if isSameDate(day.date, vm.selectedDay) { Color("blueColor") } } @@ -91,7 +113,7 @@ struct MonthTabView: View { ) .overlay ( Group { - if day.date.isToday && !isSameDate(day.date, currentDate) { + if day.date.isToday && !isSameDate(day.date, vm.selectedDay) { RoundedRectangle(cornerRadius: 100) .stroke(Color("blueColor"), lineWidth: 2) } @@ -99,12 +121,60 @@ struct MonthTabView: View { ) .cornerRadius(15) .onTapGesture { - currentDate = day.date - vm.updateSelectedDayIndex(currentDate) + 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() { + 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 { diff --git a/Schedule ICTIS/Main/TabViews/WeekTabView.swift b/Schedule ICTIS/Main/TabViews/WeekTabView.swift index 37a634b..4adc101 100644 --- a/Schedule ICTIS/Main/TabViews/WeekTabView.swift +++ b/Schedule ICTIS/Main/TabViews/WeekTabView.swift @@ -8,9 +8,8 @@ import SwiftUI struct WeekTabView: View { - @Binding var currentWeekIndex: Int - @Binding var weekSlider: [[Date.WeekDay]] - @Binding var currentDate: Date + @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 { @@ -27,6 +26,22 @@ struct WeekTabView: View { .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 @@ -41,24 +56,24 @@ struct WeekTabView: View { VStack (spacing: 1) { Text(day.date.format("E")) .font(.system(size: 15, weight: .semibold)) - .foregroundColor(day.date.format("E") == "Вс" ? Color(.red) : isSameDate(day.date, currentDate) ? Color("customGray1") : Color("customGray3")) + .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, currentDate) ? .white : .black) + .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, currentDate) { + if isSameDate(day.date, vm.selectedDay) { Color("blueColor") } else { Color(.white) } - if isSameDate(day.date, currentDate) { + if isSameDate(day.date, vm.selectedDay) { Color("blueColor") } } @@ -66,7 +81,7 @@ struct WeekTabView: View { ) .overlay ( Group { - if day.date.isToday && !isSameDate(day.date, currentDate) { + if day.date.isToday && !isSameDate(day.date, vm.selectedDay) { RoundedRectangle(cornerRadius: 15) .stroke(Color("blueColor"), lineWidth: 2) } @@ -74,8 +89,8 @@ struct WeekTabView: View { ) .cornerRadius(15) .onTapGesture { - currentDate = day.date - vm.updateSelectedDayIndex(currentDate) + vm.selectedDay = day.date + vm.updateSelectedDayIndex() } } } @@ -111,7 +126,7 @@ struct WeekTabView: View { weekSlider.removeLast() currentWeekIndex = 1 vm.selectedDay = calendar.date(byAdding: .weekOfYear, value: -1, to: vm.selectedDay) ?? Date.init() - currentDate = vm.selectedDay + vm.updateSelectedDayIndex() } if let lastDate = weekSlider[currentWeekIndex].last?.date, @@ -126,8 +141,7 @@ struct WeekTabView: View { weekSlider.removeFirst() currentWeekIndex = weekSlider.count - 2 vm.selectedDay = calendar.date(byAdding: .weekOfYear, value: 1, to: vm.selectedDay) ?? Date.init() - currentDate = vm.selectedDay - print(currentDate) + vm.updateSelectedDayIndex() } } } diff --git a/Schedule ICTIS/Main/TabBar/TabBarModel.swift b/Schedule ICTIS/Model/TabBarModel.swift similarity index 100% rename from Schedule ICTIS/Main/TabBar/TabBarModel.swift rename to Schedule ICTIS/Model/TabBarModel.swift diff --git a/Schedule ICTIS/ViewModel/ViewModel.swift b/Schedule ICTIS/ViewModel/ViewModel.swift index 9bd3ff3..c4bf32b 100644 --- a/Schedule ICTIS/ViewModel/ViewModel.swift +++ b/Schedule ICTIS/ViewModel/ViewModel.swift @@ -26,9 +26,11 @@ final class ViewModel: ObservableObject { @Published var isFirstStartOffApp = true @Published var isShowingAlertForIncorrectGroup: Bool = false @Published var errorInNetwork: NetworkError? + @Published var isLoading: Bool = false //MARK: Methods func fetchWeekSchedule(_ group: String, _ num: Int = 0) { + isLoading = true Task { do { var schedule: Schedule @@ -45,6 +47,7 @@ final class ViewModel: ObservableObject { classes = weekSchedule.table self.isFirstStartOffApp = false self.isShowingAlertForIncorrectGroup = false + isLoading = false } catch { if let error = error as? NetworkError { @@ -57,15 +60,15 @@ final class ViewModel: ObservableObject { default: print(2) } + isLoading = false print(error) } } } } - func updateSelectedDayIndex(_ date: Date) { - selectedDay = date - switch date.format("E") { + func updateSelectedDayIndex() { + switch selectedDay.format("E") { case "Пн": selectedIndex = 2 case "Вт": @@ -81,7 +84,6 @@ final class ViewModel: ObservableObject { default: selectedIndex = 8 } - print(selectedIndex) } }