Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(meetings): Show upcoming events 📆 #1968

Merged
merged 5 commits into from
Feb 6, 2025
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions NextcloudTalk.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -391,6 +391,10 @@
2C1ABDCF257E939600AEDFB6 /* NCContact.m in Sources */ = {isa = PBXBuildFile; fileRef = 2C1ABDCD257E939600AEDFB6 /* NCContact.m */; };
2C1ABDD0257E939600AEDFB6 /* NCContact.m in Sources */ = {isa = PBXBuildFile; fileRef = 2C1ABDCD257E939600AEDFB6 /* NCContact.m */; };
2C1ABDE5257F883400AEDFB6 /* ABContact.m in Sources */ = {isa = PBXBuildFile; fileRef = 2C1ABDE4257F883400AEDFB6 /* ABContact.m */; };
2C1C68072D51229500A7F98A /* CalendarEvent.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2C1C68062D51229500A7F98A /* CalendarEvent.swift */; };
2C1C68082D51338400A7F98A /* CalendarEvent.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2C1C68062D51229500A7F98A /* CalendarEvent.swift */; };
2C1C68092D51338400A7F98A /* CalendarEvent.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2C1C68062D51229500A7F98A /* CalendarEvent.swift */; };
2C1C680A2D51338400A7F98A /* CalendarEvent.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2C1C68062D51229500A7F98A /* CalendarEvent.swift */; };
2C1D13A3253760EE00EC0533 /* LaunchScreen.xib in Resources */ = {isa = PBXBuildFile; fileRef = 2C1D13A1253760EE00EC0533 /* LaunchScreen.xib */; };
2C1EF36B25505DCE007C9768 /* NCNavigationController.m in Sources */ = {isa = PBXBuildFile; fileRef = 2C1EF36A25505DCE007C9768 /* NCNavigationController.m */; };
2C1EF36D25505DCE007C9768 /* NCNavigationController.m in Sources */ = {isa = PBXBuildFile; fileRef = 2C1EF36A25505DCE007C9768 /* NCNavigationController.m */; };
Expand Down Expand Up @@ -900,6 +904,7 @@
2C1ABDCD257E939600AEDFB6 /* NCContact.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = NCContact.m; sourceTree = "<group>"; };
2C1ABDE3257F883400AEDFB6 /* ABContact.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ABContact.h; sourceTree = "<group>"; };
2C1ABDE4257F883400AEDFB6 /* ABContact.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = ABContact.m; sourceTree = "<group>"; };
2C1C68062D51229500A7F98A /* CalendarEvent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CalendarEvent.swift; sourceTree = "<group>"; };
2C1D13A2253760EE00EC0533 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/LaunchScreen.xib; sourceTree = "<group>"; };
2C1EF36925505DCE007C9768 /* NCNavigationController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = NCNavigationController.h; sourceTree = "<group>"; };
2C1EF36A25505DCE007C9768 /* NCNavigationController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = NCNavigationController.m; sourceTree = "<group>"; };
Expand Down Expand Up @@ -2185,6 +2190,7 @@
1F2058292CEA404F00AAA673 /* AiSummaryViewController.swift */,
1F20582B2CEA405700AAA673 /* AiSummaryViewController.xib */,
1F205B9F2CEE1B8800AAA673 /* AiSummaryController.swift */,
2C1C68062D51229500A7F98A /* CalendarEvent.swift */,
);
name = Chat;
sourceTree = "<group>";
Expand Down Expand Up @@ -2872,6 +2878,7 @@
1FF136132BFB6FCD006A6101 /* RLMSupport.swift in Sources */,
1F77A5ED2AB9A408007B6037 /* NCChatMessage.m in Sources */,
1F77A5EB2AB9A3EE007B6037 /* BGTaskHelper.swift in Sources */,
2C1C680A2D51338400A7F98A /* CalendarEvent.swift in Sources */,
1F205C532CEF91C500AAA673 /* UserAbsence.swift in Sources */,
1FF136182BFB74D0006A6101 /* NCChatMessage.swift in Sources */,
1F77A5FC2AB9A4ED007B6037 /* NCRoom.m in Sources */,
Expand Down Expand Up @@ -3019,6 +3026,7 @@
1F1B50472B90CDF800B0F2F4 /* TalkCapabilities.m in Sources */,
2CA1CCD01F1E1779002FE6A2 /* SearchTableViewController.m in Sources */,
1F1C0D8929AFB89900D17C6D /* VLCKitVideoViewController.swift in Sources */,
2C1C68072D51229500A7F98A /* CalendarEvent.swift in Sources */,
2C9B0B98217F6DBA00A4752C /* NCNotificationController.m in Sources */,
2CC3166E2CC698E1007CBE16 /* TextFieldTableViewCell.swift in Sources */,
2C36A04A261487BC0026F04A /* DetailedOptionsSelectorTableViewController.m in Sources */,
Expand Down Expand Up @@ -3177,6 +3185,7 @@
2C62AFFD24C1BDA5007E460A /* NCMessageParameter.m in Sources */,
1F35F8EC2AEEBC1400044BDA /* UIScrollView+SLKAdditions.m in Sources */,
1FF136172BFB74CF006A6101 /* NCChatMessage.swift in Sources */,
2C1C68092D51338400A7F98A /* CalendarEvent.swift in Sources */,
2CF338E22CED388B0029CACC /* AvatarView.swift in Sources */,
1FF4DA8A2C0262BB00C1B952 /* NCBaseSessionManager.swift in Sources */,
2C62B00C24C1BDC1007E460A /* NCNotification.m in Sources */,
Expand Down Expand Up @@ -3259,6 +3268,7 @@
F644A2DF2CE28C8D00E2ED81 /* NCChatFileStatus.swift in Sources */,
2C1ABDCF257E939600AEDFB6 /* NCContact.m in Sources */,
2CC001DC24A37AD400A20167 /* NCAppBranding.m in Sources */,
2C1C68082D51338400A7F98A /* CalendarEvent.swift in Sources */,
2C4446D42658147900DF1DBC /* TalkAccount.m in Sources */,
1FDCC3E329EC787400DEB39B /* AvatarManager.swift in Sources */,
1FF4DA852C025DC000C1B952 /* NCAPISessionManager.swift in Sources */,
Expand Down
29 changes: 29 additions & 0 deletions NextcloudTalk/CalendarEvent.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
//
// SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors
// SPDX-License-Identifier: GPL-3.0-or-later
//

import Foundation

@objcMembers public class CalendarEvent: NSObject {
Ivansss marked this conversation as resolved.
Show resolved Hide resolved

public var calendarAppUrl: String
public var calendarUri: String
public var location: String
public var recurrenceId: String
public var start: Int
public var summary: String
public var uri: String

init(dictionary: [String: Any]) {
self.calendarAppUrl = dictionary["calendarAppUrl"] as? String ?? ""
self.calendarUri = dictionary["calendarUri"] as? String ?? ""
self.location = dictionary["location"] as? String ?? ""
self.recurrenceId = dictionary["recurrenceId"] as? String ?? ""
self.start = dictionary["start"] as? Int ?? -1
self.summary = dictionary["summary"] as? String ?? ""
self.uri = dictionary["uri"] as? String ?? ""

super.init()
Ivansss marked this conversation as resolved.
Show resolved Hide resolved
}
}
45 changes: 42 additions & 3 deletions NextcloudTalk/ChatViewController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ import SwiftyAttributes

private var lobbyCheckTimer: Timer?

// MARK: - Call buttons in NavigationBar
// MARK: - Buttons in NavigationBar

func getCallOptionsBarButton() -> BarButtonItemWithActivity {
let symbolConfiguration = UIImage.SymbolConfiguration(pointSize: 20)
Expand Down Expand Up @@ -158,6 +158,37 @@ import SwiftyAttributes
return callOptionsButton
}()

private lazy var upcomingEventsButton: BarButtonItemWithActivity = {
let symbolConfiguration = UIImage.SymbolConfiguration(pointSize: 20)
let buttonImage = UIImage(systemName: "calendar", withConfiguration: symbolConfiguration)
let upcomingEventsButton = BarButtonItemWithActivity(width: 50, with: buttonImage)

let deferredUpcomingEvents = UIDeferredMenuElement.uncached { [weak self] completion in
guard let self = self else { return }

NCAPIController.sharedInstance().upcomingEvents(self.room, forAccount: self.account) { events in
let actions: [UIAction]
if !events.isEmpty {
actions = events.map { event in
UIAction(title: event.summary, subtitle: NCUtils.eventTime(from: (TimeInterval(event.start))), handler: { _ in })
}
} else {
actions = [UIAction(title: NSLocalizedString("No upcoming events", comment: ""), attributes: .disabled, handler: { _ in })]
}

completion(actions)
}
}

upcomingEventsButton.innerButton.menu = UIMenu(children: [deferredUpcomingEvents])
upcomingEventsButton.innerButton.showsMenuAsPrimaryAction = true

upcomingEventsButton.accessibilityLabel = NSLocalizedString("Upcoming events", comment: "")
upcomingEventsButton.accessibilityHint = NSLocalizedString("Double tap to display upcoming events", comment: "")

return upcomingEventsButton
}()

private var messageExpirationTimer: Timer?

public override init?(forRoom room: NCRoom, withAccount account: TalkAccount) {
Expand Down Expand Up @@ -209,10 +240,18 @@ import SwiftyAttributes
public override func viewDidLoad() {
super.viewDidLoad()

var barButtonsItems: [UIBarButtonItem] = []
// Call options
if room.supportsCalling {
self.navigationItem.rightBarButtonItems = [callOptionsButton]
barButtonsItems.append(callOptionsButton)
}
// Upcoming events
if NCDatabaseManager.sharedInstance().serverHasTalkCapability(kCapabilityScheduleMeeting, forAccountId: room.accountId) {
barButtonsItems.append(upcomingEventsButton)
}

self.navigationItem.rightBarButtonItems = barButtonsItems

// No sharing options in federation v1
if room.isFederated {
// When hiding the button it is still respected in the layout constraints
Expand Down Expand Up @@ -291,7 +330,7 @@ import SwiftyAttributes
// Check if new messages were added while the app was inactive (eg. via background-refresh)
self.checkForNewStoredMessages()

if !self.offlineMode {
if !self.offlineMode {
NCRoomsManager.sharedInstance().joinRoom(self.room.token, forCall: false)
}

Expand Down
22 changes: 22 additions & 0 deletions NextcloudTalk/NCAPIControllerExtensions.swift
Original file line number Diff line number Diff line change
Expand Up @@ -680,4 +680,26 @@ import Foundation
completionBlock(message)
}
}

// MARK: - Upcoming events

public func upcomingEvents(_ room: NCRoom, forAccount account: TalkAccount, completionBlock: @escaping (_ events: [CalendarEvent]) -> Void) {
Ivansss marked this conversation as resolved.
Show resolved Hide resolved
guard let encodedRoomLink = room.linkURL?.addingPercentEncoding(withAllowedCharacters: .urlHostAllowed),
let apiSessionManager = self.apiSessionManagers.object(forKey: account.accountId) as? NCAPISessionManager
else {
completionBlock([])
return
}

let urlString = "\(account.server)/ocs/v2.php/apps/dav/api/v1/events/upcoming?location=\(encodedRoomLink)"

apiSessionManager.getOcs(urlString, account: account) { ocsResponse, error in
if error == nil, let events = ocsResponse?.dataDict?["events"] as? [[String: Any]] {
let calendarEvents = events.map { CalendarEvent(dictionary: $0) }
completionBlock(calendarEvents)
} else {
completionBlock([])
}
}
}
}
1 change: 1 addition & 0 deletions NextcloudTalk/NCDatabaseManager.h
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,7 @@ extern NSString * const kCapabilityCallNotificationState;
extern NSString * const kCapabilityCallForceMute;
extern NSString * const kCapabilityTalkPollsDrafts;
extern NSString * const kCapabilityEditDraftPoll;
extern NSString * const kCapabilityScheduleMeeting;

extern NSString * const kNotificationsCapabilityExists;
extern NSString * const kNotificationsCapabilityTestPush;
Expand Down
1 change: 1 addition & 0 deletions NextcloudTalk/NCDatabaseManager.m
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,7 @@
NSString * const kCapabilityForceMute = @"force-mute";
NSString * const kCapabilityTalkPollsDrafts = @"talk-polls-drafts";
NSString * const kCapabilityEditDraftPoll = @"edit-draft-poll";
NSString * const kCapabilityScheduleMeeting = @"schedule-meeting";

NSString * const kNotificationsCapabilityExists = @"exists";
NSString * const kNotificationsCapabilityTestPush = @"test-push";
Expand Down
46 changes: 46 additions & 0 deletions NextcloudTalk/NCUtils.swift
Original file line number Diff line number Diff line change
Expand Up @@ -247,6 +247,52 @@ import AVFoundation
return Calendar.current.date(byAdding: .day, value: (weekday - currentWeekday), to: date)!
}

public static func eventTime(from timestamp: TimeInterval) -> String {
Ivansss marked this conversation as resolved.
Show resolved Hide resolved
let eventDate = Date(timeIntervalSince1970: timestamp)
let calendar = Calendar.current
let now = Date()

if eventDate <= now {
return NSLocalizedString("Now", comment: "Indicates an event happening right now")
}

let timeFormatter = DateFormatter()
timeFormatter.timeStyle = .short
timeFormatter.locale = Locale.current

let timeString = timeFormatter.string(from: eventDate)

if calendar.isDateInToday(eventDate) {
let todayFormat = NSLocalizedString("Today at %@", comment: "Indicates an event happening today")
return String(format: todayFormat, timeString)
}

if calendar.isDateInTomorrow(eventDate) {
let tomorrowFormat = NSLocalizedString("Tomorrow at %@", comment: "Indicates an event happening tomorrow")
return String(format: tomorrowFormat, timeString)
}

if let nextWeek = calendar.date(byAdding: .day, value: 7, to: now),
eventDate < calendar.startOfDay(for: nextWeek) {
let weekdayFormatter = DateFormatter()
weekdayFormatter.dateFormat = "EEEE"
weekdayFormatter.locale = Locale.current

let weekdayString = weekdayFormatter.string(from: eventDate)
let weekdayFormat = NSLocalizedString("%@ at %@", comment: "Indicates an event happening on a specific day (e.g Monday at 10:00)")
return String(format: weekdayFormat, weekdayString, timeString)
}

let fullDateFormatter = DateFormatter()
fullDateFormatter.dateStyle = .medium
fullDateFormatter.locale = Locale.current

let dateString = fullDateFormatter.string(from: eventDate)
let dateFormat = NSLocalizedString("%@ at %@", comment: "Indicates an event happening on a specific day (e.g Monday at 10:00)")

return String(format: dateFormat, dateString, timeString)
}

// MARK: - Crypto utils

public static func sha1(fromString string: String) -> String {
Expand Down
21 changes: 21 additions & 0 deletions NextcloudTalk/en.lproj/Localizable.strings
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
/* Indicates an event happening on a specific day (e.g Monday at 10:00) */
"%@ at %@" = "%1$@ at %2$@";

/* No comment provided by engineer. */
"%@ invitation" = "%@ invitation";

Expand Down Expand Up @@ -817,6 +820,9 @@
/* No comment provided by engineer. */
"Double tap to display call options" = "Double tap to display call options";

/* No comment provided by engineer. */
"Double tap to display upcoming events" = "Double tap to display upcoming events";

/* No comment provided by engineer. */
"Double tap to edit profile" = "Double tap to edit profile";

Expand Down Expand Up @@ -1360,6 +1366,9 @@
/* No comment provided by engineer. */
"No shared items" = "No shared items";

/* No comment provided by engineer. */
"No upcoming events" = "No upcoming events";

/* No comment provided by engineer. */
"No user found" = "No user found";

Expand All @@ -1384,6 +1393,9 @@
/* No comment provided by engineer. */
"Notifications: %@" = "Notifications: %@";

/* Indicates an event happening right now */
"Now" = "Now";

/* No comment provided by engineer. */
"Off" = "Off";

Expand Down Expand Up @@ -1930,9 +1942,15 @@
/* No comment provided by engineer. */
"Today" = "Today";

/* Indicates an event happening today */
"Today at %@" = "Today at %@";

/* Remind me tomorrow about that message */
"Tomorrow" = "Tomorrow";

/* Indicates an event happening tomorrow */
"Tomorrow at %@" = "Tomorrow at %@";

/* TRANSLATORS this is for transcribing a voice message to text */
"Transcribe" = "Transcribe";

Expand Down Expand Up @@ -2011,6 +2029,9 @@
/* No comment provided by engineer. */
"Unread messages" = "Unread messages";

/* No comment provided by engineer. */
"Upcoming events" = "Upcoming events";

/* No comment provided by engineer. */
"Update" = "Update";

Expand Down
Loading