diff --git a/Blink/Blink.xcodeproj/project.pbxproj b/Blink/Blink.xcodeproj/project.pbxproj new file mode 100644 index 0000000..4ffeda8 --- /dev/null +++ b/Blink/Blink.xcodeproj/project.pbxproj @@ -0,0 +1,1062 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 50; + objects = { + +/* Begin PBXBuildFile section */ + 2294603524BE4FC300BBEE2C /* Idea.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2294603424BE4FC300BBEE2C /* Idea.swift */; }; + 2294603724BF750100BBEE2C /* OSLog+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2294603624BF750100BBEE2C /* OSLog+Extensions.swift */; }; + 2294603924BF802100BBEE2C /* RankingViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2294603824BF802100BBEE2C /* RankingViewModel.swift */; }; + 4F16F00324D1F041008123F5 /* GridViewTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4F16F00224D1F041008123F5 /* GridViewTests.swift */; }; + 4F5F12D224B639FA00A7D9E7 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4F5F12D124B639FA00A7D9E7 /* AppDelegate.swift */; }; + 4F5F12D424B639FA00A7D9E7 /* SceneDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4F5F12D324B639FA00A7D9E7 /* SceneDelegate.swift */; }; + 4F5F12D824B639FC00A7D9E7 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 4F5F12D724B639FC00A7D9E7 /* Assets.xcassets */; }; + 4F5F12DB24B639FC00A7D9E7 /* Preview Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 4F5F12DA24B639FC00A7D9E7 /* Preview Assets.xcassets */; }; + 4F5F12DE24B639FC00A7D9E7 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 4F5F12DC24B639FC00A7D9E7 /* LaunchScreen.storyboard */; }; + 4F5F12E924B639FD00A7D9E7 /* Blink_iOSTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4F5F12E824B639FD00A7D9E7 /* Blink_iOSTests.swift */; }; + 4F5F12F224B63A5600A7D9E7 /* MenuViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4F5F12F124B63A5600A7D9E7 /* MenuViewModel.swift */; }; + 4F5F12FD24B63E8200A7D9E7 /* Multipeer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4F5F12FC24B63E8200A7D9E7 /* Multipeer.swift */; }; + 4F5F130024B65AC200A7D9E7 /* BrainstormingViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4F5F12FF24B65AC200A7D9E7 /* BrainstormingViewModel.swift */; }; + 4F5F130224B65D8E00A7D9E7 /* VotingViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4F5F130124B65D8E00A7D9E7 /* VotingViewModel.swift */; }; + 4F5F130D24B6612E00A7D9E7 /* Multipeer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4F5F130C24B6612E00A7D9E7 /* Multipeer.swift */; }; + 4F5F130F24B661D700A7D9E7 /* MenuViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4F5F130E24B661D700A7D9E7 /* MenuViewModel.swift */; }; + 4F5F131124B6659A00A7D9E7 /* BrainstormingViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4F5F131024B6659A00A7D9E7 /* BrainstormingViewModel.swift */; }; + 4F5F131324B78BCB00A7D9E7 /* VotingViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4F5F131224B78BCB00A7D9E7 /* VotingViewModel.swift */; }; + 4F5F131B24BCFFA400A7D9E7 /* Browser.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4F5F131A24BCFFA400A7D9E7 /* Browser.swift */; }; + A418231A24B39DDF0082962F /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = A418231924B39DDF0082962F /* AppDelegate.swift */; }; + A418231C24B39DDF0082962F /* MenuView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A418231B24B39DDF0082962F /* MenuView.swift */; }; + A418231E24B39DE10082962F /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = A418231D24B39DE10082962F /* Assets.xcassets */; }; + A418232124B39DE10082962F /* Preview Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = A418232024B39DE10082962F /* Preview Assets.xcassets */; }; + A418232424B39DE10082962F /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = A418232224B39DE10082962F /* LaunchScreen.storyboard */; }; + A418232F24B39DE10082962F /* BlinkTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = A418232E24B39DE10082962F /* BlinkTests.swift */; }; + A419E11424BCD0D1008BE53D /* GridViewVotable.swift in Sources */ = {isa = PBXBuildFile; fileRef = A419E11324BCD0D1008BE53D /* GridViewVotable.swift */; }; + A419E11B24BCF0B3008BE53D /* RankingViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = A419E11A24BCF0B3008BE53D /* RankingViewModel.swift */; }; + A43323D224B4F3620027B909 /* BrainstormingView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A43323D124B4F3620027B909 /* BrainstormingView.swift */; }; + A45A231A24D3418600D57B09 /* RankingRowView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A45A231924D3418600D57B09 /* RankingRowView.swift */; }; + A46FFD7B24C0C1F900650A2B /* Idea.swift in Sources */ = {isa = PBXBuildFile; fileRef = A46FFD7A24C0C1F900650A2B /* Idea.swift */; }; + A46FFD7D24C0C21F00650A2B /* OSLog+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = A46FFD7C24C0C21F00650A2B /* OSLog+Extensions.swift */; }; + A4706C3924BE1EB4004B00AE /* MenuView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A4706C3824BE1EB4004B00AE /* MenuView.swift */; }; + A4706C3E24BE21CE004B00AE /* BrainstormingView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A4706C3D24BE21CE004B00AE /* BrainstormingView.swift */; }; + A4706C4724BE39C5004B00AE /* VotingView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A4706C4624BE39C5004B00AE /* VotingView.swift */; }; + A4706C4C24BE3B60004B00AE /* RankingView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A4706C4B24BE3B60004B00AE /* RankingView.swift */; }; + A4CC2B2A24B6335B00C1B165 /* GridView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A4CC2B2924B6335B00C1B165 /* GridView.swift */; }; + A4CC2B2C24B635CC00C1B165 /* VotingView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A4CC2B2B24B635CC00C1B165 /* VotingView.swift */; }; + A4CC2B2E24B63AD300C1B165 /* RankingView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A4CC2B2D24B63AD300C1B165 /* RankingView.swift */; }; +/* End PBXBuildFile section */ + +/* Begin PBXContainerItemProxy section */ + 4F5F12E524B639FD00A7D9E7 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = A418230E24B39DDF0082962F /* Project object */; + proxyType = 1; + remoteGlobalIDString = 4F5F12CE24B639FA00A7D9E7; + remoteInfo = Blink_iOS; + }; + A418232B24B39DE10082962F /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = A418230E24B39DDF0082962F /* Project object */; + proxyType = 1; + remoteGlobalIDString = A418231524B39DDF0082962F; + remoteInfo = Blink; + }; +/* End PBXContainerItemProxy section */ + +/* Begin PBXFileReference section */ + 2294603424BE4FC300BBEE2C /* Idea.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Idea.swift; sourceTree = ""; }; + 2294603624BF750100BBEE2C /* OSLog+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "OSLog+Extensions.swift"; sourceTree = ""; }; + 2294603824BF802100BBEE2C /* RankingViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RankingViewModel.swift; sourceTree = ""; }; + 4F16F00224D1F041008123F5 /* GridViewTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GridViewTests.swift; sourceTree = ""; }; + 4F5F12CF24B639FA00A7D9E7 /* Blink.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Blink.app; sourceTree = BUILT_PRODUCTS_DIR; }; + 4F5F12D124B639FA00A7D9E7 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; + 4F5F12D324B639FA00A7D9E7 /* SceneDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SceneDelegate.swift; sourceTree = ""; }; + 4F5F12D724B639FC00A7D9E7 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; + 4F5F12DA24B639FC00A7D9E7 /* Preview Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = "Preview Assets.xcassets"; sourceTree = ""; }; + 4F5F12DD24B639FC00A7D9E7 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; + 4F5F12DF24B639FC00A7D9E7 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + 4F5F12E424B639FD00A7D9E7 /* Blink_iOSTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = Blink_iOSTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; + 4F5F12E824B639FD00A7D9E7 /* Blink_iOSTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Blink_iOSTests.swift; sourceTree = ""; }; + 4F5F12EA24B639FD00A7D9E7 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + 4F5F12F124B63A5600A7D9E7 /* MenuViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MenuViewModel.swift; sourceTree = ""; }; + 4F5F12FC24B63E8200A7D9E7 /* Multipeer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Multipeer.swift; sourceTree = ""; }; + 4F5F12FF24B65AC200A7D9E7 /* BrainstormingViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BrainstormingViewModel.swift; sourceTree = ""; }; + 4F5F130124B65D8E00A7D9E7 /* VotingViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VotingViewModel.swift; sourceTree = ""; }; + 4F5F130C24B6612E00A7D9E7 /* Multipeer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Multipeer.swift; sourceTree = ""; }; + 4F5F130E24B661D700A7D9E7 /* MenuViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MenuViewModel.swift; sourceTree = ""; }; + 4F5F131024B6659A00A7D9E7 /* BrainstormingViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BrainstormingViewModel.swift; sourceTree = ""; }; + 4F5F131224B78BCB00A7D9E7 /* VotingViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VotingViewModel.swift; sourceTree = ""; }; + 4F5F131A24BCFFA400A7D9E7 /* Browser.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Browser.swift; sourceTree = ""; }; + A418231624B39DDF0082962F /* Blink.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Blink.app; sourceTree = BUILT_PRODUCTS_DIR; }; + A418231924B39DDF0082962F /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; + A418231B24B39DDF0082962F /* MenuView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MenuView.swift; sourceTree = ""; }; + A418231D24B39DE10082962F /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; + A418232024B39DE10082962F /* Preview Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = "Preview Assets.xcassets"; sourceTree = ""; }; + A418232324B39DE10082962F /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; + A418232524B39DE10082962F /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + A418232A24B39DE10082962F /* BlinkTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = BlinkTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; + A418232E24B39DE10082962F /* BlinkTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BlinkTests.swift; sourceTree = ""; }; + A418233024B39DE10082962F /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + A419E11324BCD0D1008BE53D /* GridViewVotable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GridViewVotable.swift; sourceTree = ""; }; + A419E11A24BCF0B3008BE53D /* RankingViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RankingViewModel.swift; sourceTree = ""; }; + A43323D124B4F3620027B909 /* BrainstormingView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BrainstormingView.swift; sourceTree = ""; }; + A45A231924D3418600D57B09 /* RankingRowView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RankingRowView.swift; sourceTree = ""; }; + A46FFD7A24C0C1F900650A2B /* Idea.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Idea.swift; sourceTree = ""; }; + A46FFD7C24C0C21F00650A2B /* OSLog+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "OSLog+Extensions.swift"; sourceTree = ""; }; + A4706C3824BE1EB4004B00AE /* MenuView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = MenuView.swift; path = Blink/Ranking/ViewModels/MenuView.swift; sourceTree = SOURCE_ROOT; }; + A4706C3D24BE21CE004B00AE /* BrainstormingView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BrainstormingView.swift; sourceTree = ""; }; + A4706C4624BE39C5004B00AE /* VotingView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VotingView.swift; sourceTree = ""; }; + A4706C4B24BE3B60004B00AE /* RankingView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RankingView.swift; sourceTree = ""; }; + A4CC2B2924B6335B00C1B165 /* GridView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GridView.swift; sourceTree = ""; }; + A4CC2B2B24B635CC00C1B165 /* VotingView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VotingView.swift; sourceTree = ""; }; + A4CC2B2D24B63AD300C1B165 /* RankingView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RankingView.swift; sourceTree = ""; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + 4F5F12CC24B639FA00A7D9E7 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 4F5F12E124B639FD00A7D9E7 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + A418231324B39DDF0082962F /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + A418232724B39DE10082962F /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 4F5F12D024B639FA00A7D9E7 /* Blink_iOS */ = { + isa = PBXGroup; + children = ( + 4F5F130524B660CD00A7D9E7 /* Menu */, + 4F5F130424B660BF00A7D9E7 /* Brainstorming */, + 4F5F130324B660B500A7D9E7 /* Voting */, + A4706C4824BE3B42004B00AE /* Ranking */, + 4F5F130C24B6612E00A7D9E7 /* Multipeer.swift */, + 4F5F12D124B639FA00A7D9E7 /* AppDelegate.swift */, + 4F5F12D324B639FA00A7D9E7 /* SceneDelegate.swift */, + 4F5F12D724B639FC00A7D9E7 /* Assets.xcassets */, + 4F5F12DC24B639FC00A7D9E7 /* LaunchScreen.storyboard */, + 4F5F12DF24B639FC00A7D9E7 /* Info.plist */, + 4F5F12D924B639FC00A7D9E7 /* Preview Content */, + 2294603424BE4FC300BBEE2C /* Idea.swift */, + 2294603624BF750100BBEE2C /* OSLog+Extensions.swift */, + ); + path = Blink_iOS; + sourceTree = ""; + }; + 4F5F12D924B639FC00A7D9E7 /* Preview Content */ = { + isa = PBXGroup; + children = ( + 4F5F12DA24B639FC00A7D9E7 /* Preview Assets.xcassets */, + ); + path = "Preview Content"; + sourceTree = ""; + }; + 4F5F12E724B639FD00A7D9E7 /* Blink_iOSTests */ = { + isa = PBXGroup; + children = ( + 4F5F12E824B639FD00A7D9E7 /* Blink_iOSTests.swift */, + 4F5F12EA24B639FD00A7D9E7 /* Info.plist */, + ); + path = Blink_iOSTests; + sourceTree = ""; + }; + 4F5F12F324B63D9300A7D9E7 /* Menu */ = { + isa = PBXGroup; + children = ( + 4F5F12F424B63DBD00A7D9E7 /* Views */, + 4F5F12F524B63DCA00A7D9E7 /* ViewModels */, + ); + path = Menu; + sourceTree = ""; + }; + 4F5F12F424B63DBD00A7D9E7 /* Views */ = { + isa = PBXGroup; + children = ( + A418231B24B39DDF0082962F /* MenuView.swift */, + ); + path = Views; + sourceTree = ""; + }; + 4F5F12F524B63DCA00A7D9E7 /* ViewModels */ = { + isa = PBXGroup; + children = ( + 4F5F12F124B63A5600A7D9E7 /* MenuViewModel.swift */, + ); + path = ViewModels; + sourceTree = ""; + }; + 4F5F12F624B63DED00A7D9E7 /* Brainstorming */ = { + isa = PBXGroup; + children = ( + 4F5F12F724B63DF700A7D9E7 /* Views */, + 4F5F12F824B63E0000A7D9E7 /* ViewModels */, + ); + path = Brainstorming; + sourceTree = ""; + }; + 4F5F12F724B63DF700A7D9E7 /* Views */ = { + isa = PBXGroup; + children = ( + A43323D124B4F3620027B909 /* BrainstormingView.swift */, + ); + path = Views; + sourceTree = ""; + }; + 4F5F12F824B63E0000A7D9E7 /* ViewModels */ = { + isa = PBXGroup; + children = ( + 4F5F12FF24B65AC200A7D9E7 /* BrainstormingViewModel.swift */, + ); + path = ViewModels; + sourceTree = ""; + }; + 4F5F12F924B63E1400A7D9E7 /* Voting */ = { + isa = PBXGroup; + children = ( + 4F5F12FA24B63E2200A7D9E7 /* Views */, + 4F5F12FB24B63E2E00A7D9E7 /* ViewModel */, + ); + path = Voting; + sourceTree = ""; + }; + 4F5F12FA24B63E2200A7D9E7 /* Views */ = { + isa = PBXGroup; + children = ( + A4CC2B2B24B635CC00C1B165 /* VotingView.swift */, + ); + path = Views; + sourceTree = ""; + }; + 4F5F12FB24B63E2E00A7D9E7 /* ViewModel */ = { + isa = PBXGroup; + children = ( + 4F5F130124B65D8E00A7D9E7 /* VotingViewModel.swift */, + ); + path = ViewModel; + sourceTree = ""; + }; + 4F5F130324B660B500A7D9E7 /* Voting */ = { + isa = PBXGroup; + children = ( + A4706C4524BE39B6004B00AE /* Views */, + 4F5F130A24B6610F00A7D9E7 /* ViewModels */, + ); + path = Voting; + sourceTree = ""; + }; + 4F5F130424B660BF00A7D9E7 /* Brainstorming */ = { + isa = PBXGroup; + children = ( + A4706C3C24BE21C0004B00AE /* Views */, + 4F5F130824B660FF00A7D9E7 /* ViewModels */, + ); + path = Brainstorming; + sourceTree = ""; + }; + 4F5F130524B660CD00A7D9E7 /* Menu */ = { + isa = PBXGroup; + children = ( + 4F5F130724B660F600A7D9E7 /* View */, + 4F5F130624B660ED00A7D9E7 /* ViewModels */, + ); + path = Menu; + sourceTree = ""; + }; + 4F5F130624B660ED00A7D9E7 /* ViewModels */ = { + isa = PBXGroup; + children = ( + 4F5F130E24B661D700A7D9E7 /* MenuViewModel.swift */, + ); + path = ViewModels; + sourceTree = ""; + }; + 4F5F130724B660F600A7D9E7 /* View */ = { + isa = PBXGroup; + children = ( + A4706C3824BE1EB4004B00AE /* MenuView.swift */, + 4F5F131A24BCFFA400A7D9E7 /* Browser.swift */, + ); + path = View; + sourceTree = ""; + }; + 4F5F130824B660FF00A7D9E7 /* ViewModels */ = { + isa = PBXGroup; + children = ( + 4F5F131024B6659A00A7D9E7 /* BrainstormingViewModel.swift */, + ); + path = ViewModels; + sourceTree = ""; + }; + 4F5F130A24B6610F00A7D9E7 /* ViewModels */ = { + isa = PBXGroup; + children = ( + 4F5F131224B78BCB00A7D9E7 /* VotingViewModel.swift */, + ); + path = ViewModels; + sourceTree = ""; + }; + A418230D24B39DDF0082962F = { + isa = PBXGroup; + children = ( + A418231824B39DDF0082962F /* Blink */, + A418232D24B39DE10082962F /* BlinkTests */, + 4F5F12D024B639FA00A7D9E7 /* Blink_iOS */, + 4F5F12E724B639FD00A7D9E7 /* Blink_iOSTests */, + A418231724B39DDF0082962F /* Products */, + ); + sourceTree = ""; + }; + A418231724B39DDF0082962F /* Products */ = { + isa = PBXGroup; + children = ( + A418231624B39DDF0082962F /* Blink.app */, + A418232A24B39DE10082962F /* BlinkTests.xctest */, + 4F5F12CF24B639FA00A7D9E7 /* Blink.app */, + 4F5F12E424B639FD00A7D9E7 /* Blink_iOSTests.xctest */, + ); + name = Products; + sourceTree = ""; + }; + A418231824B39DDF0082962F /* Blink */ = { + isa = PBXGroup; + children = ( + 4F5F12F324B63D9300A7D9E7 /* Menu */, + 4F5F12F624B63DED00A7D9E7 /* Brainstorming */, + 4F5F12F924B63E1400A7D9E7 /* Voting */, + A419E11724BCF008008BE53D /* Ranking */, + 4F5F12FC24B63E8200A7D9E7 /* Multipeer.swift */, + A4CC2B2F24B6491700C1B165 /* Grid */, + A418231924B39DDF0082962F /* AppDelegate.swift */, + A418231D24B39DE10082962F /* Assets.xcassets */, + A418232224B39DE10082962F /* LaunchScreen.storyboard */, + A418232524B39DE10082962F /* Info.plist */, + A418231F24B39DE10082962F /* Preview Content */, + A46FFD7A24C0C1F900650A2B /* Idea.swift */, + A46FFD7C24C0C21F00650A2B /* OSLog+Extensions.swift */, + ); + path = Blink; + sourceTree = ""; + }; + A418231F24B39DE10082962F /* Preview Content */ = { + isa = PBXGroup; + children = ( + A418232024B39DE10082962F /* Preview Assets.xcassets */, + ); + path = "Preview Content"; + sourceTree = ""; + }; + A418232D24B39DE10082962F /* BlinkTests */ = { + isa = PBXGroup; + children = ( + A418232E24B39DE10082962F /* BlinkTests.swift */, + A418233024B39DE10082962F /* Info.plist */, + 4F16F00224D1F041008123F5 /* GridViewTests.swift */, + ); + path = BlinkTests; + sourceTree = ""; + }; + A419E11724BCF008008BE53D /* Ranking */ = { + isa = PBXGroup; + children = ( + A419E11924BCF017008BE53D /* ViewModels */, + A419E11824BCF012008BE53D /* Views */, + ); + path = Ranking; + sourceTree = ""; + }; + A419E11824BCF012008BE53D /* Views */ = { + isa = PBXGroup; + children = ( + A4CC2B2D24B63AD300C1B165 /* RankingView.swift */, + A45A231924D3418600D57B09 /* RankingRowView.swift */, + ); + path = Views; + sourceTree = ""; + }; + A419E11924BCF017008BE53D /* ViewModels */ = { + isa = PBXGroup; + children = ( + A419E11A24BCF0B3008BE53D /* RankingViewModel.swift */, + ); + path = ViewModels; + sourceTree = ""; + }; + A4706C3C24BE21C0004B00AE /* Views */ = { + isa = PBXGroup; + children = ( + A4706C3D24BE21CE004B00AE /* BrainstormingView.swift */, + ); + path = Views; + sourceTree = ""; + }; + A4706C4524BE39B6004B00AE /* Views */ = { + isa = PBXGroup; + children = ( + A4706C4624BE39C5004B00AE /* VotingView.swift */, + ); + path = Views; + sourceTree = ""; + }; + A4706C4824BE3B42004B00AE /* Ranking */ = { + isa = PBXGroup; + children = ( + A4706C4924BE3B49004B00AE /* Views */, + A4706C4A24BE3B4E004B00AE /* ViewModels */, + ); + path = Ranking; + sourceTree = ""; + }; + A4706C4924BE3B49004B00AE /* Views */ = { + isa = PBXGroup; + children = ( + A4706C4B24BE3B60004B00AE /* RankingView.swift */, + ); + path = Views; + sourceTree = ""; + }; + A4706C4A24BE3B4E004B00AE /* ViewModels */ = { + isa = PBXGroup; + children = ( + 2294603824BF802100BBEE2C /* RankingViewModel.swift */, + ); + path = ViewModels; + sourceTree = ""; + }; + A4CC2B2F24B6491700C1B165 /* Grid */ = { + isa = PBXGroup; + children = ( + A4CC2B2924B6335B00C1B165 /* GridView.swift */, + A419E11324BCD0D1008BE53D /* GridViewVotable.swift */, + ); + path = Grid; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + 4F5F12CE24B639FA00A7D9E7 /* Blink_iOS */ = { + isa = PBXNativeTarget; + buildConfigurationList = 4F5F12EF24B639FD00A7D9E7 /* Build configuration list for PBXNativeTarget "Blink_iOS" */; + buildPhases = ( + 4F5F12CB24B639FA00A7D9E7 /* Sources */, + 4F5F12CC24B639FA00A7D9E7 /* Frameworks */, + 4F5F12CD24B639FA00A7D9E7 /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = Blink_iOS; + productName = Blink_iOS; + productReference = 4F5F12CF24B639FA00A7D9E7 /* Blink.app */; + productType = "com.apple.product-type.application"; + }; + 4F5F12E324B639FD00A7D9E7 /* Blink_iOSTests */ = { + isa = PBXNativeTarget; + buildConfigurationList = 4F5F12F024B639FD00A7D9E7 /* Build configuration list for PBXNativeTarget "Blink_iOSTests" */; + buildPhases = ( + 4F5F12E024B639FD00A7D9E7 /* Sources */, + 4F5F12E124B639FD00A7D9E7 /* Frameworks */, + 4F5F12E224B639FD00A7D9E7 /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + 4F5F12E624B639FD00A7D9E7 /* PBXTargetDependency */, + ); + name = Blink_iOSTests; + productName = Blink_iOSTests; + productReference = 4F5F12E424B639FD00A7D9E7 /* Blink_iOSTests.xctest */; + productType = "com.apple.product-type.bundle.unit-test"; + }; + A418231524B39DDF0082962F /* Blink */ = { + isa = PBXNativeTarget; + buildConfigurationList = A418233324B39DE10082962F /* Build configuration list for PBXNativeTarget "Blink" */; + buildPhases = ( + A418231224B39DDF0082962F /* Sources */, + A418231324B39DDF0082962F /* Frameworks */, + A418231424B39DDF0082962F /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = Blink; + productName = Blink; + productReference = A418231624B39DDF0082962F /* Blink.app */; + productType = "com.apple.product-type.application"; + }; + A418232924B39DE10082962F /* BlinkTests */ = { + isa = PBXNativeTarget; + buildConfigurationList = A418233624B39DE10082962F /* Build configuration list for PBXNativeTarget "BlinkTests" */; + buildPhases = ( + A418232624B39DE10082962F /* Sources */, + A418232724B39DE10082962F /* Frameworks */, + A418232824B39DE10082962F /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + A418232C24B39DE10082962F /* PBXTargetDependency */, + ); + name = BlinkTests; + productName = BlinkTests; + productReference = A418232A24B39DE10082962F /* BlinkTests.xctest */; + productType = "com.apple.product-type.bundle.unit-test"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + A418230E24B39DDF0082962F /* Project object */ = { + isa = PBXProject; + attributes = { + LastSwiftUpdateCheck = 1140; + LastUpgradeCheck = 1150; + ORGANIZATIONNAME = "Artur Carneiro"; + TargetAttributes = { + 4F5F12CE24B639FA00A7D9E7 = { + CreatedOnToolsVersion = 11.4; + }; + 4F5F12E324B639FD00A7D9E7 = { + CreatedOnToolsVersion = 11.4; + TestTargetID = 4F5F12CE24B639FA00A7D9E7; + }; + A418231524B39DDF0082962F = { + CreatedOnToolsVersion = 11.5; + }; + A418232924B39DE10082962F = { + CreatedOnToolsVersion = 11.5; + TestTargetID = A418231524B39DDF0082962F; + }; + }; + }; + buildConfigurationList = A418231124B39DDF0082962F /* Build configuration list for PBXProject "Blink" */; + compatibilityVersion = "Xcode 9.3"; + developmentRegion = en; + hasScannedForEncodings = 0; + knownRegions = ( + en, + Base, + ); + mainGroup = A418230D24B39DDF0082962F; + productRefGroup = A418231724B39DDF0082962F /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + A418231524B39DDF0082962F /* Blink */, + A418232924B39DE10082962F /* BlinkTests */, + 4F5F12CE24B639FA00A7D9E7 /* Blink_iOS */, + 4F5F12E324B639FD00A7D9E7 /* Blink_iOSTests */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + 4F5F12CD24B639FA00A7D9E7 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 4F5F12DE24B639FC00A7D9E7 /* LaunchScreen.storyboard in Resources */, + 4F5F12DB24B639FC00A7D9E7 /* Preview Assets.xcassets in Resources */, + 4F5F12D824B639FC00A7D9E7 /* Assets.xcassets in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 4F5F12E224B639FD00A7D9E7 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + A418231424B39DDF0082962F /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + A418232424B39DE10082962F /* LaunchScreen.storyboard in Resources */, + A418232124B39DE10082962F /* Preview Assets.xcassets in Resources */, + A418231E24B39DE10082962F /* Assets.xcassets in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + A418232824B39DE10082962F /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + 4F5F12CB24B639FA00A7D9E7 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 2294603524BE4FC300BBEE2C /* Idea.swift in Sources */, + 2294603724BF750100BBEE2C /* OSLog+Extensions.swift in Sources */, + 4F5F131124B6659A00A7D9E7 /* BrainstormingViewModel.swift in Sources */, + A4706C4C24BE3B60004B00AE /* RankingView.swift in Sources */, + A4706C3924BE1EB4004B00AE /* MenuView.swift in Sources */, + A4706C3E24BE21CE004B00AE /* BrainstormingView.swift in Sources */, + 4F5F131B24BCFFA400A7D9E7 /* Browser.swift in Sources */, + 4F5F12D224B639FA00A7D9E7 /* AppDelegate.swift in Sources */, + 4F5F130D24B6612E00A7D9E7 /* Multipeer.swift in Sources */, + 2294603924BF802100BBEE2C /* RankingViewModel.swift in Sources */, + 4F5F12D424B639FA00A7D9E7 /* SceneDelegate.swift in Sources */, + 4F5F130F24B661D700A7D9E7 /* MenuViewModel.swift in Sources */, + A4706C4724BE39C5004B00AE /* VotingView.swift in Sources */, + 4F5F131324B78BCB00A7D9E7 /* VotingViewModel.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 4F5F12E024B639FD00A7D9E7 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 4F5F12E924B639FD00A7D9E7 /* Blink_iOSTests.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + A418231224B39DDF0082962F /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + A45A231A24D3418600D57B09 /* RankingRowView.swift in Sources */, + A46FFD7B24C0C1F900650A2B /* Idea.swift in Sources */, + 4F5F130024B65AC200A7D9E7 /* BrainstormingViewModel.swift in Sources */, + 4F5F130224B65D8E00A7D9E7 /* VotingViewModel.swift in Sources */, + A419E11424BCD0D1008BE53D /* GridViewVotable.swift in Sources */, + 4F5F12FD24B63E8200A7D9E7 /* Multipeer.swift in Sources */, + 4F5F12F224B63A5600A7D9E7 /* MenuViewModel.swift in Sources */, + A4CC2B2C24B635CC00C1B165 /* VotingView.swift in Sources */, + A4CC2B2E24B63AD300C1B165 /* RankingView.swift in Sources */, + A418231C24B39DDF0082962F /* MenuView.swift in Sources */, + A43323D224B4F3620027B909 /* BrainstormingView.swift in Sources */, + A46FFD7D24C0C21F00650A2B /* OSLog+Extensions.swift in Sources */, + A4CC2B2A24B6335B00C1B165 /* GridView.swift in Sources */, + A418231A24B39DDF0082962F /* AppDelegate.swift in Sources */, + A419E11B24BCF0B3008BE53D /* RankingViewModel.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + A418232624B39DE10082962F /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + A418232F24B39DE10082962F /* BlinkTests.swift in Sources */, + 4F16F00324D1F041008123F5 /* GridViewTests.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin PBXTargetDependency section */ + 4F5F12E624B639FD00A7D9E7 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 4F5F12CE24B639FA00A7D9E7 /* Blink_iOS */; + targetProxy = 4F5F12E524B639FD00A7D9E7 /* PBXContainerItemProxy */; + }; + A418232C24B39DE10082962F /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = A418231524B39DDF0082962F /* Blink */; + targetProxy = A418232B24B39DE10082962F /* PBXContainerItemProxy */; + }; +/* End PBXTargetDependency section */ + +/* Begin PBXVariantGroup section */ + 4F5F12DC24B639FC00A7D9E7 /* LaunchScreen.storyboard */ = { + isa = PBXVariantGroup; + children = ( + 4F5F12DD24B639FC00A7D9E7 /* Base */, + ); + name = LaunchScreen.storyboard; + sourceTree = ""; + }; + A418232224B39DE10082962F /* LaunchScreen.storyboard */ = { + isa = PBXVariantGroup; + children = ( + A418232324B39DE10082962F /* Base */, + ); + name = LaunchScreen.storyboard; + sourceTree = ""; + }; +/* End PBXVariantGroup section */ + +/* Begin XCBuildConfiguration section */ + 4F5F12EB24B639FD00A7D9E7 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 4; + DEVELOPMENT_ASSET_PATHS = "\"Blink_iOS/Preview Content\""; + DEVELOPMENT_TEAM = 93JG72P729; + ENABLE_PREVIEWS = YES; + INFOPLIST_FILE = Blink_iOS/Info.plist; + IPHONEOS_DEPLOYMENT_TARGET = 13.0; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + MARKETING_VERSION = 1.1; + PRODUCT_BUNDLE_IDENTIFIER = com.aev.blink; + PRODUCT_NAME = Blink; + SDKROOT = iphoneos; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = 1; + }; + name = Debug; + }; + 4F5F12EC24B639FD00A7D9E7 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 4; + DEVELOPMENT_ASSET_PATHS = "\"Blink_iOS/Preview Content\""; + DEVELOPMENT_TEAM = 93JG72P729; + ENABLE_PREVIEWS = YES; + INFOPLIST_FILE = Blink_iOS/Info.plist; + IPHONEOS_DEPLOYMENT_TARGET = 13.0; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + MARKETING_VERSION = 1.1; + PRODUCT_BUNDLE_IDENTIFIER = com.aev.blink; + PRODUCT_NAME = Blink; + SDKROOT = iphoneos; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = 1; + }; + name = Release; + }; + 4F5F12ED24B639FD00A7D9E7 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; + BUNDLE_LOADER = "$(TEST_HOST)"; + CODE_SIGN_STYLE = Automatic; + DEVELOPMENT_TEAM = 93JG72P729; + INFOPLIST_FILE = Blink_iOSTests/Info.plist; + IPHONEOS_DEPLOYMENT_TARGET = 13.4; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@loader_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = "com.edgarsgroi.Blink-iOSTests"; + PRODUCT_NAME = "$(TARGET_NAME)"; + SDKROOT = iphoneos; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Blink_iOS.app/Blink_iOS"; + }; + name = Debug; + }; + 4F5F12EE24B639FD00A7D9E7 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; + BUNDLE_LOADER = "$(TEST_HOST)"; + CODE_SIGN_STYLE = Automatic; + DEVELOPMENT_TEAM = 93JG72P729; + INFOPLIST_FILE = Blink_iOSTests/Info.plist; + IPHONEOS_DEPLOYMENT_TARGET = 13.4; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@loader_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = "com.edgarsgroi.Blink-iOSTests"; + PRODUCT_NAME = "$(TARGET_NAME)"; + SDKROOT = iphoneos; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Blink_iOS.app/Blink_iOS"; + }; + name = Release; + }; + A418233124B39DE10082962F /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = dwarf; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + GCC_C_LANGUAGE_STANDARD = gnu11; + GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; + MTL_FAST_MATH = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = appletvos; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + TVOS_DEPLOYMENT_TARGET = 13.4; + }; + name = Debug; + }; + A418233224B39DE10082962F /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_C_LANGUAGE_STANDARD = gnu11; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + MTL_ENABLE_DEBUG_INFO = NO; + MTL_FAST_MATH = YES; + SDKROOT = appletvos; + SWIFT_COMPILATION_MODE = wholemodule; + SWIFT_OPTIMIZATION_LEVEL = "-O"; + TVOS_DEPLOYMENT_TARGET = 13.4; + VALIDATE_PRODUCT = YES; + }; + name = Release; + }; + A418233424B39DE10082962F /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = "App Icon & Top Shelf Image"; + CODE_SIGN_IDENTITY = "Apple Development"; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 3; + DEVELOPMENT_ASSET_PATHS = "\"Blink/Preview Content\""; + DEVELOPMENT_TEAM = 93JG72P729; + ENABLE_PREVIEWS = YES; + INFOPLIST_FILE = Blink/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + MARKETING_VERSION = 1.1; + PRODUCT_BUNDLE_IDENTIFIER = com.aev.blink; + PRODUCT_NAME = "$(TARGET_NAME)"; + PROVISIONING_PROFILE_SPECIFIER = ""; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = 3; + TVOS_DEPLOYMENT_TARGET = 13.0; + }; + name = Debug; + }; + A418233524B39DE10082962F /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = "App Icon & Top Shelf Image"; + CODE_SIGN_IDENTITY = "Apple Development"; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 3; + DEVELOPMENT_ASSET_PATHS = "\"Blink/Preview Content\""; + DEVELOPMENT_TEAM = 93JG72P729; + ENABLE_PREVIEWS = YES; + INFOPLIST_FILE = Blink/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + MARKETING_VERSION = 1.1; + PRODUCT_BUNDLE_IDENTIFIER = com.aev.blink; + PRODUCT_NAME = "$(TARGET_NAME)"; + PROVISIONING_PROFILE_SPECIFIER = ""; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = 3; + TVOS_DEPLOYMENT_TARGET = 13.0; + }; + name = Release; + }; + A418233724B39DE10082962F /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; + BUNDLE_LOADER = "$(TEST_HOST)"; + CODE_SIGN_STYLE = Automatic; + DEVELOPMENT_TEAM = L76X5KFZD9; + INFOPLIST_FILE = BlinkTests/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@loader_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = com.csfar.BlinkTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = 3; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Blink.app/Blink"; + TVOS_DEPLOYMENT_TARGET = 13.4; + }; + name = Debug; + }; + A418233824B39DE10082962F /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; + BUNDLE_LOADER = "$(TEST_HOST)"; + CODE_SIGN_STYLE = Automatic; + DEVELOPMENT_TEAM = L76X5KFZD9; + INFOPLIST_FILE = BlinkTests/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@loader_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = com.csfar.BlinkTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = 3; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Blink.app/Blink"; + TVOS_DEPLOYMENT_TARGET = 13.4; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 4F5F12EF24B639FD00A7D9E7 /* Build configuration list for PBXNativeTarget "Blink_iOS" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 4F5F12EB24B639FD00A7D9E7 /* Debug */, + 4F5F12EC24B639FD00A7D9E7 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 4F5F12F024B639FD00A7D9E7 /* Build configuration list for PBXNativeTarget "Blink_iOSTests" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 4F5F12ED24B639FD00A7D9E7 /* Debug */, + 4F5F12EE24B639FD00A7D9E7 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + A418231124B39DDF0082962F /* Build configuration list for PBXProject "Blink" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + A418233124B39DE10082962F /* Debug */, + A418233224B39DE10082962F /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + A418233324B39DE10082962F /* Build configuration list for PBXNativeTarget "Blink" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + A418233424B39DE10082962F /* Debug */, + A418233524B39DE10082962F /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + A418233624B39DE10082962F /* Build configuration list for PBXNativeTarget "BlinkTests" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + A418233724B39DE10082962F /* Debug */, + A418233824B39DE10082962F /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + }; + rootObject = A418230E24B39DDF0082962F /* Project object */; +} diff --git a/Blink/Blink.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/Blink/Blink.xcodeproj/project.xcworkspace/contents.xcworkspacedata new file mode 100644 index 0000000..4ddf38c --- /dev/null +++ b/Blink/Blink.xcodeproj/project.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + diff --git a/Blink/Blink.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/Blink/Blink.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 0000000..18d9810 --- /dev/null +++ b/Blink/Blink.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/Blink/Blink/AppDelegate.swift b/Blink/Blink/AppDelegate.swift new file mode 100644 index 0000000..3a671ec --- /dev/null +++ b/Blink/Blink/AppDelegate.swift @@ -0,0 +1,56 @@ +// +// AppDelegate.swift +// Blink +// +// Created by Artur Carneiro on 06/07/20. +// Copyright © 2020 Artur Carneiro. All rights reserved. +// + +import UIKit +import SwiftUI + +@UIApplicationMain +class AppDelegate: UIResponder, UIApplicationDelegate { + + var window: UIWindow? + + + func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { + + // Create the SwiftUI view that provides the window contents. + let contentView = MenuView(viewmodel: MenuViewModel()) + + // Use a UIHostingController as window root view controller. + let window = UIWindow(frame: UIScreen.main.bounds) + window.rootViewController = UIHostingController(rootView: contentView) + self.window = window + window.makeKeyAndVisible() + return true + } + + func applicationWillResignActive(_ application: UIApplication) { + // Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state. + // Use this method to pause ongoing tasks, disable timers, and throttle down OpenGL ES frame rates. Games should use this method to pause the game. + } + + func applicationDidEnterBackground(_ application: UIApplication) { + // Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later. + } + + func applicationWillEnterForeground(_ application: UIApplication) { + // Called as part of the transition from the background to the active state; here you can undo many of the changes made on entering the background. + } + + func applicationDidBecomeActive(_ application: UIApplication) { + // Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface. + } + + +} + + +struct AppDelegate_Previews: PreviewProvider { + static var previews: some View { + /*@START_MENU_TOKEN@*/Text("Hello, World!")/*@END_MENU_TOKEN@*/ + } +} diff --git a/Blink/Blink/Assets.xcassets/Accent.colorset/Contents.json b/Blink/Blink/Assets.xcassets/Accent.colorset/Contents.json new file mode 100644 index 0000000..88db513 --- /dev/null +++ b/Blink/Blink/Assets.xcassets/Accent.colorset/Contents.json @@ -0,0 +1,23 @@ +{ + "colors" : [ + { + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0.408", + "green" : "0.408", + "red" : "1.000" + } + }, + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + }, + "properties" : { + "localizable" : true + } +} diff --git a/Blink/Blink/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - App Store.imagestack/Back.imagestacklayer/Content.imageset/Contents.json b/Blink/Blink/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - App Store.imagestack/Back.imagestacklayer/Content.imageset/Contents.json new file mode 100644 index 0000000..0fa1bfd --- /dev/null +++ b/Blink/Blink/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - App Store.imagestack/Back.imagestacklayer/Content.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "new-tvos-icon-back-large.png", + "idiom" : "tv" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Blink/Blink/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - App Store.imagestack/Back.imagestacklayer/Content.imageset/new-tvos-icon-back-large.png b/Blink/Blink/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - App Store.imagestack/Back.imagestacklayer/Content.imageset/new-tvos-icon-back-large.png new file mode 100644 index 0000000..b5eb1d4 Binary files /dev/null and b/Blink/Blink/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - App Store.imagestack/Back.imagestacklayer/Content.imageset/new-tvos-icon-back-large.png differ diff --git a/Blink/Blink/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - App Store.imagestack/Back.imagestacklayer/Contents.json b/Blink/Blink/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - App Store.imagestack/Back.imagestacklayer/Contents.json new file mode 100644 index 0000000..73c0059 --- /dev/null +++ b/Blink/Blink/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - App Store.imagestack/Back.imagestacklayer/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Blink/Blink/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - App Store.imagestack/Contents.json b/Blink/Blink/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - App Store.imagestack/Contents.json new file mode 100644 index 0000000..de59d88 --- /dev/null +++ b/Blink/Blink/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - App Store.imagestack/Contents.json @@ -0,0 +1,17 @@ +{ + "info" : { + "author" : "xcode", + "version" : 1 + }, + "layers" : [ + { + "filename" : "Front.imagestacklayer" + }, + { + "filename" : "Middle.imagestacklayer" + }, + { + "filename" : "Back.imagestacklayer" + } + ] +} diff --git a/Blink/Blink/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - App Store.imagestack/Front.imagestacklayer/Content.imageset/Contents.json b/Blink/Blink/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - App Store.imagestack/Front.imagestacklayer/Content.imageset/Contents.json new file mode 100644 index 0000000..2908c64 --- /dev/null +++ b/Blink/Blink/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - App Store.imagestack/Front.imagestacklayer/Content.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "new-tvos-icon-front-large.png", + "idiom" : "tv" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Blink/Blink/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - App Store.imagestack/Front.imagestacklayer/Content.imageset/new-tvos-icon-front-large.png b/Blink/Blink/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - App Store.imagestack/Front.imagestacklayer/Content.imageset/new-tvos-icon-front-large.png new file mode 100644 index 0000000..44e37e8 Binary files /dev/null and b/Blink/Blink/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - App Store.imagestack/Front.imagestacklayer/Content.imageset/new-tvos-icon-front-large.png differ diff --git a/Blink/Blink/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - App Store.imagestack/Front.imagestacklayer/Contents.json b/Blink/Blink/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - App Store.imagestack/Front.imagestacklayer/Contents.json new file mode 100644 index 0000000..73c0059 --- /dev/null +++ b/Blink/Blink/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - App Store.imagestack/Front.imagestacklayer/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Blink/Blink/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - App Store.imagestack/Middle.imagestacklayer/Content.imageset/Contents.json b/Blink/Blink/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - App Store.imagestack/Middle.imagestacklayer/Content.imageset/Contents.json new file mode 100644 index 0000000..6891eac --- /dev/null +++ b/Blink/Blink/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - App Store.imagestack/Middle.imagestacklayer/Content.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "new-tvos-icon-middle-large.png", + "idiom" : "tv" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Blink/Blink/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - App Store.imagestack/Middle.imagestacklayer/Content.imageset/new-tvos-icon-middle-large.png b/Blink/Blink/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - App Store.imagestack/Middle.imagestacklayer/Content.imageset/new-tvos-icon-middle-large.png new file mode 100644 index 0000000..6126357 Binary files /dev/null and b/Blink/Blink/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - App Store.imagestack/Middle.imagestacklayer/Content.imageset/new-tvos-icon-middle-large.png differ diff --git a/Blink/Blink/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - App Store.imagestack/Middle.imagestacklayer/Contents.json b/Blink/Blink/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - App Store.imagestack/Middle.imagestacklayer/Contents.json new file mode 100644 index 0000000..73c0059 --- /dev/null +++ b/Blink/Blink/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - App Store.imagestack/Middle.imagestacklayer/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Blink/Blink/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon.imagestack/Back.imagestacklayer/Content.imageset/Contents.json b/Blink/Blink/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon.imagestack/Back.imagestacklayer/Content.imageset/Contents.json new file mode 100644 index 0000000..5a7ec90 --- /dev/null +++ b/Blink/Blink/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon.imagestack/Back.imagestacklayer/Content.imageset/Contents.json @@ -0,0 +1,18 @@ +{ + "images" : [ + { + "filename" : "new-tvos-icon-back.png", + "idiom" : "tv", + "scale" : "1x" + }, + { + "filename" : "new-tvos-icon-back@2x.png", + "idiom" : "tv", + "scale" : "2x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Blink/Blink/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon.imagestack/Back.imagestacklayer/Content.imageset/new-tvos-icon-back.png b/Blink/Blink/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon.imagestack/Back.imagestacklayer/Content.imageset/new-tvos-icon-back.png new file mode 100644 index 0000000..881a487 Binary files /dev/null and b/Blink/Blink/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon.imagestack/Back.imagestacklayer/Content.imageset/new-tvos-icon-back.png differ diff --git a/Blink/Blink/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon.imagestack/Back.imagestacklayer/Content.imageset/new-tvos-icon-back@2x.png b/Blink/Blink/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon.imagestack/Back.imagestacklayer/Content.imageset/new-tvos-icon-back@2x.png new file mode 100644 index 0000000..c5c90f2 Binary files /dev/null and b/Blink/Blink/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon.imagestack/Back.imagestacklayer/Content.imageset/new-tvos-icon-back@2x.png differ diff --git a/Blink/Blink/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon.imagestack/Back.imagestacklayer/Contents.json b/Blink/Blink/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon.imagestack/Back.imagestacklayer/Contents.json new file mode 100644 index 0000000..73c0059 --- /dev/null +++ b/Blink/Blink/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon.imagestack/Back.imagestacklayer/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Blink/Blink/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon.imagestack/Contents.json b/Blink/Blink/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon.imagestack/Contents.json new file mode 100644 index 0000000..de59d88 --- /dev/null +++ b/Blink/Blink/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon.imagestack/Contents.json @@ -0,0 +1,17 @@ +{ + "info" : { + "author" : "xcode", + "version" : 1 + }, + "layers" : [ + { + "filename" : "Front.imagestacklayer" + }, + { + "filename" : "Middle.imagestacklayer" + }, + { + "filename" : "Back.imagestacklayer" + } + ] +} diff --git a/Blink/Blink/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon.imagestack/Front.imagestacklayer/Content.imageset/Contents.json b/Blink/Blink/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon.imagestack/Front.imagestacklayer/Content.imageset/Contents.json new file mode 100644 index 0000000..2333a66 --- /dev/null +++ b/Blink/Blink/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon.imagestack/Front.imagestacklayer/Content.imageset/Contents.json @@ -0,0 +1,18 @@ +{ + "images" : [ + { + "filename" : "new-tvos-icon-front.png", + "idiom" : "tv", + "scale" : "1x" + }, + { + "filename" : "new-tvos-icon-front@2x.png", + "idiom" : "tv", + "scale" : "2x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Blink/Blink/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon.imagestack/Front.imagestacklayer/Content.imageset/new-tvos-icon-front.png b/Blink/Blink/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon.imagestack/Front.imagestacklayer/Content.imageset/new-tvos-icon-front.png new file mode 100644 index 0000000..ca762c0 Binary files /dev/null and b/Blink/Blink/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon.imagestack/Front.imagestacklayer/Content.imageset/new-tvos-icon-front.png differ diff --git a/Blink/Blink/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon.imagestack/Front.imagestacklayer/Content.imageset/new-tvos-icon-front@2x.png b/Blink/Blink/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon.imagestack/Front.imagestacklayer/Content.imageset/new-tvos-icon-front@2x.png new file mode 100644 index 0000000..02efc26 Binary files /dev/null and b/Blink/Blink/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon.imagestack/Front.imagestacklayer/Content.imageset/new-tvos-icon-front@2x.png differ diff --git a/Blink/Blink/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon.imagestack/Front.imagestacklayer/Contents.json b/Blink/Blink/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon.imagestack/Front.imagestacklayer/Contents.json new file mode 100644 index 0000000..73c0059 --- /dev/null +++ b/Blink/Blink/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon.imagestack/Front.imagestacklayer/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Blink/Blink/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon.imagestack/Middle.imagestacklayer/Content.imageset/Contents.json b/Blink/Blink/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon.imagestack/Middle.imagestacklayer/Content.imageset/Contents.json new file mode 100644 index 0000000..58ce039 --- /dev/null +++ b/Blink/Blink/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon.imagestack/Middle.imagestacklayer/Content.imageset/Contents.json @@ -0,0 +1,18 @@ +{ + "images" : [ + { + "filename" : "new-tvos-icon-middle.png", + "idiom" : "tv", + "scale" : "1x" + }, + { + "filename" : "new-tvos-icon-middle@2x.png", + "idiom" : "tv", + "scale" : "2x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Blink/Blink/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon.imagestack/Middle.imagestacklayer/Content.imageset/new-tvos-icon-middle.png b/Blink/Blink/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon.imagestack/Middle.imagestacklayer/Content.imageset/new-tvos-icon-middle.png new file mode 100644 index 0000000..fa444a3 Binary files /dev/null and b/Blink/Blink/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon.imagestack/Middle.imagestacklayer/Content.imageset/new-tvos-icon-middle.png differ diff --git a/Blink/Blink/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon.imagestack/Middle.imagestacklayer/Content.imageset/new-tvos-icon-middle@2x.png b/Blink/Blink/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon.imagestack/Middle.imagestacklayer/Content.imageset/new-tvos-icon-middle@2x.png new file mode 100644 index 0000000..359649e Binary files /dev/null and b/Blink/Blink/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon.imagestack/Middle.imagestacklayer/Content.imageset/new-tvos-icon-middle@2x.png differ diff --git a/Blink/Blink/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon.imagestack/Middle.imagestacklayer/Contents.json b/Blink/Blink/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon.imagestack/Middle.imagestacklayer/Contents.json new file mode 100644 index 0000000..73c0059 --- /dev/null +++ b/Blink/Blink/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon.imagestack/Middle.imagestacklayer/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Blink/Blink/Assets.xcassets/App Icon & Top Shelf Image.brandassets/Contents.json b/Blink/Blink/Assets.xcassets/App Icon & Top Shelf Image.brandassets/Contents.json new file mode 100644 index 0000000..f47ba43 --- /dev/null +++ b/Blink/Blink/Assets.xcassets/App Icon & Top Shelf Image.brandassets/Contents.json @@ -0,0 +1,32 @@ +{ + "assets" : [ + { + "filename" : "App Icon - App Store.imagestack", + "idiom" : "tv", + "role" : "primary-app-icon", + "size" : "1280x768" + }, + { + "filename" : "App Icon.imagestack", + "idiom" : "tv", + "role" : "primary-app-icon", + "size" : "400x240" + }, + { + "filename" : "Top Shelf Image Wide.imageset", + "idiom" : "tv", + "role" : "top-shelf-image-wide", + "size" : "2320x720" + }, + { + "filename" : "Top Shelf Image.imageset", + "idiom" : "tv", + "role" : "top-shelf-image", + "size" : "1920x720" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Blink/Blink/Assets.xcassets/App Icon & Top Shelf Image.brandassets/Top Shelf Image Wide.imageset/Contents.json b/Blink/Blink/Assets.xcassets/App Icon & Top Shelf Image.brandassets/Top Shelf Image Wide.imageset/Contents.json new file mode 100644 index 0000000..b29679f --- /dev/null +++ b/Blink/Blink/Assets.xcassets/App Icon & Top Shelf Image.brandassets/Top Shelf Image Wide.imageset/Contents.json @@ -0,0 +1,28 @@ +{ + "images" : [ + { + "filename" : "top-shelf.png", + "idiom" : "tv", + "scale" : "1x" + }, + { + "filename" : "top-shelf@2x.png", + "idiom" : "tv", + "scale" : "2x" + }, + { + "filename" : "top-shelf-1.png", + "idiom" : "tv-marketing", + "scale" : "1x" + }, + { + "filename" : "top-shelf@2x-1.png", + "idiom" : "tv-marketing", + "scale" : "2x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Blink/Blink/Assets.xcassets/App Icon & Top Shelf Image.brandassets/Top Shelf Image Wide.imageset/top-shelf-1.png b/Blink/Blink/Assets.xcassets/App Icon & Top Shelf Image.brandassets/Top Shelf Image Wide.imageset/top-shelf-1.png new file mode 100644 index 0000000..dcd01d9 Binary files /dev/null and b/Blink/Blink/Assets.xcassets/App Icon & Top Shelf Image.brandassets/Top Shelf Image Wide.imageset/top-shelf-1.png differ diff --git a/Blink/Blink/Assets.xcassets/App Icon & Top Shelf Image.brandassets/Top Shelf Image Wide.imageset/top-shelf.png b/Blink/Blink/Assets.xcassets/App Icon & Top Shelf Image.brandassets/Top Shelf Image Wide.imageset/top-shelf.png new file mode 100644 index 0000000..dcd01d9 Binary files /dev/null and b/Blink/Blink/Assets.xcassets/App Icon & Top Shelf Image.brandassets/Top Shelf Image Wide.imageset/top-shelf.png differ diff --git a/Blink/Blink/Assets.xcassets/App Icon & Top Shelf Image.brandassets/Top Shelf Image Wide.imageset/top-shelf@2x-1.png b/Blink/Blink/Assets.xcassets/App Icon & Top Shelf Image.brandassets/Top Shelf Image Wide.imageset/top-shelf@2x-1.png new file mode 100644 index 0000000..2ea04d5 Binary files /dev/null and b/Blink/Blink/Assets.xcassets/App Icon & Top Shelf Image.brandassets/Top Shelf Image Wide.imageset/top-shelf@2x-1.png differ diff --git a/Blink/Blink/Assets.xcassets/App Icon & Top Shelf Image.brandassets/Top Shelf Image Wide.imageset/top-shelf@2x.png b/Blink/Blink/Assets.xcassets/App Icon & Top Shelf Image.brandassets/Top Shelf Image Wide.imageset/top-shelf@2x.png new file mode 100644 index 0000000..2ea04d5 Binary files /dev/null and b/Blink/Blink/Assets.xcassets/App Icon & Top Shelf Image.brandassets/Top Shelf Image Wide.imageset/top-shelf@2x.png differ diff --git a/Blink/Blink/Assets.xcassets/App Icon & Top Shelf Image.brandassets/Top Shelf Image.imageset/Contents.json b/Blink/Blink/Assets.xcassets/App Icon & Top Shelf Image.brandassets/Top Shelf Image.imageset/Contents.json new file mode 100644 index 0000000..5ece1c9 --- /dev/null +++ b/Blink/Blink/Assets.xcassets/App Icon & Top Shelf Image.brandassets/Top Shelf Image.imageset/Contents.json @@ -0,0 +1,28 @@ +{ + "images" : [ + { + "filename" : "top-shelf-image.png", + "idiom" : "tv", + "scale" : "1x" + }, + { + "filename" : "top-shelf-image@2x.png", + "idiom" : "tv", + "scale" : "2x" + }, + { + "filename" : "top-shelf-image-1.png", + "idiom" : "tv-marketing", + "scale" : "1x" + }, + { + "filename" : "top-shelf-image@2x-1.png", + "idiom" : "tv-marketing", + "scale" : "2x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Blink/Blink/Assets.xcassets/App Icon & Top Shelf Image.brandassets/Top Shelf Image.imageset/top-shelf-image-1.png b/Blink/Blink/Assets.xcassets/App Icon & Top Shelf Image.brandassets/Top Shelf Image.imageset/top-shelf-image-1.png new file mode 100644 index 0000000..29aa253 Binary files /dev/null and b/Blink/Blink/Assets.xcassets/App Icon & Top Shelf Image.brandassets/Top Shelf Image.imageset/top-shelf-image-1.png differ diff --git a/Blink/Blink/Assets.xcassets/App Icon & Top Shelf Image.brandassets/Top Shelf Image.imageset/top-shelf-image.png b/Blink/Blink/Assets.xcassets/App Icon & Top Shelf Image.brandassets/Top Shelf Image.imageset/top-shelf-image.png new file mode 100644 index 0000000..29aa253 Binary files /dev/null and b/Blink/Blink/Assets.xcassets/App Icon & Top Shelf Image.brandassets/Top Shelf Image.imageset/top-shelf-image.png differ diff --git a/Blink/Blink/Assets.xcassets/App Icon & Top Shelf Image.brandassets/Top Shelf Image.imageset/top-shelf-image@2x-1.png b/Blink/Blink/Assets.xcassets/App Icon & Top Shelf Image.brandassets/Top Shelf Image.imageset/top-shelf-image@2x-1.png new file mode 100644 index 0000000..d72f22e Binary files /dev/null and b/Blink/Blink/Assets.xcassets/App Icon & Top Shelf Image.brandassets/Top Shelf Image.imageset/top-shelf-image@2x-1.png differ diff --git a/Blink/Blink/Assets.xcassets/App Icon & Top Shelf Image.brandassets/Top Shelf Image.imageset/top-shelf-image@2x.png b/Blink/Blink/Assets.xcassets/App Icon & Top Shelf Image.brandassets/Top Shelf Image.imageset/top-shelf-image@2x.png new file mode 100644 index 0000000..d72f22e Binary files /dev/null and b/Blink/Blink/Assets.xcassets/App Icon & Top Shelf Image.brandassets/Top Shelf Image.imageset/top-shelf-image@2x.png differ diff --git a/Blink/Blink/Assets.xcassets/Background.colorset/Contents.json b/Blink/Blink/Assets.xcassets/Background.colorset/Contents.json new file mode 100644 index 0000000..34fdb07 --- /dev/null +++ b/Blink/Blink/Assets.xcassets/Background.colorset/Contents.json @@ -0,0 +1,20 @@ +{ + "colors" : [ + { + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0.949", + "green" : "0.949", + "red" : "0.949" + } + }, + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Blink/Blink/Assets.xcassets/Black.colorset/Contents.json b/Blink/Blink/Assets.xcassets/Black.colorset/Contents.json new file mode 100644 index 0000000..de988c3 --- /dev/null +++ b/Blink/Blink/Assets.xcassets/Black.colorset/Contents.json @@ -0,0 +1,20 @@ +{ + "colors" : [ + { + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0.416", + "green" : "0.416", + "red" : "0.416" + } + }, + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Blink/Blink/Assets.xcassets/Contents.json b/Blink/Blink/Assets.xcassets/Contents.json new file mode 100644 index 0000000..73c0059 --- /dev/null +++ b/Blink/Blink/Assets.xcassets/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Blink/Blink/Assets.xcassets/new-tvos-icon-front-large.imageset/Contents.json b/Blink/Blink/Assets.xcassets/new-tvos-icon-front-large.imageset/Contents.json new file mode 100644 index 0000000..0c08763 --- /dev/null +++ b/Blink/Blink/Assets.xcassets/new-tvos-icon-front-large.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "filename" : "new-tvos-icon-front-large.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Blink/Blink/Assets.xcassets/new-tvos-icon-front-large.imageset/new-tvos-icon-front-large.png b/Blink/Blink/Assets.xcassets/new-tvos-icon-front-large.imageset/new-tvos-icon-front-large.png new file mode 100644 index 0000000..44e37e8 Binary files /dev/null and b/Blink/Blink/Assets.xcassets/new-tvos-icon-front-large.imageset/new-tvos-icon-front-large.png differ diff --git a/Blink/Blink/Base.lproj/LaunchScreen.storyboard b/Blink/Blink/Base.lproj/LaunchScreen.storyboard new file mode 100644 index 0000000..660ba53 --- /dev/null +++ b/Blink/Blink/Base.lproj/LaunchScreen.storyboard @@ -0,0 +1,24 @@ + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Blink/Blink/Brainstorming/ViewModels/BrainstormingViewModel.swift b/Blink/Blink/Brainstorming/ViewModels/BrainstormingViewModel.swift new file mode 100644 index 0000000..5c5a878 --- /dev/null +++ b/Blink/Blink/Brainstorming/ViewModels/BrainstormingViewModel.swift @@ -0,0 +1,217 @@ +// +// BrainstormingViewModel.swift +// Blink +// +// Created by Edgar Sgroi on 08/07/20. +// Copyright © 2020 Artur Carneiro. All rights reserved. +// + +import Foundation +import MultipeerConnectivity +import os.log + +/// `BrainstormingView`'s ViewModel dependant on `MultipeerConnectivity`. +class BrainstormingViewModel: NSObject, ObservableObject { + + /// Shared instance of the Multipeer Singleton. + private let multipeerConnection = Multipeer.shared + + /// Published variable of the idea Matrix. + /// Any changes that occur in this variable will make the view update. + @Published var ideasMatrix: [[Idea]] = [[Idea(content: "Waiting for ideas... 🤔")]] + + /// The topic set for the session. + @Published private(set) var topic: String + + /// The timer set for the session. + @Published private(set) var timer: String = "" + + var brainstormTimer = Timer() + + /// Variables of Bool type to help in Timer behavior. + /// - isTimerActive: Inform if the timer is currently running or not. + /// - timeless: Inform if Brainstorm Session is timeless + @Published var isTimerActive: Bool = true + @Published var timeless: Bool = false + + /// String array variable to store ideas. + /// When an idea is sent through P2P connection, + /// It will be stored in this array. + var ideas: [Idea] = [Idea]() + + /// Initialize a new instance of this type. + /// Sets itself as the MultipeerConnectivity delegate. + /// - Parameter ideas: An array of Strings containing the ideas of + /// a brainstorming session. Empty by default. + /// - Parameter topic: A session's topic. Empty by default. + /// - Parameter timer: A session's timer. Empty by default. + init(topic: String = "", + timer: Int = 0) { + self.topic = topic + super.init() + self.startBrainstormTimer(counter: timer) + multipeerConnection.mcSession.delegate = self + + + os_log("BrainstormingViewModel initialized as MCSession's delegate.", log: .multipeer, type: .info) + } + + func addIdea(_ content: String) { + let newIdea = Idea(content: content) + ideas.append(newIdea) + ideasMatrix = convertIdeasArrayInMatrix(ideas: ideas.reversed()) + } + + private func addNew(idea: Idea) { + ideas.append(idea) + DispatchQueue.main.async { [weak self] in + guard let self = self else { return } + self.ideasMatrix = self.makeGridList(with: self.ideas.reversed()) + } + } + + /// Internal functional that converts the idea String array + /// in an idea 2D String matrix with 3 columns and N rows. + /// This function is called with the following parameters: + /// - Parameter ideas: The String array that contains the ideas sent through P2P connection. + func convertIdeasArrayInMatrix(ideas: [Idea]) -> [[Idea]] { + var matrixIdeas: [[Idea]] = [] + var colIndex: Int = 0 + var ideaArray: [Idea] = [] + for idea in ideas { + if colIndex == 3 { + matrixIdeas.append(ideaArray) + ideaArray = [] + colIndex = 0 + + if ideaArray.isEmpty { + ideaArray.append(idea) + colIndex += 1 + } + + } else { + ideaArray.append(idea) + colIndex += 1 + } + } + if ideaArray.isEmpty == false { + matrixIdeas.append(ideaArray) + } + return matrixIdeas + } + + /// Internal function that creates a + /// scheduled timer for the Brainstorm View. + /// This function is called with the following parameters: + /// - Parameter counter: An Int type variable that tells the time amount for the Brainstorm Timer. + private func startBrainstormTimer(counter: Int) { + + /// Create a var to put the counter variable in the function scope. + var timerCounter = counter * 60 + var minute: Int = 0 + var second: Int = 0 + + if timerCounter > 0 { + /// Instanciating the brainstormTimer as a repeatable scheduledTimer with a 1 second interval. + brainstormTimer = Timer.scheduledTimer(withTimeInterval: 1, repeats: true, block: { [weak self] (_) in + + /// Safely unwrapping the self in the timer scope + guard let self = self else { return } + + /// Updating the timerCounter by decreasing it, and also updating the ViewModel timer String. + timerCounter = timerCounter - 1 + minute = timerCounter / 60 + second = timerCounter % 60 + + /// The timer will use 2 in both minute and second display. + /// If one of them are under 10, a 0 will be added to keep the standard size. + if minute < 10 { + if second < 10 { + self.timer = "0\(minute) : 0\(second)" + } else { + self.timer = "0\(minute) : \(second)" + } + } else { + if second < 10 { + self.timer = "\(minute) : 0\(second)" + } else { + self.timer = "\(minute) : \(second)" + } + } + + /// When the timer reaches 0, it will be stopped through the invalidate method. + if timerCounter == 0 { + self.brainstormTimer.invalidate() + self.isTimerActive = false + } + }) + + os_log("Timer set for @ minutes", log: .brainstorm, type: .info, counter) + } else { + self.timeless = true + self.timer = "Without Time Limit" + self.isTimerActive = false + + os_log("Timer set without limit", log: .brainstorm, type: .info) + } + } + + private func makeGridList(with ideas: [Idea]) -> [[Idea]] { + var gridList: [[Idea]] = [[Idea]]() + var row = "" + var rowArr = [Idea]() + for idea in ideas { + let aux = row.isEmpty ? "\(idea.content)" : "\(row) \(idea.content)" + if aux.count < 65 { + row = aux + rowArr.append(idea) + + } else { + gridList.append(rowArr) + row = "\(idea.content)" + rowArr = [] + rowArr.append(idea) + } + if ideas.last == idea { + gridList.append(rowArr) + } + } + return gridList + } +} + +// MARK: - MultipeerConnectivity Session Delegate Functions +extension BrainstormingViewModel: MCSessionDelegate { + func session(_ session: MCSession, peer peerID: MCPeerID, didChange state: MCSessionState) { + switch state { + case MCSessionState.connected: + multipeerConnection.connectionStatus = .connected + case MCSessionState.connecting: + multipeerConnection.connectionStatus = .connecting + case MCSessionState.notConnected: + multipeerConnection.connectionStatus = .notConnected + @unknown default: + multipeerConnection.connectionStatus = .unknown + } + } + + func session(_ session: MCSession, didReceive data: Data, fromPeer peerID: MCPeerID) { + do { + let idea = try JSONDecoder().decode(Idea.self, from: data) + addNew(idea: idea) + + os_log("Received new idea from a participant", log: .brainstorm, type: .info) + } catch { + os_log("Failed to decode Idea from participant", log: .brainstorm, type: .error) + } + } + + func session(_ session: MCSession, didReceive stream: InputStream, withName streamName: String, fromPeer peerID: MCPeerID) { + } + + func session(_ session: MCSession, didStartReceivingResourceWithName resourceName: String, fromPeer peerID: MCPeerID, with progress: Progress) { + } + + func session(_ session: MCSession, didFinishReceivingResourceWithName resourceName: String, fromPeer peerID: MCPeerID, at localURL: URL?, withError error: Error?) { + } +} diff --git a/Blink/Blink/Brainstorming/Views/BrainstormingView.swift b/Blink/Blink/Brainstorming/Views/BrainstormingView.swift new file mode 100644 index 0000000..7a56bbf --- /dev/null +++ b/Blink/Blink/Brainstorming/Views/BrainstormingView.swift @@ -0,0 +1,115 @@ +// +// BrainstormingView.swift +// Blink +// +// Created by Artur Carneiro on 07/07/20. +// Copyright © 2020 Artur Carneiro. All rights reserved. +// + +import SwiftUI + +/// Representation of the brainstorming screen. +struct BrainstormingView: View { + + /// `BrainstormingView`'s viewmodel. + @ObservedObject var viewmodel: BrainstormingViewModel + + @State var newIdea: String = "" + @State var shouldVote: Bool = false + @State var showKeyboard: Bool = false + + /// The body of a `BrainstormingView` + var body: some View { + VStack { + + /// The HStack containing the topic, timer and + /// number of ideas added. + HStack(alignment: .center) { + Spacer() + Text(viewmodel.timer) + .foregroundColor(Color.white) + .font(.system(size: 45, weight: .regular, design: .rounded)) + Spacer() + Text(viewmodel.topic) + .foregroundColor(Color.white) + .font(.system(size: 50, weight: .bold, design: .rounded)) + Spacer() + Text("\(viewmodel.ideasMatrix.reduce(0) { $0 + $1.count })") + .foregroundColor(Color.white) + .font(.system(size: 45, weight: .regular, design: .rounded)) + Spacer() + } + .frame(width: UIScreen.main.bounds.width, height: 200, alignment: .center) + .background(Color("Accent")) + + Spacer() + + /// The `GridView` used to layout the ideas in a + /// 3-column grid. + GridView(items: $viewmodel.ideasMatrix) + Spacer() + + /// The HStack containing the buttons for + /// adding a new idea, using the Apple TV remote, + /// and moving forward to voting. + HStack(alignment: .center) { + + VStack { + /// Conditional to check if timer is active or + /// if the Brainstorm Session is running timeless. + /// This will prompt the view to add or remove the button + /// that adds Ideas. + if self.viewmodel.isTimerActive || self.viewmodel.timeless { + /// The Button responsible for adding new ideas + /// using the Apple TV remote. + Button(action: { + self.showKeyboard.toggle() + }) { + HStack(alignment: .center) { + Image(systemName: "plus") + Spacer() + Text("Add") + Spacer() + }.frame(width: (UIScreen.main.bounds.width/2 * 0.8) / 2 ) + .foregroundColor(Color("Black")) + } + } + + if showKeyboard { + TextField("Idea", text: self.$newIdea, onEditingChanged: {_ in}) { + self.viewmodel.addIdea(self.newIdea) + self.showKeyboard.toggle() + self.newIdea = "" + }.frame(width: (UIScreen.main.bounds.width/2 * 0.8) / 2 ) + .foregroundColor(Color("Black")) + } + } + + /// The Button responsible for moving forward to + /// voting. Should alert the user before moving on. + Button(action: { + /// Make it certain that the timer is no longer active. + self.viewmodel.brainstormTimer.invalidate() + self.shouldVote.toggle() + }) { + HStack(alignment: .center) { + Image(systemName: "checkmark.circle.fill") + Spacer() + Text("Vote") + Spacer() + }.frame(width: (UIScreen.main.bounds.width/2 * 0.8) / 2 ) + .foregroundColor(Color("Black")) + } + + /// The conditional responsible for creating the NavigationLink + if self.shouldVote { + NavigationLink(destination: VotingView(viewmodel: VotingViewModel(ideas: viewmodel.convertIdeasArrayInMatrix(ideas: viewmodel.ideas), topic: viewmodel.topic)), isActive: $shouldVote) { EmptyView() } + } + + }.frame(height: UIScreen.main.bounds.height * 0.15) + Spacer() + }.navigationBarBackButtonHidden(true).onExitCommand(perform: {}) + .background(Color.white) + .edgesIgnoringSafeArea(.vertical) + } +} diff --git a/Blink/Blink/Grid/GridView.swift b/Blink/Blink/Grid/GridView.swift new file mode 100644 index 0000000..a8f6c76 --- /dev/null +++ b/Blink/Blink/Grid/GridView.swift @@ -0,0 +1,45 @@ +// +// GridView.swift +// Blink +// +// Created by Artur Carneiro on 08/07/20. +// Copyright © 2020 Artur Carneiro. All rights reserved. +// + +import SwiftUI + +/// Representation of a 2D grid using +/// `GridRowView` as rows. +struct GridView: View { + /// The 2D matrix containing the items as Strings. + @Binding var items: [[Idea]] + + /// The body of a `GridView` + var body: some View { + VStack { + ScrollView(.vertical) { + ForEach(items, id: \.self) { row in + HStack { + ForEach(row, id: \.id) { col in + self.makeText(col, row).focusable() + } + }.padding() + } + }.padding() + } + } + + private func makeText(_ idea: Idea, _ row: [Idea]) -> Text { + guard let indexRow = items.firstIndex(of: row) else { return Text("")} + guard let indexCol = row.firstIndex(of: idea) else { return Text("")} + + + if ((indexCol + indexRow) % 2 == 0) { + return Text(idea.content).foregroundColor(Color("Accent")).font(.system(size: 40, weight: .semibold, design: .rounded)) + } else { + return Text(idea.content).foregroundColor(Color("Black")).font(.system(size: 40, weight: .semibold, design: .rounded)) + } + } +} + + diff --git a/Blink/Blink/Grid/GridViewVotable.swift b/Blink/Blink/Grid/GridViewVotable.swift new file mode 100644 index 0000000..ab9748d --- /dev/null +++ b/Blink/Blink/Grid/GridViewVotable.swift @@ -0,0 +1,66 @@ +// +// GridViewVotable.swift +// Blink +// +// Created by Artur Carneiro on 13/07/20. +// Copyright © 2020 Artur Carneiro. All rights reserved. +// + +import SwiftUI + +struct GridViewVotableRow: View { + let idea: Idea + + var body: some View { + HStack { + Text(idea.content) + Spacer() + if idea.isSelected { + Image(systemName: "checkmark.circle.fill") + } else { + Image(systemName: "circle") + } + }.frame(width: 400, height: 50).font(.headline) + } +} + +struct GridViewVotable: View { + /// The 2D matrix containing the items as Strings. + @Binding var items: [[Idea]] + + @State private var currentlyChosen: Idea = Idea(content: "") + + /// The body of a `GridView` + var body: some View { + VStack { + ScrollView(.vertical) { + ForEach(0 ..< items.count) { row in + HStack { + ForEach(0 ..< self.items[row].count) { col in + HStack { + Button(action: { + if self.items[row][col] == self.currentlyChosen { + self.items[row][col].isSelected.toggle() + self.currentlyChosen = Idea(content: "") + } else { + self.items[row][col].isSelected.toggle() + self.currentlyChosen = self.items[row][col] + } + }) { + if self.items[row][col] == self.currentlyChosen { + GridViewVotableRow(idea: self.currentlyChosen) + } else { + GridViewVotableRow(idea: self.items[row][col]) + } + } + .foregroundColor(Color("Black")) + .frame(height: UIScreen.main.bounds.height * 0.15) + .padding() + } + } + } + } + } + } + } +} diff --git a/Blink/Blink/Idea.swift b/Blink/Blink/Idea.swift new file mode 100644 index 0000000..3f014fe --- /dev/null +++ b/Blink/Blink/Idea.swift @@ -0,0 +1,26 @@ +// +// Idea.swift +// Blink +// +// Created by Artur Carneiro on 16/07/20. +// Copyright © 2020 Artur Carneiro. All rights reserved. +// + +import Foundation + +struct Idea: Identifiable { + + let id: UUID = UUID() + var content: String + var isSelected: Bool = false + var votes: Int = 0 + var position: Int = 1 +} + +extension Idea: Codable {} +extension Idea: Hashable {} +extension Idea: Equatable { + static func ==(lhs: Idea, rhs: Idea) -> Bool { + return lhs.id == rhs.id && lhs.content == rhs.content && lhs.isSelected == rhs.isSelected && lhs.votes == rhs.votes && lhs.position == rhs.position + } +} diff --git a/Blink/Blink/Info.plist b/Blink/Blink/Info.plist new file mode 100644 index 0000000..0039fac --- /dev/null +++ b/Blink/Blink/Info.plist @@ -0,0 +1,32 @@ + + + + + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + $(PRODUCT_BUNDLE_PACKAGE_TYPE) + CFBundleShortVersionString + $(MARKETING_VERSION) + CFBundleVersion + $(CURRENT_PROJECT_VERSION) + LSRequiresIPhoneOS + + UILaunchStoryboardName + LaunchScreen + UIRequiredDeviceCapabilities + + arm64 + + UIUserInterfaceStyle + Automatic + + diff --git a/Blink/Blink/Menu/ViewModels/MenuViewModel.swift b/Blink/Blink/Menu/ViewModels/MenuViewModel.swift new file mode 100644 index 0000000..ef70232 --- /dev/null +++ b/Blink/Blink/Menu/ViewModels/MenuViewModel.swift @@ -0,0 +1,70 @@ +// +// MenuViewModel.swift +// Blink +// +// Created by Edgar Sgroi on 08/07/20. +// Copyright © 2020 Artur Carneiro. All rights reserved. +// + +import Foundation +import MultipeerConnectivity +import os.log + +final class MenuViewModel: NSObject, ObservableObject { + + private lazy var multipeerConnection = Multipeer.shared + + @Published var topic: String = "" + @Published var isHosting: Bool = false + + var anyConnected: Bool { + if multipeerConnection.mcSession.connectedPeers.count > 0 { + return true + } else { + return false + } + } + + override init() { + super.init() + startHosting() + multipeerConnection.mcSession.delegate = self + + os_log("MenuViewModel initialized as MCSession's delegate.", log: .multipeer, type: .info) + } + /// Starts hosting a Multipeer session with `blnk` as service type. + func startHosting() { + multipeerConnection.mcAdvertiserAssistant = MCAdvertiserAssistant(serviceType: "blnk", discoveryInfo: nil, session: multipeerConnection.mcSession) + multipeerConnection.mcAdvertiserAssistant.start() + isHosting.toggle() + os_log("Started hosting a Multipeer session", log: .multipeer, type: .info) + } +} + +// MARK: - MultipeerConnectivity Session Delegate Functions +extension MenuViewModel: MCSessionDelegate { + func session(_ session: MCSession, peer peerID: MCPeerID, didChange state: MCSessionState) { + switch state { + case MCSessionState.connected: + multipeerConnection.connectionStatus = .connected + case MCSessionState.connecting: + multipeerConnection.connectionStatus = .connecting + case MCSessionState.notConnected: + multipeerConnection.connectionStatus = .notConnected + @unknown default: + multipeerConnection.connectionStatus = .unknown + } + } + + func session(_ session: MCSession, didReceive data: Data, fromPeer peerID: MCPeerID) { + } + + func session(_ session: MCSession, didReceive stream: InputStream, withName streamName: String, fromPeer peerID: MCPeerID) { + } + + func session(_ session: MCSession, didStartReceivingResourceWithName resourceName: String, fromPeer peerID: MCPeerID, with progress: Progress) { + } + + func session(_ session: MCSession, didFinishReceivingResourceWithName resourceName: String, fromPeer peerID: MCPeerID, at localURL: URL?, withError error: Error?) { + } +} diff --git a/Blink/Blink/Menu/Views/MenuView.swift b/Blink/Blink/Menu/Views/MenuView.swift new file mode 100644 index 0000000..c9298c1 --- /dev/null +++ b/Blink/Blink/Menu/Views/MenuView.swift @@ -0,0 +1,101 @@ +// +// MenuView.swift +// Blink +// +// Created by Artur Carneiro on 06/07/20. +// Copyright © 2020 Artur Carneiro. All rights reserved. +// + +import SwiftUI + +/// Representation of the main menu. This view should be +/// the app's entry point. +struct MenuView: View { + + @ObservedObject var viewmodel: MenuViewModel + + @State var shouldStart: Bool = false + + @State var timer: String = "" + + /// The body of a `MenuView`. + var body: some View { + NavigationView { + HStack(spacing: 0) { + VStack(alignment: .center) { + Spacer(minLength: UIScreen.main.bounds.height * 0.05) + HStack { + Image("new-tvos-icon-front-large") + .resizable() + .frame(width: UIScreen.main.bounds.width * 0.1, height: UIScreen.main.bounds.height * 0.1) + Spacer() + Spacer() + } + .background(Color("Accent")) + Spacer() + + Text("\(timer) min") + .font(.system(size: 225, weight: .bold, design: .rounded)) + .foregroundColor(Color("Background")) + .frame(width: UIScreen.main.bounds.width/2 * 0.8, height: UIScreen.main.bounds.height * 0.32) + + TextField("Set the amount of minutes", text: $timer) + .keyboardType(.numberPad) + .padding() + .frame(width: UIScreen.main.bounds.width/2 * 0.8) + .foregroundColor(Color("Black")) + + Spacer(minLength: UIScreen.main.bounds.height * 0.245) + } + .frame(width: UIScreen.main.bounds.width/2) + .background(Color("Accent")) + + + VStack(alignment: .leading) { + Spacer() + + Text("Topic: \(viewmodel.topic)") + .font(.system(size: 100, weight: .bold, design: .rounded)) + .foregroundColor(Color("Accent")) + .multilineTextAlignment(.leading) + .lineLimit(nil) + .frame(width: UIScreen.main.bounds.width/2 * 0.8, height: UIScreen.main.bounds.height * 0.5) + + + HStack(alignment: .top) { + TextField("Set a topic", text: $viewmodel.topic) + .keyboardType(.default) + .padding() + .frame(width: (UIScreen.main.bounds.width/2 * 0.8) / 2 ) + .foregroundColor(Color("Black")) + + Button(action: { + self.shouldStart.toggle() + }, label: { + + HStack { + Image(systemName: "play.fill") + + Text("Start session") + .font(.system(.body, design: .rounded)) + + }.foregroundColor(Color("Black")) + + }).frame(width: (UIScreen.main.bounds.width/2 * 0.8) / 2 ) + + } + + Spacer() + + } + .frame(width: UIScreen.main.bounds.width/2) + .background(Color("Background")) + if shouldStart == true { + NavigationLink(destination: BrainstormingView(viewmodel: BrainstormingViewModel(topic: viewmodel.topic, timer: Int(timer) ?? 0)), isActive: self.$shouldStart) { EmptyView() } + } + } + .edgesIgnoringSafeArea(.vertical) + .navigationBarBackButtonHidden(true).onExitCommand(perform: {}) + } + } +} diff --git a/Blink/Blink/Multipeer.swift b/Blink/Blink/Multipeer.swift new file mode 100644 index 0000000..474c6ff --- /dev/null +++ b/Blink/Blink/Multipeer.swift @@ -0,0 +1,48 @@ +// +// Multipeer.swift +// Blink +// +// Created by Edgar Sgroi on 08/07/20. +// Copyright © 2020 Artur Carneiro. All rights reserved. +// + +import Foundation +import MultipeerConnectivity +import Combine +import os.log + +/// Representation of a `MulipeerConnectivity` session status. +enum ConnectionStatus { + case connected + case connecting + case notConnected + case unknown +} + +/// Singleton responsible for managing a `MultipeerConnectivity` session. +final class Multipeer: NSObject, ObservableObject { + + static let shared = Multipeer() + + var peerID: MCPeerID + var mcSession: MCSession + var mcAdvertiserAssistant: MCAdvertiserAssistant + + var connectionStatus: ConnectionStatus + @Published var connectedPeersName: [String] = [] + + override init() { + peerID = MCPeerID(displayName: "Brainstorm Host \(UIDevice.current.name)") + mcSession = MCSession(peer: peerID, securityIdentity: nil, encryptionPreference: .required) + connectionStatus = .notConnected + mcAdvertiserAssistant = MCAdvertiserAssistant(serviceType: "blnk", discoveryInfo: nil, session: mcSession) + super.init() + } +} + +extension MCSession { + + var peersName: [String] { + connectedPeers.map({$0.displayName}) + } +} diff --git a/Blink/Blink/OSLog+Extensions.swift b/Blink/Blink/OSLog+Extensions.swift new file mode 100644 index 0000000..ab09a51 --- /dev/null +++ b/Blink/Blink/OSLog+Extensions.swift @@ -0,0 +1,23 @@ +// +// OSLog+Extensions.swift +// Blink +// +// Created by Artur Carneiro on 16/07/20. +// Copyright © 2020 Artur Carneiro. All rights reserved. +// + +import Foundation +import os.log + +extension OSLog { + private static var subsystem = Bundle.main.bundleIdentifier ?? "com.edsgroi.Blink.error" + + static let brainstorm = OSLog(subsystem: subsystem, category: "brainstorm") + static let voting = OSLog(subsystem: subsystem, category: "voting") + static let ranking = OSLog(subsystem: subsystem, category: "ranking") + static let interaction = OSLog(subsystem: subsystem, category: "interaction") + static let multipeer = OSLog(subsystem: subsystem, category: "multipeer") +} + + + diff --git a/Blink/Blink/Preview Content/Preview Assets.xcassets/Contents.json b/Blink/Blink/Preview Content/Preview Assets.xcassets/Contents.json new file mode 100644 index 0000000..73c0059 --- /dev/null +++ b/Blink/Blink/Preview Content/Preview Assets.xcassets/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Blink/Blink/Ranking/ViewModels/MenuView.swift b/Blink/Blink/Ranking/ViewModels/MenuView.swift new file mode 100644 index 0000000..1aea7ae --- /dev/null +++ b/Blink/Blink/Ranking/ViewModels/MenuView.swift @@ -0,0 +1,63 @@ +// +// MenuView.swift +// Blink_iOS +// +// Created by Artur Carneiro on 14/07/20. +// Copyright © 2020 Artur Carneiro. All rights reserved. +// + +import SwiftUI + +struct MenuView: View { + + @ObservedObject var viewmodel: MenuViewModel + /// This a Bool type var that controls if the Brainstorming + /// session has started or not. + @State var hasStarted: Bool = false + + var body: some View { + NavigationView { + VStack { + Spacer() + Image("blink-icon-menu") + .resizable() + .aspectRatio(contentMode: .fit) + .frame(height: UIScreen.main.bounds.height * 0.3) + .shadow(radius: 5, y: 15) + Spacer() + Button(action: { + self.viewmodel.isJoining.toggle() + }) { + Text("Join session") + .font(.system(.title, design: .rounded)) + .bold() + .foregroundColor(Color("Accent")) + } + .frame(width: UIScreen.main.bounds.width * 0.6, height: UIScreen.main.bounds.height * 0.1) + .background(Color("Background")) + .cornerRadius(15) + .shadow(radius: 5, y: 15) + + Spacer().sheet(isPresented: $viewmodel.isJoining) { + Browser(delegate: self.viewmodel).onDisappear() { + self.hasStarted = true + } + } + if viewmodel.isConnected { + NavigationLink(destination: BrainstormingView(viewmodel: BrainstormingViewModel(), hasStarted: $hasStarted), isActive: $hasStarted, label: {EmptyView()}).isDetailLink(false) + } + }.frame(width: UIScreen.main.bounds.width) + .background(Color("Accent")) + .edgesIgnoringSafeArea(.vertical) + } + .navigationBarBackButtonHidden(true) + .navigationBarHidden(true) + /// This guarantee that the hasStarted var stays false when this view has appeared. + .onAppear() { + self.hasStarted = false + UINavigationBar.appearance().backgroundColor = UIColor(named: "Accent") + UINavigationBar.appearance().tintColor = UIColor(named: "Background") + } + } +} + diff --git a/Blink/Blink/Ranking/ViewModels/RankingViewModel.swift b/Blink/Blink/Ranking/ViewModels/RankingViewModel.swift new file mode 100644 index 0000000..889018e --- /dev/null +++ b/Blink/Blink/Ranking/ViewModels/RankingViewModel.swift @@ -0,0 +1,114 @@ +// +// RankingViewModel.swift +// Blink +// +// Created by Artur Carneiro on 13/07/20. +// Copyright © 2020 Artur Carneiro. All rights reserved. +// + +import Foundation +import MultipeerConnectivity +import os.log + +final class RankingViewModel: NSObject, ObservableObject { + + private let multipeerConnection = Multipeer.shared + + @Published var ideas: [Idea] = [Idea]() + + @Published var topic: String + + init(ideas: [[Idea]], + votedIdeas: [Idea], + topic: String = "") { + self.topic = topic + super.init() + self.ideas = countVotes(ideas, votedIdeas) + self.ideas = giveRankingPosition(self.ideas) + multipeerConnection.mcSession.delegate = self + + os_log("RankingViewModel initialized as MCSession's delegate.", log: .multipeer, type: .info) + + sendIdeas() + } + + func countVotes(_ ideas: [[Idea]], _ votedIdeas: [Idea]) -> [Idea] { + let convertedArray: [Idea] = convertIdeasMatrixIntoArray(ideas) + return convertedArray.map { + var countedIdea = $0 + for idea in votedIdeas { + if idea.content == countedIdea.content { + countedIdea.votes += 1 + } + } + return countedIdea + }.sorted { $0.votes > $1.votes } + } + + /// This function gives a rank position to the idea based on + /// the amount of votes that the idea has. It has the following parameter: + /// - Parameter ideas: The Idea type array that will have its positions given. + func giveRankingPosition(_ ideas: [Idea]) -> [Idea] { + var rankedIdeas = ideas + var position = 1 + + for index in 0 ..< rankedIdeas.count { + rankedIdeas[index].position = position + if index != 0 && rankedIdeas[index].votes == rankedIdeas[index - 1].votes { + rankedIdeas[index].position = rankedIdeas[index - 1].position + } else { + position += 1 + } + } + return rankedIdeas + } + + private func sendIdeas() { + let mcSession = multipeerConnection.mcSession + if mcSession.connectedPeers.count > 0 { + do { + let data = try JSONEncoder().encode(ideas) + try mcSession.send(data, toPeers: mcSession.connectedPeers, with: .reliable) + os_log("Ranking sent to participants", log: .ranking, type: .info) + } catch _ as EncodingError { + os_log("Failed to encode ranking", log: .ranking, type: .error) + } catch { + os_log("Failed to send ranking", log: .ranking, type: .error) + } + } + } + + private func convertIdeasMatrixIntoArray(_ ideas: [[Idea]]) -> [Idea] { + var arr: [Idea] = [Idea]() + for row in ideas { + arr.append(contentsOf: row) + } + return arr.map { if $0.isSelected { + var idea = $0 + idea.votes += 1 + return idea + } else { + return $0 + }}.sorted { $0.votes > $1.votes } + } + +} + +extension RankingViewModel: MCSessionDelegate { + func session(_ session: MCSession, peer peerID: MCPeerID, didChange state: MCSessionState) { + } + + func session(_ session: MCSession, didReceive data: Data, fromPeer peerID: MCPeerID) { + } + + func session(_ session: MCSession, didReceive stream: InputStream, withName streamName: String, fromPeer peerID: MCPeerID) { + } + + func session(_ session: MCSession, didStartReceivingResourceWithName resourceName: String, fromPeer peerID: MCPeerID, with progress: Progress) { + } + + func session(_ session: MCSession, didFinishReceivingResourceWithName resourceName: String, fromPeer peerID: MCPeerID, at localURL: URL?, withError error: Error?) { + } + + +} diff --git a/Blink/Blink/Ranking/Views/RankingRowView.swift b/Blink/Blink/Ranking/Views/RankingRowView.swift new file mode 100644 index 0000000..3391a28 --- /dev/null +++ b/Blink/Blink/Ranking/Views/RankingRowView.swift @@ -0,0 +1,48 @@ +// +// RankingRowView.swift +// Blink +// +// Created by Artur Carneiro on 30/07/20. +// Copyright © 2020 Artur Carneiro. All rights reserved. +// + +import SwiftUI + +struct RankingViewRow: View { + let position: Int + let content: String + let votes: Int + + var body: some View { + HStack { + if position == 1 { + Image(systemName: "rosette") + .font(.headline) + .foregroundColor(Color.yellow) + } else if position == 2 { + Image(systemName: "rosette") + .font(.headline) + .foregroundColor(Color.white) + } else if position == 3 { + Image(systemName: "rosette") + .font(.headline) + .foregroundColor(Color.orange) + } else { + Image(systemName: "minus") + } + Text("\(position)") + .font(.system(.subheadline, design: .rounded)) + .foregroundColor(Color("Black")) + Text("\(content)") + .font(.system(.headline, design: .rounded)) + .foregroundColor(Color("Black")) + Spacer() + Text("\(votes)") + .font(.system(.body, design: .rounded)).bold() + .foregroundColor(Color("Black")) + Text("votes") + .font(.system(.body, design: .rounded)) + .foregroundColor(Color("Black")) + } + } +} diff --git a/Blink/Blink/Ranking/Views/RankingView.swift b/Blink/Blink/Ranking/Views/RankingView.swift new file mode 100644 index 0000000..84262b0 --- /dev/null +++ b/Blink/Blink/Ranking/Views/RankingView.swift @@ -0,0 +1,77 @@ +// +// RankingView.swift +// Blink +// +// Created by Artur Carneiro on 08/07/20. +// Copyright © 2020 Artur Carneiro. All rights reserved. +// + +import SwiftUI + +/// Representation of the ranking screen. +struct RankingView: View { + /// `RankingView`s ViewModel. + @ObservedObject var viewmodel: RankingViewModel + @State var shouldRestart: Bool = false + + /// The body of a `RankingView`. + var body: some View { + VStack { + /// The HStack containing the topic and + /// number of ideas added. + HStack(alignment: .center) { + Spacer() + Text(viewmodel.topic) + .foregroundColor(Color.white) + .font(.system(size: 45, weight: .regular, design: .rounded)) + Spacer() + Text("Ranking") + .foregroundColor(Color.white) + .font(.system(size: 50, weight: .bold, design: .rounded)) + Spacer() + Text("\(viewmodel.ideas.count)") + .foregroundColor(Color.white) + .font(.system(size: 45, weight: .regular, design: .rounded)) + Spacer() + } + .frame(width: UIScreen.main.bounds.width, height: 200, alignment: .center) + .background(Color("Accent")) + + Spacer() + + /// The list of ideas in order represented by Buttons. + ScrollView(.vertical){ + ForEach(0 ..< viewmodel.ideas.count) { index in + Button(action: { + }) { + RankingViewRow(position: self.viewmodel.ideas[index].position, content: self.viewmodel.ideas[index].content, votes: self.viewmodel.ideas[index].votes) + .frame(width: 1000, height: 50) + }.padding() + } + } + Spacer() + + /// The Button responsible for moving back to + /// the menu. Should alert the user before moving on. + /// This button works as a NavigationLink. + Button(action: { + self.shouldRestart.toggle() + }, label: { + HStack(alignment: .center) { + Image(systemName: "arrow.clockwise") + Spacer() + Text("Restart?") + Spacer() + } + }).frame(width: (UIScreen.main.bounds.width/2 * 0.8) / 2, height: UIScreen.main.bounds.height * 0.15) + .foregroundColor(Color("Black")) + Spacer() + /// Navigation Link responsible to restarting the Brainstorm + if shouldRestart { + NavigationLink(destination: MenuView(viewmodel: MenuViewModel()), isActive: $shouldRestart) { EmptyView() } + } + }.navigationBarBackButtonHidden(true).onExitCommand(perform: {}) + .background(Color.white) + .edgesIgnoringSafeArea(.vertical) + } +} diff --git a/Blink/Blink/Voting/ViewModel/VotingViewModel.swift b/Blink/Blink/Voting/ViewModel/VotingViewModel.swift new file mode 100644 index 0000000..48da02e --- /dev/null +++ b/Blink/Blink/Voting/ViewModel/VotingViewModel.swift @@ -0,0 +1,160 @@ +// +// VotingViewModel.swift +// Blink +// +// Created by Edgar Sgroi on 08/07/20. +// Copyright © 2020 Artur Carneiro. All rights reserved. +// + +import Foundation +import MultipeerConnectivity +import os.log + + +/// `VotingView`'s ViewModel dependant on `MultipeerConnectivity`. +class VotingViewModel: NSObject, ObservableObject { + /// Shared instace of the Multipeer Singleton. + private let multipeerConnection = Multipeer.shared + + /// Published variable of the idea Matrix. + /// Any changes that occur in this variable will make the view update. + @Published var ideas: [[Idea]] = [[Idea]]() + + /// The poll of votes for the session. + @Published var votes: [Idea] = [Idea]() + + private var arrIdeas: [Idea] = [Idea]() + + var votedIdeas: [Idea] = [Idea]() + + @Published var shouldShowRanking: Bool = false + + /// The topic set for the session. + var topic: String + + /// Initialization of this ViewModel with the following parameters: + /// - Parameter ideas: An array of String type that composes the ideas + /// - Parameter topic: A session's topic. Empty by default. + init(ideas: [[Idea]], + topic: String = "") { + self.ideas = ideas + self.topic = topic + super.init() + multipeerConnection.mcSession.delegate = self + + os_log("VotingViewModel initialized as MCSession's delegate.", log: .multipeer, type: .info) + + self.arrIdeas = convertIdeasMatrixIntoArray(ideas) + sendIdeas() + } + + /// Sends ideas generated during the Brainstorming session to + /// all iOS users connected to the current session. + private func sendIdeas() { + let mcSession = multipeerConnection.mcSession + if mcSession.connectedPeers.count > 0 { + do { + let data = try JSONEncoder().encode(countVotes(ideas, votedIdeas)) + try mcSession.send(data, toPeers: mcSession.connectedPeers, with: .reliable) + os_log("Ideas sent to participants", log: .voting, type: .info) + } catch _ as EncodingError { + os_log("Failed to encode ideas to be sent for voting", log: .voting, type: .error) + } catch { + os_log("Failed to send ideas to be voted on", log: .voting, type: .error) + } + } + } + + func countVotes(_ ideas: [[Idea]], _ votedIdeas: [Idea]) -> [Idea] { + let convertedArray: [Idea] = convertIdeasMatrixIntoArray(ideas) + return convertedArray.map { + var countedIdea = $0 + for idea in votedIdeas { + if idea.content == countedIdea.content { + countedIdea.votes += 1 + } + } + return countedIdea + }.sorted { $0.votes > $1.votes } + + } + + private func convertIdeasMatrixIntoArray(_ ideas: [[Idea]]) -> [Idea] { + var arr: [Idea] = [Idea]() + for row in ideas { + arr.append(contentsOf: row) + } + return arr.map { if $0.isSelected { + var idea = $0 + idea.votes += 1 + return idea + } else { + return $0 + }}.sorted { $0.votes > $1.votes } + } + + /// Internal functional that converts the idea String array + /// in an idea 2D String matrix with 3 columns and N rows. + /// This function is called with the following parameters: + /// - Parameter ideas: The String array that contains the ideas sent through P2P connection. + private func convertIdeasArrayInMatrix(ideas: [Idea]) -> [[Idea]] { + var matrixIdeas: [[Idea]] = [] + var colIndex: Int = 0 + var ideaArray: [Idea] = [] + for idea in ideas { + if colIndex == 3 { + matrixIdeas.append(ideaArray) + ideaArray = [] + colIndex = 0 + + if ideaArray.isEmpty { + ideaArray.append(idea) + colIndex += 1 + } + + } else { + ideaArray.append(idea) + colIndex += 1 + } + } + if ideaArray.isEmpty == false { + matrixIdeas.append(ideaArray) + } + return matrixIdeas + } +} + +// MARK: - MultipeerConnectivity Session Delegate Functions +extension VotingViewModel: MCSessionDelegate { + func session(_ session: MCSession, peer peerID: MCPeerID, didChange state: MCSessionState) { + switch state { + case MCSessionState.connected: + multipeerConnection.connectionStatus = .connected + case MCSessionState.connecting: + multipeerConnection.connectionStatus = .connecting + case MCSessionState.notConnected: + multipeerConnection.connectionStatus = .notConnected + @unknown default: + multipeerConnection.connectionStatus = .unknown + } + } + + func session(_ session: MCSession, didReceive data: Data, fromPeer peerID: MCPeerID) { + do { + let idea = try JSONDecoder().decode([Idea].self, from: data) + votedIdeas.append(contentsOf: idea) + os_log("Received votes from participant", log: .voting, type: .info ) + } catch { + os_log("Failed to decode Idea from iOS participant", log: .voting, type: .error) + } + } + + func session(_ session: MCSession, didReceive stream: InputStream, withName streamName: String, fromPeer peerID: MCPeerID) { + } + + func session(_ session: MCSession, didStartReceivingResourceWithName resourceName: String, fromPeer peerID: MCPeerID, with progress: Progress) { + } + + func session(_ session: MCSession, didFinishReceivingResourceWithName resourceName: String, fromPeer peerID: MCPeerID, at localURL: URL?, withError error: Error?) { + } +} diff --git a/Blink/Blink/Voting/Views/VotingView.swift b/Blink/Blink/Voting/Views/VotingView.swift new file mode 100644 index 0000000..2b81da9 --- /dev/null +++ b/Blink/Blink/Voting/Views/VotingView.swift @@ -0,0 +1,70 @@ +// +// VotingView.swift +// Blink +// +// Created by Artur Carneiro on 08/07/20. +// Copyright © 2020 Artur Carneiro. All rights reserved. +// + +import SwiftUI + +/// Representation of the voting screen. +struct VotingView: View { + + /// `VotingView`'s viewmodel. + @ObservedObject var viewmodel: VotingViewModel + + /// The body of a `VotingView` + var body: some View { + VStack { + /// The HStack containing the topic, timer and + /// number of ideas added. + HStack(alignment: .center) { + Spacer() + Text(viewmodel.topic) + .foregroundColor(Color.white) + .font(.system(size: 45, weight: .regular, design: .rounded)) + Spacer() + Text("Time to vote!") + .foregroundColor(Color.white) + .font(.system(size: 50, weight: .bold, design: .rounded)) + Spacer() + Text("\(viewmodel.ideas.reduce(0) { $0 + $1.count })") + .foregroundColor(Color.white) + .font(.system(size: 45, weight: .regular, design: .rounded)) + Spacer() + } + .frame(width: UIScreen.main.bounds.width, height: 200, alignment: .center) + .background(Color("Accent")) + Spacer() + + /// A votable version of `GridView` following the same 3-column + /// structure. Requires a `Binding` for voting. + GridViewVotable(items: $viewmodel.ideas) + Spacer() + + /// The Button responsible for moving forward to + /// ranking. Should alert the user before moving on. + if viewmodel.shouldShowRanking { + NavigationLink(destination: RankingView(viewmodel: RankingViewModel(ideas: viewmodel.ideas, votedIdeas: viewmodel.votedIdeas, topic: viewmodel.topic)), + isActive: $viewmodel.shouldShowRanking, + label: {EmptyView()}) + } + Button(action: { + self.viewmodel.shouldShowRanking.toggle() + }, label: { + HStack(alignment: .center) { + Image(systemName: "list.number") + Spacer() + Text("Rank") + Spacer() + } + }).frame(width: (UIScreen.main.bounds.width/2 * 0.8) / 2, height: UIScreen.main.bounds.height * 0.15) + .foregroundColor(Color("Black")) + + Spacer() + }.navigationBarBackButtonHidden(true).onExitCommand(perform: {}) + .background(Color.white) + .edgesIgnoringSafeArea(.vertical) + } +} diff --git a/Blink/BlinkTests/BlinkTests.swift b/Blink/BlinkTests/BlinkTests.swift new file mode 100644 index 0000000..1624be8 --- /dev/null +++ b/Blink/BlinkTests/BlinkTests.swift @@ -0,0 +1,15 @@ +// +// BlinkTests.swift +// BlinkTests +// +// Created by Artur Carneiro on 06/07/20. +// Copyright © 2020 Artur Carneiro. All rights reserved. +// + +import XCTest +@testable import Blink + +class BlinkTests: XCTestCase { + + +} diff --git a/Blink/BlinkTests/GridViewTests.swift b/Blink/BlinkTests/GridViewTests.swift new file mode 100644 index 0000000..6b0d438 --- /dev/null +++ b/Blink/BlinkTests/GridViewTests.swift @@ -0,0 +1,50 @@ +// +// GridViewTests.swift +// BlinkTests +// +// Created by Edgar Sgroi on 29/07/20. +// Copyright © 2020 Artur Carneiro. All rights reserved. +// + +import XCTest +@testable import Blink + +class GridViewTests: XCTestCase { + + func testMakeGridList() { + let ideas = [Idea(content: "Idea 1"), Idea(content: "Idea 2"), Idea(content: "Idea 3"), + Idea(content: "Idea 4"), Idea(content: "Idea 5"), Idea(content: "Idea 6"), + Idea(content: "Idea 7"), Idea(content: "Idea 8"), Idea(content: "Idea 9"), + Idea(content: "Idea 10"), Idea(content: "Idea 11"), Idea(content: "Idea 12"), + Idea(content: "Idea 13"), Idea(content: "Idea 14"), Idea(content: "Idea 15"), + Idea(content: "Idea 16"), Idea(content: "Idea 16.5"), Idea(content: "Idea 17"), + Idea(content: "Idea 18"), Idea(content: "Idea 19"), Idea(content: "Idea 20"), + Idea(content: "Idea 21"), Idea(content: "Idea 22"), Idea(content: "Idea 23"), + Idea(content: "Idea 24"), Idea(content: "Idea 25"), Idea(content: "Idea 26"), + Idea(content: "Idea 27"), Idea(content: "Idea 28"), Idea(content: "Idea 29"), + Idea(content: "Idea 30")] + + let formattedIdeas = makeGridList(with: ideas) + print(formattedIdeas) + print(formattedIdeas[0].count) + + } + + func makeGridList(with ideas: [Idea]) -> [[Idea]] { + var gridList: [[Idea]] = [] + var row = "" + var rowArr = [Idea]() + for idea in ideas { + let aux = row.isEmpty ? "\(idea.content)" : "\(row) \(idea.content)" + if aux.count < 100 { + row = aux + rowArr.append(idea) + } else { + gridList.append(rowArr) + row = "" + rowArr = [] + } + } + return gridList + } +} diff --git a/Blink/BlinkTests/Info.plist b/Blink/BlinkTests/Info.plist new file mode 100644 index 0000000..64d65ca --- /dev/null +++ b/Blink/BlinkTests/Info.plist @@ -0,0 +1,22 @@ + + + + + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + $(PRODUCT_BUNDLE_PACKAGE_TYPE) + CFBundleShortVersionString + 1.0 + CFBundleVersion + 1 + + diff --git a/Blink/Blink_iOS/AppDelegate.swift b/Blink/Blink_iOS/AppDelegate.swift new file mode 100644 index 0000000..35c2cc6 --- /dev/null +++ b/Blink/Blink_iOS/AppDelegate.swift @@ -0,0 +1,37 @@ +// +// AppDelegate.swift +// Blink_iOS +// +// Created by Edgar Sgroi on 08/07/20. +// Copyright © 2020 Artur Carneiro. All rights reserved. +// + +import UIKit + +@UIApplicationMain +class AppDelegate: UIResponder, UIApplicationDelegate { + + + + func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { + // Override point for customization after application launch. + return true + } + + // MARK: UISceneSession Lifecycle + + func application(_ application: UIApplication, configurationForConnecting connectingSceneSession: UISceneSession, options: UIScene.ConnectionOptions) -> UISceneConfiguration { + // Called when a new scene session is being created. + // Use this method to select a configuration to create the new scene with. + return UISceneConfiguration(name: "Default Configuration", sessionRole: connectingSceneSession.role) + } + + func application(_ application: UIApplication, didDiscardSceneSessions sceneSessions: Set) { + // Called when the user discards a scene session. + // If any sessions were discarded while the application was not running, this will be called shortly after application:didFinishLaunchingWithOptions. + // Use this method to release any resources that were specific to the discarded scenes, as they will not return. + } + + +} + diff --git a/Blink/Blink_iOS/Assets.xcassets/Accent.colorset/Contents.json b/Blink/Blink_iOS/Assets.xcassets/Accent.colorset/Contents.json new file mode 100644 index 0000000..88db513 --- /dev/null +++ b/Blink/Blink_iOS/Assets.xcassets/Accent.colorset/Contents.json @@ -0,0 +1,23 @@ +{ + "colors" : [ + { + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0.408", + "green" : "0.408", + "red" : "1.000" + } + }, + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + }, + "properties" : { + "localizable" : true + } +} diff --git a/Blink/Blink_iOS/Assets.xcassets/AppIcon.appiconset/Contents.json b/Blink/Blink_iOS/Assets.xcassets/AppIcon.appiconset/Contents.json new file mode 100644 index 0000000..0c21cc7 --- /dev/null +++ b/Blink/Blink_iOS/Assets.xcassets/AppIcon.appiconset/Contents.json @@ -0,0 +1,116 @@ +{ + "images" : [ + { + "filename" : "new-icon-40pt.png", + "idiom" : "iphone", + "scale" : "2x", + "size" : "20x20" + }, + { + "filename" : "new-icon-60pt.png", + "idiom" : "iphone", + "scale" : "3x", + "size" : "20x20" + }, + { + "filename" : "new-icon-58pt-1.png", + "idiom" : "iphone", + "scale" : "2x", + "size" : "29x29" + }, + { + "filename" : "new-icon-87pt.png", + "idiom" : "iphone", + "scale" : "3x", + "size" : "29x29" + }, + { + "filename" : "new-icon-80pt.png", + "idiom" : "iphone", + "scale" : "2x", + "size" : "40x40" + }, + { + "filename" : "new-icon-120pt.png", + "idiom" : "iphone", + "scale" : "3x", + "size" : "40x40" + }, + { + "filename" : "new-icon-120pt-1.png", + "idiom" : "iphone", + "scale" : "2x", + "size" : "60x60" + }, + { + "filename" : "new-icon-180pt.png", + "idiom" : "iphone", + "scale" : "3x", + "size" : "60x60" + }, + { + "filename" : "new-icon-20px.png", + "idiom" : "ipad", + "scale" : "1x", + "size" : "20x20" + }, + { + "filename" : "new-icon-40pt-1.png", + "idiom" : "ipad", + "scale" : "2x", + "size" : "20x20" + }, + { + "filename" : "new-icon-29pt.png", + "idiom" : "ipad", + "scale" : "1x", + "size" : "29x29" + }, + { + "filename" : "new-icon-58pt.png", + "idiom" : "ipad", + "scale" : "2x", + "size" : "29x29" + }, + { + "filename" : "new-icon-40pt-2.png", + "idiom" : "ipad", + "scale" : "1x", + "size" : "40x40" + }, + { + "filename" : "new-icon-80pt-1.png", + "idiom" : "ipad", + "scale" : "2x", + "size" : "40x40" + }, + { + "filename" : "new-icon-76pt.png", + "idiom" : "ipad", + "scale" : "1x", + "size" : "76x76" + }, + { + "filename" : "new-icon-152pt.png", + "idiom" : "ipad", + "scale" : "2x", + "size" : "76x76" + }, + { + "filename" : "new-icon-167pt.png", + "idiom" : "ipad", + "scale" : "2x", + "size" : "83.5x83.5" + }, + { + "filename" : "new-icon-1024pt.png", + "idiom" : "ios-marketing", + "scale" : "1x", + "size" : "1024x1024" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Blink/Blink_iOS/Assets.xcassets/AppIcon.appiconset/new-icon-1024pt.png b/Blink/Blink_iOS/Assets.xcassets/AppIcon.appiconset/new-icon-1024pt.png new file mode 100644 index 0000000..31fdfd3 Binary files /dev/null and b/Blink/Blink_iOS/Assets.xcassets/AppIcon.appiconset/new-icon-1024pt.png differ diff --git a/Blink/Blink_iOS/Assets.xcassets/AppIcon.appiconset/new-icon-120pt-1.png b/Blink/Blink_iOS/Assets.xcassets/AppIcon.appiconset/new-icon-120pt-1.png new file mode 100644 index 0000000..240b9e9 Binary files /dev/null and b/Blink/Blink_iOS/Assets.xcassets/AppIcon.appiconset/new-icon-120pt-1.png differ diff --git a/Blink/Blink_iOS/Assets.xcassets/AppIcon.appiconset/new-icon-120pt.png b/Blink/Blink_iOS/Assets.xcassets/AppIcon.appiconset/new-icon-120pt.png new file mode 100644 index 0000000..240b9e9 Binary files /dev/null and b/Blink/Blink_iOS/Assets.xcassets/AppIcon.appiconset/new-icon-120pt.png differ diff --git a/Blink/Blink_iOS/Assets.xcassets/AppIcon.appiconset/new-icon-152pt.png b/Blink/Blink_iOS/Assets.xcassets/AppIcon.appiconset/new-icon-152pt.png new file mode 100644 index 0000000..a178bbf Binary files /dev/null and b/Blink/Blink_iOS/Assets.xcassets/AppIcon.appiconset/new-icon-152pt.png differ diff --git a/Blink/Blink_iOS/Assets.xcassets/AppIcon.appiconset/new-icon-167pt.png b/Blink/Blink_iOS/Assets.xcassets/AppIcon.appiconset/new-icon-167pt.png new file mode 100644 index 0000000..c68d978 Binary files /dev/null and b/Blink/Blink_iOS/Assets.xcassets/AppIcon.appiconset/new-icon-167pt.png differ diff --git a/Blink/Blink_iOS/Assets.xcassets/AppIcon.appiconset/new-icon-180pt.png b/Blink/Blink_iOS/Assets.xcassets/AppIcon.appiconset/new-icon-180pt.png new file mode 100644 index 0000000..5dfae4d Binary files /dev/null and b/Blink/Blink_iOS/Assets.xcassets/AppIcon.appiconset/new-icon-180pt.png differ diff --git a/Blink/Blink_iOS/Assets.xcassets/AppIcon.appiconset/new-icon-20px.png b/Blink/Blink_iOS/Assets.xcassets/AppIcon.appiconset/new-icon-20px.png new file mode 100644 index 0000000..5770171 Binary files /dev/null and b/Blink/Blink_iOS/Assets.xcassets/AppIcon.appiconset/new-icon-20px.png differ diff --git a/Blink/Blink_iOS/Assets.xcassets/AppIcon.appiconset/new-icon-29pt.png b/Blink/Blink_iOS/Assets.xcassets/AppIcon.appiconset/new-icon-29pt.png new file mode 100644 index 0000000..69b69ac Binary files /dev/null and b/Blink/Blink_iOS/Assets.xcassets/AppIcon.appiconset/new-icon-29pt.png differ diff --git a/Blink/Blink_iOS/Assets.xcassets/AppIcon.appiconset/new-icon-40pt-1.png b/Blink/Blink_iOS/Assets.xcassets/AppIcon.appiconset/new-icon-40pt-1.png new file mode 100644 index 0000000..987da3c Binary files /dev/null and b/Blink/Blink_iOS/Assets.xcassets/AppIcon.appiconset/new-icon-40pt-1.png differ diff --git a/Blink/Blink_iOS/Assets.xcassets/AppIcon.appiconset/new-icon-40pt-2.png b/Blink/Blink_iOS/Assets.xcassets/AppIcon.appiconset/new-icon-40pt-2.png new file mode 100644 index 0000000..987da3c Binary files /dev/null and b/Blink/Blink_iOS/Assets.xcassets/AppIcon.appiconset/new-icon-40pt-2.png differ diff --git a/Blink/Blink_iOS/Assets.xcassets/AppIcon.appiconset/new-icon-40pt.png b/Blink/Blink_iOS/Assets.xcassets/AppIcon.appiconset/new-icon-40pt.png new file mode 100644 index 0000000..987da3c Binary files /dev/null and b/Blink/Blink_iOS/Assets.xcassets/AppIcon.appiconset/new-icon-40pt.png differ diff --git a/Blink/Blink_iOS/Assets.xcassets/AppIcon.appiconset/new-icon-58pt-1.png b/Blink/Blink_iOS/Assets.xcassets/AppIcon.appiconset/new-icon-58pt-1.png new file mode 100644 index 0000000..4f69688 Binary files /dev/null and b/Blink/Blink_iOS/Assets.xcassets/AppIcon.appiconset/new-icon-58pt-1.png differ diff --git a/Blink/Blink_iOS/Assets.xcassets/AppIcon.appiconset/new-icon-58pt.png b/Blink/Blink_iOS/Assets.xcassets/AppIcon.appiconset/new-icon-58pt.png new file mode 100644 index 0000000..4f69688 Binary files /dev/null and b/Blink/Blink_iOS/Assets.xcassets/AppIcon.appiconset/new-icon-58pt.png differ diff --git a/Blink/Blink_iOS/Assets.xcassets/AppIcon.appiconset/new-icon-60pt.png b/Blink/Blink_iOS/Assets.xcassets/AppIcon.appiconset/new-icon-60pt.png new file mode 100644 index 0000000..16babae Binary files /dev/null and b/Blink/Blink_iOS/Assets.xcassets/AppIcon.appiconset/new-icon-60pt.png differ diff --git a/Blink/Blink_iOS/Assets.xcassets/AppIcon.appiconset/new-icon-76pt.png b/Blink/Blink_iOS/Assets.xcassets/AppIcon.appiconset/new-icon-76pt.png new file mode 100644 index 0000000..b87ecf4 Binary files /dev/null and b/Blink/Blink_iOS/Assets.xcassets/AppIcon.appiconset/new-icon-76pt.png differ diff --git a/Blink/Blink_iOS/Assets.xcassets/AppIcon.appiconset/new-icon-80pt-1.png b/Blink/Blink_iOS/Assets.xcassets/AppIcon.appiconset/new-icon-80pt-1.png new file mode 100644 index 0000000..d946294 Binary files /dev/null and b/Blink/Blink_iOS/Assets.xcassets/AppIcon.appiconset/new-icon-80pt-1.png differ diff --git a/Blink/Blink_iOS/Assets.xcassets/AppIcon.appiconset/new-icon-80pt.png b/Blink/Blink_iOS/Assets.xcassets/AppIcon.appiconset/new-icon-80pt.png new file mode 100644 index 0000000..d946294 Binary files /dev/null and b/Blink/Blink_iOS/Assets.xcassets/AppIcon.appiconset/new-icon-80pt.png differ diff --git a/Blink/Blink_iOS/Assets.xcassets/AppIcon.appiconset/new-icon-87pt.png b/Blink/Blink_iOS/Assets.xcassets/AppIcon.appiconset/new-icon-87pt.png new file mode 100644 index 0000000..8f336ea Binary files /dev/null and b/Blink/Blink_iOS/Assets.xcassets/AppIcon.appiconset/new-icon-87pt.png differ diff --git a/Blink/Blink_iOS/Assets.xcassets/Background.colorset/Contents.json b/Blink/Blink_iOS/Assets.xcassets/Background.colorset/Contents.json new file mode 100644 index 0000000..1c91a47 --- /dev/null +++ b/Blink/Blink_iOS/Assets.xcassets/Background.colorset/Contents.json @@ -0,0 +1,20 @@ +{ + "colors" : [ + { + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0xF4", + "green" : "0xF4", + "red" : "0xF4" + } + }, + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Blink/Blink_iOS/Assets.xcassets/Black.colorset/Contents.json b/Blink/Blink_iOS/Assets.xcassets/Black.colorset/Contents.json new file mode 100644 index 0000000..de988c3 --- /dev/null +++ b/Blink/Blink_iOS/Assets.xcassets/Black.colorset/Contents.json @@ -0,0 +1,20 @@ +{ + "colors" : [ + { + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0.416", + "green" : "0.416", + "red" : "0.416" + } + }, + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Blink/Blink_iOS/Assets.xcassets/Contents.json b/Blink/Blink_iOS/Assets.xcassets/Contents.json new file mode 100644 index 0000000..73c0059 --- /dev/null +++ b/Blink/Blink_iOS/Assets.xcassets/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Blink/Blink_iOS/Assets.xcassets/blink-icon-menu.imageset/Contents.json b/Blink/Blink_iOS/Assets.xcassets/blink-icon-menu.imageset/Contents.json new file mode 100644 index 0000000..f49d359 --- /dev/null +++ b/Blink/Blink_iOS/Assets.xcassets/blink-icon-menu.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "filename" : "blink-icon-menu.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Blink/Blink_iOS/Assets.xcassets/blink-icon-menu.imageset/blink-icon-menu.png b/Blink/Blink_iOS/Assets.xcassets/blink-icon-menu.imageset/blink-icon-menu.png new file mode 100644 index 0000000..d1ffb20 Binary files /dev/null and b/Blink/Blink_iOS/Assets.xcassets/blink-icon-menu.imageset/blink-icon-menu.png differ diff --git a/Blink/Blink_iOS/Base.lproj/LaunchScreen.storyboard b/Blink/Blink_iOS/Base.lproj/LaunchScreen.storyboard new file mode 100644 index 0000000..865e932 --- /dev/null +++ b/Blink/Blink_iOS/Base.lproj/LaunchScreen.storyboard @@ -0,0 +1,25 @@ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Blink/Blink_iOS/Brainstorming/ViewModels/BrainstormingViewModel.swift b/Blink/Blink_iOS/Brainstorming/ViewModels/BrainstormingViewModel.swift new file mode 100644 index 0000000..d695a6e --- /dev/null +++ b/Blink/Blink_iOS/Brainstorming/ViewModels/BrainstormingViewModel.swift @@ -0,0 +1,90 @@ +// +// BrainstormingViewModel.swift +// Blink_iOS +// +// Created by Edgar Sgroi on 08/07/20. +// Copyright © 2020 Artur Carneiro. All rights reserved. +// + +import Foundation +import MultipeerConnectivity +import os.log + +final class BrainstormingViewModel: NSObject, ObservableObject { + + private let multipeerConnection = Multipeer.shared + @Published var topic: String = "" + + @Published var ideas: [Idea] = [Idea]() + @Published var shouldVote: Bool = false + private var shouldDelegate: Bool? + + override init() { + super.init() + if let _ = shouldDelegate { + os_log("BrainstormingViewModel initialized.", log: .multipeer, type: .info) + } else { + multipeerConnection.mcSession.delegate = self + os_log("BrainstormingViewModel initialized as MCSession's delegate.", log: .multipeer, type: .info) + } + + } + + func sendIdea(_ content: String) { + let idea = Idea(content: content) + let mcSession = multipeerConnection.mcSession + if mcSession.connectedPeers.count > 0 { + do { + let data = try JSONEncoder().encode(idea) + try mcSession.send(data, toPeers: mcSession.connectedPeers, with: .reliable) + os_log("Idea sent to mediator", log: .brainstorm, type: .info) + } catch _ as EncodingError { + os_log("Failed to encode idea as JSON.", log: OSLog.brainstorm, type: .error) + } catch _ as NSError { + os_log("Failed to send data to mediator.", log: OSLog.brainstorm, type: .error) + } + } + } +} +extension BrainstormingViewModel: MCSessionDelegate { + func session(_ session: MCSession, peer peerID: MCPeerID, didChange state: MCSessionState) { + switch state { + case MCSessionState.connected: + multipeerConnection.connectionStatus = .connected + case MCSessionState.connecting: + multipeerConnection.connectionStatus = .connecting + case MCSessionState.notConnected: + multipeerConnection.connectionStatus = .notConnected + @unknown default: + multipeerConnection.connectionStatus = .unknown + } + } + + func session(_ session: MCSession, didReceive data: Data, fromPeer peerID: MCPeerID) { + DispatchQueue.main.async { + do { + let ideas = try JSONDecoder().decode([Idea].self, from: data) + self.ideas = ideas + self.shouldVote = true + self.shouldDelegate = false + os_log("Ideas received. Moving on to voting.", log: .brainstorm, type: .info) + } catch { + os_log("Failed to decode ideas from Mediator to be voted on", log: .voting, type: .error) + } + } + } + + func session(_ session: MCSession, didReceive stream: InputStream, withName streamName: String, fromPeer peerID: MCPeerID) { + + } + + func session(_ session: MCSession, didStartReceivingResourceWithName resourceName: String, fromPeer peerID: MCPeerID, with progress: Progress) { + + } + + func session(_ session: MCSession, didFinishReceivingResourceWithName resourceName: String, fromPeer peerID: MCPeerID, at localURL: URL?, withError error: Error?) { + + } + + +} diff --git a/Blink/Blink_iOS/Brainstorming/Views/BrainstormingView.swift b/Blink/Blink_iOS/Brainstorming/Views/BrainstormingView.swift new file mode 100644 index 0000000..ac2dd3d --- /dev/null +++ b/Blink/Blink_iOS/Brainstorming/Views/BrainstormingView.swift @@ -0,0 +1,82 @@ +// +// BrainstormingView.swift +// Blink_iOS +// +// Created by Artur Carneiro on 14/07/20. +// Copyright © 2020 Artur Carneiro. All rights reserved. +// + +import SwiftUI + +struct BrainstormingView: View { + + @ObservedObject var viewmodel: BrainstormingViewModel + @State private var newIdea: String = "" + /// Bool type variable that controls the showing of the alert box. + /// This alert box is a placeholder feedback. It'll only be used as long + /// as there isn't a more suitable option. + @State private var showIdeaFeedback: Bool = false + /// Bool type variable that refreshes the placeholder text once + /// an idea is sent through the press of the button. + @State private var refreshPlaceholder: Bool = false + /// Binding that has the reference for the State var on the MenuView + @Binding var hasStarted: Bool + + + var body: some View { + VStack { + Spacer() + Text("Brainstorming") + .foregroundColor(Color("Background")) + .font(.system(.title, design: .rounded)) + .bold() + .frame(height: UIScreen.main.bounds.height * 0.2) + + TextField("Idea" + (refreshPlaceholder ? "" : " "), text: $newIdea) + .frame(width: UIScreen.main.bounds.width * 0.8) + .padding() + .background(Color("Background")) + .cornerRadius(15) + .shadow(radius: 5, y: 15) + .foregroundColor(Color("Accent")) + + if !newIdea.isEmpty { + Spacer() + Button(action: { + self.viewmodel.sendIdea(self.newIdea) + self.showIdeaFeedback.toggle() + + }) { + Text("Send") + .font(.system(.headline, design: .rounded)) + .bold() + .foregroundColor(Color("Accent")) + } + .frame(width: UIScreen.main.bounds.width * 0.4, + height: UIScreen.main.bounds.height * 0.08) + .background(Color("Background")) + .cornerRadius(15) + .shadow(radius: 5, y: 15) + /// Alert that is created to serve as feedback to inform the user + /// that an idea was sent to the TV. + .alert(isPresented: $showIdeaFeedback) { + Alert(title: Text("Idea Sent!"), message: Text("Your idea was sent to the TV."), dismissButton: .default(Text("Ok!")) { + /// Here the TextField is cleared and the placeholder is refreshed. + self.newIdea = "" + self.refreshPlaceholder.toggle() + }) + } + } + + Spacer(minLength: UIScreen.main.bounds.height * 0.5) + + if viewmodel.shouldVote { + NavigationLink(destination: VotingView(viewmodel: VotingViewModel(ideas: self.viewmodel.ideas), hasStarted: $hasStarted), isActive: self.$viewmodel.shouldVote, label: {EmptyView().navigationBarItems(trailing: Text("Vote"))}).isDetailLink(false) + } + }.navigationBarBackButtonHidden(true) + .frame(width: UIScreen.main.bounds.width) + .background(Color("Accent")) + .edgesIgnoringSafeArea(.bottom) + } +} + diff --git a/Blink/Blink_iOS/Idea.swift b/Blink/Blink_iOS/Idea.swift new file mode 100644 index 0000000..43e7cb5 --- /dev/null +++ b/Blink/Blink_iOS/Idea.swift @@ -0,0 +1,26 @@ +// +// Idea.swift +// Blink_iOS +// +// Created by Victor Falcetta do Nascimento on 14/07/20. +// Copyright © 2020 Artur Carneiro. All rights reserved. +// + +import Foundation + +struct Idea: Identifiable { + + let id: UUID = UUID() + var content: String + var isSelected: Bool = false + var votes: Int = 0 + var position: Int = 1 +} + +extension Idea: Codable {} +extension Idea: Hashable {} +extension Idea: Equatable { + static func ==(lhs: Idea, rhs: Idea) -> Bool { + return lhs.id == rhs.id && lhs.content == rhs.content && lhs.isSelected == rhs.isSelected && lhs.votes == rhs.votes && lhs.position == rhs.position + } +} diff --git a/Blink/Blink_iOS/Info.plist b/Blink/Blink_iOS/Info.plist new file mode 100644 index 0000000..d6c288d --- /dev/null +++ b/Blink/Blink_iOS/Info.plist @@ -0,0 +1,58 @@ + + + + + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + $(PRODUCT_BUNDLE_PACKAGE_TYPE) + CFBundleShortVersionString + $(MARKETING_VERSION) + CFBundleVersion + $(CURRENT_PROJECT_VERSION) + LSRequiresIPhoneOS + + UIApplicationSceneManifest + + UIApplicationSupportsMultipleScenes + + UISceneConfigurations + + UIWindowSceneSessionRoleApplication + + + UISceneConfigurationName + Default Configuration + UISceneDelegateClassName + $(PRODUCT_MODULE_NAME).SceneDelegate + + + + + UILaunchStoryboardName + LaunchScreen + UIRequiredDeviceCapabilities + + armv7 + + UISupportedInterfaceOrientations + + UIInterfaceOrientationPortrait + + UISupportedInterfaceOrientations~ipad + + UIInterfaceOrientationPortrait + UIInterfaceOrientationPortraitUpsideDown + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + + diff --git a/Blink/Blink_iOS/Menu/View/Browser.swift b/Blink/Blink_iOS/Menu/View/Browser.swift new file mode 100644 index 0000000..b3e7647 --- /dev/null +++ b/Blink/Blink_iOS/Menu/View/Browser.swift @@ -0,0 +1,27 @@ +// +// Browser.swift +// Blink_iOS +// +// Created by Edgar Sgroi on 13/07/20. +// Copyright © 2020 Artur Carneiro. All rights reserved. +// + +import Foundation +import MultipeerConnectivity +import SwiftUI + + +struct Browser: UIViewControllerRepresentable { + var delegate: MenuViewModel + + func makeUIViewController(context: Context) -> MCBrowserViewController { + let mcBrowser = MCBrowserViewController(serviceType: "blnk", session: Multipeer.shared.mcSession) + mcBrowser.delegate = self.delegate + return mcBrowser + } + + func updateUIViewController(_ uiViewController: MCBrowserViewController, context: Context) { + } + + typealias UIViewControllerType = MCBrowserViewController +} diff --git a/Blink/Blink_iOS/Menu/View/MenuView.swift b/Blink/Blink_iOS/Menu/View/MenuView.swift new file mode 100644 index 0000000..12f4143 --- /dev/null +++ b/Blink/Blink_iOS/Menu/View/MenuView.swift @@ -0,0 +1,31 @@ +// +// MenuView.swift +// Blink_iOS +// +// Created by Edgar Sgroi on 13/07/20. +// Copyright © 2020 Artur Carneiro. All rights reserved. +// + +import SwiftUI +import MultipeerConnectivity + +struct MenuView: View { + @ObservedObject var viewmodel: MenuViewModel + @State private var showingBroswer = false + + /// The body of a `MenuView`. + var body: some View { + HStack(alignment: .center) { + + /// The Button responsible for starting the session. + Button(action: { + self.showingBroswer = true + }) { + Image(systemName: "play") + } + } + .sheet(isPresented: $showingBroswer, content: { + Browser() + }) + } +} diff --git a/Blink/Blink_iOS/Menu/ViewModels/MenuViewModel.swift b/Blink/Blink_iOS/Menu/ViewModels/MenuViewModel.swift new file mode 100644 index 0000000..45c4178 --- /dev/null +++ b/Blink/Blink_iOS/Menu/ViewModels/MenuViewModel.swift @@ -0,0 +1,73 @@ +// +// MenuViewModel.swift +// Blink_iOS +// +// Created by Edgar Sgroi on 08/07/20. +// Copyright © 2020 Artur Carneiro. All rights reserved. +// + +import Foundation +import MultipeerConnectivity +import os.log + +final class MenuViewModel: NSObject, ObservableObject { + + //TODO - Pesquisar como usar o Browser no SwiftUI + + let multipeerConnection = Multipeer.shared + + @Published var isConnected: Bool = false + @Published var isJoining: Bool = false + + typealias MCBrowserViewControllerType = MCBrowserViewController + + override init() { + super.init() + multipeerConnection.delegate = self + + os_log("MenuViewModel initialized as MCSession's delegate.", log: .multipeer, type: .info) + } + +} + +extension MenuViewModel: MCSessionDelegate { + func session(_ session: MCSession, peer peerID: MCPeerID, didChange state: MCSessionState) { + switch state { + case MCSessionState.connected: + multipeerConnection.connectionStatus = .connected + case MCSessionState.connecting: + multipeerConnection.connectionStatus = .connecting + case MCSessionState.notConnected: + multipeerConnection.connectionStatus = .notConnected + @unknown default: + multipeerConnection.connectionStatus = .unknown + } + } + + func session(_ session: MCSession, didReceive data: Data, fromPeer peerID: MCPeerID) { + } + + func session(_ session: MCSession, didReceive stream: InputStream, withName streamName: String, fromPeer peerID: MCPeerID) { + } + + func session(_ session: MCSession, didStartReceivingResourceWithName resourceName: String, fromPeer peerID: MCPeerID, with progress: Progress) { + } + + func session(_ session: MCSession, didFinishReceivingResourceWithName resourceName: String, fromPeer peerID: MCPeerID, at localURL: URL?, withError error: Error?) { + } +} + +extension MenuViewModel: MCBrowserViewControllerDelegate { + func browserViewControllerDidFinish(_ browserViewController: MCBrowserViewController) { + browserViewController.dismiss(animated: true) { [weak self] in + self?.isConnected.toggle() + } + } + + func browserViewControllerWasCancelled(_ browserViewController: MCBrowserViewController) { + browserViewController.dismiss(animated: true) { + } + } + + +} diff --git a/Blink/Blink_iOS/Multipeer.swift b/Blink/Blink_iOS/Multipeer.swift new file mode 100644 index 0000000..1bc68ce --- /dev/null +++ b/Blink/Blink_iOS/Multipeer.swift @@ -0,0 +1,37 @@ +// +// Multipeer.swift +// Blink_iOS +// +// Created by Edgar Sgroi on 08/07/20. +// Copyright © 2020 Artur Carneiro. All rights reserved. +// + +import Foundation +import MultipeerConnectivity + +enum ConnectionStatus { + case connected + case connecting + case notConnected + case unknown +} + +final class Multipeer: NSObject { + + static let shared = Multipeer() + + private(set) var peerID: MCPeerID + private(set) var mcSession: MCSession + var mcAdvertiserAssistant: MCAdvertiserAssistant + + var connectionStatus: ConnectionStatus + var delegate: MCSessionDelegate? + + override init() { + peerID = MCPeerID(displayName: UIDevice.current.name) + mcSession = MCSession(peer: peerID, securityIdentity: nil, encryptionPreference: .required) + connectionStatus = .notConnected + mcAdvertiserAssistant = MCAdvertiserAssistant(serviceType: "blnk", discoveryInfo: nil, session: mcSession) + super.init() + } +} diff --git a/Blink/Blink_iOS/OSLog+Extensions.swift b/Blink/Blink_iOS/OSLog+Extensions.swift new file mode 100644 index 0000000..f506032 --- /dev/null +++ b/Blink/Blink_iOS/OSLog+Extensions.swift @@ -0,0 +1,20 @@ +// +// OSLog+Extensions.swift +// Blink_iOS +// +// Created by Victor Falcetta do Nascimento on 15/07/20. +// Copyright © 2020 Artur Carneiro. All rights reserved. +// + +import Foundation +import os.log + +extension OSLog { + private static var subsystem = Bundle.main.bundleIdentifier ?? "com.csfar.Blink.error" + + static let brainstorm = OSLog(subsystem: subsystem, category: "brainstorm") + static let voting = OSLog(subsystem: subsystem, category: "voting") + static let ranking = OSLog(subsystem: subsystem, category: "ranking") + static let interaction = OSLog(subsystem: subsystem, category: "interaction") + static let multipeer = OSLog(subsystem: subsystem, category: "multipeer") +} diff --git a/Blink/Blink_iOS/Preview Content/Preview Assets.xcassets/Contents.json b/Blink/Blink_iOS/Preview Content/Preview Assets.xcassets/Contents.json new file mode 100644 index 0000000..73c0059 --- /dev/null +++ b/Blink/Blink_iOS/Preview Content/Preview Assets.xcassets/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Blink/Blink_iOS/Ranking/ViewModels/RankingViewModel.swift b/Blink/Blink_iOS/Ranking/ViewModels/RankingViewModel.swift new file mode 100644 index 0000000..50fe6cc --- /dev/null +++ b/Blink/Blink_iOS/Ranking/ViewModels/RankingViewModel.swift @@ -0,0 +1,89 @@ +// +// RankingViewModel.swift +// Blink_iOS +// +// Created by Victor Falcetta do Nascimento on 15/07/20. +// Copyright © 2020 Artur Carneiro. All rights reserved. +// + +import Foundation +import MultipeerConnectivity +import os.log + +final class RankingViewModel: NSObject, ObservableObject { + + private let multipeerConnection = Multipeer.shared + + @Published var topic: String + @Published var ranking: [Idea] + + init(topic: String = "", ranking: [Idea]) { + self.topic = topic + self.ranking = ranking + super.init() + /// Gives a proper rank position to the ideas + self.ranking = giveRankingPosition(self.ranking) + multipeerConnection.mcSession.delegate = self + print(ranking) + os_log("RankingViewModel initialized as MCSession's delegate.", log: .multipeer, type: .info) + } + + /// This function gives a rank position to the idea based on + /// the amount of votes that the idea has. It has the following parameter: + /// - Parameter ideas: The Idea type array that will have its positions given. + func giveRankingPosition(_ ideas: [Idea]) -> [Idea] { + var rankedIdeas = ideas + var position = 1 + + for index in 0 ..< rankedIdeas.count { + rankedIdeas[index].position = position + if index != 0 && rankedIdeas[index].votes == rankedIdeas[index - 1].votes { + rankedIdeas[index].position = rankedIdeas[index - 1].position + } else { + position += 1 + } + } + return rankedIdeas + } + +} + +extension RankingViewModel: MCSessionDelegate { + + func session(_ session: MCSession, peer peerID: MCPeerID, didChange state: MCSessionState) { + switch state { + case MCSessionState.connected: + multipeerConnection.connectionStatus = .connected + case MCSessionState.connecting: + multipeerConnection.connectionStatus = .connecting + case MCSessionState.notConnected: + multipeerConnection.connectionStatus = .notConnected + @unknown default: + multipeerConnection.connectionStatus = .unknown + } + } + + func session(_ session: MCSession, didReceive data: Data, fromPeer peerID: MCPeerID) { + do { + let receivedIdeas = try JSONDecoder().decode([Idea].self, from: data) + self.ranking = receivedIdeas + os_log("Restarting session.", log: .ranking, type: .info) + } catch { + os_log("Could not receive ranking data from Host.", log: OSLog.ranking, type: .error) + } + } + + func session(_ session: MCSession, didReceive stream: InputStream, withName streamName: String, fromPeer peerID: MCPeerID) { + + } + + func session(_ session: MCSession, didStartReceivingResourceWithName resourceName: String, fromPeer peerID: MCPeerID, with progress: Progress) { + + } + + func session(_ session: MCSession, didFinishReceivingResourceWithName resourceName: String, fromPeer peerID: MCPeerID, at localURL: URL?, withError error: Error?) { + + } + + +} diff --git a/Blink/Blink_iOS/Ranking/Views/RankingView.swift b/Blink/Blink_iOS/Ranking/Views/RankingView.swift new file mode 100644 index 0000000..2fb18f6 --- /dev/null +++ b/Blink/Blink_iOS/Ranking/Views/RankingView.swift @@ -0,0 +1,91 @@ +// +// RankingView.swift +// Blink_iOS +// +// Created by Artur Carneiro on 14/07/20. +// Copyright © 2020 Artur Carneiro. All rights reserved. +// + +import SwiftUI + +struct RankingViewRow: View { + let position: Int + let content: String + let votes: Int + + var body: some View { + HStack { + if position == 1 { + Image(systemName: "rosette") + .font(.headline) + .foregroundColor(Color.yellow) + } else if position == 2 { + Image(systemName: "rosette") + .font(.headline) + .foregroundColor(Color.gray) + } else if position == 3 { + Image(systemName: "rosette") + .font(.headline) + .foregroundColor(Color.orange) + } else { + Image(systemName: "minus") + .foregroundColor(Color("Background")) + } + Text("\(position)") + .font(.system(.subheadline, design: .rounded)) + .foregroundColor(Color("Background")) + Text("\(content)") + .font(.system(.headline, design: .rounded)) + .foregroundColor(Color("Background")) + Spacer() + Text("\(votes)") + .font(.system(.headline, design: .rounded)) + .foregroundColor(Color("Background")) + Text("votes") + .font(.system(.body, design: .rounded)) + .foregroundColor(Color("Background")) + } + } +} + +struct RankingView: View { + @ObservedObject var viewmodel: RankingViewModel + /// Binding that has the reference for the State var on the MenuView + @Binding var hasStarted: Bool + + var body: some View { + VStack { + List { + ForEach(0 ..< viewmodel.ranking.count) { index in + RankingViewRow(position: self.viewmodel.ranking[index].position, content: self.viewmodel.ranking[index].content, votes: self.viewmodel.ranking[index].votes) + } + } + .onAppear(perform: { + UITableViewCell.appearance().backgroundColor = UIColor(named: "Accent") + UITableView.appearance().backgroundColor = UIColor(named: "Accent") + UINavigationBar.appearance().tintColor = UIColor(named: "Background") + }).navigationBarTitle(Text("Ranking").foregroundColor(Color("Background"))).navigationBarBackButtonHidden(true).padding() + + /// Restart button to go back to menu. + /// This will make it possible for the user in iOS + /// to restart their brainstorming when current one is finished. + Button(action: { + self.hasStarted.toggle() + }) { + Text("Restart") + .font(.system(.headline, design: .rounded)) + .bold() + .foregroundColor(Color("Accent")) + } + .frame(width: UIScreen.main.bounds.width * 0.4, + height: UIScreen.main.bounds.height * 0.1) + .background(Color("Background")) + .cornerRadius(15) + .shadow(radius: 5, y: 15) + + Spacer(minLength: 20) + }.background(Color("Accent")) + .foregroundColor(Color("Background")) + .edgesIgnoringSafeArea(.bottom) + } +} diff --git a/Blink/Blink_iOS/SceneDelegate.swift b/Blink/Blink_iOS/SceneDelegate.swift new file mode 100644 index 0000000..c264d15 --- /dev/null +++ b/Blink/Blink_iOS/SceneDelegate.swift @@ -0,0 +1,64 @@ +// +// SceneDelegate.swift +// Blink_iOS +// +// Created by Edgar Sgroi on 08/07/20. +// Copyright © 2020 Artur Carneiro. All rights reserved. +// + +import UIKit +import SwiftUI + +class SceneDelegate: UIResponder, UIWindowSceneDelegate { + + var window: UIWindow? + + + func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) { + // Use this method to optionally configure and attach the UIWindow `window` to the provided UIWindowScene `scene`. + // If using a storyboard, the `window` property will automatically be initialized and attached to the scene. + // This delegate does not imply the connecting scene or session are new (see `application:configurationForConnectingSceneSession` instead). + + // Create the SwiftUI view that provides the window contents.\ + let contentView = MenuView(viewmodel: MenuViewModel()) + + // Use a UIHostingController as window root view controller. + if let windowScene = scene as? UIWindowScene { + let window = UIWindow(windowScene: windowScene) + window.rootViewController = UIHostingController(rootView: contentView) + self.window = window + window.makeKeyAndVisible() + } + } + + func sceneDidDisconnect(_ scene: UIScene) { + // Called as the scene is being released by the system. + // This occurs shortly after the scene enters the background, or when its session is discarded. + // Release any resources associated with this scene that can be re-created the next time the scene connects. + // The scene may re-connect later, as its session was not neccessarily discarded (see `application:didDiscardSceneSessions` instead). + } + + func sceneDidBecomeActive(_ scene: UIScene) { + // Called when the scene has moved from an inactive state to an active state. + // Use this method to restart any tasks that were paused (or not yet started) when the scene was inactive. + } + + func sceneWillResignActive(_ scene: UIScene) { + // Called when the scene will move from an active state to an inactive state. + // This may occur due to temporary interruptions (ex. an incoming phone call). + } + + func sceneWillEnterForeground(_ scene: UIScene) { + // Called as the scene transitions from the background to the foreground. + // Use this method to undo the changes made on entering the background. + } + + func sceneDidEnterBackground(_ scene: UIScene) { + // Called as the scene transitions from the foreground to the background. + // Use this method to save data, release shared resources, and store enough scene-specific state information + // to restore the scene back to its current state. + } + + +} + diff --git a/Blink/Blink_iOS/Voting/ViewModels/VotingViewModel.swift b/Blink/Blink_iOS/Voting/ViewModels/VotingViewModel.swift new file mode 100644 index 0000000..a9e0052 --- /dev/null +++ b/Blink/Blink_iOS/Voting/ViewModels/VotingViewModel.swift @@ -0,0 +1,98 @@ +// +// VotingViewModel.swift +// Blink_iOS +// +// Created by Edgar Sgroi on 09/07/20. +// Copyright © 2020 Artur Carneiro. All rights reserved. +// + +import Foundation +import MultipeerConnectivity +import os.log + +final class VotingViewModel: NSObject, ObservableObject { + + private var multipeerConnection = Multipeer.shared + @Published var inVoting: Bool + + @Published var topic: String + @Published var ideas: [Idea] + + @Published var shouldShowRank: Bool = false + + init(ideas: [Idea], + topic: String = "") { + self.ideas = ideas + self.topic = topic + self.inVoting = true + super.init() + multipeerConnection.mcSession.delegate = self + + os_log("VotingViewModel initialized as MCSession's delegate.", log: .multipeer, type: .info) + } + + func checkVotedIdeas(_ ideas: [Idea]) { + let votedIdeas = ideas.filter { $0.isSelected == true } + sendVotes(votedIdeas) + } + + /// - TODO: Check any necessary changes on p2p for new data structure. + private func sendVotes(_ votes: [Idea]) { + let mcSession = multipeerConnection.mcSession + if mcSession.connectedPeers.count > 0 { + do { + let data = try JSONEncoder().encode(votes) + try mcSession.send(data, toPeers: mcSession.connectedPeers, with: .reliable) + + os_log("Votes sent to mediator", log: .voting, type: .info) + } catch _ as EncodingError { + os_log("Failed to encode voted ideas as JSON.", log: OSLog.voting, type: .error) + } catch _ as NSError { + os_log("Failed to send data through connected peers.", log: OSLog.voting, type: .error) + } + } + } +} + +extension VotingViewModel: MCSessionDelegate { + func session(_ session: MCSession, peer peerID: MCPeerID, didChange state: MCSessionState) { + switch state { + case MCSessionState.connected: + multipeerConnection.connectionStatus = .connected + case MCSessionState.connecting: + multipeerConnection.connectionStatus = .connecting + case MCSessionState.notConnected: + multipeerConnection.connectionStatus = .notConnected + @unknown default: + multipeerConnection.connectionStatus = .unknown + } + } + + /// - TODO: Check any necessary changes on p2p for new data structure. + func session(_ session: MCSession, didReceive data: Data, fromPeer peerID: MCPeerID) { + DispatchQueue.main.async { [weak self] in + guard let self = self else { return } + do { + if peerID.displayName.contains("Brainstorm Host") { + let receivedIdeas = try JSONDecoder().decode([Idea].self, from: data) + self.ideas = receivedIdeas + self.shouldShowRank = true + os_log("Ranking received. Moving on to ranking.", log: .voting, type: .info) + } + } catch { + os_log("Failed to decode ideas data from mediator.", log: OSLog.voting, type: .error) + } + } + } + + func session(_ session: MCSession, didReceive stream: InputStream, withName streamName: String, fromPeer peerID: MCPeerID) { + } + + func session(_ session: MCSession, didStartReceivingResourceWithName resourceName: String, fromPeer peerID: MCPeerID, with progress: Progress) { + } + + func session(_ session: MCSession, didFinishReceivingResourceWithName resourceName: String, fromPeer peerID: MCPeerID, at localURL: URL?, withError error: Error?) { + } + + +} diff --git a/Blink/Blink_iOS/Voting/Views/VotingView.swift b/Blink/Blink_iOS/Voting/Views/VotingView.swift new file mode 100644 index 0000000..df9a56d --- /dev/null +++ b/Blink/Blink_iOS/Voting/Views/VotingView.swift @@ -0,0 +1,105 @@ +// +// VotingView.swift +// Blink_iOS +// +// Created by Artur Carneiro on 14/07/20. +// Copyright © 2020 Artur Carneiro. All rights reserved. +// + +import SwiftUI + +struct VotingView: View { + @ObservedObject var viewmodel: VotingViewModel + + @State var currentlyChosen: Idea = Idea(content: "") + /// Bool type variable that controls if the voting feedback alert box + /// should appear or not. + @State var showVotingFeedback: Bool = false + /// Bool type variable that controls if the alert box that tells + /// the user if he has voted or not. + /// Bool type variable that tells if the user has voted or not. + @State var hasVotted: Bool = false + /// Binding that has the reference for the State var on the MenuView + @Binding var hasStarted: Bool + + var body: some View { + VStack { + /// Conditional to check if iOS user has votted or not. + if !hasVotted { + List { + ForEach(0 ..< viewmodel.ideas.count) { index in + HStack(alignment: .center) { + Text("\(self.viewmodel.ideas[index].content)") + .font(.system(.headline, design: .rounded)) + .foregroundColor(Color("Background")) + + Spacer() + + if self.viewmodel.ideas[index].isSelected { + Button(action: { + self.viewmodel.ideas[index].isSelected.toggle() + self.currentlyChosen = self.viewmodel.ideas[index] + self.currentlyChosen = Idea(content: "") + }, label: { + Image(systemName: "checkmark.circle.fill") + .foregroundColor(Color("Background")) + .font(.system(.headline, design: .rounded)) + }) + } else { + Button(action: { + self.viewmodel.ideas[index].isSelected.toggle() + self.currentlyChosen = self.viewmodel.ideas[index] + }, label: { + Image(systemName: "circle") + .foregroundColor(Color("Background")) + .font(.system(.headline, design: .rounded)) + }) + } + } + } + }.onAppear { + UITableView.appearance().backgroundColor = UIColor(named: "Accent") + UITableViewCell.appearance().backgroundColor = UIColor(named: "Accent") + UINavigationBar.appearance().tintColor = UIColor(named: "Background") + } + } else { + /// When user has voted, this View Content will appear while he awaits for the TV + /// to go to the Ranking phase. + + /// This Text is just a placeholder. Final design will be put in its place. + VStack(alignment: .center) { + Text("Waiting for TV Mediator to go to ranking phase.") + .font(.system(size: 22, weight: .semibold, design: .rounded)) + .foregroundColor(Color("Background")) + .padding() + } + } + /// The conditional that controls when RankingView should appear on screen. + /// It will only activate when the tv goes to its RankingView. + if viewmodel.shouldShowRank { + NavigationLink(destination: RankingView(viewmodel: RankingViewModel(topic: viewmodel.topic, ranking: viewmodel.ideas), hasStarted: $hasStarted), isActive: $viewmodel.shouldShowRank, label: {EmptyView().navigationBarItems(trailing: Text("Rank"))}).isDetailLink(false) + } + } + .frame(width: UIScreen.main.bounds.width, height: UIScreen.main.bounds.height) + .background(Color("Accent")) + + /// All the necessary setup and handling for navigation itens. + /// Elements such as NavButtons and NavTitles will be configured here + .navigationBarTitle(Text("Voting").foregroundColor(Color("Background"))).navigationBarBackButtonHidden(true).padding() + .navigationBarItems(trailing: Button("Send") { + if self.hasVotted == false { + self.viewmodel.checkVotedIdeas(self.viewmodel.ideas) + self.showVotingFeedback.toggle() + } + }.foregroundColor(Color("Background"))) + /// Alert created to provide a feedback to the user so + /// he can knows that his idea was sent. + .alert(isPresented: $showVotingFeedback) { + Alert(title: Text("Vote Sent!"), message: Text("Your vote was sent to the TV"), dismissButton: .default(Text("Ok!")) { self.hasVotted.toggle() + self.viewmodel.inVoting = false + }) + } + .edgesIgnoringSafeArea(.bottom) + } +} + diff --git a/Blink/Blink_iOSTests/Blink_iOSTests.swift b/Blink/Blink_iOSTests/Blink_iOSTests.swift new file mode 100644 index 0000000..4e51a37 --- /dev/null +++ b/Blink/Blink_iOSTests/Blink_iOSTests.swift @@ -0,0 +1,34 @@ +// +// Blink_iOSTests.swift +// Blink_iOSTests +// +// Created by Edgar Sgroi on 08/07/20. +// Copyright © 2020 Artur Carneiro. All rights reserved. +// + +import XCTest +@testable import Blink_iOS + +class Blink_iOSTests: XCTestCase { + + override func setUpWithError() throws { + // Put setup code here. This method is called before the invocation of each test method in the class. + } + + override func tearDownWithError() throws { + // Put teardown code here. This method is called after the invocation of each test method in the class. + } + + func testExample() throws { + // This is an example of a functional test case. + // Use XCTAssert and related functions to verify your tests produce the correct results. + } + + func testPerformanceExample() throws { + // This is an example of a performance test case. + self.measure { + // Put the code you want to measure the time of here. + } + } + +} diff --git a/Blink/Blink_iOSTests/Info.plist b/Blink/Blink_iOSTests/Info.plist new file mode 100644 index 0000000..64d65ca --- /dev/null +++ b/Blink/Blink_iOSTests/Info.plist @@ -0,0 +1,22 @@ + + + + + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + $(PRODUCT_BUNDLE_PACKAGE_TYPE) + CFBundleShortVersionString + 1.0 + CFBundleVersion + 1 + + diff --git a/FullIcon/iOS Icon.png b/FullIcon/iOS Icon.png new file mode 100644 index 0000000..7f6fc13 Binary files /dev/null and b/FullIcon/iOS Icon.png differ diff --git a/FullIcon/tvOS Icon.png b/FullIcon/tvOS Icon.png new file mode 100644 index 0000000..02dcd43 Binary files /dev/null and b/FullIcon/tvOS Icon.png differ diff --git a/README.md b/README.md index 3b74ecf..a708209 100644 --- a/README.md +++ b/README.md @@ -1 +1,41 @@ -# blink \ No newline at end of file +# Blink! - Brainstorming + +![BlinkLogo](https://github.com/csfar/blink/blob/dev/FullIcon/tvOS%20Icon.png) + +Carry out your brainstormings with Blink! - Brainstorming. With it you will be able to connect with up to 7 iPhones and conduct a complete brainstorming session. + +Brainstorming is a idea creation and problem resolution process commonly used in the through out the entire world. With "Blink!" you will be able to connect simultaneously with up to 7 iPhones, send ideas through your connected within the timelimit, conduct voting sessions and check out the final ranking of the most voted ideas. + +## Features + +- [x] Timer +- [x] Multipeer +- [x] Brainstorming + * Add ideas (tvOS) + * Add ideas (iOS) +- [x] Voting + * Vote on ideas (tvOS) + * Vote on ideas (iOS) +- [x] Ranking + +## Users + +### Mediator +* tvOS +* Siri Dictation +* Controls the session +* Can add ideas +* Can vote on ideas + +### Participants +* iOS +* Multipeer +* Can add ideas +* Can vote on ideas + +## Architecture + +Blink uses MVVM architecture. + +## Universal Links +App Store: https://apps.apple.com/br/app/blink-brainstorming/id1524089387