Skip to content

Commit

Permalink
- Add expandable cards
Browse files Browse the repository at this point in the history
- Fix color storage

Still need to fix random coloring in preview schedule and weird list animations in schedule view
  • Loading branch information
adisve committed Jan 27, 2023
1 parent 6373aa6 commit be8bb15
Show file tree
Hide file tree
Showing 24 changed files with 315 additions and 244 deletions.
40 changes: 32 additions & 8 deletions tumble-ios.xcodeproj/project.pbxproj

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,10 @@ extension Font {
static let mediumSmallFont = Font.custom("roboto_regular", size: Font.TextStyle.footnote.size, relativeTo: .caption)
static let smallFont = Font.custom("roboto_regular", size: Font.TextStyle.caption.size, relativeTo: .caption)
static let verySmallFont = Font.custom("roboto_regular", size: Font.TextStyle.caption2.size, relativeTo: .caption)
static let drawerItemFont = Font.custom("roboto_regular", size: 16, relativeTo: .caption)
static let smallIconFont = Font.custom("roboto_regular", size: 14, relativeTo: .caption)
static let mediumIconFont = Font.custom("roboto_regular", size: 16, relativeTo: .caption)
static let largeIconFont = Font.custom("roboto_regular", size: 18, relativeTo: .caption)
static let veryLargeIconFont = Font.custom("roboto_regular", size: 20, relativeTo: .caption)
}

extension Font.TextStyle {
Expand Down
64 changes: 35 additions & 29 deletions tumble-ios/Core/Extensions/ScheduleExtensions.swift
Original file line number Diff line number Diff line change
Expand Up @@ -11,16 +11,16 @@ import SwiftUI
//To convert API result date (ISO8601) to `Date`, this property should not be inside any methods
let inDateFormatter = ISO8601DateFormatter()



extension [API.Types.Response.Schedule] {

func flatten() -> [DayUiModel] {
inDateFormatter.formatOptions = [.withInternetDateTime, .withFractionalSeconds]
var days: [DayUiModel] = []
self.forEach { schedule in
days.append(contentsOf: schedule.days.reduce(into: []) {
let day = Calendar.current.dateComponents([.day], from: inDateFormatter.date(from: $1.isoString)!).day!
let today = Calendar.current.dateComponents([.day], from: Date.now).day!
if day >= today {$0.append($1)}}.toUiModel())
if $1.isValidDay() {$0.append($1)}}.toUiModel())
}
return days.toOrderedDayUiModels()
}
Expand Down Expand Up @@ -49,20 +49,31 @@ extension [API.Types.Response.Schedule] {
}

extension API.Types.Response.Schedule {
func assignRandomCoursesColors() -> [String : [String : Color]] {
var coursesColors: [String : [String : Color]] = [:]

func assignCoursesRandomColors() -> [String : [String : Color]] {
var courseColors: [String : [String : Color]] = [:]
for day in self.days {
for event in day.events {
if coursesColors[event.course.id] == nil {
if courseColors[event.course.id] == nil {
let hexColorString = colors.randomElement()!;
coursesColors[event.course.id] = [hexColorString : hexStringToUIColor(hex: hexColorString)]
courseColors[event.course.id] = [hexColorString : hexStringToUIColor(hex: hexColorString)]
}
}
}
return coursesColors
return courseColors
}


func courses() -> [String] {
var courses: [String] = []
for day in self.days {
for event in day.events {
if !courses.contains(event.course.id) {
courses.append(event.course.id)
}
}
}
return courses
}
}

extension [DayUiModel] {
Expand Down Expand Up @@ -90,39 +101,34 @@ extension [DayUiModel] {
}
}


extension [API.Types.Response.Day] {
func toUiModel() -> [DayUiModel] {
return self.map { day in
return DayUiModel(name: day.name, date: day.date, isoString: day.isoString, weekNumber: day.weekNumber, events: day.events)
}
}



// Used in ScheduleMainPageView when loading a schedule
// from the local database and converting into a list of UI models
func toOrderedDays() -> [DayUiModel] {
inDateFormatter.formatOptions = [.withInternetDateTime, .withFractionalSeconds]
var days: [DayUiModel] = []
days.append(contentsOf: self.reduce(into: []) {
let day = Calendar.current.dateComponents([.day], from: inDateFormatter.date(from: $1.isoString)!).day!
let today = Calendar.current.dateComponents([.day], from: Date.now).day!
if day >= today {$0.append($1)}}.toUiModel())
if $1.isValidDay() {$0.append($1)}}.toUiModel())
return days.toOrderedDayUiModels()
}
}

extension API.Types.Response.Event {
func color() -> Color {
var hexColor: String = ""
CourseColorStore.load { result in
switch result {
case .failure(_):
print("Error on course with id: \(self.id)")
case .success(let courses):
if !courses.isEmpty {
print(course.id)
hexColor = courses[course.id]!
}
}
}
return hexColor == "" ? hexStringToUIColor(hex: colors.randomElement() ?? "FFFFFF") : hexStringToUIColor(hex: hexColor)
extension API.Types.Response.Day {
// Determines if a schedule should be shown in the UI view based
// on whether or not the day of the month has already passed or not
func isValidDay() -> Bool {
let dayIsoString: String = self.isoString
let day: Int = Calendar.current.dateComponents([.day], from: inDateFormatter.date(from: dayIsoString)!).day!
let today: Int = Calendar.current.dateComponents([.day], from: inDateFormatter.date(from: dayIsoString)!).day!
let month: Int = Calendar.current.dateComponents([.month], from: Date.now).month!
let todaysMonth: Int = Calendar.current.dateComponents([.month], from: Date.now).month!
return ((day >= today && month == todaysMonth) || (month != todaysMonth))
}
}
21 changes: 0 additions & 21 deletions tumble-ios/Core/Utilities/Color.swift
Original file line number Diff line number Diff line change
Expand Up @@ -29,24 +29,3 @@ func hexStringToUIColor (hex:String) -> Color {
)
}

func assignCardColor(event: API.Types.Response.Event) -> String {
var cardColor: String = ""
let group = DispatchGroup()
group.enter()
CourseColorStore.load { result in
DispatchQueue.main.async {
switch result {
case .failure(_):
print("Error on course with id: \(event.course.id)")
case .success(let courses):
if !courses.isEmpty {
let hexColor = courses[event.course.id]!;
cardColor = hexColor == "" ? colors.randomElement() ?? "FFFFFF" : hexColor
}
}
}
group.leave()
}
group.wait()
return cardColor
}
1 change: 0 additions & 1 deletion tumble-ios/Core/ViewModels/HomePageView-ViewModel.swift
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,6 @@ extension HomePageView {
}

let schoolUrl: String = schools.first(where: {$0.name == school!.name})!.schoolUrl
print(schoolUrl)
return schoolUrl
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,9 +26,8 @@ extension ScheduleMainPageView {
@MainActor class ScheduleMainPageViewModel: ObservableObject {
@Published var scheduleViewTypes: [ScheduleViewType] = ScheduleViewType.allValues
@Published var status: ScheduleMainPageStatus = .loading
@Published var schedules: [API.Types.Response.Schedule] = []
@Published var days: [DayUiModel] = []
@Published var courseColors: [String : String] = [:]
@Published var courseColors: CourseAndColorDict = [:]
@Published var viewType: ScheduleViewType = {
let hasView: Bool = UserDefaults.standard.isKeyPresentInUserDefaults(key: UserDefaults.StoreKey.viewType.rawValue)
if !(hasView) {
Expand Down
77 changes: 46 additions & 31 deletions tumble-ios/Core/ViewModels/SearchView-ViewModel.swift
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ enum SearchStatus {
case empty
}

enum PreviewDelegateStatus {
enum SchedulePreviewStatus {
case loaded
case loading
case error
Expand All @@ -34,10 +34,10 @@ extension SearchParentView {
@Published var scheduleForPreview: API.Types.Response.Schedule? = nil
@Published var scheduleListOfDays: [DayUiModel]? = nil
@Published var presentPreview: Bool = false
@Published var previewDelegateStatus: PreviewDelegateStatus = .loading
@Published var schedulePreviewStatus: SchedulePreviewStatus = .loading
@Published var school: School? = UserDefaults.standard.getDefaultSchool()
@Published var schedulePreviewIsSaved: Bool = false
@Published var courseColors: [String : String] = [:]
@Published var courseColors: CourseAndColorDict = [:]

private var client: API.Client = API.Client.shared

Expand All @@ -59,19 +59,48 @@ extension SearchParentView {
}
}

// Handles child modal status, [.loaded, .loading, .error, .empty]
func onLoadSchedule(programme: API.Types.Response.Programme) -> Void {
self.schedulePreviewIsSaved = false
self.schedulePreviewStatus = .loading
self.presentPreview = true
self.checkSavedSchedule(scheduleId: programme.id)
client.get(.schedule(scheduleId: programme.id, schoolId: String(school!.id))) { (result: Result<API.Types.Response.Schedule, API.Types.Error>) in
DispatchQueue.main.async {
switch result {
case .success(let result):
self.scheduleForPreview = result
self.scheduleListOfDays = result.days.toOrderedDays()
self.initCourseColors()
self.presentPreview = true
self.schedulePreviewStatus = .loaded
case .failure(_):
self.schedulePreviewStatus = .error
}
}
}
}

func getCourseColors() -> [String : [String : Color]] {
return courseColors.reduce(into: [:]) { (coursesAndColorsDict, course) in
let (courseId, color) = course
coursesAndColorsDict[courseId, default: [:]][color] = hexStringToUIColor(hex: color)
}
}


func onBookmark(courseColors: [String : [String : Color]]) -> Void {
// If the schedule isn't already saved in the local database
if !self.schedulePreviewIsSaved {
print("Saving schedule")
ScheduleStore.save(schedule: self.scheduleForPreview!) { result in
ScheduleStore.save(schedule: self.scheduleForPreview!) { scheduleResult in
DispatchQueue.main.async {
if case .failure(let error) = result {
if case .failure(let error) = scheduleResult {
fatalError(error.localizedDescription)
} else {
print("Saving schedule")
self.schedulePreviewIsSaved = true
CourseColorStore.save(newCourses: courseColors) { result in
if case .failure(let error) = result {
CourseColorStore.save(coursesAndColors: courseColors) { courseResult in
if case .failure(let error) = courseResult {
fatalError(error.localizedDescription)
} else {
print("Applying course colors ...")
Expand All @@ -91,6 +120,13 @@ extension SearchParentView {
} else {
print("Removed schedule")
self.schedulePreviewIsSaved = false
CourseColorStore.remove(removeCourses: self.scheduleForPreview!.courses()) { result in
if case .failure(let error) = result {
fatalError(error.localizedDescription)
} else {
print("Removed course colors ...")
}
}
}
}
}
Expand All @@ -103,9 +139,9 @@ extension SearchParentView {
client.get(.searchProgramme(searchQuery: searchQuery, schoolId: String(school!.id))) { (result: Result<API.Types.Response.Search, API.Types.Error>) in
DispatchQueue.main.async {
switch result {
case .success(let success):
case .success(let result):
self.status = SearchStatus.loading
self.parseSearchResults(success)
self.parseSearchResults(result)
case .failure( _):
self.status = SearchStatus.error
print("error")
Expand All @@ -123,27 +159,6 @@ extension SearchParentView {
self.searchBarText = ""
}

func onLoadSchedule(programme: API.Types.Response.Programme) -> Void {
self.schedulePreviewIsSaved = false
self.previewDelegateStatus = .loading
self.presentPreview = true
self.checkSavedSchedule(scheduleId: programme.id)
client.get(.schedule(scheduleId: programme.id, schoolId: String(school!.id))) { (result: Result<API.Types.Response.Schedule, API.Types.Error>) in
DispatchQueue.main.async {
switch result {
case .success(let schedule):
self.scheduleForPreview = schedule
self.scheduleListOfDays = schedule.days.toOrderedDays()
self.initCourseColors()
self.presentPreview = true
self.previewDelegateStatus = .loaded
case .failure(_):
self.previewDelegateStatus = .error
}
}
}
}

func initCourseColors() -> Void {
for day in self.scheduleListOfDays! {
for event in day.events {
Expand Down
21 changes: 6 additions & 15 deletions tumble-ios/Core/Views/AppNavigation/AppNavigatorView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -68,23 +68,10 @@ struct AppNavigatorView: View {
.navigationBarTitleDisplayMode(.inline)
.toolbar {
ToolbarItem(placement: .navigationBarLeading, content: {
Button(action: {
viewModel.onToggleDrawer()
}, label: {
Image(systemName: viewModel.menuOpened ? "xmark" : "line.3.horizontal")
.font(.system(size: 17))
.foregroundColor(Color("OnBackground"))
})
DrawerButtonView(onToggleDrawer: onToggleDrawer, menuOpened: viewModel.menuOpened)
})
ToolbarItem(placement: .navigationBarTrailing, content: {
NavigationLink(destination:
SearchParentView()
.navigationBarBackButtonHidden(true)
.navigationBarItems(leading: BackButton()), label: {
Image(systemName: "magnifyingglass")
.font(.system(size: 17))
.foregroundColor(Color("OnBackground"))
})
SearchButtonView()
})
ToolbarItemGroup(placement: .bottomBar, content: {
BottomBarView(onChangeTab: onChangeTab).environmentObject(viewModel)
Expand Down Expand Up @@ -125,6 +112,10 @@ struct AppNavigatorView: View {
func onChangeTab(tab: TabType) -> Void {
viewModel.onChangeTab(tab: tab)
}

func onToggleDrawer() -> Void {
viewModel.onToggleDrawer()
}
}


Expand Down
25 changes: 25 additions & 0 deletions tumble-ios/Core/Views/AppNavigation/DrawerButtonView.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
//
// DrawerButtonView.swift
// tumble-ios
//
// Created by Adis Veletanlic on 2023-01-26.
//

import SwiftUI

typealias OnToggleDrawer = () -> Void

struct DrawerButtonView: View {
let onToggleDrawer: OnToggleDrawer
let menuOpened: Bool
var body: some View {
Button(action: {
onToggleDrawer()
}, label: {
Image(systemName: menuOpened ? "xmark" : "line.3.horizontal")
.font(.system(size: 17))
.foregroundColor(Color("OnBackground"))
})
}
}

27 changes: 27 additions & 0 deletions tumble-ios/Core/Views/AppNavigation/SearchButtonView.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
//
// SearchButtonView.swift
// tumble-ios
//
// Created by Adis Veletanlic on 2023-01-26.
//

import SwiftUI

struct SearchButtonView: View {
var body: some View {
NavigationLink(destination:
SearchParentView()
.navigationBarBackButtonHidden(true)
.navigationBarItems(leading: BackButton()), label: {
Image(systemName: "magnifyingglass")
.font(.system(size: 17))
.foregroundColor(Color("OnBackground"))
})
}
}

struct SearchButtonView_Previews: PreviewProvider {
static var previews: some View {
SearchButtonView()
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ struct HomePageLinkOptionView: View {
HStack (spacing: 0) {
HStack {
Image(systemName: link.image)
.font(.system(size: 17))
.font(.largeIconFont)
.frame(width: 17, height: 17)
.padding(15)
.foregroundColor(Color("OnPrimary"))
Expand Down
Loading

0 comments on commit be8bb15

Please sign in to comment.