diff --git a/Ring/Cartfile b/Ring/Cartfile index 53cd434b9f013fc7a63ad980a9d2c1bf4b4ce1d6..a3b764c392d97dd478cc60cccd5b0aa3f66720af 100644 --- a/Ring/Cartfile +++ b/Ring/Cartfile @@ -5,3 +5,4 @@ github "AliSoftware/Reusable" ~> 4.0 github "SwiftyBeaver/SwiftyBeaver" github "ViccAlexander/Chameleon" github "andreamazz/AMPopTip" +github "ashleymills/Reachability.swift" diff --git a/Ring/Cartfile.resolved b/Ring/Cartfile.resolved index 6c11cfdb3944f140ac9167d7f556a5e9193b4bfc..8e13fb592ddc47183c7c67c6aa71a84147dbcc6c 100644 --- a/Ring/Cartfile.resolved +++ b/Ring/Cartfile.resolved @@ -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" diff --git a/Ring/Ring.xcodeproj/project.pbxproj b/Ring/Ring.xcodeproj/project.pbxproj index 8deccaa0b32f90ff600063b0fa0123927732f8d1..06e7bc644f4bd4a10c426ad77f96be57ad5412a8 100644 --- a/Ring/Ring.xcodeproj/project.pbxproj +++ b/Ring/Ring.xcodeproj/project.pbxproj @@ -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 */, diff --git a/Ring/Ring/AppDelegate.swift b/Ring/Ring/AppDelegate.swift index 57f30ffbf9b3e34349d3b755c2c57d654f44ed0b..7a73e47b9d156025c16bbf182da89562e5849a1d 100644 --- a/Ring/Ring/AppDelegate.swift +++ b/Ring/Ring/AppDelegate.swift @@ -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) diff --git a/Ring/Ring/Constants/Generated/Images.swift b/Ring/Ring/Constants/Generated/Images.swift index a1305fc567077e411d87d73922d7032760f92103..7fc3ef64fb3fd928fbe6d263f6d39e910e1c0b40 100644 --- a/Ring/Ring/Constants/Generated/Images.swift +++ b/Ring/Ring/Constants/Generated/Images.swift @@ -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") diff --git a/Ring/Ring/Constants/Generated/Strings.swift b/Ring/Ring/Constants/Generated/Strings.swift index 8a0a35e977dd0e6457a027c29e12f5fa014610f7..cfa3ccbb94e36a16c26de5206b543c1cb34a7989 100644 --- a/Ring/Ring/Constants/Generated/Strings.swift +++ b/Ring/Ring/Constants/Generated/Strings.swift @@ -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... diff --git a/Ring/Ring/Coordinators/InjectionBag.swift b/Ring/Ring/Coordinators/InjectionBag.swift index 5c003c8571921510632426710c0040348c6a1439..0968c3a988c76d4478c1de329afd3696206906d5 100644 --- a/Ring/Ring/Coordinators/InjectionBag.swift +++ b/Ring/Ring/Coordinators/InjectionBag.swift @@ -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 } } diff --git a/Ring/Ring/Features/Conversations/SmartList/SmartlistViewController.storyboard b/Ring/Ring/Features/Conversations/SmartList/SmartlistViewController.storyboard index 2eab0160c3be631b39d6d4741eb9e8d61ddee840..8603f41e66dd4b5c97f4a1c31712c144a8169f89 100644 --- a/Ring/Ring/Features/Conversations/SmartList/SmartlistViewController.storyboard +++ b/Ring/Ring/Features/Conversations/SmartList/SmartlistViewController.storyboard @@ -1,11 +1,11 @@ <?xml version="1.0" encoding="UTF-8"?> -<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="12121" systemVersion="16F73" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" colorMatched="YES" initialViewController="Raw-Ee-7sK"> +<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="13196" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" colorMatched="YES" initialViewController="Raw-Ee-7sK"> <device id="retina4_7" orientation="portrait"> <adaptation id="fullscreen"/> </device> <dependencies> <deployment identifier="iOS"/> - <plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="12089"/> + <plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="13174"/> <capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/> </dependencies> <scenes> @@ -21,8 +21,59 @@ <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="e5o-cY-djH" userLabel="Network Alert View"> + <rect key="frame" x="0.0" y="76" width="375" height="56"/> + <subviews> + <view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="HKv-H1-GYI" userLabel="Alert Labels View"> + <rect key="frame" x="171" y="0.0" width="33" height="56"/> + <subviews> + <label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Label" textAlignment="center" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="Fu7-Dr-XvA" userLabel="Network Alert Label"> + <rect key="frame" x="-2.5" y="10" width="37.5" height="18"/> + <fontDescription key="fontDescription" type="system" pointSize="15"/> + <color key="textColor" white="1" alpha="1" colorSpace="calibratedWhite"/> + <nil key="highlightedColor"/> + </label> + <label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" misplaced="YES" text="Label" textAlignment="center" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="dHy-gp-i6K" userLabel="Cellular Alert Label"> + <rect key="frame" x="101" y="55" width="37.5" height="18"/> + <fontDescription key="fontDescription" type="system" pointSize="13"/> + <color key="textColor" white="1" alpha="1" colorSpace="calibratedWhite"/> + <nil key="highlightedColor"/> + </label> + </subviews> + <constraints> + <constraint firstItem="Fu7-Dr-XvA" firstAttribute="top" secondItem="HKv-H1-GYI" secondAttribute="top" constant="10" id="SAj-hd-YiO"/> + <constraint firstItem="Fu7-Dr-XvA" firstAttribute="centerX" secondItem="HKv-H1-GYI" secondAttribute="centerX" id="apx-hI-eg9"/> + <constraint firstAttribute="height" constant="56" id="j6O-zU-YVL"/> + <constraint firstAttribute="width" secondItem="dHy-gp-i6K" secondAttribute="width" id="mfY-pK-Ush"/> + <constraint firstItem="dHy-gp-i6K" firstAttribute="centerX" secondItem="HKv-H1-GYI" secondAttribute="centerX" id="orq-Lq-Fmo"/> + <constraint firstItem="dHy-gp-i6K" firstAttribute="top" secondItem="Fu7-Dr-XvA" secondAttribute="bottom" id="pOy-bX-Jpj"/> + </constraints> + </view> + <button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="iaz-fd-fEz" userLabel="Settings Button"> + <rect key="frame" x="220" y="16" width="24" height="24"/> + <accessibility key="accessibilityConfiguration"> + <accessibilityTraits key="traits" button="YES" image="YES"/> + </accessibility> + <constraints> + <constraint firstAttribute="height" constant="24" id="d6g-Zu-vR7"/> + <constraint firstAttribute="width" constant="24" id="nbF-R8-5kY"/> + </constraints> + <state key="normal" image="settings_icon"/> + </button> + </subviews> + <color key="backgroundColor" red="0.94117647058823528" green="0.4392156862745098" blue="0.4392156862745098" alpha="1" colorSpace="calibratedRGB"/> + <constraints> + <constraint firstItem="iaz-fd-fEz" firstAttribute="leading" secondItem="HKv-H1-GYI" secondAttribute="trailing" constant="16" id="AFY-4C-aBu"/> + <constraint firstItem="HKv-H1-GYI" firstAttribute="leading" relation="greaterThanOrEqual" secondItem="e5o-cY-djH" secondAttribute="leading" constant="10" id="QoG-HF-kux"/> + <constraint firstAttribute="trailing" relation="greaterThanOrEqual" secondItem="iaz-fd-fEz" secondAttribute="trailing" constant="10" id="VGV-dn-yaS"/> + <constraint firstItem="HKv-H1-GYI" firstAttribute="centerY" secondItem="e5o-cY-djH" secondAttribute="centerY" id="jtZ-C0-Lms"/> + <constraint firstAttribute="height" constant="56" id="qrO-B9-Qmw"/> + <constraint firstItem="iaz-fd-fEz" firstAttribute="centerY" secondItem="e5o-cY-djH" secondAttribute="centerY" id="xx8-oG-TVU"/> + <constraint firstItem="HKv-H1-GYI" firstAttribute="centerX" secondItem="e5o-cY-djH" secondAttribute="centerX" priority="750" id="yI5-9z-dUv"/> + </constraints> + </view> <searchBar contentMode="redraw" placeholder="Enter name..." translatesAutoresizingMaskIntoConstraints="NO" id="xPr-nI-I35"> - <rect key="frame" x="0.0" y="20" width="375" height="44"/> + <rect key="frame" x="0.0" y="20" width="375" height="56"/> <color key="backgroundColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/> <color key="tintColor" white="0.33333333333333331" alpha="1" colorSpace="calibratedWhite"/> <offsetWrapper key="searchFieldBackgroundPositionAdjustment" horizontal="0.0" vertical="0.0"/> @@ -32,14 +83,14 @@ </connections> </searchBar> <tableView clipsSubviews="YES" contentMode="scaleToFill" alwaysBounceVertical="YES" dataMode="prototypes" style="plain" separatorStyle="default" rowHeight="44" sectionHeaderHeight="28" sectionFooterHeight="28" translatesAutoresizingMaskIntoConstraints="NO" id="HFM-G6-hMN"> - <rect key="frame" x="0.0" y="64" width="375" height="603"/> + <rect key="frame" x="0.0" y="132" width="375" height="535"/> <color key="backgroundColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/> </tableView> <view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="EvL-Bu-O1T"> - <rect key="frame" x="0.0" y="64" width="375" height="603"/> + <rect key="frame" x="0.0" y="132" width="375" height="535"/> <subviews> <label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="No conversations" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="8bB-XU-6gh"> - <rect key="frame" x="121.5" y="291" width="133" height="21"/> + <rect key="frame" x="121.5" y="257" width="133" height="21"/> <fontDescription key="fontDescription" type="system" pointSize="17"/> <color key="textColor" white="0.33333333333333331" alpha="1" colorSpace="calibratedWhite"/> <nil key="highlightedColor"/> @@ -47,12 +98,12 @@ </subviews> <color key="backgroundColor" white="1" alpha="1" colorSpace="calibratedWhite"/> <constraints> - <constraint firstItem="8bB-XU-6gh" firstAttribute="centerY" secondItem="EvL-Bu-O1T" secondAttribute="centerY" id="PQg-Np-JN3"/> - <constraint firstItem="8bB-XU-6gh" firstAttribute="centerX" secondItem="EvL-Bu-O1T" secondAttribute="centerX" id="gY7-3l-mS7"/> + <constraint firstItem="8bB-XU-6gh" firstAttribute="centerY" secondItem="EvL-Bu-O1T" secondAttribute="centerY" id="1R2-tE-dtX"/> + <constraint firstItem="8bB-XU-6gh" firstAttribute="centerX" secondItem="EvL-Bu-O1T" secondAttribute="centerX" id="PCa-ph-Sbp"/> </constraints> </view> <tableView hidden="YES" clipsSubviews="YES" contentMode="scaleToFill" alwaysBounceVertical="YES" dataMode="prototypes" style="grouped" separatorStyle="default" rowHeight="44" sectionHeaderHeight="1" sectionFooterHeight="1" translatesAutoresizingMaskIntoConstraints="NO" id="opE-y7-3Rm"> - <rect key="frame" x="0.0" y="64" width="375" height="603"/> + <rect key="frame" x="0.0" y="132" width="375" height="535"/> <color key="backgroundColor" cocoaTouchSystemColor="groupTableViewBackgroundColor"/> <label key="tableHeaderView" opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Label" textAlignment="center" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" id="4Yu-Fe-ixq"> <rect key="frame" x="0.0" y="0.0" width="375" height="24"/> @@ -71,15 +122,18 @@ <constraint firstItem="xPr-nI-I35" firstAttribute="leading" secondItem="2dZ-8A-4nq" secondAttribute="leading" id="4NO-53-KBh"/> <constraint firstAttribute="trailing" secondItem="HFM-G6-hMN" secondAttribute="trailing" id="7H1-ka-2Ti"/> <constraint firstItem="HFM-G6-hMN" firstAttribute="leading" secondItem="2dZ-8A-4nq" secondAttribute="leading" id="8tr-Rd-1kr"/> - <constraint firstItem="HFM-G6-hMN" firstAttribute="top" secondItem="xPr-nI-I35" secondAttribute="bottom" id="9lS-x7-SaG"/> + <constraint firstItem="e5o-cY-djH" firstAttribute="top" secondItem="xPr-nI-I35" secondAttribute="bottom" id="CLm-6J-lYv"/> <constraint firstAttribute="trailing" secondItem="opE-y7-3Rm" secondAttribute="trailing" id="D0f-nM-KEs"/> + <constraint firstItem="HFM-G6-hMN" firstAttribute="top" secondItem="e5o-cY-djH" secondAttribute="bottom" id="KK7-rp-AZz"/> + <constraint firstItem="opE-y7-3Rm" firstAttribute="top" secondItem="e5o-cY-djH" secondAttribute="bottom" id="NKz-A0-hLf"/> <constraint firstItem="xPr-nI-I35" firstAttribute="top" secondItem="sbJ-yn-t3e" secondAttribute="bottom" id="OX7-et-d3F"/> <constraint firstItem="cfq-zl-uux" firstAttribute="top" secondItem="EvL-Bu-O1T" secondAttribute="bottom" id="TsM-9H-eI1"/> <constraint firstItem="cfq-zl-uux" firstAttribute="top" secondItem="HFM-G6-hMN" secondAttribute="bottom" id="VfB-5H-uHq"/> - <constraint firstItem="opE-y7-3Rm" firstAttribute="top" secondItem="xPr-nI-I35" secondAttribute="bottom" id="aeg-VV-nOD"/> + <constraint firstItem="EvL-Bu-O1T" firstAttribute="top" secondItem="e5o-cY-djH" secondAttribute="bottom" id="aH0-7e-i4a"/> <constraint firstAttribute="trailing" secondItem="xPr-nI-I35" secondAttribute="trailing" id="dtX-d4-r9P"/> - <constraint firstItem="EvL-Bu-O1T" firstAttribute="top" secondItem="xPr-nI-I35" secondAttribute="bottom" id="izx-u2-l6g"/> + <constraint firstAttribute="trailing" secondItem="e5o-cY-djH" secondAttribute="trailing" id="k9v-18-oxL"/> <constraint firstAttribute="trailing" secondItem="EvL-Bu-O1T" secondAttribute="trailing" id="nSK-QH-snj"/> + <constraint firstItem="e5o-cY-djH" firstAttribute="leading" secondItem="2dZ-8A-4nq" secondAttribute="leading" id="rXu-fF-ESz"/> <constraint firstItem="cfq-zl-uux" firstAttribute="top" secondItem="opE-y7-3Rm" secondAttribute="bottom" id="wun-hC-1JP"/> </constraints> </view> @@ -87,11 +141,15 @@ <tabBarItem key="tabBarItem" title="Home" id="o5c-No-Dpq"/> <navigationItem key="navigationItem" id="zLl-0A-Dht"/> <connections> + <outlet property="cellularAlertLabel" destination="dHy-gp-i6K" id="W9w-Mi-GTY"/> <outlet property="conversationsTableView" destination="HFM-G6-hMN" id="M97-IB-NUZ"/> + <outlet property="networkAlertLabel" destination="Fu7-Dr-XvA" id="0qV-lk-9mE"/> + <outlet property="networkAlertViewTopConstraint" destination="CLm-6J-lYv" id="LEO-Ep-Gpx"/> <outlet property="noConversationsView" destination="EvL-Bu-O1T" id="tVV-6a-4Xg"/> <outlet property="searchBar" destination="xPr-nI-I35" id="Y3U-rV-yfc"/> <outlet property="searchResultsTableView" destination="opE-y7-3Rm" id="F3g-9d-IQt"/> <outlet property="searchTableViewLabel" destination="4Yu-Fe-ixq" id="dq3-QM-bfA"/> + <outlet property="settingsButton" destination="iaz-fd-fEz" id="R2O-R8-BDk"/> <outlet property="tableView" destination="HFM-G6-hMN" id="Gci-vk-ijr"/> </connections> </viewController> @@ -99,5 +157,25 @@ </objects> <point key="canvasLocation" x="-97.5" y="-1177.8169014084508"/> </scene> + <!--View Controller--> + <scene sceneID="YBM-c2-EWl"> + <objects> + <viewController id="9uj-4q-nGi" sceneMemberID="viewController"> + <layoutGuides> + <viewControllerLayoutGuide type="top" id="EEa-ZN-DDP"/> + <viewControllerLayoutGuide type="bottom" id="VVG-rs-LcN"/> + </layoutGuides> + <view key="view" contentMode="scaleToFill" id="zCu-ae-k0m"> + <rect key="frame" x="0.0" y="0.0" width="375" height="667"/> + <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/> + <color key="backgroundColor" white="1" alpha="1" colorSpace="calibratedWhite"/> + </view> + </viewController> + <placeholder placeholderIdentifier="IBFirstResponder" id="bOs-td-edL" userLabel="First Responder" sceneMemberID="firstResponder"/> + </objects> + </scene> </scenes> + <resources> + <image name="settings_icon" width="24" height="24"/> + </resources> </document> diff --git a/Ring/Ring/Features/Conversations/SmartList/SmartlistViewController.swift b/Ring/Ring/Features/Conversations/SmartList/SmartlistViewController.swift index 52cf2121e9e5053aa78e2592f16a84485cfc28b8..e92ba3354a56716080a40c3201a0d6d37ffb9282 100644 --- a/Ring/Ring/Features/Conversations/SmartList/SmartlistViewController.swift +++ b/Ring/Ring/Features/Conversations/SmartList/SmartlistViewController.swift @@ -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) diff --git a/Ring/Ring/Features/Conversations/SmartList/SmartlistViewModel.swift b/Ring/Ring/Features/Conversations/SmartList/SmartlistViewModel.swift index 9ef2b93b39fe8639c35c73ab0de56a5d21a3443e..fcb6574481e7e0cf336bde00c15702238396bf88 100644 --- a/Ring/Ring/Features/Conversations/SmartList/SmartlistViewModel.swift +++ b/Ring/Ring/Features/Conversations/SmartList/SmartlistViewModel.swift @@ -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 diff --git a/Ring/Ring/Resources/Images.xcassets/AppIcon.appiconset/Contents.json b/Ring/Ring/Resources/Images.xcassets/AppIcon.appiconset/Contents.json index 1d060ed28827ed6aca9565d946e6b5595c8978df..d8db8d65fd79fd541b2b7eba75c7378af3448f9c 100644 --- a/Ring/Ring/Resources/Images.xcassets/AppIcon.appiconset/Contents.json +++ b/Ring/Ring/Resources/Images.xcassets/AppIcon.appiconset/Contents.json @@ -84,6 +84,11 @@ "idiom" : "ipad", "size" : "83.5x83.5", "scale" : "2x" + }, + { + "idiom" : "ios-marketing", + "size" : "1024x1024", + "scale" : "1x" } ], "info" : { diff --git a/Ring/Ring/Resources/Images.xcassets/settings_icon.imageset/Contents.json b/Ring/Ring/Resources/Images.xcassets/settings_icon.imageset/Contents.json new file mode 100644 index 0000000000000000000000000000000000000000..095c0e8e4f2e1bcf008419ccfc40f32f62fba949 --- /dev/null +++ b/Ring/Ring/Resources/Images.xcassets/settings_icon.imageset/Contents.json @@ -0,0 +1,23 @@ +{ + "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 diff --git a/Ring/Ring/Resources/Images.xcassets/settings_icon.imageset/ic_settings.png b/Ring/Ring/Resources/Images.xcassets/settings_icon.imageset/ic_settings.png new file mode 100644 index 0000000000000000000000000000000000000000..7674e94d111480840ec311dc76e1af35a636ff86 Binary files /dev/null and b/Ring/Ring/Resources/Images.xcassets/settings_icon.imageset/ic_settings.png differ diff --git a/Ring/Ring/Resources/Images.xcassets/settings_icon.imageset/ic_settings_2x.png b/Ring/Ring/Resources/Images.xcassets/settings_icon.imageset/ic_settings_2x.png new file mode 100644 index 0000000000000000000000000000000000000000..43b56f13e329ef3619047e62cb8d5a9854cdc7ee Binary files /dev/null and b/Ring/Ring/Resources/Images.xcassets/settings_icon.imageset/ic_settings_2x.png differ diff --git a/Ring/Ring/Resources/Images.xcassets/settings_icon.imageset/ic_settings_3x.png b/Ring/Ring/Resources/Images.xcassets/settings_icon.imageset/ic_settings_3x.png new file mode 100644 index 0000000000000000000000000000000000000000..12a9406dadb5cdc49a8cd20370da745142754f77 Binary files /dev/null and b/Ring/Ring/Resources/Images.xcassets/settings_icon.imageset/ic_settings_3x.png differ diff --git a/Ring/Ring/Resources/en.lproj/Localizable.strings b/Ring/Ring/Resources/en.lproj/Localizable.strings index b25c012be9015d47a87b41f7e416110ce21f8d9b..a91dcb7d2b82c821f3c92d926c9162c468ec9005 100644 --- a/Ring/Ring/Resources/en.lproj/Localizable.strings +++ b/Ring/Ring/Resources/en.lproj/Localizable.strings @@ -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 diff --git a/Ring/Ring/Services/NetworkService.swift b/Ring/Ring/Services/NetworkService.swift new file mode 100644 index 0000000000000000000000000000000000000000..68603e0d8c3b3f9fd89f47decb9e79c44227e5e0 --- /dev/null +++ b/Ring/Ring/Services/NetworkService.swift @@ -0,0 +1,70 @@ +/* + * 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") + } + + } +}