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 000000000..bda213589 Binary files /dev/null and b/Session/Meta/Images.xcassets/Session/Pin.imageset/pin.pdf differ 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