Merge pull request #851 from mpretty-cyro/feature/updated-profile-picture-modal

Updated the profile picture management UI & functionality
This commit is contained in:
Morgan Pretty 2023-05-18 15:23:11 +10:00 committed by GitHub
commit 6be759d39c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
53 changed files with 689 additions and 305 deletions

View File

@ -485,7 +485,7 @@ final class EditClosedGroupVC: BaseVC, UITableViewDataSource, UITableViewDelegat
targetView: self.view,
info: ConfirmationModal.Info(
title: title,
explanation: message,
body: .text(message),
cancelTitle: "BUTTON_OK".localized(),
cancelStyle: .alert_text
)

View File

@ -305,7 +305,7 @@ final class NewClosedGroupVC: BaseVC, UITableViewDataSource, UITableViewDelegate
let modal: ConfirmationModal = ConfirmationModal(
info: ConfirmationModal.Info(
title: title,
explanation: message,
body: .text(message),
cancelTitle: "BUTTON_OK".localized(),
cancelStyle: .alert_text
@ -350,7 +350,7 @@ final class NewClosedGroupVC: BaseVC, UITableViewDataSource, UITableViewDelegate
targetView: self?.view,
info: ConfirmationModal.Info(
title: "GROUP_CREATION_ERROR_TITLE".localized(),
explanation: "GROUP_CREATION_ERROR_MESSAGE".localized(),
body: .text("GROUP_CREATION_ERROR_MESSAGE".localized()),
cancelTitle: "BUTTON_OK".localized(),
cancelStyle: .alert_text
)

View File

@ -68,7 +68,7 @@ extension ConversationVC:
let confirmationModal: ConfirmationModal = ConfirmationModal(
info: ConfirmationModal.Info(
title: "modal_call_permission_request_title".localized(),
explanation: "modal_call_permission_request_explanation".localized(),
body: .text("modal_call_permission_request_explanation".localized()),
confirmTitle: "vc_settings_title".localized(),
confirmAccessibilityLabel: "Settings",
cancelAccessibilityLabel: "Cancel",
@ -132,11 +132,13 @@ extension ConversationVC:
format: "modal_blocked_title".localized(),
self.viewModel.threadData.displayName
),
attributedExplanation: NSAttributedString(string: message)
.adding(
attributes: [ .font: UIFont.boldSystemFont(ofSize: Values.smallFontSize) ],
range: (message as NSString).range(of: self.viewModel.threadData.displayName)
),
body: .attributedText(
NSAttributedString(string: message)
.adding(
attributes: [ .font: UIFont.boldSystemFont(ofSize: Values.smallFontSize) ],
range: (message as NSString).range(of: self.viewModel.threadData.displayName)
)
),
confirmTitle: "modal_blocked_button_title".localized(),
confirmAccessibilityLabel: "Confirm block",
cancelAccessibilityLabel: "Cancel block",
@ -205,7 +207,7 @@ extension ConversationVC:
let modal: ConfirmationModal = ConfirmationModal(
info: ConfirmationModal.Info(
title: "GIPHY_PERMISSION_TITLE".localized(),
explanation: "GIPHY_PERMISSION_MESSAGE".localized(),
body: .text("GIPHY_PERMISSION_MESSAGE".localized()),
confirmTitle: "continue_2".localized()
) { [weak self] _ in
Storage.shared.writeAsync(
@ -295,7 +297,7 @@ extension ConversationVC:
targetView: self?.view,
info: ConfirmationModal.Info(
title: "Session",
explanation: "An error occurred.",
body: .text("An error occurred."),
cancelTitle: "BUTTON_OK".localized(),
cancelStyle: .alert_text
)
@ -312,7 +314,7 @@ extension ConversationVC:
targetView: self?.view,
info: ConfirmationModal.Info(
title: "ATTACHMENT_PICKER_DOCUMENTS_PICKED_DIRECTORY_FAILED_ALERT_TITLE".localized(),
explanation: "ATTACHMENT_PICKER_DOCUMENTS_PICKED_DIRECTORY_FAILED_ALERT_BODY".localized(),
body: .text("ATTACHMENT_PICKER_DOCUMENTS_PICKED_DIRECTORY_FAILED_ALERT_BODY".localized()),
cancelTitle: "BUTTON_OK".localized(),
cancelStyle: .alert_text
)
@ -410,7 +412,7 @@ extension ConversationVC:
let modal: ConfirmationModal = ConfirmationModal(
info: ConfirmationModal.Info(
title: "modal_send_seed_title".localized(),
explanation: "modal_send_seed_explanation".localized(),
body: .text("modal_send_seed_explanation".localized()),
confirmTitle: "modal_send_seed_send_button_title".localized(),
confirmStyle: .danger,
cancelStyle: .alert_text,
@ -540,7 +542,7 @@ extension ConversationVC:
let modal: ConfirmationModal = ConfirmationModal(
info: ConfirmationModal.Info(
title: "modal_send_seed_title".localized(),
explanation: "modal_send_seed_explanation".localized(),
body: .text("modal_send_seed_explanation".localized()),
confirmTitle: "modal_send_seed_send_button_title".localized(),
confirmStyle: .danger,
cancelStyle: .alert_text,
@ -646,7 +648,7 @@ extension ConversationVC:
let linkPreviewModal: ConfirmationModal = ConfirmationModal(
info: ConfirmationModal.Info(
title: "modal_link_previews_title".localized(),
explanation: "modal_link_previews_explanation".localized(),
body: .text("modal_link_previews_explanation".localized()),
confirmTitle: "modal_link_previews_button_title".localized()
) { [weak self] _ in
Storage.shared.writeAsync { db in
@ -890,11 +892,13 @@ extension ConversationVC:
format: "modal_download_attachment_title".localized(),
cellViewModel.authorName
),
attributedExplanation: NSAttributedString(string: message)
.adding(
attributes: [ .font: UIFont.boldSystemFont(ofSize: Values.smallFontSize) ],
range: (message as NSString).range(of: cellViewModel.authorName)
),
body: .attributedText(
NSAttributedString(string: message)
.adding(
attributes: [ .font: UIFont.boldSystemFont(ofSize: Values.smallFontSize) ],
range: (message as NSString).range(of: cellViewModel.authorName)
)
),
confirmTitle: "modal_download_button_title".localized(),
confirmAccessibilityLabel: "Download media",
cancelAccessibilityLabel: "Don't download media",
@ -1541,11 +1545,13 @@ extension ConversationVC:
let modal: ConfirmationModal = ConfirmationModal(
info: ConfirmationModal.Info(
title: "Join \(finalName)?",
attributedExplanation: NSMutableAttributedString(string: message)
.adding(
attributes: [ .font: UIFont.boldSystemFont(ofSize: Values.smallFontSize) ],
range: (message as NSString).range(of: finalName)
),
body: .attributedText(
NSMutableAttributedString(string: message)
.adding(
attributes: [ .font: UIFont.boldSystemFont(ofSize: Values.smallFontSize) ],
range: (message as NSString).range(of: finalName)
)
),
confirmTitle: "JOIN_COMMUNITY_BUTTON_TITLE".localized(),
onConfirm: { modal in
guard let presentingViewController: UIViewController = modal.presentingViewController else {
@ -1582,7 +1588,7 @@ extension ConversationVC:
let errorModal: ConfirmationModal = ConfirmationModal(
info: ConfirmationModal.Info(
title: "COMMUNITY_ERROR_GENERIC".localized(),
explanation: error.localizedDescription,
body: .text(error.localizedDescription),
cancelTitle: "BUTTON_OK".localized(),
cancelStyle: .alert_text
)
@ -2048,7 +2054,7 @@ extension ConversationVC:
targetView: self.view,
info: ConfirmationModal.Info(
title: "Session",
explanation: "This will ban the selected user from this room. It won't ban them from other rooms.",
body: .text("This will ban the selected user from this room. It won't ban them from other rooms."),
confirmTitle: "BUTTON_OK".localized(),
cancelStyle: .alert_text,
onConfirm: { [weak self] _ in
@ -2072,7 +2078,7 @@ extension ConversationVC:
targetView: self?.view,
info: ConfirmationModal.Info(
title: CommonStrings.errorAlertTitle,
explanation: "context_menu_ban_user_error_alert_message".localized(),
body: .text("context_menu_ban_user_error_alert_message".localized()),
cancelTitle: "BUTTON_OK".localized(),
cancelStyle: .alert_text
)
@ -2097,7 +2103,7 @@ extension ConversationVC:
targetView: self.view,
info: ConfirmationModal.Info(
title: "Session",
explanation: "This will ban the selected user from this room and delete all messages sent by them. It won't ban them from other rooms or delete the messages they sent there.",
body: .text("This will ban the selected user from this room and delete all messages sent by them. It won't ban them from other rooms or delete the messages they sent there."),
confirmTitle: "BUTTON_OK".localized(),
cancelStyle: .alert_text,
onConfirm: { [weak self] _ in
@ -2121,7 +2127,7 @@ extension ConversationVC:
targetView: self?.view,
info: ConfirmationModal.Info(
title: CommonStrings.errorAlertTitle,
explanation: "context_menu_ban_user_error_alert_message".localized(),
body: .text("context_menu_ban_user_error_alert_message".localized()),
cancelTitle: "BUTTON_OK".localized(),
cancelStyle: .alert_text
)
@ -2231,7 +2237,7 @@ extension ConversationVC:
targetView: self.view,
info: ConfirmationModal.Info(
title: "VOICE_MESSAGE_TOO_SHORT_ALERT_TITLE".localized(),
explanation: "VOICE_MESSAGE_TOO_SHORT_ALERT_MESSAGE".localized(),
body: .text("VOICE_MESSAGE_TOO_SHORT_ALERT_MESSAGE".localized()),
cancelTitle: "BUTTON_OK".localized(),
cancelStyle: .alert_text
)
@ -2302,7 +2308,7 @@ extension ConversationVC:
targetView: self.view,
info: ConfirmationModal.Info(
title: "ATTACHMENT_ERROR_ALERT_TITLE".localized(),
explanation: (attachment.localizedErrorDescription ?? SignalAttachment.missingDataErrorMessage),
body: .text(attachment.localizedErrorDescription ?? SignalAttachment.missingDataErrorMessage),
cancelTitle: "BUTTON_OK".localized(),
cancelStyle: .alert_text,
afterClosed: onDismiss

View File

@ -1304,7 +1304,7 @@ final class ConversationVC: BaseVC, ConversationSearchControllerDelegate, UITabl
targetView: self?.view,
info: ConfirmationModal.Info(
title: CommonStrings.errorAlertTitle,
explanation: "INVALID_AUDIO_FILE_ALERT_ERROR_MESSAGE".localized(),
body: .text("INVALID_AUDIO_FILE_ALERT_ERROR_MESSAGE".localized()),
cancelTitle: "BUTTON_OK".localized(),
cancelStyle: .alert_text
)

View File

@ -3,6 +3,7 @@
import Foundation
import Combine
import GRDB
import YYImage
import DifferenceKit
import SessionUIKit
import SessionMessagingKit
@ -395,7 +396,7 @@ class ThreadSettingsViewModel: SessionTableViewModel<ThreadSettingsViewModel.Nav
accessibilityLabel: "Leave group",
confirmationInfo: ConfirmationModal.Info(
title: "leave_group_confirmation_alert_title".localized(),
attributedExplanation: {
body: .attributedText({
if currentUserIsClosedGroupAdmin {
return NSAttributedString(string: "admin_group_leave_warning".localized())
}
@ -412,7 +413,7 @@ class ThreadSettingsViewModel: SessionTableViewModel<ThreadSettingsViewModel.Nav
range: (mutableAttributedString.string as NSString).range(of: threadViewModel.displayName)
)
return mutableAttributedString
}(),
}()),
confirmTitle: "LEAVE_BUTTON_TITLE".localized(),
confirmStyle: .danger,
cancelStyle: .alert_text
@ -548,9 +549,8 @@ class ThreadSettingsViewModel: SessionTableViewModel<ThreadSettingsViewModel.Nav
threadViewModel.displayName
)
}(),
explanation: (threadViewModel.threadIsBlocked == true ?
nil :
"BLOCK_USER_BEHAVIOR_EXPLANATION".localized()
body: (threadViewModel.threadIsBlocked == true ? .none :
.text("BLOCK_USER_BEHAVIOR_EXPLANATION".localized())
),
confirmTitle: (threadViewModel.threadIsBlocked == true ?
"BLOCK_LIST_UNBLOCK_BUTTON".localized() :
@ -688,13 +688,12 @@ class ThreadSettingsViewModel: SessionTableViewModel<ThreadSettingsViewModel.Nav
displayName
)
),
explanation: (oldBlockedState == false ?
body: (oldBlockedState == true ? .none : .text(
String(
format: "BLOCK_LIST_VIEW_BLOCKED_ALERT_MESSAGE_FORMAT".localized(),
displayName
) :
nil
),
)
)),
accessibilityLabel: oldBlockedState == false ? "User blocked" : "Confirm unblock",
accessibilityId: "Test_name",
cancelTitle: "BUTTON_OK".localized(),

View File

@ -752,7 +752,7 @@ final class HomeVC: BaseVC, UITableViewDataSource, UITableViewDelegate, SeedRemi
let confirmationModal: ConfirmationModal = ConfirmationModal(
info: ConfirmationModal.Info(
title: "delete_conversation_confirmation_alert_title".localized(),
attributedExplanation: confirmationModalExplanation,
body: .attributedText(confirmationModalExplanation),
confirmTitle: "TXT_DELETE_TITLE".localized(),
confirmStyle: .danger,
cancelStyle: .alert_text,
@ -837,7 +837,7 @@ final class HomeVC: BaseVC, UITableViewDataSource, UITableViewDelegate, SeedRemi
let confirmationModal: ConfirmationModal = ConfirmationModal(
info: ConfirmationModal.Info(
title: confirmationModalTitle,
attributedExplanation: confirmationModalExplanation,
body: .attributedText(confirmationModalExplanation),
confirmTitle: "LEAVE_BUTTON_TITLE".localized(),
confirmStyle: .danger,
cancelStyle: .alert_text,

View File

@ -214,7 +214,7 @@ final class NewDMVC: BaseVC, UIPageViewControllerDataSource, UIPageViewControlle
targetView: self?.view,
info: ConfirmationModal.Info(
title: "ALERT_ERROR_TITLE".localized(),
explanation: message,
body: .text(message),
cancelTitle: "BUTTON_OK".localized(),
cancelStyle: .alert_text
)

View File

@ -389,7 +389,7 @@ class GifPickerViewController: OWSViewController, UISearchBarDelegate, UICollect
targetView: self?.view,
info: ConfirmationModal.Info(
title: "GIF_PICKER_FAILURE_ALERT_TITLE".localized(),
explanation: error.localizedDescription,
body: .text(error.localizedDescription),
confirmTitle: CommonStrings.retryButton,
cancelTitle: CommonStrings.dismissButton,
cancelStyle: .alert_text,
@ -458,7 +458,7 @@ class GifPickerViewController: OWSViewController, UISearchBarDelegate, UICollect
targetView: self.view,
info: ConfirmationModal.Info(
title: CommonStrings.errorAlertTitle,
explanation: "GIF_PICKER_VIEW_MISSING_QUERY".localized(),
body: .text("GIF_PICKER_VIEW_MISSING_QUERY".localized()),
cancelTitle: "BUTTON_OK".localized(),
cancelStyle: .alert_text
)

View File

@ -315,7 +315,7 @@ class PhotoCaptureViewController: OWSViewController {
let modal: ConfirmationModal = ConfirmationModal(
info: ConfirmationModal.Info(
title: CommonStrings.errorAlertTitle,
explanation: error.localizedDescription,
body: .text(error.localizedDescription),
cancelTitle: CommonStrings.dismissButton,
cancelStyle: .alert_text,
afterClosed: { [weak self] in self?.dismiss(animated: true) }

View File

@ -0,0 +1,23 @@
{
"images" : [
{
"filename" : "profile_placeholder.png",
"idiom" : "universal",
"scale" : "1x"
},
{
"filename" : "profile_placeholder@2x.png",
"idiom" : "universal",
"scale" : "2x"
},
{
"filename" : "profile_placeholder@3x.png",
"idiom" : "universal",
"scale" : "3x"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.8 KiB

View File

@ -623,3 +623,6 @@
"group_unable_to_leave" = "Unable to leave the Group, please try again";
"delete_conversation_confirmation_alert_message" = "Are you sure you want to delete your conversation with %@?";
"delete_conversation_confirmation_alert_title" = "Delete Conversation";
"update_profile_modal_title" = "Set Display Picture";
"update_profile_modal_upload" = "Upload";
"update_profile_modal_remove" = "Remove";

View File

@ -623,3 +623,6 @@
"group_unable_to_leave" = "Unable to leave the Group, please try again";
"delete_conversation_confirmation_alert_message" = "Are you sure you want to delete your conversation with %@?";
"delete_conversation_confirmation_alert_title" = "Delete Conversation";
"update_profile_modal_title" = "Set Display Picture";
"update_profile_modal_upload" = "Upload";
"update_profile_modal_remove" = "Remove";

View File

@ -623,3 +623,6 @@
"group_unable_to_leave" = "Unable to leave the Group, please try again";
"delete_conversation_confirmation_alert_message" = "Are you sure you want to delete your conversation with %@?";
"delete_conversation_confirmation_alert_title" = "Delete Conversation";
"update_profile_modal_title" = "Set Display Picture";
"update_profile_modal_upload" = "Upload";
"update_profile_modal_remove" = "Remove";

View File

@ -623,3 +623,6 @@
"group_unable_to_leave" = "Unable to leave the Group, please try again";
"delete_conversation_confirmation_alert_message" = "Are you sure you want to delete your conversation with %@?";
"delete_conversation_confirmation_alert_title" = "Delete Conversation";
"update_profile_modal_title" = "Set Display Picture";
"update_profile_modal_upload" = "Upload";
"update_profile_modal_remove" = "Remove";

View File

@ -623,3 +623,6 @@
"group_unable_to_leave" = "Unable to leave the Group, please try again";
"delete_conversation_confirmation_alert_message" = "Are you sure you want to delete your conversation with %@?";
"delete_conversation_confirmation_alert_title" = "Delete Conversation";
"update_profile_modal_title" = "Set Display Picture";
"update_profile_modal_upload" = "Upload";
"update_profile_modal_remove" = "Remove";

View File

@ -623,3 +623,6 @@
"group_unable_to_leave" = "Unable to leave the Group, please try again";
"delete_conversation_confirmation_alert_message" = "Are you sure you want to delete your conversation with %@?";
"delete_conversation_confirmation_alert_title" = "Delete Conversation";
"update_profile_modal_title" = "Set Display Picture";
"update_profile_modal_upload" = "Upload";
"update_profile_modal_remove" = "Remove";

View File

@ -623,3 +623,6 @@
"group_unable_to_leave" = "Unable to leave the Group, please try again";
"delete_conversation_confirmation_alert_message" = "Are you sure you want to delete your conversation with %@?";
"delete_conversation_confirmation_alert_title" = "Delete Conversation";
"update_profile_modal_title" = "Set Display Picture";
"update_profile_modal_upload" = "Upload";
"update_profile_modal_remove" = "Remove";

View File

@ -623,3 +623,6 @@
"group_unable_to_leave" = "Unable to leave the Group, please try again";
"delete_conversation_confirmation_alert_message" = "Are you sure you want to delete your conversation with %@?";
"delete_conversation_confirmation_alert_title" = "Delete Conversation";
"update_profile_modal_title" = "Set Display Picture";
"update_profile_modal_upload" = "Upload";
"update_profile_modal_remove" = "Remove";

View File

@ -623,3 +623,6 @@
"group_unable_to_leave" = "Unable to leave the Group, please try again";
"delete_conversation_confirmation_alert_message" = "Are you sure you want to delete your conversation with %@?";
"delete_conversation_confirmation_alert_title" = "Delete Conversation";
"update_profile_modal_title" = "Set Display Picture";
"update_profile_modal_upload" = "Upload";
"update_profile_modal_remove" = "Remove";

View File

@ -623,3 +623,6 @@
"group_unable_to_leave" = "Unable to leave the Group, please try again";
"delete_conversation_confirmation_alert_message" = "Are you sure you want to delete your conversation with %@?";
"delete_conversation_confirmation_alert_title" = "Delete Conversation";
"update_profile_modal_title" = "Set Display Picture";
"update_profile_modal_upload" = "Upload";
"update_profile_modal_remove" = "Remove";

View File

@ -623,3 +623,6 @@
"group_unable_to_leave" = "Unable to leave the Group, please try again";
"delete_conversation_confirmation_alert_message" = "Are you sure you want to delete your conversation with %@?";
"delete_conversation_confirmation_alert_title" = "Delete Conversation";
"update_profile_modal_title" = "Set Display Picture";
"update_profile_modal_upload" = "Upload";
"update_profile_modal_remove" = "Remove";

View File

@ -623,3 +623,6 @@
"group_unable_to_leave" = "Unable to leave the Group, please try again";
"delete_conversation_confirmation_alert_message" = "Are you sure you want to delete your conversation with %@?";
"delete_conversation_confirmation_alert_title" = "Delete Conversation";
"update_profile_modal_title" = "Set Display Picture";
"update_profile_modal_upload" = "Upload";
"update_profile_modal_remove" = "Remove";

View File

@ -623,3 +623,6 @@
"group_unable_to_leave" = "Unable to leave the Group, please try again";
"delete_conversation_confirmation_alert_message" = "Are you sure you want to delete your conversation with %@?";
"delete_conversation_confirmation_alert_title" = "Delete Conversation";
"update_profile_modal_title" = "Set Display Picture";
"update_profile_modal_upload" = "Upload";
"update_profile_modal_remove" = "Remove";

View File

@ -623,3 +623,6 @@
"group_unable_to_leave" = "Unable to leave the Group, please try again";
"delete_conversation_confirmation_alert_message" = "Are you sure you want to delete your conversation with %@?";
"delete_conversation_confirmation_alert_title" = "Delete Conversation";
"update_profile_modal_title" = "Set Display Picture";
"update_profile_modal_upload" = "Upload";
"update_profile_modal_remove" = "Remove";

View File

@ -623,3 +623,6 @@
"group_unable_to_leave" = "Unable to leave the Group, please try again";
"delete_conversation_confirmation_alert_message" = "Are you sure you want to delete your conversation with %@?";
"delete_conversation_confirmation_alert_title" = "Delete Conversation";
"update_profile_modal_title" = "Set Display Picture";
"update_profile_modal_upload" = "Upload";
"update_profile_modal_remove" = "Remove";

View File

@ -623,3 +623,6 @@
"group_unable_to_leave" = "Unable to leave the Group, please try again";
"delete_conversation_confirmation_alert_message" = "Are you sure you want to delete your conversation with %@?";
"delete_conversation_confirmation_alert_title" = "Delete Conversation";
"update_profile_modal_title" = "Set Display Picture";
"update_profile_modal_upload" = "Upload";
"update_profile_modal_remove" = "Remove";

View File

@ -623,3 +623,6 @@
"group_unable_to_leave" = "Unable to leave the Group, please try again";
"delete_conversation_confirmation_alert_message" = "Are you sure you want to delete your conversation with %@?";
"delete_conversation_confirmation_alert_title" = "Delete Conversation";
"update_profile_modal_title" = "Set Display Picture";
"update_profile_modal_upload" = "Upload";
"update_profile_modal_remove" = "Remove";

View File

@ -623,3 +623,6 @@
"group_unable_to_leave" = "Unable to leave the Group, please try again";
"delete_conversation_confirmation_alert_message" = "Are you sure you want to delete your conversation with %@?";
"delete_conversation_confirmation_alert_title" = "Delete Conversation";
"update_profile_modal_title" = "Set Display Picture";
"update_profile_modal_upload" = "Upload";
"update_profile_modal_remove" = "Remove";

View File

@ -623,3 +623,6 @@
"group_unable_to_leave" = "Unable to leave the Group, please try again";
"delete_conversation_confirmation_alert_message" = "Are you sure you want to delete your conversation with %@?";
"delete_conversation_confirmation_alert_title" = "Delete Conversation";
"update_profile_modal_title" = "Set Display Picture";
"update_profile_modal_upload" = "Upload";
"update_profile_modal_remove" = "Remove";

View File

@ -623,3 +623,6 @@
"group_unable_to_leave" = "Unable to leave the Group, please try again";
"delete_conversation_confirmation_alert_message" = "Are you sure you want to delete your conversation with %@?";
"delete_conversation_confirmation_alert_title" = "Delete Conversation";
"update_profile_modal_title" = "Set Display Picture";
"update_profile_modal_upload" = "Upload";
"update_profile_modal_remove" = "Remove";

View File

@ -623,3 +623,6 @@
"group_unable_to_leave" = "Unable to leave the Group, please try again";
"delete_conversation_confirmation_alert_message" = "Are you sure you want to delete your conversation with %@?";
"delete_conversation_confirmation_alert_title" = "Delete Conversation";
"update_profile_modal_title" = "Set Display Picture";
"update_profile_modal_upload" = "Upload";
"update_profile_modal_remove" = "Remove";

View File

@ -623,3 +623,6 @@
"group_unable_to_leave" = "Unable to leave the Group, please try again";
"delete_conversation_confirmation_alert_message" = "Are you sure you want to delete your conversation with %@?";
"delete_conversation_confirmation_alert_title" = "Delete Conversation";
"update_profile_modal_title" = "Set Display Picture";
"update_profile_modal_upload" = "Upload";
"update_profile_modal_remove" = "Remove";

View File

@ -158,7 +158,7 @@ final class DisplayNameVC: BaseVC {
targetView: self.view,
info: ConfirmationModal.Info(
title: title,
explanation: message,
body: .text(message),
cancelTitle: "BUTTON_OK".localized(),
cancelStyle: .alert_text
)

View File

@ -143,7 +143,7 @@ final class LinkDeviceVC: BaseVC, UIPageViewControllerDataSource, UIPageViewCont
let modal: ConfirmationModal = ConfirmationModal(
info: ConfirmationModal.Info(
title: "invalid_recovery_phrase".localized(),
explanation: "INVALID_RECOVERY_PHRASE_MESSAGE".localized(),
body: .text("INVALID_RECOVERY_PHRASE_MESSAGE".localized()),
cancelTitle: "BUTTON_OK".localized(),
cancelStyle: .alert_text,
afterClosed: { [weak self] in
@ -321,7 +321,7 @@ private final class RecoveryPhraseVC: UIViewController {
targetView: self.view,
info: ConfirmationModal.Info(
title: title,
explanation: message,
body: .text(message),
cancelTitle: "BUTTON_OK".localized(),
cancelStyle: .alert_text
)

View File

@ -186,7 +186,7 @@ final class RestoreVC: BaseVC {
let modal: ConfirmationModal = ConfirmationModal(
info: ConfirmationModal.Info(
title: title,
explanation: message,
body: .text(message),
cancelTitle: "BUTTON_OK".localized(),
cancelStyle: .alert_text
)

View File

@ -211,7 +211,7 @@ final class JoinOpenGroupVC: BaseVC, UIPageViewControllerDataSource, UIPageViewC
let confirmationModal: ConfirmationModal = ConfirmationModal(
info: ConfirmationModal.Info(
title: title,
explanation: message,
body: .text(message),
cancelTitle: "BUTTON_OK".localized(),
cancelStyle: .alert_text
)

View File

@ -9,8 +9,8 @@ import SignalUtilitiesKit
final class NukeDataModal: Modal {
// MARK: - Initialization
override init(targetView: UIView? = nil, afterClosed: (() -> ())? = nil) {
super.init(targetView: targetView, afterClosed: afterClosed)
override init(targetView: UIView? = nil, dismissType: DismissType = .recursive, afterClosed: (() -> ())? = nil) {
super.init(targetView: targetView, dismissType: dismissType, afterClosed: afterClosed)
self.modalPresentationStyle = .overFullScreen
self.modalTransitionStyle = .crossDissolve
@ -135,7 +135,7 @@ final class NukeDataModal: Modal {
let confirmationModal: ConfirmationModal = ConfirmationModal(
info: ConfirmationModal.Info(
title: "modal_clear_all_data_title".localized(),
explanation: "modal_clear_all_data_explanation_2".localized(),
body: .text("modal_clear_all_data_explanation_2".localized()),
confirmTitle: "modal_clear_all_data_confirm".localized(),
confirmStyle: .danger,
cancelStyle: .alert_text,
@ -184,7 +184,7 @@ final class NukeDataModal: Modal {
targetView: self?.view,
info: ConfirmationModal.Info(
title: "ALERT_ERROR_TITLE".localized(),
explanation: message,
body: .text(message),
cancelTitle: "BUTTON_OK".localized(),
cancelStyle: .alert_text
)
@ -199,7 +199,7 @@ final class NukeDataModal: Modal {
targetView: self?.view,
info: ConfirmationModal.Info(
title: "ALERT_ERROR_TITLE".localized(),
explanation: error.localizedDescription,
body: .text(error.localizedDescription),
cancelTitle: "BUTTON_OK".localized(),
cancelStyle: .alert_text
)

View File

@ -209,8 +209,8 @@ class PrivacySettingsViewModel: SessionTableViewModel<PrivacySettingsViewModel.N
accessibilityLabel: "Allow voice and video calls",
confirmationInfo: ConfirmationModal.Info(
title: "PRIVACY_CALLS_WARNING_TITLE".localized(),
explanation: "PRIVACY_CALLS_WARNING_DESCRIPTION".localized(),
stateToShow: .whenDisabled,
body: .text("PRIVACY_CALLS_WARNING_DESCRIPTION".localized()),
showCondition: .disabled,
confirmTitle: "continue_2".localized(),
confirmAccessibilityLabel: "Enable",
confirmStyle: .textPrimary,

View File

@ -131,7 +131,7 @@ final class QRCodeVC : BaseVC, UIPageViewControllerDataSource, UIPageViewControl
targetView: self.view,
info: ConfirmationModal.Info(
title: "invalid_session_id".localized(),
explanation: "INVALID_SESSION_ID_MESSAGE".localized(),
body: .text("INVALID_SESSION_ID_MESSAGE".localized()),
cancelTitle: "BUTTON_OK".localized(),
cancelStyle: .alert_text
)

View File

@ -16,8 +16,8 @@ final class SeedModal: Modal {
// MARK: - Initialization
override init(targetView: UIView? = nil, afterClosed: (() -> ())? = nil) {
super.init(targetView: targetView, afterClosed: afterClosed)
override init(targetView: UIView? = nil, dismissType: DismissType = .recursive, afterClosed: (() -> ())? = nil) {
super.init(targetView: targetView, dismissType: dismissType, afterClosed: afterClosed)
self.modalPresentationStyle = .overFullScreen
self.modalTransitionStyle = .crossDissolve

View File

@ -50,6 +50,10 @@ class SettingsViewModel: SessionTableViewModel<SettingsViewModel.NavButton, Sett
private lazy var imagePickerHandler: ImagePickerHandler = ImagePickerHandler(viewModel: self)
fileprivate var oldDisplayName: String
private var editedDisplayName: String?
private var editProfilePictureModal: ConfirmationModal?
private var editProfilePictureModalInfo: ConfirmationModal.Info?
private var editedProfilePicture: UIImage?
private var editedProfilePictureFileName: String?
// MARK: - Initialization
@ -102,11 +106,13 @@ class SettingsViewModel: SessionTableViewModel<SettingsViewModel.NavButton, Sett
}
override var rightNavItems: AnyPublisher<[NavItem]?, Never> {
navState
.map { [weak self] navState -> [NavItem] in
switch navState {
case .standard:
return [
let userSessionId: String = self.userSessionId
return navState
.map { [weak self] navState -> [NavItem] in
switch navState {
case .standard:
return [
NavItem(
id: .qrCode,
image: UIImage(named: "QRCode")?
@ -117,10 +123,10 @@ class SettingsViewModel: SessionTableViewModel<SettingsViewModel.NavButton, Sett
self?.transitionToScreen(QRCodeVC())
}
)
]
]
case .editing:
return [
case .editing:
return [
NavItem(
id: .done,
systemItem: .done,
@ -161,7 +167,7 @@ class SettingsViewModel: SessionTableViewModel<SettingsViewModel.NavButton, Sett
self?.updateProfile(
name: updatedNickname,
profilePicture: nil,
profilePictureFilePath: nil,
profilePictureFilePath: ProfileManager.profileAvatarFilepath(id: userSessionId),
isUpdatingDisplayName: true,
isUpdatingProfilePicture: false
)
@ -389,23 +395,90 @@ class SettingsViewModel: SessionTableViewModel<SettingsViewModel.NavButton, Sett
}
private func updateProfilePicture() {
let actionSheet: UIAlertController = UIAlertController(
title: "Update Profile Picture",
message: nil,
preferredStyle: .actionSheet
)
let action = UIAlertAction(
title: "MEDIA_FROM_LIBRARY_BUTTON".localized(),
style: .default,
handler: { [weak self] _ in
self?.showPhotoLibraryForAvatar()
let existingDisplayName: String = self.oldDisplayName
let existingImage: UIImage? = ProfileManager
.profileAvatar(id: self.userSessionId)
.map { UIImage(data: $0) }
let editProfilePictureModalInfo: ConfirmationModal.Info = ConfirmationModal.Info(
title: "update_profile_modal_title".localized(),
body: .image(
placeholder: UIImage(named: "profile_placeholder"),
value: existingImage,
style: .circular,
onClick: { [weak self] in self?.showPhotoLibraryForAvatar() }
),
confirmTitle: "update_profile_modal_upload".localized(),
confirmEnabled: false,
cancelTitle: "update_profile_modal_remove".localized(),
cancelEnabled: (existingImage != nil),
hasCloseButton: true,
dismissOnConfirm: false,
onConfirm: { [weak self] modal in
self?.updateProfile(
name: existingDisplayName,
profilePicture: self?.editedProfilePicture,
profilePictureFilePath: self?.editedProfilePictureFileName,
isUpdatingDisplayName: false,
isUpdatingProfilePicture: true,
onComplete: { [weak modal] in modal?.close() }
)
},
onCancel: { [weak self] modal in
self?.updateProfile(
name: existingDisplayName,
profilePicture: nil,
profilePictureFilePath: nil,
isUpdatingDisplayName: false,
isUpdatingProfilePicture: true,
onComplete: { [weak modal] in modal?.close() }
)
},
afterClosed: { [weak self] in
self?.editedProfilePicture = nil
self?.editedProfilePictureFileName = nil
self?.editProfilePictureModal = nil
self?.editProfilePictureModalInfo = nil
}
)
action.accessibilityLabel = "Photo library"
actionSheet.addAction(action)
actionSheet.addAction(UIAlertAction(title: "cancel".localized(), style: .cancel, handler: nil))
let modal: ConfirmationModal = ConfirmationModal(info: editProfilePictureModalInfo)
self.editProfilePictureModalInfo = editProfilePictureModalInfo
self.editProfilePictureModal = modal
self.transitionToScreen(modal, transitionType: .present)
}
fileprivate func updatedProfilePictureSelected(image: UIImage?, filePath: String?) {
guard let info: ConfirmationModal.Info = self.editProfilePictureModalInfo else { return }
self.transitionToScreen(actionSheet, transitionType: .present)
self.editedProfilePicture = image
self.editedProfilePictureFileName = filePath
if let image: UIImage = image {
self.editProfilePictureModal?.updateContent(
with: info.with(
body: .image(
placeholder: UIImage(named: "profile_placeholder"),
value: image,
style: .circular,
onClick: { [weak self] in self?.showPhotoLibraryForAvatar() }
),
confirmEnabled: true
)
)
}
else if let filePath: String = filePath {
self.editProfilePictureModal?.updateContent(
with: info.with(
body: .image(
placeholder: UIImage(named: "profile_placeholder"),
value: UIImage(contentsOfFile: filePath),
style: .circular,
onClick: { [weak self] in self?.showPhotoLibraryForAvatar() }
),
confirmEnabled: true
)
)
}
}
private func showPhotoLibraryForAvatar() {
@ -421,24 +494,20 @@ class SettingsViewModel: SessionTableViewModel<SettingsViewModel.NavButton, Sett
}
}
fileprivate func updateProfile(
private func updateProfile(
name: String,
profilePicture: UIImage?,
profilePictureFilePath: String?,
isUpdatingDisplayName: Bool,
isUpdatingProfilePicture: Bool
isUpdatingProfilePicture: Bool,
onComplete: (() -> ())? = nil
) {
let imageFilePath: String? = (
profilePictureFilePath ??
ProfileManager.profileAvatarFilepath(id: self.userSessionId)
)
let viewController = ModalActivityIndicatorViewController(canCancel: false) { [weak self] modalActivityIndicator in
ProfileManager.updateLocal(
queue: DispatchQueue.global(qos: .default),
profileName: name,
image: profilePicture,
imageFilePath: imageFilePath,
imageFilePath: profilePictureFilePath,
success: { db, updatedProfile in
if isUpdatingDisplayName {
UserDefaults.standard[.lastDisplayNameUpdate] = Date()
@ -453,7 +522,9 @@ class SettingsViewModel: SessionTableViewModel<SettingsViewModel.NavButton, Sett
// Wait for the database transaction to complete before updating the UI
db.afterNextTransaction { _ in
DispatchQueue.main.async {
modalActivityIndicator.dismiss(completion: {})
modalActivityIndicator.dismiss(completion: {
onComplete?()
})
}
}
},
@ -469,12 +540,13 @@ class SettingsViewModel: SessionTableViewModel<SettingsViewModel.NavButton, Sett
"Maximum File Size Exceeded" :
"Couldn't Update Profile"
),
explanation: (isMaxFileSizeExceeded ?
body: .text(isMaxFileSizeExceeded ?
"Please select a smaller photo and try again" :
"Please check your internet connection and try again"
),
cancelTitle: "BUTTON_OK".localized(),
cancelStyle: .alert_text
cancelStyle: .alert_text,
dismissType: .single
)
),
transitionType: .present
@ -497,8 +569,6 @@ class SettingsViewModel: SessionTableViewModel<SettingsViewModel.NavButton, Sett
DispatchQueue.main.async {
button.isUserInteractionEnabled = false
UIView.transition(
with: button,
duration: 0.25,
@ -560,7 +630,6 @@ class ImagePickerHandler: NSObject, UIImagePickerControllerDelegate & UINavigati
picker.presentingViewController?.dismiss(animated: true)
return
}
let name: String = self.viewModel.oldDisplayName
picker.presentingViewController?.dismiss(animated: true) { [weak self] in
// Check if the user selected an animated image (if so then don't crop, just
@ -574,12 +643,9 @@ class ImagePickerHandler: NSObject, UIImagePickerControllerDelegate & UINavigati
let viewController: CropScaleImageViewController = CropScaleImageViewController(
srcImage: rawAvatar,
successCompletion: { resultImage in
self?.viewModel.updateProfile(
name: name,
profilePicture: resultImage,
profilePictureFilePath: nil,
isUpdatingDisplayName: false,
isUpdatingProfilePicture: true
self?.viewModel.updatedProfilePictureSelected(
image: resultImage,
filePath: nil
)
}
)
@ -587,12 +653,9 @@ class ImagePickerHandler: NSObject, UIImagePickerControllerDelegate & UINavigati
return
}
self?.viewModel.updateProfile(
name: name,
profilePicture: nil,
profilePictureFilePath: imageUrl.path,
isUpdatingDisplayName: false,
isUpdatingProfilePicture: true
self?.viewModel.updatedProfilePictureSelected(
image: nil,
filePath: imageUrl.path
)
}
}

View File

@ -1,6 +1,7 @@
// Copyright © 2022 Rangeproof Pty Ltd. All rights reserved.
import UIKit
import SessionUIKit
import SessionMessagingKit
import SessionUtilitiesKit
import SignalUtilitiesKit
@ -271,7 +272,7 @@ class ScreenLockUI {
targetView: screenBlockingWindow.rootViewController?.view,
info: ConfirmationModal.Info(
title: "SCREEN_LOCK_UNLOCK_FAILED".localized(),
explanation: message,
body: .text(message),
cancelTitle: "BUTTON_OK".localized(),
cancelStyle: .alert_text,
afterClosed: { [weak self] in self?.ensureUI() } // After the alert, update the UI

View File

@ -339,13 +339,15 @@ class SessionTableViewController<NavItemId: Equatable, Section: SessionTableSect
self?.navigationController?.pushViewController(viewController, animated: true)
case .present:
let presenter: UIViewController? = (self?.presentedViewController ?? self)
if UIDevice.current.isIPad {
viewController.popoverPresentationController?.permittedArrowDirections = []
viewController.popoverPresentationController?.sourceView = self?.view
viewController.popoverPresentationController?.sourceRect = (self?.view.bounds ?? UIScreen.main.bounds)
viewController.popoverPresentationController?.sourceView = presenter?.view
viewController.popoverPresentationController?.sourceRect = (presenter?.view.bounds ?? UIScreen.main.bounds)
}
self?.present(viewController, animated: true)
presenter?.present(viewController, animated: true)
}
}
.store(in: &disposables)
@ -523,7 +525,7 @@ class SessionTableViewController<NavItemId: Equatable, Section: SessionTableSect
guard
let confirmationInfo: ConfirmationModal.Info = info.confirmationInfo,
confirmationInfo.stateToShow.shouldShow(for: info.currentBoolValue)
confirmationInfo.showCondition.shouldShow(for: info.currentBoolValue)
else {
performAction()
return

View File

@ -3,7 +3,9 @@
import UIKit
import Photos
import PhotosUI
import SessionUIKit
import SessionUtilitiesKit
import SessionMessagingKit
public enum Permissions {
@discardableResult public static func requestCameraPermissionIfNeeded(
@ -21,9 +23,11 @@ public enum Permissions {
let confirmationModal: ConfirmationModal = ConfirmationModal(
info: ConfirmationModal.Info(
title: "Session",
explanation: String(
format: "modal_permission_explanation".localized(),
"modal_permission_camera".localized()
body: .text(
String(
format: "modal_permission_explanation".localized(),
"modal_permission_camera".localized()
)
),
confirmTitle: "modal_permission_settings_title".localized(),
dismissOnConfirm: false
@ -59,9 +63,11 @@ public enum Permissions {
let confirmationModal: ConfirmationModal = ConfirmationModal(
info: ConfirmationModal.Info(
title: "Session",
explanation: String(
format: "modal_permission_explanation".localized(),
"modal_permission_microphone".localized()
body: .text(
String(
format: "modal_permission_explanation".localized(),
"modal_permission_microphone".localized()
)
),
confirmTitle: "modal_permission_settings_title".localized(),
dismissOnConfirm: false,
@ -128,9 +134,11 @@ public enum Permissions {
let confirmationModal: ConfirmationModal = ConfirmationModal(
info: ConfirmationModal.Info(
title: "Session",
explanation: String(
format: "modal_permission_explanation".localized(),
"modal_permission_library".localized()
body: .text(
String(
format: "modal_permission_explanation".localized(),
"modal_permission_library".localized()
)
),
confirmTitle: "modal_permission_settings_title".localized(),
dismissOnConfirm: false

View File

@ -163,7 +163,7 @@ final class SAEScreenLockViewController: ScreenLockViewController {
targetView: self.view,
info: ConfirmationModal.Info(
title: "SCREEN_LOCK_UNLOCK_FAILED".localized(),
explanation: message,
body: .text(message),
cancelTitle: "BUTTON_OK".localized(),
cancelStyle: .alert_text,
afterClosed: { [weak self] in self?.ensureUI() } // After the alert, update the UI

View File

@ -226,7 +226,7 @@ final class ShareVC: UINavigationController, ShareViewDelegate {
targetView: self.view,
info: ConfirmationModal.Info(
title: "Session",
explanation: error.localizedDescription,
body: .text(error.localizedDescription),
cancelTitle: "BUTTON_OK".localized(),
cancelStyle: .alert_text,
afterClosed: { [weak self] in self?.extensionContext?.cancelRequest(withError: error) }

View File

@ -1,135 +1,16 @@
// Copyright © 2022 Rangeproof Pty Ltd. All rights reserved.
import UIKit
import SessionUtilitiesKit
// FIXME: Refactor as part of the Groups Rebuild
public class ConfirmationModal: Modal {
public struct Info: Equatable, Hashable {
public enum State {
case whenEnabled
case whenDisabled
case always
public func shouldShow(for value: Bool) -> Bool {
switch self {
case .whenEnabled: return (value == true)
case .whenDisabled: return (value == false)
case .always: return true
}
}
}
let title: String
let explanation: String?
let attributedExplanation: NSAttributedString?
let accessibilityLabel: String?
let accessibilityIdentifier: String?
public let stateToShow: State
let confirmTitle: String?
let confirmAccessibilityLabel: String?
let confirmStyle: ThemeValue
let cancelTitle: String
let cancelAccessibilityLabel: String?
let cancelStyle: ThemeValue
let dismissOnConfirm: Bool
let onConfirm: ((UIViewController) -> ())?
let afterClosed: (() -> ())?
// MARK: - Initialization
public init(
title: String,
explanation: String? = nil,
attributedExplanation: NSAttributedString? = nil,
accessibilityLabel: String? = nil,
accessibilityId: String? = nil,
stateToShow: State = .always,
confirmTitle: String? = nil,
confirmAccessibilityLabel: String? = nil,
confirmStyle: ThemeValue = .alert_text,
cancelTitle: String = "TXT_CANCEL_TITLE".localized(),
cancelAccessibilityLabel: String? = nil,
cancelStyle: ThemeValue = .danger,
dismissOnConfirm: Bool = true,
onConfirm: ((UIViewController) -> ())? = nil,
afterClosed: (() -> ())? = nil
) {
self.title = title
self.explanation = explanation
self.attributedExplanation = attributedExplanation
self.accessibilityLabel = accessibilityLabel
self.accessibilityIdentifier = accessibilityId
self.stateToShow = stateToShow
self.confirmTitle = confirmTitle
self.confirmAccessibilityLabel = confirmAccessibilityLabel
self.confirmStyle = confirmStyle
self.cancelTitle = cancelTitle
self.cancelAccessibilityLabel = cancelAccessibilityLabel
self.cancelStyle = cancelStyle
self.dismissOnConfirm = dismissOnConfirm
self.onConfirm = onConfirm
self.afterClosed = afterClosed
}
// MARK: - Mutation
public func with(
onConfirm: ((UIViewController) -> ())? = nil,
afterClosed: (() -> ())? = nil
) -> Info {
return Info(
title: self.title,
explanation: self.explanation,
attributedExplanation: self.attributedExplanation,
accessibilityLabel: self.accessibilityLabel,
stateToShow: self.stateToShow,
confirmTitle: self.confirmTitle,
confirmAccessibilityLabel: self.confirmAccessibilityLabel,
confirmStyle: self.confirmStyle,
cancelTitle: self.cancelTitle,
cancelAccessibilityLabel: self.cancelAccessibilityLabel,
cancelStyle: self.cancelStyle,
dismissOnConfirm: self.dismissOnConfirm,
onConfirm: (onConfirm ?? self.onConfirm),
afterClosed: (afterClosed ?? self.afterClosed)
)
}
// MARK: - Confirmance
public static func == (lhs: ConfirmationModal.Info, rhs: ConfirmationModal.Info) -> Bool {
return (
lhs.title == rhs.title &&
lhs.explanation == rhs.explanation &&
lhs.attributedExplanation == rhs.attributedExplanation &&
lhs.accessibilityLabel == rhs.accessibilityLabel &&
lhs.stateToShow == rhs.stateToShow &&
lhs.confirmTitle == rhs.confirmTitle &&
lhs.confirmAccessibilityLabel == rhs.confirmAccessibilityLabel &&
lhs.confirmStyle == rhs.confirmStyle &&
lhs.cancelTitle == rhs.cancelTitle &&
lhs.cancelAccessibilityLabel == rhs.cancelAccessibilityLabel &&
lhs.cancelStyle == rhs.cancelStyle &&
lhs.dismissOnConfirm == rhs.dismissOnConfirm
)
}
public func hash(into hasher: inout Hasher) {
title.hash(into: &hasher)
explanation.hash(into: &hasher)
attributedExplanation.hash(into: &hasher)
accessibilityLabel.hash(into: &hasher)
stateToShow.hash(into: &hasher)
confirmTitle.hash(into: &hasher)
confirmAccessibilityLabel.hash(into: &hasher)
confirmStyle.hash(into: &hasher)
cancelTitle.hash(into: &hasher)
cancelAccessibilityLabel.hash(into: &hasher)
cancelStyle.hash(into: &hasher)
dismissOnConfirm.hash(into: &hasher)
}
}
private static let imageSize: CGFloat = 80
private static let closeSize: CGFloat = 24
private let internalOnConfirm: (UIViewController) -> ()
private var internalOnConfirm: ((ConfirmationModal) -> ())? = nil
private var internalOnCancel: ((ConfirmationModal) -> ())? = nil
private var internalOnBodyTap: (() -> ())? = nil
// MARK: - Components
@ -151,6 +32,24 @@ public class ConfirmationModal: Modal {
result.textAlignment = .center
result.lineBreakMode = .byWordWrapping
result.numberOfLines = 0
result.isHidden = true
return result
}()
private lazy var imageViewContainer: UIView = {
let result: UIView = UIView()
result.isHidden = true
return result
}()
private lazy var imageView: UIImageView = {
let result: UIImageView = UIImageView()
result.clipsToBounds = true
result.contentMode = .scaleAspectFill
result.set(.width, to: ConfirmationModal.imageSize)
result.set(.height, to: ConfirmationModal.imageSize)
return result
}()
@ -174,7 +73,7 @@ public class ConfirmationModal: Modal {
}()
private lazy var contentStackView: UIStackView = {
let result = UIStackView(arrangedSubviews: [ titleLabel, explanationLabel ])
let result = UIStackView(arrangedSubviews: [ titleLabel, explanationLabel, imageViewContainer ])
result.axis = .vertical
result.spacing = Values.smallSpacing
result.isLayoutMarginsRelativeArrangement = true
@ -185,13 +84,41 @@ public class ConfirmationModal: Modal {
right: Values.largeSpacing
)
let gestureRecogniser: UITapGestureRecognizer = UITapGestureRecognizer(
target: self,
action: #selector(bodyTapped)
)
result.addGestureRecognizer(gestureRecogniser)
return result
}()
private lazy var mainStackView: UIStackView = {
let result = UIStackView(arrangedSubviews: [ contentStackView, buttonStackView ])
result.axis = .vertical
result.spacing = Values.largeSpacing - Values.smallFontSize / 2
return result
}()
private lazy var closeButton: UIButton = {
let result: UIButton = UIButton()
result.setImage(
UIImage(named: "X")?
.withRenderingMode(.alwaysTemplate),
for: .normal
)
result.imageView?.contentMode = .scaleAspectFit
result.themeTintColor = .textPrimary
result.contentEdgeInsets = UIEdgeInsets(
top: 6,
left: 6,
bottom: 6,
right: 6
)
result.set(.width, to: ConfirmationModal.closeSize)
result.set(.height, to: ConfirmationModal.closeSize)
result.addTarget(self, action: #selector(close), for: .touchUpInside)
result.isHidden = true
return result
}()
@ -199,50 +126,11 @@ public class ConfirmationModal: Modal {
// MARK: - Lifecycle
public init(targetView: UIView? = nil, info: Info) {
self.internalOnConfirm = { viewController in
if info.dismissOnConfirm {
viewController.dismiss(animated: true)
}
info.onConfirm?(viewController)
}
super.init(targetView: targetView, afterClosed: info.afterClosed)
super.init(targetView: targetView, dismissType: info.dismissType, afterClosed: info.afterClosed)
self.modalPresentationStyle = .overFullScreen
self.modalTransitionStyle = .crossDissolve
// Set the content based on the provided info
titleLabel.text = info.title
// Note: We should only set the appropriate explanation/attributedExplanation value (as
// setting both when one is null can result in the other being removed)
if let explanation: String = info.explanation {
explanationLabel.text = explanation
}
if let attributedExplanation: NSAttributedString = info.attributedExplanation {
explanationLabel.attributedText = attributedExplanation
}
explanationLabel.isHidden = (
info.explanation == nil &&
info.attributedExplanation == nil
)
confirmButton.accessibilityLabel = info.confirmAccessibilityLabel
confirmButton.accessibilityIdentifier = info.confirmAccessibilityLabel
confirmButton.isAccessibilityElement = true
confirmButton.setTitle(info.confirmTitle, for: .normal)
confirmButton.setThemeTitleColor(info.confirmStyle, for: .normal)
confirmButton.isHidden = (info.confirmTitle == nil)
cancelButton.accessibilityLabel = info.cancelAccessibilityLabel
cancelButton.accessibilityIdentifier = info.cancelAccessibilityLabel
cancelButton.isAccessibilityElement = true
cancelButton.setTitle(info.cancelTitle, for: .normal)
cancelButton.setThemeTitleColor(info.cancelStyle, for: .normal)
self.contentView.accessibilityLabel = info.accessibilityLabel
self.contentView.accessibilityIdentifier = info.accessibilityIdentifier
self.updateContent(with: info)
}
required init?(coder: NSCoder) {
@ -251,13 +139,321 @@ public class ConfirmationModal: Modal {
public override func populateContentView() {
contentView.addSubview(mainStackView)
contentView.addSubview(closeButton)
imageViewContainer.addSubview(imageView)
imageView.center(.horizontal, in: imageViewContainer)
imageView.pin(.top, to: .top, of: imageViewContainer, withInset: 15)
imageView.pin(.bottom, to: .bottom, of: imageViewContainer, withInset: -15)
mainStackView.pin(to: contentView)
closeButton.pin(.top, to: .top, of: contentView, withInset: 8)
closeButton.pin(.right, to: .right, of: contentView, withInset: -8)
}
// MARK: - Content
public func updateContent(with info: Info) {
internalOnBodyTap = nil
internalOnConfirm = { modal in
if info.dismissOnConfirm {
modal.close()
}
info.onConfirm?(modal)
}
internalOnCancel = { modal in
guard info.onCancel != nil else { return modal.close() }
info.onCancel?(modal)
}
// Set the content based on the provided info
titleLabel.text = info.title
switch info.body {
case .none:
mainStackView.spacing = Values.smallSpacing
case .text(let text):
mainStackView.spacing = Values.smallSpacing
explanationLabel.text = text
explanationLabel.isHidden = false
case .attributedText(let attributedText):
mainStackView.spacing = Values.smallSpacing
explanationLabel.attributedText = attributedText
explanationLabel.isHidden = false
case .image(let placeholder, let value, let style, let onClick):
mainStackView.spacing = 0
imageView.image = (value ?? placeholder)
imageView.layer.cornerRadius = (style == .circular ?
(ConfirmationModal.imageSize / 2) :
0
)
imageViewContainer.isHidden = false
internalOnBodyTap = onClick
}
confirmButton.accessibilityLabel = info.confirmAccessibilityLabel
confirmButton.accessibilityIdentifier = info.confirmAccessibilityLabel
confirmButton.isAccessibilityElement = true
confirmButton.setTitle(info.confirmTitle, for: .normal)
confirmButton.setThemeTitleColor(info.confirmStyle, for: .normal)
confirmButton.setThemeTitleColor(.disabled, for: .disabled)
confirmButton.isHidden = (info.confirmTitle == nil)
confirmButton.isEnabled = info.confirmEnabled
cancelButton.accessibilityLabel = info.cancelAccessibilityLabel
cancelButton.accessibilityIdentifier = info.cancelAccessibilityLabel
cancelButton.isAccessibilityElement = true
cancelButton.setTitle(info.cancelTitle, for: .normal)
cancelButton.setThemeTitleColor(info.cancelStyle, for: .normal)
cancelButton.setThemeTitleColor(.disabled, for: .disabled)
cancelButton.isEnabled = info.cancelEnabled
closeButton.isHidden = !info.hasCloseButton
contentView.accessibilityLabel = info.accessibilityLabel
contentView.accessibilityIdentifier = info.accessibilityIdentifier
}
// MARK: - Interaction
@objc private func bodyTapped() {
internalOnBodyTap?()
}
@objc private func confirmationPressed() {
internalOnConfirm(self)
internalOnConfirm?(self)
}
override public func cancel() {
internalOnCancel?(self)
}
}
// MARK: - Types
public extension ConfirmationModal {
struct Info: Equatable, Hashable {
let title: String
let body: Body
let accessibilityLabel: String?
let accessibilityIdentifier: String?
public let showCondition: ShowCondition
let confirmTitle: String?
let confirmAccessibilityLabel: String?
let confirmStyle: ThemeValue
let confirmEnabled: Bool
let cancelTitle: String
let cancelAccessibilityLabel: String?
let cancelStyle: ThemeValue
let cancelEnabled: Bool
let hasCloseButton: Bool
let dismissOnConfirm: Bool
let dismissType: Modal.DismissType
let onConfirm: ((ConfirmationModal) -> ())?
let onCancel: ((ConfirmationModal) -> ())?
let afterClosed: (() -> ())?
// MARK: - Initialization
public init(
title: String,
body: Body = .none,
accessibilityLabel: String? = nil,
accessibilityId: String? = nil,
showCondition: ShowCondition = .none,
confirmTitle: String? = nil,
confirmAccessibilityLabel: String? = nil,
confirmStyle: ThemeValue = .alert_text,
confirmEnabled: Bool = true,
cancelTitle: String = "TXT_CANCEL_TITLE".localized(),
cancelAccessibilityLabel: String? = nil,
cancelStyle: ThemeValue = .danger,
cancelEnabled: Bool = true,
hasCloseButton: Bool = false,
dismissOnConfirm: Bool = true,
dismissType: Modal.DismissType = .recursive,
onConfirm: ((ConfirmationModal) -> ())? = nil,
onCancel: ((ConfirmationModal) -> ())? = nil,
afterClosed: (() -> ())? = nil
) {
self.title = title
self.body = body
self.accessibilityLabel = accessibilityLabel
self.accessibilityIdentifier = accessibilityId
self.showCondition = showCondition
self.confirmTitle = confirmTitle
self.confirmAccessibilityLabel = confirmAccessibilityLabel
self.confirmStyle = confirmStyle
self.confirmEnabled = confirmEnabled
self.cancelTitle = cancelTitle
self.cancelAccessibilityLabel = cancelAccessibilityLabel
self.cancelStyle = cancelStyle
self.cancelEnabled = cancelEnabled
self.hasCloseButton = hasCloseButton
self.dismissOnConfirm = dismissOnConfirm
self.dismissType = dismissType
self.onConfirm = onConfirm
self.onCancel = onCancel
self.afterClosed = afterClosed
}
// MARK: - Mutation
public func with(
body: Body? = nil,
confirmEnabled: Bool? = nil,
cancelEnabled: Bool? = nil,
onConfirm: ((ConfirmationModal) -> ())? = nil,
onCancel: ((ConfirmationModal) -> ())? = nil,
afterClosed: (() -> ())? = nil
) -> Info {
return Info(
title: self.title,
body: (body ?? self.body),
accessibilityLabel: self.accessibilityLabel,
showCondition: self.showCondition,
confirmTitle: self.confirmTitle,
confirmAccessibilityLabel: self.confirmAccessibilityLabel,
confirmStyle: self.confirmStyle,
confirmEnabled: (confirmEnabled ?? self.confirmEnabled),
cancelTitle: self.cancelTitle,
cancelAccessibilityLabel: self.cancelAccessibilityLabel,
cancelStyle: self.cancelStyle,
cancelEnabled: (cancelEnabled ?? self.cancelEnabled),
hasCloseButton: self.hasCloseButton,
dismissOnConfirm: self.dismissOnConfirm,
dismissType: self.dismissType,
onConfirm: (onConfirm ?? self.onConfirm),
onCancel: (onCancel ?? self.onCancel),
afterClosed: (afterClosed ?? self.afterClosed)
)
}
// MARK: - Confirmance
public static func == (lhs: ConfirmationModal.Info, rhs: ConfirmationModal.Info) -> Bool {
return (
lhs.title == rhs.title &&
lhs.body == rhs.body &&
lhs.accessibilityLabel == rhs.accessibilityLabel &&
lhs.showCondition == rhs.showCondition &&
lhs.confirmTitle == rhs.confirmTitle &&
lhs.confirmAccessibilityLabel == rhs.confirmAccessibilityLabel &&
lhs.confirmStyle == rhs.confirmStyle &&
lhs.confirmEnabled == rhs.confirmEnabled &&
lhs.cancelTitle == rhs.cancelTitle &&
lhs.cancelAccessibilityLabel == rhs.cancelAccessibilityLabel &&
lhs.cancelStyle == rhs.cancelStyle &&
lhs.cancelEnabled == rhs.cancelEnabled &&
lhs.hasCloseButton == rhs.hasCloseButton &&
lhs.dismissOnConfirm == rhs.dismissOnConfirm &&
lhs.dismissType == rhs.dismissType
)
}
public func hash(into hasher: inout Hasher) {
title.hash(into: &hasher)
body.hash(into: &hasher)
accessibilityLabel.hash(into: &hasher)
showCondition.hash(into: &hasher)
confirmTitle.hash(into: &hasher)
confirmAccessibilityLabel.hash(into: &hasher)
confirmStyle.hash(into: &hasher)
confirmEnabled.hash(into: &hasher)
cancelTitle.hash(into: &hasher)
cancelAccessibilityLabel.hash(into: &hasher)
cancelStyle.hash(into: &hasher)
cancelEnabled.hash(into: &hasher)
hasCloseButton.hash(into: &hasher)
dismissOnConfirm.hash(into: &hasher)
dismissType.hash(into: &hasher)
}
}
}
public extension ConfirmationModal.Info {
// MARK: - ShowCondition
enum ShowCondition {
case none
case enabled
case disabled
public func shouldShow(for value: Bool) -> Bool {
switch self {
case .none: return true
case .enabled: return (value == true)
case .disabled: return (value == false)
}
}
}
// MARK: - Body
enum Body: Equatable, Hashable {
public enum ImageStyle: Equatable, Hashable {
case inherit
case circular
}
case none
case text(String)
case attributedText(NSAttributedString)
// FIXME: Implement these
// case input(placeholder: String, value: String?)
// case radio(explanation: NSAttributedString?, options: [(title: String, selected: Bool)])
case image(
placeholder: UIImage?,
value: UIImage?,
style: ImageStyle,
onClick: (() -> ())
)
public static func == (lhs: ConfirmationModal.Info.Body, rhs: ConfirmationModal.Info.Body) -> Bool {
switch (lhs, rhs) {
case (.none, .none): return true
case (.text(let lhsText), .text(let rhsText)): return (lhsText == rhsText)
case (.attributedText(let lhsText), .attributedText(let rhsText)): return (lhsText == rhsText)
// FIXME: Implement these
//case (.input(let lhsPlaceholder, let lhsValue), .input(let rhsPlaceholder, let rhsValue)):
// return (
// lhsPlaceholder == rhsPlaceholder &&
// lhsValue == rhsValue &&
// )
// FIXME: Implement these
//case (.radio(let lhsExplanation, let lhsOptions), .radio(let rhsExplanation, let rhsOptions)):
// return (
// lhsExplanation == rhsExplanation &&
// lhsOptions.map { "\($0.0)-\($0.1)" } == rhsValue.map { "\($0.0)-\($0.1)" }
// )
case (.image(let lhsPlaceholder, let lhsValue, let lhsStyle, _), .image(let rhsPlaceholder, let rhsValue, let rhsStyle, _)):
return (
lhsPlaceholder == rhsPlaceholder &&
lhsValue == rhsValue &&
lhsStyle == rhsStyle
)
default: return false
}
}
public func hash(into hasher: inout Hasher) {
switch self {
case .none: break
case .text(let text): text.hash(into: &hasher)
case .attributedText(let text): text.hash(into: &hasher)
case .image(let placeholder, let value, let style, _):
placeholder.hash(into: &hasher)
value.hash(into: &hasher)
style.hash(into: &hasher)
}
}
}
}

View File

@ -6,6 +6,12 @@ import SessionUtilitiesKit
open class Modal: UIViewController, UIGestureRecognizerDelegate {
private static let cornerRadius: CGFloat = 11
public enum DismissType: Equatable, Hashable {
case single
case recursive
}
private let dismissType: DismissType
private let afterClosed: (() -> ())?
// MARK: - Components
@ -47,14 +53,19 @@ open class Modal: UIViewController, UIGestureRecognizerDelegate {
public lazy var cancelButton: UIButton = {
let result: UIButton = Modal.createButton(title: "cancel".localized(), titleColor: .textPrimary)
result.addTarget(self, action: #selector(close), for: .touchUpInside)
result.addTarget(self, action: #selector(cancel), for: .touchUpInside)
return result
}()
// MARK: - Lifecycle
public init(targetView: UIView? = nil, afterClosed: (() -> ())? = nil) {
public init(
targetView: UIView? = nil,
dismissType: DismissType = .recursive,
afterClosed: (() -> ())? = nil
) {
self.dismissType = dismissType
self.afterClosed = afterClosed
super.init(nibName: nil, bundle: nil)
@ -129,13 +140,22 @@ open class Modal: UIViewController, UIGestureRecognizerDelegate {
// MARK: - Interaction
@objc func close() {
@objc public func cancel() {
close()
}
@objc public final func close() {
// Recursively dismiss all modals (ie. find the first modal presented by a non-modal
// and get that to dismiss it's presented view controller)
var targetViewController: UIViewController? = self
while targetViewController?.presentingViewController is Modal {
targetViewController = targetViewController?.presentingViewController
switch dismissType {
case .single: break
case .recursive:
while targetViewController?.presentingViewController is Modal {
targetViewController = targetViewController?.presentingViewController
}
}
targetViewController?.presentingViewController?.dismiss(animated: true) { [weak self] in

View File

@ -21,7 +21,7 @@ public final class Values : NSObject {
@objc public static let smallButtonHeight = isIPhone5OrSmaller ? CGFloat(24) : CGFloat(28)
@objc public static let mediumButtonHeight = isIPhone5OrSmaller ? CGFloat(30) : CGFloat(34)
@objc public static let largeButtonHeight = isIPhone5OrSmaller ? CGFloat(40) : CGFloat(45)
@objc public static let alertButtonHeight: CGFloat = 50
@objc public static let alertButtonHeight: CGFloat = 51 // 19px tall font with 16px margins
@objc public static let accentLineThickness = CGFloat(4)

View File

@ -653,7 +653,7 @@ public class MediaMessageView: UIView, OWSAudioPlayerDelegate {
targetView: CurrentAppContext().frontmostViewController()?.view,
info: ConfirmationModal.Info(
title: CommonStrings.errorAlertTitle,
explanation: "INVALID_AUDIO_FILE_ALERT_ERROR_MESSAGE".localized(),
body: .text("INVALID_AUDIO_FILE_ALERT_ERROR_MESSAGE".localized()),
cancelTitle: "BUTTON_OK".localized(),
cancelStyle: .alert_text
)