Implement landing screen redesign

This commit is contained in:
Niels Andriesse 2019-12-06 14:42:43 +11:00
parent 7ea5e5bd46
commit 1972f1526d
15 changed files with 230 additions and 12 deletions

View File

@ -567,6 +567,8 @@
B821F2F82272CED3002C88C0 /* DisplayNameVC.swift in Sources */ = {isa = PBXBuildFile; fileRef = B821F2F72272CED3002C88C0 /* DisplayNameVC.swift */; };
B821F2FA2272CEEE002C88C0 /* SeedVC.swift in Sources */ = {isa = PBXBuildFile; fileRef = B821F2F92272CEEE002C88C0 /* SeedVC.swift */; };
B82584A02315024B001B41CB /* LokiRSSFeedPoller.swift in Sources */ = {isa = PBXBuildFile; fileRef = B825849F2315024B001B41CB /* LokiRSSFeedPoller.swift */; };
B82B40882399EB0E00A248E7 /* LandingVC.swift in Sources */ = {isa = PBXBuildFile; fileRef = B82B40872399EB0E00A248E7 /* LandingVC.swift */; };
B82B408A2399EC0600A248E7 /* FakeChatView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B82B40892399EC0600A248E7 /* FakeChatView.swift */; };
B846365B22B7418B00AF1514 /* Identicon+ObjC.swift in Sources */ = {isa = PBXBuildFile; fileRef = B846365A22B7418B00AF1514 /* Identicon+ObjC.swift */; };
B84664F5235022F30083A1CD /* MentionUtilities.swift in Sources */ = {isa = PBXBuildFile; fileRef = B84664F4235022F30083A1CD /* MentionUtilities.swift */; };
B86BD08423399ACF000F5AE3 /* Modal.swift in Sources */ = {isa = PBXBuildFile; fileRef = B86BD08323399ACF000F5AE3 /* Modal.swift */; };
@ -1397,6 +1399,8 @@
B821F2F72272CED3002C88C0 /* DisplayNameVC.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DisplayNameVC.swift; sourceTree = "<group>"; };
B821F2F92272CEEE002C88C0 /* SeedVC.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SeedVC.swift; sourceTree = "<group>"; };
B825849F2315024B001B41CB /* LokiRSSFeedPoller.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LokiRSSFeedPoller.swift; sourceTree = "<group>"; };
B82B40872399EB0E00A248E7 /* LandingVC.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LandingVC.swift; sourceTree = "<group>"; };
B82B40892399EC0600A248E7 /* FakeChatView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FakeChatView.swift; sourceTree = "<group>"; };
B846365A22B7418B00AF1514 /* Identicon+ObjC.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Identicon+ObjC.swift"; sourceTree = "<group>"; };
B84664F4235022F30083A1CD /* MentionUtilities.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MentionUtilities.swift; sourceTree = "<group>"; };
B86BD08323399ACF000F5AE3 /* Modal.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Modal.swift; sourceTree = "<group>"; };
@ -2770,6 +2774,7 @@
children = (
B8B5BCEB2394D869003823C9 /* Button.swift */,
B8BB82AA238F669C00BA5194 /* ConversationCell.swift */,
B82B40892399EC0600A248E7 /* FakeChatView.swift */,
B8BB82AC238F734800BA5194 /* ProfilePictureView.swift */,
B8BB82B02390C37000BA5194 /* SearchBar.swift */,
B8BB82B82394911B00BA5194 /* Separator.swift */,
@ -2797,6 +2802,7 @@
B80C6B582384C4E700FDBC8B /* DeviceNameModal.swift */,
B80C6B5A2384C7F900FDBC8B /* DeviceNameModalDelegate.swift */,
B8BB82A4238F627000BA5194 /* HomeVC.swift */,
B82B40872399EB0E00A248E7 /* LandingVC.swift */,
B86BD08323399ACF000F5AE3 /* Modal.swift */,
B8CCF63623961D6D0091D419 /* NewPrivateChatVC.swift */,
B894D0742339EDCF00B4D94D /* NukeDataModal.swift */,
@ -3846,8 +3852,10 @@
34B6A907218B5241007C4606 /* TypingIndicatorCell.swift in Sources */,
4CFD151D22415AA400F2450F /* CallVideoHintView.swift in Sources */,
34D1F0AB1F867BFC0066283D /* OWSContactOffersCell.m in Sources */,
B82B408A2399EC0600A248E7 /* FakeChatView.swift in Sources */,
B8BB82B92394911B00BA5194 /* Separator.swift in Sources */,
343A65981FC4CFE7000477A1 /* ConversationScrollButton.m in Sources */,
B82B40882399EB0E00A248E7 /* LandingVC.swift in Sources */,
34386A51207D0C01009F5D9C /* HomeViewController.m in Sources */,
34D1F0A91F867BFC0066283D /* ConversationViewCell.m in Sources */,
34A4C62022175C5C0042EF2E /* OnboardingProfileViewController.swift in Sources */,

View File

@ -1422,7 +1422,7 @@ static NSTimeInterval launchStartedAt;
}
} else {
rootViewController = [[OnboardingController new] initialViewController];
navigationBarHidden = YES;
navigationBarHidden = NO;
}
OWSAssertDebug(rootViewController);
OWSNavigationController *navigationController =

View File

@ -4,7 +4,7 @@ final class Button : UIButton {
private let size: Size
enum Style {
case unimportant, regular, prominent
case unimportant, regular, prominentOutline, prominentFilled
}
enum Size {
@ -31,19 +31,22 @@ final class Button : UIButton {
switch style {
case .unimportant: fillColor = Colors.unimportantButtonBackground
case .regular: fillColor = UIColor.clear
case .prominent: fillColor = UIColor.clear
case .prominentOutline: fillColor = UIColor.clear
case .prominentFilled: fillColor = Colors.accent
}
let borderColor: UIColor
switch style {
case .unimportant: borderColor = Colors.unimportantButtonBackground
case .regular: borderColor = Colors.text
case .prominent: borderColor = Colors.accent
case .prominentOutline: borderColor = Colors.accent
case .prominentFilled: borderColor = Colors.accent
}
let textColor: UIColor
switch style {
case .unimportant: textColor = Colors.text
case .regular: textColor = Colors.text
case .prominent: textColor = Colors.accent
case .prominentOutline: textColor = Colors.accent
case .prominentFilled: textColor = Colors.text
}
let height: CGFloat
switch size {

View File

@ -0,0 +1,108 @@
final class FakeChatView : UIView {
private let spacing = Values.mediumSpacing
private lazy var chatBubbles = [
getChatBubble(withText: NSLocalizedString("What is Loki Messenger? A completely decentralised private messaging application for all platforms.", comment: ""), wasSentByCurrentUser: true),
getChatBubble(withText: NSLocalizedString("So no metadata collection, or personally identifiable information? How does it work?", comment: ""), wasSentByCurrentUser: false),
getChatBubble(withText: NSLocalizedString("Through a combination of advanced blockchain techniques including onion routing through Lokinet's private servers.", comment: ""), wasSentByCurrentUser: true),
getChatBubble(withText: NSLocalizedString("Friends don't let friends use compromised messengers. You're welcome.", comment: ""), wasSentByCurrentUser: true)
]
private lazy var scrollView: UIScrollView = {
let result = UIScrollView()
result.showsVerticalScrollIndicator = false
return result
}()
override init(frame: CGRect) {
super.init(frame: frame)
setUpViewHierarchy()
animate()
}
required init?(coder: NSCoder) {
super.init(coder: coder)
setUpViewHierarchy()
animate()
}
private func setUpViewHierarchy() {
let stackView = UIStackView(arrangedSubviews: chatBubbles)
stackView.axis = .vertical
stackView.spacing = spacing
stackView.alignment = .fill
stackView.set(.width, to: UIScreen.main.bounds.width)
stackView.layoutMargins = UIEdgeInsets(top: 8, leading: Values.veryLargeSpacing, bottom: 8, trailing: Values.veryLargeSpacing)
stackView.isLayoutMarginsRelativeArrangement = true
scrollView.addSubview(stackView)
stackView.pin(to: scrollView)
addSubview(scrollView)
scrollView.pin(to: self)
}
private func getChatBubble(withText text: String, wasSentByCurrentUser: Bool) -> UIView {
let result = UIView()
let bubbleView = UIView()
bubbleView.set(.width, to: Values.fakeChatBubbleWidth)
bubbleView.layer.cornerRadius = Values.fakeChatBubbleCornerRadius
let backgroundColor = wasSentByCurrentUser ? Colors.accent : Colors.fakeChatBubbleBackground
bubbleView.backgroundColor = backgroundColor
let label = UILabel()
let textColor = wasSentByCurrentUser ? Colors.fakeChatBubbleText : Colors.text
label.textColor = textColor
label.font = .boldSystemFont(ofSize: Values.mediumFontSize)
label.numberOfLines = 0
label.lineBreakMode = .byWordWrapping
label.text = text
bubbleView.addSubview(label)
label.pin(to: bubbleView, withInset: Values.smallSpacing)
result.addSubview(bubbleView)
bubbleView.pin(.top, to: .top, of: result)
result.pin(.bottom, to: .bottom, of: bubbleView)
if wasSentByCurrentUser {
bubbleView.pin(.leading, to: .leading, of: result)
} else {
result.pin(.trailing, to: .trailing, of: bubbleView)
}
return result
}
private func animate() {
let animationDuration = Values.fakeChatAnimationDuration
let delayBetweenMessages = Values.fakeChatDelay
chatBubbles.forEach { $0.alpha = 0 }
Timer.scheduledTimer(withTimeInterval: Values.fakeChatStartDelay, repeats: false) { [weak self] _ in
self?.showChatBubble(at: 0)
Timer.scheduledTimer(withTimeInterval: delayBetweenMessages, repeats: false) { _ in
self?.showChatBubble(at: 1)
Timer.scheduledTimer(withTimeInterval: delayBetweenMessages, repeats: false) { _ in
self?.showChatBubble(at: 2)
UIView.animate(withDuration: animationDuration) {
guard let self = self else { return }
self.scrollView.contentOffset = CGPoint(x: 0, y: self.chatBubbles[0].height() + self.spacing)
}
Timer.scheduledTimer(withTimeInterval: delayBetweenMessages, repeats: false) { _ in
self?.showChatBubble(at: 3)
UIView.animate(withDuration: animationDuration) {
guard let self = self else { return }
self.scrollView.contentOffset = CGPoint(x: 0, y: self.chatBubbles[0].height() + self.spacing + self.chatBubbles[1].height() + self.spacing)
}
}
}
}
}
}
private func showChatBubble(at index: Int) {
let chatBubble = chatBubbles[index]
UIView.animate(withDuration: Values.fakeChatAnimationDuration) {
chatBubble.alpha = 1
}
let scale = Values.fakeChatMessagePopAnimationStartScale
chatBubble.transform = CGAffineTransform(scaleX: scale, y: scale)
UIView.animate(withDuration: Values.fakeChatAnimationDuration, delay: 0, usingSpringWithDamping: 0.68, initialSpringVelocity: 4, options: .curveEaseInOut, animations: {
chatBubble.transform = CGAffineTransform(scaleX: 1, y: 1)
}, completion: nil)
}
}

View File

@ -29,4 +29,6 @@ final class Colors : NSObject {
@objc static let settingButtonSelected = UIColor(hex: 0x0C0C0C)
@objc static let modalBackground = UIColor(hex: 0x101011)
@objc static let modalBorder = UIColor(hex: 0x212121)
@objc static let fakeChatBubbleBackground = UIColor(hex: 0x3F4146)
@objc static let fakeChatBubbleText = UIColor(hex: 0x000000)
}

View File

@ -35,6 +35,9 @@ final class Values : NSObject {
@objc static let settingButtonHeight = CGFloat(75)
@objc static let modalCornerRadius = CGFloat(10)
@objc static let modalButtonCornerRadius = CGFloat(5)
@objc static let fakeChatBubbleWidth = CGFloat(224)
@objc static let fakeChatBubbleCornerRadius = CGFloat(10)
@objc static let fakeChatViewHeight = CGFloat(300)
// MARK: - Distances
@objc static let verySmallSpacing = CGFloat(4)
@ -44,4 +47,11 @@ final class Values : NSObject {
@objc static let veryLargeSpacing = CGFloat(35)
@objc static let massiveSpacing = CGFloat(64)
@objc static let newConversationButtonBottomOffset = CGFloat(52)
@objc static let restoreButtonBottomOffset = CGFloat(52)
// MARK: - Animation Values
@objc static let fakeChatStartDelay: TimeInterval = 2
@objc static let fakeChatAnimationDuration: TimeInterval = 0.4
@objc static let fakeChatDelay: TimeInterval = 4
@objc static let fakeChatMessagePopAnimationStartScale: CGFloat = 0.6
}

View File

@ -43,6 +43,13 @@ extension UIView {
[ VerticalEdge.top, VerticalEdge.bottom ].forEach { pin($0, to: $0, of: view) }
}
func pin(to view: UIView, withInset inset: CGFloat) {
pin(.leading, to: .leading, of: view, withInset: inset)
pin(.top, to: .top, of: view, withInset: inset)
view.pin(.trailing, to: .trailing, of: self, withInset: inset)
view.pin(.bottom, to: .bottom, of: self, withInset: inset)
}
@discardableResult
func center(_ direction: Direction, in view: UIView) -> NSLayoutConstraint {
translatesAutoresizingMaskIntoConstraints = false

View File

@ -24,7 +24,7 @@ final class DeviceLinksVC : UIViewController, UITableViewDataSource, UITableView
explanationLabel.lineBreakMode = .byWordWrapping
explanationLabel.textAlignment = .center
explanationLabel.text = NSLocalizedString("You don't have any linked devices yet", comment: "")
let linkNewDeviceButton = Button(style: .prominent, size: .medium)
let linkNewDeviceButton = Button(style: .prominentOutline, size: .medium)
linkNewDeviceButton.setTitle(NSLocalizedString("Link a Device", comment: ""), for: UIControl.State.normal)
linkNewDeviceButton.addTarget(self, action: #selector(linkNewDevice), for: UIControl.Event.touchUpInside)
linkNewDeviceButton.set(.width, to: 160)

View File

@ -182,7 +182,7 @@ private final class EnterChatURLVC : UIViewController {
explanationLabel.textAlignment = .center
explanationLabel.lineBreakMode = .byWordWrapping
// Next button
let nextButton = Button(style: .prominent, size: .large)
let nextButton = Button(style: .prominentOutline, size: .large)
nextButton.setTitle(NSLocalizedString("Next", comment: ""), for: UIControl.State.normal)
nextButton.addTarget(self, action: #selector(joinPublicChatIfPossible), for: UIControl.Event.touchUpInside)
let nextButtonContainer = UIView()

View File

@ -0,0 +1,74 @@
final class LandingVC : UIViewController {
// MARK: Settings
override var preferredStatusBarStyle: UIStatusBarStyle { return .lightContent }
// MARK: Lifecycle
override func viewDidLoad() {
// Set gradient background
view.backgroundColor = .clear
let gradient = Gradients.defaultLokiBackground
view.setGradient(gradient)
// Set up navigation bar
let navigationBar = navigationController!.navigationBar
navigationBar.setBackgroundImage(UIImage(), for: UIBarMetrics.default)
navigationBar.shadowImage = UIImage()
navigationBar.isTranslucent = false
navigationBar.barTintColor = Colors.navigationBarBackground
// Set up logo image view
let logoImageView = UIImageView()
logoImageView.image = #imageLiteral(resourceName: "Loki")
logoImageView.contentMode = .scaleAspectFit
logoImageView.set(.width, to: 32)
logoImageView.set(.height, to: 32)
navigationItem.titleView = logoImageView
// Set up title label
let titleLabel = UILabel()
titleLabel.textColor = Colors.text
titleLabel.font = .boldSystemFont(ofSize: Values.veryLargeFontSize)
titleLabel.text = NSLocalizedString("Your Loki Messenger begins here...", comment: "")
titleLabel.numberOfLines = 0
titleLabel.lineBreakMode = .byWordWrapping
// Set up title label container
let titleLabelContainer = UIView()
titleLabelContainer.addSubview(titleLabel)
titleLabel.pin(.leading, to: .leading, of: titleLabelContainer, withInset: Values.veryLargeSpacing)
titleLabel.pin(.top, to: .top, of: titleLabelContainer)
titleLabelContainer.pin(.trailing, to: .trailing, of: titleLabel, withInset: Values.veryLargeSpacing)
titleLabelContainer.pin(.bottom, to: .bottom, of: titleLabel)
// Set up fake chat view
let fakeChatView = FakeChatView()
// Set up main stack view
let mainStackView = UIStackView(arrangedSubviews: [ titleLabelContainer, fakeChatView ])
mainStackView.axis = .vertical
mainStackView.spacing = Values.mediumSpacing // The fake chat view has an internal top margin
mainStackView.alignment = .fill
view.addSubview(mainStackView)
mainStackView.pin(.leading, to: .leading, of: view)
view.pin(.trailing, to: .trailing, of: mainStackView)
mainStackView.set(.height, to: Values.fakeChatViewHeight)
mainStackView.center(.vertical, in: view)
// Set up view
let screen = UIScreen.main.bounds
view.set(.width, to: screen.width)
view.set(.height, to: screen.height)
// Set up register button
let registerButton = Button(style: .prominentFilled, size: .large)
registerButton.setTitle(NSLocalizedString("Create Account", comment: ""), for: UIControl.State.normal)
registerButton.titleLabel!.font = .boldSystemFont(ofSize: Values.mediumFontSize)
// Set up restore button
let restoreButton = Button(style: .prominentOutline, size: .large)
restoreButton.setTitle(NSLocalizedString("Continue your Loki Messenger", comment: ""), for: UIControl.State.normal)
restoreButton.titleLabel!.font = .boldSystemFont(ofSize: Values.mediumFontSize)
// Set up button stack view
let buttonStackView = UIStackView(arrangedSubviews: [ registerButton, restoreButton ])
buttonStackView.axis = .vertical
buttonStackView.spacing = Values.mediumSpacing
buttonStackView.alignment = .fill
view.addSubview(buttonStackView)
buttonStackView.pin(.leading, to: .leading, of: view, withInset: Values.massiveSpacing)
view.pin(.trailing, to: .trailing, of: buttonStackView, withInset: Values.massiveSpacing)
view.pin(.bottom, to: .bottom, of: buttonStackView, withInset: Values.restoreButtonBottomOffset)
}
}

View File

@ -192,7 +192,7 @@ private final class EnterPublicKeyVC : UIViewController {
buttonContainer.spacing = Values.mediumSpacing
buttonContainer.distribution = .fillEqually
// Next button
let nextButton = Button(style: .prominent, size: .large)
let nextButton = Button(style: .prominentOutline, size: .large)
nextButton.setTitle(NSLocalizedString("Next", comment: ""), for: UIControl.State.normal)
nextButton.addTarget(self, action: #selector(startNewPrivateChatIfPossible), for: UIControl.Event.touchUpInside)
let nextButtonContainer = UIView()

View File

@ -47,7 +47,7 @@ final class SettingsVC : UIViewController, AvatarViewHelperDelegate {
}()
private lazy var copyButton: Button = {
let result = Button(style: .prominent, size: .medium)
let result = Button(style: .prominentOutline, size: .medium)
result.setTitle(NSLocalizedString("Copy", comment: ""), for: UIControl.State.normal)
result.addTarget(self, action: #selector(copyPublicKey), for: UIControl.Event.touchUpInside)
return result

View File

@ -12,6 +12,7 @@
#import <SignalServiceKit/TSGroupModel.h>
#import <SignalServiceKit/TSGroupThread.h>
#import <SignalServiceKit/TSThread.h>
#import <SignalMessaging/SignalMessaging-Swift.h>
NS_ASSUME_NONNULL_BEGIN

View File

@ -90,9 +90,7 @@ public class OnboardingController: NSObject {
@objc
public func initialViewController() -> UIViewController {
AssertIsOnMainThread()
let view = OnboardingSplashViewController(onboardingController: self)
return view
return LandingVC()
}
// MARK: - Transitions

View File

@ -2724,3 +2724,10 @@
"Couldn't Update Profile Picture" = "Couldn't Update Profile Picture";
"Clear" = "Clear";
"Enter a display name" = "Enter a display name";
"Your Loki Messenger begins here..." = "Your Loki Messenger begins here...";
"What is Loki Messenger? A completely decentralised private messaging application for all platforms." = "What is Loki Messenger? A completely decentralised private messaging application for all platforms.";
"So no metadata collection, or personally identifiable information? How does it work?" = "So no metadata collection, or personally identifiable information? How does it work?";
"Through a combination of advanced blockchain techniques including onion routing through Lokinet's private servers." = "Through a combination of advanced blockchain techniques including onion routing through Lokinet's private servers.";
"Friends don't let friends use compromised messengers. You're welcome." = "Friends don't let friends use compromised messengers. You're welcome.";
"Create Account" = "Create Account";
"Continue your Loki Messenger" = "Continue your Loki Messenger";