diff --git a/BrickController2.sln b/BrickController2.sln index 427c9da4..bdd401b9 100644 --- a/BrickController2.sln +++ b/BrickController2.sln @@ -9,130 +9,276 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BrickController2.iOS", "Bri EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "BrickController2", "BrickController2\BrickController2\BrickController2.csproj", "{852D9034-471A-42D0-8701-63D12E2EDACA}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BrickController2.UWP", "BrickController2\BrickController2.UWP\BrickController2.UWP.csproj", "{DC1AE8D2-77CD-4C25-8E43-D2D3F3E68339}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Ad-Hoc|Any CPU = Ad-Hoc|Any CPU + Ad-Hoc|ARM = Ad-Hoc|ARM Ad-Hoc|iPhone = Ad-Hoc|iPhone Ad-Hoc|iPhoneSimulator = Ad-Hoc|iPhoneSimulator + Ad-Hoc|x64 = Ad-Hoc|x64 + Ad-Hoc|x86 = Ad-Hoc|x86 AppStore|Any CPU = AppStore|Any CPU + AppStore|ARM = AppStore|ARM AppStore|iPhone = AppStore|iPhone AppStore|iPhoneSimulator = AppStore|iPhoneSimulator + AppStore|x64 = AppStore|x64 + AppStore|x86 = AppStore|x86 Debug|Any CPU = Debug|Any CPU + Debug|ARM = Debug|ARM Debug|iPhone = Debug|iPhone Debug|iPhoneSimulator = Debug|iPhoneSimulator + Debug|x64 = Debug|x64 + Debug|x86 = Debug|x86 Release|Any CPU = Release|Any CPU + Release|ARM = Release|ARM Release|iPhone = Release|iPhone Release|iPhoneSimulator = Release|iPhoneSimulator + Release|x64 = Release|x64 + Release|x86 = Release|x86 EndGlobalSection GlobalSection(ProjectConfigurationPlatforms) = postSolution {A82CFFAA-423D-473B-820C-174F7AE48B7E}.Ad-Hoc|Any CPU.ActiveCfg = Release|Any CPU {A82CFFAA-423D-473B-820C-174F7AE48B7E}.Ad-Hoc|Any CPU.Build.0 = Release|Any CPU {A82CFFAA-423D-473B-820C-174F7AE48B7E}.Ad-Hoc|Any CPU.Deploy.0 = Release|Any CPU + {A82CFFAA-423D-473B-820C-174F7AE48B7E}.Ad-Hoc|ARM.ActiveCfg = Debug|Any CPU + {A82CFFAA-423D-473B-820C-174F7AE48B7E}.Ad-Hoc|ARM.Build.0 = Debug|Any CPU + {A82CFFAA-423D-473B-820C-174F7AE48B7E}.Ad-Hoc|ARM.Deploy.0 = Debug|Any CPU {A82CFFAA-423D-473B-820C-174F7AE48B7E}.Ad-Hoc|iPhone.ActiveCfg = Release|Any CPU {A82CFFAA-423D-473B-820C-174F7AE48B7E}.Ad-Hoc|iPhone.Build.0 = Release|Any CPU {A82CFFAA-423D-473B-820C-174F7AE48B7E}.Ad-Hoc|iPhone.Deploy.0 = Release|Any CPU {A82CFFAA-423D-473B-820C-174F7AE48B7E}.Ad-Hoc|iPhoneSimulator.ActiveCfg = Release|Any CPU {A82CFFAA-423D-473B-820C-174F7AE48B7E}.Ad-Hoc|iPhoneSimulator.Build.0 = Release|Any CPU {A82CFFAA-423D-473B-820C-174F7AE48B7E}.Ad-Hoc|iPhoneSimulator.Deploy.0 = Release|Any CPU + {A82CFFAA-423D-473B-820C-174F7AE48B7E}.Ad-Hoc|x64.ActiveCfg = Debug|Any CPU + {A82CFFAA-423D-473B-820C-174F7AE48B7E}.Ad-Hoc|x64.Build.0 = Debug|Any CPU + {A82CFFAA-423D-473B-820C-174F7AE48B7E}.Ad-Hoc|x64.Deploy.0 = Debug|Any CPU + {A82CFFAA-423D-473B-820C-174F7AE48B7E}.Ad-Hoc|x86.ActiveCfg = Debug|Any CPU + {A82CFFAA-423D-473B-820C-174F7AE48B7E}.Ad-Hoc|x86.Build.0 = Debug|Any CPU + {A82CFFAA-423D-473B-820C-174F7AE48B7E}.Ad-Hoc|x86.Deploy.0 = Debug|Any CPU {A82CFFAA-423D-473B-820C-174F7AE48B7E}.AppStore|Any CPU.ActiveCfg = Release|Any CPU {A82CFFAA-423D-473B-820C-174F7AE48B7E}.AppStore|Any CPU.Build.0 = Release|Any CPU {A82CFFAA-423D-473B-820C-174F7AE48B7E}.AppStore|Any CPU.Deploy.0 = Release|Any CPU + {A82CFFAA-423D-473B-820C-174F7AE48B7E}.AppStore|ARM.ActiveCfg = Debug|Any CPU + {A82CFFAA-423D-473B-820C-174F7AE48B7E}.AppStore|ARM.Build.0 = Debug|Any CPU + {A82CFFAA-423D-473B-820C-174F7AE48B7E}.AppStore|ARM.Deploy.0 = Debug|Any CPU {A82CFFAA-423D-473B-820C-174F7AE48B7E}.AppStore|iPhone.ActiveCfg = Release|Any CPU {A82CFFAA-423D-473B-820C-174F7AE48B7E}.AppStore|iPhone.Build.0 = Release|Any CPU {A82CFFAA-423D-473B-820C-174F7AE48B7E}.AppStore|iPhone.Deploy.0 = Release|Any CPU {A82CFFAA-423D-473B-820C-174F7AE48B7E}.AppStore|iPhoneSimulator.ActiveCfg = Release|Any CPU {A82CFFAA-423D-473B-820C-174F7AE48B7E}.AppStore|iPhoneSimulator.Build.0 = Release|Any CPU {A82CFFAA-423D-473B-820C-174F7AE48B7E}.AppStore|iPhoneSimulator.Deploy.0 = Release|Any CPU + {A82CFFAA-423D-473B-820C-174F7AE48B7E}.AppStore|x64.ActiveCfg = Debug|Any CPU + {A82CFFAA-423D-473B-820C-174F7AE48B7E}.AppStore|x64.Build.0 = Debug|Any CPU + {A82CFFAA-423D-473B-820C-174F7AE48B7E}.AppStore|x64.Deploy.0 = Debug|Any CPU + {A82CFFAA-423D-473B-820C-174F7AE48B7E}.AppStore|x86.ActiveCfg = Debug|Any CPU + {A82CFFAA-423D-473B-820C-174F7AE48B7E}.AppStore|x86.Build.0 = Debug|Any CPU + {A82CFFAA-423D-473B-820C-174F7AE48B7E}.AppStore|x86.Deploy.0 = Debug|Any CPU {A82CFFAA-423D-473B-820C-174F7AE48B7E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {A82CFFAA-423D-473B-820C-174F7AE48B7E}.Debug|Any CPU.Build.0 = Debug|Any CPU {A82CFFAA-423D-473B-820C-174F7AE48B7E}.Debug|Any CPU.Deploy.0 = Debug|Any CPU + {A82CFFAA-423D-473B-820C-174F7AE48B7E}.Debug|ARM.ActiveCfg = Debug|Any CPU + {A82CFFAA-423D-473B-820C-174F7AE48B7E}.Debug|ARM.Build.0 = Debug|Any CPU + {A82CFFAA-423D-473B-820C-174F7AE48B7E}.Debug|ARM.Deploy.0 = Debug|Any CPU {A82CFFAA-423D-473B-820C-174F7AE48B7E}.Debug|iPhone.ActiveCfg = Debug|Any CPU {A82CFFAA-423D-473B-820C-174F7AE48B7E}.Debug|iPhone.Build.0 = Debug|Any CPU {A82CFFAA-423D-473B-820C-174F7AE48B7E}.Debug|iPhone.Deploy.0 = Debug|Any CPU {A82CFFAA-423D-473B-820C-174F7AE48B7E}.Debug|iPhoneSimulator.ActiveCfg = Debug|Any CPU {A82CFFAA-423D-473B-820C-174F7AE48B7E}.Debug|iPhoneSimulator.Build.0 = Debug|Any CPU {A82CFFAA-423D-473B-820C-174F7AE48B7E}.Debug|iPhoneSimulator.Deploy.0 = Debug|Any CPU + {A82CFFAA-423D-473B-820C-174F7AE48B7E}.Debug|x64.ActiveCfg = Debug|Any CPU + {A82CFFAA-423D-473B-820C-174F7AE48B7E}.Debug|x64.Build.0 = Debug|Any CPU + {A82CFFAA-423D-473B-820C-174F7AE48B7E}.Debug|x64.Deploy.0 = Debug|Any CPU + {A82CFFAA-423D-473B-820C-174F7AE48B7E}.Debug|x86.ActiveCfg = Debug|Any CPU + {A82CFFAA-423D-473B-820C-174F7AE48B7E}.Debug|x86.Build.0 = Debug|Any CPU + {A82CFFAA-423D-473B-820C-174F7AE48B7E}.Debug|x86.Deploy.0 = Debug|Any CPU {A82CFFAA-423D-473B-820C-174F7AE48B7E}.Release|Any CPU.ActiveCfg = Release|Any CPU {A82CFFAA-423D-473B-820C-174F7AE48B7E}.Release|Any CPU.Build.0 = Release|Any CPU {A82CFFAA-423D-473B-820C-174F7AE48B7E}.Release|Any CPU.Deploy.0 = Release|Any CPU + {A82CFFAA-423D-473B-820C-174F7AE48B7E}.Release|ARM.ActiveCfg = Release|Any CPU + {A82CFFAA-423D-473B-820C-174F7AE48B7E}.Release|ARM.Build.0 = Release|Any CPU + {A82CFFAA-423D-473B-820C-174F7AE48B7E}.Release|ARM.Deploy.0 = Release|Any CPU {A82CFFAA-423D-473B-820C-174F7AE48B7E}.Release|iPhone.ActiveCfg = Release|Any CPU {A82CFFAA-423D-473B-820C-174F7AE48B7E}.Release|iPhone.Build.0 = Release|Any CPU {A82CFFAA-423D-473B-820C-174F7AE48B7E}.Release|iPhone.Deploy.0 = Release|Any CPU {A82CFFAA-423D-473B-820C-174F7AE48B7E}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU {A82CFFAA-423D-473B-820C-174F7AE48B7E}.Release|iPhoneSimulator.Build.0 = Release|Any CPU {A82CFFAA-423D-473B-820C-174F7AE48B7E}.Release|iPhoneSimulator.Deploy.0 = Release|Any CPU + {A82CFFAA-423D-473B-820C-174F7AE48B7E}.Release|x64.ActiveCfg = Release|Any CPU + {A82CFFAA-423D-473B-820C-174F7AE48B7E}.Release|x64.Build.0 = Release|Any CPU + {A82CFFAA-423D-473B-820C-174F7AE48B7E}.Release|x64.Deploy.0 = Release|Any CPU + {A82CFFAA-423D-473B-820C-174F7AE48B7E}.Release|x86.ActiveCfg = Release|Any CPU + {A82CFFAA-423D-473B-820C-174F7AE48B7E}.Release|x86.Build.0 = Release|Any CPU + {A82CFFAA-423D-473B-820C-174F7AE48B7E}.Release|x86.Deploy.0 = Release|Any CPU {E52D9D91-6F31-42E2-8261-D85AA62D39D1}.Ad-Hoc|Any CPU.ActiveCfg = Ad-Hoc|iPhone {E52D9D91-6F31-42E2-8261-D85AA62D39D1}.Ad-Hoc|Any CPU.Build.0 = Ad-Hoc|iPhone {E52D9D91-6F31-42E2-8261-D85AA62D39D1}.Ad-Hoc|Any CPU.Deploy.0 = Ad-Hoc|iPhone + {E52D9D91-6F31-42E2-8261-D85AA62D39D1}.Ad-Hoc|ARM.ActiveCfg = Ad-Hoc|iPhoneSimulator {E52D9D91-6F31-42E2-8261-D85AA62D39D1}.Ad-Hoc|iPhone.ActiveCfg = Ad-Hoc|iPhone {E52D9D91-6F31-42E2-8261-D85AA62D39D1}.Ad-Hoc|iPhone.Build.0 = Ad-Hoc|iPhone {E52D9D91-6F31-42E2-8261-D85AA62D39D1}.Ad-Hoc|iPhone.Deploy.0 = Ad-Hoc|iPhone {E52D9D91-6F31-42E2-8261-D85AA62D39D1}.Ad-Hoc|iPhoneSimulator.ActiveCfg = Ad-Hoc|iPhoneSimulator {E52D9D91-6F31-42E2-8261-D85AA62D39D1}.Ad-Hoc|iPhoneSimulator.Build.0 = Ad-Hoc|iPhoneSimulator {E52D9D91-6F31-42E2-8261-D85AA62D39D1}.Ad-Hoc|iPhoneSimulator.Deploy.0 = Ad-Hoc|iPhoneSimulator + {E52D9D91-6F31-42E2-8261-D85AA62D39D1}.Ad-Hoc|x64.ActiveCfg = Ad-Hoc|iPhoneSimulator + {E52D9D91-6F31-42E2-8261-D85AA62D39D1}.Ad-Hoc|x86.ActiveCfg = Ad-Hoc|iPhoneSimulator {E52D9D91-6F31-42E2-8261-D85AA62D39D1}.AppStore|Any CPU.ActiveCfg = AppStore|iPhone {E52D9D91-6F31-42E2-8261-D85AA62D39D1}.AppStore|Any CPU.Build.0 = AppStore|iPhone {E52D9D91-6F31-42E2-8261-D85AA62D39D1}.AppStore|Any CPU.Deploy.0 = AppStore|iPhone + {E52D9D91-6F31-42E2-8261-D85AA62D39D1}.AppStore|ARM.ActiveCfg = AppStore|iPhoneSimulator {E52D9D91-6F31-42E2-8261-D85AA62D39D1}.AppStore|iPhone.ActiveCfg = AppStore|iPhone {E52D9D91-6F31-42E2-8261-D85AA62D39D1}.AppStore|iPhone.Build.0 = AppStore|iPhone {E52D9D91-6F31-42E2-8261-D85AA62D39D1}.AppStore|iPhone.Deploy.0 = AppStore|iPhone {E52D9D91-6F31-42E2-8261-D85AA62D39D1}.AppStore|iPhoneSimulator.ActiveCfg = AppStore|iPhoneSimulator {E52D9D91-6F31-42E2-8261-D85AA62D39D1}.AppStore|iPhoneSimulator.Build.0 = AppStore|iPhoneSimulator {E52D9D91-6F31-42E2-8261-D85AA62D39D1}.AppStore|iPhoneSimulator.Deploy.0 = AppStore|iPhoneSimulator + {E52D9D91-6F31-42E2-8261-D85AA62D39D1}.AppStore|x64.ActiveCfg = AppStore|iPhoneSimulator + {E52D9D91-6F31-42E2-8261-D85AA62D39D1}.AppStore|x86.ActiveCfg = AppStore|iPhoneSimulator {E52D9D91-6F31-42E2-8261-D85AA62D39D1}.Debug|Any CPU.ActiveCfg = Debug|iPhoneSimulator {E52D9D91-6F31-42E2-8261-D85AA62D39D1}.Debug|Any CPU.Build.0 = Debug|iPhoneSimulator {E52D9D91-6F31-42E2-8261-D85AA62D39D1}.Debug|Any CPU.Deploy.0 = Debug|iPhoneSimulator + {E52D9D91-6F31-42E2-8261-D85AA62D39D1}.Debug|ARM.ActiveCfg = Debug|iPhone {E52D9D91-6F31-42E2-8261-D85AA62D39D1}.Debug|iPhone.ActiveCfg = Debug|iPhone {E52D9D91-6F31-42E2-8261-D85AA62D39D1}.Debug|iPhone.Build.0 = Debug|iPhone {E52D9D91-6F31-42E2-8261-D85AA62D39D1}.Debug|iPhone.Deploy.0 = Debug|iPhone {E52D9D91-6F31-42E2-8261-D85AA62D39D1}.Debug|iPhoneSimulator.ActiveCfg = Debug|iPhoneSimulator {E52D9D91-6F31-42E2-8261-D85AA62D39D1}.Debug|iPhoneSimulator.Build.0 = Debug|iPhoneSimulator {E52D9D91-6F31-42E2-8261-D85AA62D39D1}.Debug|iPhoneSimulator.Deploy.0 = Debug|iPhoneSimulator + {E52D9D91-6F31-42E2-8261-D85AA62D39D1}.Debug|x64.ActiveCfg = Debug|iPhone + {E52D9D91-6F31-42E2-8261-D85AA62D39D1}.Debug|x86.ActiveCfg = Debug|iPhone {E52D9D91-6F31-42E2-8261-D85AA62D39D1}.Release|Any CPU.ActiveCfg = Release|iPhone {E52D9D91-6F31-42E2-8261-D85AA62D39D1}.Release|Any CPU.Build.0 = Release|iPhone {E52D9D91-6F31-42E2-8261-D85AA62D39D1}.Release|Any CPU.Deploy.0 = Release|iPhone + {E52D9D91-6F31-42E2-8261-D85AA62D39D1}.Release|ARM.ActiveCfg = Release|iPhoneSimulator {E52D9D91-6F31-42E2-8261-D85AA62D39D1}.Release|iPhone.ActiveCfg = Release|iPhone {E52D9D91-6F31-42E2-8261-D85AA62D39D1}.Release|iPhone.Build.0 = Release|iPhone {E52D9D91-6F31-42E2-8261-D85AA62D39D1}.Release|iPhone.Deploy.0 = Release|iPhone {E52D9D91-6F31-42E2-8261-D85AA62D39D1}.Release|iPhoneSimulator.ActiveCfg = Release|iPhoneSimulator {E52D9D91-6F31-42E2-8261-D85AA62D39D1}.Release|iPhoneSimulator.Build.0 = Release|iPhoneSimulator {E52D9D91-6F31-42E2-8261-D85AA62D39D1}.Release|iPhoneSimulator.Deploy.0 = Release|iPhoneSimulator + {E52D9D91-6F31-42E2-8261-D85AA62D39D1}.Release|x64.ActiveCfg = Release|iPhoneSimulator + {E52D9D91-6F31-42E2-8261-D85AA62D39D1}.Release|x86.ActiveCfg = Release|iPhoneSimulator {852D9034-471A-42D0-8701-63D12E2EDACA}.Ad-Hoc|Any CPU.ActiveCfg = Debug|Any CPU {852D9034-471A-42D0-8701-63D12E2EDACA}.Ad-Hoc|Any CPU.Build.0 = Debug|Any CPU {852D9034-471A-42D0-8701-63D12E2EDACA}.Ad-Hoc|Any CPU.Deploy.0 = Debug|Any CPU + {852D9034-471A-42D0-8701-63D12E2EDACA}.Ad-Hoc|ARM.ActiveCfg = Release|Any CPU + {852D9034-471A-42D0-8701-63D12E2EDACA}.Ad-Hoc|ARM.Build.0 = Release|Any CPU {852D9034-471A-42D0-8701-63D12E2EDACA}.Ad-Hoc|iPhone.ActiveCfg = Debug|Any CPU {852D9034-471A-42D0-8701-63D12E2EDACA}.Ad-Hoc|iPhone.Build.0 = Debug|Any CPU {852D9034-471A-42D0-8701-63D12E2EDACA}.Ad-Hoc|iPhone.Deploy.0 = Debug|Any CPU {852D9034-471A-42D0-8701-63D12E2EDACA}.Ad-Hoc|iPhoneSimulator.ActiveCfg = Debug|Any CPU {852D9034-471A-42D0-8701-63D12E2EDACA}.Ad-Hoc|iPhoneSimulator.Build.0 = Debug|Any CPU {852D9034-471A-42D0-8701-63D12E2EDACA}.Ad-Hoc|iPhoneSimulator.Deploy.0 = Debug|Any CPU + {852D9034-471A-42D0-8701-63D12E2EDACA}.Ad-Hoc|x64.ActiveCfg = Release|Any CPU + {852D9034-471A-42D0-8701-63D12E2EDACA}.Ad-Hoc|x64.Build.0 = Release|Any CPU + {852D9034-471A-42D0-8701-63D12E2EDACA}.Ad-Hoc|x86.ActiveCfg = Release|Any CPU + {852D9034-471A-42D0-8701-63D12E2EDACA}.Ad-Hoc|x86.Build.0 = Release|Any CPU {852D9034-471A-42D0-8701-63D12E2EDACA}.AppStore|Any CPU.ActiveCfg = Debug|Any CPU {852D9034-471A-42D0-8701-63D12E2EDACA}.AppStore|Any CPU.Build.0 = Debug|Any CPU {852D9034-471A-42D0-8701-63D12E2EDACA}.AppStore|Any CPU.Deploy.0 = Debug|Any CPU + {852D9034-471A-42D0-8701-63D12E2EDACA}.AppStore|ARM.ActiveCfg = Release|Any CPU + {852D9034-471A-42D0-8701-63D12E2EDACA}.AppStore|ARM.Build.0 = Release|Any CPU {852D9034-471A-42D0-8701-63D12E2EDACA}.AppStore|iPhone.ActiveCfg = Debug|Any CPU {852D9034-471A-42D0-8701-63D12E2EDACA}.AppStore|iPhone.Build.0 = Debug|Any CPU {852D9034-471A-42D0-8701-63D12E2EDACA}.AppStore|iPhone.Deploy.0 = Debug|Any CPU {852D9034-471A-42D0-8701-63D12E2EDACA}.AppStore|iPhoneSimulator.ActiveCfg = Debug|Any CPU {852D9034-471A-42D0-8701-63D12E2EDACA}.AppStore|iPhoneSimulator.Build.0 = Debug|Any CPU {852D9034-471A-42D0-8701-63D12E2EDACA}.AppStore|iPhoneSimulator.Deploy.0 = Debug|Any CPU + {852D9034-471A-42D0-8701-63D12E2EDACA}.AppStore|x64.ActiveCfg = Release|Any CPU + {852D9034-471A-42D0-8701-63D12E2EDACA}.AppStore|x64.Build.0 = Release|Any CPU + {852D9034-471A-42D0-8701-63D12E2EDACA}.AppStore|x86.ActiveCfg = Release|Any CPU + {852D9034-471A-42D0-8701-63D12E2EDACA}.AppStore|x86.Build.0 = Release|Any CPU {852D9034-471A-42D0-8701-63D12E2EDACA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {852D9034-471A-42D0-8701-63D12E2EDACA}.Debug|Any CPU.Build.0 = Debug|Any CPU {852D9034-471A-42D0-8701-63D12E2EDACA}.Debug|Any CPU.Deploy.0 = Debug|Any CPU + {852D9034-471A-42D0-8701-63D12E2EDACA}.Debug|ARM.ActiveCfg = Debug|Any CPU + {852D9034-471A-42D0-8701-63D12E2EDACA}.Debug|ARM.Build.0 = Debug|Any CPU {852D9034-471A-42D0-8701-63D12E2EDACA}.Debug|iPhone.ActiveCfg = Debug|Any CPU {852D9034-471A-42D0-8701-63D12E2EDACA}.Debug|iPhone.Build.0 = Debug|Any CPU {852D9034-471A-42D0-8701-63D12E2EDACA}.Debug|iPhone.Deploy.0 = Debug|Any CPU {852D9034-471A-42D0-8701-63D12E2EDACA}.Debug|iPhoneSimulator.ActiveCfg = Debug|Any CPU {852D9034-471A-42D0-8701-63D12E2EDACA}.Debug|iPhoneSimulator.Build.0 = Debug|Any CPU {852D9034-471A-42D0-8701-63D12E2EDACA}.Debug|iPhoneSimulator.Deploy.0 = Debug|Any CPU + {852D9034-471A-42D0-8701-63D12E2EDACA}.Debug|x64.ActiveCfg = Debug|Any CPU + {852D9034-471A-42D0-8701-63D12E2EDACA}.Debug|x64.Build.0 = Debug|Any CPU + {852D9034-471A-42D0-8701-63D12E2EDACA}.Debug|x86.ActiveCfg = Debug|Any CPU + {852D9034-471A-42D0-8701-63D12E2EDACA}.Debug|x86.Build.0 = Debug|Any CPU {852D9034-471A-42D0-8701-63D12E2EDACA}.Release|Any CPU.ActiveCfg = Release|Any CPU {852D9034-471A-42D0-8701-63D12E2EDACA}.Release|Any CPU.Build.0 = Release|Any CPU {852D9034-471A-42D0-8701-63D12E2EDACA}.Release|Any CPU.Deploy.0 = Release|Any CPU + {852D9034-471A-42D0-8701-63D12E2EDACA}.Release|ARM.ActiveCfg = Release|Any CPU + {852D9034-471A-42D0-8701-63D12E2EDACA}.Release|ARM.Build.0 = Release|Any CPU {852D9034-471A-42D0-8701-63D12E2EDACA}.Release|iPhone.ActiveCfg = Release|Any CPU {852D9034-471A-42D0-8701-63D12E2EDACA}.Release|iPhone.Build.0 = Release|Any CPU {852D9034-471A-42D0-8701-63D12E2EDACA}.Release|iPhone.Deploy.0 = Release|Any CPU {852D9034-471A-42D0-8701-63D12E2EDACA}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU {852D9034-471A-42D0-8701-63D12E2EDACA}.Release|iPhoneSimulator.Build.0 = Release|Any CPU {852D9034-471A-42D0-8701-63D12E2EDACA}.Release|iPhoneSimulator.Deploy.0 = Release|Any CPU + {852D9034-471A-42D0-8701-63D12E2EDACA}.Release|x64.ActiveCfg = Release|Any CPU + {852D9034-471A-42D0-8701-63D12E2EDACA}.Release|x64.Build.0 = Release|Any CPU + {852D9034-471A-42D0-8701-63D12E2EDACA}.Release|x86.ActiveCfg = Release|Any CPU + {852D9034-471A-42D0-8701-63D12E2EDACA}.Release|x86.Build.0 = Release|Any CPU + {DC1AE8D2-77CD-4C25-8E43-D2D3F3E68339}.Ad-Hoc|Any CPU.ActiveCfg = Debug|ARM + {DC1AE8D2-77CD-4C25-8E43-D2D3F3E68339}.Ad-Hoc|Any CPU.Build.0 = Debug|ARM + {DC1AE8D2-77CD-4C25-8E43-D2D3F3E68339}.Ad-Hoc|Any CPU.Deploy.0 = Debug|ARM + {DC1AE8D2-77CD-4C25-8E43-D2D3F3E68339}.Ad-Hoc|ARM.ActiveCfg = Debug|ARM + {DC1AE8D2-77CD-4C25-8E43-D2D3F3E68339}.Ad-Hoc|ARM.Build.0 = Debug|ARM + {DC1AE8D2-77CD-4C25-8E43-D2D3F3E68339}.Ad-Hoc|ARM.Deploy.0 = Debug|ARM + {DC1AE8D2-77CD-4C25-8E43-D2D3F3E68339}.Ad-Hoc|iPhone.ActiveCfg = Debug|ARM + {DC1AE8D2-77CD-4C25-8E43-D2D3F3E68339}.Ad-Hoc|iPhone.Build.0 = Debug|ARM + {DC1AE8D2-77CD-4C25-8E43-D2D3F3E68339}.Ad-Hoc|iPhone.Deploy.0 = Debug|ARM + {DC1AE8D2-77CD-4C25-8E43-D2D3F3E68339}.Ad-Hoc|iPhoneSimulator.ActiveCfg = Debug|ARM + {DC1AE8D2-77CD-4C25-8E43-D2D3F3E68339}.Ad-Hoc|iPhoneSimulator.Build.0 = Debug|ARM + {DC1AE8D2-77CD-4C25-8E43-D2D3F3E68339}.Ad-Hoc|iPhoneSimulator.Deploy.0 = Debug|ARM + {DC1AE8D2-77CD-4C25-8E43-D2D3F3E68339}.Ad-Hoc|x64.ActiveCfg = Debug|x64 + {DC1AE8D2-77CD-4C25-8E43-D2D3F3E68339}.Ad-Hoc|x64.Build.0 = Debug|x64 + {DC1AE8D2-77CD-4C25-8E43-D2D3F3E68339}.Ad-Hoc|x64.Deploy.0 = Debug|x64 + {DC1AE8D2-77CD-4C25-8E43-D2D3F3E68339}.Ad-Hoc|x86.ActiveCfg = Debug|x86 + {DC1AE8D2-77CD-4C25-8E43-D2D3F3E68339}.Ad-Hoc|x86.Build.0 = Debug|x86 + {DC1AE8D2-77CD-4C25-8E43-D2D3F3E68339}.Ad-Hoc|x86.Deploy.0 = Debug|x86 + {DC1AE8D2-77CD-4C25-8E43-D2D3F3E68339}.AppStore|Any CPU.ActiveCfg = Debug|ARM + {DC1AE8D2-77CD-4C25-8E43-D2D3F3E68339}.AppStore|Any CPU.Build.0 = Debug|ARM + {DC1AE8D2-77CD-4C25-8E43-D2D3F3E68339}.AppStore|Any CPU.Deploy.0 = Debug|ARM + {DC1AE8D2-77CD-4C25-8E43-D2D3F3E68339}.AppStore|ARM.ActiveCfg = Debug|ARM + {DC1AE8D2-77CD-4C25-8E43-D2D3F3E68339}.AppStore|ARM.Build.0 = Debug|ARM + {DC1AE8D2-77CD-4C25-8E43-D2D3F3E68339}.AppStore|ARM.Deploy.0 = Debug|ARM + {DC1AE8D2-77CD-4C25-8E43-D2D3F3E68339}.AppStore|iPhone.ActiveCfg = Debug|ARM + {DC1AE8D2-77CD-4C25-8E43-D2D3F3E68339}.AppStore|iPhone.Build.0 = Debug|ARM + {DC1AE8D2-77CD-4C25-8E43-D2D3F3E68339}.AppStore|iPhone.Deploy.0 = Debug|ARM + {DC1AE8D2-77CD-4C25-8E43-D2D3F3E68339}.AppStore|iPhoneSimulator.ActiveCfg = Debug|ARM + {DC1AE8D2-77CD-4C25-8E43-D2D3F3E68339}.AppStore|iPhoneSimulator.Build.0 = Debug|ARM + {DC1AE8D2-77CD-4C25-8E43-D2D3F3E68339}.AppStore|iPhoneSimulator.Deploy.0 = Debug|ARM + {DC1AE8D2-77CD-4C25-8E43-D2D3F3E68339}.AppStore|x64.ActiveCfg = Debug|x64 + {DC1AE8D2-77CD-4C25-8E43-D2D3F3E68339}.AppStore|x64.Build.0 = Debug|x64 + {DC1AE8D2-77CD-4C25-8E43-D2D3F3E68339}.AppStore|x64.Deploy.0 = Debug|x64 + {DC1AE8D2-77CD-4C25-8E43-D2D3F3E68339}.AppStore|x86.ActiveCfg = Debug|x86 + {DC1AE8D2-77CD-4C25-8E43-D2D3F3E68339}.AppStore|x86.Build.0 = Debug|x86 + {DC1AE8D2-77CD-4C25-8E43-D2D3F3E68339}.AppStore|x86.Deploy.0 = Debug|x86 + {DC1AE8D2-77CD-4C25-8E43-D2D3F3E68339}.Debug|Any CPU.ActiveCfg = Debug|x86 + {DC1AE8D2-77CD-4C25-8E43-D2D3F3E68339}.Debug|ARM.ActiveCfg = Debug|ARM + {DC1AE8D2-77CD-4C25-8E43-D2D3F3E68339}.Debug|ARM.Build.0 = Debug|ARM + {DC1AE8D2-77CD-4C25-8E43-D2D3F3E68339}.Debug|ARM.Deploy.0 = Debug|ARM + {DC1AE8D2-77CD-4C25-8E43-D2D3F3E68339}.Debug|iPhone.ActiveCfg = Debug|x86 + {DC1AE8D2-77CD-4C25-8E43-D2D3F3E68339}.Debug|iPhoneSimulator.ActiveCfg = Debug|x86 + {DC1AE8D2-77CD-4C25-8E43-D2D3F3E68339}.Debug|x64.ActiveCfg = Debug|x64 + {DC1AE8D2-77CD-4C25-8E43-D2D3F3E68339}.Debug|x64.Build.0 = Debug|x64 + {DC1AE8D2-77CD-4C25-8E43-D2D3F3E68339}.Debug|x64.Deploy.0 = Debug|x64 + {DC1AE8D2-77CD-4C25-8E43-D2D3F3E68339}.Debug|x86.ActiveCfg = Debug|x86 + {DC1AE8D2-77CD-4C25-8E43-D2D3F3E68339}.Debug|x86.Build.0 = Debug|x86 + {DC1AE8D2-77CD-4C25-8E43-D2D3F3E68339}.Debug|x86.Deploy.0 = Debug|x86 + {DC1AE8D2-77CD-4C25-8E43-D2D3F3E68339}.Release|Any CPU.ActiveCfg = Release|x86 + {DC1AE8D2-77CD-4C25-8E43-D2D3F3E68339}.Release|ARM.ActiveCfg = Release|ARM + {DC1AE8D2-77CD-4C25-8E43-D2D3F3E68339}.Release|ARM.Build.0 = Release|ARM + {DC1AE8D2-77CD-4C25-8E43-D2D3F3E68339}.Release|ARM.Deploy.0 = Release|ARM + {DC1AE8D2-77CD-4C25-8E43-D2D3F3E68339}.Release|iPhone.ActiveCfg = Release|x86 + {DC1AE8D2-77CD-4C25-8E43-D2D3F3E68339}.Release|iPhoneSimulator.ActiveCfg = Release|x86 + {DC1AE8D2-77CD-4C25-8E43-D2D3F3E68339}.Release|x64.ActiveCfg = Release|x64 + {DC1AE8D2-77CD-4C25-8E43-D2D3F3E68339}.Release|x64.Build.0 = Release|x64 + {DC1AE8D2-77CD-4C25-8E43-D2D3F3E68339}.Release|x64.Deploy.0 = Release|x64 + {DC1AE8D2-77CD-4C25-8E43-D2D3F3E68339}.Release|x86.ActiveCfg = Release|x86 + {DC1AE8D2-77CD-4C25-8E43-D2D3F3E68339}.Release|x86.Build.0 = Release|x86 + {DC1AE8D2-77CD-4C25-8E43-D2D3F3E68339}.Release|x86.Deploy.0 = Release|x86 EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/BrickController2/BrickController2.Android/PlatformServices/BluetoothLE/BluetoothLEDevice.cs b/BrickController2/BrickController2.Android/PlatformServices/BluetoothLE/BluetoothLEDevice.cs index 54cdc625..283f287d 100644 --- a/BrickController2/BrickController2.Android/PlatformServices/BluetoothLE/BluetoothLEDevice.cs +++ b/BrickController2/BrickController2.Android/PlatformServices/BluetoothLE/BluetoothLEDevice.cs @@ -131,7 +131,6 @@ public Task DisconnectAsync() { Disconnect(); } - return Task.CompletedTask; } diff --git a/BrickController2/BrickController2.UWP/App.xaml b/BrickController2/BrickController2.UWP/App.xaml new file mode 100644 index 00000000..9fed9db8 --- /dev/null +++ b/BrickController2/BrickController2.UWP/App.xaml @@ -0,0 +1,7 @@ + + + diff --git a/BrickController2/BrickController2.UWP/App.xaml.cs b/BrickController2/BrickController2.UWP/App.xaml.cs new file mode 100644 index 00000000..95eadb51 --- /dev/null +++ b/BrickController2/BrickController2.UWP/App.xaml.cs @@ -0,0 +1,83 @@ +using BrickController2.Helpers; +using System; +using Windows.ApplicationModel; +using Windows.ApplicationModel.Activation; +using Windows.UI.Xaml; +using Windows.UI.Xaml.Controls; + +namespace BrickController2.Windows +{ + /// + /// Provides application-specific behavior to supplement the default Application class. + /// + sealed partial class App : Application + { + /// + /// Initializes the singleton application object. This is the first line of authored code + /// executed, and as such is the logical equivalent of main() or WinMain(). + /// + public App() + { + // workaround resource loading for EN language + // enforce blocking of modern resource loading so as EN resources are properly loaded + var location = System.IO.Path.GetDirectoryName(typeof(ResourceHelper).Assembly.Location); + AppDomain.CurrentDomain.SetData("PLATFORM_RESOURCE_ROOTS", location); + + + this.InitializeComponent(); + this.Suspending += OnSuspending; + } + + /// + /// Invoked when the application is launched normally by the end user. Other entry points + /// will be used such as when the application is launched to open a specific file. + /// + /// Details about the launch request and process. + protected override void OnLaunched(LaunchActivatedEventArgs e) + { +#if DEBUG + if (System.Diagnostics.Debugger.IsAttached) + { + this.DebugSettings.EnableFrameRateCounter = true; + } +#endif + + Frame rootFrame = Window.Current.Content as Frame; + + // Do not repeat app initialization when the Window already has content, + if (rootFrame == null) + { + // Create a Frame to act as the navigation context and navigate to the first page + rootFrame = new Frame(); + + + Xamarin.Forms.Forms.Init(e); + + // Place the frame in the current Window + Window.Current.Content = rootFrame; + } + + if (rootFrame.Content == null) + { + // navigate to the first page + rootFrame.Navigate(typeof(MainPage), e.Arguments); + } + // Ensure the current window is active + Window.Current.Activate(); + } + + + /// + /// Invoked when application execution is being suspended. Application state is saved + /// without knowing whether the application will be terminated or resumed with the contents + /// of memory still intact. + /// + /// The source of the suspend request. + /// Details about the suspend request. + private void OnSuspending(object sender, SuspendingEventArgs e) + { + var deferral = e.SuspendingOperation.GetDeferral(); + deferral.Complete(); + } + } +} diff --git a/BrickController2/BrickController2.UWP/Assets/SplashScreen.scale-100.png b/BrickController2/BrickController2.UWP/Assets/SplashScreen.scale-100.png new file mode 100644 index 00000000..d014072a Binary files /dev/null and b/BrickController2/BrickController2.UWP/Assets/SplashScreen.scale-100.png differ diff --git a/BrickController2/BrickController2.UWP/Assets/SplashScreen.scale-200.png b/BrickController2/BrickController2.UWP/Assets/SplashScreen.scale-200.png new file mode 100644 index 00000000..32e82d21 Binary files /dev/null and b/BrickController2/BrickController2.UWP/Assets/SplashScreen.scale-200.png differ diff --git a/BrickController2/BrickController2.UWP/Assets/SplashScreen.scale-400.png b/BrickController2/BrickController2.UWP/Assets/SplashScreen.scale-400.png new file mode 100644 index 00000000..5edb898d Binary files /dev/null and b/BrickController2/BrickController2.UWP/Assets/SplashScreen.scale-400.png differ diff --git a/BrickController2/BrickController2.UWP/Assets/Square44x44Logo.altform-unplated_targetsize-16.png b/BrickController2/BrickController2.UWP/Assets/Square44x44Logo.altform-unplated_targetsize-16.png new file mode 100644 index 00000000..5d026440 Binary files /dev/null and b/BrickController2/BrickController2.UWP/Assets/Square44x44Logo.altform-unplated_targetsize-16.png differ diff --git a/BrickController2/BrickController2.UWP/Assets/Square44x44Logo.altform-unplated_targetsize-256.png b/BrickController2/BrickController2.UWP/Assets/Square44x44Logo.altform-unplated_targetsize-256.png new file mode 100644 index 00000000..3e1b0a80 Binary files /dev/null and b/BrickController2/BrickController2.UWP/Assets/Square44x44Logo.altform-unplated_targetsize-256.png differ diff --git a/BrickController2/BrickController2.UWP/Assets/Square44x44Logo.altform-unplated_targetsize-48.png b/BrickController2/BrickController2.UWP/Assets/Square44x44Logo.altform-unplated_targetsize-48.png new file mode 100644 index 00000000..6a86ecc7 Binary files /dev/null and b/BrickController2/BrickController2.UWP/Assets/Square44x44Logo.altform-unplated_targetsize-48.png differ diff --git a/BrickController2/BrickController2.UWP/Assets/Square44x44Logo.scale-100.png b/BrickController2/BrickController2.UWP/Assets/Square44x44Logo.scale-100.png new file mode 100644 index 00000000..dce32382 Binary files /dev/null and b/BrickController2/BrickController2.UWP/Assets/Square44x44Logo.scale-100.png differ diff --git a/BrickController2/BrickController2.UWP/Assets/Square44x44Logo.scale-200.png b/BrickController2/BrickController2.UWP/Assets/Square44x44Logo.scale-200.png new file mode 100644 index 00000000..e4638aac Binary files /dev/null and b/BrickController2/BrickController2.UWP/Assets/Square44x44Logo.scale-200.png differ diff --git a/BrickController2/BrickController2.UWP/Assets/Square44x44Logo.scale-400.png b/BrickController2/BrickController2.UWP/Assets/Square44x44Logo.scale-400.png new file mode 100644 index 00000000..5776f819 Binary files /dev/null and b/BrickController2/BrickController2.UWP/Assets/Square44x44Logo.scale-400.png differ diff --git a/BrickController2/BrickController2.UWP/Assets/Square44x44Logo.targetsize-16.png b/BrickController2/BrickController2.UWP/Assets/Square44x44Logo.targetsize-16.png new file mode 100644 index 00000000..5d026440 Binary files /dev/null and b/BrickController2/BrickController2.UWP/Assets/Square44x44Logo.targetsize-16.png differ diff --git a/BrickController2/BrickController2.UWP/Assets/Square44x44Logo.targetsize-256.png b/BrickController2/BrickController2.UWP/Assets/Square44x44Logo.targetsize-256.png new file mode 100644 index 00000000..3e1b0a80 Binary files /dev/null and b/BrickController2/BrickController2.UWP/Assets/Square44x44Logo.targetsize-256.png differ diff --git a/BrickController2/BrickController2.UWP/Assets/Square44x44Logo.targetsize-48.png b/BrickController2/BrickController2.UWP/Assets/Square44x44Logo.targetsize-48.png new file mode 100644 index 00000000..6a86ecc7 Binary files /dev/null and b/BrickController2/BrickController2.UWP/Assets/Square44x44Logo.targetsize-48.png differ diff --git a/BrickController2/BrickController2.UWP/Assets/StoreLogo.scale-100.png b/BrickController2/BrickController2.UWP/Assets/StoreLogo.scale-100.png new file mode 100644 index 00000000..dce32382 Binary files /dev/null and b/BrickController2/BrickController2.UWP/Assets/StoreLogo.scale-100.png differ diff --git a/BrickController2/BrickController2.UWP/Assets/StoreLogo.scale-200.png b/BrickController2/BrickController2.UWP/Assets/StoreLogo.scale-200.png new file mode 100644 index 00000000..e4638aac Binary files /dev/null and b/BrickController2/BrickController2.UWP/Assets/StoreLogo.scale-200.png differ diff --git a/BrickController2/BrickController2.UWP/Assets/StoreLogo.scale-400.png b/BrickController2/BrickController2.UWP/Assets/StoreLogo.scale-400.png new file mode 100644 index 00000000..5776f819 Binary files /dev/null and b/BrickController2/BrickController2.UWP/Assets/StoreLogo.scale-400.png differ diff --git a/BrickController2/BrickController2.UWP/Assets/ic_launcher.png b/BrickController2/BrickController2.UWP/Assets/ic_launcher.png new file mode 100644 index 00000000..b00cbb33 Binary files /dev/null and b/BrickController2/BrickController2.UWP/Assets/ic_launcher.png differ diff --git a/BrickController2/BrickController2.UWP/BrickController2.UWP.csproj b/BrickController2/BrickController2.UWP/BrickController2.UWP.csproj new file mode 100644 index 00000000..1a0a9da0 --- /dev/null +++ b/BrickController2/BrickController2.UWP/BrickController2.UWP.csproj @@ -0,0 +1,173 @@ + + + + + Debug + x86 + {DC1AE8D2-77CD-4C25-8E43-D2D3F3E68339} + {98C37F10-6541-44BE-B1E6-7EB3EB8C08F1} + AppContainerExe + Properties + BrickController2.Windows + BrickController2.UWP + en + UAP + 10.0.19041.0 + 10.0.16299.0 + 14 + true + 512 + {A5A43C5B-DE2A-4C0C-9213-0A381AF9435A};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} + false + + + true + bin\ARM\Debug\ + DEBUG;TRACE;NETFX_CORE;WINDOWS_UWP + ;2008 + portable + ARM + false + prompt + true + + + bin\ARM\Release\ + TRACE;NETFX_CORE;WINDOWS_UWP + true + ;2008 + pdbonly + ARM + false + prompt + true + true + + + true + bin\x64\Debug\ + DEBUG;TRACE;NETFX_CORE;WINDOWS_UWP + ;2008 + portable + x64 + false + prompt + true + true + + + bin\x64\Release\ + TRACE;NETFX_CORE;WINDOWS_UWP + true + ;2008 + pdbonly + x64 + false + prompt + true + true + + + true + bin\x86\Debug\ + DEBUG;TRACE;NETFX_CORE;WINDOWS_UWP + ;2008 + portable + x86 + false + prompt + true + + + bin\x86\Release\ + TRACE;NETFX_CORE;WINDOWS_UWP + true + ;2008 + pdbonly + x86 + false + prompt + true + true + + + + App.xaml + + + + + + + MainPage.xaml + + + + + + + + + + + + + + + + + + + + + + + + Designer + + + + + + + + + + + + + + + + + + + + + + + MSBuild:Compile + Designer + + + MSBuild:Compile + Designer + + + + + + + + + + {B1508B2C-0A46-4407-9984-D4412DEF337F} + BrickController2 + + + + + 14.0 + + + \ No newline at end of file diff --git a/BrickController2/BrickController2.UWP/Extensions/AdvertismentExtensions.cs b/BrickController2/BrickController2.UWP/Extensions/AdvertismentExtensions.cs new file mode 100644 index 00000000..2164c041 --- /dev/null +++ b/BrickController2/BrickController2.UWP/Extensions/AdvertismentExtensions.cs @@ -0,0 +1,23 @@ +using Windows.Devices.Bluetooth.Advertisement; + +namespace BrickController2.Windows.Extensions +{ + public static class AdvertismentExtensions + { + public static string GetLocalName(this BluetoothLEAdvertisementReceivedEventArgs args) + { + return args.Advertisement.LocalName.TrimEnd(); + } + + public static bool IsValidDeviceName(this string deviceName) + { + return !string.IsNullOrEmpty(deviceName); + } + + public static bool CanCarryData(this BluetoothLEAdvertisementReceivedEventArgs args) + { + return args.AdvertisementType == BluetoothLEAdvertisementType.ScanResponse || + args.AdvertisementType == BluetoothLEAdvertisementType.ConnectableUndirected; + } + } +} diff --git a/BrickController2/BrickController2.UWP/Extensions/ControllerExtensions.cs b/BrickController2/BrickController2.UWP/Extensions/ControllerExtensions.cs new file mode 100644 index 00000000..0c6c3f9d --- /dev/null +++ b/BrickController2/BrickController2.UWP/Extensions/ControllerExtensions.cs @@ -0,0 +1,13 @@ +using Windows.Gaming.Input; + +namespace BrickController2.Windows.Extensions +{ + public static class ControllerExtensions + { + public static string GetDeviceId(this Gamepad gamepad) + { + // kinda hack + return gamepad.User.NonRoamableId; + } + } +} diff --git a/BrickController2/BrickController2.UWP/Extensions/ConvertExtensions.cs b/BrickController2/BrickController2.UWP/Extensions/ConvertExtensions.cs new file mode 100644 index 00000000..e647fb2a --- /dev/null +++ b/BrickController2/BrickController2.UWP/Extensions/ConvertExtensions.cs @@ -0,0 +1,59 @@ +namespace BrickController2.Windows.Extensions +{ + public static class ConvertExtensions + { + public static string ToBluetoothAddressString(this ulong bluetoothAddress) + { + // 48bit physical BT address + var a = (byte)((bluetoothAddress >> 40) & 0xFF); + var b = (byte)((bluetoothAddress >> 32) & 0xFF); + var c = (byte)((bluetoothAddress >> 24) & 0xFF); + var d = (byte)((bluetoothAddress >> 16) & 0xFF); + var e = (byte)((bluetoothAddress >> 8) & 0xFF); + var f = (byte)(bluetoothAddress & 0xFF); + + return $"{a:X2}:{b:X2}:{c:X2}:{d:X2}:{e:X2}:{f:X2}"; + } + + public static bool TryParseBluetoothAddressString(this string stringValue, out ulong bluetoothAddress) + { + bluetoothAddress = default; + + if (string.IsNullOrEmpty(stringValue) || stringValue.Length != 17) + { + return false; + } + + ulong value = 0; + + for (int i = 1; i <= stringValue.Length; i++) + { + var ch = (uint)stringValue[i - 1]; + if (i % 3 == 0) + { + if (ch != '-' && ch != ':') + { + // missing dash + return false; + } + } + else if (ch >= 0x30 && ch <= 0x39) + { + value = (value << 4) + ch - 0x30; + } + else if (ch >= 0x41 && ch <= 0x46) + { + value = (value << 4) + ch - 0x37; + } + else + { + // wrong character + return false; + } + } + + bluetoothAddress = value; + return true; + } + } +} diff --git a/BrickController2/BrickController2.UWP/Extensions/IBufferExtensions.cs b/BrickController2/BrickController2.UWP/Extensions/IBufferExtensions.cs new file mode 100644 index 00000000..5f435316 --- /dev/null +++ b/BrickController2/BrickController2.UWP/Extensions/IBufferExtensions.cs @@ -0,0 +1,26 @@ +using Windows.Storage.Streams; + +namespace BrickController2.Windows.Extensions +{ + public static class IBufferExtensions + { + public static IBuffer ToBuffer(this byte[] data) + { + var writer = new DataWriter(); + writer.WriteBytes(data); + + return writer.DetachBuffer(); + } + + public static byte[] ToByteArray(this IBuffer buffer) + { + using (var reader = DataReader.FromBuffer(buffer)) + { + byte[] input = new byte[reader.UnconsumedBufferLength]; + reader.ReadBytes(input); + + return input; + } + } + } +} diff --git a/BrickController2/BrickController2.UWP/MainPage.xaml b/BrickController2/BrickController2.UWP/MainPage.xaml new file mode 100644 index 00000000..171e0fac --- /dev/null +++ b/BrickController2/BrickController2.UWP/MainPage.xaml @@ -0,0 +1,12 @@ + + + + + + \ No newline at end of file diff --git a/BrickController2/BrickController2.UWP/MainPage.xaml.cs b/BrickController2/BrickController2.UWP/MainPage.xaml.cs new file mode 100644 index 00000000..895b6366 --- /dev/null +++ b/BrickController2/BrickController2.UWP/MainPage.xaml.cs @@ -0,0 +1,53 @@ +using Autofac; +using BrickController2.BusinessLogic.DI; +using BrickController2.CreationManagement.DI; +using BrickController2.Database.DI; +using BrickController2.DeviceManagement.DI; +using BrickController2.UI.DI; +using BrickController2.Windows.PlatformServices.DI; +using BrickController2.Windows.PlatformServices.GameController; +using Windows.UI; +using Windows.UI.ViewManagement; +using Windows.UI.Xaml; + +namespace BrickController2.Windows +{ + public sealed partial class MainPage + { + private readonly GameControllerService _gameControllerService; + private readonly IContainer _container; + + public MainPage() + { + this.InitializeComponent(); + + // override system settings + var appView = ApplicationView.GetForCurrentView(); + appView.TitleBar.ButtonBackgroundColor = Colors.White; + appView.TitleBar.ButtonPressedBackgroundColor = appView.TitleBar.ButtonHoverBackgroundColor = Colors.Red; + + _container = InitDI(); + _gameControllerService = _container.Resolve(); + + base.LoadApplication(_container.Resolve()); + + // ensure GameControllerService is properly linked + _gameControllerService.InitializeComponent(Window.Current.CoreWindow); + } + + private static IContainer InitDI() + { + var builder = new ContainerBuilder(); + + builder.RegisterModule(new PlatformServicesModule()); + + builder.RegisterModule(new BusinessLogicModule()); + builder.RegisterModule(new DatabaseModule()); + builder.RegisterModule(new CreationManagementModule()); + builder.RegisterModule(new DeviceManagementModule()); + builder.RegisterModule(new UiModule()); + + return builder.Build(); + } + } +} diff --git a/BrickController2/BrickController2.UWP/Package.appxmanifest b/BrickController2/BrickController2.UWP/Package.appxmanifest new file mode 100644 index 00000000..24eae599 --- /dev/null +++ b/BrickController2/BrickController2.UWP/Package.appxmanifest @@ -0,0 +1,58 @@ + + + + + + + + + + BrickController2.UWP + 20e2eb05-a9ce-4df2-b83a-efe50e08ac16 + Assets\StoreLogo.png + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/BrickController2/BrickController2.UWP/PlatformServices/BluetoothLE/BleDevice.cs b/BrickController2/BrickController2.UWP/PlatformServices/BluetoothLE/BleDevice.cs new file mode 100644 index 00000000..a278edd5 --- /dev/null +++ b/BrickController2/BrickController2.UWP/PlatformServices/BluetoothLE/BleDevice.cs @@ -0,0 +1,313 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using BrickController2.Helpers; +using BrickController2.PlatformServices.BluetoothLE; +using BrickController2.Windows.Extensions; +using Windows.Devices.Bluetooth; +using Windows.Devices.Bluetooth.GenericAttributeProfile; + +namespace BrickController2.Windows.PlatformServices.BluetoothLE +{ + public class BleDevice : IBluetoothLEDevice + { + private readonly AsyncLock _lock = new AsyncLock(); + + private BluetoothLEDevice _bluetoothDevice; + private ICollection _services; + + private TaskCompletionSource> _connectCompletionSource; + + private Action _onCharacteristicChanged; + private Action _onDeviceDisconnected; + + public BleDevice(string address) + { + Address = address; + } + + public string Address { get; } + public BluetoothLEDeviceState State { get; private set; } = BluetoothLEDeviceState.Disconnected; + + public async Task> ConnectAndDiscoverServicesAsync( + bool autoConnect, + Action onCharacteristicChanged, + Action onDeviceDisconnected, + CancellationToken token) + { + using (var tokenRegistration = token.Register(async () => + { + using (await _lock.LockAsync()) + { + InternalDisconnect(); + _connectCompletionSource?.TrySetResult(null); + } + })) + { + _services = await ConnectAsync(onCharacteristicChanged, onDeviceDisconnected); + return _services; + } + } + + private async Task> ConnectAsync( + Action onCharacteristicChanged, + Action onDeviceDisconnected) + { + using (await _lock.LockAsync()) + { + if (State != BluetoothLEDeviceState.Disconnected) + { + return null; + } + _onCharacteristicChanged = onCharacteristicChanged; + _onDeviceDisconnected = onDeviceDisconnected; + + State = BluetoothLEDeviceState.Connecting; + + if (Address.TryParseBluetoothAddressString(out var bluetoothAddress)) + { + _bluetoothDevice?.Dispose(); + _bluetoothDevice = await BluetoothLEDevice.FromBluetoothAddressAsync(bluetoothAddress); + } + + if (_bluetoothDevice == null) + { + InternalDisconnect(); + return null; + } + + _bluetoothDevice.ConnectionStatusChanged += _bluetoothDevice_ConnectionStatusChanged; + + _connectCompletionSource = new TaskCompletionSource>(TaskCreationOptions.RunContinuationsAsynchronously); + } + + // enforce connection check + await OnConnection(); + + var result = await _connectCompletionSource.Task; + _connectCompletionSource = null; + + return result; + } + + public async Task DisconnectAsync() + { + using (await _lock.LockAsync()) + { + InternalDisconnect(); + } + } + + private void InternalDisconnect() + { + _onDeviceDisconnected = null; + _onCharacteristicChanged = null; + + if (_services != null) + { + foreach (var service in _services) + { + service.Dispose(); + } + _services = null; + } + + if (_bluetoothDevice != null) + { + _bluetoothDevice.ConnectionStatusChanged -= _bluetoothDevice_ConnectionStatusChanged; + _bluetoothDevice.Dispose(); + _bluetoothDevice = null; + } + + State = BluetoothLEDeviceState.Disconnected; + } + + public async Task EnableNotificationAsync(IGattCharacteristic characteristic, CancellationToken token) + { + using (await _lock.LockAsync()) + { + if (State == BluetoothLEDeviceState.Connected && + characteristic is BleGattCharacteristic bleGattCharacteristic && + bleGattCharacteristic.CanNotify) + { + return await bleGattCharacteristic + .EnableNotificationAsync(_onCharacteristicChanged); + } + + return false; + } + } + + public async Task DisableNotificationAsync(IGattCharacteristic characteristic, CancellationToken token) + { + using (await _lock.LockAsync()) + { + if (State == BluetoothLEDeviceState.Connected && + characteristic is BleGattCharacteristic bleGattCharacteristic && + bleGattCharacteristic.CanNotify) + { + return await bleGattCharacteristic.DisableNotificationAsync(); + } + + return false; + } + } + + public async Task WriteAsync(IGattCharacteristic characteristic, byte[] data, CancellationToken token) + { + using (await _lock.LockAsync()) + { + if (State == BluetoothLEDeviceState.Connected && + characteristic is BleGattCharacteristic bleGattCharacteristic) + { + + var result = await bleGattCharacteristic.WriteWithResponseAsync(data); + return result.Status == GattCommunicationStatus.Success; + } + return false; + } + } + + public async Task WriteNoResponseAsync(IGattCharacteristic characteristic, byte[] data, CancellationToken token) + { + using (await _lock.LockAsync()) + { + if (State == BluetoothLEDeviceState.Connected && + characteristic is BleGattCharacteristic bleGattCharacteristic) + { + var result = await bleGattCharacteristic.WriteNoResponseAsync(data); + return result == GattCommunicationStatus.Success; + } + return false; + } + } + + public async Task ReadAsync(IGattCharacteristic characteristic, CancellationToken token) + { + using (await _lock.LockAsync()) + { + if (State == BluetoothLEDeviceState.Connected && + characteristic is BleGattCharacteristic bleGattCharacteristic) + { + var result = await bleGattCharacteristic.ReadValueAsync(); + + if (result.Status == GattCommunicationStatus.Success) + { + return result.Value.ToByteArray(); + } + } + return null; + } + } + + private void _bluetoothDevice_ConnectionStatusChanged(BluetoothLEDevice sender, object args) + { + // check for a raise condition + if (sender != _bluetoothDevice) + return; + + // uses lock inside OnXXX methods, execution is not awaited + switch (sender.ConnectionStatus) + { + case BluetoothConnectionStatus.Connected: + _ = OnConnection(); + break; + + case BluetoothConnectionStatus.Disconnected: + _ = OnDisconnection(); + break; + } + } + + private async Task OnConnection() + { + using (await _lock.LockAsync()) + { + if (State == BluetoothLEDeviceState.Connecting) + { + State = BluetoothLEDeviceState.Discovering; + + await DiscoverServices(BluetoothCacheMode.Uncached); + } + else if (State == BluetoothLEDeviceState.Connected) + { + // no need to react + } + else + { + InternalDisconnect(); + _connectCompletionSource?.SetResult(null); + } + } + } + + private async Task OnDisconnection() + { + using (await _lock.LockAsync()) + { + switch (State) + { + case BluetoothLEDeviceState.Connecting: + case BluetoothLEDeviceState.Discovering: + InternalDisconnect(); + _connectCompletionSource?.SetResult(null); + break; + + case BluetoothLEDeviceState.Connected: + + var onDeviceDisconnected = _onDeviceDisconnected; + InternalDisconnect(); + onDeviceDisconnected?.Invoke(this); + break; + + default: + break; + } + } + } + + private async Task DiscoverServices(BluetoothCacheMode cacheMode) + { + // expectation is the method is already called within lock + if (_bluetoothDevice != null && State == BluetoothLEDeviceState.Discovering) + { + var services = new List(); + + var availabelServices = await _bluetoothDevice.GetGattServicesAsync(cacheMode); + + if (availabelServices.Status == GattCommunicationStatus.Success) + { + foreach (var service in availabelServices.Services) + { + var openStatus = await service.OpenAsync(GattSharingMode.SharedReadAndWrite); + + if (openStatus != GattOpenStatus.Success) + { + //TODO log + continue; + } + + var availableCharacteristics = await service.GetCharacteristicsAsync(cacheMode); + + if (availableCharacteristics.Status == GattCommunicationStatus.Success) + { + var characteristics = availableCharacteristics.Characteristics + .Select(ch => new BleGattCharacteristic(ch)) + .ToList(); + + services.Add(new BleGattService(service, characteristics)); + } + } + State = BluetoothLEDeviceState.Connected; + _connectCompletionSource?.SetResult(services); + return true; + } + } + InternalDisconnect(); + _connectCompletionSource?.SetResult(null); + return false; + } + } +} \ No newline at end of file diff --git a/BrickController2/BrickController2.UWP/PlatformServices/BluetoothLE/BleGattCharacteristic.cs b/BrickController2/BrickController2.UWP/PlatformServices/BluetoothLE/BleGattCharacteristic.cs new file mode 100644 index 00000000..6b5fa711 --- /dev/null +++ b/BrickController2/BrickController2.UWP/PlatformServices/BluetoothLE/BleGattCharacteristic.cs @@ -0,0 +1,122 @@ +using System; +using System.Threading.Tasks; +using BrickController2.PlatformServices.BluetoothLE; +using BrickController2.Windows.Extensions; +using Windows.Devices.Bluetooth; +using Windows.Devices.Bluetooth.GenericAttributeProfile; + +namespace BrickController2.Windows.PlatformServices.BluetoothLE +{ + internal class BleGattCharacteristic : IGattCharacteristic + { + private readonly GattCharacteristic _gattCharacteristic; + + private bool isNotifySet; + private Action _valueChangedCallback; + + public BleGattCharacteristic(GattCharacteristic bluetoothGattCharacteristic) + { + _gattCharacteristic = bluetoothGattCharacteristic; + Uuid = bluetoothGattCharacteristic.Uuid; + } + + public Guid Uuid { get; } + + public bool CanNotify => _gattCharacteristic != null && + _gattCharacteristic.CharacteristicProperties.HasFlag(GattCharacteristicProperties.Notify); + + public async Task WriteNoResponseAsync(byte[] data) + { + var buffer = data.ToBuffer(); + + return await _gattCharacteristic + .WriteValueAsync(buffer, GattWriteOption.WriteWithoutResponse) + .AsTask(); + } + + public async Task WriteWithResponseAsync(byte[] data) + { + var buffer = data.ToBuffer(); + + return await _gattCharacteristic + .WriteValueWithResultAsync(buffer, GattWriteOption.WriteWithResponse) + .AsTask(); + } + + public async Task ReadValueAsync() + { + return await _gattCharacteristic + .ReadValueAsync(BluetoothCacheMode.Uncached); + } + + internal async Task EnableNotificationAsync(Action callback) + { + // setup callback before writing client char. so as no event is skipped + _gattCharacteristic.ValueChanged += _gattCharacteristic_ValueChanged; + _valueChangedCallback = callback; + + var result = await ApplyClientCharacteristicConfigurationDescriptorAsync(GattClientCharacteristicConfigurationDescriptorValue.Notify, isNotifySet) + .ConfigureAwait(false); + + isNotifySet = result; + return result; + } + + internal async Task DisableNotificationAsync() + { + _valueChangedCallback = null; + _gattCharacteristic.ValueChanged -= _gattCharacteristic_ValueChanged; + + var result = await ApplyClientCharacteristicConfigurationDescriptorAsync(GattClientCharacteristicConfigurationDescriptorValue.None, isNotifySet); + + isNotifySet = result; + return result; + } + + private void _gattCharacteristic_ValueChanged(GattCharacteristic sender, GattValueChangedEventArgs args) + { + if (_valueChangedCallback != null) + { + var eventData = args.CharacteristicValue.ToByteArray(); + + _valueChangedCallback.Invoke(Uuid, eventData); + } + } + + /// + /// Sets the notify / indicate / characteristic + /// + /// If application was successfull (or has been already applied) + private async Task ApplyClientCharacteristicConfigurationDescriptorAsync(GattClientCharacteristicConfigurationDescriptorValue value, bool currentFlagValue) + { + bool targetFlagValue = value == GattClientCharacteristicConfigurationDescriptorValue.None ? false : true; + + if (currentFlagValue == targetFlagValue) + { + // already applied + return true; + } + + try + { + // write ClientCharacteristicConfigurationDescriptor in order to get notifications + // it's recieved in ValueChanged event handler than + var result = await _gattCharacteristic.WriteClientCharacteristicConfigurationDescriptorWithResultAsync(value); + if (result.Status == GattCommunicationStatus.Success) + { + return true; + } + } + catch (UnauthorizedAccessException) + { + //TODO report + } + catch (Exception) + { + //TODO report + } + + return false; + } + } +} \ No newline at end of file diff --git a/BrickController2/BrickController2.UWP/PlatformServices/BluetoothLE/BleGattService.cs b/BrickController2/BrickController2.UWP/PlatformServices/BluetoothLE/BleGattService.cs new file mode 100644 index 00000000..7d878930 --- /dev/null +++ b/BrickController2/BrickController2.UWP/PlatformServices/BluetoothLE/BleGattService.cs @@ -0,0 +1,37 @@ +using System; +using System.Collections.Generic; +using BrickController2.PlatformServices.BluetoothLE; +using Windows.Devices.Bluetooth.GenericAttributeProfile; + +namespace BrickController2.Windows.PlatformServices.BluetoothLE +{ + internal class BleGattService : IGattService, IDisposable + { + public BleGattService(GattDeviceService bluetoothGattService, IEnumerable characteristics) + { + BluetoothGattService = bluetoothGattService; + Characteristics = characteristics; + } + + public GattDeviceService BluetoothGattService { get; } + public Guid Uuid => BluetoothGattService.Uuid; + public IEnumerable Characteristics { get; } + + private bool disposed; + + public void Dispose() + { + try + { + if (!disposed) + { + disposed = true; + BluetoothGattService.Dispose(); + } + } + catch (ObjectDisposedException) + { + } + } + } +} \ No newline at end of file diff --git a/BrickController2/BrickController2.UWP/PlatformServices/BluetoothLE/BleScanner.cs b/BrickController2/BrickController2.UWP/PlatformServices/BluetoothLE/BleScanner.cs new file mode 100644 index 00000000..ea2863d7 --- /dev/null +++ b/BrickController2/BrickController2.UWP/PlatformServices/BluetoothLE/BleScanner.cs @@ -0,0 +1,99 @@ +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using BrickController2.PlatformServices.BluetoothLE; +using BrickController2.Windows.Extensions; +using Windows.Devices.Bluetooth.Advertisement; + +namespace BrickController2.Windows.PlatformServices.BluetoothLE +{ + public class BleScanner + { + private readonly Action _scanCallback; + private readonly ConcurrentDictionary _deviceNameCache; + + private readonly BluetoothLEAdvertisementWatcher _passiveWatcher; + private readonly BluetoothLEAdvertisementWatcher _activeWatcher; + + private static readonly HashSet AdvertismentDataTypes = new HashSet(new[] + { + BluetoothLEAdvertisementDataTypes.ManufacturerSpecificData, + BluetoothLEAdvertisementDataTypes.IncompleteService128BitUuids, + BluetoothLEAdvertisementDataTypes.CompleteLocalName + }); + + public BleScanner(Action scanCallback) + { + _scanCallback = scanCallback; + _deviceNameCache = new ConcurrentDictionary(); + + // use passive advertisment for name resolution + _passiveWatcher = new BluetoothLEAdvertisementWatcher { ScanningMode = BluetoothLEScanningMode.Passive }; + + _passiveWatcher.Received += _passiveWatcher_Received; + _passiveWatcher.Stopped += _passiveWatcher_Stopped; + + // use active scanner as ScanResult advertisment processor + // because SBrick contains large manufacture data which may not come in single packet with device name + _activeWatcher = new BluetoothLEAdvertisementWatcher { ScanningMode = BluetoothLEScanningMode.Active }; + _activeWatcher.Received += _activeWatcher_Received; + } + + public void Start() + { + _passiveWatcher.Start(); + _activeWatcher.Start(); + } + + private void _passiveWatcher_Received(BluetoothLEAdvertisementWatcher sender, BluetoothLEAdvertisementReceivedEventArgs args) + { + // simply update device name cache - if valid + var deviceName = args.GetLocalName(); + if (deviceName.IsValidDeviceName()) + { + _deviceNameCache.AddOrUpdate(args.BluetoothAddress, deviceName, (key, oldValue) => deviceName); + } + } + + private void _passiveWatcher_Stopped(BluetoothLEAdvertisementWatcher sender, BluetoothLEAdvertisementWatcherStoppedEventArgs args) + { + _deviceNameCache.Clear(); + } + + private void _activeWatcher_Received(BluetoothLEAdvertisementWatcher sender, BluetoothLEAdvertisementReceivedEventArgs args) + { + if (!args.CanCarryData()) + { + return; + } + // prefer local name if set, otherwise use cache (where only valid names can be) + string deviceName = args.GetLocalName(); + if (!deviceName.IsValidDeviceName() && !_deviceNameCache.TryGetValue(args.BluetoothAddress, out deviceName)) + { + return; + } + + var bluetoothAddress = args.BluetoothAddress.ToBluetoothAddressString(); + + var advertismentData = args.Advertisement.DataSections + .Where(s => AdvertismentDataTypes.Contains(s.DataType)) + .ToDictionary(s => s.DataType, s => s.Data.ToByteArray()); + + // enrich data with name manually (SBrick do not like CompleteLocalName, but Buwizz3 requires it) + if (!advertismentData.ContainsKey(BluetoothLEAdvertisementDataTypes.CompleteLocalName)) + { + advertismentData[BluetoothLEAdvertisementDataTypes.CompleteLocalName] = Encoding.ASCII.GetBytes(deviceName); + } + + _scanCallback(new ScanResult(deviceName, bluetoothAddress, advertismentData)); + } + + public void Stop() + { + _passiveWatcher.Stop(); + _activeWatcher.Stop(); + } + } +} \ No newline at end of file diff --git a/BrickController2/BrickController2.UWP/PlatformServices/BluetoothLE/BleService.cs b/BrickController2/BrickController2.UWP/PlatformServices/BluetoothLE/BleService.cs new file mode 100644 index 00000000..5cbe3d9e --- /dev/null +++ b/BrickController2/BrickController2.UWP/PlatformServices/BluetoothLE/BleService.cs @@ -0,0 +1,104 @@ +using System; +using System.Threading; +using System.Threading.Tasks; +using BrickController2.PlatformServices.BluetoothLE; +using Windows.Devices.Bluetooth; + +namespace BrickController2.Windows.PlatformServices.BluetoothLE +{ + public class BleService : IBluetoothLEService + { + [Flags] + private enum BluetoothStatus + { + None = 0x00, + ClassicSupported = 0x01, + LowEnergySupported = 0x02, + + AllFeatures = ClassicSupported | LowEnergySupported + } + + private bool _isScanning; + + public BleService() + { + } + + public bool IsBluetoothLESupported => CurrentBluetoothStatus.HasFlag(BluetoothStatus.LowEnergySupported); + public bool IsBluetoothOn => CurrentBluetoothStatus.HasFlag(BluetoothStatus.ClassicSupported); + + private BluetoothStatus CurrentBluetoothStatus + { + get + { + // synchroniously wait + var adapterTask = GetBluetoothAdapter(); + adapterTask.Wait(); + + BluetoothStatus status = (adapterTask.Result?.IsClassicSupported ?? false) ? BluetoothStatus.ClassicSupported : BluetoothStatus.None; + status |= (adapterTask.Result?.IsLowEnergySupported ?? false) ? BluetoothStatus.LowEnergySupported : BluetoothStatus.None; + + return status; + } + } + + private static async Task GetBluetoothAdapter() => await BluetoothAdapter.GetDefaultAsync() + .AsTask() + .ConfigureAwait(false); + + public async Task ScanDevicesAsync(Action scanCallback, CancellationToken token) + { + if (_isScanning || CurrentBluetoothStatus != BluetoothStatus.AllFeatures) + { + return false; + } + + try + { + _isScanning = true; + return await NewScanAsync(scanCallback, token); + } + catch (Exception) + { + return false; + } + finally + { + _isScanning = false; + } + } + + public IBluetoothLEDevice GetKnownDevice(string address) + { + if (!IsBluetoothLESupported) + { + return null; + } + + return new BleDevice(address); + } + + private async Task NewScanAsync(Action scanCallback, CancellationToken token) + { + try + { + var leScanner = new BleScanner(scanCallback); + + leScanner.Start(); + + var tcs = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); + token.Register(() => + { + leScanner.Stop(); + tcs.SetResult(true); + }); + + return await tcs.Task; + } + catch (Exception) + { + return false; + } + } + } +} \ No newline at end of file diff --git a/BrickController2/BrickController2.UWP/PlatformServices/DI/PlatformServicesModule.cs b/BrickController2/BrickController2.UWP/PlatformServices/DI/PlatformServicesModule.cs new file mode 100644 index 00000000..884ffadd --- /dev/null +++ b/BrickController2/BrickController2.UWP/PlatformServices/DI/PlatformServicesModule.cs @@ -0,0 +1,33 @@ +using Autofac; +using BrickController2.Windows.PlatformServices.BluetoothLE; +using BrickController2.PlatformServices.BluetoothLE; +using BrickController2.PlatformServices.GameController; +using BrickController2.PlatformServices.Infrared; +using BrickController2.PlatformServices.Localization; +using BrickController2.PlatformServices.SharedFileStorage; +using BrickController2.PlatformServices.Versioning; +using BrickController2.Windows.PlatformServices.Infrared; +using BrickController2.Windows.PlatformServices.Versioning; +using BrickController2.Windows.PlatformServices.Localization; +using BrickController2.Windows.PlatformServices.GameController; +using BrickController2.Windows.PlatformServices.SharedFileStorage; +using BrickController2.Windows.PlatformServices.Permission; +using BrickController2.PlatformServices.Permission; + +namespace BrickController2.Windows.PlatformServices.DI +{ + public class PlatformServicesModule : Module + { + protected override void Load(ContainerBuilder builder) + { + builder.RegisterType().As().SingleInstance(); + builder.RegisterType().AsSelf().As().SingleInstance(); + builder.RegisterType().As().SingleInstance(); + builder.RegisterType().As().SingleInstance(); + builder.RegisterType().As().SingleInstance(); + builder.RegisterType().As().SingleInstance(); + builder.RegisterType().As().InstancePerDependency(); + builder.RegisterType().As().InstancePerDependency(); + } + } +} \ No newline at end of file diff --git a/BrickController2/BrickController2.UWP/PlatformServices/GameController/GameControllerService.cs b/BrickController2/BrickController2.UWP/PlatformServices/GameController/GameControllerService.cs new file mode 100644 index 00000000..1ec3330d --- /dev/null +++ b/BrickController2/BrickController2.UWP/PlatformServices/GameController/GameControllerService.cs @@ -0,0 +1,220 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using BrickController2.PlatformServices.GameController; +using BrickController2.UI.Services.MainThread; +using BrickController2.Windows.Extensions; +using Windows.Gaming.Input; +using Windows.System; +using Windows.UI.Core; +using Windows.UI.Xaml.Input; + +namespace BrickController2.Windows.PlatformServices.GameController +{ + public class GameControllerService : IGameControllerService + { + + private readonly IDictionary _availableControllers = new Dictionary(); + private readonly object _lockObject = new object(); + private readonly IMainThreadService _mainThreadService; + private CoreWindow _coreWindow; + + private event EventHandler GameControllerEventInternal; + + public GameControllerService(IMainThreadService mainThreadService) + { + _mainThreadService = mainThreadService; + } + + public event EventHandler GameControllerEvent + { + add + { + lock (_lockObject) + { + if (GameControllerEventInternal == null) + { + InitializeKeyHandling(); + InitializeControllers(); + } + + GameControllerEventInternal += value; + } + } + + remove + { + lock (_lockObject) + { + GameControllerEventInternal -= value; + + if (GameControllerEventInternal == null) + { + TerminateKeyHandling(); + TerminateControllers(); + } + } + } + } + + internal void RaiseEvent(IDictionary<(GameControllerEventType, string), float> events) + { + if (!events.Any()) + { + return; + } + + GameControllerEventInternal?.Invoke(this, new GameControllerEventArgs(events)); + } + + internal void RaiseEvent(string deviceId, string key, GameControllerEventType eventType, float value = 0.0f) + { + GameControllerEventInternal?.Invoke(this, new GameControllerEventArgs(eventType, key, value)); + } + + internal void InitializeComponent(CoreWindow coreWindow) + { + _coreWindow = coreWindow; + } + + private void CoreWindow_KeyDown(CoreWindow sender, KeyEventArgs args) + { + args.Handled = HandleKeyDown(args.DeviceId, args.VirtualKey, args.KeyStatus); + } + + private void CoreWindow_KeyUp(CoreWindow sender, KeyEventArgs args) + { + args.Handled = HandleKeyUp(args.DeviceId, args.VirtualKey, args.KeyStatus); + } + + private bool HandleKeyDown(string deviceId, VirtualKey key, CorePhysicalKeyStatus keyStatus) + { + if (GamepadMapping.IsGamepadButton(key, out string buttonCode)) + { + if (keyStatus.RepeatCount == 1) + { + RaiseEvent(deviceId, buttonCode, GameControllerEventType.Button, GamepadMapping.Positive); + } + return true; + } + else if (GamepadMapping.IsGamepadAxis(key, out string axisCode, out float axisValue)) + { + if (keyStatus.RepeatCount == 1) + { + RaiseEvent(deviceId, axisCode, GameControllerEventType.Axis, axisValue); + } + return true; + } + + return false; + } + + private bool HandleKeyUp(string deviceId, VirtualKey key, CorePhysicalKeyStatus keyStatus) + { + if (GamepadMapping.IsGamepadButton(key, out string buttonCode)) + { + if (keyStatus.RepeatCount == 1) + { + RaiseEvent(deviceId, buttonCode, GameControllerEventType.Button); + } + return true; + } + else if (GamepadMapping.IsGamepadAxis(key, out string axisCode, out float _)) + { + if (keyStatus.RepeatCount == 1) + { + RaiseEvent(deviceId, axisCode, GameControllerEventType.Axis); + } + return true; + } + + return false; + } + + private void InitializeKeyHandling() + { + if (_coreWindow == null) + return; + + _coreWindow.KeyDown += CoreWindow_KeyDown; + _coreWindow.KeyUp += CoreWindow_KeyUp; + } + + private void TerminateKeyHandling() + { + if (_coreWindow == null) + return; + + _coreWindow.KeyDown -= CoreWindow_KeyDown; + _coreWindow.KeyUp -= CoreWindow_KeyUp; + } + + private void InitializeControllers() + { + // get all available gamepads + if (Gamepad.Gamepads.Any()) + { + AddDevices(Gamepad.Gamepads); + } + + Gamepad.GamepadRemoved += Gamepad_GamepadRemoved; + Gamepad.GamepadAdded += Gamepad_GamepadAdded; + } + + private void TerminateControllers() + { + Gamepad.GamepadRemoved -= Gamepad_GamepadRemoved; + Gamepad.GamepadAdded -= Gamepad_GamepadAdded; + + foreach (var controller in _availableControllers.Values) + { + controller.Stop(); + } + _availableControllers.Clear(); + } + + private void Gamepad_GamepadRemoved(object sender, Gamepad e) + { + lock (_lockObject) + { + var deviceId = e.GetDeviceId(); + + if (_availableControllers.TryGetValue(deviceId, out var controller)) + { + _availableControllers.Remove(deviceId); + + // enesure created in UI thread + _ = _mainThreadService.RunOnMainThread(() => controller.Stop()); + } + } + } + + private void Gamepad_GamepadAdded(object sender, Gamepad e) + { + _ = AddDevicesInMainThread(new[] { e }); + } + + private Task AddDevicesInMainThread(IEnumerable gamepads) + { + // enesure created in UI thread + return _mainThreadService.RunOnMainThread(() => AddDevices(gamepads)); + } + + private void AddDevices(IEnumerable gamepads) + { + lock (_lockObject) + { + foreach (var gamepad in gamepads) + { + var deviceId = gamepad.GetDeviceId(); + + var newController = new GamepadController(this, gamepad); + _availableControllers[deviceId] = newController; + + newController.Start(); + } + } + } + } +} \ No newline at end of file diff --git a/BrickController2/BrickController2.UWP/PlatformServices/GameController/GamepadController.cs b/BrickController2/BrickController2.UWP/PlatformServices/GameController/GamepadController.cs new file mode 100644 index 00000000..b3345a2e --- /dev/null +++ b/BrickController2/BrickController2.UWP/PlatformServices/GameController/GamepadController.cs @@ -0,0 +1,98 @@ +using BrickController2.PlatformServices.GameController; +using BrickController2.Windows.Extensions; +using System; +using System.Collections.Generic; +using System.Linq; +using Windows.Gaming.Input; +using Windows.UI.Xaml; + +namespace BrickController2.Windows.PlatformServices.GameController +{ + internal class GamepadController + { + private static readonly TimeSpan DefaultInterval = TimeSpan.FromMilliseconds(20); + + private readonly GameControllerService _controllerService; + private readonly IDictionary _lastReadingValues = new Dictionary(); + + private DispatcherTimer _timer; + + public GamepadController(GameControllerService service, Gamepad gamepad) : this(service, gamepad, DefaultInterval) + { + } + + private GamepadController(GameControllerService service, Gamepad gamepad, TimeSpan timerInterval) + { + _controllerService = service ?? throw new ArgumentNullException(nameof(service)); + UwpController = gamepad ?? throw new ArgumentNullException(nameof(gamepad)); + + _timer = new DispatcherTimer + { + Interval = timerInterval + }; + _timer.Tick += Timer_Tick; + } + + public Gamepad UwpController { get; } + + public string DeviceId => UwpController.GetDeviceId(); + + public void Start() + { + _lastReadingValues.Clear(); + + // finally start timer + _timer.Start(); + } + + public void Stop() + { + _timer.Stop(); + + _lastReadingValues.Clear(); + } + + private void Timer_Tick(object sender, object e) + { + var currentReading = GetCurrentReadings(); + + var currentEvents = currentReading + .Where(HasChanged) + .ToDictionary(x => (GameControllerEventType.Axis, x.AxisName), x => x.Value); + + _controllerService.RaiseEvent(currentEvents); + } + + private IEnumerable<(string AxisName, float Value)> GetCurrentReadings() + { + var currentReading = UwpController.GetCurrentReading(); + + yield return GamepadMapping.GetAxisValue(GamepadMapping.XAxis, currentReading.LeftThumbstickX); + yield return GamepadMapping.GetAxisValue(GamepadMapping.YAxis, currentReading.LeftThumbstickY); + yield return GamepadMapping.GetAxisValue(GamepadMapping.BrakeAxis, currentReading.LeftTrigger); + yield return GamepadMapping.GetAxisValue(GamepadMapping.ZAxis, currentReading.RightThumbstickX); + yield return GamepadMapping.GetAxisValue(GamepadMapping.RzAxis, currentReading.RightThumbstickY); + yield return GamepadMapping.GetAxisValue(GamepadMapping.GasAxis, currentReading.RightTrigger); + } + + private static bool AreAlmostEqual(float a, float b) + { + return Math.Abs(a - b) < 0.001; + } + + private bool HasChanged((string AxisName, float Value) readingValue) + { + if (_lastReadingValues.TryGetValue(readingValue.AxisName, out float lastValue)) + { + if (AreAlmostEqual(readingValue.Value, lastValue)) + { + // axisValue == lastValue + return false; + } + } + + _lastReadingValues[readingValue.AxisName] = readingValue.Value; + return true; + } + } +} diff --git a/BrickController2/BrickController2.UWP/PlatformServices/GameController/GamepadMapping.cs b/BrickController2/BrickController2.UWP/PlatformServices/GameController/GamepadMapping.cs new file mode 100644 index 00000000..288a0d06 --- /dev/null +++ b/BrickController2/BrickController2.UWP/PlatformServices/GameController/GamepadMapping.cs @@ -0,0 +1,104 @@ +using System; +using System.Collections.Generic; +using Windows.System; + +namespace BrickController2.Windows.PlatformServices.GameController +{ + public static class GamepadMapping + { + public const float Positive = 1.0f; + public const float Negative = -1.0f; + + public const string XAxis = "X"; + public const string YAxis = "Y"; + public const string RzAxis = "Rz"; + public const string ZAxis = "Z"; + public const string BrakeAxis = "Brake"; + public const string GasAxis = "Gas"; + + /// + /// Set of virtual keys related to gamepad buttons + /// + private static readonly Dictionary GamePadButtonMapping = new Dictionary() + { + { VirtualKey.GamepadA, "ButtonA" }, + { VirtualKey.GamepadB, "ButtonB" }, + { VirtualKey.GamepadX, "ButtonX" }, + { VirtualKey.GamepadY, "ButtonY" }, + + { VirtualKey.GamepadLeftShoulder, "ButtonL1" }, + { VirtualKey.GamepadRightShoulder, "ButtonR1" }, + { VirtualKey.GamepadLeftTrigger, "ButtonL2" }, + { VirtualKey.GamepadRightTrigger, "ButtonR2" }, + + { VirtualKey.GamepadMenu, "ButtonStart" }, + { VirtualKey.GamepadView, "ButtonSelect" }, + + { VirtualKey.GamepadLeftThumbstickButton, "ButtonThumbl" }, + { VirtualKey.GamepadRightThumbstickButton, "ButtonThumbr" }, + }; + + /// + /// Set of virtual keys related to gamepad buttons handled as AXIS + /// + private static readonly Dictionary GamePadAxisMapping = new Dictionary() + { + { VirtualKey.GamepadDPadUp, ("HatY", Negative) }, + { VirtualKey.GamepadDPadDown, ("HatY", Positive) }, + { VirtualKey.GamepadDPadLeft, ("HatX", Negative) }, + { VirtualKey.GamepadDPadRight, ("HatX", Positive) }, + }; + + /// + /// Set of inverted axis + /// + private static readonly HashSet GamePadInvertedAxis = new HashSet { YAxis, RzAxis }; + + public static bool IsGamepadButton(VirtualKey virtualKey, out string buttonCode) + { + if (GamePadButtonMapping.TryGetValue(virtualKey, out buttonCode)) + { + return true; + } + + buttonCode = default; + return false; + } + + public static bool IsGamepadAxis(VirtualKey virtualKey, out string axisCode, out float axisValue) + { + if (GamePadAxisMapping.TryGetValue(virtualKey, out (string, float) button)) + { + axisCode = button.Item1; + axisValue = button.Item2; + + return true; + } + + axisCode = default; + axisValue = default; + + return false; + } + + public static (string AxisName, float Value) GetAxisValue(string axisName, double value) + { + if (Math.Abs(value) < 0.05) + { + return (axisName, 0.0F); + } + + float coef = GamePadInvertedAxis.Contains(axisName) ? Negative : Positive; + + if (value > 0.95) + { + return (axisName, coef); + } + if (value < -0.95) + { + return (axisName, -coef); + } + return (axisName, coef * (float)value); + } + } +} diff --git a/BrickController2/BrickController2.UWP/PlatformServices/Infrared/InfraredService.cs b/BrickController2/BrickController2.UWP/PlatformServices/Infrared/InfraredService.cs new file mode 100644 index 00000000..ba16163e --- /dev/null +++ b/BrickController2/BrickController2.UWP/PlatformServices/Infrared/InfraredService.cs @@ -0,0 +1,25 @@ +using BrickController2.PlatformServices.Infrared; +using System; +using System.Threading.Tasks; + +namespace BrickController2.Windows.PlatformServices.Infrared +{ + public class InfraredService : IInfraredService + { + public InfraredService() + { + } + + public bool IsInfraredSupported => false; + + public bool IsCarrierFrequencySupported(int carrierFrequency) + { + throw new NotImplementedException(); + } + + public Task SendPacketAsync(int carrierFrequency, int[] packet) + { + throw new NotImplementedException(); + } + } +} \ No newline at end of file diff --git a/BrickController2/BrickController2.UWP/PlatformServices/Localization/LocalizationService.cs b/BrickController2/BrickController2.UWP/PlatformServices/Localization/LocalizationService.cs new file mode 100644 index 00000000..8c5c85cd --- /dev/null +++ b/BrickController2/BrickController2.UWP/PlatformServices/Localization/LocalizationService.cs @@ -0,0 +1,26 @@ +using BrickController2.PlatformServices.Localization; +using System.Globalization; +using System.Threading; +using Xamarin.Forms; + +[assembly:Dependency(typeof(BrickController2.Windows.PlatformServices.Localization.LocalizationService))] +namespace BrickController2.Windows.PlatformServices.Localization +{ + public class LocalizationService : ILocalizationService + { + public CultureInfo CurrentCultureInfo + { + get + { + return CultureInfo.CurrentUICulture; + } + + set + { + CultureInfo.CurrentUICulture = value; + Thread.CurrentThread.CurrentCulture = value; + Thread.CurrentThread.CurrentUICulture = value; + } + } + } +} \ No newline at end of file diff --git a/BrickController2/BrickController2.UWP/PlatformServices/Permission/BluetoothPermission.cs b/BrickController2/BrickController2.UWP/PlatformServices/Permission/BluetoothPermission.cs new file mode 100644 index 00000000..8f532c26 --- /dev/null +++ b/BrickController2/BrickController2.UWP/PlatformServices/Permission/BluetoothPermission.cs @@ -0,0 +1,12 @@ +using System.Collections.Generic; +using static Xamarin.Essentials.Permissions; +using BrickController2.PlatformServices.Permission; +using System; + +namespace BrickController2.Windows.PlatformServices.Permission +{ + public class BluetoothPermission : BasePlatformPermission, IBluetoothPermission + { + protected override Func> RequiredDeclarations => () => new[] { "bluetooth" }; + } +} \ No newline at end of file diff --git a/BrickController2/BrickController2.UWP/PlatformServices/Permission/ReadWriteExternalStoragePermission.cs b/BrickController2/BrickController2.UWP/PlatformServices/Permission/ReadWriteExternalStoragePermission.cs new file mode 100644 index 00000000..596dfc66 --- /dev/null +++ b/BrickController2/BrickController2.UWP/PlatformServices/Permission/ReadWriteExternalStoragePermission.cs @@ -0,0 +1,12 @@ +using System.Collections.Generic; +using static Xamarin.Essentials.Permissions; +using BrickController2.PlatformServices.Permission; +using System; + +namespace BrickController2.Windows.PlatformServices.Permission +{ + public class ReadWriteExternalStoragePermission : BasePlatformPermission, IReadWriteExternalStoragePermission + { + protected override Func> RequiredDeclarations => () => new[] { "removableStorage" }; + } +} \ No newline at end of file diff --git a/BrickController2/BrickController2.UWP/PlatformServices/SharedFileStorage/SharedFileStorageService.cs b/BrickController2/BrickController2.UWP/PlatformServices/SharedFileStorage/SharedFileStorageService.cs new file mode 100644 index 00000000..1f02fe67 --- /dev/null +++ b/BrickController2/BrickController2.UWP/PlatformServices/SharedFileStorage/SharedFileStorageService.cs @@ -0,0 +1,15 @@ +using BrickController2.PlatformServices.SharedFileStorage; +using System; +using Windows.Storage; + +namespace BrickController2.Windows.PlatformServices.SharedFileStorage +{ + public class SharedFileStorageService : ISharedFileStorageService + { + public bool IsSharedStorageAvailable => true; + + public bool IsPermissionGranted { get; set; } + + public string SharedStorageDirectory => ApplicationData.Current.RoamingFolder.Path; + } +} diff --git a/BrickController2/BrickController2.UWP/PlatformServices/Versioning/VersionService.cs b/BrickController2/BrickController2.UWP/PlatformServices/Versioning/VersionService.cs new file mode 100644 index 00000000..92c0cda9 --- /dev/null +++ b/BrickController2/BrickController2.UWP/PlatformServices/Versioning/VersionService.cs @@ -0,0 +1,29 @@ +using BrickController2.PlatformServices.Versioning; +using Windows.ApplicationModel; + +namespace BrickController2.Windows.PlatformServices.Versioning +{ + public class VersionService : IVersionService + { + public VersionService() + { + + } + + public string ApplicationVersion + { + get + { + try + { + var info = Package.Current.Id.Version; + return $"{info.Major}.{info.Minor}.{info.Revision}"; + } + catch + { + return "Unknown version"; + } + } + } + } +} \ No newline at end of file diff --git a/BrickController2/BrickController2.UWP/Properties/AssemblyInfo.cs b/BrickController2/BrickController2.UWP/Properties/AssemblyInfo.cs new file mode 100644 index 00000000..c3b9eed6 --- /dev/null +++ b/BrickController2/BrickController2.UWP/Properties/AssemblyInfo.cs @@ -0,0 +1,22 @@ +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("BrickController2")] +[assembly: AssemblyDescription("Cross platform mobile application for controlling Lego creations using a bluetooth gamepad.")] +[assembly: AssemblyProduct("BrickController2")] +[assembly: AssemblyCopyright("Copyright © 2021")] + +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +// You can specify all the values or you can default the Build and Revision Numbers +// by using the '*' as shown below: +[assembly: AssemblyVersion("1.0.0.0")] \ No newline at end of file diff --git a/BrickController2/BrickController2.UWP/Properties/Default.rd.xml b/BrickController2/BrickController2.UWP/Properties/Default.rd.xml new file mode 100644 index 00000000..7c40ffeb --- /dev/null +++ b/BrickController2/BrickController2.UWP/Properties/Default.rd.xml @@ -0,0 +1,31 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/BrickController2/BrickController2.UWP/UI/CustomRenderers/ExtendedSliderRenderer.cs b/BrickController2/BrickController2.UWP/UI/CustomRenderers/ExtendedSliderRenderer.cs new file mode 100644 index 00000000..ed36be99 --- /dev/null +++ b/BrickController2/BrickController2.UWP/UI/CustomRenderers/ExtendedSliderRenderer.cs @@ -0,0 +1,31 @@ +using BrickController2.UI.Controls; +using BrickController2.Windows.UI.CustomRenderers; +using Xamarin.Forms; +using Xamarin.Forms.Platform.UWP; + +[assembly: ExportRenderer(typeof(ExtendedSlider), typeof(ExtendedSliderRenderer))] +namespace BrickController2.Windows.UI.CustomRenderers +{ + public class ExtendedSliderRenderer : SliderRenderer + { + public ExtendedSliderRenderer() : base() + { + } + + protected override void OnElementChanged(ElementChangedEventArgs e) + { + base.OnElementChanged(e); + + if (Element is ExtendedSlider extendedSlider && Control != null) + { + Control.PointerCaptureLost += (sender, args) => extendedSlider.TouchUp(); + + if (extendedSlider.Step > 0) + { + Control.StepFrequency = extendedSlider.Step; + Control.SmallChange = extendedSlider.Step; + } + } + } + } +} \ No newline at end of file diff --git a/BrickController2/BrickController2.UWP/UI/CustomRenderers/ExtendedSwipeViewRenderer.cs b/BrickController2/BrickController2.UWP/UI/CustomRenderers/ExtendedSwipeViewRenderer.cs new file mode 100644 index 00000000..7b208269 --- /dev/null +++ b/BrickController2/BrickController2.UWP/UI/CustomRenderers/ExtendedSwipeViewRenderer.cs @@ -0,0 +1,33 @@ +using BrickController2.Windows.UI.CustomRenderers; +using System.Linq; +using Windows.UI.Xaml.Input; +using Xamarin.Forms; +using Xamarin.Forms.Platform.UWP; + +[assembly: ExportRenderer(typeof(SwipeView), typeof(ExtendedSwipeViewRenderer))] +namespace BrickController2.Windows.UI.CustomRenderers +{ + public class ExtendedSwipeViewRenderer : SwipeViewRenderer + { + public ExtendedSwipeViewRenderer() : base() + { + } + + protected override void OnElementChanged(ElementChangedEventArgs e) + { + base.OnElementChanged(e); + + if (Control != null) + { + Control.RightTapped += Control_RightTapped; + } + } + + private void Control_RightTapped(object sender, RightTappedRoutedEventArgs e) + { + // invoke command of the first left item to suppport deletion (workaround for Windouws without touch controls) + var item = Element.LeftItems?.First(); + item?.Command?.Execute(item?.CommandParameter); + } + } +} \ No newline at end of file diff --git a/BrickController2/BrickController2.UWP/UI/CustomRenderers/NavPageOverrideRenderer.cs b/BrickController2/BrickController2.UWP/UI/CustomRenderers/NavPageOverrideRenderer.cs new file mode 100644 index 00000000..27db85e8 --- /dev/null +++ b/BrickController2/BrickController2.UWP/UI/CustomRenderers/NavPageOverrideRenderer.cs @@ -0,0 +1,51 @@ +using BrickController2.Windows.UI.CustomRenderers; +using Xamarin.Forms; +using Xamarin.Forms.Internals; +using Xamarin.Forms.Platform.UWP; + +[assembly: ExportRenderer(typeof(NavigationPage), typeof(NavPageOverrideRenderer))] +namespace BrickController2.Windows.UI.CustomRenderers +{ + public class NavPageOverrideRenderer : NavigationPageRenderer + { + public NavPageOverrideRenderer() + { + } + + private void WorkaroundHideTitle(Page page) + { + page.Title = string.Empty; + } + + protected override void OnElementChanged(VisualElementChangedEventArgs e) + { + if (e.NewElement is NavigationPage page) + { + WorkaroundHideTitle(page.CurrentPage); + } + + base.OnElementChanged(e); + } + + protected override void OnPopRequested(object sender, NavigationRequestedEventArgs e) + { + WorkaroundHideTitle(e.Page); + + base.OnPopRequested(sender, e); + } + + protected override void OnPopToRootRequested(object sender, NavigationRequestedEventArgs e) + { + WorkaroundHideTitle(e.Page); + + base.OnPopToRootRequested(sender, e); + } + + protected override void OnPushRequested(object sender, NavigationRequestedEventArgs e) + { + WorkaroundHideTitle(e.Page); + + base.OnPushRequested(sender, e); + } + } +} diff --git a/BrickController2/BrickController2/BrickController2.csproj b/BrickController2/BrickController2/BrickController2.csproj index f87cd62c..3296968e 100644 --- a/BrickController2/BrickController2/BrickController2.csproj +++ b/BrickController2/BrickController2/BrickController2.csproj @@ -1,7 +1,7 @@  - netstandard2.1 + netstandard2.0 latest @@ -102,7 +102,8 @@ - + + diff --git a/BrickController2/BrickController2/CreationManagement/CreationManager.cs b/BrickController2/BrickController2/CreationManagement/CreationManager.cs index 4fe0a1d6..05da88ea 100644 --- a/BrickController2/BrickController2/CreationManagement/CreationManager.cs +++ b/BrickController2/BrickController2/CreationManagement/CreationManager.cs @@ -7,6 +7,8 @@ using System.IO; using Newtonsoft.Json; +using File = BrickController2.FileWorkaround; + namespace BrickController2.CreationManagement { public class CreationManager : ICreationManager diff --git a/BrickController2/BrickController2/FileWorkaround.cs b/BrickController2/BrickController2/FileWorkaround.cs new file mode 100644 index 00000000..b35e2d82 --- /dev/null +++ b/BrickController2/BrickController2/FileWorkaround.cs @@ -0,0 +1,39 @@ +using System; +using System.IO; +using System.Text; +using System.Threading.Tasks; + +namespace BrickController2 +{ + internal static class FileWorkaround + { + public static async Task ReadAllTextAsync(string filePath) + { + using (FileStream sourceStream = new FileStream(filePath, FileMode.Open, FileAccess.Read, FileShare.Read, bufferSize: 4096, useAsync: true)) + using (StreamReader reader = new StreamReader(sourceStream)) + { + return await reader.ReadToEndAsync(); + } + } + + public static async Task WriteAllTextAsync(string path, string contents) + { + if (path == null) + throw new ArgumentNullException(nameof(path)); + + if (string.IsNullOrEmpty(contents)) + { + new FileStream(path, FileMode.Create, FileAccess.Write, FileShare.Read).Dispose(); + return; + } + + FileStream stream = new FileStream(path, FileMode.Create, FileAccess.Write, FileShare.Read, 4096, FileOptions.Asynchronous); + + using (var sw = new StreamWriter(stream, Encoding.Unicode)) + { + await sw.WriteAsync(contents).ConfigureAwait(false); + await sw.FlushAsync().ConfigureAwait(false); + } + } + } +} diff --git a/BrickController2/BrickController2/Helpers/FileHelper.cs b/BrickController2/BrickController2/Helpers/FileHelper.cs index d1c21979..53e04697 100644 --- a/BrickController2/BrickController2/Helpers/FileHelper.cs +++ b/BrickController2/BrickController2/Helpers/FileHelper.cs @@ -17,9 +17,7 @@ public static Dictionary EnumerateDirectoryFilesToFilenameMap(st { var filePaths = Directory.EnumerateFiles(directoryPath, searchPattern, SearchOption.TopDirectoryOnly); - var filenameFilepathMap = new Dictionary(filePaths.Select(fp => KeyValuePair.Create(Path.GetFileNameWithoutExtension(fp), fp))); - - return filenameFilepathMap; + return filePaths.ToDictionary(fp => Path.GetFileNameWithoutExtension(fp), fp => fp); } public static bool FilenameValidator(string filename) diff --git a/BrickController2/BrickController2/UI/Controls/ImageButton.xaml b/BrickController2/BrickController2/UI/Controls/ImageButton.xaml index 1d86371c..7d810198 100644 --- a/BrickController2/BrickController2/UI/Controls/ImageButton.xaml +++ b/BrickController2/BrickController2/UI/Controls/ImageButton.xaml @@ -6,11 +6,11 @@ x:Class="BrickController2.UI.Controls.ImageButton"> - - + + - + - + \ No newline at end of file diff --git a/README.md b/README.md index 03b2f9d2..51931f3d 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# BrickController Essentials +# BrickController Essentials A fork of [BrickController 2](https://github.com/imurvai/brickcontroller2) which has [features](#features-and-fixes) not included in the original application yet. @@ -8,6 +8,7 @@ Cross platform mobile application for controlling Lego creations using a bluetoo - Android 4.3+ - iOS 8+ +- Windows 10 (experimental) ## Supported receivers