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(polls): Allow editing and deleting poll drafts #1965

Merged
merged 1 commit into from
Jan 31, 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
3 changes: 2 additions & 1 deletion NextcloudTalk/NCAPIController.h
Original file line number Diff line number Diff line change
Expand Up @@ -208,8 +208,9 @@ extern NSInteger const kReceivedChatMessagesLimit;

// Polls Controller
- (NSURLSessionDataTask *)createPollWithQuestion:(NSString *)question options:(NSArray *)options resultMode:(NCPollResultMode)resultMode maxVotes:(NSInteger)maxVotes inRoom:(NSString *)token asDraft:(BOOL)asDraft forAccount:(TalkAccount *)account withCompletionBlock:(PollCompletionBlock)block;
- (NSURLSessionDataTask *)getPollDraftsInRoom:(NSString *)token forAccount:(TalkAccount *)account withCompletionBlock:(PollDraftsCompletionBlock)block;
- (NSURLSessionDataTask *)getPollWithId:(NSInteger)pollId inRoom:(NSString *)token forAccount:(TalkAccount *)account withCompletionBlock:(PollCompletionBlock)block;
- (NSURLSessionDataTask *)editPollDraftWithId:(NSInteger)draftId question:(NSString *)question options:(NSArray *)options resultMode:(NCPollResultMode)resultMode maxVotes:(NSInteger)maxVotes inRoom:(NSString *)token forAccount:(TalkAccount *)account withCompletionBlock:(PollCompletionBlock)block;
- (NSURLSessionDataTask *)getPollDraftsInRoom:(NSString *)token forAccount:(TalkAccount *)account withCompletionBlock:(PollDraftsCompletionBlock)block;
- (NSURLSessionDataTask *)voteOnPollWithId:(NSInteger)pollId inRoom:(NSString *)token withOptions:(NSArray *)options forAccount:(TalkAccount *)account withCompletionBlock:(PollCompletionBlock)block;
- (NSURLSessionDataTask *)closePollWithId:(NSInteger)pollId inRoom:(NSString *)token forAccount:(TalkAccount *)account withCompletionBlock:(PollCompletionBlock)block;

Expand Down
30 changes: 30 additions & 0 deletions NextcloudTalk/NCAPIController.m
Original file line number Diff line number Diff line change
Expand Up @@ -1646,6 +1646,36 @@ - (NSURLSessionDataTask *)getPollWithId:(NSInteger)pollId inRoom:(NSString *)tok
return task;
}

- (NSURLSessionDataTask *)editPollDraftWithId:(NSInteger)draftId question:(NSString *)question options:(NSArray *)options resultMode:(NCPollResultMode)resultMode maxVotes:(NSInteger)maxVotes inRoom:(NSString *)token forAccount:(TalkAccount *)account withCompletionBlock:(PollCompletionBlock)block
{
NSString *encodedToken = [token stringByAddingPercentEncodingWithAllowedCharacters:[NSCharacterSet URLHostAllowedCharacterSet]];
NSString *endpoint = [NSString stringWithFormat:@"poll/%@/draft/%ld", encodedToken, (long)draftId];
NSInteger pollsAPIVersion = [self pollsAPIVersionForAccount:account];
NSString *URLString = [self getRequestURLForEndpoint:endpoint withAPIVersion:pollsAPIVersion forAccount:account];
NSDictionary *parameters = @{@"question" : question,
@"options" : options,
@"resultMode" : @(resultMode),
@"maxVotes" : @(maxVotes)
};
NCAPISessionManager *apiSessionManager = [_apiSessionManagers objectForKey:account.accountId];
NSURLSessionDataTask *task = [apiSessionManager POST:URLString parameters:parameters progress:nil success:^(NSURLSessionDataTask * _Nonnull task, id _Nullable responseObject) {
NSDictionary *pollDict = [[responseObject objectForKey:@"ocs"] objectForKey:@"data"];
NCPoll *poll = [NCPoll initWithPollDictionary:pollDict];
if (block) {
NSHTTPURLResponse *httpResponse = (NSHTTPURLResponse *)task.response;
block(poll, nil, httpResponse.statusCode);
}
} failure:^(NSURLSessionDataTask * _Nullable task, NSError * _Nonnull error) {
NSInteger statusCode = [self getResponseStatusCode:task.response];
[self checkResponseStatusCode:statusCode forAccount:account];
if (block) {
block(nil, error, statusCode);
}
}];

return task;
}

- (NSURLSessionDataTask *)getPollDraftsInRoom:(NSString *)token forAccount:(TalkAccount *)account withCompletionBlock:(PollDraftsCompletionBlock)block
{
NSString *encodedToken = [token stringByAddingPercentEncodingWithAllowedCharacters:[NSCharacterSet URLHostAllowedCharacterSet]];
Expand Down
1 change: 1 addition & 0 deletions NextcloudTalk/NCDatabaseManager.h
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,7 @@ extern NSString * const kCapabilityArchivedConversationsV2;
extern NSString * const kCapabilityCallNotificationState;
extern NSString * const kCapabilityCallForceMute;
extern NSString * const kCapabilityTalkPollsDrafts;
extern NSString * const kCapabilityEditDraftPoll;

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 @@ -80,6 +80,7 @@
NSString * const kCapabilityCallNotificationState = @"call-notification-state-api";
NSString * const kCapabilityForceMute = @"force-mute";
NSString * const kCapabilityTalkPollsDrafts = @"talk-polls-drafts";
NSString * const kCapabilityEditDraftPoll = @"edit-draft-poll";

NSString * const kNotificationsCapabilityExists = @"exists";
NSString * const kNotificationsCapabilityTestPush = @"test-push";
Expand Down
89 changes: 72 additions & 17 deletions NextcloudTalk/PollCreationViewController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import UIKit
let kQuestionTextFieldTag = 9999

var room: NCRoom
var editingDraftId: Int?
var draftsAvailable: Bool = false
var question: String = ""
var options: [String] = ["", ""]
Expand Down Expand Up @@ -58,7 +59,7 @@ import UIKit
self.navigationController?.navigationBar.tintColor = NCAppBranding.themeTextColor()
self.navigationController?.navigationBar.barTintColor = NCAppBranding.themeColor()
self.navigationController?.navigationBar.isTranslucent = false
self.navigationItem.title = NSLocalizedString("New poll", comment: "")
self.setNavigationBarTitle()

let appearance = UINavigationBarAppearance()
appearance.configureWithOpaqueBackground()
Expand All @@ -70,8 +71,8 @@ import UIKit

self.tableView.isEditing = true

// Set footer buttons
self.tableView.tableFooterView = pollFooterView()
// Configure footer buttons
configureFooterButtons()

self.navigationItem.leftBarButtonItem = UIBarButtonItem(barButtonSystemItem: .cancel, target: self, action: #selector(self.cancelButtonPressed))
self.navigationItem.leftBarButtonItem?.tintColor = NCAppBranding.themeTextColor()
Expand All @@ -98,31 +99,61 @@ import UIKit
present(navController, animated: true, completion: nil)
}

func didSelectPollDraft(question: String, options: [String], resultMode: NCPollResultMode, maxVotes: Int) {
func didSelectPollDraft(draft: NCPoll, forEditing: Bool) {
// End editing for any textfield
self.view.endEditing(true)

// Assign poll draft values
self.question = question
self.options = options
self.anonymousPollSwitch.isOn = resultMode == .hidden
self.multipleAnswersSwitch.isOn = maxVotes == 0
self.question = draft.question
self.options = draft.options.compactMap { $0 as? String }
self.anonymousPollSwitch.isOn = draft.resultMode == .hidden
self.multipleAnswersSwitch.isOn = draft.maxVotes == 0

// Check if editing poll draft
if forEditing {
self.editingDraftId = draft.pollId
} else {
self.editingDraftId = nil
}

// Refresh poll creation view
self.refreshPollCreationView()
}

func refreshPollCreationView() {
self.tableView.reloadData()
self.setNavigationBarTitle()
self.setMoreOptionsButton()
self.configureFooterButtons()
self.checkIfPollIsReadyToCreate()
}

func setNavigationBarTitle() {
if let editingDraftId = self.editingDraftId {
self.navigationItem.title = NSLocalizedString("Editing poll draft", comment: "")
} else {
self.navigationItem.title = NSLocalizedString("New poll", comment: "")
}
}

func showCreationError() {
let alert = UIAlertController(title: NSLocalizedString("Creating poll failed", comment: ""),
message: NSLocalizedString("An error occurred while creating the poll", comment: ""),
preferredStyle: .alert)
alert.addAction(UIAlertAction(title: NSLocalizedString("OK", comment: ""), style: .cancel, handler: nil))
self.present(alert, animated: true)
removePollCreationUI()
}

func showEditionError() {
let alert = UIAlertController(title: NSLocalizedString("Editing poll failed", comment: ""),
message: NSLocalizedString("An error occurred while editing the poll", comment: ""),
preferredStyle: .alert)
alert.addAction(UIAlertAction(title: NSLocalizedString("OK", comment: ""), style: .cancel, handler: nil))
self.present(alert, animated: true)
}

func showDraftCreationSuccess() {
NotificationPresenter.shared().present(text: NSLocalizedString("Poll draft has been saved", comment: ""), dismissAfterDelay: 5.0, includedStyle: .dark)
removePollCreationUI()
}

func showPollCreationUI() {
Expand All @@ -138,7 +169,7 @@ import UIKit
}

func setMoreOptionsButton() {
if draftsAvailable {
if draftsAvailable, editingDraftId == nil {
let menuAction = UIAction(
title: NSLocalizedString("Browse poll drafts", comment: ""),
image: UIImage(systemName: "doc")) { _ in
Expand Down Expand Up @@ -166,14 +197,19 @@ import UIKit
footerView.secondaryButton.setButtonEnabled(enabled: false)
}

func pollFooterView() -> UIView {
footerView.primaryButton.setTitle(NSLocalizedString("Create poll", comment: ""), for: .normal)
footerView.primaryButton.setButtonAction(target: self, selector: #selector(createPollButtonPressed))
func configureFooterButtons() {
if editingDraftId != nil {
footerView.primaryButton.setTitle(NSLocalizedString("Save", comment: ""), for: .normal)
footerView.primaryButton.setButtonAction(target: self, selector: #selector(saveEditedPollDraftButtonPressed))
} else {
footerView.primaryButton.setTitle(NSLocalizedString("Create poll", comment: ""), for: .normal)
footerView.primaryButton.setButtonAction(target: self, selector: #selector(createPollButtonPressed))
}

footerView.frame = CGRect(x: 0, y: 0, width: 0, height: PollFooterView.heightForOption)
footerView.secondaryButtonContainerView.isHidden = true

if draftsAvailable {
if draftsAvailable, editingDraftId == nil {
footerView.secondaryButton.setTitle(NSLocalizedString("Save as draft", comment: ""), for: .normal)
footerView.secondaryButton.setButtonStyle(style: .tertiary)
footerView.secondaryButton.setButtonAction(target: self, selector: #selector(createPollDraftButtonPressed))
Expand All @@ -183,7 +219,7 @@ import UIKit
}

checkIfPollIsReadyToCreate()
return footerView
self.tableView.tableFooterView = footerView
}

func createPollButtonPressed() {
Expand All @@ -199,8 +235,8 @@ import UIKit
let maxVotes: Int = multipleAnswersSwitch.isOn ? 0 : 1

showPollCreationUI()

NCAPIController.sharedInstance().createPoll(withQuestion: question, options: options, resultMode: resultMode, maxVotes: maxVotes, inRoom: room.token, asDraft: asDraft, for: room.account) { _, error, _ in
self.removePollCreationUI()
if error != nil {
self.showCreationError()
} else if asDraft {
Expand All @@ -211,6 +247,25 @@ import UIKit
}
}

func saveEditedPollDraftButtonPressed() {
guard let draftId = editingDraftId else { return }

let resultMode: NCPollResultMode = anonymousPollSwitch.isOn ? .hidden : .public
let maxVotes: Int = multipleAnswersSwitch.isOn ? 0 : 1

showPollCreationUI()
NCAPIController.sharedInstance().editPollDraft(withId: draftId, question: question, options: options, resultMode: resultMode, maxVotes: maxVotes, inRoom: room.token, for: room.account) { _, error, _ in
self.removePollCreationUI()
if error != nil {
self.showEditionError()
} else {
self.editingDraftId = nil
self.refreshPollCreationView()
self.presentPollDraftsView()
}
}
}

func checkIfPollIsReadyToCreate() {
disablePollCreationButtons()

Expand Down
41 changes: 39 additions & 2 deletions NextcloudTalk/PollDraftsViewController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
import UIKit

protocol PollDraftsViewControllerDelegate: AnyObject {
func didSelectPollDraft(question: String, options: [String], resultMode: NCPollResultMode, maxVotes: Int)
func didSelectPollDraft(draft:NCPoll, forEditing: Bool)
}

class PollDraftsViewController: UITableViewController {
Expand Down Expand Up @@ -83,6 +83,15 @@ class PollDraftsViewController: UITableViewController {
}
}

// MARK: - Error dialogs
func showDeletionError() {
let alert = UIAlertController(title: NSLocalizedString("Deleting poll draft failed", comment: ""),
message: NSLocalizedString("An error occurred while deleting the poll draft", comment: ""),
preferredStyle: .alert)
alert.addAction(UIAlertAction(title: NSLocalizedString("OK", comment: ""), style: .cancel, handler: nil))
self.present(alert, animated: true)
}

// MARK: - TableView DataSource
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return drafts.count
Expand All @@ -105,7 +114,35 @@ class PollDraftsViewController: UITableViewController {
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
dismiss(animated: true) {
let draft = self.drafts[indexPath.row]
self.delegate?.didSelectPollDraft(question: draft.question, options: draft.options.compactMap { $0 as? String }, resultMode: draft.resultMode, maxVotes: draft.maxVotes)
self.delegate?.didSelectPollDraft(draft: draft, forEditing: false)
}
}

override func tableView(_ tableView: UITableView, contextMenuConfigurationForRowAt indexPath: IndexPath, point: CGPoint) -> UIContextMenuConfiguration? {
return UIContextMenuConfiguration(identifier: indexPath as NSIndexPath, previewProvider: nil) { _ in
let deleteAction = UIAction(title: NSLocalizedString("Delete", comment: ""), image: UIImage(systemName: "trash"), attributes: .destructive) { [unowned self] _ in
let draft = self.drafts[indexPath.row]
NCAPIController.sharedInstance().closePoll(withId: draft.pollId, inRoom: room.token, for: room.account) { _, error, _ in
if error == nil {
self.getPollDrafts()
} else {
self.showDeletionError()
}
}
}

if NCDatabaseManager.sharedInstance().serverHasTalkCapability(kCapabilityEditDraftPoll, forAccountId: self.room.accountId) {
let editAction = UIAction(title: NSLocalizedString("Edit", comment: ""), image: UIImage(systemName: "pencil")) { [unowned self] _ in
dismiss(animated: true) {
let draft = self.drafts[indexPath.row]
self.delegate?.didSelectPollDraft(draft: draft, forEditing: true)
}
}

return UIMenu(children: [editAction, deleteAction])
}

return UIMenu(children: [deleteAction])
}
}
}
15 changes: 15 additions & 0 deletions NextcloudTalk/en.lproj/Localizable.strings
Original file line number Diff line number Diff line change
Expand Up @@ -262,9 +262,15 @@
/* No comment provided by engineer. */
"An error occurred while deleting the message" = "An error occurred while deleting the message";

/* No comment provided by engineer. */
"An error occurred while deleting the poll draft" = "An error occurred while deleting the poll draft";

/* No comment provided by engineer. */
"An error occurred while disabling absence" = "An error occurred while disabling absence";

/* No comment provided by engineer. */
"An error occurred while editing the poll" = "An error occurred while editing the poll";

/* No comment provided by engineer. */
"An error occurred while opening the file %@" = "An error occurred while opening the file %@";

Expand Down Expand Up @@ -709,6 +715,9 @@
/* No comment provided by engineer. */
"Deleting message" = "Deleting message";

/* No comment provided by engineer. */
"Deleting poll draft failed" = "Deleting poll draft failed";

/* No comment provided by engineer. */
"Demote from moderator" = "Demote from moderator";

Expand Down Expand Up @@ -874,6 +883,12 @@
/* No comment provided by engineer. */
"Editing Message" = "Editing Message";

/* No comment provided by engineer. */
"Editing poll draft" = "Editing poll draft";

/* No comment provided by engineer. */
"Editing poll failed" = "Editing poll failed";

/* No comment provided by engineer. */
"Either you don't have chat permission or the conversation is read-only." = "Either you don't have chat permission or the conversation is read-only.";

Expand Down
Loading