Skip to content

Commit

Permalink
[CIS-736] Improve Demo App experience 🤔 (#910)
Browse files Browse the repository at this point in the history
* Polish Demo App login screen

* Add haptic feedback upon long-pressing message in messageList

* Not show reaction on message when there is no image for it

* Add loading indicator to channelList
  • Loading branch information
DominikBucher12 authored Mar 25, 2021
1 parent f19a900 commit 7d4c9dd
Show file tree
Hide file tree
Showing 9 changed files with 127 additions and 44 deletions.
16 changes: 0 additions & 16 deletions DemoApp/AdvancedOptionsViewController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,6 @@
import UIKit

class MainButton: UIButton {
override func tintColorDidChange() {
backgroundColor = tintColor
setTitleColor(.white, for: .normal)
}

override func updateConstraints() {
super.updateConstraints()
translatesAutoresizingMaskIntoConstraints = false
Expand All @@ -29,15 +24,4 @@ class AdvancedOptionsViewController: UIViewController {
mainStackView.isLayoutMarginsRelativeArrangement = true
}
}

override func viewDidLoad() {
super.viewDidLoad()

title = "Advanced Options"
}

override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
navigationController?.isNavigationBarHidden = false
}
}
19 changes: 11 additions & 8 deletions DemoApp/Base.lproj/Main.storyboard
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
<device id="retina6_1" orientation="portrait" appearance="light"/>
<dependencies>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="17703"/>
<capability name="Image references" minToolsVersion="12.0"/>
<capability name="Named colors" minToolsVersion="9.0"/>
<capability name="Safe area layout guides" minToolsVersion="9.0"/>
<capability name="Stack View standard spacing" minToolsVersion="9.0"/>
Expand Down Expand Up @@ -120,7 +121,7 @@
<navigationItem key="navigationItem" id="u60-gi-N8s"/>
<connections>
<outlet property="tableView" destination="b2C-Ge-Ivw" id="5sp-Sf-IDQ"/>
<segue destination="yjx-9T-97B" kind="show" identifier="show_advanced_options" id="lV5-dW-IzB"/>
<segue destination="yjx-9T-97B" kind="presentation" identifier="show_advanced_options" id="lV5-dW-IzB"/>
</connections>
</viewController>
<placeholder placeholderIdentifier="IBFirstResponder" id="dkx-z0-nzr" sceneMemberID="firstResponder"/>
Expand All @@ -132,11 +133,11 @@
<objects>
<viewController id="yjx-9T-97B" customClass="AdvancedOptionsViewController" customModule="ChatSample" customModuleProvider="target" sceneMemberID="viewController">
<view key="view" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" layoutMarginsFollowReadableWidth="YES" id="LM0-SF-FGq">
<rect key="frame" x="0.0" y="0.0" width="414" height="896"/>
<rect key="frame" x="0.0" y="0.0" width="414" height="842"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<stackView opaque="NO" contentMode="scaleToFill" axis="vertical" spacingType="standard" translatesAutoresizingMaskIntoConstraints="NO" id="8QA-pW-MTy">
<rect key="frame" x="0.0" y="104" width="414" height="160"/>
<rect key="frame" x="0.0" y="16" width="414" height="160"/>
<subviews>
<textField opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="left" contentVerticalAlignment="center" borderStyle="roundedRect" placeholder="Chat API Key" textAlignment="natural" minimumFontSize="17" translatesAutoresizingMaskIntoConstraints="NO" id="Jye-aX-OTy">
<rect key="frame" x="0.0" y="0.0" width="414" height="34"/>
Expand All @@ -161,7 +162,7 @@
</subviews>
</stackView>
<button opaque="NO" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" layoutMarginsFollowReadableWidth="YES" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="system" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="NAL-Cp-UKn" customClass="MainButton" customModule="ChatSample" customModuleProvider="target">
<rect key="frame" x="0.0" y="296" width="414" height="44"/>
<rect key="frame" x="0.0" y="208" width="414" height="44"/>
<color key="backgroundColor" name="AccentColor"/>
<constraints>
<constraint firstAttribute="height" constant="44" id="JRh-ZO-YhQ"/>
Expand All @@ -175,9 +176,9 @@
<constraints>
<constraint firstItem="8QA-pW-MTy" firstAttribute="leading" secondItem="tcf-9B-eio" secondAttribute="leading" id="0Hc-zp-VRf"/>
<constraint firstItem="tcf-9B-eio" firstAttribute="trailing" secondItem="8QA-pW-MTy" secondAttribute="trailing" id="GAZ-Qz-94z"/>
<constraint firstItem="tcf-9B-eio" firstAttribute="trailing" secondItem="NAL-Cp-UKn" secondAttribute="trailing" id="Zy7-3Y-DqS"/>
<constraint firstItem="NAL-Cp-UKn" firstAttribute="leading" secondItem="iFV-cQ-oZA" secondAttribute="leading" id="Klb-ST-SQC"/>
<constraint firstItem="NAL-Cp-UKn" firstAttribute="trailing" secondItem="iFV-cQ-oZA" secondAttribute="trailing" id="bBg-Fm-XdB"/>
<constraint firstItem="8QA-pW-MTy" firstAttribute="top" secondItem="tcf-9B-eio" secondAttribute="top" constant="16" id="gTl-BX-qq8"/>
<constraint firstItem="NAL-Cp-UKn" firstAttribute="leading" secondItem="tcf-9B-eio" secondAttribute="leading" id="mug-kk-zH9"/>
<constraint firstItem="NAL-Cp-UKn" firstAttribute="top" secondItem="8QA-pW-MTy" secondAttribute="bottom" constant="32" id="zLe-dh-aXa"/>
</constraints>
</view>
Expand Down Expand Up @@ -235,8 +236,10 @@
</textField>
<button opaque="NO" contentMode="scaleToFill" horizontalHuggingPriority="251" contentHorizontalAlignment="center" contentVerticalAlignment="center" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="3vQ-eI-nnd">
<rect key="frame" x="363" y="0.0" width="19" height="56"/>
<color key="tintColor" white="0.0" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
<state key="normal" image="person" catalog="system"/>
<color key="tintColor" systemColor="labelColor"/>
<state key="normal">
<imageReference key="image" image="person" catalog="system" renderingMode="template"/>
</state>
<connections>
<action selector="addPersonTapped:" destination="rmm-yd-0KJ" eventType="touchUpInside" id="Xai-xu-cPZ"/>
</connections>
Expand Down
4 changes: 1 addition & 3 deletions DemoApp/LoginViewController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -46,9 +46,7 @@ class LoginViewController: UIViewController {
super.viewDidLoad()
tableView.delegate = self
tableView.dataSource = self

tableView.bounces = false


// An old trick to force the table view to hide empty lines
tableView.tableFooterView = UIView()
}
Expand Down
27 changes: 26 additions & 1 deletion Sources/StreamChatUI/ChatChannelList/ChatChannelListVC.swift
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,14 @@ open class _ChatChannelListVC<ExtraData: ExtraDataTypes>: _ViewController,
SwipeableViewDelegate {
/// The `ChatChannelListController` instance that provides channels data.
public var controller: _ChatChannelListController<ExtraData>!

open private(set) lazy var loadingIndicator: UIActivityIndicatorView = {
if #available(iOS 13.0, *) {
return UIActivityIndicatorView(style: .large).withoutAutoresizingMaskConstraints
} else {
return UIActivityIndicatorView(style: .whiteLarge).withoutAutoresizingMaskConstraints
}
}()

/// The `_ChatChannelListRouter` instance responsible for navigation.
open private(set) lazy var router: _ChatChannelListRouter<ExtraData> = uiConfig
Expand Down Expand Up @@ -53,7 +61,7 @@ open class _ChatChannelListVC<ExtraData: ExtraDataTypes>: _ViewController,

override open func setUp() {
super.setUp()

controller.setDelegate(self)
controller.synchronize()

Expand All @@ -80,6 +88,8 @@ open class _ChatChannelListVC<ExtraData: ExtraDataTypes>: _ViewController,
override open func setUpLayout() {
super.setUpLayout()
view.embed(collectionView)
collectionView.addSubview(loadingIndicator)
loadingIndicator.pin(anchors: [.centerX, .centerY], to: view)
}

override public func defaultAppearance() {
Expand Down Expand Up @@ -244,3 +254,18 @@ extension _ChatChannelListVC: _ChatChannelListControllerDelegate {
)
}
}

extension _ChatChannelListVC: DataControllerStateDelegate {
public func controller(_ controller: DataController, didChangeState state: DataController.State) {
switch state {
case .initialized, .localDataFetched:
if self.controller.channels.isEmpty {
loadingIndicator.startAnimating()
} else {
loadingIndicator.stopAnimating()
}
default:
loadingIndicator.stopAnimating()
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ open class _ChatMessageListRouter<ExtraData: ExtraDataTypes>: ChatRouter<_ChatMe
popup.modalPresentationStyle = .overFullScreen
popup.modalTransitionStyle = .crossDissolve

rootViewController.present(popup, animated: true)
rootViewController.present(popup, animated: false)
}

open func showPreview(for attachment: ChatMessageDefaultAttachment) {
Expand Down
60 changes: 55 additions & 5 deletions Sources/StreamChatUI/ChatMessageList/ChatMessageListVC.swift
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,8 @@ open class _ChatMessageListVC<ExtraData: ExtraDataTypes>: _ViewController,
public var dataSource: DataSource = .empty()
public var delegate: Delegate?

public lazy var impactFeedbackGenerator = UIImpactFeedbackGenerator(style: .medium)

public lazy var router = uiConfig.navigation.messageListRouter.init(rootViewController: self)

public private(set) lazy var collectionViewLayout = uiConfig
Expand Down Expand Up @@ -118,6 +120,7 @@ open class _ChatMessageListVC<ExtraData: ExtraDataTypes>: _ViewController,
super.setUp()

let longPress = UILongPressGestureRecognizer(target: self, action: #selector(didLongPress(_:)))
longPress.minimumPressDuration = 0.33
collectionView.addGestureRecognizer(longPress)

registerMessageCell(uiConfig.messageList.defaultMessageCell)
Expand Down Expand Up @@ -313,6 +316,53 @@ open class _ChatMessageListVC<ExtraData: ExtraDataTypes>: _ViewController,
)
}

private func presentReactionsControllerAnimated(
for cell: _СhatMessageCollectionViewCell<ExtraData>,
with messageData: _ChatMessageGroupPart<ExtraData>,
actionsController: _ChatMessageActionsVC<ExtraData>,
reactionsController: _ChatMessageReactionsVC<ExtraData>?
) {
// TODO: for PR: This should be doable via:
// 1. options: [.autoreverse, .repeat] and
// 2. `UIView.setAnimationRepeatCount(0)` inside the animation block...
//
// and then just set completion to the animation to transform this back. aka `cell.messageView.transform = .identity`
// however, this doesn't work as after the animation is done, it clips back to the value set in animation block
// and then on completion goes back to `.identity`... This is really strange, but I was fighting it for some time
// and couldn't find proper solution...
// Also there are some limitations to the current solution ->
// According to my debug view hiearchy, the content inside `messageView.messageBubbleView` is not constrainted to the
// bubble view itself, meaning right now if we want to scale the view of incoming message, we scale the avatarView
// of the sender as well...
UIView.animate(
withDuration: 0.2,
delay: 0,
options: [.curveEaseIn],
animations: {
cell.messageView.transform = CGAffineTransform(scaleX: 0.95, y: 0.95)
},
completion: { _ in
self.impactFeedbackGenerator.impactOccurred()

UIView.animate(
withDuration: 0.1,
delay: 0,
options: [.curveEaseOut],
animations: {
cell.messageView.transform = .identity
}
)

self.router.showMessageActionsPopUp(
messageContentFrame: cell.messageView.superview!.convert(cell.messageView.frame, to: nil),
messageData: messageData,
messageActionsController: actionsController,
messageReactionsController: reactionsController
)
}
)
}

private func didSelectMessageCell(_ cell: _СhatMessageCollectionViewCell<ExtraData>) {
guard let messageData = cell.message, messageData.isInteractionEnabled else { return }

Expand All @@ -328,11 +378,11 @@ open class _ChatMessageListVC<ExtraData: ExtraDataTypes>: _ViewController,
return controller
}

router.showMessageActionsPopUp(
messageContentFrame: cell.messageView.superview!.convert(cell.messageView.frame, to: nil),
messageData: messageData,
messageActionsController: actionsController,
messageReactionsController: reactionsController
presentReactionsControllerAnimated(
for: cell,
with: messageData,
actionsController: actionsController,
reactionsController: reactionsController
)
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,15 @@ open class _ChatMessageReactionsBubbleView<ExtraData: ExtraDataTypes>: _View, UI
// MARK: - Life Cycle

override open func updateContent() {
// TODO: Fix this when refactoring reactions.
// We check if we have available images for the given type of reaction, if not, we hide the reaction.
guard !(content?.reactions.compactMap { uiConfig.images.availableReactions[$0.type] }.isEmpty ?? false)
else {
isHidden = true
return
}

isHidden = false
contentView.content = content.flatMap {
.init(
useBigIcons: $0.style.isBig,
Expand Down
30 changes: 22 additions & 8 deletions Sources/StreamChatUI/MessageActionsPopup/ChatMessagePopupVC.swift
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ open class _ChatMessagePopupVC<ExtraData: ExtraDataTypes>: _ViewController, UICo

public var message: _ChatMessageGroupPart<ExtraData>!
public var messageViewFrame: CGRect!
public var originalMessageView: UIView!
public var actionsController: _ChatMessageActionsVC<ExtraData>!
public var reactionsController: _ChatMessageReactionsVC<ExtraData>?

Expand All @@ -53,6 +54,13 @@ open class _ChatMessagePopupVC<ExtraData: ExtraDataTypes>: _ViewController, UICo

override public func defaultAppearance() {
view.backgroundColor = .clear
blurView.alpha = 0

reactionsView?.alpha = 0
reactionsView?.transform = .init(scaleX: 0.5, y: 0.5)

actionsView.alpha = 0
actionsView.transform = .init(scaleX: 0.5, y: 0.5)
}

override open func setUpLayout() {
Expand Down Expand Up @@ -144,7 +152,6 @@ open class _ChatMessagePopupVC<ExtraData: ExtraDataTypes>: _ViewController, UICo

override open func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)

// Initially, the `applyInitialContentOffset` invocation was in `viewDidLayoutSubviews`
// since the content offset can be applied when all the views are laid out
// and `scrollView` content size is calculated.
Expand All @@ -157,23 +164,30 @@ open class _ChatMessagePopupVC<ExtraData: ExtraDataTypes>: _ViewController, UICo
// 2. postpones it to the next run-loop iteration which guarantees it happens after `viewDidLayoutSubviews`
DispatchQueue.main.async {
self.applyInitialContentOffset()

Animate {
self.scrollToMakeMessageVisible() // Makes the animation look a bit weird, but it's much faster...
self.blurView.alpha = 1

self.actionsView.alpha = 1
self.actionsView.transform = .identity
}

Animate(delay: 0.1) {
self.reactionsView?.alpha = 1
self.reactionsView?.transform = .identity
}
}
}

override open func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)

scrollToMakeMessageVisible()
}

open func applyInitialContentOffset() {
let contentOffset = CGPoint(x: 0, y: max(0, -messageViewFrame.minY + spacing + reactionsViewHeight))
scrollView.setContentOffset(contentOffset, animated: false)
}

open func scrollToMakeMessageVisible() {
let contentRect = scrollContentView.convert(contentView.frame, to: scrollView)
scrollView.scrollRectToVisible(contentRect, animated: true)
scrollView.scrollRectToVisible(contentRect, animated: false)
}

// MARK: - Actions
Expand Down
4 changes: 2 additions & 2 deletions Sources/StreamChatUI/Utils/Animation.swift
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

import UIKit

func Animate(_ actions: @escaping () -> Void, completion: ((Bool) -> Void)? = nil) {
func Animate(delay: TimeInterval = 0, _ actions: @escaping () -> Void, completion: ((Bool) -> Void)? = nil) {
guard !UIAccessibility.isReduceMotionEnabled else {
actions()
completion?(true)
Expand All @@ -13,7 +13,7 @@ func Animate(_ actions: @escaping () -> Void, completion: ((Bool) -> Void)? = ni

UIView.animate(
withDuration: 0.25,
delay: 0,
delay: delay,
usingSpringWithDamping: 0.8,
initialSpringVelocity: 4,
options: .curveEaseInOut,
Expand Down

0 comments on commit 7d4c9dd

Please sign in to comment.