Skip to content

Commit

Permalink
Merge pull request #1965 from nextcloud/feat/noid/allow-editing-poll-…
Browse files Browse the repository at this point in the history
…drafts

feat(polls): Allow editing and deleting poll drafts
  • Loading branch information
SystemKeeper authored Jan 31, 2025
2 parents 9e18b7d + b971ea5 commit cc6f743
Show file tree
Hide file tree
Showing 7 changed files with 160 additions and 20 deletions.
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 @@ -715,6 +721,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 @@ -886,6 +895,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

0 comments on commit cc6f743

Please sign in to comment.