Skip to content

Commit

Permalink
Communication: Mention content (#107)
Browse files Browse the repository at this point in the history
* Margins for content

* Parse mention

* Add navigation titles

* Create SendMessageMentionContentView

* Display all results if query is empty

* Create LectureService

* View lecture, lecture unit, and slide

* Select lecture unit

* Select lecture

* Select slide
  • Loading branch information
nityanandaz committed Jun 3, 2024
1 parent 535407c commit fdc6945
Show file tree
Hide file tree
Showing 15 changed files with 335 additions and 69 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,11 @@
"lecturesUnavailable" = "No Lectures";
"membersUnavailable" = "No Members";
"messageAction" = "Message %@";
"mentionSlideNumber" = "Slide %i";

// MARK: SendMessageMentionContentView
"members" = "Members";
"mention" = "Mention";

// MARK: ReactionsView
"emojis" = "Emojis";
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
//
// LectureService.swift
//
//
// Created by Nityananda Zbil on 30.05.24.
//

import Common
import SharedModels

protocol LectureService {
func getLecturesWithSlides(courseId: Int) async -> DataState<[Lecture]>
}

enum LectureServiceFactory {
static let shared: LectureService = LectureServiceImpl()
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
//
// LectureServiceImpl.swift
//
//
// Created by Nityananda Zbil on 30.05.24.
//

import APIClient
import Common
import SharedModels

class LectureServiceImpl: LectureService {

let client = APIClient()

struct GetLecturesWithSlidesRequest: APIRequest {
typealias Response = [Lecture]

let courseId: Int

var method: HTTPMethod {
.get
}

var resourceName: String {
"api/courses/\(courseId)/lectures-with-slides"
}
}

func getLecturesWithSlides(courseId: Int) async -> DataState<[Lecture]> {
let result = await client.sendRequest(GetLecturesWithSlidesRequest(courseId: courseId))

switch result {
case let .success((response, _)):
return .done(response: response)
case let .failure(error):
return .failure(error: UserFacingError(error: error))
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,16 +8,26 @@
import Foundation

enum MentionScheme {
case attachment(Int)
case channel(Int64)
case exercise(Int)
case lecture(Int)
case lectureUnit
case member(String)
case message(Int)
case slide

init?(_ url: URL) {
guard url.scheme == "mention" else {
return nil
}
switch url.host() {
case "attachment":
// E.g., mention://attachment/lecture/3/LectureAttachment_2024-05-24T21-05-08-351_d37182b7.png
if url.pathComponents.count >= 3, let lectureId = Int(url.pathComponents[3]) {
self = .attachment(lectureId)
return
}
case "channel":
if let id = Int64(url.lastPathComponent) {
self = .channel(id)
Expand All @@ -33,9 +43,20 @@ enum MentionScheme {
self = .lecture(id)
return
}
case "lecture-unit":
// E.g., mention://lecture-unit/attachment-unit/7/AttachmentUnit_2024-05-24T21-12-25-915_Inheritance__part_1_.pdf
self = .lectureUnit
case "member":
self = .member(url.lastPathComponent)
return
case "message":
// E.g., mention://message/1
if let id = Int(url.lastPathComponent) {
self = .message(id)
}
case "slide":
// E.g., mention://slide/attachment-unit/10/slide/1
self = .slide
default:
return nil
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
//
// SendMessageLecturePickerViewModel.swift
//
//
// Created by Nityananda Zbil on 30.05.24.
//

import SharedModels
import SwiftUI

@Observable
@MainActor
final class SendMessageLecturePickerViewModel {

let course: Course
var lectureUnits: [LectureUnit]

private let delegate: SendMessageMentionContentDelegate
private let lectureService: LectureService

init(
course: Course,
lectureUnits: [LectureUnit] = [],
delegate: SendMessageMentionContentDelegate,
lectureService: LectureService = LectureServiceFactory.shared
) {
self.course = course
self.lectureUnits = lectureUnits
self.delegate = delegate
self.lectureService = lectureService
}

func task() async {
let lectures = await lectureService.getLecturesWithSlides(courseId: course.id)

if case let .done(lectures) = lectures,
let lecture = lectures.first,
let lectureUnits = lecture.lectureUnits {
self.lectureUnits = lectureUnits
}
}

func select(lecture: Lecture) {
if let title = lecture.title {
delegate.pickerDidSelect("[lecture]\(title)(/courses/\(course.id)/lectures/\(lecture.id))[/lecture]")
}
}

func select(lectureUnit: LectureUnit) {
if let name = lectureUnit.baseUnit.name,
case let .attachment(attachment) = lectureUnit,
case let .file(file) = attachment.attachment,
let link = file.link,
let url = URL(string: link),
url.pathComponents.count >= 7 {
let path = url.pathComponents[4...]
let id = path.joined(separator: "/")

delegate.pickerDidSelect("[lecture-unit]\(name)(\(id))[/lecture-unit]")
}
}

func select(lectureUnit: LectureUnit, slide: Slide) {
if let name = lectureUnit.baseUnit.name,
let slideNumber = slide.slideNumber,
let slideImagePath = slide.slideImagePath,
let url = URL(string: slideImagePath),
url.pathComponents.count >= 9 {
let path = url.pathComponents[4...7]
let id = path.joined(separator: "/")

delegate.pickerDidSelect("[slide]\(name) Slide \(slideNumber)(\(id))[/slide]")
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ final class SendMessageMentionChannelViewModel {

func search(idOrName: String) async {
let channels = await messagesService.getChannelsPublicOverview(for: course.id)
if case let .done(channels) = channels {
if case let .done(channels) = channels, !idOrName.isEmpty {
let filtered = channels.filter { channel in
let range = channel.name.range(of: idOrName, options: [.caseInsensitive, .diacriticInsensitive])
return range != nil
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
//
// SendMessageMentionContentDelegate.swift
//
//
// Created by Nityananda Zbil on 30.05.24.
//

struct SendMessageMentionContentDelegate {
var pickerDidSelect: (_ mention: String) -> Void
}
Original file line number Diff line number Diff line change
Expand Up @@ -23,15 +23,6 @@ extension SendMessageViewModel {
case memberPicker
case channelPicker
}

enum ModalPresentation: Identifiable {
case exercisePicker
case lecturePicker

var id: Self {
self
}
}
}

@MainActor
Expand Down Expand Up @@ -78,7 +69,7 @@ final class SendMessageViewModel {
var isMemberPickerSuppressed = false
var isChannelPickerSuppressed = false

var modalPresentation: ModalPresentation?
var isMentionContentViewPresented = false

// MARK: Life cycle

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -199,18 +199,26 @@ private extension MessageCell {
if let mention = MentionScheme(url) {
let coursePath = CoursePath(course: conversationViewModel.course)
switch mention {
case let .attachment(id):
navigationController.path.append(LecturePath(id: id, coursePath: coursePath))
case let .channel(id):
navigationController.path.append(ConversationPath(id: id, coursePath: coursePath))
case let .exercise(id):
navigationController.path.append(ExercisePath(id: id, coursePath: coursePath))
case let .lecture(id):
navigationController.path.append(LecturePath(id: id, coursePath: coursePath))
case let .lectureUnit:
break
case let .member(login):
Task {
if let conversation = await viewModel.getOneToOneChatOrCreate(login: login) {
navigationController.path.append(ConversationPath(conversation: conversation, coursePath: coursePath))
}
}
case let .message(id):
break
case let .slide:
break
}
return .handled
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -132,7 +132,7 @@ public struct MessagesAvailableView: View {
ScrollView {
CodeOfConductView(course: viewModel.course)
}
.padding()
.contentMargins(.l, for: .scrollContent)
.navigationTitle(R.string.localizable.codeOfConduct())
.navigationBarTitleDisplayMode(.inline)
.toolbar {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ public struct MessagesTabView: View {
Spacer()
}
}
.padding()
.contentMargins(.l, for: .scrollContent)
}
}
.task {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,30 +10,32 @@ import SwiftUI

struct SendMessageExercisePicker: View {

@Environment(\.dismiss) var dismiss

@Binding var text: String
let delegate: SendMessageMentionContentDelegate

let course: Course

var body: some View {
if let exercises = course.exercises, !exercises.isEmpty {
List(exercises) { exercise in
if let title = exercise.baseExercise.title {
Button(title) {
appendMarkdown(for: exercise)
dismiss()
Group {
if let exercises = course.exercises, !exercises.isEmpty {
List(exercises) { exercise in
if let title = exercise.baseExercise.title {
Button(title) {
selectMention(for: exercise)
}
}
}
.listStyle(.plain)
} else {
ContentUnavailableView(R.string.localizable.exercisesUnavailable(), systemImage: "magnifyingglass")
}
} else {
ContentUnavailableView(R.string.localizable.exercisesUnavailable(), systemImage: "magnifyingglass")
}
.navigationTitle("Exercises")
.navigationBarTitleDisplayMode(.inline)
}
}

private extension SendMessageExercisePicker {
func appendMarkdown(for exercise: Exercise) {
func selectMention(for exercise: Exercise) {
let type: String?
switch exercise {
case .fileUpload:
Expand All @@ -54,6 +56,6 @@ private extension SendMessageExercisePicker {
return
}

text.append("[\(type)]\(title)(/courses/\(course.id)/exercises/\(exercise.id))[/\(type)]")
delegate.pickerDidSelect("[\(type)]\(title)(/courses/\(course.id)/exercises/\(exercise.id))[/\(type)]")
}
}
Loading

0 comments on commit fdc6945

Please sign in to comment.