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 all 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
57 changes: 57 additions & 0 deletions NextcloudTalk/CalendarEvent.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
//
// SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors
// SPDX-License-Identifier: GPL-3.0-or-later
//

import Foundation

struct CalendarEvent {

var calendarAppUrl: String
var calendarUri: String
var location: String
var recurrenceId: String
var start: Int
var summary: String
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 ?? ""
}

func readableStartTime() -> String {
let eventDate = Date(timeIntervalSince1970: TimeInterval(start))
let now = Date()

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

// Event happening following days (except today or tomorrow)
let calendar = Calendar.current
if let nextWeek = calendar.date(byAdding: .day, value: 7, to: now),
!calendar.isDateInToday(eventDate), !calendar.isDateInTomorrow(eventDate),
eventDate < calendar.startOfDay(for: nextWeek) {
return eventDate.formatted(
.dateTime
.weekday(.wide)
.hour(.conversationalTwoDigits(amPM: .wide))
.minute(.defaultDigits))
}

// Event happening today, tomorrow or later than a week
let dateFormatter = DateFormatter()
dateFormatter.dateStyle = .medium
dateFormatter.timeStyle = .short
dateFormatter.doesRelativeDateFormatting = true

return dateFormatter.string(from: eventDate)
}
}
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 { [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: event.readableStartTime(), 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
23 changes: 23 additions & 0 deletions NextcloudTalk/NCAPIControllerExtensions.swift
Original file line number Diff line number Diff line change
Expand Up @@ -680,4 +680,27 @@ import Foundation
completionBlock(message)
}
}

// MARK: - Upcoming events

@nonobjc
func upcomingEvents(_ room: NCRoom, forAccount account: TalkAccount, completionBlock: @escaping (_ events: [CalendarEvent]) -> Void) {
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
12 changes: 12 additions & 0 deletions NextcloudTalk/en.lproj/Localizable.strings
Original file line number Diff line number Diff line change
Expand Up @@ -817,6 +817,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 +1363,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 +1390,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 @@ -2011,6 +2020,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