diff --git a/Quarrel.sln b/Quarrel.sln index 9d9dabf59..5b9f07dfa 100644 --- a/Quarrel.sln +++ b/Quarrel.sln @@ -42,26 +42,35 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Quarrel.Testing.DirtyServic EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Quarrel.Markdown", "src\Libs\Quarrel.Markdown\Quarrel.Markdown.csproj", "{E4B6DFB8-B9B7-4EE7-9F86-AFDDC221FFB3}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Quarrel.Common", "src\Libs\Quarrel.Common\Quarrel.Common.csproj", "{DA2758C2-8D98-46EF-8DE3-833089AF99C4}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution + Alpha|Any CPU = Alpha|Any CPU Alpha|ARM = Alpha|ARM Alpha|ARM64 = Alpha|ARM64 Alpha|x64 = Alpha|x64 Alpha|x86 = Alpha|x86 + Debug|Any CPU = Debug|Any CPU Debug|ARM = Debug|ARM Debug|ARM64 = Debug|ARM64 Debug|x64 = Debug|x64 Debug|x86 = Debug|x86 + Insider|Any CPU = Insider|Any CPU Insider|ARM = Insider|ARM Insider|ARM64 = Insider|ARM64 Insider|x64 = Insider|x64 Insider|x86 = Insider|x86 + Release|Any CPU = Release|Any CPU Release|ARM = Release|ARM Release|ARM64 = Release|ARM64 Release|x64 = Release|x64 Release|x86 = Release|x86 EndGlobalSection GlobalSection(ProjectConfigurationPlatforms) = postSolution + {0247C94D-5FD1-45D6-9312-7440317F6C40}.Alpha|Any CPU.ActiveCfg = Alpha|x64 + {0247C94D-5FD1-45D6-9312-7440317F6C40}.Alpha|Any CPU.Build.0 = Alpha|x64 + {0247C94D-5FD1-45D6-9312-7440317F6C40}.Alpha|Any CPU.Deploy.0 = Alpha|x64 {0247C94D-5FD1-45D6-9312-7440317F6C40}.Alpha|ARM.ActiveCfg = Alpha|ARM {0247C94D-5FD1-45D6-9312-7440317F6C40}.Alpha|ARM.Build.0 = Alpha|ARM {0247C94D-5FD1-45D6-9312-7440317F6C40}.Alpha|ARM.Deploy.0 = Alpha|ARM @@ -74,6 +83,9 @@ Global {0247C94D-5FD1-45D6-9312-7440317F6C40}.Alpha|x86.ActiveCfg = Alpha|x86 {0247C94D-5FD1-45D6-9312-7440317F6C40}.Alpha|x86.Build.0 = Alpha|x86 {0247C94D-5FD1-45D6-9312-7440317F6C40}.Alpha|x86.Deploy.0 = Alpha|x86 + {0247C94D-5FD1-45D6-9312-7440317F6C40}.Debug|Any CPU.ActiveCfg = Debug|x64 + {0247C94D-5FD1-45D6-9312-7440317F6C40}.Debug|Any CPU.Build.0 = Debug|x64 + {0247C94D-5FD1-45D6-9312-7440317F6C40}.Debug|Any CPU.Deploy.0 = Debug|x64 {0247C94D-5FD1-45D6-9312-7440317F6C40}.Debug|ARM.ActiveCfg = Debug|ARM {0247C94D-5FD1-45D6-9312-7440317F6C40}.Debug|ARM.Build.0 = Debug|ARM {0247C94D-5FD1-45D6-9312-7440317F6C40}.Debug|ARM.Deploy.0 = Debug|ARM @@ -86,6 +98,9 @@ Global {0247C94D-5FD1-45D6-9312-7440317F6C40}.Debug|x86.ActiveCfg = Debug|x86 {0247C94D-5FD1-45D6-9312-7440317F6C40}.Debug|x86.Build.0 = Debug|x86 {0247C94D-5FD1-45D6-9312-7440317F6C40}.Debug|x86.Deploy.0 = Debug|x86 + {0247C94D-5FD1-45D6-9312-7440317F6C40}.Insider|Any CPU.ActiveCfg = Insider|x64 + {0247C94D-5FD1-45D6-9312-7440317F6C40}.Insider|Any CPU.Build.0 = Insider|x64 + {0247C94D-5FD1-45D6-9312-7440317F6C40}.Insider|Any CPU.Deploy.0 = Insider|x64 {0247C94D-5FD1-45D6-9312-7440317F6C40}.Insider|ARM.ActiveCfg = Insider|ARM {0247C94D-5FD1-45D6-9312-7440317F6C40}.Insider|ARM.Build.0 = Insider|ARM {0247C94D-5FD1-45D6-9312-7440317F6C40}.Insider|ARM.Deploy.0 = Insider|ARM @@ -98,6 +113,9 @@ Global {0247C94D-5FD1-45D6-9312-7440317F6C40}.Insider|x86.ActiveCfg = Insider|x86 {0247C94D-5FD1-45D6-9312-7440317F6C40}.Insider|x86.Build.0 = Insider|x86 {0247C94D-5FD1-45D6-9312-7440317F6C40}.Insider|x86.Deploy.0 = Insider|x86 + {0247C94D-5FD1-45D6-9312-7440317F6C40}.Release|Any CPU.ActiveCfg = Release|x64 + {0247C94D-5FD1-45D6-9312-7440317F6C40}.Release|Any CPU.Build.0 = Release|x64 + {0247C94D-5FD1-45D6-9312-7440317F6C40}.Release|Any CPU.Deploy.0 = Release|x64 {0247C94D-5FD1-45D6-9312-7440317F6C40}.Release|ARM.ActiveCfg = Release|ARM {0247C94D-5FD1-45D6-9312-7440317F6C40}.Release|ARM.Build.0 = Release|ARM {0247C94D-5FD1-45D6-9312-7440317F6C40}.Release|ARM.Deploy.0 = Release|ARM @@ -110,6 +128,8 @@ Global {0247C94D-5FD1-45D6-9312-7440317F6C40}.Release|x86.ActiveCfg = Release|x86 {0247C94D-5FD1-45D6-9312-7440317F6C40}.Release|x86.Build.0 = Release|x86 {0247C94D-5FD1-45D6-9312-7440317F6C40}.Release|x86.Deploy.0 = Release|x86 + {E069A285-68FE-43C7-957F-9CC6BBF17BCE}.Alpha|Any CPU.ActiveCfg = Alpha|Any CPU + {E069A285-68FE-43C7-957F-9CC6BBF17BCE}.Alpha|Any CPU.Build.0 = Alpha|Any CPU {E069A285-68FE-43C7-957F-9CC6BBF17BCE}.Alpha|ARM.ActiveCfg = Alpha|Any CPU {E069A285-68FE-43C7-957F-9CC6BBF17BCE}.Alpha|ARM.Build.0 = Alpha|Any CPU {E069A285-68FE-43C7-957F-9CC6BBF17BCE}.Alpha|ARM64.ActiveCfg = Alpha|Any CPU @@ -118,6 +138,8 @@ Global {E069A285-68FE-43C7-957F-9CC6BBF17BCE}.Alpha|x64.Build.0 = Alpha|Any CPU {E069A285-68FE-43C7-957F-9CC6BBF17BCE}.Alpha|x86.ActiveCfg = Alpha|Any CPU {E069A285-68FE-43C7-957F-9CC6BBF17BCE}.Alpha|x86.Build.0 = Alpha|Any CPU + {E069A285-68FE-43C7-957F-9CC6BBF17BCE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {E069A285-68FE-43C7-957F-9CC6BBF17BCE}.Debug|Any CPU.Build.0 = Debug|Any CPU {E069A285-68FE-43C7-957F-9CC6BBF17BCE}.Debug|ARM.ActiveCfg = Debug|Any CPU {E069A285-68FE-43C7-957F-9CC6BBF17BCE}.Debug|ARM.Build.0 = Debug|Any CPU {E069A285-68FE-43C7-957F-9CC6BBF17BCE}.Debug|ARM64.ActiveCfg = Debug|Any CPU @@ -126,6 +148,8 @@ Global {E069A285-68FE-43C7-957F-9CC6BBF17BCE}.Debug|x64.Build.0 = Debug|Any CPU {E069A285-68FE-43C7-957F-9CC6BBF17BCE}.Debug|x86.ActiveCfg = Debug|Any CPU {E069A285-68FE-43C7-957F-9CC6BBF17BCE}.Debug|x86.Build.0 = Debug|Any CPU + {E069A285-68FE-43C7-957F-9CC6BBF17BCE}.Insider|Any CPU.ActiveCfg = Insider|Any CPU + {E069A285-68FE-43C7-957F-9CC6BBF17BCE}.Insider|Any CPU.Build.0 = Insider|Any CPU {E069A285-68FE-43C7-957F-9CC6BBF17BCE}.Insider|ARM.ActiveCfg = Insider|Any CPU {E069A285-68FE-43C7-957F-9CC6BBF17BCE}.Insider|ARM.Build.0 = Insider|Any CPU {E069A285-68FE-43C7-957F-9CC6BBF17BCE}.Insider|ARM64.ActiveCfg = Insider|Any CPU @@ -134,6 +158,8 @@ Global {E069A285-68FE-43C7-957F-9CC6BBF17BCE}.Insider|x64.Build.0 = Insider|Any CPU {E069A285-68FE-43C7-957F-9CC6BBF17BCE}.Insider|x86.ActiveCfg = Insider|Any CPU {E069A285-68FE-43C7-957F-9CC6BBF17BCE}.Insider|x86.Build.0 = Insider|Any CPU + {E069A285-68FE-43C7-957F-9CC6BBF17BCE}.Release|Any CPU.ActiveCfg = Release|Any CPU + {E069A285-68FE-43C7-957F-9CC6BBF17BCE}.Release|Any CPU.Build.0 = Release|Any CPU {E069A285-68FE-43C7-957F-9CC6BBF17BCE}.Release|ARM.ActiveCfg = Release|Any CPU {E069A285-68FE-43C7-957F-9CC6BBF17BCE}.Release|ARM.Build.0 = Release|Any CPU {E069A285-68FE-43C7-957F-9CC6BBF17BCE}.Release|ARM64.ActiveCfg = Release|Any CPU @@ -142,6 +168,8 @@ Global {E069A285-68FE-43C7-957F-9CC6BBF17BCE}.Release|x64.Build.0 = Release|Any CPU {E069A285-68FE-43C7-957F-9CC6BBF17BCE}.Release|x86.ActiveCfg = Release|Any CPU {E069A285-68FE-43C7-957F-9CC6BBF17BCE}.Release|x86.Build.0 = Release|Any CPU + {43360223-1CD9-4962-84CC-0698F6D11837}.Alpha|Any CPU.ActiveCfg = Alpha|Any CPU + {43360223-1CD9-4962-84CC-0698F6D11837}.Alpha|Any CPU.Build.0 = Alpha|Any CPU {43360223-1CD9-4962-84CC-0698F6D11837}.Alpha|ARM.ActiveCfg = Alpha|Any CPU {43360223-1CD9-4962-84CC-0698F6D11837}.Alpha|ARM.Build.0 = Alpha|Any CPU {43360223-1CD9-4962-84CC-0698F6D11837}.Alpha|ARM64.ActiveCfg = Alpha|Any CPU @@ -150,6 +178,8 @@ Global {43360223-1CD9-4962-84CC-0698F6D11837}.Alpha|x64.Build.0 = Alpha|Any CPU {43360223-1CD9-4962-84CC-0698F6D11837}.Alpha|x86.ActiveCfg = Alpha|Any CPU {43360223-1CD9-4962-84CC-0698F6D11837}.Alpha|x86.Build.0 = Alpha|Any CPU + {43360223-1CD9-4962-84CC-0698F6D11837}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {43360223-1CD9-4962-84CC-0698F6D11837}.Debug|Any CPU.Build.0 = Debug|Any CPU {43360223-1CD9-4962-84CC-0698F6D11837}.Debug|ARM.ActiveCfg = Debug|Any CPU {43360223-1CD9-4962-84CC-0698F6D11837}.Debug|ARM.Build.0 = Debug|Any CPU {43360223-1CD9-4962-84CC-0698F6D11837}.Debug|ARM64.ActiveCfg = Debug|Any CPU @@ -158,6 +188,8 @@ Global {43360223-1CD9-4962-84CC-0698F6D11837}.Debug|x64.Build.0 = Debug|Any CPU {43360223-1CD9-4962-84CC-0698F6D11837}.Debug|x86.ActiveCfg = Debug|Any CPU {43360223-1CD9-4962-84CC-0698F6D11837}.Debug|x86.Build.0 = Debug|Any CPU + {43360223-1CD9-4962-84CC-0698F6D11837}.Insider|Any CPU.ActiveCfg = Insider|Any CPU + {43360223-1CD9-4962-84CC-0698F6D11837}.Insider|Any CPU.Build.0 = Insider|Any CPU {43360223-1CD9-4962-84CC-0698F6D11837}.Insider|ARM.ActiveCfg = Release|Any CPU {43360223-1CD9-4962-84CC-0698F6D11837}.Insider|ARM.Build.0 = Release|Any CPU {43360223-1CD9-4962-84CC-0698F6D11837}.Insider|ARM64.ActiveCfg = Release|Any CPU @@ -166,6 +198,8 @@ Global {43360223-1CD9-4962-84CC-0698F6D11837}.Insider|x64.Build.0 = Insider|Any CPU {43360223-1CD9-4962-84CC-0698F6D11837}.Insider|x86.ActiveCfg = Release|Any CPU {43360223-1CD9-4962-84CC-0698F6D11837}.Insider|x86.Build.0 = Release|Any CPU + {43360223-1CD9-4962-84CC-0698F6D11837}.Release|Any CPU.ActiveCfg = Release|Any CPU + {43360223-1CD9-4962-84CC-0698F6D11837}.Release|Any CPU.Build.0 = Release|Any CPU {43360223-1CD9-4962-84CC-0698F6D11837}.Release|ARM.ActiveCfg = Release|Any CPU {43360223-1CD9-4962-84CC-0698F6D11837}.Release|ARM.Build.0 = Release|Any CPU {43360223-1CD9-4962-84CC-0698F6D11837}.Release|ARM64.ActiveCfg = Release|Any CPU @@ -174,6 +208,8 @@ Global {43360223-1CD9-4962-84CC-0698F6D11837}.Release|x64.Build.0 = Release|Any CPU {43360223-1CD9-4962-84CC-0698F6D11837}.Release|x86.ActiveCfg = Release|Any CPU {43360223-1CD9-4962-84CC-0698F6D11837}.Release|x86.Build.0 = Release|Any CPU + {CEBA2186-BA2C-4616-BEEE-01E0F899A4A1}.Alpha|Any CPU.ActiveCfg = Alpha|Any CPU + {CEBA2186-BA2C-4616-BEEE-01E0F899A4A1}.Alpha|Any CPU.Build.0 = Alpha|Any CPU {CEBA2186-BA2C-4616-BEEE-01E0F899A4A1}.Alpha|ARM.ActiveCfg = Alpha|Any CPU {CEBA2186-BA2C-4616-BEEE-01E0F899A4A1}.Alpha|ARM.Build.0 = Alpha|Any CPU {CEBA2186-BA2C-4616-BEEE-01E0F899A4A1}.Alpha|ARM64.ActiveCfg = Alpha|Any CPU @@ -182,6 +218,8 @@ Global {CEBA2186-BA2C-4616-BEEE-01E0F899A4A1}.Alpha|x64.Build.0 = Alpha|Any CPU {CEBA2186-BA2C-4616-BEEE-01E0F899A4A1}.Alpha|x86.ActiveCfg = Alpha|Any CPU {CEBA2186-BA2C-4616-BEEE-01E0F899A4A1}.Alpha|x86.Build.0 = Alpha|Any CPU + {CEBA2186-BA2C-4616-BEEE-01E0F899A4A1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {CEBA2186-BA2C-4616-BEEE-01E0F899A4A1}.Debug|Any CPU.Build.0 = Debug|Any CPU {CEBA2186-BA2C-4616-BEEE-01E0F899A4A1}.Debug|ARM.ActiveCfg = Debug|Any CPU {CEBA2186-BA2C-4616-BEEE-01E0F899A4A1}.Debug|ARM.Build.0 = Debug|Any CPU {CEBA2186-BA2C-4616-BEEE-01E0F899A4A1}.Debug|ARM64.ActiveCfg = Debug|Any CPU @@ -190,6 +228,8 @@ Global {CEBA2186-BA2C-4616-BEEE-01E0F899A4A1}.Debug|x64.Build.0 = Debug|Any CPU {CEBA2186-BA2C-4616-BEEE-01E0F899A4A1}.Debug|x86.ActiveCfg = Debug|Any CPU {CEBA2186-BA2C-4616-BEEE-01E0F899A4A1}.Debug|x86.Build.0 = Debug|Any CPU + {CEBA2186-BA2C-4616-BEEE-01E0F899A4A1}.Insider|Any CPU.ActiveCfg = Insider|Any CPU + {CEBA2186-BA2C-4616-BEEE-01E0F899A4A1}.Insider|Any CPU.Build.0 = Insider|Any CPU {CEBA2186-BA2C-4616-BEEE-01E0F899A4A1}.Insider|ARM.ActiveCfg = Insider|Any CPU {CEBA2186-BA2C-4616-BEEE-01E0F899A4A1}.Insider|ARM.Build.0 = Insider|Any CPU {CEBA2186-BA2C-4616-BEEE-01E0F899A4A1}.Insider|ARM64.ActiveCfg = Insider|Any CPU @@ -198,6 +238,8 @@ Global {CEBA2186-BA2C-4616-BEEE-01E0F899A4A1}.Insider|x64.Build.0 = Insider|Any CPU {CEBA2186-BA2C-4616-BEEE-01E0F899A4A1}.Insider|x86.ActiveCfg = Insider|Any CPU {CEBA2186-BA2C-4616-BEEE-01E0F899A4A1}.Insider|x86.Build.0 = Insider|Any CPU + {CEBA2186-BA2C-4616-BEEE-01E0F899A4A1}.Release|Any CPU.ActiveCfg = Release|Any CPU + {CEBA2186-BA2C-4616-BEEE-01E0F899A4A1}.Release|Any CPU.Build.0 = Release|Any CPU {CEBA2186-BA2C-4616-BEEE-01E0F899A4A1}.Release|ARM.ActiveCfg = Release|Any CPU {CEBA2186-BA2C-4616-BEEE-01E0F899A4A1}.Release|ARM.Build.0 = Release|Any CPU {CEBA2186-BA2C-4616-BEEE-01E0F899A4A1}.Release|ARM64.ActiveCfg = Release|Any CPU @@ -206,6 +248,8 @@ Global {CEBA2186-BA2C-4616-BEEE-01E0F899A4A1}.Release|x64.Build.0 = Release|Any CPU {CEBA2186-BA2C-4616-BEEE-01E0F899A4A1}.Release|x86.ActiveCfg = Release|Any CPU {CEBA2186-BA2C-4616-BEEE-01E0F899A4A1}.Release|x86.Build.0 = Release|Any CPU + {E20B2927-0C9E-437F-9B51-B76BAE2EF67C}.Alpha|Any CPU.ActiveCfg = Alpha|Any CPU + {E20B2927-0C9E-437F-9B51-B76BAE2EF67C}.Alpha|Any CPU.Build.0 = Alpha|Any CPU {E20B2927-0C9E-437F-9B51-B76BAE2EF67C}.Alpha|ARM.ActiveCfg = Alpha|Any CPU {E20B2927-0C9E-437F-9B51-B76BAE2EF67C}.Alpha|ARM.Build.0 = Alpha|Any CPU {E20B2927-0C9E-437F-9B51-B76BAE2EF67C}.Alpha|ARM64.ActiveCfg = Alpha|Any CPU @@ -214,6 +258,8 @@ Global {E20B2927-0C9E-437F-9B51-B76BAE2EF67C}.Alpha|x64.Build.0 = Alpha|Any CPU {E20B2927-0C9E-437F-9B51-B76BAE2EF67C}.Alpha|x86.ActiveCfg = Alpha|Any CPU {E20B2927-0C9E-437F-9B51-B76BAE2EF67C}.Alpha|x86.Build.0 = Alpha|Any CPU + {E20B2927-0C9E-437F-9B51-B76BAE2EF67C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {E20B2927-0C9E-437F-9B51-B76BAE2EF67C}.Debug|Any CPU.Build.0 = Debug|Any CPU {E20B2927-0C9E-437F-9B51-B76BAE2EF67C}.Debug|ARM.ActiveCfg = Debug|Any CPU {E20B2927-0C9E-437F-9B51-B76BAE2EF67C}.Debug|ARM.Build.0 = Debug|Any CPU {E20B2927-0C9E-437F-9B51-B76BAE2EF67C}.Debug|ARM64.ActiveCfg = Debug|Any CPU @@ -222,6 +268,8 @@ Global {E20B2927-0C9E-437F-9B51-B76BAE2EF67C}.Debug|x64.Build.0 = Debug|Any CPU {E20B2927-0C9E-437F-9B51-B76BAE2EF67C}.Debug|x86.ActiveCfg = Debug|Any CPU {E20B2927-0C9E-437F-9B51-B76BAE2EF67C}.Debug|x86.Build.0 = Debug|Any CPU + {E20B2927-0C9E-437F-9B51-B76BAE2EF67C}.Insider|Any CPU.ActiveCfg = Insider|Any CPU + {E20B2927-0C9E-437F-9B51-B76BAE2EF67C}.Insider|Any CPU.Build.0 = Insider|Any CPU {E20B2927-0C9E-437F-9B51-B76BAE2EF67C}.Insider|ARM.ActiveCfg = Insider|Any CPU {E20B2927-0C9E-437F-9B51-B76BAE2EF67C}.Insider|ARM.Build.0 = Insider|Any CPU {E20B2927-0C9E-437F-9B51-B76BAE2EF67C}.Insider|ARM64.ActiveCfg = Insider|Any CPU @@ -230,6 +278,8 @@ Global {E20B2927-0C9E-437F-9B51-B76BAE2EF67C}.Insider|x64.Build.0 = Insider|Any CPU {E20B2927-0C9E-437F-9B51-B76BAE2EF67C}.Insider|x86.ActiveCfg = Insider|Any CPU {E20B2927-0C9E-437F-9B51-B76BAE2EF67C}.Insider|x86.Build.0 = Insider|Any CPU + {E20B2927-0C9E-437F-9B51-B76BAE2EF67C}.Release|Any CPU.ActiveCfg = Release|Any CPU + {E20B2927-0C9E-437F-9B51-B76BAE2EF67C}.Release|Any CPU.Build.0 = Release|Any CPU {E20B2927-0C9E-437F-9B51-B76BAE2EF67C}.Release|ARM.ActiveCfg = Release|Any CPU {E20B2927-0C9E-437F-9B51-B76BAE2EF67C}.Release|ARM.Build.0 = Release|Any CPU {E20B2927-0C9E-437F-9B51-B76BAE2EF67C}.Release|ARM64.ActiveCfg = Release|Any CPU @@ -238,6 +288,8 @@ Global {E20B2927-0C9E-437F-9B51-B76BAE2EF67C}.Release|x64.Build.0 = Release|Any CPU {E20B2927-0C9E-437F-9B51-B76BAE2EF67C}.Release|x86.ActiveCfg = Release|Any CPU {E20B2927-0C9E-437F-9B51-B76BAE2EF67C}.Release|x86.Build.0 = Release|Any CPU + {D5C342A2-FB47-4D19-8073-E7F15EE43F24}.Alpha|Any CPU.ActiveCfg = Alpha|Any CPU + {D5C342A2-FB47-4D19-8073-E7F15EE43F24}.Alpha|Any CPU.Build.0 = Alpha|Any CPU {D5C342A2-FB47-4D19-8073-E7F15EE43F24}.Alpha|ARM.ActiveCfg = Alpha|Any CPU {D5C342A2-FB47-4D19-8073-E7F15EE43F24}.Alpha|ARM.Build.0 = Alpha|Any CPU {D5C342A2-FB47-4D19-8073-E7F15EE43F24}.Alpha|ARM64.ActiveCfg = Alpha|Any CPU @@ -246,6 +298,8 @@ Global {D5C342A2-FB47-4D19-8073-E7F15EE43F24}.Alpha|x64.Build.0 = Alpha|Any CPU {D5C342A2-FB47-4D19-8073-E7F15EE43F24}.Alpha|x86.ActiveCfg = Alpha|Any CPU {D5C342A2-FB47-4D19-8073-E7F15EE43F24}.Alpha|x86.Build.0 = Alpha|Any CPU + {D5C342A2-FB47-4D19-8073-E7F15EE43F24}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {D5C342A2-FB47-4D19-8073-E7F15EE43F24}.Debug|Any CPU.Build.0 = Debug|Any CPU {D5C342A2-FB47-4D19-8073-E7F15EE43F24}.Debug|ARM.ActiveCfg = Debug|Any CPU {D5C342A2-FB47-4D19-8073-E7F15EE43F24}.Debug|ARM.Build.0 = Debug|Any CPU {D5C342A2-FB47-4D19-8073-E7F15EE43F24}.Debug|ARM64.ActiveCfg = Debug|Any CPU @@ -254,6 +308,8 @@ Global {D5C342A2-FB47-4D19-8073-E7F15EE43F24}.Debug|x64.Build.0 = Debug|Any CPU {D5C342A2-FB47-4D19-8073-E7F15EE43F24}.Debug|x86.ActiveCfg = Debug|Any CPU {D5C342A2-FB47-4D19-8073-E7F15EE43F24}.Debug|x86.Build.0 = Debug|Any CPU + {D5C342A2-FB47-4D19-8073-E7F15EE43F24}.Insider|Any CPU.ActiveCfg = Insider|Any CPU + {D5C342A2-FB47-4D19-8073-E7F15EE43F24}.Insider|Any CPU.Build.0 = Insider|Any CPU {D5C342A2-FB47-4D19-8073-E7F15EE43F24}.Insider|ARM.ActiveCfg = Insider|Any CPU {D5C342A2-FB47-4D19-8073-E7F15EE43F24}.Insider|ARM.Build.0 = Insider|Any CPU {D5C342A2-FB47-4D19-8073-E7F15EE43F24}.Insider|ARM64.ActiveCfg = Insider|Any CPU @@ -262,6 +318,8 @@ Global {D5C342A2-FB47-4D19-8073-E7F15EE43F24}.Insider|x64.Build.0 = Insider|Any CPU {D5C342A2-FB47-4D19-8073-E7F15EE43F24}.Insider|x86.ActiveCfg = Insider|Any CPU {D5C342A2-FB47-4D19-8073-E7F15EE43F24}.Insider|x86.Build.0 = Insider|Any CPU + {D5C342A2-FB47-4D19-8073-E7F15EE43F24}.Release|Any CPU.ActiveCfg = Release|Any CPU + {D5C342A2-FB47-4D19-8073-E7F15EE43F24}.Release|Any CPU.Build.0 = Release|Any CPU {D5C342A2-FB47-4D19-8073-E7F15EE43F24}.Release|ARM.ActiveCfg = Release|Any CPU {D5C342A2-FB47-4D19-8073-E7F15EE43F24}.Release|ARM.Build.0 = Release|Any CPU {D5C342A2-FB47-4D19-8073-E7F15EE43F24}.Release|ARM64.ActiveCfg = Release|Any CPU @@ -270,6 +328,8 @@ Global {D5C342A2-FB47-4D19-8073-E7F15EE43F24}.Release|x64.Build.0 = Release|Any CPU {D5C342A2-FB47-4D19-8073-E7F15EE43F24}.Release|x86.ActiveCfg = Release|Any CPU {D5C342A2-FB47-4D19-8073-E7F15EE43F24}.Release|x86.Build.0 = Release|Any CPU + {D34F80C0-0316-43C3-BBE9-7AF099A1C6A0}.Alpha|Any CPU.ActiveCfg = Alpha|Any CPU + {D34F80C0-0316-43C3-BBE9-7AF099A1C6A0}.Alpha|Any CPU.Build.0 = Alpha|Any CPU {D34F80C0-0316-43C3-BBE9-7AF099A1C6A0}.Alpha|ARM.ActiveCfg = Alpha|Any CPU {D34F80C0-0316-43C3-BBE9-7AF099A1C6A0}.Alpha|ARM.Build.0 = Alpha|Any CPU {D34F80C0-0316-43C3-BBE9-7AF099A1C6A0}.Alpha|ARM64.ActiveCfg = Alpha|Any CPU @@ -278,6 +338,8 @@ Global {D34F80C0-0316-43C3-BBE9-7AF099A1C6A0}.Alpha|x64.Build.0 = Alpha|Any CPU {D34F80C0-0316-43C3-BBE9-7AF099A1C6A0}.Alpha|x86.ActiveCfg = Alpha|Any CPU {D34F80C0-0316-43C3-BBE9-7AF099A1C6A0}.Alpha|x86.Build.0 = Alpha|Any CPU + {D34F80C0-0316-43C3-BBE9-7AF099A1C6A0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {D34F80C0-0316-43C3-BBE9-7AF099A1C6A0}.Debug|Any CPU.Build.0 = Debug|Any CPU {D34F80C0-0316-43C3-BBE9-7AF099A1C6A0}.Debug|ARM.ActiveCfg = Debug|Any CPU {D34F80C0-0316-43C3-BBE9-7AF099A1C6A0}.Debug|ARM.Build.0 = Debug|Any CPU {D34F80C0-0316-43C3-BBE9-7AF099A1C6A0}.Debug|ARM64.ActiveCfg = Debug|Any CPU @@ -286,6 +348,8 @@ Global {D34F80C0-0316-43C3-BBE9-7AF099A1C6A0}.Debug|x64.Build.0 = Debug|Any CPU {D34F80C0-0316-43C3-BBE9-7AF099A1C6A0}.Debug|x86.ActiveCfg = Debug|Any CPU {D34F80C0-0316-43C3-BBE9-7AF099A1C6A0}.Debug|x86.Build.0 = Debug|Any CPU + {D34F80C0-0316-43C3-BBE9-7AF099A1C6A0}.Insider|Any CPU.ActiveCfg = Insider|Any CPU + {D34F80C0-0316-43C3-BBE9-7AF099A1C6A0}.Insider|Any CPU.Build.0 = Insider|Any CPU {D34F80C0-0316-43C3-BBE9-7AF099A1C6A0}.Insider|ARM.ActiveCfg = Insider|Any CPU {D34F80C0-0316-43C3-BBE9-7AF099A1C6A0}.Insider|ARM.Build.0 = Insider|Any CPU {D34F80C0-0316-43C3-BBE9-7AF099A1C6A0}.Insider|ARM64.ActiveCfg = Insider|Any CPU @@ -294,6 +358,8 @@ Global {D34F80C0-0316-43C3-BBE9-7AF099A1C6A0}.Insider|x64.Build.0 = Insider|Any CPU {D34F80C0-0316-43C3-BBE9-7AF099A1C6A0}.Insider|x86.ActiveCfg = Insider|Any CPU {D34F80C0-0316-43C3-BBE9-7AF099A1C6A0}.Insider|x86.Build.0 = Insider|Any CPU + {D34F80C0-0316-43C3-BBE9-7AF099A1C6A0}.Release|Any CPU.ActiveCfg = Release|Any CPU + {D34F80C0-0316-43C3-BBE9-7AF099A1C6A0}.Release|Any CPU.Build.0 = Release|Any CPU {D34F80C0-0316-43C3-BBE9-7AF099A1C6A0}.Release|ARM.ActiveCfg = Release|Any CPU {D34F80C0-0316-43C3-BBE9-7AF099A1C6A0}.Release|ARM.Build.0 = Release|Any CPU {D34F80C0-0316-43C3-BBE9-7AF099A1C6A0}.Release|ARM64.ActiveCfg = Release|Any CPU @@ -302,6 +368,8 @@ Global {D34F80C0-0316-43C3-BBE9-7AF099A1C6A0}.Release|x64.Build.0 = Release|Any CPU {D34F80C0-0316-43C3-BBE9-7AF099A1C6A0}.Release|x86.ActiveCfg = Release|Any CPU {D34F80C0-0316-43C3-BBE9-7AF099A1C6A0}.Release|x86.Build.0 = Release|Any CPU + {BA8BE893-2B4D-4213-A4AF-53AA28BF2E46}.Alpha|Any CPU.ActiveCfg = Alpha|x64 + {BA8BE893-2B4D-4213-A4AF-53AA28BF2E46}.Alpha|Any CPU.Build.0 = Alpha|x64 {BA8BE893-2B4D-4213-A4AF-53AA28BF2E46}.Alpha|ARM.ActiveCfg = Alpha|ARM {BA8BE893-2B4D-4213-A4AF-53AA28BF2E46}.Alpha|ARM.Build.0 = Alpha|ARM {BA8BE893-2B4D-4213-A4AF-53AA28BF2E46}.Alpha|ARM64.ActiveCfg = Alpha|ARM64 @@ -310,6 +378,8 @@ Global {BA8BE893-2B4D-4213-A4AF-53AA28BF2E46}.Alpha|x64.Build.0 = Alpha|x64 {BA8BE893-2B4D-4213-A4AF-53AA28BF2E46}.Alpha|x86.ActiveCfg = Alpha|x86 {BA8BE893-2B4D-4213-A4AF-53AA28BF2E46}.Alpha|x86.Build.0 = Alpha|x86 + {BA8BE893-2B4D-4213-A4AF-53AA28BF2E46}.Debug|Any CPU.ActiveCfg = Debug|x64 + {BA8BE893-2B4D-4213-A4AF-53AA28BF2E46}.Debug|Any CPU.Build.0 = Debug|x64 {BA8BE893-2B4D-4213-A4AF-53AA28BF2E46}.Debug|ARM.ActiveCfg = Debug|ARM {BA8BE893-2B4D-4213-A4AF-53AA28BF2E46}.Debug|ARM.Build.0 = Debug|ARM {BA8BE893-2B4D-4213-A4AF-53AA28BF2E46}.Debug|ARM64.ActiveCfg = Debug|ARM64 @@ -318,6 +388,8 @@ Global {BA8BE893-2B4D-4213-A4AF-53AA28BF2E46}.Debug|x64.Build.0 = Debug|x64 {BA8BE893-2B4D-4213-A4AF-53AA28BF2E46}.Debug|x86.ActiveCfg = Debug|x86 {BA8BE893-2B4D-4213-A4AF-53AA28BF2E46}.Debug|x86.Build.0 = Debug|x86 + {BA8BE893-2B4D-4213-A4AF-53AA28BF2E46}.Insider|Any CPU.ActiveCfg = Insider|x64 + {BA8BE893-2B4D-4213-A4AF-53AA28BF2E46}.Insider|Any CPU.Build.0 = Insider|x64 {BA8BE893-2B4D-4213-A4AF-53AA28BF2E46}.Insider|ARM.ActiveCfg = Insider|ARM {BA8BE893-2B4D-4213-A4AF-53AA28BF2E46}.Insider|ARM.Build.0 = Insider|ARM {BA8BE893-2B4D-4213-A4AF-53AA28BF2E46}.Insider|ARM64.ActiveCfg = Insider|ARM64 @@ -326,6 +398,8 @@ Global {BA8BE893-2B4D-4213-A4AF-53AA28BF2E46}.Insider|x64.Build.0 = Insider|x64 {BA8BE893-2B4D-4213-A4AF-53AA28BF2E46}.Insider|x86.ActiveCfg = Insider|x86 {BA8BE893-2B4D-4213-A4AF-53AA28BF2E46}.Insider|x86.Build.0 = Insider|x86 + {BA8BE893-2B4D-4213-A4AF-53AA28BF2E46}.Release|Any CPU.ActiveCfg = Release|x64 + {BA8BE893-2B4D-4213-A4AF-53AA28BF2E46}.Release|Any CPU.Build.0 = Release|x64 {BA8BE893-2B4D-4213-A4AF-53AA28BF2E46}.Release|ARM.ActiveCfg = Release|ARM {BA8BE893-2B4D-4213-A4AF-53AA28BF2E46}.Release|ARM.Build.0 = Release|ARM {BA8BE893-2B4D-4213-A4AF-53AA28BF2E46}.Release|ARM64.ActiveCfg = Release|ARM64 @@ -334,10 +408,16 @@ Global {BA8BE893-2B4D-4213-A4AF-53AA28BF2E46}.Release|x64.Build.0 = Release|x64 {BA8BE893-2B4D-4213-A4AF-53AA28BF2E46}.Release|x86.ActiveCfg = Release|x86 {BA8BE893-2B4D-4213-A4AF-53AA28BF2E46}.Release|x86.Build.0 = Release|x86 + {4304E7AB-92E3-4313-AD8B-EFDCB033C0CE}.Alpha|Any CPU.ActiveCfg = Debug|x86 + {4304E7AB-92E3-4313-AD8B-EFDCB033C0CE}.Alpha|Any CPU.Build.0 = Debug|x86 + {4304E7AB-92E3-4313-AD8B-EFDCB033C0CE}.Alpha|Any CPU.Deploy.0 = Debug|x86 {4304E7AB-92E3-4313-AD8B-EFDCB033C0CE}.Alpha|ARM.ActiveCfg = Debug|ARM {4304E7AB-92E3-4313-AD8B-EFDCB033C0CE}.Alpha|ARM64.ActiveCfg = Debug|ARM64 {4304E7AB-92E3-4313-AD8B-EFDCB033C0CE}.Alpha|x64.ActiveCfg = Debug|x64 {4304E7AB-92E3-4313-AD8B-EFDCB033C0CE}.Alpha|x86.ActiveCfg = Debug|x86 + {4304E7AB-92E3-4313-AD8B-EFDCB033C0CE}.Debug|Any CPU.ActiveCfg = Debug|x64 + {4304E7AB-92E3-4313-AD8B-EFDCB033C0CE}.Debug|Any CPU.Build.0 = Debug|x64 + {4304E7AB-92E3-4313-AD8B-EFDCB033C0CE}.Debug|Any CPU.Deploy.0 = Debug|x64 {4304E7AB-92E3-4313-AD8B-EFDCB033C0CE}.Debug|ARM.ActiveCfg = Debug|ARM {4304E7AB-92E3-4313-AD8B-EFDCB033C0CE}.Debug|ARM.Build.0 = Debug|ARM {4304E7AB-92E3-4313-AD8B-EFDCB033C0CE}.Debug|ARM.Deploy.0 = Debug|ARM @@ -350,18 +430,28 @@ Global {4304E7AB-92E3-4313-AD8B-EFDCB033C0CE}.Debug|x86.ActiveCfg = Debug|x86 {4304E7AB-92E3-4313-AD8B-EFDCB033C0CE}.Debug|x86.Build.0 = Debug|x86 {4304E7AB-92E3-4313-AD8B-EFDCB033C0CE}.Debug|x86.Deploy.0 = Debug|x86 + {4304E7AB-92E3-4313-AD8B-EFDCB033C0CE}.Insider|Any CPU.ActiveCfg = Debug|x86 + {4304E7AB-92E3-4313-AD8B-EFDCB033C0CE}.Insider|Any CPU.Build.0 = Debug|x86 + {4304E7AB-92E3-4313-AD8B-EFDCB033C0CE}.Insider|Any CPU.Deploy.0 = Debug|x86 {4304E7AB-92E3-4313-AD8B-EFDCB033C0CE}.Insider|ARM.ActiveCfg = Debug|ARM {4304E7AB-92E3-4313-AD8B-EFDCB033C0CE}.Insider|ARM64.ActiveCfg = Debug|ARM64 {4304E7AB-92E3-4313-AD8B-EFDCB033C0CE}.Insider|x64.ActiveCfg = Debug|x64 {4304E7AB-92E3-4313-AD8B-EFDCB033C0CE}.Insider|x86.ActiveCfg = Debug|x86 + {4304E7AB-92E3-4313-AD8B-EFDCB033C0CE}.Release|Any CPU.ActiveCfg = Debug|x86 + {4304E7AB-92E3-4313-AD8B-EFDCB033C0CE}.Release|Any CPU.Build.0 = Debug|x86 + {4304E7AB-92E3-4313-AD8B-EFDCB033C0CE}.Release|Any CPU.Deploy.0 = Debug|x86 {4304E7AB-92E3-4313-AD8B-EFDCB033C0CE}.Release|ARM.ActiveCfg = Debug|ARM {4304E7AB-92E3-4313-AD8B-EFDCB033C0CE}.Release|ARM64.ActiveCfg = Debug|ARM64 {4304E7AB-92E3-4313-AD8B-EFDCB033C0CE}.Release|x64.ActiveCfg = Debug|x64 {4304E7AB-92E3-4313-AD8B-EFDCB033C0CE}.Release|x86.ActiveCfg = Debug|x86 + {8BD6BF36-6ECD-4103-B064-1CAE37E954F2}.Alpha|Any CPU.ActiveCfg = Release|Any CPU + {8BD6BF36-6ECD-4103-B064-1CAE37E954F2}.Alpha|Any CPU.Build.0 = Release|Any CPU {8BD6BF36-6ECD-4103-B064-1CAE37E954F2}.Alpha|ARM.ActiveCfg = Debug|Any CPU {8BD6BF36-6ECD-4103-B064-1CAE37E954F2}.Alpha|ARM64.ActiveCfg = Debug|Any CPU {8BD6BF36-6ECD-4103-B064-1CAE37E954F2}.Alpha|x64.ActiveCfg = Debug|Any CPU {8BD6BF36-6ECD-4103-B064-1CAE37E954F2}.Alpha|x86.ActiveCfg = Debug|Any CPU + {8BD6BF36-6ECD-4103-B064-1CAE37E954F2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {8BD6BF36-6ECD-4103-B064-1CAE37E954F2}.Debug|Any CPU.Build.0 = Debug|Any CPU {8BD6BF36-6ECD-4103-B064-1CAE37E954F2}.Debug|ARM.ActiveCfg = Debug|Any CPU {8BD6BF36-6ECD-4103-B064-1CAE37E954F2}.Debug|ARM.Build.0 = Debug|Any CPU {8BD6BF36-6ECD-4103-B064-1CAE37E954F2}.Debug|ARM64.ActiveCfg = Debug|Any CPU @@ -370,14 +460,20 @@ Global {8BD6BF36-6ECD-4103-B064-1CAE37E954F2}.Debug|x64.Build.0 = Debug|Any CPU {8BD6BF36-6ECD-4103-B064-1CAE37E954F2}.Debug|x86.ActiveCfg = Debug|Any CPU {8BD6BF36-6ECD-4103-B064-1CAE37E954F2}.Debug|x86.Build.0 = Debug|Any CPU + {8BD6BF36-6ECD-4103-B064-1CAE37E954F2}.Insider|Any CPU.ActiveCfg = Insider|Any CPU + {8BD6BF36-6ECD-4103-B064-1CAE37E954F2}.Insider|Any CPU.Build.0 = Insider|Any CPU {8BD6BF36-6ECD-4103-B064-1CAE37E954F2}.Insider|ARM.ActiveCfg = Insider|Any CPU {8BD6BF36-6ECD-4103-B064-1CAE37E954F2}.Insider|ARM64.ActiveCfg = Insider|Any CPU {8BD6BF36-6ECD-4103-B064-1CAE37E954F2}.Insider|x64.ActiveCfg = Insider|Any CPU {8BD6BF36-6ECD-4103-B064-1CAE37E954F2}.Insider|x86.ActiveCfg = Insider|Any CPU + {8BD6BF36-6ECD-4103-B064-1CAE37E954F2}.Release|Any CPU.ActiveCfg = Release|Any CPU + {8BD6BF36-6ECD-4103-B064-1CAE37E954F2}.Release|Any CPU.Build.0 = Release|Any CPU {8BD6BF36-6ECD-4103-B064-1CAE37E954F2}.Release|ARM.ActiveCfg = Release|Any CPU {8BD6BF36-6ECD-4103-B064-1CAE37E954F2}.Release|ARM64.ActiveCfg = Release|Any CPU {8BD6BF36-6ECD-4103-B064-1CAE37E954F2}.Release|x64.ActiveCfg = Release|Any CPU {8BD6BF36-6ECD-4103-B064-1CAE37E954F2}.Release|x86.ActiveCfg = Release|Any CPU + {E4B6DFB8-B9B7-4EE7-9F86-AFDDC221FFB3}.Alpha|Any CPU.ActiveCfg = Alpha|x64 + {E4B6DFB8-B9B7-4EE7-9F86-AFDDC221FFB3}.Alpha|Any CPU.Build.0 = Alpha|x64 {E4B6DFB8-B9B7-4EE7-9F86-AFDDC221FFB3}.Alpha|ARM.ActiveCfg = Alpha|ARM {E4B6DFB8-B9B7-4EE7-9F86-AFDDC221FFB3}.Alpha|ARM.Build.0 = Alpha|ARM {E4B6DFB8-B9B7-4EE7-9F86-AFDDC221FFB3}.Alpha|ARM64.ActiveCfg = Alpha|ARM64 @@ -386,6 +482,8 @@ Global {E4B6DFB8-B9B7-4EE7-9F86-AFDDC221FFB3}.Alpha|x64.Build.0 = Alpha|x64 {E4B6DFB8-B9B7-4EE7-9F86-AFDDC221FFB3}.Alpha|x86.ActiveCfg = Alpha|x86 {E4B6DFB8-B9B7-4EE7-9F86-AFDDC221FFB3}.Alpha|x86.Build.0 = Alpha|x86 + {E4B6DFB8-B9B7-4EE7-9F86-AFDDC221FFB3}.Debug|Any CPU.ActiveCfg = Debug|x64 + {E4B6DFB8-B9B7-4EE7-9F86-AFDDC221FFB3}.Debug|Any CPU.Build.0 = Debug|x64 {E4B6DFB8-B9B7-4EE7-9F86-AFDDC221FFB3}.Debug|ARM.ActiveCfg = Debug|ARM {E4B6DFB8-B9B7-4EE7-9F86-AFDDC221FFB3}.Debug|ARM.Build.0 = Debug|ARM {E4B6DFB8-B9B7-4EE7-9F86-AFDDC221FFB3}.Debug|ARM64.ActiveCfg = Debug|ARM64 @@ -394,6 +492,8 @@ Global {E4B6DFB8-B9B7-4EE7-9F86-AFDDC221FFB3}.Debug|x64.Build.0 = Debug|x64 {E4B6DFB8-B9B7-4EE7-9F86-AFDDC221FFB3}.Debug|x86.ActiveCfg = Debug|x86 {E4B6DFB8-B9B7-4EE7-9F86-AFDDC221FFB3}.Debug|x86.Build.0 = Debug|x86 + {E4B6DFB8-B9B7-4EE7-9F86-AFDDC221FFB3}.Insider|Any CPU.ActiveCfg = Insider|x64 + {E4B6DFB8-B9B7-4EE7-9F86-AFDDC221FFB3}.Insider|Any CPU.Build.0 = Insider|x64 {E4B6DFB8-B9B7-4EE7-9F86-AFDDC221FFB3}.Insider|ARM.ActiveCfg = Insider|ARM {E4B6DFB8-B9B7-4EE7-9F86-AFDDC221FFB3}.Insider|ARM.Build.0 = Insider|ARM {E4B6DFB8-B9B7-4EE7-9F86-AFDDC221FFB3}.Insider|ARM64.ActiveCfg = Insider|ARM64 @@ -402,6 +502,8 @@ Global {E4B6DFB8-B9B7-4EE7-9F86-AFDDC221FFB3}.Insider|x64.Build.0 = Insider|x64 {E4B6DFB8-B9B7-4EE7-9F86-AFDDC221FFB3}.Insider|x86.ActiveCfg = Insider|x86 {E4B6DFB8-B9B7-4EE7-9F86-AFDDC221FFB3}.Insider|x86.Build.0 = Insider|x86 + {E4B6DFB8-B9B7-4EE7-9F86-AFDDC221FFB3}.Release|Any CPU.ActiveCfg = Release|x64 + {E4B6DFB8-B9B7-4EE7-9F86-AFDDC221FFB3}.Release|Any CPU.Build.0 = Release|x64 {E4B6DFB8-B9B7-4EE7-9F86-AFDDC221FFB3}.Release|ARM.ActiveCfg = Release|ARM {E4B6DFB8-B9B7-4EE7-9F86-AFDDC221FFB3}.Release|ARM.Build.0 = Release|ARM {E4B6DFB8-B9B7-4EE7-9F86-AFDDC221FFB3}.Release|ARM64.ActiveCfg = Release|ARM64 @@ -410,6 +512,46 @@ Global {E4B6DFB8-B9B7-4EE7-9F86-AFDDC221FFB3}.Release|x64.Build.0 = Release|x64 {E4B6DFB8-B9B7-4EE7-9F86-AFDDC221FFB3}.Release|x86.ActiveCfg = Release|x86 {E4B6DFB8-B9B7-4EE7-9F86-AFDDC221FFB3}.Release|x86.Build.0 = Release|x86 + {DA2758C2-8D98-46EF-8DE3-833089AF99C4}.Alpha|Any CPU.ActiveCfg = Debug|Any CPU + {DA2758C2-8D98-46EF-8DE3-833089AF99C4}.Alpha|Any CPU.Build.0 = Debug|Any CPU + {DA2758C2-8D98-46EF-8DE3-833089AF99C4}.Alpha|ARM.ActiveCfg = Debug|Any CPU + {DA2758C2-8D98-46EF-8DE3-833089AF99C4}.Alpha|ARM.Build.0 = Debug|Any CPU + {DA2758C2-8D98-46EF-8DE3-833089AF99C4}.Alpha|ARM64.ActiveCfg = Debug|Any CPU + {DA2758C2-8D98-46EF-8DE3-833089AF99C4}.Alpha|ARM64.Build.0 = Debug|Any CPU + {DA2758C2-8D98-46EF-8DE3-833089AF99C4}.Alpha|x64.ActiveCfg = Debug|Any CPU + {DA2758C2-8D98-46EF-8DE3-833089AF99C4}.Alpha|x64.Build.0 = Debug|Any CPU + {DA2758C2-8D98-46EF-8DE3-833089AF99C4}.Alpha|x86.ActiveCfg = Debug|Any CPU + {DA2758C2-8D98-46EF-8DE3-833089AF99C4}.Alpha|x86.Build.0 = Debug|Any CPU + {DA2758C2-8D98-46EF-8DE3-833089AF99C4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {DA2758C2-8D98-46EF-8DE3-833089AF99C4}.Debug|Any CPU.Build.0 = Debug|Any CPU + {DA2758C2-8D98-46EF-8DE3-833089AF99C4}.Debug|ARM.ActiveCfg = Debug|Any CPU + {DA2758C2-8D98-46EF-8DE3-833089AF99C4}.Debug|ARM.Build.0 = Debug|Any CPU + {DA2758C2-8D98-46EF-8DE3-833089AF99C4}.Debug|ARM64.ActiveCfg = Debug|Any CPU + {DA2758C2-8D98-46EF-8DE3-833089AF99C4}.Debug|ARM64.Build.0 = Debug|Any CPU + {DA2758C2-8D98-46EF-8DE3-833089AF99C4}.Debug|x64.ActiveCfg = Debug|Any CPU + {DA2758C2-8D98-46EF-8DE3-833089AF99C4}.Debug|x64.Build.0 = Debug|Any CPU + {DA2758C2-8D98-46EF-8DE3-833089AF99C4}.Debug|x86.ActiveCfg = Debug|Any CPU + {DA2758C2-8D98-46EF-8DE3-833089AF99C4}.Debug|x86.Build.0 = Debug|Any CPU + {DA2758C2-8D98-46EF-8DE3-833089AF99C4}.Insider|Any CPU.ActiveCfg = Insider|Any CPU + {DA2758C2-8D98-46EF-8DE3-833089AF99C4}.Insider|Any CPU.Build.0 = Insider|Any CPU + {DA2758C2-8D98-46EF-8DE3-833089AF99C4}.Insider|ARM.ActiveCfg = Insider|Any CPU + {DA2758C2-8D98-46EF-8DE3-833089AF99C4}.Insider|ARM.Build.0 = Insider|Any CPU + {DA2758C2-8D98-46EF-8DE3-833089AF99C4}.Insider|ARM64.ActiveCfg = Insider|Any CPU + {DA2758C2-8D98-46EF-8DE3-833089AF99C4}.Insider|ARM64.Build.0 = Insider|Any CPU + {DA2758C2-8D98-46EF-8DE3-833089AF99C4}.Insider|x64.ActiveCfg = Insider|Any CPU + {DA2758C2-8D98-46EF-8DE3-833089AF99C4}.Insider|x64.Build.0 = Insider|Any CPU + {DA2758C2-8D98-46EF-8DE3-833089AF99C4}.Insider|x86.ActiveCfg = Insider|Any CPU + {DA2758C2-8D98-46EF-8DE3-833089AF99C4}.Insider|x86.Build.0 = Insider|Any CPU + {DA2758C2-8D98-46EF-8DE3-833089AF99C4}.Release|Any CPU.ActiveCfg = Release|Any CPU + {DA2758C2-8D98-46EF-8DE3-833089AF99C4}.Release|Any CPU.Build.0 = Release|Any CPU + {DA2758C2-8D98-46EF-8DE3-833089AF99C4}.Release|ARM.ActiveCfg = Release|Any CPU + {DA2758C2-8D98-46EF-8DE3-833089AF99C4}.Release|ARM.Build.0 = Release|Any CPU + {DA2758C2-8D98-46EF-8DE3-833089AF99C4}.Release|ARM64.ActiveCfg = Release|Any CPU + {DA2758C2-8D98-46EF-8DE3-833089AF99C4}.Release|ARM64.Build.0 = Release|Any CPU + {DA2758C2-8D98-46EF-8DE3-833089AF99C4}.Release|x64.ActiveCfg = Release|Any CPU + {DA2758C2-8D98-46EF-8DE3-833089AF99C4}.Release|x64.Build.0 = Release|Any CPU + {DA2758C2-8D98-46EF-8DE3-833089AF99C4}.Release|x86.ActiveCfg = Release|Any CPU + {DA2758C2-8D98-46EF-8DE3-833089AF99C4}.Release|x86.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -424,6 +566,7 @@ Global {4304E7AB-92E3-4313-AD8B-EFDCB033C0CE} = {7B631D3A-F353-4557-B848-A68140341F53} {8BD6BF36-6ECD-4103-B064-1CAE37E954F2} = {E3BFEBEE-5570-4885-B4C7-2CB3E7B04C60} {E4B6DFB8-B9B7-4EE7-9F86-AFDDC221FFB3} = {29E0E840-B85F-4209-933B-8369DB2EA187} + {DA2758C2-8D98-46EF-8DE3-833089AF99C4} = {29E0E840-B85F-4209-933B-8369DB2EA187} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {2B4323A3-5C28-4929-AB1C-20CE992D6024} diff --git a/samples/Quarrel.Samples.RichPresence/MainPage.xaml.cs b/samples/Quarrel.Samples.RichPresence/MainPage.xaml.cs index 09c16983d..f1b30cacd 100644 --- a/samples/Quarrel.Samples.RichPresence/MainPage.xaml.cs +++ b/samples/Quarrel.Samples.RichPresence/MainPage.xaml.cs @@ -43,7 +43,7 @@ private async void Connect(object sender, RoutedEventArgs e) private async void SetActivity(object sender, RoutedEventArgs e) { Activity activity = new Activity(ActivityName.Text); - bool success = await _connection.SetActivity(activity); + await _connection.SetActivity(activity); } } } diff --git a/src/API/Discord.API.Status/Discord.API.Status.csproj b/src/API/Discord.API.Status/Discord.API.Status.csproj index 217d52365..221d2b7d1 100644 --- a/src/API/Discord.API.Status/Discord.API.Status.csproj +++ b/src/API/Discord.API.Status/Discord.API.Status.csproj @@ -3,6 +3,7 @@ netstandard2.0 Debug;Insider;Alpha;Release + false diff --git a/src/API/Discord.API.Status/Models/Datum.cs b/src/API/Discord.API.Status/Models/Datum.cs index 2ff08f822..f2b159a9a 100644 --- a/src/API/Discord.API.Status/Models/Datum.cs +++ b/src/API/Discord.API.Status/Models/Datum.cs @@ -2,6 +2,9 @@ using System.Text.Json.Serialization; +// JSON models don't need to respect standard nullable rules. +#pragma warning disable CS8618 + namespace Discord.API.Status.Models { /// diff --git a/src/API/Discord.API.Status/Models/Summary.cs b/src/API/Discord.API.Status/Models/Summary.cs index 5a68e80b0..92ade592f 100644 --- a/src/API/Discord.API.Status/Models/Summary.cs +++ b/src/API/Discord.API.Status/Models/Summary.cs @@ -2,13 +2,25 @@ using System.Text.Json.Serialization; +// JSON models don't need to respect standard nullable rules. +#pragma warning disable CS8618 + namespace Discord.API.Status.Models { + /// + /// A summary of a data set. + /// public partial class Summary { + /// + /// The sum of the data. + /// [JsonPropertyName("sum")] public double Sum { get; set; } + /// + /// The mean of the data. + /// [JsonPropertyName("mean")] public double Mean { get; set; } } diff --git a/src/API/Discord.API/Discord.API.csproj b/src/API/Discord.API/Discord.API.csproj index 7b563d1a1..582f183a0 100644 --- a/src/API/Discord.API/Discord.API.csproj +++ b/src/API/Discord.API/Discord.API.csproj @@ -10,4 +10,8 @@ + + + + diff --git a/src/API/Discord.API/Gateways/Gateway.Requests.cs b/src/API/Discord.API/Gateways/Gateway.Requests.cs index 17bcb7c65..cee451962 100644 --- a/src/API/Discord.API/Gateways/Gateway.Requests.cs +++ b/src/API/Discord.API/Gateways/Gateway.Requests.cs @@ -1,6 +1,7 @@ // Quarrel © 2022 using Discord.API.Gateways.Models; +using Discord.API.Models.Enums.Users; using System; using System.Threading.Tasks; @@ -43,11 +44,11 @@ public void SubscribeToGuildAsync(ulong[] channelIds) throw new NotImplementedException(); } - public async Task UpdateStatusAsync(string status, int? idleSince, bool isAfk) + public async Task UpdateStatusAsync(UserStatus status, int? idleSince = null, bool isAfk = false) { var payload = new StatusUpdate() { - Status = status, + Status = status.GetStringValue(), IdleSince = idleSince, IsAFK = isAfk, }; @@ -55,7 +56,7 @@ public async Task UpdateStatusAsync(string status, int? idleSince, bool isAfk) await UpdateStatusAsync(payload); } - public async Task UpdateStatusAsync(StatusUpdate payload) + private async Task UpdateStatusAsync(StatusUpdate payload) { var frame = new SocketFrame() { diff --git a/src/API/Discord.API/Models/Enums/Messages/ComponentType.cs b/src/API/Discord.API/Models/Enums/Messages/ComponentType.cs index e53491ee4..a8587308e 100644 --- a/src/API/Discord.API/Models/Enums/Messages/ComponentType.cs +++ b/src/API/Discord.API/Models/Enums/Messages/ComponentType.cs @@ -1,9 +1,5 @@ // Quarrel © 2022 -using System; -using System.Collections.Generic; -using System.Text; - namespace Discord.API.Models.Enums.Messages { public enum ComponentType diff --git a/src/API/Discord.API/Models/Enums/Messages/MessageFlags.cs b/src/API/Discord.API/Models/Enums/Messages/MessageFlags.cs index df1fab8aa..f2ac91d64 100644 --- a/src/API/Discord.API/Models/Enums/Messages/MessageFlags.cs +++ b/src/API/Discord.API/Models/Enums/Messages/MessageFlags.cs @@ -1,9 +1,5 @@ // Quarrel © 2022 -using System; -using System.Collections.Generic; -using System.Text; - namespace Discord.API.Models.Enums.Messages { public enum MessageFlags diff --git a/src/API/Discord.API/Models/Enums/Users/UserStatus.cs b/src/API/Discord.API/Models/Enums/Users/UserStatus.cs index baeae8432..f8c098ad8 100644 --- a/src/API/Discord.API/Models/Enums/Users/UserStatus.cs +++ b/src/API/Discord.API/Models/Enums/Users/UserStatus.cs @@ -1,5 +1,7 @@ // Quarrel © 2022 +using Quarrel.Attributes; + namespace Discord.API.Models.Enums.Users { /// @@ -10,31 +12,37 @@ public enum UserStatus /// /// The user is offline. /// + [StringValue("offline")] Offline, /// /// The user is online. /// + [StringValue("online")] Online, /// /// The user is idle. /// + [StringValue("idle")] Idle, /// /// The user is AFK. /// + [StringValue("afk")] AFK, /// /// The user is on do not disturb. /// + [StringValue("dnd")] DoNotDisturb, /// /// The user is marked offline. /// + [StringValue("invisible")] Invisible, } } diff --git a/src/API/Discord.API/Models/Json/Applications/JsonApplication.cs b/src/API/Discord.API/Models/Json/Applications/JsonApplication.cs index 17a815413..acb67f844 100644 --- a/src/API/Discord.API/Models/Json/Applications/JsonApplication.cs +++ b/src/API/Discord.API/Models/Json/Applications/JsonApplication.cs @@ -1,11 +1,12 @@ // Quarrel © 2022 using Discord.API.Models.Json.Users; -using System; using System.Collections.Generic; -using System.Text; using System.Text.Json.Serialization; +// JSON models don't need to respect standard nullable rules. +#pragma warning disable CS8618 + namespace Discord.API.Models.Json.Applications { internal class JsonApplication diff --git a/src/API/Discord.API/Models/Json/Messages/JsonMessageComponent.cs b/src/API/Discord.API/Models/Json/Messages/JsonMessageComponent.cs index 4c08dc543..0b38a8099 100644 --- a/src/API/Discord.API/Models/Json/Messages/JsonMessageComponent.cs +++ b/src/API/Discord.API/Models/Json/Messages/JsonMessageComponent.cs @@ -1,11 +1,12 @@ // Quarrel © 2022 using Discord.API.Models.Enums.Messages; -using System; using System.Collections.Generic; -using System.Text; using System.Text.Json.Serialization; +// JSON models don't need to respect standard nullable rules. +#pragma warning disable CS8618 + namespace Discord.API.Models.Json.Messages { internal class JsonMessageComponent diff --git a/src/API/Discord.API/Models/Json/Messages/JsonMessageInteraction.cs b/src/API/Discord.API/Models/Json/Messages/JsonMessageInteraction.cs index 3cd27892f..584135b8b 100644 --- a/src/API/Discord.API/Models/Json/Messages/JsonMessageInteraction.cs +++ b/src/API/Discord.API/Models/Json/Messages/JsonMessageInteraction.cs @@ -4,6 +4,9 @@ using Discord.API.Models.Json.Users; using System.Text.Json.Serialization; +// JSON models don't need to respect standard nullable rules. +#pragma warning disable CS8618 + namespace Discord.API.Models.Json.Messages { internal class JsonMessageInteraction diff --git a/src/API/Discord.API/Models/Json/Messages/JsonMessageStickerItem.cs b/src/API/Discord.API/Models/Json/Messages/JsonMessageStickerItem.cs index a61903288..93722bcff 100644 --- a/src/API/Discord.API/Models/Json/Messages/JsonMessageStickerItem.cs +++ b/src/API/Discord.API/Models/Json/Messages/JsonMessageStickerItem.cs @@ -3,6 +3,9 @@ using Discord.API.Models.Enums.Stickers; using System.Text.Json.Serialization; +// JSON models don't need to respect standard nullable rules. +#pragma warning disable CS8618 + namespace Discord.API.Models.Json.Messages { internal class JsonMessageStickerItem diff --git a/src/API/Discord.API/Models/Json/Settings/JsonModifyUserSettings.cs b/src/API/Discord.API/Models/Json/Settings/JsonModifyUserSettings.cs new file mode 100644 index 000000000..4339de6ad --- /dev/null +++ b/src/API/Discord.API/Models/Json/Settings/JsonModifyUserSettings.cs @@ -0,0 +1,15 @@ +// Quarrel © 2022 + +using System.Text.Json.Serialization; + +// JSON models don't need to respect standard nullable rules. +#pragma warning disable CS8618 + +namespace Discord.API.Models.Json.Settings +{ + internal class JsonModifyUserSettings + { + [JsonPropertyName("status")] + public string Status { get; set; } + } +} diff --git a/src/API/Discord.API/Rest/DiscordRestFactory.cs b/src/API/Discord.API/Rest/DiscordRestFactory.cs index 69a505311..5a4c65a13 100644 --- a/src/API/Discord.API/Rest/DiscordRestFactory.cs +++ b/src/API/Discord.API/Rest/DiscordRestFactory.cs @@ -46,6 +46,14 @@ internal IChannelService GetChannelService() { return RestService.For(GetHttpClient(), _settings); } + + /// + /// Gets an instance of the . + /// + internal IUserService GetUserService() + { + return RestService.For(GetHttpClient(), _settings); + } private HttpClient GetHttpClient(bool authenticated = true) { diff --git a/src/API/Discord.API/Rest/IChannelService.cs b/src/API/Discord.API/Rest/IChannelService.cs index fef47d71f..54193e8d2 100644 --- a/src/API/Discord.API/Rest/IChannelService.cs +++ b/src/API/Discord.API/Rest/IChannelService.cs @@ -16,5 +16,8 @@ internal interface IChannelService [Post("/v9/channels/{channelId}/messages")] Task CreateMessage([AliasAs("channelId")] ulong channelId, [Body] JsonMessageUpsert message); + + [Delete("/v9/channels/{channelId}/messages/{messageId}")] + Task DeleteMessage([AliasAs("channelId")] ulong channelId, [AliasAs("messageId")] ulong messageId); } } diff --git a/src/API/Discord.API/Rest/IUserService.cs b/src/API/Discord.API/Rest/IUserService.cs new file mode 100644 index 000000000..bc4b44218 --- /dev/null +++ b/src/API/Discord.API/Rest/IUserService.cs @@ -0,0 +1,15 @@ +// Quarrel © 2022 + +using Discord.API.Models.Json.Settings; +using Refit; +using System.Threading.Tasks; + +namespace Discord.API.Rest +{ + internal interface IUserService + { + [Patch("/v6/users/@me/settings")] + [Headers("Content-Type: application/json;")] + Task UpdateSettings([Body] JsonModifyUserSettings settings); + } +} diff --git a/src/Quarrel.ViewModels/Attributes/StringValueAttribute.cs b/src/Libs/Quarrel.Common/Attributes/StringValueAttribute.cs similarity index 100% rename from src/Quarrel.ViewModels/Attributes/StringValueAttribute.cs rename to src/Libs/Quarrel.Common/Attributes/StringValueAttribute.cs diff --git a/src/Quarrel.ViewModels/Extensions/System/EnumExtensions.cs b/src/Libs/Quarrel.Common/Extensions/System/EnumExtensions.cs similarity index 100% rename from src/Quarrel.ViewModels/Extensions/System/EnumExtensions.cs rename to src/Libs/Quarrel.Common/Extensions/System/EnumExtensions.cs diff --git a/src/Libs/Quarrel.Common/Quarrel.Common.csproj b/src/Libs/Quarrel.Common/Quarrel.Common.csproj new file mode 100644 index 000000000..5813cf0b4 --- /dev/null +++ b/src/Libs/Quarrel.Common/Quarrel.Common.csproj @@ -0,0 +1,10 @@ + + + + netstandard2.0 + Quarrel.Common + Quarrel + Debug;Insider;Alpha;Release + + + diff --git a/src/Libs/Quarrel.Markdown/Quarrel.Markdown.csproj b/src/Libs/Quarrel.Markdown/Quarrel.Markdown.csproj index 9741facdd..e7e2602b4 100644 --- a/src/Libs/Quarrel.Markdown/Quarrel.Markdown.csproj +++ b/src/Libs/Quarrel.Markdown/Quarrel.Markdown.csproj @@ -3814,11 +3814,6 @@ - - - ..\..\..\..\..\..\.nuget\packages\microsoft.xaml.behaviors.uwp.managed\2.0.1\lib\uap10.0\Microsoft.Xaml.Interactivity.dll - - 14.0 diff --git a/src/Libs/Quarrel.Markdown/Rendering/Elements/TimestampElement.cs b/src/Libs/Quarrel.Markdown/Rendering/Elements/TimestampElement.cs index f69eb8f33..c0b25be98 100644 --- a/src/Libs/Quarrel.Markdown/Rendering/Elements/TimestampElement.cs +++ b/src/Libs/Quarrel.Markdown/Rendering/Elements/TimestampElement.cs @@ -14,11 +14,11 @@ internal TimestampElement(Timestamp timestamp) : base(timestamp) { "F" or "" => timestamp.Time.ToString("F"), "D" => timestamp.Time.ToString("d MMMM yyyy"), - "R" => timestamp.Time.Humanize(), "T" => timestamp.Time.ToString("T"), "d" => timestamp.Time.ToString("d"), "f" => timestamp.Time.ToString("MMMM yyyy HH:mm"), "t" => timestamp.Time.ToString("t"), + "R" or _ => timestamp.Time.Humanize(), }; } } diff --git a/src/Quarrel.Client/Models/Messages/Embeds/Attachment.cs b/src/Quarrel.Client/Models/Messages/Embeds/Attachment.cs index 9a6b03e16..3f8f8943c 100644 --- a/src/Quarrel.Client/Models/Messages/Embeds/Attachment.cs +++ b/src/Quarrel.Client/Models/Messages/Embeds/Attachment.cs @@ -5,6 +5,9 @@ namespace Quarrel.Client.Models.Messages.Embeds { + /// + /// An attachment in a message. + /// public class Attachment : SnowflakeItem { internal Attachment(JsonAttachment jsonAttachment, QuarrelClient context) : @@ -12,22 +15,41 @@ internal Attachment(JsonAttachment jsonAttachment, QuarrelClient context) : { Id = jsonAttachment.Id; Filename = jsonAttachment.Filename; + Size = jsonAttachment.Size; Url = jsonAttachment.Url; ProxyUrl = jsonAttachment.ProxyUrl; Height = jsonAttachment.Height; Width = jsonAttachment.Width; } + /// + /// Gets the name of the attached file. + /// public string Filename { get; } + /// + /// Gets the size of the attached file. + /// public ulong Size { get; } + /// + /// Gets the url of the attached file. + /// public string Url { get; } + /// + /// Gets the proxy url of the attached file. + /// public string ProxyUrl { get; } + /// + /// Gets the height of the attached file if the file is an image or video. + /// public int? Height { get; } + /// + /// Gets the width of the attached file if the file is an image or video. + /// public int? Width { get; } } } diff --git a/src/Quarrel.Client/Models/Messages/Message.cs b/src/Quarrel.Client/Models/Messages/Message.cs index 78d246ded..5d9bd725b 100644 --- a/src/Quarrel.Client/Models/Messages/Message.cs +++ b/src/Quarrel.Client/Models/Messages/Message.cs @@ -8,6 +8,9 @@ using Quarrel.Client.Models.Users; using System; +// JSON models don't need to respect standard nullable rules. +#pragma warning disable CS8618 + namespace Quarrel.Client.Models.Messages { /// @@ -66,8 +69,10 @@ internal Message(JsonMessage jsonMessage, QuarrelClient context) : Interaction = jsonMessage.Interaction; } - public ulong? ChannelId { get; private set; } + /// + public ulong ChannelId { get; private set; } + /// public ulong? GuildId { get; private set; } /// @@ -92,7 +97,10 @@ internal Message(JsonMessage jsonMessage, QuarrelClient context) : public DateTimeOffset? EditedTimestamp { get; private set; } /// - public User? Author { get; private set; } + public User Author { get; private set; } + + /// + public bool IsOwn => Author.Id == Context.CurrentUser?.Id; /// public User[] Mentions { get; private set; } @@ -108,5 +116,11 @@ internal Message(JsonMessage jsonMessage, QuarrelClient context) : /// public ulong? WebhookId { get; private set; } + + /// + public Uri MessageUri + => new Uri($"https://discord.com/channels/{GuildDisplayId}/{ChannelId}/{Id}"); + + private string GuildDisplayId => GuildId.HasValue ? $"{GuildId.Value}" : "@me"; } } diff --git a/src/Quarrel.Client/Models/Users/Interfaces/IUser.cs b/src/Quarrel.Client/Models/Users/Interfaces/IUser.cs index 74b1e7783..376340998 100644 --- a/src/Quarrel.Client/Models/Users/Interfaces/IUser.cs +++ b/src/Quarrel.Client/Models/Users/Interfaces/IUser.cs @@ -5,6 +5,9 @@ namespace Quarrel.Client.Models.Users.Interfaces { + /// + /// An interface for an object representing a user. + /// public interface IUser : ISnowflakeItem { /// diff --git a/src/Quarrel.Client/QuarrelClient.Methods.cs b/src/Quarrel.Client/QuarrelClient.Methods.cs index ddfd94255..fce54b977 100644 --- a/src/Quarrel.Client/QuarrelClient.Methods.cs +++ b/src/Quarrel.Client/QuarrelClient.Methods.cs @@ -1,8 +1,9 @@ // Quarrel © 2022 using CommunityToolkit.Diagnostics; +using Discord.API.Models.Enums.Users; using Discord.API.Models.Json.Messages; -using Quarrel.Client.Models.Channels; +using Discord.API.Models.Json.Settings; using Quarrel.Client.Models.Channels.Interfaces; using Quarrel.Client.Models.Guilds; using Quarrel.Client.Models.Messages; @@ -10,7 +11,6 @@ using Quarrel.Client.Models.Users; using Refit; using System; -using System.Collections; using System.Collections.Generic; using System.Threading.Tasks; @@ -26,12 +26,19 @@ public partial class QuarrelClient return CurrentUser; } + /// + /// Gets a user by id. + /// + /// The id of the user to get. public User? GetUser(ulong id) { _userMap.TryGetValue(id, out var user); return user; } + /// + /// Gets the client settings. + /// public Settings? GetSettings() { return _settings; @@ -166,12 +173,57 @@ public IPrivateChannel[] GetPrivateChannels() return privateChannels; } - public void SendMessage(ulong channelId, string content) + /// + /// Sends a message. + /// + /// The channel to send the message in. + /// The content of the message. + public async Task SendMessage(ulong channelId, string content) { Guard.IsNotNull(_channelService, nameof(_channelService)); ulong nonce = (ulong)new DateTimeOffset(DateTime.Now).ToUnixTimeMilliseconds() << 22; JsonMessageUpsert message = new JsonMessageUpsert(content, false, $"{nonce}"); - _channelService.CreateMessage(channelId, message); + await MakeRefitRequest(() => _channelService.CreateMessage(channelId, message)); + } + + /// + /// Deletes a message. + /// + /// The id of channel the message belongs to. + /// The id of the message to delete. + public async Task DeleteMessage(ulong channelId, ulong messageId) + { + Guard.IsNotNull(_channelService, nameof(_channelService)); + await MakeRefitRequest(() => _channelService.DeleteMessage(channelId, messageId)); + } + + /// + /// Updates the user's online status. + /// + /// The new online status to set. + public async Task UpdateStatus(UserStatus status) + { + Guard.IsNotNull(_gateway, nameof(_gateway)); + Guard.IsNotNull(_userService, nameof(_userService)); + + await _gateway.UpdateStatusAsync(status); + var settingsUpdate = new JsonModifyUserSettings() + { + Status = status.GetStringValue(), + }; + await _userService.UpdateSettings(settingsUpdate); + } + + private async Task MakeRefitRequest(Func request) + { + try + { + await request(); + } + catch (ApiException ex) + { + HttpExceptionHandled?.Invoke(this, ex); + } } private async Task MakeRefitRequest(Func> request) diff --git a/src/Quarrel.Client/QuarrelClient.State.cs b/src/Quarrel.Client/QuarrelClient.State.cs index 258c52610..2f557fb3c 100644 --- a/src/Quarrel.Client/QuarrelClient.State.cs +++ b/src/Quarrel.Client/QuarrelClient.State.cs @@ -318,6 +318,13 @@ internal void UpdateSettings(JsonUserSettings jsonUserSettings) { var settings = new Settings(jsonUserSettings, this); _settings = settings; + + Guard.IsNotNull(_selfUser, nameof(_selfUser)); + + _selfUser.Presence = new Presence(new JsonPresence() + { + Status = jsonUserSettings.Status, + }); } } } diff --git a/src/Quarrel.Client/QuarrelClient.cs b/src/Quarrel.Client/QuarrelClient.cs index 1bf434aeb..3fa190d19 100644 --- a/src/Quarrel.Client/QuarrelClient.cs +++ b/src/Quarrel.Client/QuarrelClient.cs @@ -20,6 +20,7 @@ namespace Quarrel.Client public partial class QuarrelClient { private IChannelService? _channelService; + private IUserService? _userService; private IGatewayService? _gatewayService; private Gateway? _gateway; private string? _token; @@ -61,6 +62,7 @@ private void InitializeServices(string token) }; _channelService = restFactory.GetChannelService(); _gatewayService = restFactory.GetGatewayService(); + _userService = restFactory.GetUserService(); } private async Task SetupGatewayAsync(string token) diff --git a/src/Quarrel.ViewModels/Bindables/Channels/Abstract/BindableChannel.cs b/src/Quarrel.ViewModels/Bindables/Channels/Abstract/BindableChannel.cs index fe9923a06..048412c6d 100644 --- a/src/Quarrel.ViewModels/Bindables/Channels/Abstract/BindableChannel.cs +++ b/src/Quarrel.ViewModels/Bindables/Channels/Abstract/BindableChannel.cs @@ -75,7 +75,10 @@ private set /// public abstract bool IsAccessible { get; } - + + /// + /// A virtual method that notifies property changed for a . + /// protected virtual void AckUpdate() { OnPropertyChanged(nameof(Channel)); @@ -93,6 +96,7 @@ private void AckUpdateRoot() /// /// Creates a new instance of a based on the type. /// + /// The to pass to the . /// The to pass to the . /// The to pass to the . /// The to pass to the . diff --git a/src/Quarrel.ViewModels/Bindables/Channels/Abstract/BindableGuildChannel.cs b/src/Quarrel.ViewModels/Bindables/Channels/Abstract/BindableGuildChannel.cs index f38946100..6e842afb0 100644 --- a/src/Quarrel.ViewModels/Bindables/Channels/Abstract/BindableGuildChannel.cs +++ b/src/Quarrel.ViewModels/Bindables/Channels/Abstract/BindableGuildChannel.cs @@ -65,6 +65,7 @@ internal BindableGuildChannel( /// /// Creates a new based on the type. /// + /// The to pass to the . /// The to pass to the . /// The to pass to the . /// The to pass to the . diff --git a/src/Quarrel.ViewModels/Bindables/Channels/Abstract/BindablePrivateChannel.cs b/src/Quarrel.ViewModels/Bindables/Channels/Abstract/BindablePrivateChannel.cs index 375ca3e32..00890bf0f 100644 --- a/src/Quarrel.ViewModels/Bindables/Channels/Abstract/BindablePrivateChannel.cs +++ b/src/Quarrel.ViewModels/Bindables/Channels/Abstract/BindablePrivateChannel.cs @@ -1,6 +1,7 @@ // Quarrel © 2022 using Microsoft.Toolkit.Mvvm.Messaging; +using Quarrel.Bindables.Abstract; using Quarrel.Bindables.Channels.Interfaces; using Quarrel.Client.Models.Channels.Abstract; using Quarrel.Client.Models.Channels.Interfaces; @@ -36,6 +37,14 @@ internal BindablePrivateChannel( /// public IMessageChannel MessageChannel => (IMessageChannel)Channel; + /// + /// Creates a new instance of a based on the type. + /// + /// The to pass to the . + /// The to pass to the . + /// The to pass to the . + /// The to pass to the . + /// The channel to wrap. public static BindablePrivateChannel? Create( IMessenger messenger, IDiscordService discordService, diff --git a/src/Quarrel.ViewModels/Bindables/Messages/BindableMessage.cs b/src/Quarrel.ViewModels/Bindables/Messages/BindableMessage.cs index f6e242fbb..db3a46ce0 100644 --- a/src/Quarrel.ViewModels/Bindables/Messages/BindableMessage.cs +++ b/src/Quarrel.ViewModels/Bindables/Messages/BindableMessage.cs @@ -1,12 +1,15 @@ // Quarrel © 2022 using Discord.API.Models.Enums.Messages; +using Microsoft.Toolkit.Mvvm.Input; using Microsoft.Toolkit.Mvvm.Messaging; using Quarrel.Bindables.Abstract; +using Quarrel.Bindables.Channels.Interfaces; using Quarrel.Bindables.Messages.Embeds; using Quarrel.Bindables.Users; using Quarrel.Client.Models.Messages; using Quarrel.Messages.Discord.Messages; +using Quarrel.Services.Clipboard; using Quarrel.Services.Discord; using Quarrel.Services.Dispatcher; using System.Collections.Generic; @@ -18,7 +21,10 @@ namespace Quarrel.Bindables.Messages /// public class BindableMessage : SelectableItem { + private readonly IClipboardService _clipboardService; + private Message _message; + private Message? _previousMessage; private bool _isDeleted; /// @@ -28,12 +34,17 @@ internal BindableMessage( IMessenger messenger, IDiscordService discordService, IDispatcherService dispatcherService, + IClipboardService clipboardService, + IBindableMessageChannel channel, Message message, Message? previousMessage = null) : base(messenger, discordService, dispatcherService) { + _clipboardService = clipboardService; + _message = message; _previousMessage = previousMessage; + Channel = channel; Users = new Dictionary(); if (message.Author is not null) @@ -61,6 +72,10 @@ internal BindableMessage( Attachments[i] = new BindableAttachment(messenger, discordService, dispatcherService, _message.Attachments[i]); } + CopyIdCommand = new RelayCommand(() => _clipboardService.Copy($"{Id}")); + CopyLinkCommand = new RelayCommand(() => _clipboardService.Copy($"{message.MessageUri}")); + DeleteCommand = new RelayCommand(() => _discordService.DeleteMessage(ChannelId, Id)); + _messenger.Register(this, (_, e) => { if (Id == e.Message.Id) @@ -81,6 +96,12 @@ internal BindableMessage( /// public ulong Id => Message.Id; + /// + public ulong ChannelId => Message.ChannelId; + + /// + /// Gets the wrapped . + /// public Message Message { get => _message; @@ -91,27 +112,43 @@ public Message Message } } - public string Content => Message.Content; - + /// + /// Gets whether or not the message has been deleted. + /// public bool IsDeleted { get => _isDeleted; - set => SetProperty(ref _isDeleted, value); + private set => SetProperty(ref _isDeleted, value); } + /// + /// Gets the that the message belongs to. + /// + public IBindableMessageChannel Channel { get; } + /// /// Gets the author of the message as a bindable user. /// public BindableUser? Author { get; } + /// + /// Gets the author of the message as a bindable guild memeber. + /// public BindableGuildMember? AuthorMember { get; } + /// + /// Gets a dictionary of bindable users relavent to + /// public Dictionary Users { get; } + /// + /// Gets the message attachments. + /// public BindableAttachment[] Attachments { get; } - private Message? _previousMessage; - + /// + /// Gets whether or not the message is a continuation. + /// public bool IsContinuation => !( _previousMessage == null || _message.Type is MessageType.ApplicationCommand or MessageType.ContextMenuCommand || @@ -126,10 +163,31 @@ _message.Type is MessageType.ApplicationCommand or MessageType.ContextMenuComman _message.WebhookId != null && _previousMessage.Author?.Username != _message.Author?.Username || _message.Timestamp.ToUnixTimeMilliseconds() - _previousMessage.Timestamp.ToUnixTimeMilliseconds() > 7 * 60 * 1000))); + /// + /// Gets whether or not the user can delete the message. + /// + // TODO: Properly handle deletable condition + public bool CanDelete => Message.IsOwn; + + /// + /// Gets a command that copies the message id to the clipboard. + /// + public RelayCommand CopyIdCommand { get; } + + /// + /// Gets a command that copies the message link to the clipboard. + /// + public RelayCommand CopyLinkCommand { get; } + + /// + /// Gets a command that deletes the message. + /// + public RelayCommand DeleteCommand { get; } + + /// protected virtual void AckUpdate() { OnPropertyChanged(nameof(Message)); - OnPropertyChanged(nameof(Content)); } } } diff --git a/src/Quarrel.ViewModels/Bindables/Messages/Embeds/BindableAttachment.cs b/src/Quarrel.ViewModels/Bindables/Messages/Embeds/BindableAttachment.cs index a2bdf3d27..c3000d077 100644 --- a/src/Quarrel.ViewModels/Bindables/Messages/Embeds/BindableAttachment.cs +++ b/src/Quarrel.ViewModels/Bindables/Messages/Embeds/BindableAttachment.cs @@ -5,11 +5,17 @@ using Quarrel.Client.Models.Messages.Embeds; using Quarrel.Services.Discord; using Quarrel.Services.Dispatcher; +using System.Text.RegularExpressions; namespace Quarrel.Bindables.Messages.Embeds { + /// + /// A wrapper of a that can be bound to the UI. + /// public class BindableAttachment : BindableItem { + private const string FileExtensionRegex = @"\.([\w]+)$"; + private Attachment _attachment; internal BindableAttachment( @@ -19,13 +25,28 @@ internal BindableAttachment( Attachment attachment) : base(messenger, discordService, dispatcherService) { - Attachment = attachment; + _attachment = attachment; } + /// + /// Gets the wrapped . + /// public Attachment Attachment { get => _attachment; - set => SetProperty(ref _attachment, value); + private set => SetProperty(ref _attachment, value); + } + + /// + /// Gets the file's extension. + /// + public string FileExtension + { + get + { + var match = Regex.Match(Attachment.Filename, FileExtensionRegex); + return match.Groups[1].Value; + } } } } diff --git a/src/Quarrel.ViewModels/Bindables/Users/BindableGuildMember.cs b/src/Quarrel.ViewModels/Bindables/Users/BindableGuildMember.cs index 8ed853291..8cfcefe5c 100644 --- a/src/Quarrel.ViewModels/Bindables/Users/BindableGuildMember.cs +++ b/src/Quarrel.ViewModels/Bindables/Users/BindableGuildMember.cs @@ -8,6 +8,9 @@ namespace Quarrel.Bindables.Users { + /// + /// A wrapper of a that can be bound to the UI. + /// public class BindableGuildMember : BindableItem { private GuildMember _guildMember; @@ -25,6 +28,9 @@ internal BindableGuildMember( _guildMember = guildMember; } + /// + /// Gets the wrapped . + /// public GuildMember GuildMember { get => _guildMember; diff --git a/src/Quarrel.ViewModels/Messages/Discord/Channels/ChannelUpdatedMessage.cs b/src/Quarrel.ViewModels/Messages/Discord/Channels/ChannelUpdatedMessage.cs index ef33060ca..da2779844 100644 --- a/src/Quarrel.ViewModels/Messages/Discord/Channels/ChannelUpdatedMessage.cs +++ b/src/Quarrel.ViewModels/Messages/Discord/Channels/ChannelUpdatedMessage.cs @@ -4,13 +4,23 @@ namespace Quarrel.Messages.Discord.Channels { + /// + /// A message sent when a channel is updated. + /// public class ChannelUpdatedMessage { + /// + /// Initializes a new instance of the class. + /// + /// The channel updated. public ChannelUpdatedMessage(Channel channel) { Channel = channel; } + /// + /// Gets the updated channel. + /// public Channel Channel { get; } } } diff --git a/src/Quarrel.ViewModels/Messages/Discord/Messages/MessageCreatedMessage.cs b/src/Quarrel.ViewModels/Messages/Discord/Messages/MessageCreatedMessage.cs index 40465a0bb..da1a94c93 100644 --- a/src/Quarrel.ViewModels/Messages/Discord/Messages/MessageCreatedMessage.cs +++ b/src/Quarrel.ViewModels/Messages/Discord/Messages/MessageCreatedMessage.cs @@ -4,13 +4,23 @@ namespace Quarrel.Messages.Discord.Messages { + /// + /// A message sent when a message is created. + /// public class MessageCreatedMessage { + /// + /// Initializes a new instance of the class. + /// + /// The message created. public MessageCreatedMessage(Message message) { Message = message; } + /// + /// Gets the message created. + /// public Message Message { get; } } } diff --git a/src/Quarrel.ViewModels/Messages/Discord/Messages/MessageDeletedMessage.cs b/src/Quarrel.ViewModels/Messages/Discord/Messages/MessageDeletedMessage.cs index 68074393a..b476b76fe 100644 --- a/src/Quarrel.ViewModels/Messages/Discord/Messages/MessageDeletedMessage.cs +++ b/src/Quarrel.ViewModels/Messages/Discord/Messages/MessageDeletedMessage.cs @@ -2,16 +2,30 @@ namespace Quarrel.Messages.Discord.Messages { + /// + /// A message sent when a message is deleted. + /// public class MessageDeletedMessage { + /// + /// Initializes a new instance of the class. + /// + /// The channel id of deleted message. + /// The id of the deleted message. public MessageDeletedMessage(ulong channelId, ulong messageId) { ChannelId = channelId; MessageId = messageId; } + /// + /// Gets the channel id of deleted message. + /// public ulong ChannelId { get; } + /// + /// Gets the id of deleted message. + /// public ulong MessageId { get; } } } diff --git a/src/Quarrel.ViewModels/Messages/Discord/Messages/MessageUpdatedMessage.cs b/src/Quarrel.ViewModels/Messages/Discord/Messages/MessageUpdatedMessage.cs index 89899cf6d..4404f768e 100644 --- a/src/Quarrel.ViewModels/Messages/Discord/Messages/MessageUpdatedMessage.cs +++ b/src/Quarrel.ViewModels/Messages/Discord/Messages/MessageUpdatedMessage.cs @@ -4,13 +4,23 @@ namespace Quarrel.Messages.Discord.Messages { + /// + /// A message sent when a message is updated. + /// public class MessageUpdatedMessage { + /// + /// Initializes a new instance of the class. + /// + /// The message updated. public MessageUpdatedMessage(Message message) { Message = message; } + /// + /// Gets the updated message. + /// public Message Message { get; } } } diff --git a/src/Quarrel.ViewModels/Quarrel.ViewModels.csproj b/src/Quarrel.ViewModels/Quarrel.ViewModels.csproj index dd9920eb9..b74dd08e3 100644 --- a/src/Quarrel.ViewModels/Quarrel.ViewModels.csproj +++ b/src/Quarrel.ViewModels/Quarrel.ViewModels.csproj @@ -21,6 +21,7 @@ + diff --git a/src/Quarrel.ViewModels/Services/Analytics/Enums/LoggedEvent.cs b/src/Quarrel.ViewModels/Services/Analytics/Enums/LoggedEvent.cs index bf4f6df1e..4ebbc6dc9 100644 --- a/src/Quarrel.ViewModels/Services/Analytics/Enums/LoggedEvent.cs +++ b/src/Quarrel.ViewModels/Services/Analytics/Enums/LoggedEvent.cs @@ -22,6 +22,12 @@ public enum LoggedEvent [StringValue("Message Sent")] MessageSent, + /// + /// The user's status was set. + /// + [StringValue("Status Set")] + StatusSet, + /// /// Opened a channel. /// diff --git a/src/Quarrel.ViewModels/Services/Clipboard/IClipboardService.cs b/src/Quarrel.ViewModels/Services/Clipboard/IClipboardService.cs new file mode 100644 index 000000000..52a031a37 --- /dev/null +++ b/src/Quarrel.ViewModels/Services/Clipboard/IClipboardService.cs @@ -0,0 +1,26 @@ +// Quarrel © 2022 + +using System; + +namespace Quarrel.Services.Clipboard +{ + /// + /// An interface for a service that handles interactions with the clipboard. + /// + public interface IClipboardService + { + /// + /// Copies text to the clipboard. + /// + /// The text to copy. + /// Whether or not to flush the clipboard for persistent when the app closes. + void Copy(string text, bool flush = true); + + /// + /// Copies text to the clipboard. + /// + /// The uri to copy. + /// Whether or not to flush the clipboard for persistent when the app closes. + void Copy(Uri uri, bool flush = true); + } +} diff --git a/src/Quarrel.ViewModels/Services/Discord/DiscordService.Events.cs b/src/Quarrel.ViewModels/Services/Discord/DiscordService.Events.cs index 02e720fe5..dcc41e9aa 100644 --- a/src/Quarrel.ViewModels/Services/Discord/DiscordService.Events.cs +++ b/src/Quarrel.ViewModels/Services/Discord/DiscordService.Events.cs @@ -10,7 +10,7 @@ namespace Quarrel.Services.Discord { public partial class DiscordService { - public void RegisterChannelEvents() + private void RegisterChannelEvents() { _quarrelClient.MessageCreated += OnMessageCreate; _quarrelClient.MessageUpdated += OnMessageUpdated; diff --git a/src/Quarrel.ViewModels/Services/Discord/DiscordService.Methods.cs b/src/Quarrel.ViewModels/Services/Discord/DiscordService.Methods.cs index ee70ec07f..bbdc65d22 100644 --- a/src/Quarrel.ViewModels/Services/Discord/DiscordService.Methods.cs +++ b/src/Quarrel.ViewModels/Services/Discord/DiscordService.Methods.cs @@ -2,11 +2,11 @@ using CommunityToolkit.Diagnostics; using Discord.API.Models.Enums.Channels; +using Discord.API.Models.Enums.Users; using Quarrel.Bindables.Channels; using Quarrel.Bindables.Channels.Abstract; using Quarrel.Bindables.Channels.Interfaces; using Quarrel.Bindables.Guilds; -using Quarrel.Bindables.Messages; using Quarrel.Bindables.Users; using Quarrel.Client.Models.Channels; using Quarrel.Client.Models.Channels.Interfaces; @@ -179,11 +179,23 @@ public async Task GetChannelMessagesAsync(IBindableMessageChannel cha } /// - public void SendMessage(ulong channelId, string content) + public async Task SendMessage(ulong channelId, string content) { _analyticsService.Log(LoggedEvent.MessageSent); - _quarrelClient.SendMessage(channelId, content); + await _quarrelClient.SendMessage(channelId, content); + } + + /// + public async Task DeleteMessage(ulong channelId, ulong messageId) + { + await _quarrelClient.DeleteMessage(channelId, messageId); + } + + /// + public async Task SetStatus(UserStatus status) + { + await _quarrelClient.UpdateStatus(status); } } } diff --git a/src/Quarrel.ViewModels/Services/Discord/IDiscordService.cs b/src/Quarrel.ViewModels/Services/Discord/IDiscordService.cs index d9412068b..1b715eb05 100644 --- a/src/Quarrel.ViewModels/Services/Discord/IDiscordService.cs +++ b/src/Quarrel.ViewModels/Services/Discord/IDiscordService.cs @@ -1,5 +1,6 @@ // Quarrel © 2022 +using Discord.API.Models.Enums.Users; using Quarrel.Bindables.Channels.Abstract; using Quarrel.Bindables.Channels.Interfaces; using Quarrel.Bindables.Guilds; @@ -58,13 +59,13 @@ public interface IDiscordService /// The if of the last message to load messages before, or null. /// An array of s from the channel. Task GetChannelMessagesAsync(IBindableMessageChannel channel, ulong? beforeId = null); - + /// /// Gets the channels in a guild. /// /// The guild to get the channels for. /// The selected channel as an . - /// An array of s from the guild. + /// An array of s from the guild. BindableGuildChannel?[] GetGuildChannels(BindableGuild guild, out IBindableSelectableChannel? selectedChannel); /// @@ -72,11 +73,29 @@ public interface IDiscordService /// /// The . /// The selected channel as an . - /// An array of s. + /// An array of s. BindablePrivateChannel?[] GetPrivateChannels(BindableHomeItem home, out IBindableSelectableChannel? selectedChannel); - BindableGuildMember GetGuildMember(ulong userId, ulong guildId); + BindableGuildMember? GetGuildMember(ulong userId, ulong guildId); + + /// + /// Sends a message. + /// + /// The id of the channel to send the message in. + /// The content of the message. + Task SendMessage(ulong channelId, string content); + + /// + /// Deletes a message. + /// + /// The id of the channel the message is in. + /// The id of the message to delete. + Task DeleteMessage(ulong channelId, ulong messageId); - void SendMessage(ulong channelId, string content); + /// + /// Updates the user's online status. + /// + /// The new online status to set. + Task SetStatus(UserStatus status); } } diff --git a/src/Quarrel.ViewModels/Services/Localization/ILocalizationService.cs b/src/Quarrel.ViewModels/Services/Localization/ILocalizationService.cs index 7af675c46..51b9d710c 100644 --- a/src/Quarrel.ViewModels/Services/Localization/ILocalizationService.cs +++ b/src/Quarrel.ViewModels/Services/Localization/ILocalizationService.cs @@ -29,6 +29,9 @@ public interface ILocalizationService /// string CommaList(params string[] args); + /// + /// Gets or sets the app's language override. + /// public string LanguageOverride { get; set; } /// diff --git a/src/Quarrel.ViewModels/ViewModels/CurrentUserViewModel.cs b/src/Quarrel.ViewModels/ViewModels/CurrentUserViewModel.cs index fafb55235..d4ffbead7 100644 --- a/src/Quarrel.ViewModels/ViewModels/CurrentUserViewModel.cs +++ b/src/Quarrel.ViewModels/ViewModels/CurrentUserViewModel.cs @@ -1,14 +1,18 @@ // Quarrel © 2022 +using Discord.API.Models.Enums.Users; using Microsoft.Toolkit.Mvvm.ComponentModel; using Microsoft.Toolkit.Mvvm.Input; using Microsoft.Toolkit.Mvvm.Messaging; using Quarrel.Bindables.Users; using Quarrel.Messages; using Quarrel.Messages.Navigation.SubPages; +using Quarrel.Services.Analytics; +using Quarrel.Services.Analytics.Enums; using Quarrel.Services.Discord; using Quarrel.Services.Dispatcher; using Quarrel.ViewModels.SubPages.Settings; +using System; namespace Quarrel.ViewModels { @@ -17,22 +21,26 @@ namespace Quarrel.ViewModels /// public partial class CurrentUserViewModel : ObservableRecipient { + private readonly IAnalyticsService _analyticsService; private readonly IMessenger _messenger; private readonly IDiscordService _discordService; private readonly IDispatcherService _dispatcherService; - [ObservableProperty] private BindableSelfUser? _me; /// /// Initializes a new instance of the class. /// - public CurrentUserViewModel(IMessenger messenger, IDiscordService discordService, IDispatcherService dispatcherService) + public CurrentUserViewModel(IAnalyticsService analyticsService, IMessenger messenger, IDiscordService discordService, IDispatcherService dispatcherService) { + _analyticsService = analyticsService; _messenger = messenger; _discordService = discordService; _dispatcherService = dispatcherService; + NavigateToSettingsCommand = new RelayCommand(NavigateToSettings); + SetStatusCommand = new RelayCommand(SetStatus); + _messenger.Register(this, (_, _) => { _dispatcherService.RunOnUIThread(() => @@ -43,12 +51,35 @@ public CurrentUserViewModel(IMessenger messenger, IDiscordService discordService } /// - /// Sends a request to open the settings subpage. + /// Gets the bindable current self user. + /// + public BindableSelfUser? Me + { + get => _me; + set => SetProperty(ref _me, value); + } + + /// + /// Gets a command that requests navigation to settings. + /// + public RelayCommand NavigateToSettingsCommand { get; } + + /// + /// Gets a command that sets the current user's status. /// - [ICommand] - public void NavigateToSettings() + public RelayCommand SetStatusCommand { get; } + + private void NavigateToSettings() { _messenger.Send(new NavigateToSubPageMessage(typeof(UserSettingsPageViewModel))); } + + private void SetStatus(UserStatus status) + { + _analyticsService.Log(LoggedEvent.StatusSet, + ("Status", status.GetStringValue())); + + _discordService.SetStatus(status); + } } } diff --git a/src/Quarrel.ViewModels/ViewModels/Panels/CommandBarViewModel.cs b/src/Quarrel.ViewModels/ViewModels/Panels/CommandBarViewModel.cs index 8e59ff9d5..805cde1fd 100644 --- a/src/Quarrel.ViewModels/ViewModels/Panels/CommandBarViewModel.cs +++ b/src/Quarrel.ViewModels/ViewModels/Panels/CommandBarViewModel.cs @@ -9,6 +9,9 @@ namespace Quarrel.ViewModels.Panels { + /// + /// The view model for the command bar. + /// public class CommandBarViewModel : ObservableRecipient { private readonly IAnalyticsService _analyticsService; @@ -17,6 +20,9 @@ public class CommandBarViewModel : ObservableRecipient private IBindableSelectableChannel? _selectedChannel; + /// + /// Initializes a new instance of the class. + /// public CommandBarViewModel(IAnalyticsService analyticsService, IMessenger messenger, IDiscordService discordService) { _analyticsService = analyticsService; @@ -26,10 +32,13 @@ public CommandBarViewModel(IAnalyticsService analyticsService, IMessenger messen _messenger.Register>(this, (_, m) => SelectedChannel = m.Channel); } + /// + /// Gets the selected channel. + /// public IBindableSelectableChannel? SelectedChannel { get => _selectedChannel; - set => SetProperty(ref _selectedChannel, value); + private set => SetProperty(ref _selectedChannel, value); } } } diff --git a/src/Quarrel.ViewModels/ViewModels/Panels/MessageBoxViewModel.cs b/src/Quarrel.ViewModels/ViewModels/Panels/MessageBoxViewModel.cs index 0fa0b2b93..12a762270 100644 --- a/src/Quarrel.ViewModels/ViewModels/Panels/MessageBoxViewModel.cs +++ b/src/Quarrel.ViewModels/ViewModels/Panels/MessageBoxViewModel.cs @@ -10,6 +10,9 @@ namespace Quarrel.ViewModels.Panels { + /// + /// The view model for the message box. + /// public class MessageBoxViewModel : ObservableRecipient { private readonly IMessenger _messenger; @@ -17,8 +20,11 @@ public class MessageBoxViewModel : ObservableRecipient private readonly IDispatcherService _dispatcherService; private ulong? _channelId; - private string _draftText; + private string? _draftText; + /// + /// Initializes a new instance of the class. + /// public MessageBoxViewModel(IMessenger messenger, IDiscordService discordService, IDispatcherService dispatcherService) { _messenger = messenger; @@ -33,25 +39,34 @@ public MessageBoxViewModel(IMessenger messenger, IDiscordService discordService, }); } + /// + /// Gets a command that sends the drafted message. + /// public RelayCommand SendMessageCommand { get; } - public string DraftText + /// + /// Gets or sets the drafted text in the message box. + /// + public string? DraftText { get => _draftText; set => SetProperty(ref _draftText, value); } + /// + /// Gets the current channel id. + /// public ulong? ChannelId { get => _channelId; - set => SetProperty(ref _channelId, value); + private set => SetProperty(ref _channelId, value); } - public void SendMessage() + private void SendMessage() { - if (ChannelId.HasValue) + if (ChannelId.HasValue && !string.IsNullOrEmpty(DraftText)) { - _discordService.SendMessage(ChannelId.Value, DraftText); + _discordService.SendMessage(ChannelId.Value, DraftText!); DraftText = string.Empty; } } diff --git a/src/Quarrel.ViewModels/ViewModels/Panels/MessagesViewModel.cs b/src/Quarrel.ViewModels/ViewModels/Panels/MessagesViewModel.cs index 0c2e582c3..a674220a2 100644 --- a/src/Quarrel.ViewModels/ViewModels/Panels/MessagesViewModel.cs +++ b/src/Quarrel.ViewModels/ViewModels/Panels/MessagesViewModel.cs @@ -8,6 +8,7 @@ using Quarrel.Client.Models.Messages; using Quarrel.Messages.Discord.Messages; using Quarrel.Messages.Navigation; +using Quarrel.Services.Clipboard; using Quarrel.Services.Discord; using Quarrel.Services.Dispatcher; using System.Collections.ObjectModel; @@ -23,6 +24,7 @@ public partial class MessagesViewModel : ObservableRecipient private readonly IMessenger _messenger; private readonly IDiscordService _discordService; private readonly IDispatcherService _dispatcherService; + private readonly IClipboardService _clipboardService; private bool _isLoading; private SemaphoreSlim _semaphore; @@ -31,11 +33,16 @@ public partial class MessagesViewModel : ObservableRecipient /// /// Initializes a new instance of the class. /// - public MessagesViewModel(IMessenger messenger, IDiscordService discordService, IDispatcherService dispatcherService) + public MessagesViewModel( + IMessenger messenger, + IDiscordService discordService, + IDispatcherService dispatcherService, + IClipboardService clipboardService) { _messenger = messenger; _discordService = discordService; _dispatcherService = dispatcherService; + _clipboardService = clipboardService; _semaphore = new SemaphoreSlim(1,1); Source = new ObservableRangeCollection(); @@ -54,6 +61,9 @@ public MessagesViewModel(IMessenger messenger, IDiscordService discordService, I /// public ObservableRangeCollection Source; + /// + /// Gets the currently selected channel. + /// public IBindableSelectableChannel? SelectedChannel { get => _selectedChannel; @@ -145,13 +155,16 @@ private void LoadChannel() private BindableMessage[] ParseMessages(Message[] messages) { + IBindableMessageChannel? channel = SelectedChannel as IBindableMessageChannel; + Guard.IsNotNull(channel, nameof(channel)); + BindableMessage[] bindableMessages = new BindableMessage[messages.Length]; if (bindableMessages.Length == 0) return bindableMessages; - bindableMessages[0] = new BindableMessage(_messenger, _discordService, _dispatcherService, messages[messages.Length - 1]); + bindableMessages[0] = new BindableMessage(_messenger, _discordService, _dispatcherService, _clipboardService, channel, messages[messages.Length - 1]); for (int i = 1; i < messages.Length; i++) { - bindableMessages[i] = new BindableMessage(_messenger, _discordService, _dispatcherService, messages[messages.Length - 1 - i], messages[messages.Length - i]); + bindableMessages[i] = new BindableMessage(_messenger, _discordService, _dispatcherService, _clipboardService, channel, messages[messages.Length - 1 - i], messages[messages.Length - i]); } return bindableMessages; } @@ -161,7 +174,9 @@ private BindableMessage[] ParseMessages(Message[] messages) /// private void AppendMessage(Message message) { - Source.Add(new BindableMessage(_messenger, _discordService, _dispatcherService, message, Source.Count > 0 ? Source[Source.Count - 1].Message : null)); + IBindableMessageChannel? channel = SelectedChannel as IBindableMessageChannel; + Guard.IsNotNull(channel, nameof(channel)); + Source.Add(new BindableMessage(_messenger, _discordService, _dispatcherService, _clipboardService, channel, message, Source.Count > 0 ? Source[Source.Count - 1].Message : null)); } } } diff --git a/src/Quarrel.ViewModels/ViewModels/SubPages/DiscordStatus/DiscordStatusViewModel.cs b/src/Quarrel.ViewModels/ViewModels/SubPages/DiscordStatus/DiscordStatusViewModel.cs index 44f768dc2..614bfe49e 100644 --- a/src/Quarrel.ViewModels/ViewModels/SubPages/DiscordStatus/DiscordStatusViewModel.cs +++ b/src/Quarrel.ViewModels/ViewModels/SubPages/DiscordStatus/DiscordStatusViewModel.cs @@ -205,7 +205,9 @@ public async void ShowMetrics(string duration) } catch (Exception ex) { - _analyticsService.Log(LoggedEvent.DiscordStatusRequestFailed, ("Endpoint", "GetMetrics"), ("Exception", ex.Message)); + _analyticsService.Log(LoggedEvent.DiscordStatusRequestFailed, + ("Endpoint", "GetMetrics"), + ("Exception", ex.Message)); } if (metrics != null && metrics.Metrics != null && metrics.Metrics.Length > 0) diff --git a/src/Quarrel.ViewModels/ViewModels/SubPages/Meta/AboutPageViewModel.cs b/src/Quarrel.ViewModels/ViewModels/SubPages/Meta/AboutPageViewModel.cs index ca3f730bc..3fe72423b 100644 --- a/src/Quarrel.ViewModels/ViewModels/SubPages/Meta/AboutPageViewModel.cs +++ b/src/Quarrel.ViewModels/ViewModels/SubPages/Meta/AboutPageViewModel.cs @@ -17,6 +17,7 @@ public partial class AboutPageViewModel : ObservableRecipient { private const string CommitResource = "About/Commit"; private const string BranchResource = "About/Branch"; + private const string VersionResource = "About/Version"; private const string QuarrelDevResource = "About/QuarrelDev"; private const string QuarrelAlphaResource = "About/QuarrelAlpha"; private const string QuarrelInsiderResource = "About/QuarrelInsider"; @@ -42,10 +43,11 @@ public AboutPageViewModel( /// /// Gets the app version as a . /// - public string AppVersion => string.Format("{0}.{1}.{2}", + public string AppVersion => + _localizationService[VersionResource, _versioningService.AppVersion.MajorVersion, _versioningService.AppVersion.MinorVersion, - _versioningService.AppVersion.BuildNumber); + _versioningService.AppVersion.BuildNumber]; /// /// Gets the app's version type. diff --git a/src/Quarrel.ViewModels/ViewModels/SubPages/UserSettings/IUserSettingsMenuItem.cs b/src/Quarrel.ViewModels/ViewModels/SubPages/UserSettings/IUserSettingsMenuItem.cs new file mode 100644 index 000000000..87351f72a --- /dev/null +++ b/src/Quarrel.ViewModels/ViewModels/SubPages/UserSettings/IUserSettingsMenuItem.cs @@ -0,0 +1,15 @@ +// Quarrel © 2022 + +namespace Quarrel.ViewModels.SubPages.UserSettings +{ + /// + /// An interface for items in the + /// + public interface IUserSettingsMenuItem + { + /// + /// Gets the title of the menu item. + /// + string Title { get; } + } +} diff --git a/src/Quarrel.ViewModels/ViewModels/SubPages/UserSettings/Pages/Abstract/UserSettingsSubPageViewModel.cs b/src/Quarrel.ViewModels/ViewModels/SubPages/UserSettings/Pages/Abstract/UserSettingsSubPageViewModel.cs index b7f364810..c91a0a105 100644 --- a/src/Quarrel.ViewModels/ViewModels/SubPages/UserSettings/Pages/Abstract/UserSettingsSubPageViewModel.cs +++ b/src/Quarrel.ViewModels/ViewModels/SubPages/UserSettings/Pages/Abstract/UserSettingsSubPageViewModel.cs @@ -6,21 +6,40 @@ namespace Quarrel.ViewModels.SubPages.UserSettings.Pages.Abstract { - public abstract class UserSettingsSubPageViewModel : ObservableObject + /// + /// A base class for user settings sub-page view models. + /// + public abstract class UserSettingsSubPageViewModel : ObservableObject, IUserSettingsMenuItem { + /// + /// The localization service. + /// protected readonly ILocalizationService _localizationService; + + /// + /// The storage service. + /// protected readonly IStorageService _storageService; - public UserSettingsSubPageViewModel(ILocalizationService localizationService, IStorageService storageService) + internal UserSettingsSubPageViewModel(ILocalizationService localizationService, IStorageService storageService) { _localizationService = localizationService; _storageService = storageService; } + /// + /// Gets the string used as a glyph for the sub page. + /// public abstract string Glyph { get; } + /// + /// Gets the title of the sub page. + /// public abstract string Title { get; } + /// + /// Gets whether or not the page is currently active. + /// public virtual bool IsActive => false; } } diff --git a/src/Quarrel.ViewModels/ViewModels/SubPages/UserSettings/Pages/BehaviorPageViewModel.cs b/src/Quarrel.ViewModels/ViewModels/SubPages/UserSettings/Pages/BehaviorPageViewModel.cs index d19c1a274..4d0977367 100644 --- a/src/Quarrel.ViewModels/ViewModels/SubPages/UserSettings/Pages/BehaviorPageViewModel.cs +++ b/src/Quarrel.ViewModels/ViewModels/SubPages/UserSettings/Pages/BehaviorPageViewModel.cs @@ -6,6 +6,9 @@ namespace Quarrel.ViewModels.SubPages.UserSettings.Pages { + /// + /// A view model for the behaviors page in settings. + /// public class BehaviorPageViewModel : UserSettingsSubPageViewModel { private const string BehaviorResource = "UserSettings/Behavior"; diff --git a/src/Quarrel.ViewModels/ViewModels/SubPages/UserSettings/Pages/ConnectionsPageViewModel.cs b/src/Quarrel.ViewModels/ViewModels/SubPages/UserSettings/Pages/ConnectionsPageViewModel.cs index 17578450e..fabd5ed22 100644 --- a/src/Quarrel.ViewModels/ViewModels/SubPages/UserSettings/Pages/ConnectionsPageViewModel.cs +++ b/src/Quarrel.ViewModels/ViewModels/SubPages/UserSettings/Pages/ConnectionsPageViewModel.cs @@ -6,6 +6,9 @@ namespace Quarrel.ViewModels.SubPages.UserSettings.Pages { + /// + /// A view model for the connections page in settings. + /// public class ConnectionsPageViewModel : UserSettingsSubPageViewModel { private const string ConnectionsResource = "UserSettings/Connections"; diff --git a/src/Quarrel.ViewModels/ViewModels/SubPages/UserSettings/Pages/DisplayPageViewModel.cs b/src/Quarrel.ViewModels/ViewModels/SubPages/UserSettings/Pages/DisplayPageViewModel.cs index ff0ffb7df..49b6f2df8 100644 --- a/src/Quarrel.ViewModels/ViewModels/SubPages/UserSettings/Pages/DisplayPageViewModel.cs +++ b/src/Quarrel.ViewModels/ViewModels/SubPages/UserSettings/Pages/DisplayPageViewModel.cs @@ -7,6 +7,9 @@ namespace Quarrel.ViewModels.SubPages.UserSettings.Pages { + /// + /// A view model for the display page in settings. + /// public class DisplayPageViewModel : UserSettingsSubPageViewModel { private const string ConnectionsResource = "UserSettings/Display"; @@ -25,12 +28,18 @@ internal DisplayPageViewModel(ILocalizationService localizationService, IStorage /// public override bool IsActive => true; + /// + /// Gets the app selected language. + /// public string SelectedLanguage { get => _localizationService.LanguageOverride; set => _localizationService.LanguageOverride = value; } + /// + /// Gets the list of languages available for the app. + /// public IReadOnlyList LanguageOptions => _localizationService.AvailableLanguages; } } diff --git a/src/Quarrel.ViewModels/ViewModels/SubPages/UserSettings/Pages/MyAccountPageViewModel.cs b/src/Quarrel.ViewModels/ViewModels/SubPages/UserSettings/Pages/MyAccountPageViewModel.cs index 71b7c8c1c..3d2ef524c 100644 --- a/src/Quarrel.ViewModels/ViewModels/SubPages/UserSettings/Pages/MyAccountPageViewModel.cs +++ b/src/Quarrel.ViewModels/ViewModels/SubPages/UserSettings/Pages/MyAccountPageViewModel.cs @@ -8,24 +8,31 @@ namespace Quarrel.ViewModels.SubPages.UserSettings.Pages { + /// + /// A view model for the account page in settings. + /// public class MyAccountPageViewModel : UserSettingsSubPageViewModel { private const string MyAccountResource = "UserSettings/MyAccount"; private readonly IDiscordService _discordService; + private bool _isLoggedIn; private string? _email; - private string _username; + private string? _username; private int _discriminator; private string? _aboutMe; internal MyAccountPageViewModel(ILocalizationService localizationService, IStorageService storageService, IDiscordService discordService) : base(localizationService, storageService) { + _isLoggedIn = false; _discordService = discordService; var user = _discordService.GetMe(); + if (user is not null) { + _isLoggedIn = true; SetBaseValues(user); } } @@ -36,26 +43,39 @@ internal MyAccountPageViewModel(ILocalizationService localizationService, IStora /// public override string Title => _localizationService[MyAccountResource]; - public override bool IsActive => true; + /// + public override bool IsActive => _isLoggedIn; + /// + /// Gets or sets the current user's email. + /// public string? Email { get => _email; set => SetProperty(ref _email, value); } - public string Username + /// + /// Gets or sets the current user's username. + /// + public string? Username { get => _username; set => SetProperty(ref _username, value); } + /// + /// Gets or sets the current user's discriminator. + /// public int Discriminator { get => _discriminator; set => SetProperty(ref _discriminator, value); } + /// + /// Gets or sets the current user's about me description. + /// public string? AboutMe { get => _aboutMe; diff --git a/src/Quarrel.ViewModels/ViewModels/SubPages/UserSettings/Pages/NotificationsPageViewModel.cs b/src/Quarrel.ViewModels/ViewModels/SubPages/UserSettings/Pages/NotificationsPageViewModel.cs index 4cb13c4e0..0425289a7 100644 --- a/src/Quarrel.ViewModels/ViewModels/SubPages/UserSettings/Pages/NotificationsPageViewModel.cs +++ b/src/Quarrel.ViewModels/ViewModels/SubPages/UserSettings/Pages/NotificationsPageViewModel.cs @@ -6,6 +6,9 @@ namespace Quarrel.ViewModels.SubPages.UserSettings.Pages { + /// + /// A view model for the notifications page in settings. + /// public class NotificationsPageViewModel : UserSettingsSubPageViewModel { private const string NotificationsResource = "UserSettings/Notifications"; diff --git a/src/Quarrel.ViewModels/ViewModels/SubPages/UserSettings/Pages/PrivacyPageViewModel.cs b/src/Quarrel.ViewModels/ViewModels/SubPages/UserSettings/Pages/PrivacyPageViewModel.cs index bc592a857..772581363 100644 --- a/src/Quarrel.ViewModels/ViewModels/SubPages/UserSettings/Pages/PrivacyPageViewModel.cs +++ b/src/Quarrel.ViewModels/ViewModels/SubPages/UserSettings/Pages/PrivacyPageViewModel.cs @@ -8,6 +8,9 @@ namespace Quarrel.ViewModels.SubPages.UserSettings.Pages { + /// + /// A view model for the privacy page in settings. + /// public class PrivacyPageViewModel : UserSettingsSubPageViewModel { private const string PrivacyResource = "UserSettings/Privacy"; @@ -19,11 +22,14 @@ internal PrivacyPageViewModel(ILocalizationService localizationService, IStorage { _discordService = discordService; } - + + /// public override string Glyph => ""; + /// public override string Title => _localizationService[PrivacyResource]; + /// public override bool IsActive => true; private ExplicitContentFilterLevel ContentFilterLevel @@ -40,9 +46,15 @@ private ExplicitContentFilterLevel ContentFilterLevel } } + /// + /// Gets or sets if the content filter level is all. + /// + /// + /// Can only be set to , clearing public and none. + /// public bool FilterAll { - get => _contentFilterLevel == ExplicitContentFilterLevel.All; + get => ContentFilterLevel == ExplicitContentFilterLevel.All; set { if (!value) return; @@ -50,9 +62,15 @@ public bool FilterAll } } + /// + /// Gets or sets if the content filter level is public. + /// + /// + /// Can only be set to , clearing all and none. + /// public bool FilterPublic { - get => _contentFilterLevel == ExplicitContentFilterLevel.Public; + get => ContentFilterLevel == ExplicitContentFilterLevel.Public; set { if (!value) return; @@ -60,9 +78,15 @@ public bool FilterPublic } } + /// + /// Gets or sets if the content filter level is none. + /// + /// + /// Can only be set to , clearing all and public. + /// public bool FilterNone { - get => _contentFilterLevel == ExplicitContentFilterLevel.None; + get => ContentFilterLevel == ExplicitContentFilterLevel.None; set { if (!value) return; diff --git a/src/Quarrel.ViewModels/ViewModels/SubPages/UserSettings/Pages/VoicePageViewModel.cs b/src/Quarrel.ViewModels/ViewModels/SubPages/UserSettings/Pages/VoicePageViewModel.cs index cc742acd7..2f0ae4abf 100644 --- a/src/Quarrel.ViewModels/ViewModels/SubPages/UserSettings/Pages/VoicePageViewModel.cs +++ b/src/Quarrel.ViewModels/ViewModels/SubPages/UserSettings/Pages/VoicePageViewModel.cs @@ -6,6 +6,9 @@ namespace Quarrel.ViewModels.SubPages.UserSettings.Pages { + /// + /// A view model for the voice page in settings. + /// public class VoicePageViewModel : UserSettingsSubPageViewModel { private const string VoiceResource = "UserSettings/Voice"; diff --git a/src/Quarrel.ViewModels/ViewModels/SubPages/UserSettings/UserSettingsHeader.cs b/src/Quarrel.ViewModels/ViewModels/SubPages/UserSettings/UserSettingsHeader.cs new file mode 100644 index 000000000..c6e9ec2f3 --- /dev/null +++ b/src/Quarrel.ViewModels/ViewModels/SubPages/UserSettings/UserSettingsHeader.cs @@ -0,0 +1,21 @@ +// Quarrel © 2022 + +using Microsoft.Toolkit.Mvvm.ComponentModel; +using Quarrel.Services.Localization; + +namespace Quarrel.ViewModels.SubPages.UserSettings +{ + /// + /// A header for a category of user settings menu items. + /// + public class UserSettingsHeader : ObservableObject, IUserSettingsMenuItem + { + internal UserSettingsHeader(ILocalizationService localizationService, string resource) + { + Title = localizationService[resource]; + } + + /// + public string Title { get; } + } +} diff --git a/src/Quarrel.ViewModels/ViewModels/SubPages/UserSettings/UserSettingsPageViewModel.cs b/src/Quarrel.ViewModels/ViewModels/SubPages/UserSettings/UserSettingsPageViewModel.cs index 9ad3b4dd2..a9f07d1d5 100644 --- a/src/Quarrel.ViewModels/ViewModels/SubPages/UserSettings/UserSettingsPageViewModel.cs +++ b/src/Quarrel.ViewModels/ViewModels/SubPages/UserSettings/UserSettingsPageViewModel.cs @@ -4,40 +4,59 @@ using Quarrel.Services.Discord; using Quarrel.Services.Localization; using Quarrel.Services.Storage; +using Quarrel.ViewModels.SubPages.UserSettings; using Quarrel.ViewModels.SubPages.UserSettings.Pages; using Quarrel.ViewModels.SubPages.UserSettings.Pages.Abstract; using System.Collections.ObjectModel; namespace Quarrel.ViewModels.SubPages.Settings { + /// + /// A view model for the user settings page. + /// public class UserSettingsPageViewModel : ObservableObject { - private readonly ILocalizationService _localizationService; + private const string AccountSettingsResource = "UserSettings/AccountSettings"; + private const string AppSettingsResource = "UserSettings/AppSettings"; - private UserSettingsSubPageViewModel _selectedSubPage; + private readonly ILocalizationService _localizationService; + private UserSettingsSubPageViewModel? _selectedSubPage; + /// + /// Initializes a new instance of the . + /// public UserSettingsPageViewModel(ILocalizationService localizationService, IStorageService storageService, IDiscordService discordService) { _localizationService = localizationService; - Pages = new ObservableCollection(); + Pages = new ObservableCollection(); - Pages.Add(new MyAccountPageViewModel(localizationService, storageService, discordService)); + // Account settings + Pages.Add(new UserSettingsHeader(localizationService, AccountSettingsResource)); + Pages.Add(new MyAccountPageViewModel(_localizationService, storageService, discordService)); Pages.Add(new PrivacyPageViewModel(_localizationService, storageService, discordService)); Pages.Add(new ConnectionsPageViewModel(_localizationService, storageService)); + // App Settings + Pages.Add(new UserSettingsHeader(localizationService, AppSettingsResource)); Pages.Add(new DisplayPageViewModel(_localizationService, storageService)); Pages.Add(new BehaviorPageViewModel(_localizationService, storageService)); Pages.Add(new NotificationsPageViewModel(_localizationService, storageService)); Pages.Add(new VoicePageViewModel(_localizationService, storageService)); } - public UserSettingsSubPageViewModel SelectedSubPage + /// + /// Gets the view model of the selected sub page. + /// + public UserSettingsSubPageViewModel? SelectedSubPage { get => _selectedSubPage; set => SetProperty(ref _selectedSubPage, value); } - public ObservableCollection Pages { get; } + /// + /// Gets the view models of all subpage options. + /// + public ObservableCollection Pages { get; } } } diff --git a/src/Quarrel/App.Services.cs b/src/Quarrel/App.Services.cs index 3faed2625..910f18a78 100644 --- a/src/Quarrel/App.Services.cs +++ b/src/Quarrel/App.Services.cs @@ -6,6 +6,7 @@ using Quarrel.Services.Analytics; using Quarrel.Services.APIs.GitHubService; using Quarrel.Services.AppConnections; +using Quarrel.Services.Clipboard; using Quarrel.Services.Discord; using Quarrel.Services.Dispatcher; using Quarrel.Services.Localization; @@ -39,6 +40,7 @@ private IServiceProvider ConfigureServices() services.AddSingleton(); services.AddSingleton(); services.AddSingleton(new StorageService(appDataFolder, JsonAsyncSerializer.Singleton)); + services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); diff --git a/src/Quarrel/Controls/Panels/Channels/CurrentUserButton.xaml b/src/Quarrel/Controls/Panels/Channels/CurrentUserButton.xaml index add0e0d10..3790202ec 100644 --- a/src/Quarrel/Controls/Panels/Channels/CurrentUserButton.xaml +++ b/src/Quarrel/Controls/Panels/Channels/CurrentUserButton.xaml @@ -5,6 +5,8 @@ xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:qc="using:Quarrel.Controls" + xmlns:bconvert="using:Quarrel.Converters.Common.Boolean" + xmlns:uenum="using:Discord.API.Models.Enums.Users" mc:Ignorable="d" d:DesignHeight="64" d:DesignWidth="224"> @@ -25,25 +27,25 @@ + ToolTipService.ToolTip="Online" GroupName="Status" Tag="Online" + IsChecked="{x:Bind bconvert:EqualityConverter.Convert(ViewModel.Me.User.Presence.Status, uenum:UserStatus.Online), Mode=OneWay}" + Command="{x:Bind ViewModel.SetStatusCommand}" CommandParameter="{x:Bind uenum:UserStatus.Online}" + Foreground="{StaticResource OnlineBrush}" Style="{StaticResource QuarrelRadioButton}"/> + ToolTipService.ToolTip="Idle" GroupName="Status" Tag="Idle" + IsChecked="{x:Bind bconvert:EqualityConverter.Convert(ViewModel.Me.User.Presence.Status, uenum:UserStatus.Idle), Mode=OneWay}" + Command="{x:Bind ViewModel.SetStatusCommand}" CommandParameter="{x:Bind uenum:UserStatus.Idle}" + Foreground="{StaticResource IdleBrush}" Style="{StaticResource QuarrelRadioButton}"/> + ToolTipService.ToolTip="Do Not Disturb" GroupName="Status" Tag="Dnd" + IsChecked="{x:Bind bconvert:EqualityConverter.Convert(ViewModel.Me.User.Presence.Status, uenum:UserStatus.DoNotDisturb), Mode=OneWay}" + Command="{x:Bind ViewModel.SetStatusCommand}" CommandParameter="{x:Bind uenum:UserStatus.DoNotDisturb}" + Foreground="{StaticResource DndBrush}" Style="{StaticResource QuarrelRadioButton}"/> + ToolTipService.ToolTip="Invisible" GroupName="Status" Tag="Invisible" + IsChecked="{x:Bind bconvert:EqualityConverter.Convert(ViewModel.Me.User.Presence.Status, uenum:UserStatus.Invisible), Mode=OneWay}" + Command="{x:Bind ViewModel.SetStatusCommand}" CommandParameter="{x:Bind uenum:UserStatus.Invisible}" + Foreground="{StaticResource OfflineBrush}" Style="{StaticResource QuarrelRadioButton}"/> diff --git a/src/Quarrel/Controls/Panels/Guilds/GuildPanel.xaml.cs b/src/Quarrel/Controls/Panels/Guilds/GuildPanel.xaml.cs index 1a96d8d27..e5d8fdcdd 100644 --- a/src/Quarrel/Controls/Panels/Guilds/GuildPanel.xaml.cs +++ b/src/Quarrel/Controls/Panels/Guilds/GuildPanel.xaml.cs @@ -19,7 +19,6 @@ public GuildPanel() private void GuildList_OnItemInvoked(TreeView sender, TreeViewItemInvokedEventArgs args) { - var container = (TreeViewItem)sender.ContainerFromItem(args.InvokedItem); if (args.InvokedItem is IBindableSelectableGuildItem guild) { ViewModel.SelectedGuild = guild; diff --git a/src/Quarrel/Converters/Common/Boolean/IsNotNullConverter.cs b/src/Quarrel/Converters/Common/Boolean/IsNotNullConverter.cs new file mode 100644 index 000000000..2c81a06fe --- /dev/null +++ b/src/Quarrel/Converters/Common/Boolean/IsNotNullConverter.cs @@ -0,0 +1,9 @@ +// Quarrel © 2022 + +namespace Quarrel.Converters.Common.Boolean +{ + public class IsNotNullConverter + { + public static bool Convert(object? item1) => item1 is not null; + } +} diff --git a/src/Quarrel/Converters/Common/Boolean/IsNullConverter.cs b/src/Quarrel/Converters/Common/Boolean/IsNullConverter.cs new file mode 100644 index 000000000..37f44e175 --- /dev/null +++ b/src/Quarrel/Converters/Common/Boolean/IsNullConverter.cs @@ -0,0 +1,9 @@ +// Quarrel © 2022 + +namespace Quarrel.Converters.Common.Boolean +{ + public class IsNullConverter + { + public static bool Convert(object? item1) => item1 is null; + } +} diff --git a/src/Quarrel/Converters/Common/Text/CaseConverter.cs b/src/Quarrel/Converters/Common/Text/CaseConverter.cs index 7e1193f2a..affbf2eb5 100644 --- a/src/Quarrel/Converters/Common/Text/CaseConverter.cs +++ b/src/Quarrel/Converters/Common/Text/CaseConverter.cs @@ -16,7 +16,6 @@ public class CaseConverter /// public CharacterCasing Case { get; set; } - /// /// Initializes a new instance of the class. /// diff --git a/src/Quarrel/Converters/Common/Time/SmartTimeFormatConverter.cs b/src/Quarrel/Converters/Common/Time/SmartDateTimeFormatConverter.cs similarity index 83% rename from src/Quarrel/Converters/Common/Time/SmartTimeFormatConverter.cs rename to src/Quarrel/Converters/Common/Time/SmartDateTimeFormatConverter.cs index 60a1ad182..39d293d0f 100644 --- a/src/Quarrel/Converters/Common/Time/SmartTimeFormatConverter.cs +++ b/src/Quarrel/Converters/Common/Time/SmartDateTimeFormatConverter.cs @@ -6,14 +6,14 @@ namespace Quarrel.Converters.Common.Time { - public class SmartTimeFormatConverter + public class SmartDateTimeFormatConverter { - public static string Convert(DateTimeOffset time) + public static string Convert(DateTimeOffset dateTime) { ILocalizationService localizationService = App.Current.Services.GetRequiredService(); string resource; DateTimeOffset now = DateTimeOffset.Now; - var timeDiff = now - time; + var timeDiff = now - dateTime; // Minutes if (timeDiff.TotalMinutes < 1) @@ -57,20 +57,20 @@ public static string Convert(DateTimeOffset time) //} // Day + Time - string shortTime = time.ToString("t"); - if (time.DayOfYear == now.DayOfYear && time.Year == now.Year) + string shortTime = dateTime.ToString("t"); + if (dateTime.DayOfYear == now.DayOfYear && dateTime.Year == now.Year) { resource = "Time/TodayAt"; return localizationService[resource, shortTime]; } DateTime yesterday = (DateTime.Today - TimeSpan.FromDays(1)).Date; - if (time.DayOfYear == yesterday.DayOfYear && time.Year == yesterday.Year) + if (dateTime.DayOfYear == yesterday.DayOfYear && dateTime.Year == yesterday.Year) { resource = "Time/YesterdayAt"; return localizationService[resource, shortTime]; } - return time.ToString("d"); + return dateTime.ToString("d"); } } } diff --git a/src/Quarrel/Converters/Common/Time/TimeFormatConverter.cs b/src/Quarrel/Converters/Common/Time/TimeFormatConverter.cs new file mode 100644 index 000000000..fc12de4ad --- /dev/null +++ b/src/Quarrel/Converters/Common/Time/TimeFormatConverter.cs @@ -0,0 +1,14 @@ +// Quarrel © 2022 + +using System; + +namespace Quarrel.Converters.Common.Time +{ + public class TimeFormatConverter + { + public static string Convert(DateTimeOffset dateTime) + { + return dateTime.ToString("t"); + } + } +} diff --git a/src/Quarrel/Converters/Discord/Messages/Attachments/AttachmentIconConverter.cs b/src/Quarrel/Converters/Discord/Messages/Attachments/AttachmentIconConverter.cs new file mode 100644 index 000000000..88b822140 --- /dev/null +++ b/src/Quarrel/Converters/Discord/Messages/Attachments/AttachmentIconConverter.cs @@ -0,0 +1,64 @@ +// Quarrel © 2022 + +namespace Quarrel.Converters.Discord.Messages.Attachments +{ + public class AttachmentIconConverter + { + public static string Convert(string filetype) + { + // TODO: Replace with dictionary from file + return filetype switch + { + // Text file icon + "txt" => "", + + // Zipped file(s) icon + "7z" or + "gz" or + "zip" => "", + + // Image icon + "png" or + "jpg" or + "jpeg" or + "bmp" or + "gif" => "", + + // Audio icon + "mp3" or + "wav" => "", + + // Video icon + "mp4" or + "mov" => "", + + // 3D icon + "obj" or + "blend" => "", + + // Console icon + "ps1" or + "batch" => "", + + // App Package icon + "appx" or + "appxbundle" or + "msix" or + "msixbundle" or + "appinstaller" => "", + + // Certificate icon + "cer" => "", + + // Code icon + "json" => "", + + // Disc icon + "iso" => "", + + // File icon + _ => "", + }; + } + } +} diff --git a/src/Quarrel/Converters/Discord/Messages/Attachments/HumanizeByteSizeConverter.cs b/src/Quarrel/Converters/Discord/Messages/Attachments/HumanizeByteSizeConverter.cs new file mode 100644 index 000000000..db502deff --- /dev/null +++ b/src/Quarrel/Converters/Discord/Messages/Attachments/HumanizeByteSizeConverter.cs @@ -0,0 +1,16 @@ +// Quarrel © 2022 + +using Humanizer; +using Humanizer.Bytes; + +namespace Quarrel.Converters.Discord.Messages.Attachments +{ + public class HumanizeByteSizeConverter + { + public static string Convert(ulong bytes) + { + var byteSize = new ByteSize(bytes); + return byteSize.Humanize("0.00"); + } + } +} diff --git a/src/Quarrel/DataTemplates/ChannelTemplates.xaml b/src/Quarrel/DataTemplates/ChannelTemplates.xaml index 317ca5314..1491c0fac 100644 --- a/src/Quarrel/DataTemplates/ChannelTemplates.xaml +++ b/src/Quarrel/DataTemplates/ChannelTemplates.xaml @@ -3,8 +3,10 @@ xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:toolkit="using:Microsoft.Toolkit.Uwp.UI.Controls" + xmlns:vconvert="using:Quarrel.Converters.Common.Visible" xmlns:cconvert="using:Quarrel.Converters.Discord.Channels" xmlns:a="using:Quarrel.Attached" + xmlns:cenums="using:Discord.API.Models.Enums.Channels" xmlns:bindablechannels="using:Quarrel.Bindables.Channels"> @@ -20,7 +22,14 @@ - + + + + diff --git a/src/Quarrel/DataTemplates/MessageTemplates.xaml b/src/Quarrel/DataTemplates/MessageTemplates.xaml index ff6398e6b..7fbdcc275 100644 --- a/src/Quarrel/DataTemplates/MessageTemplates.xaml +++ b/src/Quarrel/DataTemplates/MessageTemplates.xaml @@ -3,6 +3,7 @@ xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:tc="using:Microsoft.Toolkit.Uwp.UI.Controls" + xmlns:aconvert="using:Quarrel.Converters.Discord.Messages.Attachments" xmlns:bconvert="using:Quarrel.Converters.Common.Boolean" xmlns:tconvert="using:Quarrel.Converters.Common.Time" xmlns:mconvert="using:Quarrel.Converters.Discord.Messages" @@ -10,8 +11,54 @@ xmlns:mcontrols="using:Quarrel.Controls.Panels.Messages" xmlns:bindablemessages="using:Quarrel.Bindables.Messages" xmlns:bindableembeds="using:Quarrel.Bindables.Messages.Embeds" + xmlns:mselector="using:Quarrel.Selectors.Messages" xmlns:markdown="using:Quarrel.Markdown"> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - + + + + + + + + + + + + - + - + @@ -69,22 +132,17 @@ - - - - - - - - - + @@ -99,14 +157,14 @@ - + - diff --git a/src/Quarrel/MultilingualResources/Quarrel.he-IL.xlf b/src/Quarrel/MultilingualResources/Quarrel.he-IL.xlf index 6d38a4bb3..685872396 100644 --- a/src/Quarrel/MultilingualResources/Quarrel.he-IL.xlf +++ b/src/Quarrel/MultilingualResources/Quarrel.he-IL.xlf @@ -375,6 +375,30 @@ Failure כישלון + + Copy ID + העתק מזהה + + + Copy Link + העתק קישור + + + Delete + מחק + + + Account Settings + הגדרות חשבון + + + App Settings + הגדרות אפליקציה + + + Version: {0}.{1}.{2} + גרסת: {0}.{1}.{2} + diff --git a/src/Quarrel/Quarrel.csproj b/src/Quarrel/Quarrel.csproj index 429992f50..fec11454b 100644 --- a/src/Quarrel/Quarrel.csproj +++ b/src/Quarrel/Quarrel.csproj @@ -141,11 +141,16 @@ UserIcon.xaml + + + + + @@ -155,7 +160,7 @@ - + @@ -175,8 +180,10 @@ + + @@ -185,6 +192,7 @@ + diff --git a/src/Quarrel/Selectors/Channels/ChannelTemplateSelector.cs b/src/Quarrel/Selectors/Channels/ChannelTemplateSelector.cs index 7c3bd8d79..245cc52a7 100644 --- a/src/Quarrel/Selectors/Channels/ChannelTemplateSelector.cs +++ b/src/Quarrel/Selectors/Channels/ChannelTemplateSelector.cs @@ -56,7 +56,7 @@ public class ChannelTemplateSelector : DataTemplateSelector return null; } - protected override DataTemplate SelectTemplateCore(object item, DependencyObject container) + protected override DataTemplate? SelectTemplateCore(object item, DependencyObject container) { return SelectTemplateCore(item); } diff --git a/src/Quarrel/Selectors/Messages/AttachmentTemplateSelector.cs b/src/Quarrel/Selectors/Messages/AttachmentTemplateSelector.cs new file mode 100644 index 000000000..3a50de003 --- /dev/null +++ b/src/Quarrel/Selectors/Messages/AttachmentTemplateSelector.cs @@ -0,0 +1,37 @@ +// Quarrel © 2022 + +using Quarrel.Bindables.Messages.Embeds; +using Windows.UI.Xaml; +using Windows.UI.Xaml.Controls; + +namespace Quarrel.Selectors.Messages +{ + public class AttachmentTemplateSelector : DataTemplateSelector + { + public DataTemplate? DefaultAttachmentTemplate { get; set; } + + public DataTemplate? ImageAttachmentTemplate { get; set; } + + protected override DataTemplate SelectTemplateCore(object item, DependencyObject container) + { + if (item is BindableAttachment attachment) + { + return attachment.FileExtension switch + { + "png" or + "jpg" or + "jpeg" or + "gif" => ImageAttachmentTemplate, + + //"mp4" or + //"mov" or + //"wmv" => VideoAttachmentTemplate + + _ => DefaultAttachmentTemplate, + }; + } + + return null; + } + } +} diff --git a/src/Quarrel/Selectors/Messages/MessageTemplateSelector.cs b/src/Quarrel/Selectors/Messages/MessageTemplateSelector.cs index fdb71a58f..586ef8ea7 100644 --- a/src/Quarrel/Selectors/Messages/MessageTemplateSelector.cs +++ b/src/Quarrel/Selectors/Messages/MessageTemplateSelector.cs @@ -9,9 +9,9 @@ namespace Quarrel.Selectors.Messages { public class MessageTemplateSelector : DataTemplateSelector { - public DataTemplate DefaultTemplate { get; set; } + public DataTemplate? DefaultTemplate { get; set; } - public DataTemplate InfoTemplate { get; set; } + public DataTemplate? InfoTemplate { get; set; } protected override DataTemplate? SelectTemplateCore(object item, DependencyObject container) { diff --git a/src/Quarrel/Selectors/SubPages/UserSettings/UserSettingsMenuItemSelector.cs b/src/Quarrel/Selectors/SubPages/UserSettings/UserSettingsMenuItemSelector.cs new file mode 100644 index 000000000..dedabb4a7 --- /dev/null +++ b/src/Quarrel/Selectors/SubPages/UserSettings/UserSettingsMenuItemSelector.cs @@ -0,0 +1,25 @@ +// Quarrel © 2022 + +using Quarrel.ViewModels.SubPages.UserSettings; +using Quarrel.ViewModels.SubPages.UserSettings.Pages.Abstract; +using Windows.UI.Xaml; +using Windows.UI.Xaml.Controls; + +namespace Quarrel.Selectors.SubPages.UserSettings +{ + public class UserSettingsMenuItemSelector : DataTemplateSelector + { + public DataTemplate? HeaderItem { get; set; } + + public DataTemplate? MenuItem { get; set; } + + protected override DataTemplate SelectTemplateCore(object item) + { + return item switch + { + UserSettingsHeader => HeaderItem, + UserSettingsSubPageViewModel or _ => MenuItem, + }; + } + } +} diff --git a/src/Quarrel/Selectors/SubPages/UserSettings/UserSettingsPageSelector.cs b/src/Quarrel/Selectors/SubPages/UserSettings/UserSettingsPageSelector.cs index 25b94429e..fe7e85259 100644 --- a/src/Quarrel/Selectors/SubPages/UserSettings/UserSettingsPageSelector.cs +++ b/src/Quarrel/Selectors/SubPages/UserSettings/UserSettingsPageSelector.cs @@ -8,19 +8,19 @@ namespace Quarrel.Selectors.SubPages.UserSettings { public class UserSettingsPageSelector : DataTemplateSelector { - public DataTemplate BehaviorsTemplate { get; set; } + public DataTemplate? BehaviorsTemplate { get; set; } - public DataTemplate ConnectionsTemplate { get; set; } + public DataTemplate? ConnectionsTemplate { get; set; } - public DataTemplate DisplayTemplate { get; set; } + public DataTemplate? DisplayTemplate { get; set; } - public DataTemplate MyAccountTemplate { get; set; } + public DataTemplate? MyAccountTemplate { get; set; } - public DataTemplate NotificationsTemplate { get; set; } + public DataTemplate? NotificationsTemplate { get; set; } - public DataTemplate PrivacyTemplate { get; set; } + public DataTemplate? PrivacyTemplate { get; set; } - public DataTemplate VoiceTemplate { get; set; } + public DataTemplate? VoiceTemplate { get; set; } protected override DataTemplate? SelectTemplateCore(object item, DependencyObject container) { diff --git a/src/Quarrel/Services/Clipboard/ClipboardService.cs b/src/Quarrel/Services/Clipboard/ClipboardService.cs new file mode 100644 index 000000000..9a67e628a --- /dev/null +++ b/src/Quarrel/Services/Clipboard/ClipboardService.cs @@ -0,0 +1,32 @@ +// Quarrel © 2022 + +using System; +using Windows.ApplicationModel.DataTransfer; +using Clip = Windows.ApplicationModel.DataTransfer.Clipboard; + +namespace Quarrel.Services.Clipboard +{ + public class ClipboardService : IClipboardService + { + public void Copy(string text, bool flush = true) + { + DataPackage package = new DataPackage(); + package.SetText(text); + Copy(package, flush); + } + + public void Copy(Uri uri, bool flush = true) + { + DataPackage package = new DataPackage(); + package.SetWebLink(uri); + Copy(package, flush); + } + + public void Copy(DataPackage data, bool flush = true) + { + data.RequestedOperation = DataPackageOperation.Copy; + Clip.SetContent(data); + if (flush) Clip.Flush(); + } + } +} diff --git a/src/Quarrel/Strings/en-US/Resources.resw b/src/Quarrel/Strings/en-US/Resources.resw index 0f91adc03..60bd77a56 100644 --- a/src/Quarrel/Strings/en-US/Resources.resw +++ b/src/Quarrel/Strings/en-US/Resources.resw @@ -154,6 +154,9 @@ Quarrel Release + + Version: {0}.{1}.{2} + View code on GitHub @@ -313,6 +316,15 @@ Token + + Copy ID + + + Copy Link + + + Delete + Send message @@ -341,6 +353,12 @@ About Me + + Account Settings + + + App Settings + Behavior diff --git a/src/Quarrel/Strings/he-IL/Resources.resw b/src/Quarrel/Strings/he-IL/Resources.resw index ea6a07992..54b955b65 100644 --- a/src/Quarrel/Strings/he-IL/Resources.resw +++ b/src/Quarrel/Strings/he-IL/Resources.resw @@ -266,4 +266,22 @@ כישלון + + העתק מזהה + + + העתק קישור + + + מחק + + + הגדרות חשבון + + + הגדרות אפליקציה + + + גרסת: {0}.{1}.{2} + \ No newline at end of file diff --git a/src/Quarrel/SubPages/Meta/AboutPage.xaml b/src/Quarrel/SubPages/Meta/AboutPage.xaml index 7c4b2b39c..8aca6ec6e 100644 --- a/src/Quarrel/SubPages/Meta/AboutPage.xaml +++ b/src/Quarrel/SubPages/Meta/AboutPage.xaml @@ -85,13 +85,9 @@ - - - - + - + - + - + + + + + + + + + + + + + + + @@ -39,16 +56,8 @@ SelectedItem="{x:Bind ViewModel.SelectedSubPage, Mode=TwoWay}" OpenPaneLength="180" IsSettingsVisible="False" - MenuItemsSource="{x:Bind ViewModel.Pages, Mode=OneWay}"> - - - - - - - - - + MenuItemsSource="{x:Bind ViewModel.Pages, Mode=OneWay}" + MenuItemTemplateSelector="{StaticResource MenuItemSelector}">