diff --git a/Ring/Ring.xcodeproj/project.pbxproj b/Ring/Ring.xcodeproj/project.pbxproj index 2777f220a1ad238a1f4ac3dadcffc7d1acce5c25..198d149675e0e101e1a20b3442246aab06239e74 100644 --- a/Ring/Ring.xcodeproj/project.pbxproj +++ b/Ring/Ring.xcodeproj/project.pbxproj @@ -40,9 +40,7 @@ 0438663B1D2313B700E06CE2 /* AccountDetailsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0438663A1D2313B700E06CE2 /* AccountDetailsViewController.swift */; }; 043999F71D1C2D9D00E99CD9 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 043999F61D1C2D9D00E99CD9 /* AppDelegate.swift */; }; 043999FA1D1C2D9D00E99CD9 /* Ring.xcdatamodeld in Sources */ = {isa = PBXBuildFile; fileRef = 043999F81D1C2D9D00E99CD9 /* Ring.xcdatamodeld */; }; - 04399A011D1C2D9D00E99CD9 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 043999FF1D1C2D9D00E99CD9 /* Main.storyboard */; }; 04399A031D1C2D9D00E99CD9 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 04399A021D1C2D9D00E99CD9 /* Assets.xcassets */; }; - 04399A061D1C2D9D00E99CD9 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 04399A041D1C2D9D00E99CD9 /* LaunchScreen.storyboard */; }; 04399A111D1C2D9D00E99CD9 /* RingTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 04399A101D1C2D9D00E99CD9 /* RingTests.swift */; }; 04399A1C1D1C2D9D00E99CD9 /* RingUITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 04399A1B1D1C2D9D00E99CD9 /* RingUITests.swift */; }; 04399A2A1D1C2DE300E99CD9 /* CoreMedia.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 04399A291D1C2DE300E99CD9 /* CoreMedia.framework */; }; @@ -108,6 +106,7 @@ 5557FD4E1E81B1F20043E394 /* AccountModelHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5516C29E1E71CEFF009D3D2D /* AccountModelHelper.swift */; }; 5557FD4F1E81B2990043E394 /* AccountCredentialsModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 02DD80C91E1EAF1A009A3510 /* AccountCredentialsModel.swift */; }; 557086521E8ADB9D001A7CE4 /* SystemAdapter.mm in Sources */ = {isa = PBXBuildFile; fileRef = 557086511E8ADB9D001A7CE4 /* SystemAdapter.mm */; }; + 562FB6CD1EFAD18A00C61A78 /* ConversationViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 562FB6CC1EFAD18A00C61A78 /* ConversationViewController.swift */; }; 56308BA71EA00E5700660275 /* NameRegistrationResponse.m in Sources */ = {isa = PBXBuildFile; fileRef = 56308BA61EA00E5700660275 /* NameRegistrationResponse.m */; }; 563AEC771EA664C0003A5641 /* RegistrationResponse.m in Sources */ = {isa = PBXBuildFile; fileRef = 563AEC761EA664C0003A5641 /* RegistrationResponse.m */; }; 564C44591E8D7F8F000F92B1 /* LocalizedStringTableNames.swift in Sources */ = {isa = PBXBuildFile; fileRef = 564C44581E8D7F8F000F92B1 /* LocalizedStringTableNames.swift */; }; @@ -122,7 +121,6 @@ 5669A8031EAA58E6003C7B93 /* LinkDeviceToAccountViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5669A8021EAA58E6003C7B93 /* LinkDeviceToAccountViewController.swift */; }; 568F56751EA7E5DE00132D7D /* PKHUD.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 568F56721EA7E38F00132D7D /* PKHUD.framework */; }; 56AC64D51E7C7F4000EA1AA9 /* WelcomeViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 56AC64D41E7C7F4000EA1AA9 /* WelcomeViewController.swift */; }; - 56AC64D91E8012CA00EA1AA9 /* Walkthrough.strings in Resources */ = {isa = PBXBuildFile; fileRef = 56AC64DB1E8012CA00EA1AA9 /* Walkthrough.strings */; }; 56AC64DF1E804ECC00EA1AA9 /* SwitchCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 56AC64DE1E804ECC00EA1AA9 /* SwitchCell.swift */; }; 56AC64E11E80542300EA1AA9 /* TextFieldCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 56AC64E01E80542300EA1AA9 /* TextFieldCell.swift */; }; 56AC64E31E805F0200EA1AA9 /* TextCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 56AC64E21E805F0200EA1AA9 /* TextCell.swift */; }; @@ -141,9 +139,16 @@ 56BBC9BA1ED715FE00CDAF8B /* ContactHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 56BBC9B81ED715FE00CDAF8B /* ContactHelper.swift */; }; 56BBC9BC1ED7161200CDAF8B /* Date+Helpers.swift in Sources */ = {isa = PBXBuildFile; fileRef = 56BBC9BB1ED7161200CDAF8B /* Date+Helpers.swift */; }; 56BBC9BF1ED7168400CDAF8B /* SmartlistViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 56BBC9BE1ED7168400CDAF8B /* SmartlistViewModel.swift */; }; - 56BBC9C51ED8BF3300CDAF8B /* libargon2.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 56BBC9C41ED8BF3300CDAF8B /* libargon2.a */; }; - 56BBC9DB1EDDC7F700CDAF8B /* LookupNameResponse.m in Sources */ = {isa = PBXBuildFile; fileRef = 56BBC9D91EDDC0B400CDAF8B /* LookupNameResponse.m */; }; - 56BBC9DC1EDDC82600CDAF8B /* ConversationViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 56BBC9AF1ED7155700CDAF8B /* ConversationViewModel.swift */; }; + 56BBC9CD1EDC5E7000CDAF8B /* MessageAccessoryView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 56BBC9C71EDC5E7000CDAF8B /* MessageAccessoryView.swift */; }; + 56BBC9CE1EDC5E7000CDAF8B /* MessageAccessoryView.xib in Resources */ = {isa = PBXBuildFile; fileRef = 56BBC9C81EDC5E7000CDAF8B /* MessageAccessoryView.xib */; }; + 56BBC9CF1EDC5E7000CDAF8B /* MessageCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 56BBC9C91EDC5E7000CDAF8B /* MessageCell.swift */; }; + 56BBC9D01EDC5E7000CDAF8B /* MessageCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = 56BBC9CA1EDC5E7000CDAF8B /* MessageCell.xib */; }; + 56BBC9D21EDC5E7000CDAF8B /* MessageViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 56BBC9CC1EDC5E7000CDAF8B /* MessageViewModel.swift */; }; + 56BBC9D41EDC7A6D00CDAF8B /* libargon2.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 56BBC9D31EDC7A6D00CDAF8B /* libargon2.a */; }; + 56BBC9D51EDCA85900CDAF8B /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 043999FF1D1C2D9D00E99CD9 /* Main.storyboard */; }; + 56BBC9DF1EDDC9D300CDAF8B /* LookupNameResponse.m in Sources */ = {isa = PBXBuildFile; fileRef = 56BBC9DE1EDDC9D300CDAF8B /* LookupNameResponse.m */; }; + 56BBC9E01EDDC9E600CDAF8B /* ConversationViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 56BBC9AF1ED7155700CDAF8B /* ConversationViewModel.swift */; }; + 56BBC9E11EDDCA5900CDAF8B /* Walkthrough.strings in Resources */ = {isa = PBXBuildFile; fileRef = 56AC64DB1E8012CA00EA1AA9 /* Walkthrough.strings */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -275,6 +280,7 @@ 5557FD491E81AE850043E394 /* AccountModelHelperTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AccountModelHelperTests.swift; sourceTree = "<group>"; }; 557086501E8ADB9D001A7CE4 /* SystemAdapter.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = SystemAdapter.h; path = Bridging/SystemAdapter.h; sourceTree = "<group>"; }; 557086511E8ADB9D001A7CE4 /* SystemAdapter.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; name = SystemAdapter.mm; path = Bridging/SystemAdapter.mm; sourceTree = "<group>"; }; + 562FB6CC1EFAD18A00C61A78 /* ConversationViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ConversationViewController.swift; sourceTree = "<group>"; }; 56308BA51EA00E5700660275 /* NameRegistrationResponse.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = NameRegistrationResponse.h; sourceTree = "<group>"; }; 56308BA61EA00E5700660275 /* NameRegistrationResponse.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = NameRegistrationResponse.m; sourceTree = "<group>"; }; 563AEC751EA664C0003A5641 /* RegistrationResponse.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RegistrationResponse.h; sourceTree = "<group>"; }; @@ -313,9 +319,14 @@ 56BBC9B81ED715FE00CDAF8B /* ContactHelper.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ContactHelper.swift; sourceTree = "<group>"; }; 56BBC9BB1ED7161200CDAF8B /* Date+Helpers.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = "Date+Helpers.swift"; path = "Extensions/Date+Helpers.swift"; sourceTree = "<group>"; }; 56BBC9BE1ED7168400CDAF8B /* SmartlistViewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SmartlistViewModel.swift; sourceTree = "<group>"; }; - 56BBC9C41ED8BF3300CDAF8B /* libargon2.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = libargon2.a; path = ../fat/lib/libargon2.a; sourceTree = "<group>"; }; - 56BBC9D81EDDC0B400CDAF8B /* LookupNameResponse.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = LookupNameResponse.h; sourceTree = "<group>"; }; - 56BBC9D91EDDC0B400CDAF8B /* LookupNameResponse.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = LookupNameResponse.m; sourceTree = "<group>"; }; + 56BBC9C71EDC5E7000CDAF8B /* MessageAccessoryView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MessageAccessoryView.swift; sourceTree = "<group>"; }; + 56BBC9C81EDC5E7000CDAF8B /* MessageAccessoryView.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = MessageAccessoryView.xib; sourceTree = "<group>"; }; + 56BBC9C91EDC5E7000CDAF8B /* MessageCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MessageCell.swift; sourceTree = "<group>"; }; + 56BBC9CA1EDC5E7000CDAF8B /* MessageCell.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = MessageCell.xib; sourceTree = "<group>"; }; + 56BBC9CC1EDC5E7000CDAF8B /* MessageViewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MessageViewModel.swift; sourceTree = "<group>"; }; + 56BBC9D31EDC7A6D00CDAF8B /* libargon2.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = libargon2.a; path = ../fat/lib/libargon2.a; sourceTree = "<group>"; }; + 56BBC9DD1EDDC9D300CDAF8B /* LookupNameResponse.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = LookupNameResponse.h; sourceTree = "<group>"; }; + 56BBC9DE1EDDC9D300CDAF8B /* LookupNameResponse.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = LookupNameResponse.m; sourceTree = "<group>"; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -323,7 +334,7 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( - 56BBC9C51ED8BF3300CDAF8B /* libargon2.a in Frameworks */, + 56BBC9D41EDC7A6D00CDAF8B /* libargon2.a in Frameworks */, 568F56751EA7E5DE00132D7D /* PKHUD.framework in Frameworks */, 02674C851E0C757B0065EDF9 /* RxCocoa.framework in Frameworks */, 02674C861E0C757B0065EDF9 /* RxSwift.framework in Frameworks */, @@ -437,7 +448,7 @@ 02AED8171DD4C4B000F740BA /* Frameworks */ = { isa = PBXGroup; children = ( - 56BBC9C41ED8BF3300CDAF8B /* libargon2.a */, + 56BBC9D31EDC7A6D00CDAF8B /* libargon2.a */, 568F56721EA7E38F00132D7D /* PKHUD.framework */, 02674C801E0C757B0065EDF9 /* RxBlocking.framework */, 02674C811E0C757B0065EDF9 /* RxCocoa.framework */, @@ -687,8 +698,8 @@ 563AEC731EA6627F003A5641 /* NameRegistration */ = { isa = PBXGroup; children = ( - 56BBC9D81EDDC0B400CDAF8B /* LookupNameResponse.h */, - 56BBC9D91EDDC0B400CDAF8B /* LookupNameResponse.m */, + 56BBC9DD1EDDC9D300CDAF8B /* LookupNameResponse.h */, + 56BBC9DE1EDDC9D300CDAF8B /* LookupNameResponse.m */, 564C445E1E943C37000F92B1 /* NameRegistrationAdapter.h */, 564C445F1E943C37000F92B1 /* NameRegistrationAdapter.mm */, 56308BA51EA00E5700660275 /* NameRegistrationResponse.h */, @@ -744,6 +755,11 @@ 56BBC9A41ED7150200CDAF8B /* Messages */ = { isa = PBXGroup; children = ( + 56BBC9C71EDC5E7000CDAF8B /* MessageAccessoryView.swift */, + 56BBC9C81EDC5E7000CDAF8B /* MessageAccessoryView.xib */, + 56BBC9C91EDC5E7000CDAF8B /* MessageCell.swift */, + 56BBC9CA1EDC5E7000CDAF8B /* MessageCell.xib */, + 56BBC9CC1EDC5E7000CDAF8B /* MessageViewModel.swift */, 56BBC9A51ED7151500CDAF8B /* MessageModel.swift */, ); name = Messages; @@ -752,6 +768,7 @@ 56BBC9AD1ED7154800CDAF8B /* Conversations */ = { isa = PBXGroup; children = ( + 562FB6CC1EFAD18A00C61A78 /* ConversationViewController.swift */, 56BBC9B21ED7156500CDAF8B /* ConversationCell.swift */, 56BBC9B31ED7156500CDAF8B /* ConversationCell.xib */, 56BBC9AE1ED7155700CDAF8B /* ConversationModel.swift */, @@ -849,6 +866,11 @@ CreatedOnToolsVersion = 7.3.1; DevelopmentTeam = KM95526DS8; LastSwiftMigration = 0810; + SystemCapabilities = { + com.apple.BackgroundModes = { + enabled = 0; + }; + }; }; 04399A0B1D1C2D9D00E99CD9 = { CreatedOnToolsVersion = 7.3.1; @@ -887,17 +909,18 @@ isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( - 56AC64D91E8012CA00EA1AA9 /* Walkthrough.strings in Resources */, 02B22E031DF755F7000358C9 /* WalkthroughStoryboard.storyboard in Resources */, 5669A7FE1EA904E4003C7B93 /* TextCell.xib in Resources */, - 04399A061D1C2D9D00E99CD9 /* LaunchScreen.storyboard in Resources */, 56BBC9AC1ED7154300CDAF8B /* Smartlist.strings in Resources */, 56BBC9AA1ED7153800CDAF8B /* Global.strings in Resources */, + 56BBC9CE1EDC5E7000CDAF8B /* MessageAccessoryView.xib in Resources */, + 56BBC9D51EDCA85900CDAF8B /* Main.storyboard in Resources */, 04399A031D1C2D9D00E99CD9 /* Assets.xcassets in Resources */, - 04399A011D1C2D9D00E99CD9 /* Main.storyboard in Resources */, + 56BBC9D01EDC5E7000CDAF8B /* MessageCell.xib in Resources */, 5669A7FA1EA904AF003C7B93 /* SwitchCell.xib in Resources */, 56BBC9B51ED7156500CDAF8B /* ConversationCell.xib in Resources */, 5669A7FC1EA904D2003C7B93 /* TextFieldCell.xib in Resources */, + 56BBC9E11EDDCA5900CDAF8B /* Walkthrough.strings in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -941,7 +964,6 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( - 56BBC9DC1EDDC82600CDAF8B /* ConversationViewModel.swift in Sources */, 557086521E8ADB9D001A7CE4 /* SystemAdapter.mm in Sources */, 5669A8031EAA58E6003C7B93 /* LinkDeviceToAccountViewController.swift in Sources */, 0273C3051E0C68B100CF00BA /* CreateProfileViewController.swift in Sources */, @@ -951,7 +973,9 @@ 02B22E091DF7585F000358C9 /* DaemonService.swift in Sources */, 0273C3061E0C68B100CF00BA /* CreateRingAccountViewController.swift in Sources */, 56BBC99F1ED714CB00CDAF8B /* MessagesAdapter.mm in Sources */, + 56BBC9CF1EDC5E7000CDAF8B /* MessageCell.swift in Sources */, 02C9B63F1E1D4E8C00F82F0C /* ServiceEvent.swift in Sources */, + 56BBC9D21EDC5E7000CDAF8B /* MessageViewModel.swift in Sources */, 02DD80CD1E1EB2E4009A3510 /* ConfigKeyModel.swift in Sources */, 56BBC9A81ED7152300CDAF8B /* SmartlistViewController.swift in Sources */, 5516C29F1E71CEFF009D3D2D /* AccountModelHelper.swift in Sources */, @@ -961,7 +985,6 @@ 56AC650E1E85694D00EA1AA9 /* RoundedTextField.swift in Sources */, 56BBC9BF1ED7168400CDAF8B /* SmartlistViewModel.swift in Sources */, 02B22E011DF755E5000358C9 /* MainTabBarViewController.swift in Sources */, - 56BBC9DB1EDDC7F700CDAF8B /* LookupNameResponse.m in Sources */, 564C44641E943E1E000F92B1 /* NameRegistrationAdapterDelegate.swift in Sources */, 043999F71D1C2D9D00E99CD9 /* AppDelegate.swift in Sources */, 02B22DFC1DF755BB000358C9 /* AccountModel.swift in Sources */, @@ -977,11 +1000,15 @@ 043999FA1D1C2D9D00E99CD9 /* Ring.xcdatamodeld in Sources */, 564C445B1E8EA44E000F92B1 /* Durations.swift in Sources */, 0438663B1D2313B700E06CE2 /* AccountDetailsViewController.swift in Sources */, + 56BBC9DF1EDDC9D300CDAF8B /* LookupNameResponse.m in Sources */, + 56BBC9CD1EDC5E7000CDAF8B /* MessageAccessoryView.swift in Sources */, 02DD80CA1E1EAF1A009A3510 /* AccountCredentialsModel.swift in Sources */, 56559B171EEED50D00BF20E1 /* Colors.swift in Sources */, 0273C3081E0C68BF00CF00BA /* RoundedButton.swift in Sources */, 56BBC9BC1ED7161200CDAF8B /* Date+Helpers.swift in Sources */, + 562FB6CD1EFAD18A00C61A78 /* ConversationViewController.swift in Sources */, 564C44621E943DE6000F92B1 /* NameService.swift in Sources */, + 56BBC9E01EDDC9E600CDAF8B /* ConversationViewModel.swift in Sources */, 043866361D22D06500E06CE2 /* AccountTableViewCell.swift in Sources */, 04399AAD1D1C304300E99CD9 /* DRingAdapter.mm in Sources */, 0273C2FF1E0C438F00CF00BA /* AccountAdapterDelegate.swift in Sources */, diff --git a/Ring/Ring/Account/AccountModelHelper.swift b/Ring/Ring/Account/AccountModelHelper.swift index f64dcb689fde8cd867f0295704eb34a474c83a89..202bdeba510804209343e65c7862089f9c197dca 100644 --- a/Ring/Ring/Account/AccountModelHelper.swift +++ b/Ring/Ring/Account/AccountModelHelper.swift @@ -120,4 +120,18 @@ struct AccountModelHelper { } return self.account } + + var ringId :String? { + + let accountUsernameKey = ConfigKeyModel(withKey: ConfigKey.AccountUsername) + let accountUsername = self.account.details.get(withConfigKeyModel: accountUsernameKey) + + let ringIdPrefix = "ring:" + if accountUsername.contains(ringIdPrefix) { + let index = accountUsername.range(of: ringIdPrefix)?.upperBound + return accountUsername.substring(from: index!) + } else { + return nil + } + } } diff --git a/Ring/Ring/Base.lproj/Main.storyboard b/Ring/Ring/Base.lproj/Main.storyboard index e764877f82a6a7e7410a3fe8fa64474347a0cada..a7363869122022e228df8d102d491020602a79f3 100644 --- a/Ring/Ring/Base.lproj/Main.storyboard +++ b/Ring/Ring/Base.lproj/Main.storyboard @@ -248,12 +248,76 @@ <navigationItem key="navigationItem" id="b8m-eG-Q9D"/> <connections> <outlet property="tableView" destination="B6Y-MZ-L7L" id="dXp-J4-x68"/> + <segue destination="Qlv-cA-wRT" kind="show" identifier="ShowMessages" id="X75-kM-dPZ"/> </connections> </viewController> <placeholder placeholderIdentifier="IBFirstResponder" id="rzQ-ll-5bo" userLabel="First Responder" sceneMemberID="firstResponder"/> </objects> <point key="canvasLocation" x="-97.5" y="-1177.8169014084508"/> </scene> + <!--Conversation View Controller--> + <scene sceneID="N9T-Vl-P5n"> + <objects> + <viewController hidesBottomBarWhenPushed="YES" id="Qlv-cA-wRT" customClass="ConversationViewController" customModule="Ring" customModuleProvider="target" sceneMemberID="viewController"> + <layoutGuides> + <viewControllerLayoutGuide type="top" id="wEb-Zj-bvJ"/> + <viewControllerLayoutGuide type="bottom" id="S9d-I1-nWj"/> + </layoutGuides> + <view key="view" contentMode="scaleToFill" misplaced="YES" id="jPi-CC-dFO"> + <rect key="frame" x="0.0" y="64" width="320" height="455"/> + <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/> + <subviews> + <tableView clipsSubviews="YES" contentMode="scaleToFill" alwaysBounceVertical="YES" dataMode="prototypes" style="plain" separatorStyle="default" rowHeight="44" sectionHeaderHeight="28" sectionFooterHeight="28" translatesAutoresizingMaskIntoConstraints="NO" id="M1r-X5-oFv"> + <rect key="frame" x="0.0" y="0.0" width="320" height="455"/> + <color key="backgroundColor" white="1" alpha="1" colorSpace="calibratedWhite"/> + <prototypes> + <tableViewCell clipsSubviews="YES" contentMode="scaleToFill" selectionStyle="default" indentationWidth="10" reuseIdentifier="MessageCellId" id="8rX-Qa-Ypu"> + <rect key="frame" x="0.0" y="28" width="320" height="44"/> + <autoresizingMask key="autoresizingMask"/> + <tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" tableViewCell="8rX-Qa-Ypu" id="7vA-nx-B3h"> + <rect key="frame" x="0.0" y="0.0" width="320" height="44"/> + <autoresizingMask key="autoresizingMask"/> + </tableViewCellContentView> + </tableViewCell> + </prototypes> + </tableView> + <view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="PYu-2x-JXM"> + <rect key="frame" x="0.0" y="0.0" width="320" height="455"/> + <subviews> + <activityIndicatorView opaque="NO" contentMode="scaleToFill" horizontalHuggingPriority="750" verticalHuggingPriority="750" animating="YES" style="whiteLarge" translatesAutoresizingMaskIntoConstraints="NO" id="oH9-4M-JpG"> + <rect key="frame" x="142" y="209" width="37" height="37"/> + <color key="color" white="0.33333333333333331" alpha="1" colorSpace="calibratedWhite"/> + </activityIndicatorView> + </subviews> + <color key="backgroundColor" white="1" alpha="1" colorSpace="calibratedWhite"/> + <constraints> + <constraint firstItem="oH9-4M-JpG" firstAttribute="centerY" secondItem="PYu-2x-JXM" secondAttribute="centerY" id="105-id-Oqu"/> + <constraint firstItem="oH9-4M-JpG" firstAttribute="centerX" secondItem="PYu-2x-JXM" secondAttribute="centerX" id="Fk6-H6-Vdq"/> + </constraints> + </view> + </subviews> + <color key="backgroundColor" white="1" alpha="1" colorSpace="calibratedWhite"/> + <constraints> + <constraint firstAttribute="trailing" secondItem="M1r-X5-oFv" secondAttribute="trailing" id="4Dh-nK-p84"/> + <constraint firstItem="M1r-X5-oFv" firstAttribute="top" secondItem="jPi-CC-dFO" secondAttribute="top" id="BFo-2E-BaG"/> + <constraint firstAttribute="bottom" secondItem="M1r-X5-oFv" secondAttribute="bottom" id="PoM-IX-CFt"/> + <constraint firstAttribute="trailing" secondItem="PYu-2x-JXM" secondAttribute="trailing" id="X7H-Er-Udk"/> + <constraint firstItem="PYu-2x-JXM" firstAttribute="leading" secondItem="jPi-CC-dFO" secondAttribute="leading" id="dQk-7P-cld"/> + <constraint firstItem="PYu-2x-JXM" firstAttribute="top" secondItem="jPi-CC-dFO" secondAttribute="top" id="g9s-sd-GJC"/> + <constraint firstItem="M1r-X5-oFv" firstAttribute="leading" secondItem="jPi-CC-dFO" secondAttribute="leading" id="i0E-9E-6co"/> + <constraint firstAttribute="bottom" secondItem="PYu-2x-JXM" secondAttribute="bottom" id="j3b-gd-iBK"/> + </constraints> + </view> + <extendedEdge key="edgesForExtendedLayout"/> + <connections> + <outlet property="spinnerView" destination="PYu-2x-JXM" id="hxZ-lU-3XC"/> + <outlet property="tableView" destination="M1r-X5-oFv" id="5Lh-Iu-nQb"/> + </connections> + </viewController> + <placeholder placeholderIdentifier="IBFirstResponder" id="bv6-qf-2Pa" userLabel="First Responder" sceneMemberID="firstResponder"/> + </objects> + <point key="canvasLocation" x="844" y="-1179"/> + </scene> <!--Ring--> <scene sceneID="oqo-zJ-m0o"> <objects> diff --git a/Ring/Ring/ConversationCell.xib b/Ring/Ring/ConversationCell.xib index c48708bf66698f6ca971f9c9e28c5943c5e1dc38..2125486ba74100ef966f7813d9c137f55471095d 100644 --- a/Ring/Ring/ConversationCell.xib +++ b/Ring/Ring/ConversationCell.xib @@ -80,7 +80,7 @@ <constraints> <constraint firstItem="2fJ-Wf-1e0" firstAttribute="leading" secondItem="pFB-Jn-TNP" secondAttribute="trailing" constant="4" id="2NV-6m-dri"/> <constraint firstItem="eug-ak-r49" firstAttribute="leading" secondItem="pFB-Jn-TNP" secondAttribute="trailing" constant="4" id="9ah-Ed-RlY"/> - <constraint firstItem="pFB-Jn-TNP" firstAttribute="centerY" secondItem="H2p-sc-9uM" secondAttribute="centerY" id="9mO-5E-3lA"/> + <constraint firstItem="pFB-Jn-TNP" firstAttribute="centerY" secondItem="H2p-sc-9uM" secondAttribute="centerY" id="9mO-5E-3lA"/> <constraint firstItem="7Yv-cC-LKx" firstAttribute="leading" secondItem="2fJ-Wf-1e0" secondAttribute="trailing" constant="4" id="BzU-Ya-2ME"/> <constraint firstAttribute="trailing" secondItem="eug-ak-r49" secondAttribute="trailing" constant="16" id="ITl-14-BeZ"/> <constraint firstItem="JTE-eF-Y5s" firstAttribute="trailing" secondItem="pFB-Jn-TNP" secondAttribute="trailing" id="MgK-cd-QXM"/> diff --git a/Ring/Ring/ConversationViewController.swift b/Ring/Ring/ConversationViewController.swift new file mode 100644 index 0000000000000000000000000000000000000000..22638ed7478bfee6ded5f72bd31ffc82fe37f4ca --- /dev/null +++ b/Ring/Ring/ConversationViewController.swift @@ -0,0 +1,168 @@ +/* + * Copyright (C) 2017 Savoir-faire Linux Inc. + * + * Author: Silbino Gonçalves Matado <silbino.gmatado@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 UIKit +import RxSwift + +class ConversationViewController: UIViewController, UITextFieldDelegate { + + let disposeBag = DisposeBag() + + var viewModel: ConversationViewModel? + var textFieldShouldEndEditing = false + var bottomOffset :CGFloat = 0 + + @IBOutlet weak var tableView: UITableView! + @IBOutlet weak var spinnerView: UIView! + + override func viewDidLoad() { + super.viewDidLoad() + + self.setupUI() + self.setupTableView() + self.setupBindings() + + self.messageAccessoryView.messageTextField.delegate = self + + /* + Register to keyboard notifications to adjust tableView insets when the keybaord appears + or disappears + */ + NotificationCenter.default.addObserver(self, selector: #selector(keyboardWillShow(withNotification:)), name: NSNotification.Name.UIKeyboardDidShow, object: nil) + NotificationCenter.default.addObserver(self, selector: #selector(keyboardWillHide(withNotification:)), name: NSNotification.Name.UIKeyboardWillHide, object: nil) + } + + func keyboardWillShow(withNotification notification: Notification) { + + let userInfo: Dictionary = notification.userInfo! + let keyboardFrame: NSValue = userInfo[UIKeyboardFrameEndUserInfoKey] as! NSValue + let keyboardRectangle = keyboardFrame.cgRectValue + let keyboardHeight = keyboardRectangle.height + + self.tableView.contentInset.bottom = keyboardHeight + self.tableView.scrollIndicatorInsets.bottom = keyboardHeight + + self.scrollToBottom(animated: true) + self.updateBottomOffset() + } + + func keyboardWillHide(withNotification notification: Notification) { + self.tableView.contentInset.bottom = 0 + self.tableView.scrollIndicatorInsets.bottom = 0 + self.updateBottomOffset() + } + + func setupUI() { + self.viewModel?.userName.bind(to: self.navigationItem.rx.title).addDisposableTo(disposeBag) + + self.tableView.contentInset.bottom = messageAccessoryView.frame.size.height + self.tableView.scrollIndicatorInsets.bottom = messageAccessoryView.frame.size.height + } + + override func viewDidAppear(_ animated: Bool) { + super.viewDidAppear(animated) + + self.scrollToBottom(animated: false) + self.messagesLoadingFinished() + } + + override func viewWillDisappear(_ animated: Bool) { + super.viewWillDisappear(animated) + + self.textFieldShouldEndEditing = true + self.viewModel?.setMessagesAsRead() + } + + func setupTableView() { + self.tableView.estimatedRowHeight = 50 + self.tableView.rowHeight = UITableViewAutomaticDimension + self.tableView.separatorStyle = .none + + //Register cell + self.tableView.register(UINib.init(nibName: "MessageCell", bundle: nil), + forCellReuseIdentifier: "MessageCellId") + + //Bind the TableView to the ViewModel + self.viewModel?.messages.bind(to: tableView.rx.items(cellIdentifier: "MessageCellId", cellType: MessageCell.self)) + { index, messageViewModel, cell in + cell.messageLabel.text = messageViewModel.content + cell.bubblePosition = messageViewModel.bubblePosition() + }.addDisposableTo(disposeBag) + + //Scroll to bottom when reloaded + self.tableView.rx.methodInvoked(#selector(UITableView.reloadData)).subscribe(onNext: { element in + self.scrollToBottomIfNeed() + self.updateBottomOffset() + }).addDisposableTo(disposeBag) + } + + fileprivate func updateBottomOffset() { + self.bottomOffset = self.tableView.contentSize.height + - ( self.tableView.frame.size.height + - self.tableView.contentInset.top + - self.tableView.contentInset.bottom ) + } + + fileprivate func messagesLoadingFinished() { + self.spinnerView.isHidden = true + } + + fileprivate func scrollToBottomIfNeed() { + if self.isBottomContentOffset { + self.scrollToBottom(animated: true) + } + } + + fileprivate func scrollToBottom(animated: Bool) { + let last = IndexPath(row: self.tableView.numberOfRows(inSection: 0) - 1, section: 0) + self.tableView.scrollToRow(at: last, at: .bottom, animated: animated) + } + + fileprivate var isBottomContentOffset: Bool { + return self.tableView.contentOffset.y + self.tableView.contentInset.top >= bottomOffset + } + + override var inputAccessoryView: UIView { + return self.messageAccessoryView + } + + override var canBecomeFirstResponder: Bool { + return true + } + + lazy var messageAccessoryView: MessageAccessoryView = { + return MessageAccessoryView.instanceFromNib() + }() + + func setupBindings() { + + //Binds the keyboard Send button action to the ViewModel + self.messageAccessoryView.messageTextField.rx.controlEvent(.editingDidEndOnExit).subscribe(onNext: { event in + self.viewModel?.sendMessage(withContent: self.messageAccessoryView.messageTextField.text!) + self.messageAccessoryView.messageTextField.text = "" + }).addDisposableTo(disposeBag) + } + + // Avoid the keyboard to be hidden when the Send button is touched + func textFieldShouldEndEditing(_ textField: UITextField) -> Bool { + return textFieldShouldEndEditing + } + +} diff --git a/Ring/Ring/ConversationViewModel.swift b/Ring/Ring/ConversationViewModel.swift index 19537035c2ece2f2a61117460ba0f5707da4f8ea..833f8d369979a07e4e292d95920673d78e3d49da 100644 --- a/Ring/Ring/ConversationViewModel.swift +++ b/Ring/Ring/ConversationViewModel.swift @@ -34,28 +34,40 @@ class ConversationViewModel { private let disposeBag = DisposeBag() + let messages :Observable<[MessageViewModel]> + + private let conversationsService = AppDelegate.conversationsService + private let accountService = AppDelegate.accountService + init(withConversation conversation: ConversationModel) { self.conversation = conversation + dateFormatter.dateStyle = .medium hourFormatter.dateFormat = "HH:mm" self.userName = ContactHelper.lookupUserName(forRingId: self.conversation.recipient.ringId, nameService: AppDelegate.nameService, disposeBag: self.disposeBag).asObservable() + + //Create observable from sorted conversations and flatMap them to view models + self.messages = self.conversationsService.conversations.asObservable().map({ conversations in + return conversations.filter({ currentConversation in + return currentConversation.recipient == conversation.recipient + }).flatMap({ conversation in + conversation.messages.map({ message in + return MessageViewModel(withMessage: message) + }) + }) + }).observeOn(MainScheduler.instance) + } var unreadMessages: String { return self.unreadMessagesCount.description } - fileprivate var unreadMessagesCount: Int { - return self.conversation.messages.filter({ message in - return message.status != .read - }).count - } - var hasUnreadMessages: Bool { - return conversation.messages.count > 0 + return unreadMessagesCount > 0 } var lastMessage: String { @@ -96,4 +108,43 @@ class ConversationViewModel { return dateFormatter.string(from: lastMessageDate) } } + + var hideNewMessagesLabel: Bool { + return self.unreadMessagesCount == 0 + } + + func sendMessage(withContent content: String) { + self.conversationsService + .sendMessage(withContent: content, + from: accountService.currentAccount!, + to: self.conversation.recipient) + .subscribe(onCompleted: { + let accountHelper = AccountModelHelper(withAccount: self.accountService.currentAccount!) + self.saveMessage(withContent: content, byAuthor: accountHelper.ringId!, toConversationWith: self.conversation.recipient.ringId) + }).addDisposableTo(disposeBag) + } + + fileprivate func saveMessage(withContent content: String, byAuthor author: String, toConversationWith account: String) { + self.conversationsService + .saveMessage(withContent: content, byAuthor: author, toConversationWith: account) + .subscribe(onCompleted: { + print("Message saved") + }) + .addDisposableTo(disposeBag) + } + + func setMessagesAsRead() { + self.conversationsService + .setMessagesAsRead(forConversation: self.conversation) + .subscribe(onCompleted: { + print("Message set as read") + }).addDisposableTo(disposeBag) + } + + fileprivate var unreadMessagesCount: Int { + let accountHelper = AccountModelHelper(withAccount: self.accountService.currentAccount!) + return self.conversation.messages.filter({ message in + return message.status != .read && message.author != accountHelper.ringId! + }).count + } } diff --git a/Ring/Ring/ConversationsService.swift b/Ring/Ring/ConversationsService.swift index 0d77c07f2d8ad3e59e61e775e247b420647846e6..a6f7b9d5c17a98658dd3e885ece8329d64039cf0 100644 --- a/Ring/Ring/ConversationsService.swift +++ b/Ring/Ring/ConversationsService.swift @@ -27,28 +27,34 @@ class ConversationsService: MessagesAdapterDelegate { fileprivate let disposeBag = DisposeBag() fileprivate let textPlainMIMEType = "text/plain" - let conversations = Variable([ConversationModel]()) + var conversations = Variable([ConversationModel]()) init(withMessageAdapter messageAdapter: MessagesAdapter) { self.messageAdapter = messageAdapter MessagesAdapter.delegate = self - } - func status(forMessageId messageId: UInt64) -> MessageStatus { - return self.messageAdapter.status(forMessageId: messageId) } - //MARK: Message Adapter delegate + func sendMessage(withContent content: String, from senderAccount: AccountModel, to recipient: ContactModel) -> Completable { - func didReceiveMessage(_ message: Dictionary<String, String>, from senderAccount: String, - to receiverAccountId: String) { + return Completable.create(subscribe: { [unowned self] completable in + let contentDict = [self.textPlainMIMEType : content] + self.messageAdapter.sendMessage(withContent: contentDict, withAccountId: senderAccount.id, to: recipient.ringId) - if let content = message[textPlainMIMEType] { - let message = MessageModel(withId: nil, receivedDate: Date(), content: content, author: senderAccount) + completable(.completed) + + return Disposables.create {} + }) + } + + func saveMessage(withContent content: String, byAuthor author: String, toConversationWith account: String) -> Completable { + + return Completable.create(subscribe: { [unowned self] completable in + let message = MessageModel(withId: nil, receivedDate: Date(), content: content, author: author) //Get conversations for this sender - var currentConversation = conversations.value.filter({ conversation in - return conversation.recipient.ringId == senderAccount + var currentConversation = self.conversations.value.filter({ conversation in + return conversation.recipient.ringId == account }).first //Get the current array of conversations @@ -56,7 +62,7 @@ class ConversationsService: MessagesAdapterDelegate { //Create a new conversation for this sender if not exists if currentConversation == nil { - currentConversation = ConversationModel(withRecipient: ContactModel(withRingId: senderAccount), accountId: receiverAccountId) + currentConversation = ConversationModel(withRecipient: ContactModel(withRingId: account), accountId: author) currentConversations.append(currentConversation!) } @@ -65,6 +71,55 @@ class ConversationsService: MessagesAdapterDelegate { //Upate the value of the Variable self.conversations.value = currentConversations + + completable(.completed) + + return Disposables.create { } + + }) + } + + func status(forMessageId messageId: UInt64) -> MessageStatus { + return self.messageAdapter.status(forMessageId: messageId) + } + + func setMessagesAsRead(forConversation conversation: ConversationModel) -> Completable { + + return Completable.create(subscribe: { completable in + + //Get the current array of conversations + let currentConversations = self.conversations.value + + //Filter unread messages + let unreadMessages = conversation.messages.filter({ messages in + return messages.status != .read + }) + + for message in unreadMessages { + message.status = .read + } + + //Upate the value of the Variable + self.conversations.value = currentConversations + + completable(.completed) + + return Disposables.create { } + + }) + } + + //MARK: Message Adapter delegate + + func didReceiveMessage(_ message: Dictionary<String, String>, from senderAccount: String, + to receiverAccountId: String) { + + if let content = message[textPlainMIMEType] { + self.saveMessage(withContent: content, byAuthor: senderAccount, toConversationWith: senderAccount) + .subscribe(onCompleted: { + print("Message saved") + }) + .addDisposableTo(disposeBag) } } diff --git a/Ring/Ring/Info.plist b/Ring/Ring/Info.plist index 3af58c779856da670ecf406ac70a6fba07529474..b4b43918405a24faceb474711e49899f51a8cf87 100644 --- a/Ring/Ring/Info.plist +++ b/Ring/Ring/Info.plist @@ -30,6 +30,8 @@ <array> <string>armv7</string> </array> + <key>UIStatusBarStyle</key> + <string>UIStatusBarStyleLightContent</string> <key>UIStatusBarTintParameters</key> <dict> <key>UINavigationBar</key> diff --git a/Ring/Ring/MessageAccessoryView.swift b/Ring/Ring/MessageAccessoryView.swift new file mode 100644 index 0000000000000000000000000000000000000000..3e285bca7be51bf1315de062e195ab0d4d3785a9 --- /dev/null +++ b/Ring/Ring/MessageAccessoryView.swift @@ -0,0 +1,32 @@ +/* + * Copyright (C) 2017 Savoir-faire Linux Inc. + * + * Author: Silbino Gonçalves Matado <silbino.gmatado@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 UIKit + +class MessageAccessoryView: UIView { + + @IBOutlet weak var messageTextField: UITextField! + + class func instanceFromNib() -> MessageAccessoryView { + return UINib(nibName: "MessageAccessoryView", bundle: nil) + .instantiate(withOwner: nil, options: nil).first as! MessageAccessoryView + } + +} diff --git a/Ring/Ring/MessageAccessoryView.xib b/Ring/Ring/MessageAccessoryView.xib new file mode 100644 index 0000000000000000000000000000000000000000..65027b081e3ac504ae14a56552833c4049a90ee9 --- /dev/null +++ b/Ring/Ring/MessageAccessoryView.xib @@ -0,0 +1,40 @@ +<?xml version="1.0" encoding="UTF-8" standalone="no"?> +<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="11762" systemVersion="15G31" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" colorMatched="YES"> + <device id="retina4_7" orientation="portrait"> + <adaptation id="fullscreen"/> + </device> + <dependencies> + <deployment identifier="iOS"/> + <plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="11757"/> + <capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/> + </dependencies> + <objects> + <placeholder placeholderIdentifier="IBFilesOwner" id="-1" userLabel="File's Owner"/> + <placeholder placeholderIdentifier="IBFirstResponder" id="-2" customClass="UIResponder"/> + <view contentMode="scaleToFill" id="Fja-dy-lIy" customClass="MessageAccessoryView" customModule="Ring" customModuleProvider="target"> + <rect key="frame" x="0.0" y="0.0" width="315" height="38"/> + <autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/> + <subviews> + <textField opaque="NO" clipsSubviews="YES" contentMode="scaleToFill" contentHorizontalAlignment="left" contentVerticalAlignment="center" borderStyle="roundedRect" placeholder="Write a message..." textAlignment="natural" minimumFontSize="17" translatesAutoresizingMaskIntoConstraints="NO" id="AJA-0c-Rp7"> + <rect key="frame" x="4" y="4" width="307" height="30"/> + <nil key="textColor"/> + <fontDescription key="fontDescription" type="system" pointSize="14"/> + <textInputTraits key="textInputTraits" returnKeyType="send" enablesReturnKeyAutomatically="YES"/> + </textField> + </subviews> + <color key="backgroundColor" white="0.66666666666666663" alpha="1" colorSpace="calibratedWhite"/> + <constraints> + <constraint firstAttribute="bottom" secondItem="AJA-0c-Rp7" secondAttribute="bottom" constant="4" id="Gph-b4-ZJH"/> + <constraint firstAttribute="trailing" secondItem="AJA-0c-Rp7" secondAttribute="trailing" constant="4" id="UyK-3a-JcH"/> + <constraint firstItem="AJA-0c-Rp7" firstAttribute="top" secondItem="Fja-dy-lIy" secondAttribute="top" constant="4" id="mDu-cF-UdO"/> + <constraint firstItem="AJA-0c-Rp7" firstAttribute="leading" secondItem="Fja-dy-lIy" secondAttribute="leading" constant="4" id="tvd-4f-jdO"/> + </constraints> + <nil key="simulatedStatusBarMetrics"/> + <freeformSimulatedSizeMetrics key="simulatedDestinationMetrics"/> + <connections> + <outlet property="messageTextField" destination="AJA-0c-Rp7" id="ga9-yi-G2h"/> + </connections> + <point key="canvasLocation" x="-15" y="-178"/> + </view> + </objects> +</document> diff --git a/Ring/Ring/MessageCell.swift b/Ring/Ring/MessageCell.swift new file mode 100644 index 0000000000000000000000000000000000000000..7cc80f6543051f5373d7c50ce8d54f671fd3edaf --- /dev/null +++ b/Ring/Ring/MessageCell.swift @@ -0,0 +1,69 @@ +/* + * Copyright (C) 2017 Savoir-faire Linux Inc. + * + * Author: Silbino Gonçalves Matado <silbino.gmatado@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 UIKit + +enum BubblePosition { + case received + case sent +} + +class MessageCell: UITableViewCell { + + @IBOutlet weak var bubble: UIView! + @IBOutlet weak var messageLabel: UILabel! + + @IBOutlet weak var minimumLeadingConstraint: NSLayoutConstraint! + @IBOutlet weak var containerLeadingConstraint: NSLayoutConstraint! + + @IBOutlet weak var minimumTrailingConstraint: NSLayoutConstraint! + @IBOutlet weak var containerTrailingConstraint: NSLayoutConstraint! + + var bubblePosition = BubblePosition.received { + didSet { + if bubblePosition == .sent { + self.minimumTrailingConstraint.priority = 1 + self.containerTrailingConstraint.priority = 999 + self.containerLeadingConstraint.priority = 1 + self.minimumLeadingConstraint.priority = 999 + + self.bubble.backgroundColor = Colors.ringMainColor + self.messageLabel.textColor = UIColor.white + } else { + self.minimumLeadingConstraint.priority = 1 + self.containerLeadingConstraint.priority = 999 + self.containerTrailingConstraint.priority = 1 + self.minimumTrailingConstraint.priority = 999 + + self.bubble.backgroundColor = UIColor.lightGray + self.messageLabel.textColor = UIColor.black + } + } + } + + override func awakeFromNib() { + super.awakeFromNib() + self.bubblePosition = .received + } + + override func setSelected(_ selected: Bool, animated: Bool) { + super.setSelected(selected, animated: animated) + } +} diff --git a/Ring/Ring/MessageCell.xib b/Ring/Ring/MessageCell.xib new file mode 100644 index 0000000000000000000000000000000000000000..0bf54454032c3abd681c537bd54cb2fc3ec331d0 --- /dev/null +++ b/Ring/Ring/MessageCell.xib @@ -0,0 +1,66 @@ +<?xml version="1.0" encoding="UTF-8" standalone="no"?> +<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="11762" systemVersion="15G31" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" colorMatched="YES"> + <device id="retina4_7" orientation="portrait"> + <adaptation id="fullscreen"/> + </device> + <dependencies> + <deployment identifier="iOS"/> + <plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="11757"/> + <capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/> + </dependencies> + <objects> + <placeholder placeholderIdentifier="IBFilesOwner" id="-1" userLabel="File's Owner"/> + <placeholder placeholderIdentifier="IBFirstResponder" id="-2" customClass="UIResponder"/> + <tableViewCell contentMode="scaleToFill" selectionStyle="none" indentationWidth="10" reuseIdentifier="MessageCellId" rowHeight="60" id="KGk-i7-Jjw" customClass="MessageCell" customModule="Ring" customModuleProvider="target"> + <rect key="frame" x="0.0" y="0.0" width="510" height="47"/> + <autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/> + <tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" tableViewCell="KGk-i7-Jjw" id="H2p-sc-9uM"> + <rect key="frame" x="0.0" y="0.0" width="510" height="46"/> + <autoresizingMask key="autoresizingMask"/> + <subviews> + <view clipsSubviews="YES" contentMode="scaleToFill" verticalHuggingPriority="251" translatesAutoresizingMaskIntoConstraints="NO" id="kZJ-Ay-LTR"> + <rect key="frame" x="16" y="8" width="152.5" height="30.5"/> + <subviews> + <label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Label Label Label Label " lineBreakMode="tailTruncation" numberOfLines="0" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="lyR-7c-S2k"> + <rect key="frame" x="8" y="4" width="136.5" height="22.5"/> + <fontDescription key="fontDescription" type="system" pointSize="12"/> + <nil key="textColor"/> + <nil key="highlightedColor"/> + </label> + </subviews> + <color key="backgroundColor" red="0.66666666666666663" green="0.66666666666666663" blue="0.66666666666666663" alpha="1" colorSpace="calibratedRGB"/> + <constraints> + <constraint firstAttribute="height" relation="greaterThanOrEqual" constant="30" id="1Kj-UZ-gu7"/> + <constraint firstItem="lyR-7c-S2k" firstAttribute="leading" secondItem="kZJ-Ay-LTR" secondAttribute="leading" constant="8" id="8m5-sR-xnh"/> + <constraint firstAttribute="bottom" secondItem="lyR-7c-S2k" secondAttribute="bottom" constant="4" id="gwN-uX-PWd"/> + <constraint firstAttribute="trailing" secondItem="lyR-7c-S2k" secondAttribute="trailing" constant="8" id="uzV-kG-oGN"/> + <constraint firstItem="lyR-7c-S2k" firstAttribute="top" secondItem="kZJ-Ay-LTR" secondAttribute="top" constant="4" id="ycc-WI-Jk6"/> + </constraints> + <userDefinedRuntimeAttributes> + <userDefinedRuntimeAttribute type="number" keyPath="layer.cornerRadius"> + <integer key="value" value="5"/> + </userDefinedRuntimeAttribute> + </userDefinedRuntimeAttributes> + </view> + </subviews> + <constraints> + <constraint firstAttribute="bottom" secondItem="kZJ-Ay-LTR" secondAttribute="bottom" constant="8" id="1QQ-bu-6Bl"/> + <constraint firstAttribute="trailing" secondItem="kZJ-Ay-LTR" secondAttribute="trailing" priority="1" constant="16" id="99Y-bR-Ioq"/> + <constraint firstItem="kZJ-Ay-LTR" firstAttribute="leading" relation="greaterThanOrEqual" secondItem="H2p-sc-9uM" secondAttribute="leading" priority="1" constant="64" id="Eso-cy-OYs"/> + <constraint firstAttribute="trailing" relation="greaterThanOrEqual" secondItem="kZJ-Ay-LTR" secondAttribute="trailing" constant="64" id="TCY-7X-mFs"/> + <constraint firstItem="kZJ-Ay-LTR" firstAttribute="top" secondItem="H2p-sc-9uM" secondAttribute="top" constant="8" id="jhd-A8-c1o"/> + <constraint firstItem="kZJ-Ay-LTR" firstAttribute="leading" secondItem="H2p-sc-9uM" secondAttribute="leading" constant="16" id="nWe-5k-Qpn"/> + </constraints> + </tableViewCellContentView> + <connections> + <outlet property="bubble" destination="kZJ-Ay-LTR" id="hdG-fG-L69"/> + <outlet property="containerLeadingConstraint" destination="nWe-5k-Qpn" id="Ahu-gW-4IY"/> + <outlet property="containerTrailingConstraint" destination="99Y-bR-Ioq" id="JJr-pE-UfA"/> + <outlet property="messageLabel" destination="lyR-7c-S2k" id="hd3-pz-Pwh"/> + <outlet property="minimumLeadingConstraint" destination="Eso-cy-OYs" id="jhV-mE-FMA"/> + <outlet property="minimumTrailingConstraint" destination="TCY-7X-mFs" id="xj3-4A-I9G"/> + </connections> + <point key="canvasLocation" x="-411" y="-132"/> + </tableViewCell> + </objects> +</document> diff --git a/Ring/Ring/MessageViewModel.swift b/Ring/Ring/MessageViewModel.swift new file mode 100644 index 0000000000000000000000000000000000000000..ea74b8715f7b91fa456fc21e9716462e045788de --- /dev/null +++ b/Ring/Ring/MessageViewModel.swift @@ -0,0 +1,46 @@ +/* + * Copyright (C) 2017 Savoir-faire Linux Inc. + * + * Author: Silbino Gonçalves Matado <silbino.gmatado@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 RxSwift + +class MessageViewModel { + + fileprivate let accountService = AppDelegate.accountService + fileprivate var message :MessageModel + + init(withMessage message: MessageModel) { + self.message = message + } + + var content: String { + return self.message.content + } + + func bubblePosition() -> BubblePosition { + + let accountHelper = AccountModelHelper(withAccount: accountService.currentAccount!) + + if self.message.author == accountHelper.ringId! { + return .sent + } else { + return .received + } + } +} diff --git a/Ring/Ring/SmartlistViewController.swift b/Ring/Ring/SmartlistViewController.swift index 0768bdf025826de9cd6042ab24399f63889b5c33..821409b9bd5ebeafd94e9687204005a62c187f58 100644 --- a/Ring/Ring/SmartlistViewController.swift +++ b/Ring/Ring/SmartlistViewController.swift @@ -30,6 +30,8 @@ class SmartlistViewController: UIViewController, UITableViewDelegate { fileprivate let disposeBag = DisposeBag() fileprivate let SmartlistRowHeight :CGFloat = 64.0 + var selectedItem: ConversationViewModel? + override func viewDidLoad() { super.viewDidLoad() self.setupUI() @@ -61,16 +63,25 @@ class SmartlistViewController: UIViewController, UITableViewDelegate { cell.lastMessagePreviewLabel.text = viewModel.lastMessage }.addDisposableTo(disposeBag) + //Deselect the row self.tableView.rx.itemSelected.asObservable().subscribe(onNext: { indexPath in self.tableView.deselectRow(at: indexPath, animated: true) }).addDisposableTo(disposeBag) + + //Show the Messages screens and pass the viewModel + self.tableView.rx.modelSelected(ConversationViewModel.self).subscribe(onNext: { item in + self.selectedItem = item + self.performSegue(withIdentifier: "ShowMessages", sender: nil) + }).addDisposableTo(disposeBag) } // MARK: - Navigation // In a storyboard-based application, you will often want to do a little preparation before navigation override func prepare(for segue: UIStoryboardSegue, sender: Any?) { - + if let msgVC = segue.destination as? ConversationViewController { + msgVC.viewModel = self.selectedItem + } } }