├── .gitattributes ├── .github └── workflows │ └── jekyll.yml ├── .gitignore ├── Assets ├── FluxorExplorer.png ├── MacIcon-256.png └── appstore-badge.svg ├── FluxorExplorer.xcodeproj ├── project.pbxproj ├── project.xcworkspace │ ├── contents.xcworkspacedata │ └── xcshareddata │ │ ├── IDEWorkspaceChecks.plist │ │ └── swiftpm │ │ └── Package.resolved └── xcshareddata │ └── xcschemes │ └── FluxorExplorer.xcscheme ├── FluxorExplorer ├── AppDelegate+Menu.swift ├── AppEnvironment.swift ├── Assets.xcassets │ ├── AppIcon.appiconset │ │ ├── Contents.json │ │ ├── Icon.png │ │ ├── Icon20pt.png │ │ ├── Icon20pt@2x-1.png │ │ ├── Icon20pt@2x.png │ │ ├── Icon20pt@3x.png │ │ ├── Icon29pt.png │ │ ├── Icon29pt@2x-1.png │ │ ├── Icon29pt@2x.png │ │ ├── Icon29pt@3x.png │ │ ├── Icon40pt.png │ │ ├── Icon40pt@2x-1.png │ │ ├── Icon40pt@2x.png │ │ ├── Icon40pt@3x.png │ │ ├── Icon60pt@2x.png │ │ ├── Icon60pt@3x.png │ │ ├── Icon76pt.png │ │ ├── Icon76pt@2x.png │ │ ├── Icon83.5@2x.png │ │ ├── MacIcon-128.png │ │ ├── MacIcon-128@2x.png │ │ ├── MacIcon-16.png │ │ ├── MacIcon-16@2x.png │ │ ├── MacIcon-256.png │ │ ├── MacIcon-256@2x.png │ │ ├── MacIcon-32.png │ │ ├── MacIcon-32@2x.png │ │ ├── MacIcon.png │ │ └── MacIcon@2x.png │ └── Contents.json ├── Base.lproj │ └── LaunchScreen.storyboard ├── Extensions │ ├── MCPeerID+Extensions.swift │ └── UIDevice+Mac.swift ├── FluxorExplorer.entitlements ├── FluxorExplorerApp.swift ├── Info.plist ├── MockData.swift ├── Preview Content │ └── Preview Assets.xcassets │ │ └── Contents.json ├── SessionHandler.swift ├── Store │ └── App │ │ ├── Actions.swift │ │ ├── AppEffects.swift │ │ ├── AppReducers.swift │ │ ├── AppSelectors.swift │ │ └── AppState.swift └── Views │ ├── DataStructureView.swift │ ├── PeersView.swift │ ├── SnapshotView.swift │ └── SnapshotsView.swift ├── FluxorExplorerTests ├── Extensions │ └── MCPeerIDExtensionsTests.swift ├── Info.plist ├── SessionHandlerTests.swift ├── Store │ ├── AppEffectsTests.swift │ ├── AppReducersTests.swift │ └── AppSelectorsTests.swift ├── Views │ ├── PeersViewTests.swift │ ├── SnapshotViewTests.swift │ ├── SnapshotsViewTests.swift │ └── ViewTestCase.swift └── Waiting.swift ├── FluxorExplorerUITests ├── FluxorExplorerUITests.swift └── Info.plist ├── Gemfile ├── Gemfile.lock ├── LICENSE ├── README.md ├── _config.yml ├── _layouts └── default.html └── assets └── css └── style.scss /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | -------------------------------------------------------------------------------- /.github/workflows/jekyll.yml: -------------------------------------------------------------------------------- 1 | name: Build and Deploy to Pages 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | 8 | jobs: 9 | build: 10 | runs-on: ubuntu-latest 11 | steps: 12 | - uses: actions/checkout@v3 13 | - name: Fix paths 14 | run: mv Assets assets 15 | - name: Build and Deploy 16 | uses: helaili/jekyll-action@2.3.1 17 | with: 18 | token: ${{ secrets.JEKYLL_PAT }} 19 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | xcuserdata 2 | .DS_Store 3 | -------------------------------------------------------------------------------- /Assets/FluxorExplorer.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FluxorOrg/FluxorExplorer/a7ed6253da3d2e763841ff9fc6b5ad7395f598a9/Assets/FluxorExplorer.png -------------------------------------------------------------------------------- /Assets/MacIcon-256.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FluxorOrg/FluxorExplorer/a7ed6253da3d2e763841ff9fc6b5ad7395f598a9/Assets/MacIcon-256.png -------------------------------------------------------------------------------- /Assets/appstore-badge.svg: -------------------------------------------------------------------------------- 1 | 2 | Download_on_the_App_Store_Badge_US-UK_RGB_blk_4SVG_092917 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | -------------------------------------------------------------------------------- /FluxorExplorer.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 52; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 5A13400B23D79B8F00B04A5E /* UIDevice+Mac.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5A13400A23D79B8F00B04A5E /* UIDevice+Mac.swift */; }; 11 | 5A22F4A0273F23680052BE1D /* FluxorExplorerApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5A22F49F273F23680052BE1D /* FluxorExplorerApp.swift */; }; 12 | 5A22F4A32741A8E60052BE1D /* ViewTestCase.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5A22F4A22741A8E60052BE1D /* ViewTestCase.swift */; }; 13 | 5A24029F237B5E3500AD7007 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 5A24029E237B5E3500AD7007 /* Assets.xcassets */; }; 14 | 5A2402A2237B5E3500AD7007 /* Preview Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 5A2402A1237B5E3500AD7007 /* Preview Assets.xcassets */; }; 15 | 5A2402A5237B5E3500AD7007 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 5A2402A3237B5E3500AD7007 /* LaunchScreen.storyboard */; }; 16 | 5A2FD681242D671A004F4DDB /* MCPeerID+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5A2FD680242D671A004F4DDB /* MCPeerID+Extensions.swift */; }; 17 | 5A3006DE243D1FD800B2B396 /* SnapshotViewTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5A3006DD243D1FD800B2B396 /* SnapshotViewTests.swift */; }; 18 | 5A3006E2243DE5AC00B2B396 /* MockData.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5A3006E1243DE5AC00B2B396 /* MockData.swift */; }; 19 | 5A32DA9423D11C2700470638 /* PeersView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5A32DA9323D11C2700470638 /* PeersView.swift */; }; 20 | 5A3F4F91243B53D500F76D00 /* SnapshotsViewTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5A3F4F90243B53D500F76D00 /* SnapshotsViewTests.swift */; }; 21 | 5A3FF57B23CBBAB60010B160 /* DataStructureView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5A3FF57A23CBBAB60010B160 /* DataStructureView.swift */; }; 22 | 5A3FF57D23CE5C560010B160 /* AppSelectors.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5A3FF57C23CE5C560010B160 /* AppSelectors.swift */; }; 23 | 5A6A837D23ABB76B00617693 /* AppEnvironment.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5A6A837C23ABB76B00617693 /* AppEnvironment.swift */; }; 24 | 5A6A837F23ABB91000617693 /* SessionHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5A6A837E23ABB91000617693 /* SessionHandler.swift */; }; 25 | 5A6A838223ABB9BF00617693 /* AppState.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5A6A838123ABB9BF00617693 /* AppState.swift */; }; 26 | 5A6A838423AC09E100617693 /* AppReducers.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5A6A838323AC09E100617693 /* AppReducers.swift */; }; 27 | 5A6A838623AC09EE00617693 /* Actions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5A6A838523AC09EE00617693 /* Actions.swift */; }; 28 | 5A80D84923D4ED8A00940802 /* AppEffects.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5A80D84823D4ED8A00940802 /* AppEffects.swift */; }; 29 | 5A903188242D697F00E18C57 /* AppReducersTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5A903187242D697F00E18C57 /* AppReducersTests.swift */; }; 30 | 5A90318A242D6B9700E18C57 /* AppSelectorsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5A903189242D6B9700E18C57 /* AppSelectorsTests.swift */; }; 31 | 5AA6C7A52439AF4C00ACAD74 /* AppEffectsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5AA6C7A42439AF4C00ACAD74 /* AppEffectsTests.swift */; }; 32 | 5AA6C7A82439F9C100ACAD74 /* ViewInspector in Frameworks */ = {isa = PBXBuildFile; productRef = 5AA6C7A72439F9C100ACAD74 /* ViewInspector */; }; 33 | 5AA6C7AB2439FACA00ACAD74 /* PeersViewTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5AA6C7AA2439FACA00ACAD74 /* PeersViewTests.swift */; }; 34 | 5AAA321326B7490300C12565 /* FluxorExplorerSnapshot in Frameworks */ = {isa = PBXBuildFile; productRef = 5AAA321226B7490300C12565 /* FluxorExplorerSnapshot */; }; 35 | 5ACBDBF426B5E45100B55586 /* FluxorTestSupport in Frameworks */ = {isa = PBXBuildFile; productRef = 5ACBDBF326B5E45100B55586 /* FluxorTestSupport */; }; 36 | 5ACBDBF826B5E45100B55586 /* Fluxor in Frameworks */ = {isa = PBXBuildFile; productRef = 5ACBDBF726B5E45100B55586 /* Fluxor */; }; 37 | 5AE8AE60242C1B6F0007FD9A /* MCPeerIDExtensionsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5AE8AE5F242C1B6F0007FD9A /* MCPeerIDExtensionsTests.swift */; }; 38 | 5AF6DA2423AEC9610039D3B2 /* SnapshotsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5AF6DA2323AEC9610039D3B2 /* SnapshotsView.swift */; }; 39 | 5AF6DA2623AECC420039D3B2 /* SnapshotView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5AF6DA2523AECC420039D3B2 /* SnapshotView.swift */; }; 40 | 5AF91198243A9D0A0062578D /* SessionHandlerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5AF91197243A9D0A0062578D /* SessionHandlerTests.swift */; }; 41 | 5AF9119A243AFE710062578D /* Waiting.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5AF91199243AFE710062578D /* Waiting.swift */; }; 42 | /* End PBXBuildFile section */ 43 | 44 | /* Begin PBXContainerItemProxy section */ 45 | 5A2402AC237B5E3600AD7007 /* PBXContainerItemProxy */ = { 46 | isa = PBXContainerItemProxy; 47 | containerPortal = 5A24028D237B5E3100AD7007 /* Project object */; 48 | proxyType = 1; 49 | remoteGlobalIDString = 5A240294237B5E3100AD7007; 50 | remoteInfo = FluxorExplorer; 51 | }; 52 | /* End PBXContainerItemProxy section */ 53 | 54 | /* Begin PBXFileReference section */ 55 | 5A0CF2A02464AFC0002EF8E3 /* FluxorExplorer.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = FluxorExplorer.entitlements; sourceTree = ""; }; 56 | 5A13400A23D79B8F00B04A5E /* UIDevice+Mac.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIDevice+Mac.swift"; sourceTree = ""; }; 57 | 5A22F49F273F23680052BE1D /* FluxorExplorerApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FluxorExplorerApp.swift; sourceTree = ""; }; 58 | 5A22F4A22741A8E60052BE1D /* ViewTestCase.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ViewTestCase.swift; sourceTree = ""; }; 59 | 5A240295237B5E3100AD7007 /* FluxorExplorer.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = FluxorExplorer.app; sourceTree = BUILT_PRODUCTS_DIR; }; 60 | 5A24029E237B5E3500AD7007 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 61 | 5A2402A1237B5E3500AD7007 /* Preview Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = "Preview Assets.xcassets"; sourceTree = ""; }; 62 | 5A2402A4237B5E3500AD7007 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 63 | 5A2402A6237B5E3500AD7007 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 64 | 5A2402AB237B5E3600AD7007 /* FluxorExplorerTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = FluxorExplorerTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 65 | 5A2402B1237B5E3600AD7007 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 66 | 5A2402BA237B5E3600AD7007 /* FluxorExplorerUITests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FluxorExplorerUITests.swift; sourceTree = ""; }; 67 | 5A2402BC237B5E3600AD7007 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 68 | 5A2FD680242D671A004F4DDB /* MCPeerID+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "MCPeerID+Extensions.swift"; sourceTree = ""; }; 69 | 5A3006DD243D1FD800B2B396 /* SnapshotViewTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SnapshotViewTests.swift; sourceTree = ""; }; 70 | 5A3006E1243DE5AC00B2B396 /* MockData.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockData.swift; sourceTree = ""; }; 71 | 5A32DA9323D11C2700470638 /* PeersView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PeersView.swift; sourceTree = ""; }; 72 | 5A3F1A25249018030037F8AB /* Assets */ = {isa = PBXFileReference; lastKnownFileType = folder; path = Assets; sourceTree = ""; }; 73 | 5A3F4F90243B53D500F76D00 /* SnapshotsViewTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SnapshotsViewTests.swift; sourceTree = ""; }; 74 | 5A3FF57A23CBBAB60010B160 /* DataStructureView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DataStructureView.swift; sourceTree = ""; }; 75 | 5A3FF57C23CE5C560010B160 /* AppSelectors.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppSelectors.swift; sourceTree = ""; }; 76 | 5A5A8D66247DC30C0090C9CE /* README.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = README.md; sourceTree = ""; }; 77 | 5A5A8D68247DC30C0090C9CE /* LICENSE */ = {isa = PBXFileReference; lastKnownFileType = text; path = LICENSE; sourceTree = ""; }; 78 | 5A6A837C23ABB76B00617693 /* AppEnvironment.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppEnvironment.swift; sourceTree = ""; }; 79 | 5A6A837E23ABB91000617693 /* SessionHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SessionHandler.swift; sourceTree = ""; }; 80 | 5A6A838123ABB9BF00617693 /* AppState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppState.swift; sourceTree = ""; }; 81 | 5A6A838323AC09E100617693 /* AppReducers.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppReducers.swift; sourceTree = ""; }; 82 | 5A6A838523AC09EE00617693 /* Actions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Actions.swift; sourceTree = ""; }; 83 | 5A80D84823D4ED8A00940802 /* AppEffects.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppEffects.swift; sourceTree = ""; }; 84 | 5A903187242D697F00E18C57 /* AppReducersTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppReducersTests.swift; sourceTree = ""; }; 85 | 5A903189242D6B9700E18C57 /* AppSelectorsTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppSelectorsTests.swift; sourceTree = ""; }; 86 | 5AA6C7A42439AF4C00ACAD74 /* AppEffectsTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppEffectsTests.swift; sourceTree = ""; }; 87 | 5AA6C7AA2439FACA00ACAD74 /* PeersViewTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PeersViewTests.swift; sourceTree = ""; }; 88 | 5AE8AE5F242C1B6F0007FD9A /* MCPeerIDExtensionsTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MCPeerIDExtensionsTests.swift; sourceTree = ""; }; 89 | 5AF6DA2323AEC9610039D3B2 /* SnapshotsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SnapshotsView.swift; sourceTree = ""; }; 90 | 5AF6DA2523AECC420039D3B2 /* SnapshotView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SnapshotView.swift; sourceTree = ""; }; 91 | 5AF91197243A9D0A0062578D /* SessionHandlerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SessionHandlerTests.swift; sourceTree = ""; }; 92 | 5AF91199243AFE710062578D /* Waiting.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Waiting.swift; sourceTree = ""; }; 93 | /* End PBXFileReference section */ 94 | 95 | /* Begin PBXFrameworksBuildPhase section */ 96 | 5A240292237B5E3100AD7007 /* Frameworks */ = { 97 | isa = PBXFrameworksBuildPhase; 98 | buildActionMask = 2147483647; 99 | files = ( 100 | 5ACBDBF826B5E45100B55586 /* Fluxor in Frameworks */, 101 | 5AAA321326B7490300C12565 /* FluxorExplorerSnapshot in Frameworks */, 102 | ); 103 | runOnlyForDeploymentPostprocessing = 0; 104 | }; 105 | 5A2402A8237B5E3600AD7007 /* Frameworks */ = { 106 | isa = PBXFrameworksBuildPhase; 107 | buildActionMask = 2147483647; 108 | files = ( 109 | 5ACBDBF426B5E45100B55586 /* FluxorTestSupport in Frameworks */, 110 | 5AA6C7A82439F9C100ACAD74 /* ViewInspector in Frameworks */, 111 | ); 112 | runOnlyForDeploymentPostprocessing = 0; 113 | }; 114 | /* End PBXFrameworksBuildPhase section */ 115 | 116 | /* Begin PBXGroup section */ 117 | 5A13400923D79B8100B04A5E /* Extensions */ = { 118 | isa = PBXGroup; 119 | children = ( 120 | 5A13400A23D79B8F00B04A5E /* UIDevice+Mac.swift */, 121 | 5A2FD680242D671A004F4DDB /* MCPeerID+Extensions.swift */, 122 | ); 123 | path = Extensions; 124 | sourceTree = ""; 125 | }; 126 | 5A24028C237B5E3100AD7007 = { 127 | isa = PBXGroup; 128 | children = ( 129 | 5A240297237B5E3100AD7007 /* FluxorExplorer */, 130 | 5A2402AE237B5E3600AD7007 /* FluxorExplorerTests */, 131 | 5A2402B9237B5E3600AD7007 /* FluxorExplorerUITests */, 132 | 5AD8211026B5E0A00034BC59 /* Frameworks */, 133 | 5A240296237B5E3100AD7007 /* Products */, 134 | 5A3F1A25249018030037F8AB /* Assets */, 135 | 5A5A8D68247DC30C0090C9CE /* LICENSE */, 136 | 5A5A8D66247DC30C0090C9CE /* README.md */, 137 | ); 138 | sourceTree = ""; 139 | }; 140 | 5A240296237B5E3100AD7007 /* Products */ = { 141 | isa = PBXGroup; 142 | children = ( 143 | 5A240295237B5E3100AD7007 /* FluxorExplorer.app */, 144 | 5A2402AB237B5E3600AD7007 /* FluxorExplorerTests.xctest */, 145 | ); 146 | name = Products; 147 | sourceTree = ""; 148 | }; 149 | 5A240297237B5E3100AD7007 /* FluxorExplorer */ = { 150 | isa = PBXGroup; 151 | children = ( 152 | 5A13400923D79B8100B04A5E /* Extensions */, 153 | 5AF6DA2223AEC9540039D3B2 /* Views */, 154 | 5A6A838023ABB9B500617693 /* Store */, 155 | 5A6A837C23ABB76B00617693 /* AppEnvironment.swift */, 156 | 5A6A837E23ABB91000617693 /* SessionHandler.swift */, 157 | 5A3006E1243DE5AC00B2B396 /* MockData.swift */, 158 | 5A24029E237B5E3500AD7007 /* Assets.xcassets */, 159 | 5A2402A3237B5E3500AD7007 /* LaunchScreen.storyboard */, 160 | 5A0CF2A02464AFC0002EF8E3 /* FluxorExplorer.entitlements */, 161 | 5A2402A6237B5E3500AD7007 /* Info.plist */, 162 | 5A2402A0237B5E3500AD7007 /* Preview Content */, 163 | 5A22F49F273F23680052BE1D /* FluxorExplorerApp.swift */, 164 | ); 165 | path = FluxorExplorer; 166 | sourceTree = ""; 167 | }; 168 | 5A2402A0237B5E3500AD7007 /* Preview Content */ = { 169 | isa = PBXGroup; 170 | children = ( 171 | 5A2402A1237B5E3500AD7007 /* Preview Assets.xcassets */, 172 | ); 173 | path = "Preview Content"; 174 | sourceTree = ""; 175 | }; 176 | 5A2402AE237B5E3600AD7007 /* FluxorExplorerTests */ = { 177 | isa = PBXGroup; 178 | children = ( 179 | 5A2FD682242D6744004F4DDB /* Extensions */, 180 | 5AA6C7A92439FA4400ACAD74 /* Views */, 181 | 5AE8AE5D242C1B550007FD9A /* Store */, 182 | 5A2402B1237B5E3600AD7007 /* Info.plist */, 183 | 5AF91197243A9D0A0062578D /* SessionHandlerTests.swift */, 184 | 5AF91199243AFE710062578D /* Waiting.swift */, 185 | ); 186 | path = FluxorExplorerTests; 187 | sourceTree = ""; 188 | }; 189 | 5A2402B9237B5E3600AD7007 /* FluxorExplorerUITests */ = { 190 | isa = PBXGroup; 191 | children = ( 192 | 5A2402BA237B5E3600AD7007 /* FluxorExplorerUITests.swift */, 193 | 5A2402BC237B5E3600AD7007 /* Info.plist */, 194 | ); 195 | path = FluxorExplorerUITests; 196 | sourceTree = ""; 197 | }; 198 | 5A2FD682242D6744004F4DDB /* Extensions */ = { 199 | isa = PBXGroup; 200 | children = ( 201 | 5AE8AE5F242C1B6F0007FD9A /* MCPeerIDExtensionsTests.swift */, 202 | ); 203 | path = Extensions; 204 | sourceTree = ""; 205 | }; 206 | 5A519EDD23D7A65E00B4F1CA /* App */ = { 207 | isa = PBXGroup; 208 | children = ( 209 | 5A6A838523AC09EE00617693 /* Actions.swift */, 210 | 5A6A838123ABB9BF00617693 /* AppState.swift */, 211 | 5A6A838323AC09E100617693 /* AppReducers.swift */, 212 | 5A3FF57C23CE5C560010B160 /* AppSelectors.swift */, 213 | 5A80D84823D4ED8A00940802 /* AppEffects.swift */, 214 | ); 215 | path = App; 216 | sourceTree = ""; 217 | }; 218 | 5A6A838023ABB9B500617693 /* Store */ = { 219 | isa = PBXGroup; 220 | children = ( 221 | 5A519EDD23D7A65E00B4F1CA /* App */, 222 | ); 223 | path = Store; 224 | sourceTree = ""; 225 | }; 226 | 5AA6C7A92439FA4400ACAD74 /* Views */ = { 227 | isa = PBXGroup; 228 | children = ( 229 | 5A22F4A22741A8E60052BE1D /* ViewTestCase.swift */, 230 | 5AA6C7AA2439FACA00ACAD74 /* PeersViewTests.swift */, 231 | 5A3F4F90243B53D500F76D00 /* SnapshotsViewTests.swift */, 232 | 5A3006DD243D1FD800B2B396 /* SnapshotViewTests.swift */, 233 | ); 234 | path = Views; 235 | sourceTree = ""; 236 | }; 237 | 5AD8211026B5E0A00034BC59 /* Frameworks */ = { 238 | isa = PBXGroup; 239 | children = ( 240 | ); 241 | name = Frameworks; 242 | sourceTree = ""; 243 | }; 244 | 5AE8AE5D242C1B550007FD9A /* Store */ = { 245 | isa = PBXGroup; 246 | children = ( 247 | 5A903187242D697F00E18C57 /* AppReducersTests.swift */, 248 | 5A903189242D6B9700E18C57 /* AppSelectorsTests.swift */, 249 | 5AA6C7A42439AF4C00ACAD74 /* AppEffectsTests.swift */, 250 | ); 251 | path = Store; 252 | sourceTree = ""; 253 | }; 254 | 5AF6DA2223AEC9540039D3B2 /* Views */ = { 255 | isa = PBXGroup; 256 | children = ( 257 | 5A32DA9323D11C2700470638 /* PeersView.swift */, 258 | 5AF6DA2323AEC9610039D3B2 /* SnapshotsView.swift */, 259 | 5AF6DA2523AECC420039D3B2 /* SnapshotView.swift */, 260 | 5A3FF57A23CBBAB60010B160 /* DataStructureView.swift */, 261 | ); 262 | path = Views; 263 | sourceTree = ""; 264 | }; 265 | /* End PBXGroup section */ 266 | 267 | /* Begin PBXNativeTarget section */ 268 | 5A240294237B5E3100AD7007 /* FluxorExplorer */ = { 269 | isa = PBXNativeTarget; 270 | buildConfigurationList = 5A2402BF237B5E3600AD7007 /* Build configuration list for PBXNativeTarget "FluxorExplorer" */; 271 | buildPhases = ( 272 | 5A240291237B5E3100AD7007 /* Sources */, 273 | 5A240292237B5E3100AD7007 /* Frameworks */, 274 | 5A240293237B5E3100AD7007 /* Resources */, 275 | ); 276 | buildRules = ( 277 | ); 278 | dependencies = ( 279 | 5ACBDBFE26B5F2F200B55586 /* PBXTargetDependency */, 280 | 5ACBDBFC26B5F2EB00B55586 /* PBXTargetDependency */, 281 | 5ACBDBFA26B5F2E800B55586 /* PBXTargetDependency */, 282 | ); 283 | name = FluxorExplorer; 284 | packageProductDependencies = ( 285 | 5ACBDBF726B5E45100B55586 /* Fluxor */, 286 | 5AAA321226B7490300C12565 /* FluxorExplorerSnapshot */, 287 | ); 288 | productName = FluxorExplorer; 289 | productReference = 5A240295237B5E3100AD7007 /* FluxorExplorer.app */; 290 | productType = "com.apple.product-type.application"; 291 | }; 292 | 5A2402AA237B5E3600AD7007 /* FluxorExplorerTests */ = { 293 | isa = PBXNativeTarget; 294 | buildConfigurationList = 5A2402C2237B5E3600AD7007 /* Build configuration list for PBXNativeTarget "FluxorExplorerTests" */; 295 | buildPhases = ( 296 | 5A2402A7237B5E3600AD7007 /* Sources */, 297 | 5A2402A8237B5E3600AD7007 /* Frameworks */, 298 | 5A2402A9237B5E3600AD7007 /* Resources */, 299 | ); 300 | buildRules = ( 301 | ); 302 | dependencies = ( 303 | 5A2402AD237B5E3600AD7007 /* PBXTargetDependency */, 304 | ); 305 | name = FluxorExplorerTests; 306 | packageProductDependencies = ( 307 | 5AA6C7A72439F9C100ACAD74 /* ViewInspector */, 308 | 5ACBDBF326B5E45100B55586 /* FluxorTestSupport */, 309 | ); 310 | productName = FluxorExplorerTests; 311 | productReference = 5A2402AB237B5E3600AD7007 /* FluxorExplorerTests.xctest */; 312 | productType = "com.apple.product-type.bundle.unit-test"; 313 | }; 314 | /* End PBXNativeTarget section */ 315 | 316 | /* Begin PBXProject section */ 317 | 5A24028D237B5E3100AD7007 /* Project object */ = { 318 | isa = PBXProject; 319 | attributes = { 320 | LastSwiftUpdateCheck = 1150; 321 | LastUpgradeCheck = 1250; 322 | ORGANIZATIONNAME = MoGee; 323 | TargetAttributes = { 324 | 5A240294237B5E3100AD7007 = { 325 | CreatedOnToolsVersion = 11.2; 326 | }; 327 | 5A2402AA237B5E3600AD7007 = { 328 | CreatedOnToolsVersion = 11.2; 329 | TestTargetID = 5A240294237B5E3100AD7007; 330 | }; 331 | }; 332 | }; 333 | buildConfigurationList = 5A240290237B5E3100AD7007 /* Build configuration list for PBXProject "FluxorExplorer" */; 334 | compatibilityVersion = "Xcode 11.0"; 335 | developmentRegion = en; 336 | hasScannedForEncodings = 0; 337 | knownRegions = ( 338 | en, 339 | Base, 340 | ); 341 | mainGroup = 5A24028C237B5E3100AD7007; 342 | packageReferences = ( 343 | 5AA6C7A62439F9C100ACAD74 /* XCRemoteSwiftPackageReference "ViewInspector" */, 344 | 5ACBDBF226B5E45100B55586 /* XCRemoteSwiftPackageReference "Fluxor" */, 345 | 5AAA321126B7490300C12565 /* XCRemoteSwiftPackageReference "FluxorExplorerSnapshot" */, 346 | ); 347 | productRefGroup = 5A240296237B5E3100AD7007 /* Products */; 348 | projectDirPath = ""; 349 | projectRoot = ""; 350 | targets = ( 351 | 5A240294237B5E3100AD7007 /* FluxorExplorer */, 352 | 5A2402AA237B5E3600AD7007 /* FluxorExplorerTests */, 353 | ); 354 | }; 355 | /* End PBXProject section */ 356 | 357 | /* Begin PBXResourcesBuildPhase section */ 358 | 5A240293237B5E3100AD7007 /* Resources */ = { 359 | isa = PBXResourcesBuildPhase; 360 | buildActionMask = 2147483647; 361 | files = ( 362 | 5A2402A5237B5E3500AD7007 /* LaunchScreen.storyboard in Resources */, 363 | 5A2402A2237B5E3500AD7007 /* Preview Assets.xcassets in Resources */, 364 | 5A24029F237B5E3500AD7007 /* Assets.xcassets in Resources */, 365 | ); 366 | runOnlyForDeploymentPostprocessing = 0; 367 | }; 368 | 5A2402A9237B5E3600AD7007 /* Resources */ = { 369 | isa = PBXResourcesBuildPhase; 370 | buildActionMask = 2147483647; 371 | files = ( 372 | ); 373 | runOnlyForDeploymentPostprocessing = 0; 374 | }; 375 | /* End PBXResourcesBuildPhase section */ 376 | 377 | /* Begin PBXSourcesBuildPhase section */ 378 | 5A240291237B5E3100AD7007 /* Sources */ = { 379 | isa = PBXSourcesBuildPhase; 380 | buildActionMask = 2147483647; 381 | files = ( 382 | 5A32DA9423D11C2700470638 /* PeersView.swift in Sources */, 383 | 5A22F4A0273F23680052BE1D /* FluxorExplorerApp.swift in Sources */, 384 | 5A6A838623AC09EE00617693 /* Actions.swift in Sources */, 385 | 5A6A837D23ABB76B00617693 /* AppEnvironment.swift in Sources */, 386 | 5A2FD681242D671A004F4DDB /* MCPeerID+Extensions.swift in Sources */, 387 | 5A6A838223ABB9BF00617693 /* AppState.swift in Sources */, 388 | 5A13400B23D79B8F00B04A5E /* UIDevice+Mac.swift in Sources */, 389 | 5A3FF57B23CBBAB60010B160 /* DataStructureView.swift in Sources */, 390 | 5A3006E2243DE5AC00B2B396 /* MockData.swift in Sources */, 391 | 5A80D84923D4ED8A00940802 /* AppEffects.swift in Sources */, 392 | 5AF6DA2623AECC420039D3B2 /* SnapshotView.swift in Sources */, 393 | 5A6A837F23ABB91000617693 /* SessionHandler.swift in Sources */, 394 | 5AF6DA2423AEC9610039D3B2 /* SnapshotsView.swift in Sources */, 395 | 5A6A838423AC09E100617693 /* AppReducers.swift in Sources */, 396 | 5A3FF57D23CE5C560010B160 /* AppSelectors.swift in Sources */, 397 | ); 398 | runOnlyForDeploymentPostprocessing = 0; 399 | }; 400 | 5A2402A7237B5E3600AD7007 /* Sources */ = { 401 | isa = PBXSourcesBuildPhase; 402 | buildActionMask = 2147483647; 403 | files = ( 404 | 5A22F4A32741A8E60052BE1D /* ViewTestCase.swift in Sources */, 405 | 5AF91198243A9D0A0062578D /* SessionHandlerTests.swift in Sources */, 406 | 5A3006DE243D1FD800B2B396 /* SnapshotViewTests.swift in Sources */, 407 | 5A3F4F91243B53D500F76D00 /* SnapshotsViewTests.swift in Sources */, 408 | 5A903188242D697F00E18C57 /* AppReducersTests.swift in Sources */, 409 | 5AA6C7A52439AF4C00ACAD74 /* AppEffectsTests.swift in Sources */, 410 | 5AF9119A243AFE710062578D /* Waiting.swift in Sources */, 411 | 5A90318A242D6B9700E18C57 /* AppSelectorsTests.swift in Sources */, 412 | 5AA6C7AB2439FACA00ACAD74 /* PeersViewTests.swift in Sources */, 413 | 5AE8AE60242C1B6F0007FD9A /* MCPeerIDExtensionsTests.swift in Sources */, 414 | ); 415 | runOnlyForDeploymentPostprocessing = 0; 416 | }; 417 | /* End PBXSourcesBuildPhase section */ 418 | 419 | /* Begin PBXTargetDependency section */ 420 | 5A2402AD237B5E3600AD7007 /* PBXTargetDependency */ = { 421 | isa = PBXTargetDependency; 422 | target = 5A240294237B5E3100AD7007 /* FluxorExplorer */; 423 | targetProxy = 5A2402AC237B5E3600AD7007 /* PBXContainerItemProxy */; 424 | }; 425 | 5ACBDBFA26B5F2E800B55586 /* PBXTargetDependency */ = { 426 | isa = PBXTargetDependency; 427 | productRef = 5ACBDBF926B5F2E800B55586 /* FluxorExplorerSnapshot */; 428 | }; 429 | 5ACBDBFC26B5F2EB00B55586 /* PBXTargetDependency */ = { 430 | isa = PBXTargetDependency; 431 | productRef = 5ACBDBFB26B5F2EB00B55586 /* Fluxor */; 432 | }; 433 | 5ACBDBFE26B5F2F200B55586 /* PBXTargetDependency */ = { 434 | isa = PBXTargetDependency; 435 | productRef = 5ACBDBFD26B5F2F200B55586 /* FluxorSwiftUI */; 436 | }; 437 | /* End PBXTargetDependency section */ 438 | 439 | /* Begin PBXVariantGroup section */ 440 | 5A2402A3237B5E3500AD7007 /* LaunchScreen.storyboard */ = { 441 | isa = PBXVariantGroup; 442 | children = ( 443 | 5A2402A4237B5E3500AD7007 /* Base */, 444 | ); 445 | name = LaunchScreen.storyboard; 446 | sourceTree = ""; 447 | }; 448 | /* End PBXVariantGroup section */ 449 | 450 | /* Begin XCBuildConfiguration section */ 451 | 5A2402BD237B5E3600AD7007 /* Debug */ = { 452 | isa = XCBuildConfiguration; 453 | buildSettings = { 454 | ALWAYS_SEARCH_USER_PATHS = NO; 455 | CLANG_ANALYZER_NONNULL = YES; 456 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 457 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; 458 | CLANG_CXX_LIBRARY = "libc++"; 459 | CLANG_ENABLE_MODULES = YES; 460 | CLANG_ENABLE_OBJC_ARC = YES; 461 | CLANG_ENABLE_OBJC_WEAK = YES; 462 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 463 | CLANG_WARN_BOOL_CONVERSION = YES; 464 | CLANG_WARN_COMMA = YES; 465 | CLANG_WARN_CONSTANT_CONVERSION = YES; 466 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 467 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 468 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 469 | CLANG_WARN_EMPTY_BODY = YES; 470 | CLANG_WARN_ENUM_CONVERSION = YES; 471 | CLANG_WARN_INFINITE_RECURSION = YES; 472 | CLANG_WARN_INT_CONVERSION = YES; 473 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 474 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 475 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 476 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 477 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; 478 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 479 | CLANG_WARN_STRICT_PROTOTYPES = YES; 480 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 481 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 482 | CLANG_WARN_UNREACHABLE_CODE = YES; 483 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 484 | COPY_PHASE_STRIP = NO; 485 | DEBUG_INFORMATION_FORMAT = dwarf; 486 | DEVELOPMENT_TEAM = R7YA4RGA8U; 487 | ENABLE_STRICT_OBJC_MSGSEND = YES; 488 | ENABLE_TESTABILITY = YES; 489 | GCC_C_LANGUAGE_STANDARD = gnu11; 490 | GCC_DYNAMIC_NO_PIC = NO; 491 | GCC_NO_COMMON_BLOCKS = YES; 492 | GCC_OPTIMIZATION_LEVEL = 0; 493 | GCC_PREPROCESSOR_DEFINITIONS = ( 494 | "DEBUG=1", 495 | "$(inherited)", 496 | ); 497 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 498 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 499 | GCC_WARN_UNDECLARED_SELECTOR = YES; 500 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 501 | GCC_WARN_UNUSED_FUNCTION = YES; 502 | GCC_WARN_UNUSED_VARIABLE = YES; 503 | IPHONEOS_DEPLOYMENT_TARGET = 15.0; 504 | MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; 505 | MTL_FAST_MATH = YES; 506 | ONLY_ACTIVE_ARCH = YES; 507 | SDKROOT = iphoneos; 508 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; 509 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 510 | SWIFT_VERSION = 5.0; 511 | TARGETED_DEVICE_FAMILY = "2,6"; 512 | }; 513 | name = Debug; 514 | }; 515 | 5A2402BE237B5E3600AD7007 /* Release */ = { 516 | isa = XCBuildConfiguration; 517 | buildSettings = { 518 | ALWAYS_SEARCH_USER_PATHS = NO; 519 | CLANG_ANALYZER_NONNULL = YES; 520 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 521 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; 522 | CLANG_CXX_LIBRARY = "libc++"; 523 | CLANG_ENABLE_MODULES = YES; 524 | CLANG_ENABLE_OBJC_ARC = YES; 525 | CLANG_ENABLE_OBJC_WEAK = YES; 526 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 527 | CLANG_WARN_BOOL_CONVERSION = YES; 528 | CLANG_WARN_COMMA = YES; 529 | CLANG_WARN_CONSTANT_CONVERSION = YES; 530 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 531 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 532 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 533 | CLANG_WARN_EMPTY_BODY = YES; 534 | CLANG_WARN_ENUM_CONVERSION = YES; 535 | CLANG_WARN_INFINITE_RECURSION = YES; 536 | CLANG_WARN_INT_CONVERSION = YES; 537 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 538 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 539 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 540 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 541 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; 542 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 543 | CLANG_WARN_STRICT_PROTOTYPES = YES; 544 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 545 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 546 | CLANG_WARN_UNREACHABLE_CODE = YES; 547 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 548 | COPY_PHASE_STRIP = NO; 549 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 550 | DEVELOPMENT_TEAM = R7YA4RGA8U; 551 | ENABLE_NS_ASSERTIONS = NO; 552 | ENABLE_STRICT_OBJC_MSGSEND = YES; 553 | GCC_C_LANGUAGE_STANDARD = gnu11; 554 | GCC_NO_COMMON_BLOCKS = YES; 555 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 556 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 557 | GCC_WARN_UNDECLARED_SELECTOR = YES; 558 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 559 | GCC_WARN_UNUSED_FUNCTION = YES; 560 | GCC_WARN_UNUSED_VARIABLE = YES; 561 | IPHONEOS_DEPLOYMENT_TARGET = 15.0; 562 | MTL_ENABLE_DEBUG_INFO = NO; 563 | MTL_FAST_MATH = YES; 564 | SDKROOT = iphoneos; 565 | SWIFT_COMPILATION_MODE = wholemodule; 566 | SWIFT_OPTIMIZATION_LEVEL = "-O"; 567 | SWIFT_VERSION = 5.0; 568 | TARGETED_DEVICE_FAMILY = "2,6"; 569 | VALIDATE_PRODUCT = YES; 570 | }; 571 | name = Release; 572 | }; 573 | 5A2402C0237B5E3600AD7007 /* Debug */ = { 574 | isa = XCBuildConfiguration; 575 | buildSettings = { 576 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 577 | CODE_SIGN_ENTITLEMENTS = FluxorExplorer/FluxorExplorer.entitlements; 578 | CURRENT_PROJECT_VERSION = 1; 579 | DEVELOPMENT_ASSET_PATHS = "\"FluxorExplorer/Preview Content\""; 580 | "ENABLE_HARDENED_RUNTIME[sdk=macosx*]" = YES; 581 | ENABLE_PREVIEWS = YES; 582 | INFOPLIST_FILE = FluxorExplorer/Info.plist; 583 | LD_RUNPATH_SEARCH_PATHS = ( 584 | "$(inherited)", 585 | "@executable_path/Frameworks", 586 | ); 587 | MARKETING_VERSION = 2021.1; 588 | PRODUCT_BUNDLE_IDENTIFIER = dev.fluxor.Explorer; 589 | PRODUCT_NAME = "$(TARGET_NAME)"; 590 | SUPPORTS_MACCATALYST = YES; 591 | }; 592 | name = Debug; 593 | }; 594 | 5A2402C1237B5E3600AD7007 /* Release */ = { 595 | isa = XCBuildConfiguration; 596 | buildSettings = { 597 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 598 | CODE_SIGN_ENTITLEMENTS = FluxorExplorer/FluxorExplorer.entitlements; 599 | CURRENT_PROJECT_VERSION = 1; 600 | DEVELOPMENT_ASSET_PATHS = "\"FluxorExplorer/Preview Content\""; 601 | "ENABLE_HARDENED_RUNTIME[sdk=macosx*]" = YES; 602 | ENABLE_PREVIEWS = YES; 603 | INFOPLIST_FILE = FluxorExplorer/Info.plist; 604 | LD_RUNPATH_SEARCH_PATHS = ( 605 | "$(inherited)", 606 | "@executable_path/Frameworks", 607 | ); 608 | MARKETING_VERSION = 2021.1; 609 | PRODUCT_BUNDLE_IDENTIFIER = dev.fluxor.Explorer; 610 | PRODUCT_NAME = "$(TARGET_NAME)"; 611 | SUPPORTS_MACCATALYST = YES; 612 | }; 613 | name = Release; 614 | }; 615 | 5A2402C3237B5E3600AD7007 /* Debug */ = { 616 | isa = XCBuildConfiguration; 617 | buildSettings = { 618 | BUNDLE_LOADER = "$(TEST_HOST)"; 619 | INFOPLIST_FILE = FluxorExplorerTests/Info.plist; 620 | LD_RUNPATH_SEARCH_PATHS = ( 621 | "$(inherited)", 622 | "@executable_path/Frameworks", 623 | "@loader_path/Frameworks", 624 | ); 625 | PRODUCT_BUNDLE_IDENTIFIER = dk.MoGee.ExplorerTests; 626 | PRODUCT_NAME = "$(TARGET_NAME)"; 627 | TEST_HOST = "$(BUILT_PRODUCTS_DIR)/FluxorExplorer.app/FluxorExplorer"; 628 | }; 629 | name = Debug; 630 | }; 631 | 5A2402C4237B5E3600AD7007 /* Release */ = { 632 | isa = XCBuildConfiguration; 633 | buildSettings = { 634 | BUNDLE_LOADER = "$(TEST_HOST)"; 635 | INFOPLIST_FILE = FluxorExplorerTests/Info.plist; 636 | LD_RUNPATH_SEARCH_PATHS = ( 637 | "$(inherited)", 638 | "@executable_path/Frameworks", 639 | "@loader_path/Frameworks", 640 | ); 641 | PRODUCT_BUNDLE_IDENTIFIER = dk.MoGee.ExplorerTests; 642 | PRODUCT_NAME = "$(TARGET_NAME)"; 643 | TEST_HOST = "$(BUILT_PRODUCTS_DIR)/FluxorExplorer.app/FluxorExplorer"; 644 | }; 645 | name = Release; 646 | }; 647 | /* End XCBuildConfiguration section */ 648 | 649 | /* Begin XCConfigurationList section */ 650 | 5A240290237B5E3100AD7007 /* Build configuration list for PBXProject "FluxorExplorer" */ = { 651 | isa = XCConfigurationList; 652 | buildConfigurations = ( 653 | 5A2402BD237B5E3600AD7007 /* Debug */, 654 | 5A2402BE237B5E3600AD7007 /* Release */, 655 | ); 656 | defaultConfigurationIsVisible = 0; 657 | defaultConfigurationName = Release; 658 | }; 659 | 5A2402BF237B5E3600AD7007 /* Build configuration list for PBXNativeTarget "FluxorExplorer" */ = { 660 | isa = XCConfigurationList; 661 | buildConfigurations = ( 662 | 5A2402C0237B5E3600AD7007 /* Debug */, 663 | 5A2402C1237B5E3600AD7007 /* Release */, 664 | ); 665 | defaultConfigurationIsVisible = 0; 666 | defaultConfigurationName = Release; 667 | }; 668 | 5A2402C2237B5E3600AD7007 /* Build configuration list for PBXNativeTarget "FluxorExplorerTests" */ = { 669 | isa = XCConfigurationList; 670 | buildConfigurations = ( 671 | 5A2402C3237B5E3600AD7007 /* Debug */, 672 | 5A2402C4237B5E3600AD7007 /* Release */, 673 | ); 674 | defaultConfigurationIsVisible = 0; 675 | defaultConfigurationName = Release; 676 | }; 677 | /* End XCConfigurationList section */ 678 | 679 | /* Begin XCRemoteSwiftPackageReference section */ 680 | 5AA6C7A62439F9C100ACAD74 /* XCRemoteSwiftPackageReference "ViewInspector" */ = { 681 | isa = XCRemoteSwiftPackageReference; 682 | repositoryURL = "https://github.com/nalexn/ViewInspector"; 683 | requirement = { 684 | kind = upToNextMajorVersion; 685 | minimumVersion = 0.9.0; 686 | }; 687 | }; 688 | 5AAA321126B7490300C12565 /* XCRemoteSwiftPackageReference "FluxorExplorerSnapshot" */ = { 689 | isa = XCRemoteSwiftPackageReference; 690 | repositoryURL = "https://github.com/FluxorOrg/FluxorExplorerSnapshot"; 691 | requirement = { 692 | kind = upToNextMajorVersion; 693 | minimumVersion = 5.0.0; 694 | }; 695 | }; 696 | 5ACBDBF226B5E45100B55586 /* XCRemoteSwiftPackageReference "Fluxor" */ = { 697 | isa = XCRemoteSwiftPackageReference; 698 | repositoryURL = "https://github.com/FluxorOrg/Fluxor"; 699 | requirement = { 700 | kind = upToNextMajorVersion; 701 | minimumVersion = 5.0.1; 702 | }; 703 | }; 704 | /* End XCRemoteSwiftPackageReference section */ 705 | 706 | /* Begin XCSwiftPackageProductDependency section */ 707 | 5AA6C7A72439F9C100ACAD74 /* ViewInspector */ = { 708 | isa = XCSwiftPackageProductDependency; 709 | package = 5AA6C7A62439F9C100ACAD74 /* XCRemoteSwiftPackageReference "ViewInspector" */; 710 | productName = ViewInspector; 711 | }; 712 | 5AAA321226B7490300C12565 /* FluxorExplorerSnapshot */ = { 713 | isa = XCSwiftPackageProductDependency; 714 | package = 5AAA321126B7490300C12565 /* XCRemoteSwiftPackageReference "FluxorExplorerSnapshot" */; 715 | productName = FluxorExplorerSnapshot; 716 | }; 717 | 5ACBDBF326B5E45100B55586 /* FluxorTestSupport */ = { 718 | isa = XCSwiftPackageProductDependency; 719 | package = 5ACBDBF226B5E45100B55586 /* XCRemoteSwiftPackageReference "Fluxor" */; 720 | productName = FluxorTestSupport; 721 | }; 722 | 5ACBDBF726B5E45100B55586 /* Fluxor */ = { 723 | isa = XCSwiftPackageProductDependency; 724 | package = 5ACBDBF226B5E45100B55586 /* XCRemoteSwiftPackageReference "Fluxor" */; 725 | productName = Fluxor; 726 | }; 727 | 5ACBDBF926B5F2E800B55586 /* FluxorExplorerSnapshot */ = { 728 | isa = XCSwiftPackageProductDependency; 729 | productName = FluxorExplorerSnapshot; 730 | }; 731 | 5ACBDBFB26B5F2EB00B55586 /* Fluxor */ = { 732 | isa = XCSwiftPackageProductDependency; 733 | package = 5ACBDBF226B5E45100B55586 /* XCRemoteSwiftPackageReference "Fluxor" */; 734 | productName = Fluxor; 735 | }; 736 | 5ACBDBFD26B5F2F200B55586 /* FluxorSwiftUI */ = { 737 | isa = XCSwiftPackageProductDependency; 738 | package = 5ACBDBF226B5E45100B55586 /* XCRemoteSwiftPackageReference "Fluxor" */; 739 | productName = FluxorSwiftUI; 740 | }; 741 | /* End XCSwiftPackageProductDependency section */ 742 | }; 743 | rootObject = 5A24028D237B5E3100AD7007 /* Project object */; 744 | } 745 | -------------------------------------------------------------------------------- /FluxorExplorer.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /FluxorExplorer.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /FluxorExplorer.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved: -------------------------------------------------------------------------------- 1 | { 2 | "object": { 3 | "pins": [ 4 | { 5 | "package": "Fluxor", 6 | "repositoryURL": "https://github.com/FluxorOrg/Fluxor", 7 | "state": { 8 | "branch": null, 9 | "revision": "1d20b74287f629c6187db11d7905988fb65a3e0d", 10 | "version": "5.0.1" 11 | } 12 | }, 13 | { 14 | "package": "FluxorExplorerSnapshot", 15 | "repositoryURL": "https://github.com/FluxorOrg/FluxorExplorerSnapshot", 16 | "state": { 17 | "branch": null, 18 | "revision": "ebcc5bb6e8b8bab403f9563aae938bf71fc67e74", 19 | "version": "5.0.0" 20 | } 21 | }, 22 | { 23 | "package": "ViewInspector", 24 | "repositoryURL": "https://github.com/nalexn/ViewInspector", 25 | "state": { 26 | "branch": null, 27 | "revision": "0d546878902bdde8c4682141f848a9dc44e04ba6", 28 | "version": "0.9.0" 29 | } 30 | } 31 | ] 32 | }, 33 | "version": 1 34 | } 35 | -------------------------------------------------------------------------------- /FluxorExplorer.xcodeproj/xcshareddata/xcschemes/FluxorExplorer.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 33 | 34 | 40 | 41 | 42 | 43 | 47 | 53 | 54 | 55 | 56 | 57 | 67 | 69 | 75 | 76 | 77 | 78 | 82 | 83 | 84 | 85 | 91 | 93 | 99 | 100 | 101 | 102 | 104 | 105 | 108 | 109 | 110 | -------------------------------------------------------------------------------- /FluxorExplorer/AppDelegate+Menu.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate+Menu.swift 3 | // FluxorExplorer 4 | // 5 | // Created by Morten Bjerg Gregersen on 07/06/2020. 6 | // Copyright © 2020 MoGee. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | extension AppDelegate { 12 | override func buildMenu(with builder: UIMenuBuilder) { 13 | if builder.system == .main { 14 | builder.insertChild(showDevicesListMenu(), atEndOfMenu: .window) 15 | } 16 | } 17 | 18 | override func validate(_ command: UICommand) {} 19 | 20 | private func showDevicesListMenu() -> UIMenu { 21 | let showDevicesListCommand = 22 | UIKeyCommand(title: "Show Devices List", 23 | image: nil, 24 | action: #selector(AppDelegate.showDevicesList), 25 | input: "D", 26 | modifierFlags: .command, 27 | propertyList: nil) 28 | let showDevicesListMenu = 29 | UIMenu(title: "", 30 | image: nil, 31 | identifier: UIMenu.Identifier("dev.fluxor.Explorer.menus.showDevicesList"), 32 | options: .displayInline, 33 | children: [showDevicesListCommand]) 34 | return showDevicesListMenu 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /FluxorExplorer/AppEnvironment.swift: -------------------------------------------------------------------------------- 1 | /* 2 | * FluxorExplorer 3 | * Copyright (c) Morten Bjerg Gregersen 2020 4 | * MIT license, see LICENSE file for details 5 | */ 6 | 7 | class AppEnvironment { 8 | let sessionHandler: SessionHandlerProtocol 9 | 10 | init(sessionHandler: SessionHandlerProtocol = SessionHandler()) { 11 | self.sessionHandler = sessionHandler 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /FluxorExplorer/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "size" : "20x20", 5 | "idiom" : "iphone", 6 | "filename" : "Icon20pt@2x-1.png", 7 | "scale" : "2x" 8 | }, 9 | { 10 | "size" : "20x20", 11 | "idiom" : "iphone", 12 | "filename" : "Icon20pt@3x.png", 13 | "scale" : "3x" 14 | }, 15 | { 16 | "size" : "29x29", 17 | "idiom" : "iphone", 18 | "filename" : "Icon29pt@2x-1.png", 19 | "scale" : "2x" 20 | }, 21 | { 22 | "size" : "29x29", 23 | "idiom" : "iphone", 24 | "filename" : "Icon29pt@3x.png", 25 | "scale" : "3x" 26 | }, 27 | { 28 | "size" : "40x40", 29 | "idiom" : "iphone", 30 | "filename" : "Icon40pt@2x-1.png", 31 | "scale" : "2x" 32 | }, 33 | { 34 | "size" : "40x40", 35 | "idiom" : "iphone", 36 | "filename" : "Icon40pt@3x.png", 37 | "scale" : "3x" 38 | }, 39 | { 40 | "size" : "60x60", 41 | "idiom" : "iphone", 42 | "filename" : "Icon60pt@2x.png", 43 | "scale" : "2x" 44 | }, 45 | { 46 | "size" : "60x60", 47 | "idiom" : "iphone", 48 | "filename" : "Icon60pt@3x.png", 49 | "scale" : "3x" 50 | }, 51 | { 52 | "size" : "20x20", 53 | "idiom" : "ipad", 54 | "filename" : "Icon20pt.png", 55 | "scale" : "1x" 56 | }, 57 | { 58 | "size" : "20x20", 59 | "idiom" : "ipad", 60 | "filename" : "Icon20pt@2x.png", 61 | "scale" : "2x" 62 | }, 63 | { 64 | "size" : "29x29", 65 | "idiom" : "ipad", 66 | "filename" : "Icon29pt.png", 67 | "scale" : "1x" 68 | }, 69 | { 70 | "size" : "29x29", 71 | "idiom" : "ipad", 72 | "filename" : "Icon29pt@2x.png", 73 | "scale" : "2x" 74 | }, 75 | { 76 | "size" : "40x40", 77 | "idiom" : "ipad", 78 | "filename" : "Icon40pt.png", 79 | "scale" : "1x" 80 | }, 81 | { 82 | "size" : "40x40", 83 | "idiom" : "ipad", 84 | "filename" : "Icon40pt@2x.png", 85 | "scale" : "2x" 86 | }, 87 | { 88 | "size" : "76x76", 89 | "idiom" : "ipad", 90 | "filename" : "Icon76pt.png", 91 | "scale" : "1x" 92 | }, 93 | { 94 | "size" : "76x76", 95 | "idiom" : "ipad", 96 | "filename" : "Icon76pt@2x.png", 97 | "scale" : "2x" 98 | }, 99 | { 100 | "size" : "83.5x83.5", 101 | "idiom" : "ipad", 102 | "filename" : "Icon83.5@2x.png", 103 | "scale" : "2x" 104 | }, 105 | { 106 | "size" : "1024x1024", 107 | "idiom" : "ios-marketing", 108 | "filename" : "Icon.png", 109 | "scale" : "1x" 110 | }, 111 | { 112 | "size" : "16x16", 113 | "idiom" : "mac", 114 | "filename" : "MacIcon-16.png", 115 | "scale" : "1x" 116 | }, 117 | { 118 | "size" : "16x16", 119 | "idiom" : "mac", 120 | "filename" : "MacIcon-16@2x.png", 121 | "scale" : "2x" 122 | }, 123 | { 124 | "size" : "32x32", 125 | "idiom" : "mac", 126 | "filename" : "MacIcon-32.png", 127 | "scale" : "1x" 128 | }, 129 | { 130 | "size" : "32x32", 131 | "idiom" : "mac", 132 | "filename" : "MacIcon-32@2x.png", 133 | "scale" : "2x" 134 | }, 135 | { 136 | "size" : "128x128", 137 | "idiom" : "mac", 138 | "filename" : "MacIcon-128.png", 139 | "scale" : "1x" 140 | }, 141 | { 142 | "size" : "128x128", 143 | "idiom" : "mac", 144 | "filename" : "MacIcon-128@2x.png", 145 | "scale" : "2x" 146 | }, 147 | { 148 | "size" : "256x256", 149 | "idiom" : "mac", 150 | "filename" : "MacIcon-256.png", 151 | "scale" : "1x" 152 | }, 153 | { 154 | "size" : "256x256", 155 | "idiom" : "mac", 156 | "filename" : "MacIcon-256@2x.png", 157 | "scale" : "2x" 158 | }, 159 | { 160 | "size" : "512x512", 161 | "idiom" : "mac", 162 | "filename" : "MacIcon.png", 163 | "scale" : "1x" 164 | }, 165 | { 166 | "size" : "512x512", 167 | "idiom" : "mac", 168 | "filename" : "MacIcon@2x.png", 169 | "scale" : "2x" 170 | } 171 | ], 172 | "info" : { 173 | "version" : 1, 174 | "author" : "xcode" 175 | } 176 | } -------------------------------------------------------------------------------- /FluxorExplorer/Assets.xcassets/AppIcon.appiconset/Icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FluxorOrg/FluxorExplorer/a7ed6253da3d2e763841ff9fc6b5ad7395f598a9/FluxorExplorer/Assets.xcassets/AppIcon.appiconset/Icon.png -------------------------------------------------------------------------------- /FluxorExplorer/Assets.xcassets/AppIcon.appiconset/Icon20pt.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FluxorOrg/FluxorExplorer/a7ed6253da3d2e763841ff9fc6b5ad7395f598a9/FluxorExplorer/Assets.xcassets/AppIcon.appiconset/Icon20pt.png -------------------------------------------------------------------------------- /FluxorExplorer/Assets.xcassets/AppIcon.appiconset/Icon20pt@2x-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FluxorOrg/FluxorExplorer/a7ed6253da3d2e763841ff9fc6b5ad7395f598a9/FluxorExplorer/Assets.xcassets/AppIcon.appiconset/Icon20pt@2x-1.png -------------------------------------------------------------------------------- /FluxorExplorer/Assets.xcassets/AppIcon.appiconset/Icon20pt@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FluxorOrg/FluxorExplorer/a7ed6253da3d2e763841ff9fc6b5ad7395f598a9/FluxorExplorer/Assets.xcassets/AppIcon.appiconset/Icon20pt@2x.png -------------------------------------------------------------------------------- /FluxorExplorer/Assets.xcassets/AppIcon.appiconset/Icon20pt@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FluxorOrg/FluxorExplorer/a7ed6253da3d2e763841ff9fc6b5ad7395f598a9/FluxorExplorer/Assets.xcassets/AppIcon.appiconset/Icon20pt@3x.png -------------------------------------------------------------------------------- /FluxorExplorer/Assets.xcassets/AppIcon.appiconset/Icon29pt.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FluxorOrg/FluxorExplorer/a7ed6253da3d2e763841ff9fc6b5ad7395f598a9/FluxorExplorer/Assets.xcassets/AppIcon.appiconset/Icon29pt.png -------------------------------------------------------------------------------- /FluxorExplorer/Assets.xcassets/AppIcon.appiconset/Icon29pt@2x-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FluxorOrg/FluxorExplorer/a7ed6253da3d2e763841ff9fc6b5ad7395f598a9/FluxorExplorer/Assets.xcassets/AppIcon.appiconset/Icon29pt@2x-1.png -------------------------------------------------------------------------------- /FluxorExplorer/Assets.xcassets/AppIcon.appiconset/Icon29pt@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FluxorOrg/FluxorExplorer/a7ed6253da3d2e763841ff9fc6b5ad7395f598a9/FluxorExplorer/Assets.xcassets/AppIcon.appiconset/Icon29pt@2x.png -------------------------------------------------------------------------------- /FluxorExplorer/Assets.xcassets/AppIcon.appiconset/Icon29pt@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FluxorOrg/FluxorExplorer/a7ed6253da3d2e763841ff9fc6b5ad7395f598a9/FluxorExplorer/Assets.xcassets/AppIcon.appiconset/Icon29pt@3x.png -------------------------------------------------------------------------------- /FluxorExplorer/Assets.xcassets/AppIcon.appiconset/Icon40pt.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FluxorOrg/FluxorExplorer/a7ed6253da3d2e763841ff9fc6b5ad7395f598a9/FluxorExplorer/Assets.xcassets/AppIcon.appiconset/Icon40pt.png -------------------------------------------------------------------------------- /FluxorExplorer/Assets.xcassets/AppIcon.appiconset/Icon40pt@2x-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FluxorOrg/FluxorExplorer/a7ed6253da3d2e763841ff9fc6b5ad7395f598a9/FluxorExplorer/Assets.xcassets/AppIcon.appiconset/Icon40pt@2x-1.png -------------------------------------------------------------------------------- /FluxorExplorer/Assets.xcassets/AppIcon.appiconset/Icon40pt@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FluxorOrg/FluxorExplorer/a7ed6253da3d2e763841ff9fc6b5ad7395f598a9/FluxorExplorer/Assets.xcassets/AppIcon.appiconset/Icon40pt@2x.png -------------------------------------------------------------------------------- /FluxorExplorer/Assets.xcassets/AppIcon.appiconset/Icon40pt@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FluxorOrg/FluxorExplorer/a7ed6253da3d2e763841ff9fc6b5ad7395f598a9/FluxorExplorer/Assets.xcassets/AppIcon.appiconset/Icon40pt@3x.png -------------------------------------------------------------------------------- /FluxorExplorer/Assets.xcassets/AppIcon.appiconset/Icon60pt@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FluxorOrg/FluxorExplorer/a7ed6253da3d2e763841ff9fc6b5ad7395f598a9/FluxorExplorer/Assets.xcassets/AppIcon.appiconset/Icon60pt@2x.png -------------------------------------------------------------------------------- /FluxorExplorer/Assets.xcassets/AppIcon.appiconset/Icon60pt@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FluxorOrg/FluxorExplorer/a7ed6253da3d2e763841ff9fc6b5ad7395f598a9/FluxorExplorer/Assets.xcassets/AppIcon.appiconset/Icon60pt@3x.png -------------------------------------------------------------------------------- /FluxorExplorer/Assets.xcassets/AppIcon.appiconset/Icon76pt.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FluxorOrg/FluxorExplorer/a7ed6253da3d2e763841ff9fc6b5ad7395f598a9/FluxorExplorer/Assets.xcassets/AppIcon.appiconset/Icon76pt.png -------------------------------------------------------------------------------- /FluxorExplorer/Assets.xcassets/AppIcon.appiconset/Icon76pt@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FluxorOrg/FluxorExplorer/a7ed6253da3d2e763841ff9fc6b5ad7395f598a9/FluxorExplorer/Assets.xcassets/AppIcon.appiconset/Icon76pt@2x.png -------------------------------------------------------------------------------- /FluxorExplorer/Assets.xcassets/AppIcon.appiconset/Icon83.5@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FluxorOrg/FluxorExplorer/a7ed6253da3d2e763841ff9fc6b5ad7395f598a9/FluxorExplorer/Assets.xcassets/AppIcon.appiconset/Icon83.5@2x.png -------------------------------------------------------------------------------- /FluxorExplorer/Assets.xcassets/AppIcon.appiconset/MacIcon-128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FluxorOrg/FluxorExplorer/a7ed6253da3d2e763841ff9fc6b5ad7395f598a9/FluxorExplorer/Assets.xcassets/AppIcon.appiconset/MacIcon-128.png -------------------------------------------------------------------------------- /FluxorExplorer/Assets.xcassets/AppIcon.appiconset/MacIcon-128@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FluxorOrg/FluxorExplorer/a7ed6253da3d2e763841ff9fc6b5ad7395f598a9/FluxorExplorer/Assets.xcassets/AppIcon.appiconset/MacIcon-128@2x.png -------------------------------------------------------------------------------- /FluxorExplorer/Assets.xcassets/AppIcon.appiconset/MacIcon-16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FluxorOrg/FluxorExplorer/a7ed6253da3d2e763841ff9fc6b5ad7395f598a9/FluxorExplorer/Assets.xcassets/AppIcon.appiconset/MacIcon-16.png -------------------------------------------------------------------------------- /FluxorExplorer/Assets.xcassets/AppIcon.appiconset/MacIcon-16@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FluxorOrg/FluxorExplorer/a7ed6253da3d2e763841ff9fc6b5ad7395f598a9/FluxorExplorer/Assets.xcassets/AppIcon.appiconset/MacIcon-16@2x.png -------------------------------------------------------------------------------- /FluxorExplorer/Assets.xcassets/AppIcon.appiconset/MacIcon-256.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FluxorOrg/FluxorExplorer/a7ed6253da3d2e763841ff9fc6b5ad7395f598a9/FluxorExplorer/Assets.xcassets/AppIcon.appiconset/MacIcon-256.png -------------------------------------------------------------------------------- /FluxorExplorer/Assets.xcassets/AppIcon.appiconset/MacIcon-256@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FluxorOrg/FluxorExplorer/a7ed6253da3d2e763841ff9fc6b5ad7395f598a9/FluxorExplorer/Assets.xcassets/AppIcon.appiconset/MacIcon-256@2x.png -------------------------------------------------------------------------------- /FluxorExplorer/Assets.xcassets/AppIcon.appiconset/MacIcon-32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FluxorOrg/FluxorExplorer/a7ed6253da3d2e763841ff9fc6b5ad7395f598a9/FluxorExplorer/Assets.xcassets/AppIcon.appiconset/MacIcon-32.png -------------------------------------------------------------------------------- /FluxorExplorer/Assets.xcassets/AppIcon.appiconset/MacIcon-32@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FluxorOrg/FluxorExplorer/a7ed6253da3d2e763841ff9fc6b5ad7395f598a9/FluxorExplorer/Assets.xcassets/AppIcon.appiconset/MacIcon-32@2x.png -------------------------------------------------------------------------------- /FluxorExplorer/Assets.xcassets/AppIcon.appiconset/MacIcon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FluxorOrg/FluxorExplorer/a7ed6253da3d2e763841ff9fc6b5ad7395f598a9/FluxorExplorer/Assets.xcassets/AppIcon.appiconset/MacIcon.png -------------------------------------------------------------------------------- /FluxorExplorer/Assets.xcassets/AppIcon.appiconset/MacIcon@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FluxorOrg/FluxorExplorer/a7ed6253da3d2e763841ff9fc6b5ad7395f598a9/FluxorExplorer/Assets.xcassets/AppIcon.appiconset/MacIcon@2x.png -------------------------------------------------------------------------------- /FluxorExplorer/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } -------------------------------------------------------------------------------- /FluxorExplorer/Base.lproj/LaunchScreen.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /FluxorExplorer/Extensions/MCPeerID+Extensions.swift: -------------------------------------------------------------------------------- 1 | /* 2 | * FluxorExplorer 3 | * Copyright (c) Morten Bjerg Gregersen 2020 4 | * MIT license, see LICENSE file for details 5 | */ 6 | 7 | import MultipeerConnectivity.MCPeerID 8 | 9 | extension MCPeerID: Encodable { 10 | public func encode(to encoder: Encoder) throws { 11 | var container = encoder.container(keyedBy: CodingKeys.self) 12 | try container.encode(self.displayName, forKey: .displayName) 13 | } 14 | 15 | enum CodingKeys: String, CodingKey { 16 | case displayName 17 | } 18 | } 19 | 20 | extension MCPeerID: Comparable { 21 | public static func < (lhs: MCPeerID, rhs: MCPeerID) -> Bool { 22 | lhs.displayName < rhs.displayName 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /FluxorExplorer/Extensions/UIDevice+Mac.swift: -------------------------------------------------------------------------------- 1 | /* 2 | * FluxorExplorer 3 | * Copyright (c) Morten Bjerg Gregersen 2020 4 | * MIT license, see LICENSE file for details 5 | */ 6 | 7 | import UIKit 8 | 9 | extension UIDevice { 10 | var isMac: Bool { 11 | #if targetEnvironment(macCatalyst) 12 | return true 13 | #else 14 | return false 15 | #endif 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /FluxorExplorer/FluxorExplorer.entitlements: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | com.apple.security.app-sandbox 6 | 7 | com.apple.security.network.client 8 | 9 | com.apple.security.network.server 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /FluxorExplorer/FluxorExplorerApp.swift: -------------------------------------------------------------------------------- 1 | /* 2 | * FluxorExplorer 3 | * Copyright (c) Morten Bjerg Gregersen 2021 4 | * MIT license, see LICENSE file for details 5 | */ 6 | 7 | import Fluxor 8 | import SwiftUI 9 | 10 | @main 11 | struct FluxorExplorerApp: App { 12 | static var store: Store = { 13 | let store = Store(initialState: AppState(), environment: AppEnvironment()) 14 | store.register(reducer: appReducer) 15 | store.register(effects: AppEffects()) 16 | #if DEBUG 17 | store.register(interceptor: PrintInterceptor()) 18 | #endif 19 | return store 20 | }() 21 | 22 | static let sessionHandler = SessionHandler() 23 | 24 | var body: some Scene { 25 | WindowGroup { 26 | NavigationView { 27 | PeersView(store: FluxorExplorerApp.store) 28 | SnapshotsView(store: FluxorExplorerApp.store) 29 | Text("Select a snapshot in the list") 30 | } 31 | .onAppear { 32 | Self.store.dispatch(action: Actions.startSessionHandler()) 33 | #if DEBUG 34 | // When using scenes, the app isn't relaunched with the environment variable. 35 | // So we set a user default, to ensure the mock data is setup on subsequent launches. 36 | let defaultsKey = "STARTED_FROM_UI_TEST" 37 | if ProcessInfo.processInfo.environment["UI_TESTING"] != nil 38 | || UserDefaults.standard.bool(forKey: defaultsKey) { 39 | setupMockData() 40 | UserDefaults.standard.set(true, forKey: defaultsKey) 41 | } 42 | #endif 43 | } 44 | } 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /FluxorExplorer/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | $(PRODUCT_BUNDLE_PACKAGE_TYPE) 17 | CFBundleShortVersionString 18 | $(MARKETING_VERSION) 19 | CFBundleVersion 20 | $(CURRENT_PROJECT_VERSION) 21 | LSApplicationCategoryType 22 | public.app-category.developer-tools 23 | LSRequiresIPhoneOS 24 | 25 | UILaunchStoryboardName 26 | LaunchScreen 27 | UIRequiredDeviceCapabilities 28 | 29 | armv7 30 | 31 | UISupportedInterfaceOrientations 32 | 33 | UIInterfaceOrientationPortrait 34 | UIInterfaceOrientationLandscapeLeft 35 | UIInterfaceOrientationLandscapeRight 36 | 37 | UISupportedInterfaceOrientations~ipad 38 | 39 | UIInterfaceOrientationPortrait 40 | UIInterfaceOrientationPortraitUpsideDown 41 | UIInterfaceOrientationLandscapeLeft 42 | UIInterfaceOrientationLandscapeRight 43 | 44 | 45 | 46 | -------------------------------------------------------------------------------- /FluxorExplorer/MockData.swift: -------------------------------------------------------------------------------- 1 | /* 2 | * FluxorExplorer 3 | * Copyright (c) Morten Bjerg Gregersen 2020 4 | * MIT license, see LICENSE file for details 5 | */ 6 | 7 | #if DEBUG 8 | import Fluxor 9 | import FluxorExplorerSnapshot 10 | import MultipeerConnectivity 11 | 12 | func setupMockData() { 13 | let peer1 = MCPeerID(displayName: "iPhone 11 Pro") 14 | let peer2 = MCPeerID(displayName: "iPhone 8") 15 | let snapshots = createSnapshots() 16 | FluxorExplorerApp.store.register(reducer: Reducer( 17 | ReduceOn(ResetStateAction.self) { state, _ in 18 | state = AppState() 19 | } 20 | )) 21 | FluxorExplorerApp.store.dispatch(action: ResetStateAction()) 22 | FluxorExplorerApp.store.dispatch(action: Actions.peerConnected(payload: peer1)) 23 | FluxorExplorerApp.store.dispatch(action: Actions.peerConnected(payload: peer2)) 24 | snapshots.forEach { 25 | FluxorExplorerApp.store.dispatch(action: Actions.didReceiveSnapshot(payload: (peerId: peer1, snapshot: $0))) 26 | } 27 | } 28 | 29 | private var oldDate = Date() 30 | private var oldState = SampleAppState(todos: TodosState(loadingTodos: false)) 31 | 32 | private func createSnapshots() -> [FluxorExplorerSnapshot] { 33 | var todos = [ 34 | Todo(title: "Dispatch actions"), 35 | Todo(title: "Create effects"), 36 | Todo(title: "Select something"), 37 | Todo(title: "Intercept everything"), 38 | ] 39 | let newTodoTitle = "Buy milk" 40 | return [ 41 | createSnapshot(action: FetchTodosAction(), newState: SampleAppState(todos: TodosState(loadingTodos: true))), 42 | createSnapshot(action: DidFetchTodosAction(todos: todos), 43 | newState: SampleAppState(todos: TodosState(todos: todos, loadingTodos: false))), 44 | createSnapshot(action: CompleteTodoAction(todo: todos[0]), 45 | newState: SampleAppState(todos: TodosState(todos: { todos[0].done = true; return todos }(), 46 | loadingTodos: false))), 47 | createSnapshot(action: AddTodoAction(title: newTodoTitle), 48 | newState: SampleAppState(todos: TodosState(todos: { 49 | todos.append(Todo(title: newTodoTitle)); return todos 50 | }(), loadingTodos: false))), 51 | createSnapshot(action: CompleteTodoAction(todo: todos[1]), 52 | newState: SampleAppState(todos: TodosState(todos: { todos[1].done = true; return todos }(), 53 | loadingTodos: false))), 54 | createSnapshot(action: UncompleteTodoAction(todo: todos[1]), 55 | newState: SampleAppState(todos: TodosState(todos: { todos[1].done = false; return todos }(), 56 | loadingTodos: false))), 57 | ] 58 | } 59 | 60 | private func createSnapshot(action: Action, newState: SampleAppState) -> FluxorExplorerSnapshot { 61 | let date = oldDate.addingTimeInterval(TimeInterval.random(in: 1 ... 5)) 62 | defer { 63 | oldState = newState 64 | oldDate = date 65 | } 66 | return FluxorExplorerSnapshot(action: action, oldState: oldState, newState: newState, date: date) 67 | } 68 | 69 | private struct SampleAppState: Encodable { 70 | var todos = TodosState() 71 | } 72 | 73 | private struct TodosState: Encodable { 74 | var todos = [Todo]() 75 | var loadingTodos = false 76 | } 77 | 78 | private struct FetchTodosAction: Action {} 79 | 80 | private struct DidFetchTodosAction: Action { 81 | let todos: [Todo] 82 | } 83 | 84 | private struct AddTodoAction: Action { 85 | let title: String 86 | } 87 | 88 | private struct CompleteTodoAction: Action { 89 | let todo: Todo 90 | } 91 | 92 | private struct UncompleteTodoAction: Action { 93 | let todo: Todo 94 | } 95 | 96 | private struct DeleteTodoAction: Action { 97 | let index: Int 98 | } 99 | 100 | private struct ResetStateAction: Action {} 101 | 102 | private struct Todo: Codable { 103 | let title: String 104 | var done = false 105 | } 106 | 107 | #endif 108 | -------------------------------------------------------------------------------- /FluxorExplorer/Preview Content/Preview Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } -------------------------------------------------------------------------------- /FluxorExplorer/SessionHandler.swift: -------------------------------------------------------------------------------- 1 | /* 2 | * FluxorExplorer 3 | * Copyright (c) Morten Bjerg Gregersen 2020 4 | * MIT license, see LICENSE file for details 5 | */ 6 | 7 | import FluxorExplorerSnapshot 8 | import MultipeerConnectivity 9 | 10 | protocol SessionHandlerProtocol { 11 | func start() 12 | } 13 | 14 | class SessionHandler: NSObject, SessionHandlerProtocol { 15 | private let peer: MCPeerID 16 | private var session: MCSession? 17 | private var mcServiceBrowser: MCNearbyServiceBrowser 18 | 19 | override init() { 20 | peer = MCPeerID(displayName: UIDevice.current.name) 21 | mcServiceBrowser = MCNearbyServiceBrowser(peer: peer, serviceType: "fluxor-explorer") 22 | super.init() 23 | mcServiceBrowser.delegate = self 24 | } 25 | 26 | func start() { 27 | mcServiceBrowser.startBrowsingForPeers() 28 | } 29 | } 30 | 31 | extension SessionHandler: MCSessionDelegate { 32 | func session(_ session: MCSession, peer peerID: MCPeerID, didChange state: MCSessionState) { 33 | switch state { 34 | case .connected: 35 | DispatchQueue.main.async { 36 | FluxorExplorerApp.store.dispatch(action: Actions.peerConnected(payload: peerID)) 37 | } 38 | case .notConnected: 39 | DispatchQueue.main.async { 40 | FluxorExplorerApp.store.dispatch(action: Actions.peerDisconnected(payload: peerID)) 41 | } 42 | default: break 43 | } 44 | } 45 | 46 | func session(_ session: MCSession, didReceive data: Data, fromPeer peerID: MCPeerID) { 47 | do { 48 | let snapshot = try JSONDecoder().decode(FluxorExplorerSnapshot.self, from: data) 49 | DispatchQueue.main.async { 50 | FluxorExplorerApp.store.dispatch(action: 51 | Actions.didReceiveSnapshot(payload: (peerId: peerID, snapshot: snapshot))) 52 | } 53 | } catch {} 54 | } 55 | 56 | func session(_ session: MCSession, 57 | didReceive stream: InputStream, 58 | withName streamName: String, 59 | fromPeer peerID: MCPeerID) {} 60 | func session(_ session: MCSession, 61 | didStartReceivingResourceWithName resourceName: String, 62 | fromPeer peerID: MCPeerID, 63 | with progress: Progress) {} 64 | func session(_ session: MCSession, 65 | didFinishReceivingResourceWithName resourceName: String, 66 | fromPeer peerID: MCPeerID, 67 | at localURL: URL?, 68 | withError error: Error?) {} 69 | } 70 | 71 | extension SessionHandler: MCNearbyServiceBrowserDelegate { 72 | func browser(_ browser: MCNearbyServiceBrowser, foundPeer peerID: MCPeerID, withDiscoveryInfo info: [String: String]?) { 73 | let session = MCSession(peer: peer, securityIdentity: nil, encryptionPreference: .none) 74 | session.delegate = self 75 | browser.invitePeer(peerID, to: session, withContext: nil, timeout: 0) 76 | self.session = session 77 | } 78 | 79 | func browser(_ browser: MCNearbyServiceBrowser, lostPeer peerID: MCPeerID) {} 80 | func browser(_ browser: MCNearbyServiceBrowser, didNotStartBrowsingForPeers error: Error) {} 81 | } 82 | -------------------------------------------------------------------------------- /FluxorExplorer/Store/App/Actions.swift: -------------------------------------------------------------------------------- 1 | /* 2 | * FluxorExplorer 3 | * Copyright (c) Morten Bjerg Gregersen 2020 4 | * MIT license, see LICENSE file for details 5 | */ 6 | 7 | import Fluxor 8 | import FluxorExplorerSnapshot 9 | import MultipeerConnectivity.MCPeerID 10 | 11 | enum Actions { 12 | static let startSessionHandler = ActionTemplate(id: "Start Session Handler") 13 | static let peerConnected = ActionTemplate(id: "Peer connected", payloadType: MCPeerID.self) 14 | static let peerDisconnected = ActionTemplate(id: "Peer disconnected", payloadType: MCPeerID.self) 15 | static let selectPeer = ActionTemplate(id: "Select peer", payloadType: MCPeerID.self) 16 | static let deselectPeer = ActionTemplate(id: "Deelect peer", payloadType: MCPeerID.self) 17 | static let didReceiveSnapshot = ActionTemplate(id: "Did receive snapshot", 18 | payloadType: (peerId: MCPeerID, 19 | snapshot: FluxorExplorerSnapshot).self) 20 | static let selectSnapshot = ActionTemplate(id: "Select snapshot", 21 | payloadType: (peerId: MCPeerID?, 22 | snapshot: FluxorExplorerSnapshot).self) 23 | static let deselectSnapshot = ActionTemplate(id: "Deelect snapshot", 24 | payloadType: (peerId: MCPeerID?, 25 | snapshot: FluxorExplorerSnapshot).self) 26 | } 27 | -------------------------------------------------------------------------------- /FluxorExplorer/Store/App/AppEffects.swift: -------------------------------------------------------------------------------- 1 | /* 2 | * FluxorExplorer 3 | * Copyright (c) Morten Bjerg Gregersen 2020 4 | * MIT license, see LICENSE file for details 5 | */ 6 | 7 | import Fluxor 8 | import UIKit 9 | 10 | class AppEffects: Effects { 11 | typealias Environment = AppEnvironment 12 | 13 | let startSessionHandler = Effect.nonDispatching { actions, environment in 14 | actions 15 | .wasCreated(from: Actions.startSessionHandler) 16 | .sink { _ in 17 | environment.sessionHandler.start() 18 | } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /FluxorExplorer/Store/App/AppReducers.swift: -------------------------------------------------------------------------------- 1 | /* 2 | * FluxorExplorer 3 | * Copyright (c) Morten Bjerg Gregersen 2020 4 | * MIT license, see LICENSE file for details 5 | */ 6 | 7 | import Fluxor 8 | 9 | let appReducer = Reducer( 10 | ReduceOn(Actions.peerConnected) { state, action in 11 | state.peers[action.payload] = Peer(id: action.payload) 12 | }, 13 | ReduceOn(Actions.peerDisconnected) { state, action in 14 | state.peers.removeValue(forKey: action.payload) 15 | }, 16 | ReduceOn(Actions.selectPeer) { state, action in 17 | state.selectedPeerId = action.payload 18 | }, 19 | ReduceOn(Actions.deselectPeer) { state, action in 20 | if state.selectedPeerId == action.payload { 21 | state.selectedPeerId = nil 22 | } 23 | }, 24 | ReduceOn(Actions.didReceiveSnapshot) { state, action in 25 | guard var snapshots = state.peers[action.payload.peerId]?.snapshots else { return } 26 | snapshots.append(action.payload.snapshot) 27 | state.peers[action.payload.peerId]!.snapshots = snapshots 28 | }, 29 | ReduceOn(Actions.selectSnapshot) { state, action in 30 | guard let peerId = action.payload.peerId else { return } 31 | state.peers[peerId]?.selectedSnaphot = action.payload.snapshot 32 | }, 33 | ReduceOn(Actions.deselectSnapshot) { state, action in 34 | guard let peerId = action.payload.peerId else { return } 35 | if state.peers[peerId]?.selectedSnaphot == action.payload.snapshot { 36 | state.peers[peerId]?.selectedSnaphot = nil 37 | } 38 | } 39 | ) 40 | -------------------------------------------------------------------------------- /FluxorExplorer/Store/App/AppSelectors.swift: -------------------------------------------------------------------------------- 1 | /* 2 | * FluxorExplorer 3 | * Copyright (c) Morten Bjerg Gregersen 2020 4 | * MIT license, see LICENSE file for details 5 | */ 6 | 7 | import Fluxor 8 | 9 | struct Selectors { 10 | private static let getPeersState = Selector(keyPath: \AppState.peers) 11 | static let getSelectedPeerId = Selector(keyPath: \AppState.selectedPeerId) 12 | static let getPeers = Selector.with(getPeersState) { $0.sorted(by: { $0.key < $1.key }).map(\.value) } 13 | 14 | private static let getSelectedPeer = Selector.with( 15 | getPeersState, getSelectedPeerId) { peers, selectedPeerId -> Peer? in 16 | guard let selectedPeerId = selectedPeerId else { return nil } 17 | return peers[selectedPeerId] 18 | } 19 | 20 | static let getSelectedPeerName = Selector.with(getSelectedPeer, keyPath: \.?.name) 21 | static let getSelectedPeerSnapshots = Selector.with(getSelectedPeer, keyPath: \.?.snapshots) 22 | static let getSelectedSnapshot = Selector.with(getSelectedPeer, keyPath: \.?.selectedSnaphot) 23 | } 24 | -------------------------------------------------------------------------------- /FluxorExplorer/Store/App/AppState.swift: -------------------------------------------------------------------------------- 1 | /* 2 | * FluxorExplorer 3 | * Copyright (c) Morten Bjerg Gregersen 2020 4 | * MIT license, see LICENSE file for details 5 | */ 6 | 7 | import FluxorExplorerSnapshot 8 | import MultipeerConnectivity.MCPeerID 9 | 10 | struct AppState: Encodable { 11 | var peers = [MCPeerID: Peer]() 12 | var selectedPeerId: MCPeerID? 13 | } 14 | 15 | struct Peer: Encodable, Equatable, Identifiable { 16 | var id: MCPeerID 17 | var name: String { id.displayName } 18 | var snapshots = [FluxorExplorerSnapshot]() 19 | var selectedSnaphot: FluxorExplorerSnapshot? 20 | } 21 | -------------------------------------------------------------------------------- /FluxorExplorer/Views/DataStructureView.swift: -------------------------------------------------------------------------------- 1 | /* 2 | * FluxorExplorer 3 | * Copyright (c) Morten Bjerg Gregersen 2020 4 | * MIT license, see LICENSE file for details 5 | */ 6 | 7 | import AnyCodable 8 | import SwiftUI 9 | 10 | struct DataStructureView: View { 11 | let dataStructure: [String: AnyCodable] 12 | 13 | var body: some View { 14 | textify(dataStructure, indentation: .init(amount: 0)) 15 | .font(.custom("Lucida Console", size: 16)) 16 | } 17 | 18 | private func textify(_ dictionary: [AnyHashable: AnyCodable], 19 | indentation: Indentation, 20 | startBraceIndentation: Indentation = .init(amount: 0)) -> Text { 21 | let subindention: Indentation 22 | let endBraceIndentation: Indentation 23 | if startBraceIndentation.amount == 0 { 24 | subindention = indentation.increased 25 | endBraceIndentation = indentation 26 | } else { 27 | subindention = startBraceIndentation.increased 28 | endBraceIndentation = startBraceIndentation 29 | } 30 | let mappedDictionary = dictionary 31 | .sorted { $0.key.description < $1.key.description } 32 | .compactMap { keyValue -> Text in 33 | let (key, value) = keyValue 34 | return Text(subindention.string) 35 | // swiftlint:disable:next force_cast 36 | + createKeyText(key as! String) 37 | + textify(value: value.value, indentation: indentation, afterColon: true) 38 | } 39 | let initialText = mappedDictionary.first ?? Text("") 40 | let formattedDictionary = mappedDictionary.dropFirst().reduce(initialText) { $0 + createNormalText(",\n") + $1 } 41 | return createNormalText("\(startBraceIndentation.string){\n") 42 | + formattedDictionary 43 | + createNormalText("\n\(endBraceIndentation.string)}") 44 | } 45 | 46 | private func textify(_ array: [Any], indentation: Indentation) -> Text { 47 | let mappedArray = array.map { element -> Text in 48 | if let dictionary = element as? [AnyHashable: AnyCodable] { 49 | return textify(dictionary, indentation: indentation, startBraceIndentation: indentation.increased) 50 | } else { 51 | return textify(value: element, indentation: indentation, afterColon: false) 52 | } 53 | } 54 | let initialText = mappedArray.first ?? Text("") 55 | let formattedArray = mappedArray.dropFirst().reduce(initialText) { $0 + createNormalText(",\n") + $1 } 56 | return createNormalText("[\n") + formattedArray + createNormalText("\n\(indentation.string)]") 57 | } 58 | 59 | private func textify(value: Any, indentation: Indentation, afterColon: Bool) -> Text { 60 | let subindentation = indentation.increased 61 | let indentationText = Text(afterColon ? "" : indentation.string) 62 | let valueText: Text 63 | switch value { 64 | case let dictionaryValue as [AnyHashable: AnyCodable]: 65 | valueText = textify(dictionaryValue, indentation: subindentation) 66 | case let arrayValue as [[AnyHashable: AnyCodable]]: 67 | valueText = textify(arrayValue, indentation: subindentation) 68 | case let arrayValue as [AnyCodable]: 69 | valueText = textify(arrayValue, indentation: subindentation) 70 | case let arrayValue as [Any]: 71 | valueText = textify(arrayValue, indentation: subindentation) 72 | case let anyCodable as AnyCodable: 73 | valueText = textify(value: anyCodable.value, indentation: indentation, afterColon: afterColon) 74 | case let stringValue as String: 75 | valueText = createValueText("\"\(stringValue)\"") 76 | case let intValue as Int: 77 | valueText = createValueText("\(intValue)") 78 | case let doubleValue as Double: 79 | valueText = createValueText("\(doubleValue)") 80 | case let boolValue as Bool: 81 | valueText = createValueText("\(boolValue ? "true" : "false")") 82 | default: 83 | valueText = createValueText(String(describing: value)) 84 | } 85 | return indentationText + valueText 86 | } 87 | 88 | private func createNormalText(_ string: String) -> Text { 89 | Text(string) 90 | } 91 | 92 | private func createKeyText(_ key: String) -> Text { 93 | Text("\"\(key)\": ") 94 | .foregroundColor(Color(.systemOrange)) 95 | } 96 | 97 | private func createValueText(_ value: String) -> Text { 98 | Text(value) 99 | .foregroundColor(Color(.systemRed)) 100 | } 101 | 102 | private struct Indentation { 103 | let amount: Int 104 | var increased: Indentation { .init(amount: amount + 1) } 105 | var decreased: Indentation { .init(amount: amount - 1) } 106 | var string: String { .init(repeating: " ", count: max(amount, 0)) } 107 | } 108 | } 109 | -------------------------------------------------------------------------------- /FluxorExplorer/Views/PeersView.swift: -------------------------------------------------------------------------------- 1 | /* 2 | * FluxorExplorer 3 | * Copyright (c) Morten Bjerg Gregersen 2020 4 | * MIT license, see LICENSE file for details 5 | */ 6 | 7 | import Fluxor 8 | import MultipeerConnectivity.MCPeerID 9 | import SwiftUI 10 | 11 | struct PeersView: View { 12 | @ObservedObject var store: Store 13 | @StoreValue(FluxorExplorerApp.store, Selectors.getPeers) private var peers 14 | 15 | var body: some View { 16 | HStack { 17 | if peers.count > 0 { 18 | List(peers) { peer in 19 | let isActive = store.binding(get: Selector.with(Selectors.getSelectedPeerId, 20 | projector: { $0 == peer.id }), 21 | send: { $0 ? Actions.selectPeer(payload: peer.id) 22 | : Actions.deselectPeer(payload: peer.id) 23 | }) 24 | NavigationLink(peer.name, isActive: isActive) { 25 | SnapshotsView(store: store) 26 | } 27 | } 28 | } else { 29 | VStack { 30 | Text("No devices connected") 31 | .font(.headline) 32 | Text("Make sure the application is launched and the Interceptor is registered.") 33 | .multilineTextAlignment(.center) 34 | .padding() 35 | } 36 | } 37 | } 38 | .navigationBarTitle("Connected devices") 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /FluxorExplorer/Views/SnapshotView.swift: -------------------------------------------------------------------------------- 1 | /* 2 | * FluxorExplorer 3 | * Copyright (c) Morten Bjerg Gregersen 2020 4 | * MIT license, see LICENSE file for details 5 | */ 6 | 7 | import Fluxor 8 | import FluxorExplorerSnapshot 9 | import SwiftUI 10 | 11 | struct SnapshotView { 12 | var snapshot: FluxorExplorerSnapshot 13 | 14 | let dateFormatter: DateFormatter = { 15 | let dateFormatter = DateFormatter() 16 | dateFormatter.dateStyle = .none 17 | dateFormatter.timeStyle = .long 18 | return dateFormatter 19 | }() 20 | } 21 | 22 | extension SnapshotView: View { 23 | var body: some View { 24 | ScrollView { 25 | VStack(alignment: .leading) { 26 | Text("Time:") 27 | .font(.headline) 28 | Text(dateFormatter.string(from: snapshot.date)) 29 | Divider() 30 | .padding(.vertical) 31 | 32 | if snapshot.actionData.payload != nil { 33 | Text("Payload") 34 | .font(.headline) 35 | DataStructureView(dataStructure: snapshot.actionData.payload!) 36 | .padding(.top) 37 | Divider() 38 | .padding(.vertical) 39 | } 40 | 41 | Text("State") 42 | .font(.headline) 43 | DataStructureView(dataStructure: snapshot.newState) 44 | .padding(.top) 45 | Spacer() 46 | } 47 | } 48 | .navigationBarTitle(snapshot.actionData.name) 49 | .frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .topLeading) 50 | .padding() 51 | } 52 | } 53 | 54 | struct SnapshotView_Previews: PreviewProvider { 55 | static var previews: some View { 56 | SnapshotView(snapshot: FluxorExplorerSnapshot(action: TestAction(increment: 1295), 57 | oldState: ["counter": 42], 58 | newState: ["counter": 1337])) 59 | } 60 | 61 | struct TestAction: Action { 62 | let increment: Int 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /FluxorExplorer/Views/SnapshotsView.swift: -------------------------------------------------------------------------------- 1 | /* 2 | * FluxorExplorer 3 | * Copyright (c) Morten Bjerg Gregersen 2020 4 | * MIT license, see LICENSE file for details 5 | */ 6 | 7 | import Fluxor 8 | import FluxorExplorerSnapshot 9 | import SwiftUI 10 | 11 | struct SnapshotsView: View { 12 | @Environment(\.colorScheme) private var colorScheme: ColorScheme 13 | @ObservedObject var store: Store 14 | @StoreValue(FluxorExplorerApp.store, Selectors.getSelectedPeerId) private var peerId 15 | @StoreValue(FluxorExplorerApp.store, Selectors.getSelectedPeerSnapshots) private var snapshots 16 | 17 | func timeBackgroundColor(for colorScheme: ColorScheme) -> Color { 18 | Color(colorScheme == .dark ? .darkGray : .init(white: 0.9, alpha: 1)) 19 | } 20 | 21 | var body: some View { 22 | HStack { 23 | if let snapshots = snapshots, snapshots.count > 0 { 24 | List { 25 | ForEach(snapshots, id: \.date) { snapshot in 26 | let isActive = store.binding(get: Selector.with(Selectors.getSelectedSnapshot, 27 | projector: { $0 == snapshot }), 28 | send: { 29 | let payload = (peerId: peerId, snapshot: snapshot) 30 | return $0 ? Actions.selectSnapshot(payload: payload) 31 | : Actions.deselectSnapshot(payload: payload) 32 | }) 33 | NavigationLink(destination: SnapshotView(snapshot: snapshot), isActive: isActive) { 34 | VStack(alignment: .leading) { 35 | Text(snapshot.actionData.name) 36 | Text(snapshot.date.formatted(date: .omitted, time: .shortened)) 37 | .foregroundColor(.secondary) 38 | } 39 | } 40 | } 41 | } 42 | } else { 43 | VStack { 44 | Text("No snapshots yet") 45 | .font(.headline) 46 | } 47 | } 48 | } 49 | .navigationBarTitle("Snapshots") 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /FluxorExplorerTests/Extensions/MCPeerIDExtensionsTests.swift: -------------------------------------------------------------------------------- 1 | /* 2 | * FluxorExplorerTests 3 | * Copyright (c) Morten Bjerg Gregersen 2020 4 | * MIT license, see LICENSE file for details 5 | */ 6 | 7 | import FluxorExplorer 8 | import MultipeerConnectivity.MCPeerID 9 | import XCTest 10 | 11 | class MCPeerIDExtensionsTests: XCTestCase { 12 | func testPeerIDEncodable() throws { 13 | let peerID = MCPeerID(displayName: "Some peer") 14 | let jsonEncoder = JSONEncoder() 15 | let json = try jsonEncoder.encode(peerID) 16 | XCTAssertEqual(String(data: json, encoding: .utf8), #"{"displayName":"Some peer"}"#) 17 | } 18 | 19 | func testPeerIDComparable() { 20 | let peerID1 = MCPeerID(displayName: "Some peer") 21 | let peerID2 = MCPeerID(displayName: "Another peer") 22 | let peerID3 = MCPeerID(displayName: "Third peer") 23 | let peers = [peerID1, peerID2, peerID3] 24 | XCTAssertEqual(peers.sorted(), [peerID2, peerID1, peerID3]) 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /FluxorExplorerTests/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | $(PRODUCT_BUNDLE_PACKAGE_TYPE) 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | 1 21 | 22 | 23 | -------------------------------------------------------------------------------- /FluxorExplorerTests/SessionHandlerTests.swift: -------------------------------------------------------------------------------- 1 | /* 2 | * FluxorExplorerTests 3 | * Copyright (c) Morten Bjerg Gregersen 2020 4 | * MIT license, see LICENSE file for details 5 | */ 6 | 7 | import Fluxor 8 | @testable import FluxorExplorer 9 | import FluxorExplorerSnapshot 10 | import FluxorTestSupport 11 | import MultipeerConnectivity 12 | import XCTest 13 | 14 | // swiftlint:disable force_cast 15 | 16 | class SessionHandlerTests: XCTestCase { 17 | let peer = MCPeerID(displayName: "Some peer") 18 | let serviceType = "fluxor-explorer" 19 | var session: MCSession! 20 | var handler: SessionHandler! 21 | var store: MockStore! 22 | 23 | override func setUp() { 24 | session = .init(peer: peer) 25 | handler = .init() 26 | store = .init(initialState: .init(), environment: AppEnvironment()) 27 | FluxorExplorerApp.store = store 28 | } 29 | 30 | func testSessionConnected() { 31 | // When 32 | handler.session(session, peer: peer, didChange: .connected) 33 | // Then 34 | waitFor { 35 | XCTAssertEqual(self.store.dispatchedActions[0] as! AnonymousAction, 36 | Actions.peerConnected(payload: self.peer)) 37 | } 38 | } 39 | 40 | func testSessionNotConnected() { 41 | // When 42 | handler.session(session, peer: peer, didChange: .notConnected) 43 | // Then 44 | waitFor { 45 | XCTAssertEqual(self.store.dispatchedActions[0] as! AnonymousAction, 46 | Actions.peerDisconnected(payload: self.peer)) 47 | } 48 | } 49 | 50 | func testSessionDidReceiveData() throws { 51 | // When 52 | let snapshot = FluxorExplorerSnapshot(action: TestAction(increment: 1), 53 | oldState: TestState(counter: 42), 54 | newState: TestState(counter: 1337)) 55 | let data = try JSONEncoder().encode(snapshot) 56 | handler.session(session, didReceive: data, fromPeer: peer) 57 | // Then 58 | waitFor { 59 | let action = self.store.dispatchedActions[0] 60 | as! AnonymousAction<(peerId: MCPeerID, snapshot: FluxorExplorerSnapshot)> 61 | XCTAssertEqual(action.payload.peerId, self.peer) 62 | XCTAssertEqual(action.payload.snapshot, snapshot) 63 | } 64 | } 65 | 66 | func testBrowserFoundPeer() { 67 | // Given 68 | let browser = MockBrowser(peer: peer, serviceType: serviceType) 69 | let otherPeer = MCPeerID(displayName: "Another peer") 70 | // When 71 | handler.browser(browser, foundPeer: otherPeer, withDiscoveryInfo: nil) 72 | // Then 73 | XCTAssertEqual(browser.invitedPeer, otherPeer) 74 | } 75 | 76 | func testNoExplosion() { 77 | // Given 78 | let session = MCSession(peer: peer) 79 | let browser = MCNearbyServiceBrowser(peer: peer, serviceType: serviceType) 80 | let handler = SessionHandler() 81 | // When 82 | handler.session(session, peer: peer, didChange: .connecting) 83 | handler.session(session, didReceive: Data(), fromPeer: peer) 84 | handler.session(session, didReceive: InputStream(), withName: "", fromPeer: peer) 85 | handler.session(session, didStartReceivingResourceWithName: "", fromPeer: peer, with: Progress()) 86 | handler.session(session, didFinishReceivingResourceWithName: "", fromPeer: peer, at: nil, withError: nil) 87 | handler.browser(browser, lostPeer: peer) 88 | handler.browser(browser, didNotStartBrowsingForPeers: MCError(.notConnected)) 89 | // Then 90 | // Nothing explodes (and coverage isn't harmed) 91 | } 92 | } 93 | 94 | private class MockBrowser: MCNearbyServiceBrowser { 95 | var invitedPeer: MCPeerID? 96 | 97 | override func invitePeer(_ peerID: MCPeerID, 98 | to session: MCSession, 99 | withContext context: Data?, 100 | timeout: TimeInterval) { 101 | invitedPeer = peerID 102 | } 103 | } 104 | 105 | struct TestState: Encodable { 106 | let counter: Int 107 | } 108 | 109 | struct TestAction: Action { 110 | let increment: Int 111 | } 112 | -------------------------------------------------------------------------------- /FluxorExplorerTests/Store/AppEffectsTests.swift: -------------------------------------------------------------------------------- 1 | /* 2 | * FluxorExplorerTests 3 | * Copyright (c) Morten Bjerg Gregersen 2020 4 | * MIT license, see LICENSE file for details 5 | */ 6 | 7 | import Fluxor 8 | @testable import FluxorExplorer 9 | import FluxorExplorerSnapshot 10 | import FluxorTestSupport 11 | import MultipeerConnectivity.MCPeerID 12 | import XCTest 13 | 14 | class AppEffectsTests: XCTestCase { 15 | func testStartSessionHandler() throws { 16 | // Given 17 | let effects = AppEffects() 18 | let mockSessionHandler = MockSessionHandler() 19 | let environment = AppEnvironment(sessionHandler: mockSessionHandler) 20 | // When 21 | try EffectRunner.run(effects.startSessionHandler, 22 | with: Actions.startSessionHandler(), 23 | environment: environment) 24 | // Then 25 | XCTAssertTrue(mockSessionHandler.hasStarted) 26 | } 27 | } 28 | 29 | class MockSessionHandler: SessionHandlerProtocol { 30 | var hasStarted = false 31 | 32 | func start() { 33 | hasStarted = true 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /FluxorExplorerTests/Store/AppReducersTests.swift: -------------------------------------------------------------------------------- 1 | /* 2 | * FluxorExplorerTests 3 | * Copyright (c) Morten Bjerg Gregersen 2020 4 | * MIT license, see LICENSE file for details 5 | */ 6 | 7 | @testable import FluxorExplorer 8 | import FluxorExplorerSnapshot 9 | import MultipeerConnectivity.MCPeerID 10 | import XCTest 11 | 12 | class AppReducersTests: XCTestCase { 13 | let peerId = MCPeerID(displayName: "Some id") 14 | let snapshot1 = FluxorExplorerSnapshot(action: TestAction(increment: 1), 15 | oldState: TestState(counter: 0), 16 | newState: TestState(counter: 1)) 17 | let snapshot2 = FluxorExplorerSnapshot(action: TestAction(increment: 2), 18 | oldState: TestState(counter: 1), 19 | newState: TestState(counter: 3)) 20 | 21 | func testReceivingPeerConnected() { 22 | // Given 23 | var state = AppState() 24 | XCTAssertEqual(state.peers.count, 0) 25 | // When 26 | appReducer.reduce(&state, Actions.peerConnected(payload: peerId)) 27 | // Then 28 | XCTAssertEqual(state.peers.count, 1) 29 | XCTAssertTrue(state.peers.contains(where: { $0.key == peerId })) 30 | } 31 | 32 | func testReceivingPeerDisconnected() { 33 | // Given 34 | var state = AppState(peers: [peerId: Peer(id: peerId)]) 35 | // When 36 | appReducer.reduce(&state, Actions.peerDisconnected(payload: peerId)) 37 | // Then 38 | XCTAssertEqual(state.peers.count, 0) 39 | } 40 | 41 | func testReceivingSelectPeer() { 42 | // Given 43 | var state = AppState() 44 | // When 45 | appReducer.reduce(&state, Actions.selectPeer(payload: peerId)) 46 | // Then 47 | XCTAssertEqual(state.selectedPeerId, peerId) 48 | } 49 | 50 | func testReceivingDeselectPeer() { 51 | // Given 52 | var state = AppState(selectedPeerId: peerId) 53 | // When 54 | appReducer.reduce(&state, Actions.deselectPeer(payload: peerId)) 55 | // Then 56 | XCTAssertNil(state.selectedPeerId) 57 | } 58 | 59 | func testReceivingDidReceiveSnapshot() { 60 | // Given 61 | var state = AppState(peers: [peerId: Peer(id: peerId, snapshots: [snapshot1])]) 62 | // When 63 | appReducer.reduce(&state, Actions.didReceiveSnapshot(payload: 64 | (peerId: peerId, snapshot: snapshot2))) 65 | // Then 66 | XCTAssertEqual(state.peers[peerId]?.snapshots, [snapshot1, snapshot2]) 67 | } 68 | 69 | func testReceivingSelectSnapshot() { 70 | // Given 71 | var state = AppState(peers: [peerId: Peer(id: peerId, snapshots: [snapshot1])]) 72 | // When 73 | appReducer.reduce(&state, Actions.selectSnapshot(payload: (peerId: peerId, snapshot: snapshot1))) 74 | // Then 75 | XCTAssertEqual(state.peers[peerId]?.selectedSnaphot, snapshot1) 76 | } 77 | 78 | func testReceivingDeselectSnapshot() { 79 | // Given 80 | var state = AppState(peers: [ 81 | peerId: Peer(id: peerId, snapshots: [snapshot1, snapshot2], selectedSnaphot: snapshot1) 82 | ]) 83 | // When 84 | appReducer.reduce(&state, Actions.deselectSnapshot(payload: (peerId: peerId, snapshot: snapshot2))) 85 | // Then 86 | XCTAssertEqual(state.peers[peerId]?.selectedSnaphot, snapshot1) 87 | // When 88 | appReducer.reduce(&state, Actions.deselectSnapshot(payload: (peerId: peerId, snapshot: snapshot1))) 89 | // Then 90 | XCTAssertNil(state.peers[peerId]?.selectedSnaphot) 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /FluxorExplorerTests/Store/AppSelectorsTests.swift: -------------------------------------------------------------------------------- 1 | /* 2 | * FluxorExplorerTests 3 | * Copyright (c) Morten Bjerg Gregersen 2020 4 | * MIT license, see LICENSE file for details 5 | */ 6 | 7 | import Fluxor 8 | @testable import FluxorExplorer 9 | import FluxorExplorerSnapshot 10 | import MultipeerConnectivity.MCPeerID 11 | import XCTest 12 | 13 | class AppSelectorsTests: XCTestCase { 14 | let peerId = MCPeerID(displayName: "Some id") 15 | 16 | func testSelectedPeerId() { 17 | // Given 18 | let state = AppState(selectedPeerId: peerId) 19 | // Then 20 | XCTAssertEqual(Selectors.getSelectedPeerId.map(state), peerId) 21 | } 22 | 23 | func testGetPeers() { 24 | // Given 25 | let peerId1 = MCPeerID(displayName: "Some peer") 26 | let peerId2 = MCPeerID(displayName: "Another peer") 27 | let peerId3 = MCPeerID(displayName: "Third peer") 28 | let peer1 = Peer(id: peerId1) 29 | let peer2 = Peer(id: peerId2) 30 | let peer3 = Peer(id: peerId3) 31 | let state = [peerId1: peer1, peerId2: peer2, peerId3: peer3] 32 | // Then 33 | XCTAssertEqual(Selectors.getPeers.projector(state), [peer2, peer1, peer3]) 34 | } 35 | 36 | func testGetSelectedPeerName() { 37 | // Given 38 | let state = AppState(peers: [peerId: Peer(id: peerId)], selectedPeerId: peerId) 39 | // Then 40 | XCTAssertEqual(Selectors.getSelectedPeerName.map(state), "Some id") 41 | } 42 | 43 | func testGetSelectedPeerSnapshots() { 44 | // Given 45 | let snapshots = [FluxorExplorerSnapshot(action: TestAction(increment: 1), 46 | oldState: TestState(counter: 0), 47 | newState: TestState(counter: 1))] 48 | let peer = Peer(id: peerId, snapshots: snapshots) 49 | // Then 50 | XCTAssertEqual(Selectors.getSelectedPeerSnapshots.projector(peer), snapshots) 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /FluxorExplorerTests/Views/PeersViewTests.swift: -------------------------------------------------------------------------------- 1 | /* 2 | * FluxorExplorerTests 3 | * Copyright (c) Morten Bjerg Gregersen 2020 4 | * MIT license, see LICENSE file for details 5 | */ 6 | 7 | import Fluxor 8 | @testable import FluxorExplorer 9 | import FluxorTestSupport 10 | import MultipeerConnectivity 11 | import ViewInspector 12 | import XCTest 13 | 14 | // swiftlint:disable force_cast 15 | 16 | class PeersViewTests: ViewTestCase { 17 | private let peerId1 = MCPeerID(displayName: "Peer 1") 18 | private let peerId2 = MCPeerID(displayName: "Peer 2") 19 | private lazy var peers = [Peer(id: peerId1), Peer(id: peerId2)] 20 | 21 | func testNoPeers() throws { 22 | // Given 23 | let view = PeersView(store: mockStore) 24 | // Then 25 | let vStack = try view.inspect().hStack().vStack(0) 26 | let headline = try vStack.text(0).string() 27 | let body = try vStack.text(1).string() 28 | XCTAssertEqual(headline, "No devices connected") 29 | XCTAssertEqual(body, "Make sure the application is launched and the Interceptor is registered.") 30 | } 31 | 32 | func testPeers() throws { 33 | // Given 34 | mockStore.overrideSelector(Selectors.getPeers, value: peers) 35 | let view = PeersView(store: mockStore) 36 | // Then 37 | let listElements = try view.inspect().hStack().list(0).forEach(0) 38 | XCTAssertEqual(listElements.count, peers.count) 39 | try peers.indices.forEach { 40 | XCTAssertEqual(try listElements.navigationLink($0).labelView().text().string(), peers[$0].name) 41 | } 42 | } 43 | 44 | func testPeerSelection() throws { 45 | // Given 46 | let peerId = MCPeerID(displayName: "Some id") 47 | mockStore.overrideSelector(Selectors.getPeers, value: peers) 48 | let view = PeersView(store: mockStore) 49 | // When 50 | let listElements = try view.inspect().hStack().list(0).forEach(0) 51 | try listElements.navigationLink(0).activate() 52 | // Then 53 | XCTAssertEqual(mockStore.dispatchedActions[0] as! AnonymousAction, 54 | Actions.selectPeer(payload: peerId)) 55 | // When 56 | try listElements.navigationLink(0).deactivate() 57 | // Then 58 | XCTAssertEqual(mockStore.dispatchedActions[1] as! AnonymousAction, 59 | Actions.deselectPeer(payload: peerId)) 60 | } 61 | } 62 | 63 | extension PeersView: Inspectable {} 64 | -------------------------------------------------------------------------------- /FluxorExplorerTests/Views/SnapshotViewTests.swift: -------------------------------------------------------------------------------- 1 | /* 2 | * FluxorExplorerTests 3 | * Copyright (c) Morten Bjerg Gregersen 2020 4 | * MIT license, see LICENSE file for details 5 | */ 6 | 7 | import Fluxor 8 | @testable import FluxorExplorer 9 | @testable import FluxorExplorerSnapshot 10 | import ViewInspector 11 | import XCTest 12 | 13 | class SnapshotViewTests: XCTestCase { 14 | let snapshotWithPayload = FluxorExplorerSnapshot(action: TestAction(increment: 1), 15 | oldState: TestState(counter: 0), 16 | newState: TestState(counter: 1), 17 | date: Date(timeIntervalSince1970: 1586294198)) 18 | let snapshotWithoutPayload = FluxorExplorerSnapshot(action: TestAction(increment: 2), 19 | oldState: TestState(counter: 1), 20 | newState: TestState(counter: 3), 21 | date: Date(timeIntervalSince1970: 1586294098)) 22 | 23 | static var originalTimeZone: TimeZone! 24 | 25 | override class func setUp() { 26 | originalTimeZone = NSTimeZone.default 27 | NSTimeZone.default = TimeZone(abbreviation: "GMT+2")! 28 | } 29 | 30 | override class func tearDown() { 31 | NSTimeZone.default = originalTimeZone 32 | } 33 | 34 | func testWithPayload() throws { 35 | // Given 36 | let view = SnapshotView(snapshot: snapshotWithPayload) 37 | // Then 38 | let vStack = try getVStack(in: view) 39 | XCTAssertEqual(try vStack.text(0).string(), "Time:") 40 | XCTAssertEqual(try vStack.text(1).string(), "11:16:38 PM GMT+2") 41 | // ViewInspector doesn't support this right now 42 | // XCTAssertEqual(try vStack.child(at: 3), "Payload") 43 | // XCTAssertEqual(try vStack.view(DataStructureView.self, 3).actualView().dataStructure, 44 | // snapshotWithPayload.actionData.payload) 45 | XCTAssertEqual(try vStack.text(4).string(), "State") 46 | XCTAssertEqual(try vStack.view(DataStructureView.self, 5).actualView().dataStructure, 47 | snapshotWithPayload.newState) 48 | } 49 | 50 | func testWithoutPayload() throws { 51 | // Given 52 | let view = SnapshotView(snapshot: snapshotWithoutPayload) 53 | // Then 54 | let vStack = try getVStack(in: view) 55 | XCTAssertEqual(try vStack.text(0).string(), "Time:") 56 | XCTAssertEqual(try vStack.text(1).string(), "11:14:58 PM GMT+2") 57 | XCTAssertEqual(try vStack.text(4).string(), "State") 58 | XCTAssertEqual(try vStack.view(DataStructureView.self, 5).actualView().dataStructure, 59 | snapshotWithoutPayload.newState) 60 | } 61 | 62 | func testPreview() { 63 | XCTAssertTrue(SnapshotView_Previews.previews is SnapshotView) 64 | } 65 | 66 | private func getVStack(in view: SnapshotView) throws -> InspectableView { 67 | try view.inspect().scrollView().vStack() 68 | } 69 | } 70 | 71 | extension SnapshotView: Inspectable {} 72 | extension DataStructureView: Inspectable {} 73 | -------------------------------------------------------------------------------- /FluxorExplorerTests/Views/SnapshotsViewTests.swift: -------------------------------------------------------------------------------- 1 | /* 2 | * FluxorExplorerTests 3 | * Copyright (c) Morten Bjerg Gregersen 2020 4 | * MIT license, see LICENSE file for details 5 | */ 6 | 7 | import Fluxor 8 | @testable import FluxorExplorer 9 | import FluxorExplorerSnapshot 10 | import MultipeerConnectivity 11 | import SwiftUI 12 | import ViewInspector 13 | import XCTest 14 | 15 | // swiftlint:disable force_cast 16 | 17 | class SnapshotsViewTests: ViewTestCase { 18 | let snapshots = [FluxorExplorerSnapshot(action: TestAction(increment: 1), 19 | oldState: TestState(counter: 1), 20 | newState: TestState(counter: 2)), 21 | FluxorExplorerSnapshot(action: TestAction(increment: 40), 22 | oldState: TestState(counter: 2), 23 | newState: TestState(counter: 42))] 24 | 25 | func testNoSnapshots() throws { 26 | // Given 27 | let view = SnapshotsView(store: mockStore) 28 | // Then 29 | let vStack = try getHStack(in: view).vStack(0) 30 | let headline = try vStack.text(0).string() 31 | XCTAssertEqual(headline, "No snapshots yet") 32 | } 33 | 34 | func testSnapshots() throws { 35 | // Given 36 | mockStore.overrideSelector(Selectors.getSelectedPeerSnapshots, value: snapshots) 37 | let view = SnapshotsView(store: mockStore) 38 | // Then 39 | let listElements = try getListElements(in: view) 40 | XCTAssertEqual(listElements.count, snapshots.count) 41 | try snapshots.indices.forEach { 42 | let navigationLink = try listElements.navigationLink($0) 43 | XCTAssertFalse(try navigationLink.isActive()) 44 | XCTAssertEqual(try navigationLink.view(SnapshotView.self).actualView().snapshot, snapshots[$0]) 45 | XCTAssertEqual(try navigationLink.labelView().vStack().text(0).string(), 46 | snapshots[$0].actionData.name) 47 | } 48 | } 49 | 50 | func testTimeBackgroundColor() throws { 51 | // Given 52 | mockStore.overrideSelector(Selectors.getSelectedPeerSnapshots, value: snapshots) 53 | let view = SnapshotsView(store: mockStore) 54 | // Then 55 | XCTAssertEqual(view.timeBackgroundColor(for: .dark), Color(.darkGray)) 56 | XCTAssertEqual(view.timeBackgroundColor(for: .light), Color(.init(white: 0.9, alpha: 1))) 57 | } 58 | 59 | func testSnapshotSelection() throws { 60 | // Given 61 | let peerId = MCPeerID(displayName: "some id") 62 | mockStore.overrideSelector(Selectors.getSelectedPeerId, value: peerId) 63 | mockStore.overrideSelector(Selectors.getSelectedPeerSnapshots, value: snapshots) 64 | let view = SnapshotsView(store: mockStore) 65 | // When 66 | let listElements = try getListElements(in: view) 67 | try listElements.navigationLink(0).activate() 68 | // Then 69 | let dispatchedSelectAction = mockStore.dispatchedActions[0] 70 | as! AnonymousAction<(peerId: MCPeerID?, snapshot: FluxorExplorerSnapshot)> 71 | let selectAction = Actions.selectSnapshot(payload: (peerId: peerId, snapshot: snapshots[0])) 72 | XCTAssertEqual(dispatchedSelectAction, selectAction) 73 | // When 74 | try listElements.navigationLink(0).deactivate() 75 | // Then 76 | let dispatchedDeselectAction = mockStore.dispatchedActions[1] 77 | as! AnonymousAction<(peerId: MCPeerID?, snapshot: FluxorExplorerSnapshot)> 78 | let deselectAction = Actions.deselectSnapshot(payload: (peerId: peerId, snapshot: snapshots[0])) 79 | XCTAssertEqual(dispatchedDeselectAction, deselectAction) 80 | } 81 | 82 | private func getHStack(in view: SnapshotsView) throws -> InspectableView { 83 | try view.inspect().hStack() 84 | } 85 | 86 | private func getListElements(in view: SnapshotsView) throws -> InspectableView { 87 | try getHStack(in: view).list(0).forEach(0) 88 | } 89 | } 90 | 91 | extension SnapshotsView: Inspectable {} 92 | -------------------------------------------------------------------------------- /FluxorExplorerTests/Views/ViewTestCase.swift: -------------------------------------------------------------------------------- 1 | /* 2 | * FluxorExplorerTests 3 | * Copyright (c) Morten Bjerg Gregersen 2021 4 | * MIT license, see LICENSE file for details 5 | */ 6 | 7 | @testable import FluxorExplorer 8 | import FluxorTestSupport 9 | import XCTest 10 | 11 | class ViewTestCase: XCTestCase { 12 | var mockStore: MockStore! 13 | 14 | override func setUp() { 15 | super.setUp() 16 | mockStore = MockStore(initialState: AppState(), environment: AppEnvironment()) 17 | FluxorExplorerApp.store = mockStore 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /FluxorExplorerTests/Waiting.swift: -------------------------------------------------------------------------------- 1 | /* 2 | * FluxorExplorerTests 3 | * Copyright (c) Morten Bjerg Gregersen 2020 4 | * MIT license, see LICENSE file for details 5 | */ 6 | 7 | import XCTest 8 | 9 | extension XCTestCase { 10 | func waitFor(interval: TimeInterval = 0.1, completion: @escaping (() -> Void)) { 11 | let expectation = XCTestExpectation(description: debugDescription) 12 | DispatchQueue.main.asyncAfter(deadline: .now() + interval) { 13 | completion() 14 | expectation.fulfill() 15 | } 16 | wait(for: [expectation], timeout: interval + 0.1) 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /FluxorExplorerUITests/FluxorExplorerUITests.swift: -------------------------------------------------------------------------------- 1 | /* 2 | * FluxorExplorerUITests 3 | * Copyright (c) Morten Bjerg Gregersen 2020 4 | * MIT license, see LICENSE file for details 5 | */ 6 | 7 | import XCTest 8 | 9 | class FluxorExplorerUITests: XCTestCase { 10 | override func setUp() { 11 | continueAfterFailure = false 12 | } 13 | 14 | func testNavigation() { 15 | #if !targetEnvironment(macCatalyst) 16 | XCUIDevice.shared.orientation = .landscapeLeft 17 | #endif 18 | let app = XCUIApplication() 19 | app.launchEnvironment["UI_TESTING"] = "1" 20 | app.launch() 21 | // Select peer 22 | let peerButton = app.tables.buttons["iPhone 11 Pro"] 23 | waitForElement(element: peerButton) 24 | peerButton.tap() 25 | // Select action 26 | let predicate = NSPredicate(format: "label CONTAINS[c] 'CompleteTodoAction'") 27 | let actionButton = app.tables.buttons.containing(predicate).element(boundBy: 0) 28 | waitForElement(element: actionButton) 29 | actionButton.tap() 30 | } 31 | } 32 | 33 | extension XCTestCase { 34 | public func waitForElement(element: XCUIElement, timeout: TimeInterval = 5) { 35 | let exists = NSPredicate(format: "exists == true") 36 | expectation(for: exists, evaluatedWith: element, handler: nil) 37 | waitForExpectations(timeout: timeout, handler: nil) 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /FluxorExplorerUITests/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | $(PRODUCT_BUNDLE_PACKAGE_TYPE) 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | 1 21 | 22 | 23 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | source 'https://rubygems.org' 4 | gem 'github-pages', group: :jekyll_plugins 5 | -------------------------------------------------------------------------------- /Gemfile.lock: -------------------------------------------------------------------------------- 1 | GEM 2 | remote: https://rubygems.org/ 3 | specs: 4 | activesupport (8.0.0) 5 | base64 6 | benchmark (>= 0.3) 7 | bigdecimal 8 | concurrent-ruby (~> 1.0, >= 1.3.1) 9 | connection_pool (>= 2.2.5) 10 | drb 11 | i18n (>= 1.6, < 2) 12 | logger (>= 1.4.2) 13 | minitest (>= 5.1) 14 | securerandom (>= 0.3) 15 | tzinfo (~> 2.0, >= 2.0.5) 16 | uri (>= 0.13.1) 17 | addressable (2.8.7) 18 | public_suffix (>= 2.0.2, < 7.0) 19 | base64 (0.2.0) 20 | benchmark (0.4.0) 21 | bigdecimal (3.1.8) 22 | coffee-script (2.4.1) 23 | coffee-script-source 24 | execjs 25 | coffee-script-source (1.12.2) 26 | colorator (1.1.0) 27 | commonmarker (0.23.10) 28 | concurrent-ruby (1.3.4) 29 | connection_pool (2.4.1) 30 | csv (3.3.0) 31 | dnsruby (1.72.2) 32 | simpleidn (~> 0.2.1) 33 | drb (2.2.1) 34 | em-websocket (0.5.3) 35 | eventmachine (>= 0.12.9) 36 | http_parser.rb (~> 0) 37 | ethon (0.16.0) 38 | ffi (>= 1.15.0) 39 | eventmachine (1.2.7) 40 | execjs (2.10.0) 41 | faraday (2.12.1) 42 | faraday-net_http (>= 2.0, < 3.5) 43 | json 44 | logger 45 | faraday-net_http (3.4.0) 46 | net-http (>= 0.5.0) 47 | ffi (1.17.0-aarch64-linux-gnu) 48 | ffi (1.17.0-aarch64-linux-musl) 49 | ffi (1.17.0-arm-linux-gnu) 50 | ffi (1.17.0-arm-linux-musl) 51 | ffi (1.17.0-arm64-darwin) 52 | ffi (1.17.0-x86-linux-gnu) 53 | ffi (1.17.0-x86-linux-musl) 54 | ffi (1.17.0-x86_64-darwin) 55 | ffi (1.17.0-x86_64-linux-gnu) 56 | ffi (1.17.0-x86_64-linux-musl) 57 | forwardable-extended (2.6.0) 58 | gemoji (4.1.0) 59 | github-pages (232) 60 | github-pages-health-check (= 1.18.2) 61 | jekyll (= 3.10.0) 62 | jekyll-avatar (= 0.8.0) 63 | jekyll-coffeescript (= 1.2.2) 64 | jekyll-commonmark-ghpages (= 0.5.1) 65 | jekyll-default-layout (= 0.1.5) 66 | jekyll-feed (= 0.17.0) 67 | jekyll-gist (= 1.5.0) 68 | jekyll-github-metadata (= 2.16.1) 69 | jekyll-include-cache (= 0.2.1) 70 | jekyll-mentions (= 1.6.0) 71 | jekyll-optional-front-matter (= 0.3.2) 72 | jekyll-paginate (= 1.1.0) 73 | jekyll-readme-index (= 0.3.0) 74 | jekyll-redirect-from (= 0.16.0) 75 | jekyll-relative-links (= 0.6.1) 76 | jekyll-remote-theme (= 0.4.3) 77 | jekyll-sass-converter (= 1.5.2) 78 | jekyll-seo-tag (= 2.8.0) 79 | jekyll-sitemap (= 1.4.0) 80 | jekyll-swiss (= 1.0.0) 81 | jekyll-theme-architect (= 0.2.0) 82 | jekyll-theme-cayman (= 0.2.0) 83 | jekyll-theme-dinky (= 0.2.0) 84 | jekyll-theme-hacker (= 0.2.0) 85 | jekyll-theme-leap-day (= 0.2.0) 86 | jekyll-theme-merlot (= 0.2.0) 87 | jekyll-theme-midnight (= 0.2.0) 88 | jekyll-theme-minimal (= 0.2.0) 89 | jekyll-theme-modernist (= 0.2.0) 90 | jekyll-theme-primer (= 0.6.0) 91 | jekyll-theme-slate (= 0.2.0) 92 | jekyll-theme-tactile (= 0.2.0) 93 | jekyll-theme-time-machine (= 0.2.0) 94 | jekyll-titles-from-headings (= 0.5.3) 95 | jemoji (= 0.13.0) 96 | kramdown (= 2.4.0) 97 | kramdown-parser-gfm (= 1.1.0) 98 | liquid (= 4.0.4) 99 | mercenary (~> 0.3) 100 | minima (= 2.5.1) 101 | nokogiri (>= 1.16.2, < 2.0) 102 | rouge (= 3.30.0) 103 | terminal-table (~> 1.4) 104 | webrick (~> 1.8) 105 | github-pages-health-check (1.18.2) 106 | addressable (~> 2.3) 107 | dnsruby (~> 1.60) 108 | octokit (>= 4, < 8) 109 | public_suffix (>= 3.0, < 6.0) 110 | typhoeus (~> 1.3) 111 | html-pipeline (2.14.3) 112 | activesupport (>= 2) 113 | nokogiri (>= 1.4) 114 | http_parser.rb (0.8.0) 115 | i18n (1.14.6) 116 | concurrent-ruby (~> 1.0) 117 | jekyll (3.10.0) 118 | addressable (~> 2.4) 119 | colorator (~> 1.0) 120 | csv (~> 3.0) 121 | em-websocket (~> 0.5) 122 | i18n (>= 0.7, < 2) 123 | jekyll-sass-converter (~> 1.0) 124 | jekyll-watch (~> 2.0) 125 | kramdown (>= 1.17, < 3) 126 | liquid (~> 4.0) 127 | mercenary (~> 0.3.3) 128 | pathutil (~> 0.9) 129 | rouge (>= 1.7, < 4) 130 | safe_yaml (~> 1.0) 131 | webrick (>= 1.0) 132 | jekyll-avatar (0.8.0) 133 | jekyll (>= 3.0, < 5.0) 134 | jekyll-coffeescript (1.2.2) 135 | coffee-script (~> 2.2) 136 | coffee-script-source (~> 1.12) 137 | jekyll-commonmark (1.4.0) 138 | commonmarker (~> 0.22) 139 | jekyll-commonmark-ghpages (0.5.1) 140 | commonmarker (>= 0.23.7, < 1.1.0) 141 | jekyll (>= 3.9, < 4.0) 142 | jekyll-commonmark (~> 1.4.0) 143 | rouge (>= 2.0, < 5.0) 144 | jekyll-default-layout (0.1.5) 145 | jekyll (>= 3.0, < 5.0) 146 | jekyll-feed (0.17.0) 147 | jekyll (>= 3.7, < 5.0) 148 | jekyll-gist (1.5.0) 149 | octokit (~> 4.2) 150 | jekyll-github-metadata (2.16.1) 151 | jekyll (>= 3.4, < 5.0) 152 | octokit (>= 4, < 7, != 4.4.0) 153 | jekyll-include-cache (0.2.1) 154 | jekyll (>= 3.7, < 5.0) 155 | jekyll-mentions (1.6.0) 156 | html-pipeline (~> 2.3) 157 | jekyll (>= 3.7, < 5.0) 158 | jekyll-optional-front-matter (0.3.2) 159 | jekyll (>= 3.0, < 5.0) 160 | jekyll-paginate (1.1.0) 161 | jekyll-readme-index (0.3.0) 162 | jekyll (>= 3.0, < 5.0) 163 | jekyll-redirect-from (0.16.0) 164 | jekyll (>= 3.3, < 5.0) 165 | jekyll-relative-links (0.6.1) 166 | jekyll (>= 3.3, < 5.0) 167 | jekyll-remote-theme (0.4.3) 168 | addressable (~> 2.0) 169 | jekyll (>= 3.5, < 5.0) 170 | jekyll-sass-converter (>= 1.0, <= 3.0.0, != 2.0.0) 171 | rubyzip (>= 1.3.0, < 3.0) 172 | jekyll-sass-converter (1.5.2) 173 | sass (~> 3.4) 174 | jekyll-seo-tag (2.8.0) 175 | jekyll (>= 3.8, < 5.0) 176 | jekyll-sitemap (1.4.0) 177 | jekyll (>= 3.7, < 5.0) 178 | jekyll-swiss (1.0.0) 179 | jekyll-theme-architect (0.2.0) 180 | jekyll (> 3.5, < 5.0) 181 | jekyll-seo-tag (~> 2.0) 182 | jekyll-theme-cayman (0.2.0) 183 | jekyll (> 3.5, < 5.0) 184 | jekyll-seo-tag (~> 2.0) 185 | jekyll-theme-dinky (0.2.0) 186 | jekyll (> 3.5, < 5.0) 187 | jekyll-seo-tag (~> 2.0) 188 | jekyll-theme-hacker (0.2.0) 189 | jekyll (> 3.5, < 5.0) 190 | jekyll-seo-tag (~> 2.0) 191 | jekyll-theme-leap-day (0.2.0) 192 | jekyll (> 3.5, < 5.0) 193 | jekyll-seo-tag (~> 2.0) 194 | jekyll-theme-merlot (0.2.0) 195 | jekyll (> 3.5, < 5.0) 196 | jekyll-seo-tag (~> 2.0) 197 | jekyll-theme-midnight (0.2.0) 198 | jekyll (> 3.5, < 5.0) 199 | jekyll-seo-tag (~> 2.0) 200 | jekyll-theme-minimal (0.2.0) 201 | jekyll (> 3.5, < 5.0) 202 | jekyll-seo-tag (~> 2.0) 203 | jekyll-theme-modernist (0.2.0) 204 | jekyll (> 3.5, < 5.0) 205 | jekyll-seo-tag (~> 2.0) 206 | jekyll-theme-primer (0.6.0) 207 | jekyll (> 3.5, < 5.0) 208 | jekyll-github-metadata (~> 2.9) 209 | jekyll-seo-tag (~> 2.0) 210 | jekyll-theme-slate (0.2.0) 211 | jekyll (> 3.5, < 5.0) 212 | jekyll-seo-tag (~> 2.0) 213 | jekyll-theme-tactile (0.2.0) 214 | jekyll (> 3.5, < 5.0) 215 | jekyll-seo-tag (~> 2.0) 216 | jekyll-theme-time-machine (0.2.0) 217 | jekyll (> 3.5, < 5.0) 218 | jekyll-seo-tag (~> 2.0) 219 | jekyll-titles-from-headings (0.5.3) 220 | jekyll (>= 3.3, < 5.0) 221 | jekyll-watch (2.2.1) 222 | listen (~> 3.0) 223 | jemoji (0.13.0) 224 | gemoji (>= 3, < 5) 225 | html-pipeline (~> 2.2) 226 | jekyll (>= 3.0, < 5.0) 227 | json (2.8.2) 228 | kramdown (2.4.0) 229 | rexml 230 | kramdown-parser-gfm (1.1.0) 231 | kramdown (~> 2.0) 232 | liquid (4.0.4) 233 | listen (3.9.0) 234 | rb-fsevent (~> 0.10, >= 0.10.3) 235 | rb-inotify (~> 0.9, >= 0.9.10) 236 | logger (1.6.1) 237 | mercenary (0.3.6) 238 | minima (2.5.1) 239 | jekyll (>= 3.5, < 5.0) 240 | jekyll-feed (~> 0.9) 241 | jekyll-seo-tag (~> 2.1) 242 | minitest (5.25.1) 243 | net-http (0.5.0) 244 | uri 245 | nokogiri (1.16.7-aarch64-linux) 246 | racc (~> 1.4) 247 | nokogiri (1.16.7-arm-linux) 248 | racc (~> 1.4) 249 | nokogiri (1.16.7-arm64-darwin) 250 | racc (~> 1.4) 251 | nokogiri (1.16.7-x86-linux) 252 | racc (~> 1.4) 253 | nokogiri (1.16.7-x86_64-darwin) 254 | racc (~> 1.4) 255 | nokogiri (1.16.7-x86_64-linux) 256 | racc (~> 1.4) 257 | octokit (4.25.1) 258 | faraday (>= 1, < 3) 259 | sawyer (~> 0.9) 260 | pathutil (0.16.2) 261 | forwardable-extended (~> 2.6) 262 | public_suffix (5.1.1) 263 | racc (1.8.1) 264 | rb-fsevent (0.11.2) 265 | rb-inotify (0.11.1) 266 | ffi (~> 1.0) 267 | rexml (3.3.9) 268 | rouge (3.30.0) 269 | rubyzip (2.3.2) 270 | safe_yaml (1.0.5) 271 | sass (3.7.4) 272 | sass-listen (~> 4.0.0) 273 | sass-listen (4.0.0) 274 | rb-fsevent (~> 0.9, >= 0.9.4) 275 | rb-inotify (~> 0.9, >= 0.9.7) 276 | sawyer (0.9.2) 277 | addressable (>= 2.3.5) 278 | faraday (>= 0.17.3, < 3) 279 | securerandom (0.3.2) 280 | simpleidn (0.2.3) 281 | terminal-table (1.8.0) 282 | unicode-display_width (~> 1.1, >= 1.1.1) 283 | typhoeus (1.4.1) 284 | ethon (>= 0.9.0) 285 | tzinfo (2.0.6) 286 | concurrent-ruby (~> 1.0) 287 | unicode-display_width (1.8.0) 288 | uri (1.0.2) 289 | webrick (1.9.0) 290 | 291 | PLATFORMS 292 | aarch64-linux 293 | aarch64-linux-gnu 294 | aarch64-linux-musl 295 | arm-linux 296 | arm-linux-gnu 297 | arm-linux-musl 298 | arm64-darwin 299 | x86-linux 300 | x86-linux-gnu 301 | x86-linux-musl 302 | x86_64-darwin 303 | x86_64-linux 304 | x86_64-linux-gnu 305 | x86_64-linux-musl 306 | 307 | DEPENDENCIES 308 | github-pages 309 | 310 | BUNDLED WITH 311 | 2.5.23 312 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Morten Bjerg Gregersen 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |
2 | 3 |

FluxorExplorer

4 |

5 | 6 | Download on the App Store 7 | 8 |
9 | A macOS and iPadOS app for debugging apps using Fluxor. 10 |

11 |

12 | Platform: Mac+iOS 13 | Swift 5.2 14 | 15 | Twitter: @mortengregersen 16 | 17 |
18 | 19 | Build Status 20 | 21 | 22 | Maintainability 23 | 24 | 25 | Test Coverage 26 | 27 |

28 |
29 | 30 | ![](https://raw.githubusercontent.com/FluxorOrg/FluxorExplorer/master/Assets/FluxorExplorer.png) 31 | 32 | FluxorExplorer allows developers of apps using Fluxor, to step through the actions dispatched and the corresponding state changes, to easily debug the data flow of their app. 33 | 34 | ## 🗣 Make your app talk 35 | 36 | The only thing you need to do, to make your app send out the dispatched actions and state changes, is to register the [FluxorExplorerInterceptor](https://github.com/FluxorOrg/FluxorExplorerInterceptor) in the store: 37 | 38 | ```swift 39 | let store = Store(initialState: AppState()) 40 | #if DEBUG 41 | store.register(interceptor: FluxorExplorerInterceptor(displayName: UIDevice.current.name)) 42 | #endif 43 | ``` 44 | 45 | **Note:** It is recommended that the FluxorExplorerInterceptor is only registered in debug builds. 46 | 47 | ## 🔌 How does it connect? 48 | 49 | FluxorExplorer and FluxorExplorerInterceptor uses Apple's [MultipeerConnectivity](https://developer.apple.com/documentation/multipeerconnectivity) framework to connect and communicate. 50 | 51 | 1. When FluxorExplorer is launched, it instantiates a [`MCNearbyServiceBrowser`](https://developer.apple.com/documentation/multipeerconnectivity/mcnearbyservicebrowser) and starts browsing for peers. 52 | 2. When a FluxorExplorerInterceptor is instantiated a [`MCNearbyServiceAdvertiser`](https://developer.apple.com/documentation/multipeerconnectivity/mcnearbyserviceadvertiser) and starts advertising. 53 | 3. When FluxorExplorer discovers an advertising peer it automatically sends an invite to the peer 54 | 4. When the advertising FluxorExplorerInterceptor receives an invite, it automatically accepts it 55 | 56 | This means that as long as FluxorExplorer and the app using FluxorExplorerInterceptor is on the same network, they will automatically connect and send/receive data. 57 | -------------------------------------------------------------------------------- /_config.yml: -------------------------------------------------------------------------------- 1 | theme: jekyll-theme-slate 2 | repository: FluxorOrg/FluxorExplorer 3 | -------------------------------------------------------------------------------- /_layouts/default.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | {% seo %} 11 | 12 | 13 | 14 | 15 |
16 |
17 | {% if site.github.is_project_page %} 18 | View on GitHub 19 | {% endif %} 20 | {{ content }} 21 |
22 |
23 | 24 | 25 | 33 | 34 | 35 | -------------------------------------------------------------------------------- /assets/css/style.scss: -------------------------------------------------------------------------------- 1 | --- 2 | --- 3 | 4 | @import "{{ site.theme }}"; 5 | img { 6 | margin: 0; 7 | padding: 0; 8 | border: 0; 9 | } 10 | p img { 11 | width: 100%; 12 | } --------------------------------------------------------------------------------