Further implement home screen redesign
|
@ -11,7 +11,7 @@
|
|||
241C6315231F64CE00B4198E /* CGFloat+Rounding.swift in Sources */ = {isa = PBXBuildFile; fileRef = 241C6312231F5F1D00B4198E /* CGFloat+Rounding.swift */; };
|
||||
241C6316231F64CE00B4198E /* UIColor+Helper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 241C6310231F5C4400B4198E /* UIColor+Helper.swift */; };
|
||||
24A830A22293CD0100F4CAC0 /* LokiP2PServer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 24A830A12293CD0100F4CAC0 /* LokiP2PServer.swift */; };
|
||||
24BD2609234DA2050008EB0A /* NewPublicChatVC.swift in Sources */ = {isa = PBXBuildFile; fileRef = 24BD2608234DA2050008EB0A /* NewPublicChatVC.swift */; };
|
||||
24BD2609234DA2050008EB0A /* JoinPublicChatVC.swift in Sources */ = {isa = PBXBuildFile; fileRef = 24BD2608234DA2050008EB0A /* JoinPublicChatVC.swift */; };
|
||||
2AE2882E4C2B96BFFF9EE27C /* Pods_SignalShareExtension.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 0F94C85CB0B235DA37F68ED0 /* Pods_SignalShareExtension.framework */; };
|
||||
3403B95D20EA9527001A1F44 /* OWSContactShareButtonsView.m in Sources */ = {isa = PBXBuildFile; fileRef = 3403B95B20EA9526001A1F44 /* OWSContactShareButtonsView.m */; };
|
||||
34074F61203D0CBE004596AE /* OWSSounds.m in Sources */ = {isa = PBXBuildFile; fileRef = 34074F5F203D0CBD004596AE /* OWSSounds.m */; };
|
||||
|
@ -590,6 +590,7 @@
|
|||
B8BB82A9238F62FB00BA5194 /* Gradients.swift in Sources */ = {isa = PBXBuildFile; fileRef = B8BB82A8238F62FB00BA5194 /* Gradients.swift */; };
|
||||
B8BB82AB238F669C00BA5194 /* ConversationCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = B8BB82AA238F669C00BA5194 /* ConversationCell.swift */; };
|
||||
B8BB82AD238F734800BA5194 /* ProfilePictureView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B8BB82AC238F734800BA5194 /* ProfilePictureView.swift */; };
|
||||
B8BB82B12390C37000BA5194 /* SearchBar.swift in Sources */ = {isa = PBXBuildFile; fileRef = B8BB82B02390C37000BA5194 /* SearchBar.swift */; };
|
||||
B90418E6183E9DD40038554A /* DateUtil.m in Sources */ = {isa = PBXBuildFile; fileRef = B90418E5183E9DD40038554A /* DateUtil.m */; };
|
||||
B9EB5ABD1884C002007CBB57 /* MessageUI.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = B9EB5ABC1884C002007CBB57 /* MessageUI.framework */; };
|
||||
BFF3FB9730634F37D25903F4 /* Pods_Signal.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D17BB5C25D615AB49813100C /* Pods_Signal.framework */; };
|
||||
|
@ -691,7 +692,7 @@
|
|||
241C6310231F5C4400B4198E /* UIColor+Helper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIColor+Helper.swift"; sourceTree = "<group>"; };
|
||||
241C6312231F5F1D00B4198E /* CGFloat+Rounding.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "CGFloat+Rounding.swift"; sourceTree = "<group>"; };
|
||||
24A830A12293CD0100F4CAC0 /* LokiP2PServer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LokiP2PServer.swift; sourceTree = "<group>"; };
|
||||
24BD2608234DA2050008EB0A /* NewPublicChatVC.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NewPublicChatVC.swift; sourceTree = "<group>"; };
|
||||
24BD2608234DA2050008EB0A /* JoinPublicChatVC.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = JoinPublicChatVC.swift; sourceTree = "<group>"; };
|
||||
264242150E87D10A357DB07B /* Pods_SignalMessaging.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_SignalMessaging.framework; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
3403B95B20EA9526001A1F44 /* OWSContactShareButtonsView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = OWSContactShareButtonsView.m; sourceTree = "<group>"; };
|
||||
3403B95C20EA9527001A1F44 /* OWSContactShareButtonsView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = OWSContactShareButtonsView.h; sourceTree = "<group>"; };
|
||||
|
@ -1408,6 +1409,7 @@
|
|||
B8BB82A8238F62FB00BA5194 /* Gradients.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Gradients.swift; sourceTree = "<group>"; };
|
||||
B8BB82AA238F669C00BA5194 /* ConversationCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConversationCell.swift; sourceTree = "<group>"; };
|
||||
B8BB82AC238F734800BA5194 /* ProfilePictureView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProfilePictureView.swift; sourceTree = "<group>"; };
|
||||
B8BB82B02390C37000BA5194 /* SearchBar.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SearchBar.swift; sourceTree = "<group>"; };
|
||||
B90418E4183E9DD40038554A /* DateUtil.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DateUtil.h; sourceTree = "<group>"; };
|
||||
B90418E5183E9DD40038554A /* DateUtil.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = DateUtil.m; sourceTree = "<group>"; };
|
||||
B97940251832BD2400BD66CB /* UIUtil.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = UIUtil.h; sourceTree = "<group>"; };
|
||||
|
@ -2670,6 +2672,7 @@
|
|||
B86BD0882339A253000F5AE3 /* Utilities */,
|
||||
B8BB82A4238F627000BA5194 /* HomeVC.swift */,
|
||||
B8BB82AA238F669C00BA5194 /* ConversationCell.swift */,
|
||||
B8BB82B02390C37000BA5194 /* SearchBar.swift */,
|
||||
B8BB82AC238F734800BA5194 /* ProfilePictureView.swift */,
|
||||
);
|
||||
path = Loki;
|
||||
|
@ -2741,7 +2744,7 @@
|
|||
B8B26C8E234D629C004ED98C /* MentionCandidateSelectionView.swift */,
|
||||
B8B26C90234D8CBD004ED98C /* MentionCandidateSelectionViewDelegate.swift */,
|
||||
B89841E222B7579F00B1BDC6 /* NewConversationVC.swift */,
|
||||
24BD2608234DA2050008EB0A /* NewPublicChatVC.swift */,
|
||||
24BD2608234DA2050008EB0A /* JoinPublicChatVC.swift */,
|
||||
B893063E2383961A005EAA8E /* ScanQRCodeWrapperVC.swift */,
|
||||
);
|
||||
path = Messaging;
|
||||
|
@ -3867,6 +3870,7 @@
|
|||
3448E15C22133274004B052E /* OnboardingPermissionsViewController.swift in Sources */,
|
||||
34D920E720E179C200D51158 /* OWSMessageFooterView.m in Sources */,
|
||||
341341EF2187467A00192D59 /* ConversationViewModel.m in Sources */,
|
||||
B8BB82B12390C37000BA5194 /* SearchBar.swift in Sources */,
|
||||
348BB25D20A0C5530047AEC2 /* ContactShareViewHelper.swift in Sources */,
|
||||
34B3F8801E8DF1700035BE1A /* InviteFlow.swift in Sources */,
|
||||
457C87B82032645C008D52D6 /* DebugUINotifications.swift in Sources */,
|
||||
|
@ -3896,7 +3900,7 @@
|
|||
B821F2F82272CED3002C88C0 /* DisplayNameVC.swift in Sources */,
|
||||
34D8C0271ED3673300188D7C /* DebugUIMessages.m in Sources */,
|
||||
B885D5F62334A32100EE0D8E /* UIView+Constraints.swift in Sources */,
|
||||
24BD2609234DA2050008EB0A /* NewPublicChatVC.swift in Sources */,
|
||||
24BD2609234DA2050008EB0A /* JoinPublicChatVC.swift in Sources */,
|
||||
34DBF003206BD5A500025978 /* OWSMessageTextView.m in Sources */,
|
||||
34D1F0B41F86D31D0066283D /* ConversationCollectionView.m in Sources */,
|
||||
34B3F8821E8DF1700035BE1A /* NewContactThreadViewController.m in Sources */,
|
||||
|
|
After Width: | Height: | Size: 589 B |
After Width: | Height: | Size: 1.0 KiB |
|
@ -0,0 +1,22 @@
|
|||
{
|
||||
"images" : [
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"filename" : "Contacts@1x.png",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"filename" : "Contacts@2x.png",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "3x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"version" : 1,
|
||||
"author" : "xcode"
|
||||
}
|
||||
}
|
|
@ -0,0 +1,6 @@
|
|||
{
|
||||
"info" : {
|
||||
"version" : 1,
|
||||
"author" : "xcode"
|
||||
}
|
||||
}
|
|
@ -0,0 +1,22 @@
|
|||
{
|
||||
"images" : [
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"filename" : "Globe@1x.png",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"filename" : "Globe@2x.png",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "3x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"version" : 1,
|
||||
"author" : "xcode"
|
||||
}
|
||||
}
|
After Width: | Height: | Size: 1.2 KiB |
After Width: | Height: | Size: 2.3 KiB |
|
@ -0,0 +1,22 @@
|
|||
{
|
||||
"images" : [
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"filename" : "TickFilled@1x.png",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"filename" : "TickFilled@2x.png",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "3x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"version" : 1,
|
||||
"author" : "xcode"
|
||||
}
|
||||
}
|
After Width: | Height: | Size: 257 B |
After Width: | Height: | Size: 464 B |
|
@ -0,0 +1,22 @@
|
|||
{
|
||||
"images" : [
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"filename" : "TickOutline@1x.png",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"filename" : "TickOutline@2x.png",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "3x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"version" : 1,
|
||||
"author" : "xcode"
|
||||
}
|
||||
}
|
After Width: | Height: | Size: 320 B |
After Width: | Height: | Size: 571 B |
|
@ -1,11 +1,11 @@
|
|||
|
||||
final class ConversationCell : UITableViewCell {
|
||||
public var threadViewModel: ThreadViewModel! { didSet { update() } }
|
||||
var threadViewModel: ThreadViewModel! { didSet { update() } }
|
||||
|
||||
public static let reuseIdentifier = "ConversationCell"
|
||||
static let reuseIdentifier = "ConversationCell"
|
||||
|
||||
// MARK: Components
|
||||
private lazy var unreadMessagesIndicator: UIView = {
|
||||
private lazy var unreadMessagesIndicatorView: UIView = {
|
||||
let result = UIView()
|
||||
result.backgroundColor = Colors.accent
|
||||
return result
|
||||
|
@ -21,6 +21,15 @@ final class ConversationCell : UITableViewCell {
|
|||
return result
|
||||
}()
|
||||
|
||||
private lazy var timestampLabel: UILabel = {
|
||||
let result = UILabel()
|
||||
result.font = .systemFont(ofSize: Values.smallFontSize)
|
||||
result.textColor = Colors.text
|
||||
result.lineBreakMode = .byTruncatingTail
|
||||
result.alpha = Values.conversationCellTimestampOpacity
|
||||
return result
|
||||
}()
|
||||
|
||||
private lazy var snippetLabel: UILabel = {
|
||||
let result = UILabel()
|
||||
result.font = .systemFont(ofSize: Values.smallFontSize)
|
||||
|
@ -31,6 +40,14 @@ final class ConversationCell : UITableViewCell {
|
|||
|
||||
private lazy var typingIndicatorView = TypingIndicatorView()
|
||||
|
||||
private lazy var bottomLabelStackViewSpacer = UIView.hStretchingSpacer()
|
||||
|
||||
private lazy var statusIndicatorView: UIImageView = {
|
||||
let result = UIImageView()
|
||||
result.contentMode = .center
|
||||
return result
|
||||
}()
|
||||
|
||||
// MARK: Initialization
|
||||
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
|
||||
super.init(style: style, reuseIdentifier: reuseIdentifier)
|
||||
|
@ -43,31 +60,44 @@ final class ConversationCell : UITableViewCell {
|
|||
}
|
||||
|
||||
private func setUpViewHierarchy() {
|
||||
// Make the cell transparent
|
||||
backgroundColor = .clear
|
||||
// Set up the unread messages indicator
|
||||
unreadMessagesIndicator.set(.width, to: Values.accentLineThickness)
|
||||
// Set the cell background color
|
||||
backgroundColor = Colors.conversationCellBackground
|
||||
// Set up the highlight color
|
||||
let selectedBackgroundView = UIView()
|
||||
selectedBackgroundView.backgroundColor = Colors.conversationCellSelected
|
||||
self.selectedBackgroundView = selectedBackgroundView
|
||||
// Set up the unread messages indicator view
|
||||
unreadMessagesIndicatorView.set(.width, to: Values.accentLineThickness)
|
||||
// Set up the profile picture view
|
||||
let profilePictureViewSize = Values.mediumProfilePictureSize
|
||||
profilePictureView.set(.width, to: profilePictureViewSize)
|
||||
profilePictureView.set(.height, to: profilePictureViewSize)
|
||||
profilePictureView.size = profilePictureViewSize
|
||||
// Set up the label stack view
|
||||
let topLabelStackView = UIStackView(arrangedSubviews: [ displayNameLabel, UIView.hStretchingSpacer(), timestampLabel ])
|
||||
topLabelStackView.axis = .horizontal
|
||||
topLabelStackView.spacing = Values.smallSpacing / 2 // Effectively Values.smallSpacing because there'll be spacing before and after the invisible spacer
|
||||
let snippetLabelContainer = UIView()
|
||||
snippetLabelContainer.addSubview(snippetLabel)
|
||||
snippetLabelContainer.addSubview(typingIndicatorView)
|
||||
let labelStackView = UIStackView(arrangedSubviews: [ UIView.spacer(withHeight: Values.smallSpacing), displayNameLabel, snippetLabelContainer, UIView.spacer(withHeight: Values.smallSpacing) ])
|
||||
let bottomLabelStackView = UIStackView(arrangedSubviews: [ snippetLabelContainer, bottomLabelStackViewSpacer, statusIndicatorView ])
|
||||
bottomLabelStackView.axis = .horizontal
|
||||
bottomLabelStackView.spacing = Values.smallSpacing / 2 // Effectively Values.smallSpacing because there'll be spacing before and after the invisible spacer
|
||||
let labelStackView = UIStackView(arrangedSubviews: [ UIView.spacer(withHeight: Values.smallSpacing), topLabelStackView, bottomLabelStackView, UIView.spacer(withHeight: Values.smallSpacing) ])
|
||||
labelStackView.axis = .vertical
|
||||
labelStackView.spacing = Values.smallSpacing
|
||||
// Set up the main stack view
|
||||
let stackView = UIStackView(arrangedSubviews: [ unreadMessagesIndicator, profilePictureView, labelStackView ])
|
||||
let stackView = UIStackView(arrangedSubviews: [ unreadMessagesIndicatorView, profilePictureView, labelStackView ])
|
||||
stackView.axis = .horizontal
|
||||
stackView.alignment = .center
|
||||
stackView.spacing = Values.mediumSpacing
|
||||
contentView.addSubview(stackView)
|
||||
// Set up the constraints
|
||||
unreadMessagesIndicator.pin(.top, to: .top, of: stackView)
|
||||
unreadMessagesIndicator.pin(.bottom, to: .bottom, of: stackView)
|
||||
unreadMessagesIndicatorView.pin(.top, to: .top, of: stackView)
|
||||
unreadMessagesIndicatorView.pin(.bottom, to: .bottom, of: stackView)
|
||||
timestampLabel.setContentCompressionResistancePriority(.required, for: NSLayoutConstraint.Axis.horizontal)
|
||||
statusIndicatorView.set(.width, to: Values.conversationCellStatusIndicatorSize)
|
||||
statusIndicatorView.set(.height, to: Values.conversationCellStatusIndicatorSize)
|
||||
snippetLabel.pin(to: snippetLabelContainer)
|
||||
typingIndicatorView.pin(.leading, to: .leading, of: snippetLabelContainer)
|
||||
typingIndicatorView.centerYAnchor.constraint(equalTo: snippetLabel.centerYAnchor).isActive = true
|
||||
|
@ -75,18 +105,13 @@ final class ConversationCell : UITableViewCell {
|
|||
stackView.pin(.top, to: .top, of: contentView)
|
||||
contentView.pin(.trailing, to: .trailing, of: stackView, withInset: Values.mediumSpacing)
|
||||
contentView.pin(.bottom, to: .bottom, of: stackView)
|
||||
stackView.set(.width, to: UIScreen.main.bounds.width - 2 * Values.mediumSpacing) // Workaround for weird constraints issue
|
||||
stackView.set(.width, to: UIScreen.main.bounds.width - Values.mediumSpacing) // Workaround for weird constraints issue
|
||||
}
|
||||
|
||||
// MARK: Updating
|
||||
private func update() {
|
||||
LokiAPI.populateUserHexEncodedPublicKeyCacheIfNeeded(for: threadViewModel.threadRecord.uniqueId!) // FIXME: This is a terrible place to do this
|
||||
unreadMessagesIndicator.isHidden = !threadViewModel.hasUnreadMessages
|
||||
if threadViewModel.hasUnreadMessages {
|
||||
backgroundColor = UIColor(hex: 0x1B1B1B)
|
||||
} else {
|
||||
backgroundColor = .clear
|
||||
}
|
||||
unreadMessagesIndicatorView.alpha = threadViewModel.hasUnreadMessages ? 1 : 0
|
||||
if threadViewModel.isGroupThread {
|
||||
let users = LokiAPI.userHexEncodedPublicKeyCache[threadViewModel.threadRecord.uniqueId!] ?? []
|
||||
let randomUsers = users.sorted().prefix(2) // Sort to provide a level of stability
|
||||
|
@ -102,6 +127,7 @@ final class ConversationCell : UITableViewCell {
|
|||
}
|
||||
profilePictureView.update()
|
||||
displayNameLabel.text = getDisplayName()
|
||||
timestampLabel.text = DateUtil.formatDateShort(threadViewModel.lastMessageDate)
|
||||
if SSKEnvironment.shared.typingIndicators.typingRecipientId(forThread: self.threadViewModel.threadRecord) != nil {
|
||||
snippetLabel.text = ""
|
||||
typingIndicatorView.isHidden = false
|
||||
|
@ -111,6 +137,21 @@ final class ConversationCell : UITableViewCell {
|
|||
typingIndicatorView.isHidden = true
|
||||
typingIndicatorView.stopAnimation()
|
||||
}
|
||||
let lastMessage = threadViewModel.lastMessageForInbox
|
||||
if let lastMessage = lastMessage as? TSOutgoingMessage {
|
||||
let image: UIImage
|
||||
let status = MessageRecipientStatusUtils.recipientStatus(outgoingMessage: lastMessage)
|
||||
switch status {
|
||||
case .calculatingPoW, .uploading, .sending: image = #imageLiteral(resourceName: "Cog")
|
||||
case .sent, .skipped, .delivered: image = #imageLiteral(resourceName: "TickOutline")
|
||||
case .read: image = #imageLiteral(resourceName: "TickFilled")
|
||||
case .failed: image = #imageLiteral(resourceName: "message_status_failed")
|
||||
}
|
||||
statusIndicatorView.image = image
|
||||
statusIndicatorView.isHidden = false
|
||||
} else {
|
||||
statusIndicatorView.isHidden = true
|
||||
}
|
||||
}
|
||||
|
||||
private func getDisplayName() -> String {
|
||||
|
@ -133,11 +174,12 @@ final class ConversationCell : UITableViewCell {
|
|||
private func getSnippet() -> NSMutableAttributedString {
|
||||
let result = NSMutableAttributedString()
|
||||
if threadViewModel.isMuted {
|
||||
result.append(NSAttributedString(string: "\u{e067} ", attributes: [ .font : UIFont.ows_elegantIconsFont(10), .foregroundColor : Colors.unimportant ]))
|
||||
result.append(NSAttributedString(string: "\u{e067} ", attributes: [ .font : UIFont.ows_elegantIconsFont(10), .foregroundColor : Colors.unimportant ]))
|
||||
}
|
||||
if let rawSnippet = threadViewModel.lastMessageText {
|
||||
let snippet = MentionUtilities.highlightMentions(in: rawSnippet, threadID: threadViewModel.threadRecord.uniqueId!)
|
||||
result.append(NSAttributedString(string: snippet, attributes: [ .font : UIFont.systemFont(ofSize: Values.smallFontSize), .foregroundColor : Colors.text ]))
|
||||
let font = threadViewModel.hasUnreadMessages ? UIFont.boldSystemFont(ofSize: Values.smallFontSize) : UIFont.systemFont(ofSize: Values.smallFontSize)
|
||||
result.append(NSAttributedString(string: snippet, attributes: [ .font : font, .foregroundColor : Colors.text ]))
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
|
||||
public final class HomeVC : UIViewController, UITableViewDataSource, UITableViewDelegate {
|
||||
final class HomeVC : UIViewController, UITableViewDataSource, UITableViewDelegate, UIViewControllerPreviewingDelegate {
|
||||
private var threadViewModelCache: [String:ThreadViewModel] = [:]
|
||||
private var isObservingDatabase = true
|
||||
private var isViewVisible = false { didSet { updateIsObservingDatabase() } }
|
||||
|
||||
private var threads: YapDatabaseViewMappings = {
|
||||
let result = YapDatabaseViewMappings(groups: [ TSInboxGroup ], view: TSThreadDatabaseViewExtensionName)
|
||||
|
@ -14,10 +16,14 @@ public final class HomeVC : UIViewController, UITableViewDataSource, UITableView
|
|||
return result
|
||||
}()
|
||||
|
||||
private let editingDatabaseConnection = OWSPrimaryStorage.shared().newDatabaseConnection()
|
||||
|
||||
// MARK: Settings
|
||||
public override var preferredStatusBarStyle: UIStatusBarStyle { return .lightContent }
|
||||
|
||||
// MARK: Components
|
||||
private lazy var searchBar = SearchBar()
|
||||
|
||||
private lazy var tableView: UITableView = {
|
||||
let result = UITableView()
|
||||
result.backgroundColor = .clear
|
||||
|
@ -27,20 +33,97 @@ public final class HomeVC : UIViewController, UITableViewDataSource, UITableView
|
|||
}()
|
||||
|
||||
// MARK: Lifecycle
|
||||
public override func viewDidLoad() {
|
||||
override func viewDidLoad() {
|
||||
// Set gradient background
|
||||
view.backgroundColor = .clear
|
||||
let gradient = Gradients.defaultLokiBackground
|
||||
view.setGradient(gradient)
|
||||
// Set navigation bar background color
|
||||
if let navigationBar = navigationController?.navigationBar {
|
||||
navigationBar.setBackgroundImage(UIImage(), for: UIBarMetrics.default)
|
||||
navigationBar.shadowImage = UIImage()
|
||||
navigationBar.isTranslucent = false
|
||||
navigationBar.barTintColor = Colors.navigationBarBackground
|
||||
}
|
||||
// Set up the navigation bar buttons
|
||||
updateNavigationBarButtons()
|
||||
// Customize title
|
||||
navigationItem.title = NSLocalizedString("Messages", comment: "")
|
||||
navigationController?.navigationBar.titleTextAttributes = [ .foregroundColor : Colors.text, .font : UIFont.boldSystemFont(ofSize: Values.veryLargeFontSize) ]
|
||||
let titleLabel = UILabel()
|
||||
titleLabel.text = NSLocalizedString("Messages", comment: "")
|
||||
titleLabel.textColor = Colors.text
|
||||
titleLabel.font = UIFont.boldSystemFont(ofSize: Values.veryLargeFontSize)
|
||||
navigationItem.titleView = titleLabel
|
||||
// Set up table view
|
||||
tableView.dataSource = self
|
||||
tableView.delegate = self
|
||||
view.addSubview(tableView)
|
||||
tableView.pin(to: view)
|
||||
// Set up search bar
|
||||
tableView.tableHeaderView = searchBar
|
||||
searchBar.sizeToFit()
|
||||
tableView.contentOffset = CGPoint(x: 0, y: searchBar.frame.height)
|
||||
// Set up new conversation button
|
||||
// let newConversationButton = UIImageView(image: #imageLiteral(resourceName: "ic_plus_24").asTintedImage(color: UIColor(hex: 0x121212)))
|
||||
// newConversationButton.backgroundColor = Colors.accent
|
||||
// newConversationButton.set(.width, to: Values.newConversationButtonSize)
|
||||
// newConversationButton.set(.height, to: Values.newConversationButtonSize)
|
||||
// view.addSubview(newConversationButton)
|
||||
// newConversationButton.center(.horizontal, in: view)
|
||||
// newConversationButton.center(.vertical, in: view)
|
||||
// Set up previewing
|
||||
if (traitCollection.forceTouchCapability == .available) {
|
||||
registerForPreviewing(with: self, sourceView: tableView)
|
||||
}
|
||||
// Listen for notifications
|
||||
let notificationCenter = NotificationCenter.default
|
||||
notificationCenter.addObserver(self, selector: #selector(handleYapDatabaseModifiedNotification(_:)), name: .YapDatabaseModified, object: OWSPrimaryStorage.shared().dbNotificationObject)
|
||||
notificationCenter.addObserver(self, selector: #selector(handleYapDatabaseModifiedExternallyNotification(_:)), name: .YapDatabaseModifiedExternally, object: OWSPrimaryStorage.shared().dbNotificationObject)
|
||||
notificationCenter.addObserver(self, selector: #selector(handleApplicationDidBecomeActiveNotification(_:)), name: .OWSApplicationDidBecomeActive, object: nil)
|
||||
notificationCenter.addObserver(self, selector: #selector(handleApplicationWillResignActiveNotification(_:)), name: .OWSApplicationWillResignActive, object: nil)
|
||||
notificationCenter.addObserver(self, selector: #selector(handleLocalProfileDidChangeNotification(_:)), name: Notification.Name(kNSNotificationName_LocalProfileDidChange), object: nil)
|
||||
// Set up public chats and RSS feeds if needed
|
||||
if OWSIdentityManager.shared().identityKeyPair() != nil {
|
||||
let appDelegate = UIApplication.shared.delegate as! AppDelegate
|
||||
appDelegate.setUpDefaultPublicChatsIfNeeded()
|
||||
appDelegate.createRSSFeedsIfNeeded()
|
||||
LokiPublicChatManager.shared.startPollersIfNeeded()
|
||||
appDelegate.startRSSFeedPollersIfNeeded()
|
||||
}
|
||||
// Do initial update
|
||||
reload()
|
||||
}
|
||||
|
||||
override func viewDidAppear(_ animated: Bool) {
|
||||
super.viewDidAppear(animated)
|
||||
isViewVisible = true
|
||||
}
|
||||
|
||||
override func viewWillDisappear(_ animated: Bool) {
|
||||
super.viewWillDisappear(animated)
|
||||
isViewVisible = false
|
||||
}
|
||||
|
||||
deinit {
|
||||
NotificationCenter.default.removeObserver(self)
|
||||
}
|
||||
|
||||
// MARK: Data
|
||||
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
|
||||
return Int(threads.numberOfItems(inGroup: TSInboxGroup))
|
||||
}
|
||||
|
||||
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
|
||||
let cell = tableView.dequeueReusableCell(withIdentifier: ConversationCell.reuseIdentifier) as! ConversationCell
|
||||
cell.threadViewModel = threadViewModel(at: indexPath.row)
|
||||
return cell
|
||||
}
|
||||
|
||||
// MARK: Updating
|
||||
private func updateIsObservingDatabase() {
|
||||
isObservingDatabase = isViewVisible && CurrentAppContext().isAppForegroundAndActive()
|
||||
}
|
||||
|
||||
private func reload() {
|
||||
uiDatabaseConnection.beginLongLivedReadTransaction()
|
||||
uiDatabaseConnection.read { transaction in
|
||||
self.threads.update(with: transaction)
|
||||
|
@ -48,15 +131,156 @@ public final class HomeVC : UIViewController, UITableViewDataSource, UITableView
|
|||
tableView.reloadData()
|
||||
}
|
||||
|
||||
// MARK: Data
|
||||
public func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
|
||||
return Int(threads.numberOfItems(inGroup: TSInboxGroup))
|
||||
@objc private func handleYapDatabaseModifiedExternallyNotification(_ notification: Notification) {
|
||||
guard isObservingDatabase else { return }
|
||||
reload()
|
||||
}
|
||||
|
||||
public func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
|
||||
let cell = tableView.dequeueReusableCell(withIdentifier: ConversationCell.reuseIdentifier) as! ConversationCell
|
||||
cell.threadViewModel = threadViewModel(at: indexPath.row)
|
||||
return cell
|
||||
@objc private func handleYapDatabaseModifiedNotification(_ notification: Notification) {
|
||||
guard isObservingDatabase else { return }
|
||||
let transaction = uiDatabaseConnection.beginLongLivedReadTransaction()
|
||||
let hasChanges = (uiDatabaseConnection.ext(TSThreadDatabaseViewExtensionName) as! YapDatabaseViewConnection).hasChanges(forGroup: TSInboxGroup, in: transaction)
|
||||
guard hasChanges else {
|
||||
uiDatabaseConnection.read { transaction in
|
||||
self.threads.update(with: transaction)
|
||||
}
|
||||
return
|
||||
}
|
||||
var sectionChanges = NSArray()
|
||||
var rowChanges = NSArray()
|
||||
(uiDatabaseConnection.ext(TSThreadDatabaseViewExtensionName) as! YapDatabaseViewConnection).getSectionChanges(§ionChanges, rowChanges: &rowChanges, for: transaction, with: threads)
|
||||
guard sectionChanges.count > 0 || rowChanges.count > 0 else { return }
|
||||
tableView.beginUpdates()
|
||||
rowChanges.forEach { rowChange in
|
||||
let rowChange = rowChange as! YapDatabaseViewRowChange
|
||||
let key = rowChange.collectionKey.key
|
||||
threadViewModelCache[key] = nil
|
||||
switch rowChange.type {
|
||||
case .delete: tableView.deleteRows(at: [ rowChange.indexPath! ], with: UITableView.RowAnimation.automatic)
|
||||
case .insert: tableView.insertRows(at: [ rowChange.newIndexPath! ], with: UITableView.RowAnimation.automatic)
|
||||
case .move:
|
||||
tableView.deleteRows(at: [ rowChange.indexPath! ], with: UITableView.RowAnimation.automatic)
|
||||
tableView.insertRows(at: [ rowChange.newIndexPath! ], with: UITableView.RowAnimation.automatic)
|
||||
case .update:
|
||||
tableView.reloadRows(at: [ rowChange.indexPath! ], with: UITableView.RowAnimation.none)
|
||||
default: break
|
||||
}
|
||||
}
|
||||
tableView.endUpdates()
|
||||
}
|
||||
|
||||
@objc private func handleApplicationDidBecomeActiveNotification(_ notification: Notification) {
|
||||
updateIsObservingDatabase()
|
||||
}
|
||||
|
||||
@objc private func handleApplicationWillResignActiveNotification(_ notification: Notification) {
|
||||
updateIsObservingDatabase()
|
||||
}
|
||||
|
||||
@objc private func handleLocalProfileDidChangeNotification(_ notification: Notification) {
|
||||
updateNavigationBarButtons()
|
||||
}
|
||||
|
||||
private func updateNavigationBarButtons() {
|
||||
let profilePictureSize = Values.verySmallProfilePictureSize
|
||||
let profilePictureView = ProfilePictureView()
|
||||
profilePictureView.size = profilePictureSize
|
||||
let userHexEncodedPublicKey = OWSIdentityManager.shared().identityKeyPair()!.hexEncodedPublicKey
|
||||
profilePictureView.hexEncodedPublicKey = userHexEncodedPublicKey
|
||||
profilePictureView.update()
|
||||
profilePictureView.set(.width, to: profilePictureSize)
|
||||
profilePictureView.set(.height, to: profilePictureSize)
|
||||
profilePictureView.onTap = { [weak self] in self?.openSettings() }
|
||||
navigationItem.leftBarButtonItem = UIBarButtonItem(customView: profilePictureView)
|
||||
let createPrivateGroupChatButton = UIBarButtonItem(image: #imageLiteral(resourceName: "Contacts"), style: .plain, target: self, action: #selector(createPrivateGroupChat))
|
||||
createPrivateGroupChatButton.tintColor = Colors.text
|
||||
let joinPublicChatButton = UIBarButtonItem(image: #imageLiteral(resourceName: "Globe"), style: .plain, target: self, action: #selector(joinPublicChat))
|
||||
joinPublicChatButton.tintColor = Colors.text
|
||||
navigationItem.rightBarButtonItems = [ createPrivateGroupChatButton, joinPublicChatButton ]
|
||||
}
|
||||
|
||||
// MARK: Interaction
|
||||
func previewingContext(_ previewingContext: UIViewControllerPreviewing, viewControllerForLocation location: CGPoint) -> UIViewController? {
|
||||
guard let indexPath = tableView.indexPathForRow(at: location), let thread = self.thread(at: indexPath.row) else { return nil }
|
||||
previewingContext.sourceRect = tableView.rectForRow(at: indexPath)
|
||||
let conversationVC = ConversationViewController()
|
||||
conversationVC.configure(for: thread, action: .none, focusMessageId: nil)
|
||||
conversationVC.peekSetup()
|
||||
return conversationVC
|
||||
}
|
||||
|
||||
func previewingContext(_ previewingContext: UIViewControllerPreviewing, commit viewControllerToCommit: UIViewController) {
|
||||
guard let conversationVC = viewControllerToCommit as? ConversationViewController else { return }
|
||||
conversationVC.popped()
|
||||
navigationController?.pushViewController(conversationVC, animated: false)
|
||||
}
|
||||
|
||||
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
|
||||
guard let thread = self.thread(at: indexPath.row) else { return }
|
||||
show(thread, with: ConversationViewAction.none, animated: true)
|
||||
tableView.deselectRow(at: indexPath, animated: true)
|
||||
}
|
||||
|
||||
private func show(_ thread: TSThread, with action: ConversationViewAction, animated: Bool) {
|
||||
DispatchMainThreadSafe {
|
||||
let conversationVC = ConversationViewController()
|
||||
conversationVC.configure(for: thread, action: action, focusMessageId: nil) // TODO: focusMessageId
|
||||
self.navigationController?.setViewControllers([ self, conversationVC ], animated: true)
|
||||
}
|
||||
}
|
||||
|
||||
func tableView(_ tableView: UITableView, canEditRowAt indexPath: IndexPath) -> Bool {
|
||||
guard let threadID = self.thread(at: indexPath.row)?.uniqueId else { return false }
|
||||
var publicChat: LokiPublicChat?
|
||||
OWSPrimaryStorage.shared().dbReadConnection.read { transaction in
|
||||
publicChat = LokiDatabaseUtilities.getPublicChat(for: threadID, in: transaction)
|
||||
}
|
||||
if let publicChat = publicChat {
|
||||
return publicChat.isDeletable
|
||||
} else {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
func tableView(_ tableView: UITableView, editActionsForRowAt indexPath: IndexPath) -> [UITableViewRowAction]? {
|
||||
guard let thread = self.thread(at: indexPath.row) else { return [] }
|
||||
var publicChat: LokiPublicChat?
|
||||
OWSPrimaryStorage.shared().dbReadConnection.read { transaction in
|
||||
publicChat = LokiDatabaseUtilities.getPublicChat(for: thread.uniqueId!, in: transaction)
|
||||
}
|
||||
let delete = UITableViewRowAction(style: .destructive, title: NSLocalizedString("Delete", comment: "")) { [weak self] action, indexPath in
|
||||
let alert = UIAlertController(title: NSLocalizedString("CONVERSATION_DELETE_CONFIRMATION_ALERT_TITLE", comment: ""), message: NSLocalizedString("CONVERSATION_DELETE_CONFIRMATION_ALERT_MESSAGE", comment: ""), preferredStyle: .alert)
|
||||
alert.addAction(UIAlertAction(title: NSLocalizedString("TXT_DELETE_TITLE", comment: ""), style: .destructive) { _ in
|
||||
guard let self = self else { return }
|
||||
self.editingDatabaseConnection.readWrite { transaction in
|
||||
thread.remove(with: transaction)
|
||||
}
|
||||
NotificationCenter.default.post(name: .threadDeleted, object: nil, userInfo: [ "threadId" : thread.uniqueId! ])
|
||||
})
|
||||
alert.addAction(UIAlertAction(title: NSLocalizedString("TXT_CANCEL_TITLE", comment: ""), style: .default) { _ in })
|
||||
guard let self = self else { return }
|
||||
self.present(alert, animated: true, completion: nil)
|
||||
}
|
||||
if let publicChat = publicChat {
|
||||
return publicChat.isDeletable ? [ delete ] : []
|
||||
} else {
|
||||
return [ delete ]
|
||||
}
|
||||
}
|
||||
|
||||
@objc private func openSettings() {
|
||||
let navigationController = AppSettingsViewController.inModalNavigationController()
|
||||
present(navigationController, animated: true, completion: nil)
|
||||
}
|
||||
|
||||
@objc private func joinPublicChat() {
|
||||
let joinPublicChatVC = JoinPublicChatVC()
|
||||
let navigationController = OWSNavigationController(rootViewController: joinPublicChatVC)
|
||||
present(navigationController, animated: true, completion: nil)
|
||||
}
|
||||
|
||||
@objc private func createPrivateGroupChat() {
|
||||
// TODO: Implement
|
||||
}
|
||||
|
||||
// MARK: Convenience
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
|
||||
@objc(LKNewPublicChatVC)
|
||||
final class NewPublicChatVC : OWSViewController {
|
||||
@objc(LKJoinPublicChatVC)
|
||||
final class JoinPublicChatVC : OWSViewController {
|
||||
|
||||
// MARK: Components
|
||||
private lazy var urlTextField: UITextField = {
|
|
@ -1,22 +1,23 @@
|
|||
|
||||
public final class ProfilePictureView : UIView {
|
||||
final class ProfilePictureView : UIView {
|
||||
private var imageViewWidthConstraint: NSLayoutConstraint!
|
||||
private var imageViewHeightConstraint: NSLayoutConstraint!
|
||||
public var size: CGFloat!
|
||||
public var hexEncodedPublicKey: String!
|
||||
public var additionalHexEncodedPublicKey: String?
|
||||
var size: CGFloat!
|
||||
var hexEncodedPublicKey: String!
|
||||
var additionalHexEncodedPublicKey: String?
|
||||
var onTap: (() -> Void)? = nil
|
||||
|
||||
// MARK: Components
|
||||
private lazy var imageView = getImageView()
|
||||
private lazy var additionalImageView = getImageView()
|
||||
|
||||
// MARK: Lifecycle
|
||||
public override init(frame: CGRect) {
|
||||
override init(frame: CGRect) {
|
||||
super.init(frame: frame)
|
||||
initialize()
|
||||
}
|
||||
|
||||
public required init?(coder: NSCoder) {
|
||||
required init?(coder: NSCoder) {
|
||||
super.init(coder: coder)
|
||||
initialize()
|
||||
}
|
||||
|
@ -34,21 +35,32 @@ public final class ProfilePictureView : UIView {
|
|||
additionalImageView.set(.width, to: additionalImageViewSize)
|
||||
additionalImageView.set(.height, to: additionalImageViewSize)
|
||||
additionalImageView.layer.cornerRadius = additionalImageViewSize / 2
|
||||
// Set up gesture recognizer
|
||||
let gestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(handleTap))
|
||||
addGestureRecognizer(gestureRecognizer)
|
||||
}
|
||||
|
||||
// MARK: Updating
|
||||
public func update() {
|
||||
func update() {
|
||||
if let imageViewWidthConstraint = imageViewWidthConstraint, let imageViewHeightConstraint = imageViewHeightConstraint {
|
||||
imageView.removeConstraint(imageViewWidthConstraint)
|
||||
imageView.removeConstraint(imageViewHeightConstraint)
|
||||
}
|
||||
func getProfilePicture(of size: CGFloat, for hexEncodedPublicKey: String) -> UIImage {
|
||||
let userHexEncodedPublicKey = OWSIdentityManager.shared().identityKeyPair()!.hexEncodedPublicKey
|
||||
if hexEncodedPublicKey == userHexEncodedPublicKey, let profilePicture = OWSProfileManager.shared().localProfileAvatarImage() {
|
||||
return profilePicture
|
||||
} else {
|
||||
return Identicon.generateIcon(string: hexEncodedPublicKey, size: size)
|
||||
}
|
||||
}
|
||||
let size: CGFloat
|
||||
if let additionalHexEncodedPublicKey = additionalHexEncodedPublicKey {
|
||||
size = Values.smallProfilePictureSize
|
||||
imageViewWidthConstraint = imageView.set(.width, to: size)
|
||||
imageViewHeightConstraint = imageView.set(.height, to: size)
|
||||
additionalImageView.isHidden = false
|
||||
additionalImageView.image = Identicon.generateIcon(string: additionalHexEncodedPublicKey, size: size)
|
||||
additionalImageView.image = getProfilePicture(of: size, for: additionalHexEncodedPublicKey)
|
||||
} else {
|
||||
size = self.size
|
||||
imageViewWidthConstraint = imageView.pin(.trailing, to: .trailing, of: self)
|
||||
|
@ -56,17 +68,23 @@ public final class ProfilePictureView : UIView {
|
|||
additionalImageView.isHidden = true
|
||||
additionalImageView.image = nil
|
||||
}
|
||||
imageView.image = Identicon.generateIcon(string: hexEncodedPublicKey, size: size)
|
||||
imageView.image = getProfilePicture(of: size, for: hexEncodedPublicKey)
|
||||
imageView.layer.cornerRadius = size / 2
|
||||
}
|
||||
|
||||
// MARK: Interaction
|
||||
@objc private func handleTap() {
|
||||
onTap?()
|
||||
}
|
||||
|
||||
// MARK: Convenience
|
||||
private func getImageView() -> UIImageView {
|
||||
let result = UIImageView()
|
||||
result.layer.masksToBounds = true
|
||||
result.backgroundColor = Colors.unimportant
|
||||
result.layer.borderColor = Colors.border.cgColor
|
||||
result.layer.borderColor = Colors.profilePictureBorder.cgColor
|
||||
result.layer.borderWidth = Values.profilePictureBorderThickness
|
||||
result.contentMode = .scaleAspectFit
|
||||
return result
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,37 @@
|
|||
|
||||
final class SearchBar : UISearchBar {
|
||||
|
||||
override init(frame: CGRect) {
|
||||
super.init(frame: frame)
|
||||
update()
|
||||
}
|
||||
|
||||
required init?(coder: NSCoder) {
|
||||
super.init(coder: coder)
|
||||
update()
|
||||
}
|
||||
|
||||
private func update() {
|
||||
searchBarStyle = .minimal // Hide the border around the search bar
|
||||
barStyle = .black // Use Apple's black design as a base
|
||||
tintColor = Colors.accent // The cursor color
|
||||
let searchImage = #imageLiteral(resourceName: "searchbar_search").asTintedImage(color: Colors.searchBarPlaceholder)!
|
||||
setImage(searchImage, for: .search, state: .normal)
|
||||
let clearImage = #imageLiteral(resourceName: "searchbar_clear").asTintedImage(color: Colors.searchBarPlaceholder)!
|
||||
setImage(clearImage, for: .clear, state: .normal)
|
||||
searchTextField.backgroundColor = Colors.searchBarBackground // The search bar background color
|
||||
searchTextField.textColor = Colors.text
|
||||
searchTextField.attributedPlaceholder = NSAttributedString(string: NSLocalizedString("Search", comment: ""), attributes: [ .foregroundColor : Colors.searchBarPlaceholder ])
|
||||
searchTextField.keyboardAppearance = .dark
|
||||
setPositionAdjustment(UIOffset(horizontal: 4, vertical: 0), for: UISearchBar.Icon.search)
|
||||
searchTextPositionAdjustment = UIOffset(horizontal: 2, vertical: 0)
|
||||
setPositionAdjustment(UIOffset(horizontal: -4, vertical: 0), for: UISearchBar.Icon.clear)
|
||||
searchTextField.removeConstraints(searchTextField.constraints)
|
||||
searchTextField.pin(.leading, to: .leading, of: searchTextField.superview!, withInset: Values.mediumSpacing + 3)
|
||||
searchTextField.pin(.top, to: .top, of: searchTextField.superview!, withInset: 10)
|
||||
searchTextField.superview!.pin(.trailing, to: .trailing, of: searchTextField, withInset: Values.mediumSpacing + 3)
|
||||
searchTextField.superview!.pin(.bottom, to: .bottom, of: searchTextField, withInset: 10)
|
||||
searchTextField.set(.height, to: Values.searchBarHeight)
|
||||
searchTextField.set(.width, to: UIScreen.main.bounds.width - 2 * Values.mediumSpacing)
|
||||
}
|
||||
}
|
|
@ -1,7 +1,7 @@
|
|||
|
||||
@objc public extension UIColor {
|
||||
@objc extension UIColor {
|
||||
|
||||
@objc convenience init(hex value: UInt) { // Doesn't need to be declared public because the extension is already public
|
||||
@objc convenience init(hex value: UInt) {
|
||||
let red = CGFloat((value >> 16) & 0xff) / 255
|
||||
let green = CGFloat((value >> 8) & 0xff) / 255
|
||||
let blue = CGFloat((value >> 0) & 0xff) / 255
|
||||
|
@ -10,10 +10,15 @@
|
|||
}
|
||||
|
||||
@objc(LKColors)
|
||||
public final class Colors : NSObject {
|
||||
final class Colors : NSObject {
|
||||
|
||||
@objc public static let accent = UIColor(hex: 0x00F782)
|
||||
@objc public static let text = UIColor(hex: 0xFFFFFF)
|
||||
@objc public static let unimportant = UIColor(hex: 0xD8D8D8)
|
||||
@objc public static let border = UIColor(hex: 0x979797)
|
||||
@objc static let accent = UIColor(hex: 0x00F782)
|
||||
@objc static let text = UIColor(hex: 0xFFFFFF)
|
||||
@objc static let unimportant = UIColor(hex: 0xD8D8D8)
|
||||
@objc static let profilePictureBorder = UIColor(hex: 0x979797)
|
||||
@objc static let conversationCellBackground = UIColor(hex: 0x1B1B1B)
|
||||
@objc static let conversationCellSelected = UIColor(hex: 0x0C0C0C)
|
||||
@objc static let navigationBarBackground = UIColor(hex: 0x161616)
|
||||
@objc static let searchBarPlaceholder = UIColor(hex: 0x8E8E93) // Also used for the icons
|
||||
@objc static let searchBarBackground = UIColor(red: 142 / 255, green: 142 / 255, blue: 147 / 255, alpha: 0.12)
|
||||
}
|
||||
|
|
|
@ -1,33 +1,31 @@
|
|||
|
||||
@objc(LKGradient)
|
||||
public final class Gradient : NSObject {
|
||||
public let start: UIColor
|
||||
public let end: UIColor
|
||||
final class Gradient : NSObject {
|
||||
let start: UIColor
|
||||
let end: UIColor
|
||||
|
||||
private override init() { preconditionFailure("Use init(start:end:) instead.") }
|
||||
|
||||
@objc public init(start: UIColor, end: UIColor) {
|
||||
@objc init(start: UIColor, end: UIColor) {
|
||||
self.start = start
|
||||
self.end = end
|
||||
super.init()
|
||||
}
|
||||
}
|
||||
|
||||
@objc public extension UIView {
|
||||
@objc extension UIView {
|
||||
|
||||
@objc func setGradient(_ gradient: Gradient) { // Doesn't need to be declared public because the extension is already public
|
||||
@objc func setGradient(_ gradient: Gradient) {
|
||||
let layer = CAGradientLayer()
|
||||
layer.frame = UIScreen.main.bounds
|
||||
layer.colors = [ gradient.start.cgColor, gradient.end.cgColor ]
|
||||
layer.startPoint = CGPoint(x: 0.5, y: 0)
|
||||
layer.endPoint = CGPoint(x: 0.5, y: 1)
|
||||
let index = UInt32((self.layer.sublayers ?? []).count)
|
||||
self.layer.insertSublayer(layer, at: index)
|
||||
}
|
||||
}
|
||||
|
||||
@objc(LKGradients)
|
||||
public final class Gradients : NSObject {
|
||||
final class Gradients : NSObject {
|
||||
|
||||
@objc public static let defaultLokiBackground = Gradient(start: UIColor(hex: 0x171717), end: UIColor(hex:0x121212))
|
||||
@objc static let defaultLokiBackground = Gradient(start: UIColor(hex: 0x171717), end: UIColor(hex:0x121212))
|
||||
}
|
||||
|
|
|
@ -1,26 +1,32 @@
|
|||
|
||||
@objc(LKValues)
|
||||
public final class Values : NSObject {
|
||||
final class Values : NSObject {
|
||||
|
||||
// MARK: - Alpha Values
|
||||
@objc public static let inactiveElementOpacity = CGFloat(0.6)
|
||||
@objc static let inactiveElementOpacity = CGFloat(0.6)
|
||||
@objc static let conversationCellTimestampOpacity = CGFloat(0.4)
|
||||
|
||||
// MARK: - Font Sizes
|
||||
@objc public static let smallFontSize = CGFloat(13)
|
||||
@objc public static let mediumFontSize = CGFloat(15)
|
||||
@objc public static let largeFontSize = CGFloat(20)
|
||||
@objc public static let veryLargeFontSize = CGFloat(25)
|
||||
@objc public static let massiveFontSize = CGFloat(50)
|
||||
@objc static let smallFontSize = CGFloat(13)
|
||||
@objc static let mediumFontSize = CGFloat(15)
|
||||
@objc static let largeFontSize = CGFloat(20)
|
||||
@objc static let veryLargeFontSize = CGFloat(25)
|
||||
@objc static let massiveFontSize = CGFloat(50)
|
||||
|
||||
// MARK: - Element Sizes
|
||||
@objc public static let buttonHeight = CGFloat(34)
|
||||
@objc public static let accentLineThickness = CGFloat(4)
|
||||
@objc public static let smallProfilePictureSize = CGFloat(35)
|
||||
@objc public static let mediumProfilePictureSize = CGFloat(45)
|
||||
@objc public static let largeProfilePictureSize = CGFloat(65)
|
||||
@objc public static let profilePictureBorderThickness = CGFloat(1)
|
||||
@objc static let buttonHeight = CGFloat(34)
|
||||
@objc static let accentLineThickness = CGFloat(4)
|
||||
@objc static let verySmallProfilePictureSize = CGFloat(26)
|
||||
@objc static let smallProfilePictureSize = CGFloat(35)
|
||||
@objc static let mediumProfilePictureSize = CGFloat(45)
|
||||
@objc static let largeProfilePictureSize = CGFloat(65)
|
||||
@objc static let profilePictureBorderThickness = CGFloat(1)
|
||||
@objc static let conversationCellStatusIndicatorSize = CGFloat(14)
|
||||
@objc static let searchBarHeight = CGFloat(36)
|
||||
@objc static let newConversationButtonSize = CGFloat(45)
|
||||
|
||||
// MARK: - Distances
|
||||
@objc public static let smallSpacing = CGFloat(8)
|
||||
@objc public static let mediumSpacing = CGFloat(16)
|
||||
@objc static let smallSpacing = CGFloat(8)
|
||||
@objc static let mediumSpacing = CGFloat(16)
|
||||
@objc static let largeSpacing = CGFloat(24)
|
||||
}
|
||||
|
|
|
@ -867,8 +867,8 @@ typedef NS_ENUM(NSInteger, HomeViewControllerSection) {
|
|||
|
||||
- (void)showNewPublicChatVC
|
||||
{
|
||||
LKNewPublicChatVC *newPublicChatVC = [LKNewPublicChatVC new];
|
||||
OWSNavigationController *navigationController = [[OWSNavigationController alloc] initWithRootViewController:newPublicChatVC];
|
||||
LKJoinPublicChatVC *joinPublicChatVC = [LKJoinPublicChatVC new];
|
||||
OWSNavigationController *navigationController = [[OWSNavigationController alloc] initWithRootViewController:joinPublicChatVC];
|
||||
[self.navigationController presentViewController:navigationController animated:YES completion:nil];
|
||||
}
|
||||
|
||||
|
|
|
@ -2677,3 +2677,5 @@
|
|||
"Messages" = "Messages";
|
||||
"Note to Self" = "Note to Self";
|
||||
"New Group" = "New Group";
|
||||
"Delete" = "Delete";
|
||||
"Search" = "Search";
|
||||
|
|