From 1cc2f17469ef7c908de37c16c0d3c3090fd7c178 Mon Sep 17 00:00:00 2001 From: Ryan Zhao Date: Wed, 17 Nov 2021 15:51:53 +1100 Subject: [PATCH] pin conversations --- Session/Home/HomeVC.swift | 19 ++++++++- .../Session/Pin.imageset/Contents.json | 12 ++++++ .../Session/Pin.imageset/pin.pdf | Bin 0 -> 4551 bytes .../Translations/en.lproj/Localizable.strings | 2 + Session/Shared/ConversationCell.swift | 15 ++++++- SessionMessagingKit/Database/TSDatabaseView.m | 5 ++- SessionMessagingKit/Threads/TSThread.h | 1 + SessionUIKit/Style Guide/Colors.swift | 1 + .../Contents.json | 38 ++++++++++++++++++ .../Messaging/ThreadViewModel.swift | 2 + 10 files changed, 91 insertions(+), 4 deletions(-) create mode 100644 Session/Meta/Images.xcassets/Session/Pin.imageset/Contents.json create mode 100644 Session/Meta/Images.xcassets/Session/Pin.imageset/pin.pdf create mode 100644 SessionUIKit/Style Guide/Colors.xcassets/session_cell_pinned.colorset/Contents.json diff --git a/Session/Home/HomeVC.swift b/Session/Home/HomeVC.swift index 7c5abfffd..bd1c04564 100644 --- a/Session/Home/HomeVC.swift +++ b/Session/Home/HomeVC.swift @@ -356,6 +356,21 @@ final class HomeVC : BaseVC, UITableViewDataSource, UITableViewDelegate, NewConv self.present(alert, animated: true, completion: nil) } delete.backgroundColor = Colors.destructive + + let isPinned = thread.isPinned + let pin = UITableViewRowAction(style: .normal, title: NSLocalizedString("PIN_BUTTON_TEXT", comment: "")) { _, _ in + thread.isPinned = true + thread.save() + tableView.reloadRows(at: [ indexPath ], with: UITableView.RowAnimation.fade) + } + pin.backgroundColor = Colors.pathsBuilding + let unpin = UITableViewRowAction(style: .normal, title: NSLocalizedString("UNPIN_BUTTON_TEXT", comment: "")) { _, _ in + thread.isPinned = false + thread.save() + tableView.reloadRows(at: [ indexPath ], with: UITableView.RowAnimation.fade) + } + unpin.backgroundColor = Colors.pathsBuilding + if let thread = thread as? TSContactThread { let publicKey = thread.contactSessionID() let blockingManager = SSKEnvironment.shared.blockingManager @@ -370,9 +385,9 @@ final class HomeVC : BaseVC, UITableViewDataSource, UITableViewDelegate, NewConv tableView.reloadRows(at: [ indexPath ], with: UITableView.RowAnimation.fade) } unblock.backgroundColor = Colors.unimportant - return [ delete, (isBlocked ? unblock : block) ] + return [ delete, (isBlocked ? unblock : block), (isPinned ? unpin : pin) ] } else { - return [ delete ] + return [ delete, (isPinned ? unpin : pin) ] } } diff --git a/Session/Meta/Images.xcassets/Session/Pin.imageset/Contents.json b/Session/Meta/Images.xcassets/Session/Pin.imageset/Contents.json new file mode 100644 index 000000000..ed8e0a0bf --- /dev/null +++ b/Session/Meta/Images.xcassets/Session/Pin.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "pin.pdf", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Session/Meta/Images.xcassets/Session/Pin.imageset/pin.pdf b/Session/Meta/Images.xcassets/Session/Pin.imageset/pin.pdf new file mode 100644 index 0000000000000000000000000000000000000000..bda21358994ad192ac1b3dbbc6ea83029c8db076 GIT binary patch literal 4551 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 literal 0 HcmV?d00001 diff --git a/Session/Meta/Translations/en.lproj/Localizable.strings b/Session/Meta/Translations/en.lproj/Localizable.strings index 3e0abe961..d4b3a0152 100644 --- a/Session/Meta/Translations/en.lproj/Localizable.strings +++ b/Session/Meta/Translations/en.lproj/Localizable.strings @@ -576,3 +576,5 @@ "system_mode_theme" = "System"; "dark_mode_theme" = "Dark"; "light_mode_theme" = "Light"; +"PIN_BUTTON_TEXT" = "Pin"; +"UNPIN_BUTTON_TEXT" = "Unpin"; diff --git a/Session/Shared/ConversationCell.swift b/Session/Shared/ConversationCell.swift index 6f8a23afe..2eaf22d3a 100644 --- a/Session/Shared/ConversationCell.swift +++ b/Session/Shared/ConversationCell.swift @@ -58,6 +58,17 @@ final class ConversationCell : UITableViewCell { return result }() + private lazy var isPinnedIcon: UIImageView = { + let result = UIImageView(image: UIImage(named: "Pin")!.withRenderingMode(.alwaysTemplate)) + result.contentMode = .scaleAspectFit + let size = ConversationCell.unreadCountViewSize + result.set(.width, to: size) + result.set(.height, to: size) + result.tintColor = Colors.text + result.layer.masksToBounds = true + return result + }() + private lazy var timestampLabel: UILabel = { let result = UILabel() result.font = .systemFont(ofSize: Values.smallFontSize) @@ -124,7 +135,7 @@ final class ConversationCell : UITableViewCell { hasMentionLabel.pin(to: hasMentionView) // Label stack view let topLabelSpacer = UIView.hStretchingSpacer() - let topLabelStackView = UIStackView(arrangedSubviews: [ displayNameLabel, unreadCountView, hasMentionView, topLabelSpacer, timestampLabel ]) + let topLabelStackView = UIStackView(arrangedSubviews: [ displayNameLabel, isPinnedIcon, unreadCountView, hasMentionView, topLabelSpacer, timestampLabel ]) topLabelStackView.axis = .horizontal topLabelStackView.alignment = .center topLabelStackView.spacing = Values.smallSpacing / 2 // Effectively Values.smallSpacing because there'll be spacing before and after the invisible spacer @@ -182,6 +193,7 @@ final class ConversationCell : UITableViewCell { private func update() { AssertIsOnMainThread() guard let thread = threadViewModel?.threadRecord else { return } + backgroundColor = thread.isPinned ? Colors.cellPinned : Colors.cellBackground let isBlocked: Bool if let thread = thread as? TSContactThread { isBlocked = SSKEnvironment.shared.blockingManager.isRecipientIdBlocked(thread.contactSessionID()) @@ -195,6 +207,7 @@ final class ConversationCell : UITableViewCell { accentLineView.backgroundColor = Colors.accent accentLineView.alpha = threadViewModel.hasUnreadMessages ? 1 : 0.0001 // Setting the alpha to exactly 0 causes an issue on iOS 12 } + isPinnedIcon.isHidden = !threadViewModel.isPinned unreadCountView.isHidden = !threadViewModel.hasUnreadMessages let unreadCount = threadViewModel.unreadCount unreadCountLabel.text = unreadCount < 100 ? "\(unreadCount)" : "99+" diff --git a/SessionMessagingKit/Database/TSDatabaseView.m b/SessionMessagingKit/Database/TSDatabaseView.m index 4668aeb44..d899289e4 100644 --- a/SessionMessagingKit/Database/TSDatabaseView.m +++ b/SessionMessagingKit/Database/TSDatabaseView.m @@ -278,7 +278,10 @@ NSString *const TSLazyRestoreAttachmentsGroup = @"TSLazyRestoreAttachmentsGroup" TSThread *thread1 = (TSThread *)object1; TSThread *thread2 = (TSThread *)object2; if ([group isEqualToString:TSArchiveGroup] || [group isEqualToString:TSInboxGroup]) { - + if (thread1.isPinned != thread2.isPinned) { + if (thread1.isPinned) { return NSOrderedDescending; } + if (thread2.isPinned) { return NSOrderedAscending; } + } TSInteraction *_Nullable lastInteractionForInbox1 = [thread1 lastInteractionForInboxWithTransaction:transaction]; NSDate *lastInteractionForInboxDate1 = lastInteractionForInbox1 ? lastInteractionForInbox1.receivedAtDate : thread1.creationDate; diff --git a/SessionMessagingKit/Threads/TSThread.h b/SessionMessagingKit/Threads/TSThread.h index aa333d245..c2449397a 100644 --- a/SessionMessagingKit/Threads/TSThread.h +++ b/SessionMessagingKit/Threads/TSThread.h @@ -16,6 +16,7 @@ BOOL IsNoteToSelfEnabled(void); */ @interface TSThread : TSYapDatabaseObject +@property (nonatomic) BOOL isPinned; @property (nonatomic) BOOL shouldBeVisible; @property (nonatomic, readonly) NSDate *creationDate; @property (nonatomic, readonly, nullable) NSDate *lastInteractionDate; diff --git a/SessionUIKit/Style Guide/Colors.swift b/SessionUIKit/Style Guide/Colors.swift index 4d25e7cc5..06f4e3095 100644 --- a/SessionUIKit/Style Guide/Colors.swift +++ b/SessionUIKit/Style Guide/Colors.swift @@ -20,6 +20,7 @@ public final class Colors : NSObject { @objc public static var border: UIColor { UIColor(named: "session_border")! } @objc public static var cellBackground: UIColor { UIColor(named: "session_cell_background")! } @objc public static var cellSelected: UIColor { UIColor(named: "session_cell_selected")! } + @objc public static var cellPinned: UIColor { UIColor(named: "session_cell_pinned")! } @objc public static var navigationBarBackground: UIColor { UIColor(named: "session_navigation_bar_background")! } @objc public static var searchBarPlaceholder: UIColor { UIColor(named: "session_search_bar_placeholder")! } // Also used for the icons @objc public static var searchBarBackground: UIColor { UIColor(named: "session_search_bar_background")! } diff --git a/SessionUIKit/Style Guide/Colors.xcassets/session_cell_pinned.colorset/Contents.json b/SessionUIKit/Style Guide/Colors.xcassets/session_cell_pinned.colorset/Contents.json new file mode 100644 index 000000000..a5a409cf4 --- /dev/null +++ b/SessionUIKit/Style Guide/Colors.xcassets/session_cell_pinned.colorset/Contents.json @@ -0,0 +1,38 @@ +{ + "colors" : [ + { + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0xF0", + "green" : "0xF0", + "red" : "0xF0" + } + }, + "idiom" : "universal" + }, + { + "appearances" : [ + { + "appearance" : "luminosity", + "value" : "dark" + } + ], + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0x40", + "green" : "0x40", + "red" : "0x40" + } + }, + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/SignalUtilitiesKit/Messaging/ThreadViewModel.swift b/SignalUtilitiesKit/Messaging/ThreadViewModel.swift index 0162b873f..2e6f9bab1 100644 --- a/SignalUtilitiesKit/Messaging/ThreadViewModel.swift +++ b/SignalUtilitiesKit/Messaging/ThreadViewModel.swift @@ -14,6 +14,7 @@ public class ThreadViewModel: NSObject { @objc public let contactSessionID: String? @objc public let name: String @objc public let isMuted: Bool + @objc public let isPinned: Bool @objc public let isOnlyNotifyingForMentions: Bool @objc public let hasUnreadMentions: Bool @@ -31,6 +32,7 @@ public class ThreadViewModel: NSObject { self.isGroupThread = thread.isGroupThread() self.name = thread.name() self.isMuted = thread.isMuted + self.isPinned = thread.isPinned self.lastMessageText = thread.lastMessageText(transaction: transaction) let lastInteraction = thread.lastInteractionForInbox(transaction: transaction) self.lastMessageForInbox = lastInteraction