Commit b39593cc authored by Andreas Traczyk's avatar Andreas Traczyk Committed by Kateryna Kostiuk

connectivity: add network connection monitoring

- Adds Reachability via Carthage

Change-Id: Id4043c33afd4b62a5b69e285126eb301d924047f
Reviewed-by: Kateryna Kostiuk's avatarKateryna Kostiuk <kateryna.kostiuk@savoirfairelinux.com>
parent 153b2b5d
......@@ -5,3 +5,4 @@ github "AliSoftware/Reusable" ~> 4.0
github "SwiftyBeaver/SwiftyBeaver"
github "ViccAlexander/Chameleon"
github "andreamazz/AMPopTip"
github "ashleymills/Reachability.swift"
......@@ -5,5 +5,6 @@ github "RxSwiftCommunity/RxRealm" "0.6.0"
github "SwiftyBeaver/SwiftyBeaver" "1.4.2"
github "ViccAlexander/Chameleon" "2.2.0"
github "andreamazz/AMPopTip" "3.0.2"
github "ashleymills/Reachability.swift" "v4.1.0"
github "pkluz/PKHUD" "4.2.3"
github "realm/realm-cocoa" "v2.8.3"
......@@ -201,6 +201,8 @@
62A88D371F6C2ED400F8AB18 /* PresenceAdapterDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 62A88D361F6C2ED400F8AB18 /* PresenceAdapterDelegate.swift */; };
62A88D391F6C323500F8AB18 /* PresenceAdapter.mm in Sources */ = {isa = PBXBuildFile; fileRef = 62A88D381F6C323500F8AB18 /* PresenceAdapter.mm */; };
62A88D3B1F6C3ACC00F8AB18 /* PresenceService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 62A88D3A1F6C3ACC00F8AB18 /* PresenceService.swift */; };
62DFAB2C1F9FF030002D6F9C /* Reachability.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 62DFAB2B1F9FF030002D6F9C /* Reachability.framework */; };
62DFAB2E1F9FF0D0002D6F9C /* NetworkService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 62DFAB2D1F9FF0D0002D6F9C /* NetworkService.swift */; };
62E55B6D1F758E6F00D3FEF4 /* String+Helpers.swift in Sources */ = {isa = PBXBuildFile; fileRef = 62E55B6C1F758E6F00D3FEF4 /* String+Helpers.swift */; };
62E55B6F1F793ADE00D3FEF4 /* AvatarsColors.swift in Sources */ = {isa = PBXBuildFile; fileRef = 62E55B6E1F793ADE00D3FEF4 /* AvatarsColors.swift */; };
/* End PBXBuildFile section */
......@@ -445,6 +447,8 @@
62A88D361F6C2ED400F8AB18 /* PresenceAdapterDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PresenceAdapterDelegate.swift; sourceTree = "<group>"; };
62A88D381F6C323500F8AB18 /* PresenceAdapter.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = PresenceAdapter.mm; sourceTree = "<group>"; };
62A88D3A1F6C3ACC00F8AB18 /* PresenceService.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PresenceService.swift; sourceTree = "<group>"; };
62DFAB2B1F9FF030002D6F9C /* Reachability.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Reachability.framework; path = Carthage/Build/iOS/Reachability.framework; sourceTree = "<group>"; };
62DFAB2D1F9FF0D0002D6F9C /* NetworkService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NetworkService.swift; sourceTree = "<group>"; };
62E55B6C1F758E6F00D3FEF4 /* String+Helpers.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "String+Helpers.swift"; sourceTree = "<group>"; };
62E55B6E1F793ADE00D3FEF4 /* AvatarsColors.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AvatarsColors.swift; sourceTree = "<group>"; };
/* End PBXFileReference section */
......@@ -492,6 +496,7 @@
04399AFB1D1C341A00E99CD9 /* libpjmedia-codec.a in Frameworks */,
04399AFC1D1C341A00E99CD9 /* libpjmedia-videodev.a in Frameworks */,
04399AFD1D1C341A00E99CD9 /* libpjmedia.a in Frameworks */,
62DFAB2C1F9FF030002D6F9C /* Reachability.framework in Frameworks */,
04399AFE1D1C341A00E99CD9 /* libpjnath.a in Frameworks */,
04399AFF1D1C341A00E99CD9 /* libpjsip-simple.a in Frameworks */,
04399B001D1C341A00E99CD9 /* libpjsip-ua.a in Frameworks */,
......@@ -563,6 +568,7 @@
isa = PBXGroup;
children = (
0ED6660F1F9FED1C00743D42 /* AMPopTip.framework */,
62DFAB2B1F9FF030002D6F9C /* Reachability.framework */,
0EEFBA3B1F83DA21000EDBAD /* libsecp256k1.a */,
1A3CA32A1F102BB700283748 /* Chameleon.framework */,
1A1E476E1F0E894600EA9A36 /* SwiftyBeaver.framework */,
......@@ -597,6 +603,7 @@
1A5DC01F1F355DCF0075E8EF /* ContactsService.swift */,
62A88D361F6C2ED400F8AB18 /* PresenceAdapterDelegate.swift */,
62A88D3A1F6C3ACC00F8AB18 /* PresenceService.swift */,
62DFAB2D1F9FF0D0002D6F9C /* NetworkService.swift */,
);
path = Services;
sourceTree = "<group>";
......@@ -1285,6 +1292,7 @@
"$(SRCROOT)/Carthage/Build/iOS/SwiftyBeaver.framework",
"$(SRCROOT)/Carthage/Build/iOS/Chameleon.framework",
"$(SRCROOT)/Carthage/build/iOS/AMPopTip.framework",
"$(SRCROOT)/Carthage/Build/iOS/Reachability.framework",
);
name = "⚙️ Copy Frameworks";
outputPaths = (
......@@ -1423,6 +1431,7 @@
564C44621E943DE6000F92B1 /* NameService.swift in Sources */,
1A5DC02C1F3565250075E8EF /* MeViewController.swift in Sources */,
1A2041801F1E903B00C08435 /* CreateProfileViewController.swift in Sources */,
62DFAB2E1F9FF0D0002D6F9C /* NetworkService.swift in Sources */,
0EDE34C91F8691BB00FFA15C /* EditProfileViewModel.swift in Sources */,
04399AAD1D1C304300E99CD9 /* DRingAdapter.mm in Sources */,
1A2D18D11F29182500B2C785 /* ConversationViewController.swift in Sources */,
......
......@@ -37,6 +37,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
private let conversationsService = ConversationsService(withMessageAdapter: MessagesAdapter())
private let contactsService = ContactsService(withContactsAdapter: ContactsAdapter())
private let presenceService = PresenceService(withPresenceAdapter: PresenceAdapter())
private let networkService = NetworkService()
public lazy var injectionBag: InjectionBag = {
return InjectionBag(withDaemonService: self.daemonService,
......@@ -44,7 +45,9 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
withNameService: self.nameService,
withConversationService: self.conversationsService,
withContactsService: self.contactsService,
withPresenceService: self.presenceService)
withPresenceService: self.presenceService,
withNetworkService: self.networkService
)
}()
private lazy var appCoordinator: AppCoordinator = {
return AppCoordinator(with: self.injectionBag)
......@@ -69,6 +72,8 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
SystemAdapter().registerConfigurationHandler()
self.startDaemon()
self.networkService.monitorNetworkType()
// themetize the app
Chameleon.setGlobalThemeUsingPrimaryColor(UIColor.ringMain, withSecondaryColor: UIColor.ringSecondary, andContentStyle: .light)
Chameleon.setRingThemeUsingPrimaryColor(UIColor.ringMain, withSecondaryColor: UIColor.ringSecondary, andContentStyle: .light)
......
......@@ -51,6 +51,7 @@ enum Asset {
static let device = ImageAsset(name: "device")
static let icContactPicture = ImageAsset(name: "ic_contact_picture")
static let logoRingBeta2Blanc = ImageAsset(name: "logo-ring-beta2-blanc")
static let settingsIcon = ImageAsset(name: "settings_icon")
// swiftlint:disable trailing_comma
static let allColors: [ColorAsset] = [
......@@ -61,6 +62,7 @@ enum Asset {
device,
icContactPicture,
logoRingBeta2Blanc,
settingsIcon,
]
// swiftlint:enable trailing_comma
@available(*, deprecated, renamed: "allImages")
......
......@@ -118,8 +118,12 @@ enum L10n {
}
enum Smartlist {
/// Be sure cellular access is granted in your settings
static let cellularAccess = L10n.tr("Localizable", "smartlist.cellularAccess")
/// Conversations
static let conversations = L10n.tr("Localizable", "smartlist.conversations")
/// No network connectivity
static let noNetworkConnectivity = L10n.tr("Localizable", "smartlist.noNetworkConnectivity")
/// No results
static let noResults = L10n.tr("Localizable", "smartlist.noResults")
/// Searching...
......
......@@ -29,19 +29,22 @@ class InjectionBag {
let conversationsService: ConversationsService
let contactsService: ContactsService
let presenceService: PresenceService
let networkService: NetworkService
init (withDaemonService daemonService: DaemonService,
withAccountService accountService: AccountsService,
withNameService nameService: NameService,
withConversationService conversationService: ConversationsService,
withContactsService contactsService: ContactsService,
withPresenceService presenceService: PresenceService) {
withPresenceService presenceService: PresenceService,
withNetworkService networkService: NetworkService) {
self.daemonService = daemonService
self.accountService = accountService
self.nameService = nameService
self.conversationsService = conversationService
self.contactsService = contactsService
self.presenceService = presenceService
self.networkService = networkService
}
}
......@@ -44,6 +44,10 @@ class SmartlistViewController: UIViewController, StoryboardBased, ViewModelBased
@IBOutlet weak var searchBar: UISearchBar!
@IBOutlet weak var noConversationsView: UIView!
@IBOutlet weak var searchTableViewLabel: UILabel!
@IBOutlet weak var networkAlertLabel: UILabel!
@IBOutlet weak var cellularAlertLabel: UILabel!
@IBOutlet weak var networkAlertViewTopConstraint: NSLayoutConstraint!
@IBOutlet weak var settingsButton: UIButton!
// MARK: members
var viewModel: SmartlistViewModel!
......@@ -77,6 +81,31 @@ class SmartlistViewController: UIViewController, StoryboardBased, ViewModelBased
.bind(to: self.noConversationsView.rx.isHidden)
.disposed(by: disposeBag)
self.networkAlertViewTopConstraint.constant = self.viewModel.networkConnectionState() == .none ? 0.0 : -56.0
self.networkAlertLabel.text = L10n.Smartlist.noNetworkConnectivity
self.cellularAlertLabel.text = L10n.Smartlist.cellularAccess
self.viewModel.connectionState
.subscribe(onNext: { connectionState in
let newAlertHeight = connectionState == .none ? 0.0 : -56.0
UIView.animate(withDuration: 0.25) {
self.networkAlertViewTopConstraint.constant = CGFloat(newAlertHeight)
self.view.layoutIfNeeded()
}
})
.disposed(by: self.disposeBag)
self.settingsButton.backgroundColor = nil
self.settingsButton.rx.tap.subscribe(onNext: { _ in
if let url = URL(string: UIApplicationOpenSettingsURLString) {
if #available(iOS 10.0, *) {
UIApplication.shared.open(url, completionHandler: nil)
} else {
UIApplication.shared.openURL(url)
}
}
}).disposed(by: self.disposeBag)
self.navigationItem.rightBarButtonItem = self.editButtonItem
}
......@@ -256,6 +285,11 @@ class SmartlistViewController: UIViewController, StoryboardBased, ViewModelBased
self.searchBar.returnKeyType = .done
self.searchBar.layer.shadowColor = UIColor.black.cgColor
self.searchBar.layer.shadowOpacity = 0.5
self.searchBar.layer.shadowOffset = CGSize.zero
self.searchBar.layer.shadowRadius = 2
//Bind the SearchBar to the ViewModel
self.searchBar.rx.text.orEmpty
.debounce(Durations.textFieldThrottlingDuration.value, scheduler: MainScheduler.instance)
......
......@@ -19,9 +19,12 @@
*/
import RxSwift
import SwiftyBeaver
class SmartlistViewModel: Stateable, ViewModel {
private let log = SwiftyBeaver.self
// MARK: - Rx Stateable
private let stateSubject = PublishSubject<State>()
lazy var state: Observable<State> = {
......@@ -35,6 +38,7 @@ class SmartlistViewModel: Stateable, ViewModel {
fileprivate let nameService: NameService
fileprivate let accountsService: AccountsService
fileprivate let contactsService: ContactsService
fileprivate let networkService: NetworkService
let searchBarText = Variable<String>("")
var isSearching: Observable<Bool>!
......@@ -42,17 +46,30 @@ class SmartlistViewModel: Stateable, ViewModel {
var searchResults: Observable<[ConversationSection]>!
var hideNoConversationsMessage: Observable<Bool>!
var searchStatus = PublishSubject<String>()
var connectionState = PublishSubject<ConnectionType>()
fileprivate var filteredResults = Variable([ConversationViewModel]())
fileprivate var contactFoundConversation = Variable<ConversationViewModel?>(nil)
fileprivate var conversationViewModels = [ConversationViewModel]()
func networkConnectionState() -> ConnectionType {
return self.networkService.connectionState.value
}
required init(with injectionBag: InjectionBag) {
self.conversationsService = injectionBag.conversationsService
self.nameService = injectionBag.nameService
self.accountsService = injectionBag.accountService
self.contactsService = injectionBag.contactsService
self.networkService = injectionBag.networkService
// Observe connectivity changes
self.networkService.connectionStateObservable
.subscribe(onNext: { value in
self.connectionState.onNext(value)
})
.disposed(by: self.disposeBag)
//Create observable from sorted conversations and flatMap them to view models
let conversationsObservable: Observable<[ConversationViewModel]> = self.conversationsService.conversations.asObservable().map({ conversations in
......
......@@ -84,6 +84,11 @@
"idiom" : "ipad",
"size" : "83.5x83.5",
"scale" : "2x"
},
{
"idiom" : "ios-marketing",
"size" : "1024x1024",
"scale" : "1x"
}
],
"info" : {
......
{
"images" : [
{
"idiom" : "universal",
"filename" : "ic_settings.png",
"scale" : "1x"
},
{
"idiom" : "universal",
"filename" : "ic_settings_2x.png",
"scale" : "2x"
},
{
"idiom" : "universal",
"filename" : "ic_settings_3x.png",
"scale" : "3x"
}
],
"info" : {
"version" : 1,
"author" : "xcode"
}
}
\ No newline at end of file
......@@ -30,6 +30,8 @@
"smartlist.conversations" = "Conversations";
"smartlist.searching" = "Searching...";
"smartlist.noResults" = "No results";
"smartlist.noNetworkConnectivity" = "No network connectivity";
"smartlist.cellularAccess" = "Be sure cellular access is granted in your settings";
// Walkthrough
......
/*
* Copyright (C) 2017 Savoir-faire Linux Inc.
*
* Author: Andreas Traczyk <andreas.traczyk@savoirfairelinux.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
import Foundation
import Reachability
import SwiftyBeaver
import RxSwift
enum ConnectionType {
case none
case wifi
case cellular
}
class NetworkService {
private let log = SwiftyBeaver.self
let reachability: Reachability!
var connectionState = Variable<ConnectionType>(.none)
lazy var connectionStateObservable: Observable<ConnectionType> = {
return self.connectionState.asObservable()
}()
init() {
reachability = Reachability()!
}
func monitorNetworkType() {
reachability.whenReachable = { reachability in
if reachability.connection == .wifi {
self.connectionState.value = .wifi
} else {
self.connectionState.value = .cellular
}
}
reachability.whenUnreachable = { _ in
self.connectionState.value = .none
}
do {
try reachability.startNotifier()
self.log.debug("network notifier started")
} catch {
self.log.debug("unable to start network notifier")
}
}
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment