Skip to content
This repository has been archived by the owner on Jan 22, 2021. It is now read-only.

Fixed issue with push notifications and internet reachability #64

Merged
merged 1 commit into from
May 23, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions iOS/rainbow.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@
5305A1C020A219380040A9A9 /* JSON.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5305A1A220A219380040A9A9 /* JSON.swift */; };
5305A1C120A219380040A9A9 /* QueryEncoder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5305A1A320A219380040A9A9 /* QueryEncoder.swift */; };
5305A1C320A219DA0040A9A9 /* ScoreEntry+Client.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5305A1C220A219DA0040A9A9 /* ScoreEntry+Client.swift */; };
530A351D20B56389003D36C5 /* Reachability.swift in Sources */ = {isa = PBXBuildFile; fileRef = 530A351C20B56389003D36C5 /* Reachability.swift */; };
531AAF8820A4B0690056B24B /* AssetViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 531AAF8520A4B0690056B24B /* AssetViewController.swift */; };
531AAF8920A4B0690056B24B /* Booklet.json in Resources */ = {isa = PBXBuildFile; fileRef = 531AAF8620A4B0690056B24B /* Booklet.json */; };
531AAF9620A4B98F0056B24B /* Lumina.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 531AAF9420A4B98F0056B24B /* Lumina.framework */; };
Expand Down Expand Up @@ -163,6 +164,7 @@
5305A1A220A219380040A9A9 /* JSON.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = JSON.swift; sourceTree = "<group>"; };
5305A1A320A219380040A9A9 /* QueryEncoder.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = QueryEncoder.swift; sourceTree = "<group>"; };
5305A1C220A219DA0040A9A9 /* ScoreEntry+Client.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ScoreEntry+Client.swift"; sourceTree = "<group>"; };
530A351C20B56389003D36C5 /* Reachability.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Reachability.swift; sourceTree = "<group>"; };
531AAF8520A4B0690056B24B /* AssetViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AssetViewController.swift; sourceTree = "<group>"; };
531AAF8620A4B0690056B24B /* Booklet.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = Booklet.json; sourceTree = "<group>"; };
531AAF9420A4B98F0056B24B /* Lumina.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Lumina.framework; path = Carthage/Build/iOS/Lumina.framework; sourceTree = SOURCE_ROOT; };
Expand Down Expand Up @@ -430,6 +432,7 @@
children = (
53AC588A20A9E9160031EDC9 /* GameObjects.json */,
53AC588C20AA17130031EDC9 /* ObjectConfig.swift */,
530A351C20B56389003D36C5 /* Reachability.swift */,
531AAF9C20A4BA280056B24B /* StyleGuides.swift */,
53381910209757BB00DE66CB /* Info.plist */,
);
Expand Down Expand Up @@ -667,6 +670,7 @@
5305A1A420A219380040A9A9 /* Contracts.swift in Sources */,
5305A1BB20A219380040A9A9 /* Client.swift in Sources */,
53AC588920A9382B0031EDC9 /* GameStartView.swift in Sources */,
530A351D20B56389003D36C5 /* Reachability.swift in Sources */,
53BAF34820AB3BB200583BE6 /* AvatarClient.swift in Sources */,
5305A1AF20A219380040A9A9 /* RestToken.swift in Sources */,
53A66F4C20AB863E00B52E55 /* ParticipantViewController.swift in Sources */,
Expand Down
6 changes: 3 additions & 3 deletions iOS/rainbow/Config/Info.plist
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,9 @@
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>CFBundleShortVersionString</key>
<string>1.0</string>
<string>1.0.1</string>
<key>CFBundleVersion</key>
<string>1</string>
<string>2</string>
<key>LSRequiresIPhoneOS</key>
<true/>
<key>NSAppTransportSecurity</key>
Expand All @@ -28,7 +28,7 @@
<true/>
</dict>
<key>NSCameraUsageDescription</key>
<string>WatsonML can&apos;t see very well without a camera!</string>
<string>WatsonML can't see very well without a camera!</string>
<key>NSMicrophoneUsageDescription</key>
<string>WatsonML requires the use of a microphone for using the camera.</string>
<key>UIAppFonts</key>
Expand Down
159 changes: 159 additions & 0 deletions iOS/rainbow/Config/Reachability.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,159 @@
//
// Reachability.swift
// rainbow
//
// Created by David Okun IBM on 5/23/18.
// Copyright © 2018 IBM. All rights reserved.
//

import Foundation
import SystemConfiguration

class Reachability {
var hostname: String?
var isRunning = false
var isReachableOnWWAN: Bool
var reachability: SCNetworkReachability?
var reachabilityFlags = SCNetworkReachabilityFlags()
let reachabilitySerialQueue = DispatchQueue(label: "ReachabilityQueue")
init?(hostname: String) throws {
guard let reachability = SCNetworkReachabilityCreateWithName(nil, hostname) else {
throw Network.Error.failedToCreateWith(hostname)
}
self.reachability = reachability
self.hostname = hostname
isReachableOnWWAN = true
}
init?() throws {
var zeroAddress = sockaddr_in()
zeroAddress.sin_len = UInt8(MemoryLayout<sockaddr_in>.size)
zeroAddress.sin_family = sa_family_t(AF_INET)
guard let reachability = withUnsafePointer(to: &zeroAddress, {
$0.withMemoryRebound(to: sockaddr.self, capacity: 1) {
SCNetworkReachabilityCreateWithAddress(nil, $0)
}}) else {
throw Network.Error.failedToInitializeWith(zeroAddress)
}
self.reachability = reachability
isReachableOnWWAN = true
}
var status: Network.Status {
return !isConnectedToNetwork ? .unreachable :
isReachableViaWiFi ? .wifi :
isRunningOnDevice ? .wwan : .unreachable
}
var isRunningOnDevice: Bool = {
#if (arch(i386) || arch(x86_64)) && os(iOS)
return false
#else
return true
#endif
}()
deinit { stop() }
}

extension Reachability {
func start() throws {
guard let reachability = reachability, !isRunning else { return }
var context = SCNetworkReachabilityContext(version: 0, info: nil, retain: nil, release: nil, copyDescription: nil)
context.info = Unmanaged<Reachability>.passUnretained(self).toOpaque()
guard SCNetworkReachabilitySetCallback(reachability, callout, &context) else { stop()
throw Network.Error.failedToSetCallout
}
guard SCNetworkReachabilitySetDispatchQueue(reachability, reachabilitySerialQueue) else { stop()
throw Network.Error.failedToSetDispatchQueue
}
reachabilitySerialQueue.async { self.flagsChanged() }
isRunning = true
}
func stop() {
defer { isRunning = false }
guard let reachability = reachability else { return }
SCNetworkReachabilitySetCallback(reachability, nil, nil)
SCNetworkReachabilitySetDispatchQueue(reachability, nil)
self.reachability = nil
}
var isConnectedToNetwork: Bool {
return isReachable &&
!isConnectionRequiredAndTransientConnection &&
!(isRunningOnDevice && isWWAN && !isReachableOnWWAN)
}
var isReachableViaWiFi: Bool {
return isReachable && isRunningOnDevice && !isWWAN
}

/// Flags that indicate the reachability of a network node name or address, including whether a connection is required, and whether some user intervention might be required when establishing a connection.
var flags: SCNetworkReachabilityFlags? {
guard let reachability = reachability else { return nil }
var flags = SCNetworkReachabilityFlags()
return withUnsafeMutablePointer(to: &flags) {
SCNetworkReachabilityGetFlags(reachability, UnsafeMutablePointer($0))
} ? flags : nil
}

/// compares the current flags with the previous flags and if changed posts a flagsChanged notification
func flagsChanged() {
guard let flags = flags, flags != reachabilityFlags else { return }
reachabilityFlags = flags
NotificationCenter.default.post(name: .flagsChanged, object: self)
}

/// The specified node name or address can be reached via a transient connection, such as PPP.
var transientConnection: Bool { return flags?.contains(.transientConnection) == true }

/// The specified node name or address can be reached using the current network configuration.
var isReachable: Bool { return flags?.contains(.reachable) == true }

/// The specified node name or address can be reached using the current network configuration, but a connection must first be established. If this flag is set, the kSCNetworkReachabilityFlagsConnectionOnTraffic flag, kSCNetworkReachabilityFlagsConnectionOnDemand flag, or kSCNetworkReachabilityFlagsIsWWAN flag is also typically set to indicate the type of connection required. If the user must manually make the connection, the kSCNetworkReachabilityFlagsInterventionRequired flag is also set.
var connectionRequired: Bool { return flags?.contains(.connectionRequired) == true }

/// The specified node name or address can be reached using the current network configuration, but a connection must first be established. Any traffic directed to the specified name or address will initiate the connection.
var connectionOnTraffic: Bool { return flags?.contains(.connectionOnTraffic) == true }

/// The specified node name or address can be reached using the current network configuration, but a connection must first be established.
var interventionRequired: Bool { return flags?.contains(.interventionRequired) == true }

/// The specified node name or address can be reached using the current network configuration, but a connection must first be established. The connection will be established "On Demand" by the CFSocketStream programming interface (see CFStream Socket Additions for information on this). Other functions will not establish the connection.
var connectionOnDemand: Bool { return flags?.contains(.connectionOnDemand) == true }

/// The specified node name or address is one that is associated with a network interface on the current system.
var isLocalAddress: Bool { return flags?.contains(.isLocalAddress) == true }

/// Network traffic to the specified node name or address will not go through a gateway, but is routed directly to one of the interfaces in the system.
var isDirect: Bool { return flags?.contains(.isDirect) == true }

/// The specified node name or address can be reached via a cellular connection, such as EDGE or GPRS.
var isWWAN: Bool { return flags?.contains(.isWWAN) == true }

/// The specified node name or address can be reached using the current network configuration, but a connection must first be established. If this flag is set
/// The specified node name or address can be reached via a transient connection, such as PPP.
//swiftlint:disable identifier_name
var isConnectionRequiredAndTransientConnection: Bool {
return (flags?.intersection([.connectionRequired, .transientConnection]) == [.connectionRequired, .transientConnection]) == true
}
}

func callout(reachability: SCNetworkReachability, flags: SCNetworkReachabilityFlags, info: UnsafeMutableRawPointer?) {
guard let info = info else { return }
DispatchQueue.main.async {
Unmanaged<Reachability>.fromOpaque(info).takeUnretainedValue().flagsChanged()
}
}

extension Notification.Name {
static let flagsChanged = Notification.Name("FlagsChanged")
}

struct Network {
static var reachability: Reachability?
enum Status: String, CustomStringConvertible {
case unreachable, wifi, wwan
var description: String { return rawValue }
}
enum Error: Swift.Error {
case failedToSetCallout
case failedToSetDispatchQueue
case failedToCreateWith(String)
case failedToInitializeWith(sockaddr_in)
}
}
23 changes: 23 additions & 0 deletions iOS/rainbow/Controller/AppDelegate.swift
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,18 @@ class AppDelegate: UIResponder, UIApplicationDelegate, BMSPushObserver {
// MARK: remove the hardcoding in future
BMSPushClient.sharedInstance.initializeWithAppGUID(appGUID: "c8a1c28e-3934-4e03-b8e2-e305ada1bb85", clientSecret: "cead9064-e0a6-4a0e-86c0-b6bbf060d871")
BMSPushClient.sharedInstance.delegate = self
do {
Network.reachability = try Reachability(hostname: "www.google.com")
do {
try Network.reachability?.start()
} catch let error as Network.Error {
print(error)
} catch {
print(error)
}
} catch {
print(error)
}
return true
}

Expand Down Expand Up @@ -103,6 +115,17 @@ class AppDelegate: UIResponder, UIApplicationDelegate, BMSPushObserver {
// MARK: - push notification
func onChangePermission(status: Bool) {
print("Push Notification is enabled: \(status)" as NSString)
guard let networkStatus = Network.reachability?.status else {
return
}
if networkStatus == .unreachable {
// no internet connectivity - bail and warn the user
SVProgressHUD.showError(withStatus: "Can't connect to the internet right now - play the game anyway!")
NotificationCenter.default.post(name: Notification.Name("viva-ml-device-token-registered"), object: "00000000-0000-0000-0000-000000000000")
} else if status == false && networkStatus != .unreachable {
// register the user for a fake ID - they'll never get notifications
NotificationCenter.default.post(name: Notification.Name("viva-ml-device-token-registered"), object: "00000000-0000-0000-0000-000000000000")
}
}

func application (_ application: UIApplication, didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data) {
Expand Down
2 changes: 1 addition & 1 deletion iOS/rainbow/Controller/AvatarAPI/AvatarClient.swift
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ class AvatarClient {
}

static func getRandomAvatar(completion: @escaping (_ avatar: UserAvatar?, _ error: Error?) -> Void) {
let request = RestRequest(method: .get, url: "http://avatar-rainbow.mybluemix.net/new", containsSelfSignedCert: false)
let request = RestRequest(method: .get, url: "https://avatar-rainbow.mybluemix.net/new", containsSelfSignedCert: false)
request.responseObject { (response: RestResponse<UserAvatar>) in
DispatchQueue.main.async {
switch response.result {
Expand Down
7 changes: 4 additions & 3 deletions iOS/rainbow/Controller/Camera/CameraController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,8 @@ class CameraController: LuminaViewController {
continueGame()
}
} catch {
startNewGame()
// NotificationCenter.default.post(name: Notification.Name("viva-ml-device-token-registered"), object: "00000000-0000-0000-0000-000000000000")
showStartView()
}
}

Expand All @@ -126,11 +127,11 @@ class CameraController: LuminaViewController {
//update the startDate to the cloud.
ScoreEntry.ServerCalls.update(entry: savedScoreEntry, completion: { entry, error in
if error != nil {
SVProgressHUD.showError(withStatus: "Could not start new game")
SVProgressHUD.showError(withStatus: "Could not start new game on server - keep playing!!")
print("error during initial user save: \(String(describing: error?.localizedDescription))")
} else {
guard let entry = entry else {
SVProgressHUD.showError(withStatus: "Could not start new game")
SVProgressHUD.showError(withStatus: "Could not start new game on server - keep playing!!")
print("error during initial user save: \(String(describing: error?.localizedDescription))")
return
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,13 +22,6 @@ class ParticipantViewController: UIViewController {
do {
let savedEntry = try ScoreEntry.ClientPersistence.get()
nameTextField?.text = savedEntry.username
guard let imageData = savedEntry.avatarImage else {
return
}
guard let image = UIImage(data: imageData) else {
return
}
avatarImageView?.image = image
guard let leaderboardButton = leaderboardButton else {
return
}
Expand All @@ -38,6 +31,13 @@ class ParticipantViewController: UIViewController {
leaderboardButton.layer.borderWidth = 0.5
leaderboardButton.layer.borderColor = UIColor.RainbowColors.orange.cgColor
leaderboardButton.setTitleColor(UIColor.RainbowColors.orange, for: .normal)
guard let imageData = savedEntry.avatarImage else {
return
}
guard let image = UIImage(data: imageData) else {
return
}
avatarImageView?.image = image

let config = try GameConfig.load()
var foundObjects = [ObjectEntry]()
Expand Down
Loading