Skip to content
This repository has been archived by the owner on Jan 10, 2024. It is now read-only.

Commit

Permalink
Feature/roomfinder study rooms (#449)
Browse files Browse the repository at this point in the history
* Fix inconsistency on pakage updates, warnings and integrating studyRooms backend

* Fix calendar events details view

* Refactoring, improvements and merge conflict resolutions to integrade fully study rooms and RoomFinder

* Fix calendar day view on iPads

* Change Cancel button on selected event sheet to Done

* User customizable number of calendar week days

* Automatic scrolling on calendar mounth and list view

* Fix all build warnings

* Adding location and time hh:mm to events in kvkcalendar

* Remove the single vertical line on calendar day view

* Change the spcaes between horizontal map pics slide

Co-authored-by: Milen Vitanov <m.vitanov@tum.de>
  • Loading branch information
mvitanov and mvitanov authored Jun 16, 2022
1 parent 47a371a commit 0160433
Show file tree
Hide file tree
Showing 73 changed files with 2,284 additions and 470 deletions.
242 changes: 205 additions & 37 deletions Campus-iOS.xcodeproj/project.pbxproj

Large diffs are not rendered by default.

14 changes: 2 additions & 12 deletions Campus-iOS/App.swift
Original file line number Diff line number Diff line change
Expand Up @@ -90,21 +90,11 @@ struct CampusApp: App {
.navigationViewStyle(.stack)

NavigationView {
MapScreen(vm: MapViewModel(service: CafeteriasService()))
MapScreenView(vm: MapViewModel(cafeteriaService: CafeteriasService(), studyRoomsService: StudyRoomsService()))
}
.tag(3)
.tabItem {
Label("Cafeterias", systemImage: "house")
}
.navigationViewStyle(.stack)

NavigationView {
Text("Dummy StudyRooms View")
// StudyRoomsView(model: model)
}
.tag(4)
.tabItem {
Label("Study Rooms", systemImage: "book")
Label("Places", systemImage: "mappin.and.ellipse")
}
.navigationViewStyle(.stack)
}
Expand Down
6 changes: 3 additions & 3 deletions Campus-iOS/Base/Helpers/XMLSerializer.swift
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,9 @@ final class XMLSerializer: ResponseSerializer {
let emptyRequestMethods: Set<HTTPMethod>
let config: (XMLHashOptions) -> Void

public init(dataPreprocessor: DataPreprocessor = JSONResponseSerializer.defaultDataPreprocessor,
emptyResponseCodes: Set<Int> = JSONResponseSerializer.defaultEmptyResponseCodes,
emptyRequestMethods: Set<HTTPMethod> = JSONResponseSerializer.defaultEmptyRequestMethods,
public init(dataPreprocessor: DataPreprocessor = DecodableResponseSerializer<Data?>.defaultDataPreprocessor,
emptyResponseCodes: Set<Int> = DecodableResponseSerializer<Data?>.defaultEmptyResponseCodes,
emptyRequestMethods: Set<HTTPMethod> = DecodableResponseSerializer<Data?>.defaultEmptyRequestMethods,
config: @escaping (XMLHashOptions) -> Void = { config in config.detectParsingErrors = true }) {
self.dataPreprocessor = dataPreprocessor
self.emptyResponseCodes = emptyResponseCodes
Expand Down
81 changes: 81 additions & 0 deletions Campus-iOS/Base/Networking/TUMDevAppAPI.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
//
// TUMDevAppAPI.swift
// Campus-iOS
//
// Created by Milen Vitanov on 05.05.22.
//

import Foundation
import Alamofire
import CoreLocation

enum TUMDevAppAPI: URLRequestConvertible {
case room(roomNr: Int)
case rooms

static let baseURL = "https://www.devapp.it.tum.de"

var path: String {
switch self {
case .room, .rooms: return "iris/ris_api.php"
}
}

var method: HTTPMethod {
switch self {
default: return .get
}
}

static var requiresAuth: [String] = []

func asURLRequest() throws -> URLRequest {
let url = try TUMDevAppAPI.baseURL.asURL()
var urlRequest = try URLRequest(url: url.appendingPathComponent(path), method: method)

switch self {
case .room(let roomNr):
urlRequest = try URLEncoding.default.encode(urlRequest, with: ["format": "json", "raum": roomNr])
case .rooms:
urlRequest = try URLEncoding.default.encode(urlRequest, with: ["format": "json"])
}

return urlRequest
}

// Maximum size of cache: 500kB, Maximum cache entries: 1000, Lifetime: 10min
static let cache = Cache<String, Decodable>(totalCostLimit: 500_000, countLimit: 1_000, entryLifetime: 10 * 60)

static func fetchStudyRooms(forcedRefresh: Bool) async throws -> StudyRoomApiRespose {

let fullRequestURL = baseURL + self.rooms.path

if !forcedRefresh, let rawStudyRoomsResponse = cache.value(forKey: baseURL + self.rooms.path), let studyRoomsResponse = rawStudyRoomsResponse as? StudyRoomApiRespose {
print("Study rooms data from cache")
return studyRoomsResponse
} else {
print("Study rooms data from server")
// Fetch new data and store in cache.
var studyRoomsData: Data
do {
studyRoomsData = try await AF.request(self.rooms).serializingData().value
} catch {
print(error)
throw NetworkingError.deviceIsOffline
}

var studyRoomsResponse = StudyRoomApiRespose()
do {
studyRoomsResponse = try JSONDecoder().decode(StudyRoomApiRespose.self, from: studyRoomsData)
} catch {
print(error)
throw error
}

// Write value to cache
cache.setValue(studyRoomsResponse, forKey: fullRequestURL)

return studyRoomsResponse
}
}
}
26 changes: 24 additions & 2 deletions Campus-iOS/CalendarComponent/Entity/CalendarEvent.swift
Original file line number Diff line number Diff line change
Expand Up @@ -54,8 +54,7 @@ struct CalendarEvent: Identifiable, Equatable, Entity {

var kvkEvent: Event {
var event = Event(ID: self.id.description)
event.text = self.title ?? ""
event.textForList = self.title ?? ""

event.start = self.startDate ?? Date()
event.end = self.endDate ?? Date()

Expand All @@ -73,13 +72,22 @@ struct CalendarEvent: Identifiable, Equatable, Entity {
default: return UIColor(red: 34/255, green: 126/255, blue: 177/255, alpha: 1)
}
}

let titleWithTime = (self.title ?? "") + " (\(Self.dateFormatter.string(from: event.start)) - \(Self.dateFormatter.string(from: event.end)))"
var titleWithTimeAndLocation = titleWithTime

// TODO: attributedTitle in data does not work
let attributedTitle = NSMutableAttributedString(string: title ?? "").font(.systemFont(ofSize: 12, weight: .bold)).color(event.textColor)
if let location = self.location {
titleWithTimeAndLocation += "\n\n\(location)"
let attributedLocation = NSMutableAttributedString(string: location).font(.systemFont(ofSize: 12, weight: .regular)).color(event.textColor)
attributedTitle.append(NSAttributedString(string: "\n"))
attributedTitle.append(attributedLocation)
}

let textEvent = TextEvent(timeline: titleWithTimeAndLocation, month: "", list: titleWithTime)
event.title = textEvent

if let description = self.descriptionText {
let attributedLocation = NSMutableAttributedString(string: description).font(.systemFont(ofSize: 12, weight: .regular)).color(secondaryUIColor)
attributedTitle.append(NSAttributedString(string: "\n\n"))
Expand All @@ -90,6 +98,13 @@ struct CalendarEvent: Identifiable, Equatable, Entity {
return event
}

var lvNr: String? {
if let url = self.url?.description, let range = url.range(of: "LvNr=") {
return String(url[range.upperBound...])
}
return nil
}

init(
id: Int64,
status: String? = nil,
Expand Down Expand Up @@ -131,6 +146,13 @@ struct CalendarEvent: Identifiable, Equatable, Entity {
self.endDate = endDate
self.location = location
}

private static let dateFormatter: DateFormatter = {
let formatter = DateFormatter()
formatter.locale = Locale.current
formatter.dateFormat = "HH:mm"
return formatter
}()
}


Expand Down
20 changes: 15 additions & 5 deletions Campus-iOS/CalendarComponent/Entity/TumCalendarStyle.swift
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,8 @@ import KVKCalendar
struct TumCalendarStyle {
@available(*, unavailable) private init() {}

static func getStyle(type: CalendarType) -> Style {
static func getStyle(type: CalendarType, calendarWeekDays: UInt) -> Style {

var style = Style()
if UIDevice.current.userInterfaceIdiom == .phone {
style.timeline.widthTime = 40
Expand All @@ -23,12 +24,13 @@ struct TumCalendarStyle {
style.headerScroll.heightHeaderWeek = 70
style.headerScroll.titleDateFont = .boldSystemFont(ofSize: 16)
} else {
style.timeline.widthEventViewer = 350
style.timeline.widthEventViewer = 0
style.headerScroll.fontNameDay = .systemFont(ofSize: 20)
}

// Event
style.event.showRecurringEventInPast = true
style.event.isEnableVisualSelect = false
style.event.states = [.none]
if #available(iOS 13.0, *) {
style.event.iconFile = UIImage(systemName: "paperclip")
Expand All @@ -48,6 +50,14 @@ struct TumCalendarStyle {

// Day
style.allDay.backgroundColor = .systemBackground
if type == .day {
style.week.showVerticalDayDivider = false
}

// Week
if type == .week {
style.week.daysInOneWeek = calendarWeekDays
}

// Month
style.month.isHiddenEventTitle = false
Expand Down Expand Up @@ -89,9 +99,9 @@ struct TumCalendarStyle {
newStyle.headerScroll.colorNameEmptyDay = UIColor.useForStyle(dark: .systemGray6,
white: newStyle.headerScroll.colorNameEmptyDay)
// newStyle.headerScroll.colorBackground = UIColor.useForStyle(dark: .black, white: newStyle.headerScroll.colorBackground)
newStyle.headerScroll.colorTitleDate = UIColor.useForStyle(dark: .white, white: newStyle.headerScroll.colorTitleDate)
newStyle.headerScroll.colorTitleCornerDate = UIColor.useForStyle(dark: .systemRed,
white: newStyle.headerScroll.colorTitleCornerDate)
newStyle.headerScroll.titleDateColor = UIColor.useForStyle(dark: .white, white: newStyle.headerScroll.titleDateColor)
newStyle.headerScroll.titleDateColorCorner = UIColor.useForStyle(dark: .systemRed,
white: newStyle.headerScroll.titleDateColorCorner)
newStyle.headerScroll.colorDate = UIColor.useForStyle(dark: .white, white: newStyle.headerScroll.colorDate)
newStyle.headerScroll.colorNameDay = UIColor.useForStyle(dark: .white, white: newStyle.headerScroll.colorNameDay)
/*newStyle.headerScroll.colorCurrentDate = UIColor.useForStyle(dark: .systemGray6,
Expand Down
27 changes: 15 additions & 12 deletions Campus-iOS/CalendarComponent/Views/CalendarContentView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -11,17 +11,14 @@ import KVKCalendar
struct CalendarContentView: View {

@EnvironmentObject private var model: Model
@AppStorage("calendarWeekDays") var calendarWeekDays: Int = 7

@State var selectedType: CalendarType = .week
@State var selectedEventID: String?
@State var isTodayPressed: Bool = false

@ObservedObject var viewModel: CalendarViewModel


init() {
self.viewModel = CalendarViewModel()
}
@ObservedObject var viewModel = CalendarViewModel()


var body: some View {
VStack{
Expand All @@ -34,29 +31,35 @@ struct CalendarContentView: View {
events: self.viewModel.events.map({ $0.kvkEvent }),
type: .week,
selectedEventID: self.$selectedEventID,
frame: Self.getSafeAreaFrame(geometry: geo), todayPressed: self.$isTodayPressed)
frame: Self.getSafeAreaFrame(geometry: geo), todayPressed: self.$isTodayPressed, calendarWeekDays: UInt(calendarWeekDays))
case .day:
CalendarDisplayView(
events: self.viewModel.events.map({ $0.kvkEvent }),
type: .day,
selectedEventID: self.$selectedEventID,
frame: Self.getSafeAreaFrame(geometry: geo), todayPressed: self.$isTodayPressed)
frame: Self.getSafeAreaFrame(geometry: geo), todayPressed: self.$isTodayPressed, calendarWeekDays: UInt(calendarWeekDays))
case .month:
CalendarDisplayView(
events: self.viewModel.events.map({ $0.kvkEvent }),
type: .month,
selectedEventID: self.$selectedEventID,
frame: Self.getSafeAreaFrame(geometry: geo), todayPressed: self.$isTodayPressed)
frame: Self.getSafeAreaFrame(geometry: geo), todayPressed: self.$isTodayPressed, calendarWeekDays: UInt(calendarWeekDays))
default:
EmptyView()
}
}
}
.sheet(item: self.$selectedEventID) { eventId in
let chosenEvent = self.viewModel.events
.first(where: { $0.id.description == eventId })
CalendarSingleEventView(
model: self.model,
event: self.viewModel.events
.first(where: { $0.id.description == eventId })
viewModel: LectureDetailsViewModel(
model: model,
service: LectureDetailsService(),
// Yes, it is a really hacky solution...
lecture: Lecture(id: UInt64(chosenEvent?.lvNr ?? "") ?? 0, lvNumber: UInt64(chosenEvent?.lvNr ?? "") ?? 0, title: "", duration: "", stp_sp_sst: "", eventTypeDefault: "", eventTypeTag: "", semesterYear: "", semesterType: "", semester: "", semesterID: "", organisationNumber: 0, organisation: "", organisationTag: "", speaker: "")
),
event: chosenEvent
)
}
.toolbar {
Expand Down
12 changes: 7 additions & 5 deletions Campus-iOS/CalendarComponent/Views/CalendarDisplayView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -18,18 +18,20 @@ struct CalendarDisplayView: UIViewRepresentable {
private var calendar: CalendarView
var selectDate: Date?

init(events: [Event], type: CalendarType, selectedEventID: Binding<String?>, frame: CGRect, todayPressed: Binding<Bool>) {
init(events: [Event], type: CalendarType, selectedEventID: Binding<String?>, frame: CGRect, todayPressed: Binding<Bool>, calendarWeekDays: UInt) {
self.events = events
self._todayPressed = todayPressed
self._selectedEventID = selectedEventID
self.calendar = CalendarView(frame: frame, style: TumCalendarStyle.getStyle(type: type))
self.calendar = CalendarView(frame: frame, style: TumCalendarStyle.getStyle(type: type, calendarWeekDays: calendarWeekDays))
}

func makeUIView(context: UIViewRepresentableContext<CalendarDisplayView>) -> CalendarView {
calendar.dataSource = context.coordinator
calendar.delegate = context.coordinator
calendar.scrollTo(Date(), animated: true)
calendar.reloadData()
DispatchQueue.main.async {
calendar.scrollTo(Date(), animated: true)
calendar.reloadData()
}
return calendar
}

Expand Down Expand Up @@ -89,7 +91,7 @@ struct CalendarDisplayView: UIViewRepresentable {
}

self.selectedDate = date
if let firstEvent = events.filter( {$0.start.startOfDay == date.startOfDay} ).sorted(by: {
if let firstEvent = events.filter( {$0.start.kvkStartOfDay == date.kvkStartOfDay} ).sorted(by: {
return $0.start > $1.start
}).first {
view.calendar.scrollTo(firstEvent.start, animated: true)
Expand Down
30 changes: 8 additions & 22 deletions Campus-iOS/CalendarComponent/Views/CalendarSingleEventView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -16,26 +16,6 @@ struct CalendarSingleEventView: View {
@StateObject var viewModel: LectureDetailsViewModel
@State var event: CalendarEvent?

init(model: Model, event: CalendarEvent?) {
var lvNr: String? {
if let url = event?.url?.description, let range = url.range(of: "LvNr=") {
return String(url[range.upperBound...])
}

return nil
}

self._viewModel = StateObject(wrappedValue:
LectureDetailsViewModel(
model: model,
service: LectureDetailsService(),
// Yes, it is a really hacky solution...
lecture: Lecture(id: UInt64(lvNr ?? "") ?? 0, lvNumber: UInt64(lvNr ?? "") ?? 0, title: "", duration: "", stp_sp_sst: "", eventTypeDefault: "", eventTypeTag: "", semesterYear: "", semesterType: "", semester: "", semesterID: "", organisationNumber: 0, organisation: "", organisationTag: "", speaker: "")
)
)
self.event = event
}

var body: some View {
NavigationView {
VStack {
Expand Down Expand Up @@ -67,7 +47,7 @@ struct CalendarSingleEventView: View {
.navigationBarTitleDisplayMode(.inline)
.toolbar {
Button(action: {self.presentationMode.wrappedValue.dismiss()}) {
Text("Cancel").bold()
Text("Done").bold()
}
}
}
Expand All @@ -77,8 +57,14 @@ struct CalendarSingleEventView: View {
struct CalendarSingleEventView_Previews: PreviewProvider {

static var event = CalendarEvent(id: 0, title: "Test Event")
static var viewModel = LectureDetailsViewModel(
model: MockModel(),
service: LectureDetailsService(),
// Yes, it is a really hacky solution...
lecture: Lecture(id: UInt64("1") ?? 0, lvNumber: UInt64("1") ?? 0, title: "", duration: "", stp_sp_sst: "", eventTypeDefault: "", eventTypeTag: "", semesterYear: "", semesterType: "", semester: "", semesterID: "", organisationNumber: 0, organisation: "", organisationTag: "", speaker: "")
)

static var previews: some View {
CalendarSingleEventView(model: MockModel(), event: event)
CalendarSingleEventView(viewModel: viewModel, event: event)
}
}
2 changes: 1 addition & 1 deletion Campus-iOS/CalendarComponent/Views/CalendarToolbar.swift
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ struct CalendarToolbar: View {
NavigationLink(destination:
VStack{
GeometryReader { geo in
CalendarDisplayView(events: viewModel.events.map({ $0.kvkEvent }), type: .list, selectedEventID: self.$selectedEventID, frame: CalendarContentView.getSafeAreaFrame(geometry: geo), todayPressed: self.$isTodayPressed)
CalendarDisplayView(events: viewModel.events.map({ $0.kvkEvent }), type: .list, selectedEventID: self.$selectedEventID, frame: CalendarContentView.getSafeAreaFrame(geometry: geo), todayPressed: self.$isTodayPressed, calendarWeekDays: 7)
.navigationBarTitle(Text("Events"))
}
}
Expand Down
Loading

0 comments on commit 0160433

Please sign in to comment.