Skip to content
This repository has been archived by the owner on Jun 7, 2020. It is now read-only.

[NEW] In-app notifications #1504

Merged
merged 51 commits into from Apr 13, 2018
Merged
Show file tree
Hide file tree
Changes from 48 commits
Commits
Show all changes
51 commits
Select commit Hold shift + click to select a range
f7c4901
minor refactoring
Sameesunkaria Apr 3, 2018
6085621
added subscription for notifications
Sameesunkaria Apr 3, 2018
fc24d67
Merged with upstream
Sameesunkaria Apr 3, 2018
e27f01e
Merge branch 'local-notifications' into develop
Sameesunkaria Apr 3, 2018
24d043f
Merge develop into local-notifications
Sameesunkaria Apr 3, 2018
b66f2df
Added Animation
Sameesunkaria Apr 4, 2018
e52f14d
Merge branch 'develop' into pr/12
Sameesunkaria Apr 5, 2018
2bf9dad
Notification view constraints changed
Sameesunkaria Apr 5, 2018
0af288b
Merge branch 'develop' into local-notifications
Sameesunkaria Apr 5, 2018
ecc11fe
Notification view controller moved to nib.
Sameesunkaria Apr 6, 2018
7a66afd
Notification view positioned correctly
Sameesunkaria Apr 7, 2018
e14c9a4
Merged with upstream
Sameesunkaria Apr 7, 2018
4cd3e16
Notification view now responds to touches
Sameesunkaria Apr 7, 2018
3ac8f90
Merge remote-tracking branch 'origin/local-notifications' into local-…
Sameesunkaria Apr 7, 2018
9010632
Added notification manager
Sameesunkaria Apr 8, 2018
01a5539
Removed notification view nib
Sameesunkaria Apr 8, 2018
5b02f35
Removed Animation
Sameesunkaria Apr 8, 2018
869a982
Resolved conflict
Sameesunkaria Apr 8, 2018
2c75db7
Merge branch 'develop' into local-notifications
rafaelks Apr 9, 2018
0f13d34
Added notification sound.
Sameesunkaria Apr 9, 2018
344c6ff
App name corrected
Sameesunkaria Apr 9, 2018
785dd8d
Added slide out
Sameesunkaria Apr 9, 2018
0628365
Merged with upstream
Sameesunkaria Apr 9, 2018
90ddc4a
Tests added
Sameesunkaria Apr 10, 2018
6e0950c
Added test cases
Sameesunkaria Apr 10, 2018
52e9960
Refactoring tests
Sameesunkaria Apr 10, 2018
668342b
Added post notification test
Sameesunkaria Apr 10, 2018
b9fa5f8
Added test cases
Sameesunkaria Apr 10, 2018
29c6010
Added descriptions
Sameesunkaria Apr 10, 2018
510d455
Added support for opening private rooms
Sameesunkaria Apr 10, 2018
3ea4b4d
Added test cases for open rooms
Sameesunkaria Apr 10, 2018
36a42cc
Added tests for notification view constraints
Sameesunkaria Apr 10, 2018
0f2a2bd
Fixed sizing issues.
Sameesunkaria Apr 10, 2018
895603f
Minor refactoring
Sameesunkaria Apr 11, 2018
a85c471
ChatNotification structure updated
Sameesunkaria Apr 11, 2018
66f4d44
Indented correctly
Sameesunkaria Apr 11, 2018
d7c6f7c
Indentation fixed
Sameesunkaria Apr 11, 2018
f7e16f1
Restructured ChatNotification
Sameesunkaria Apr 11, 2018
1508813
Changed access control identifier on internalType
Sameesunkaria Apr 11, 2018
153845e
Merge branch 'develop' of https://github.com/RocketChat/Rocket.Chat.i…
cardoso Apr 11, 2018
45c32eb
Merge branch 'local-notifications' of https://github.com/Sameesunkari…
cardoso Apr 11, 2018
8470abf
Merge branch 'develop' of https://github.com/RocketChat/Rocket.Chat.i…
cardoso Apr 11, 2018
222dd1c
Merge branch 'develop' of https://github.com/RocketChat/Rocket.Chat.i…
cardoso Apr 11, 2018
7031ab0
Fix some code style issues
cardoso Apr 11, 2018
e230919
Merge branch 'local-notifications' of github.com:Sameesunkaria/Rocket…
rafaelks Apr 12, 2018
8fc5e75
Disabled in-app notification sound and push notifications when the so…
Sameesunkaria Apr 12, 2018
cc142d3
Merge with upstream
Sameesunkaria Apr 12, 2018
e69c2e3
Notification window test added
Sameesunkaria Apr 13, 2018
3b960d7
Merge remote-tracking branch 'origin/local-notifications' into local-…
Sameesunkaria Apr 13, 2018
82ef06a
Merge branch 'local-notifications' of github.com:Sameesunkaria/Rocket…
rafaelks Apr 13, 2018
0d7e844
Merge branch 'develop' of github.com:RocketChat/Rocket.Chat.iOS into …
rafaelks Apr 13, 2018
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
87 changes: 84 additions & 3 deletions Rocket.Chat.xcodeproj/project.pbxproj

Large diffs are not rendered by default.

10 changes: 10 additions & 0 deletions Rocket.Chat/AppDelegate.swift
Expand Up @@ -14,6 +14,7 @@ import UserNotifications
class AppDelegate: UIResponder, UIApplicationDelegate {

var window: UIWindow?
var notificationWindow: UIWindow?

func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
Launcher().prepareToLaunch(with: launchOptions)
Expand All @@ -35,9 +36,18 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
WindowManager.open(.auth(serverUrl: "", credentials: nil))
}

initNotificationWindow()

return true
}

func initNotificationWindow() {
notificationWindow = TransparentToTouchesWindow(frame: UIScreen.main.bounds)
notificationWindow?.rootViewController = NotificationViewController.shared
notificationWindow?.windowLevel = UIWindowLevelAlert
notificationWindow?.makeKeyAndVisible()
}

// MARK: AppDelegate LifeCycle

func applicationDidBecomeActive(_ application: UIApplication) {
Expand Down
150 changes: 150 additions & 0 deletions Rocket.Chat/Controllers/Notification/NotificationViewController.swift
@@ -0,0 +1,150 @@
//
// NotificationViewController.swift
// Rocket.Chat
//
// Created by Samar Sunkaria on 4/3/18.
// Copyright © 2018 Rocket.Chat. All rights reserved.
//

import UIKit
import AudioToolbox

class NotificationViewController: UIViewController {

static let shared = NotificationViewController(nibName: "NotificationViewController", bundle: nil)

@IBOutlet weak var notificationView: NotificationView!
@IBOutlet weak var hiddenConstraint: NSLayoutConstraint!
@IBOutlet weak var visibleConstraint: NSLayoutConstraint!

// MARK: - Constants
var lastTouchLocation: CGPoint?
let animationDuration: TimeInterval = 0.3
let notificationVisibleDuration: TimeInterval = 6.0
let soundUrl = Bundle.main.url(forResource: "chime", withExtension: "mp3")

var notificationViewIsHidden: Bool {
get {
if let visibleConstraint = visibleConstraint, visibleConstraint.isActive {
return false
}
return true
}

set {
visibleConstraint?.isActive = !newValue
hiddenConstraint?.isActive = newValue
(UIApplication.shared.value(forKey: "statusBarWindow") as? UIWindow)?.alpha = newValue ? 1 : 0
}
}

// MARK: - View controller life cycle methods
override func viewDidLoad() {
super.viewDidLoad()
view.layer.shadowColor = UIColor.black.cgColor
view.layer.shadowOpacity = 0.3
view.layer.shadowRadius = 8.0
view.layer.shadowOffset = CGSize(width: 0, height: 0)
view.clipsToBounds = true
}

override func viewWillLayoutSubviews() {
super.viewWillLayoutSubviews()

visibleConstraint.constant = 8
if #available(iOS 11.0, *) {
if view.safeAreaInsets.top > 20 {
visibleConstraint.constant = 38
}
}
}

// MARK: - Displaying notification
weak var timer: Timer? {
willSet {
timer?.invalidate()
}
}

func displayNotification(title: String, body: String, username: String) {
guard let notificationView = notificationView else { return }

notificationView.displayNotification(title: title, body: body, username: username)

// Commented out until a setting is added to toggle the sound.
// playSound()

UIView.animate(withDuration: animationDuration) {
self.notificationViewIsHidden = false
self.view.layoutIfNeeded()
}

timer = Timer.scheduledTimer(withTimeInterval: notificationVisibleDuration, repeats: false) { [weak self] _ in
UIView.animate(withDuration: self?.animationDuration ?? 0.0) {
self?.notificationViewIsHidden = true
self?.view.layoutIfNeeded()
}
}
}

private func playSound() {
guard let soundUrl = soundUrl else { return }
var soundId: SystemSoundID = 0
AudioServicesCreateSystemSoundID(soundUrl as CFURL, &soundId)
AudioServicesPlayAlertSound(soundId)
}

}

// MARK: - Gesture Recognizers
extension NotificationViewController {
@IBAction func handleTap(_ sender: UITapGestureRecognizer) {
if sender.state == .ended {
NotificationManager.shared.didRespondToNotification()
timer?.fire()
}
}

@IBAction func handlePan(_ sender: UIPanGestureRecognizer) {
guard let notificationView = notificationView, !notificationViewIsHidden else { return }
switch sender.state {
case .began:
lastTouchLocation = sender.location(in: view)

case .changed:
guard let lastTouchLocation = lastTouchLocation else { return }
let displacement = sender.location(in: view).y - lastTouchLocation.y
let newYOffset = notificationView.frame.origin.y + displacement

if newYOffset <= visibleConstraint.constant {
notificationView.frame.origin.y += displacement
self.lastTouchLocation = sender.location(in: view)

} else if notificationView.bounds.contains(sender.location(in: notificationView)),
newYOffset <= visibleConstraint.constant + 16 {
notificationView.frame.origin.y += displacement / 10
self.lastTouchLocation = sender.location(in: view)
}

case .ended:
lastTouchLocation = nil
if sender.velocity(in: view).y < -25 {
timer?.fire()
} else {
view.setNeedsLayout()
UIView.animate(withDuration: animationDuration) {
self.view.layoutIfNeeded()
}
}

case .cancelled:
lastTouchLocation = nil
view.setNeedsLayout()
UIView.animate(withDuration: animationDuration) {
self.view.layoutIfNeeded()
}

default: break
}
}
}
115 changes: 115 additions & 0 deletions Rocket.Chat/Controllers/Notification/NotificationViewController.xib
@@ -0,0 +1,115 @@
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="14109" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES">
<device id="retina4_7" orientation="portrait">
<adaptation id="fullscreen"/>
</device>
<dependencies>
<deployment identifier="iOS"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="14088"/>
<capability name="Alignment constraints to the first baseline" minToolsVersion="6.0"/>
<capability name="Aspect ratio constraints" minToolsVersion="5.1"/>
<capability name="Baseline standard spacing" minToolsVersion="9.0"/>
<capability name="Safe area layout guides" minToolsVersion="9.0"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</dependencies>
<objects>
<placeholder placeholderIdentifier="IBFilesOwner" id="-1" userLabel="File's Owner" customClass="NotificationViewController" customModule="Rocket_Chat" customModuleProvider="target">
<connections>
<outlet property="hiddenConstraint" destination="iSN-ku-uLM" id="kxi-dT-Kgj"/>
<outlet property="notificationView" destination="PHK-lk-IxG" id="0pR-AZ-ZxP"/>
<outlet property="view" destination="iN0-l3-epB" id="0Rj-yq-cb5"/>
<outlet property="visibleConstraint" destination="1Q8-oE-2ed" id="ucf-ab-b77"/>
</connections>
</placeholder>
<placeholder placeholderIdentifier="IBFirstResponder" id="-2" customClass="UIResponder"/>
<view contentMode="scaleToFill" id="iN0-l3-epB">
<rect key="frame" x="0.0" y="0.0" width="375" height="667"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="PHK-lk-IxG" customClass="NotificationView" customModule="Rocket_Chat" customModuleProvider="target">
<rect key="frame" x="8" y="-85.5" width="359" height="69.5"/>
<subviews>
<visualEffectView opaque="NO" contentMode="scaleToFill" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="2Sn-Lw-2jc">
<rect key="frame" x="0.0" y="0.0" width="359" height="70"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<view key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" ambiguous="YES" insetsLayoutMarginsFromSafeArea="NO" id="4JV-l7-f9r">
<rect key="frame" x="0.0" y="0.0" width="359" height="70"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<gestureRecognizers/>
</view>
<blurEffect style="extraLight"/>
</visualEffectView>
<label opaque="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="252" verticalCompressionResistancePriority="751" text="Title" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="pSR-mg-fTE">
<rect key="frame" x="69" y="12.5" width="282" height="20.5"/>
<fontDescription key="fontDescription" type="boldSystem" pointSize="17"/>
<nil key="textColor"/>
<nil key="highlightedColor"/>
</label>
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="KOK-7t-SFa">
<rect key="frame" x="12" y="12.5" width="45" height="45"/>
<color key="backgroundColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
<constraints>
<constraint firstAttribute="height" constant="45" id="AsH-aH-Iwh"/>
<constraint firstAttribute="width" secondItem="KOK-7t-SFa" secondAttribute="height" multiplier="1:1" id="dcq-yW-PH5"/>
</constraints>
</view>
<label opaque="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Body" textAlignment="natural" lineBreakMode="tailTruncation" numberOfLines="0" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="Khg-Fj-whL">
<rect key="frame" x="69" y="36.5" width="282" height="20.5"/>
<fontDescription key="fontDescription" type="system" pointSize="17"/>
<color key="textColor" red="0.26051741839999998" green="0.2605243921" blue="0.260520637" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<nil key="highlightedColor"/>
</label>
</subviews>
<color key="backgroundColor" white="0.99409537845187712" alpha="0.0" colorSpace="calibratedWhite"/>
<gestureRecognizers/>
<constraints>
<constraint firstItem="Khg-Fj-whL" firstAttribute="firstBaseline" secondItem="pSR-mg-fTE" secondAttribute="baseline" constant="24" symbolType="layoutAnchor" id="48X-KW-GjT"/>
<constraint firstAttribute="trailing" secondItem="pSR-mg-fTE" secondAttribute="trailing" constant="8" id="4QG-mg-IcO"/>
<constraint firstAttribute="trailing" secondItem="Khg-Fj-whL" secondAttribute="trailing" constant="8" id="4jL-fR-L6n"/>
<constraint firstItem="KOK-7t-SFa" firstAttribute="top" secondItem="PHK-lk-IxG" secondAttribute="top" constant="12" id="7ly-Q4-bZu"/>
<constraint firstAttribute="width" relation="lessThanOrEqual" constant="550" id="G6K-9l-94u"/>
<constraint firstAttribute="bottom" relation="greaterThanOrEqual" secondItem="Khg-Fj-whL" secondAttribute="bottom" constant="8" id="eon-ja-4kU"/>
<constraint firstAttribute="height" relation="lessThanOrEqual" constant="150" id="g82-JC-2h2"/>
<constraint firstAttribute="bottom" secondItem="KOK-7t-SFa" secondAttribute="bottom" priority="250" constant="12" id="glb-6O-WiY"/>
<constraint firstItem="KOK-7t-SFa" firstAttribute="leading" secondItem="PHK-lk-IxG" secondAttribute="leading" constant="12" id="kXI-LJ-aGu"/>
<constraint firstItem="pSR-mg-fTE" firstAttribute="top" secondItem="KOK-7t-SFa" secondAttribute="top" id="oFE-M9-Shc"/>
<constraint firstItem="Khg-Fj-whL" firstAttribute="leading" secondItem="KOK-7t-SFa" secondAttribute="trailing" constant="12" id="rl6-k2-chZ"/>
<constraint firstItem="pSR-mg-fTE" firstAttribute="leading" secondItem="KOK-7t-SFa" secondAttribute="trailing" constant="12" id="y9T-ub-deS"/>
</constraints>
<connections>
<outlet property="avatarViewContainer" destination="KOK-7t-SFa" id="pg6-OP-A6F"/>
<outlet property="bodyLabel" destination="Khg-Fj-whL" id="GaY-eI-DdQ"/>
<outlet property="titleLabel" destination="pSR-mg-fTE" id="f4g-xL-MX6"/>
<outletCollection property="gestureRecognizers" destination="0eg-LY-yJc" appends="YES" id="6lE-PV-105"/>
<outletCollection property="gestureRecognizers" destination="MDE-Cs-C7p" appends="YES" id="68S-Ej-0vb"/>
</connections>
</view>
</subviews>
<color key="backgroundColor" red="1" green="1" blue="1" alpha="0.0" colorSpace="custom" customColorSpace="sRGB"/>
<constraints>
<constraint firstItem="PHK-lk-IxG" firstAttribute="width" secondItem="iN0-l3-epB" secondAttribute="width" priority="750" constant="-16" id="0jV-re-AIy"/>
<constraint firstItem="PHK-lk-IxG" firstAttribute="top" secondItem="iN0-l3-epB" secondAttribute="top" constant="8" id="1Q8-oE-2ed"/>
<constraint firstItem="PHK-lk-IxG" firstAttribute="centerX" secondItem="vUN-kp-3ea" secondAttribute="centerX" id="GSl-WQ-03d"/>
<constraint firstItem="PHK-lk-IxG" firstAttribute="width" relation="lessThanOrEqual" secondItem="iN0-l3-epB" secondAttribute="width" constant="-16" id="dOu-9J-u0B"/>
<constraint firstAttribute="top" secondItem="PHK-lk-IxG" secondAttribute="bottom" constant="16" id="iSN-ku-uLM"/>
</constraints>
<viewLayoutGuide key="safeArea" id="vUN-kp-3ea"/>
<variation key="default">
<mask key="constraints">
<exclude reference="1Q8-oE-2ed"/>
</mask>
</variation>
<point key="canvasLocation" x="34.5" y="69.5"/>
</view>
<tapGestureRecognizer id="0eg-LY-yJc">
<connections>
<action selector="handleTap:" destination="-1" id="ci7-Qi-5JF"/>
</connections>
</tapGestureRecognizer>
<panGestureRecognizer minimumNumberOfTouches="1" id="MDE-Cs-C7p">
<connections>
<action selector="handlePan:" destination="-1" id="SLu-y0-hoL"/>
</connections>
</panGestureRecognizer>
</objects>
</document>
19 changes: 13 additions & 6 deletions Rocket.Chat/Managers/AppManager.swift
Expand Up @@ -44,6 +44,12 @@ struct AppManager {
*/
static var initialRoomId: String?

/**
Room Id for the currently active room.
*/
static var currentRoomId: String? {
return ChatViewController.shared?.subscription?.rid
}
}

extension AppManager {
Expand Down Expand Up @@ -138,17 +144,17 @@ extension AppManager {
})
}

static func openChannel(name: String) {
func openChannel() -> Bool {
guard let channel = Subscription.find(name: name, subscriptionType: [.channel]) else { return false }
static func openRoom(name: String, type: SubscriptionType = .channel) {
func openRoom() -> Bool {
guard let channel = Subscription.find(name: name, subscriptionType: [type]) else { return false }

ChatViewController.shared?.subscription = channel

return true
}

// Check if we already have this channel
if openChannel() == true {
if openRoom() == true {
return
}

Expand All @@ -167,7 +173,8 @@ extension AppManager {
realm.add(subscription, update: true)
})

_ = openChannel()
_ = openRoom()

}
}, errored: nil)
}
Expand All @@ -191,7 +198,7 @@ extension AppManager {
case let .mention(name):
openDirectMessage(username: name)
case let .channel(name):
openChannel(name: name)
openRoom(name: name)
}
}
}