diff --git a/.github/ISSUE_TEMPLATE/bug.md b/.github/ISSUE_TEMPLATE/bug.md deleted file mode 100644 index ca3d7922e..000000000 --- a/.github/ISSUE_TEMPLATE/bug.md +++ /dev/null @@ -1,80 +0,0 @@ ---- -name: Bug 报告 -about: 提交 Bug 以帮助我们提高应用体验 -title: '[Bug]' -labels: bug, 待分配 -assignees: Richasy - ---- - - - -## Bug 描述 - - - -## 复现步骤 - -- [ ] 这个问题是否能在最新版本中复现?(如果你的应用不是最新版,请在 [Release](https://github.com/Richasy/Bili.Uwp/releases) 中下载最新版) - -重现问题的步骤: - -1. 打开应用 -2. 进入 '...' -3. 点击 '....' -4. 滚动至 '....' -5. 发现问题 - -## 预期的行为 - - - -## 截图 - - - -## 环境 - - - -应用来源: - -- [ ] Microsoft Store -- [ ] Github -- [ ] 其他来源(请注明来源) - -应用版本:{应用版本} - -系统版本: - - - -- [ ] Windows 10 ver.1809 (17763) -- [ ] Windows 10 ver.1903 (18362) -- [ ] Windows 10 ver.2004 (19041) -- [ ] Windows 11 (22000) -- [ ] 内测版本 ({系统版本}) - -运行设备: - -- [ ] 桌面/台式机 -- [ ] Xbox -- [ ] ARM64 设备 -- [ ] IoT - - -Visual Studio 版本: - -- [ ] 2017 (15.{版本号}) -- [ ] 2019 (16.{版本号}) -- [ ] 2022 (17.{版本号}) - -## 日志记录 - - - -## 备注 - - diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml new file mode 100644 index 000000000..381e551be --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_report.yml @@ -0,0 +1,112 @@ +name: Bug 报告 +description: 提交 Bug 以帮助我们提高应用体验 +title: "[Bug] 我的标题" +assignees: Richasy +labels: + - "bug 🐛" +body: +- type: markdown + attributes: + value: | + ## 写在开头 + 🚨 请务必完整填写下面的内容,如果缺少必要的信息,开发者可能会在未调查的情况下直接关闭问题 🚨 + +- type: textarea + id: description + validations: + required: true + attributes: + label: Bug 描述 + description: 请简短的描述你遇到的问题 +- type: textarea + id: repro-steps + validations: + required: true + attributes: + label: 复现问题的步骤 + render: plain text + description: 请提供复现问题的步骤,如果不能,请写明原因 + placeholder: | + 干净清晰的复现步骤有助于开发者更快定位问题所在,你所遇到的问题也会获得更高的优先级. + + 示例步骤: + 1. 打开应用 + 2. 进入 '...' + 3. 点击 '....' + 4. 滚动至 '....' + 5. 发现问题 +- type: textarea + id: expected-behavior + validations: + required: true + attributes: + label: 预期行为 + description: 简要描述你希望看到什么样的结果 +- type: textarea + id: screenshots + attributes: + label: 截图 + description: 如果可以,提交截图更有助于我们分析问题 +- type: checkboxes + id: environment-package-source + attributes: + label: 应用来源 + description: 你是从哪里下载的哔哩呢? + options: + - label: Microsoft Store + - label: Github + - label: 其它 +- type: input + id: environment-package-other-source + attributes: + label: 其它来源 + description: 如果你是从其它地方下载的哔哩,请注明来源 +- type: checkboxes + id: environment-app-target-version + attributes: + label: 系统版本 + description: 选择一个你发现问题的系统版本 + options: + - label: Windows 10 1809 (Build 17763) + - label: Windows 10 1903 (Build 18362) + - label: Windows 10 1909 (Build 18363) + - label: Windows 10 2004 (Build 19041) + - label: Windows 10 20H2 (Build 19042) + - label: Windows 10 21H1 (Build 19043) + - label: Windows 11 21H2 (Build 22000) + - label: 其它 (需注明) +- type: input + id: environment-app-target-other-version + attributes: + label: 其它系统版本 + description: 如果你是在特别的系统版本中运行应用,请注明系统版本 +- type: dropdown + id: form-factor + attributes: + multiple: true + label: 运行设备 + description: 选择你当前运行哔哩的设备类型 + options: + - 桌面/台式机 + - 桌面/平板或笔记本 + - Xbox + - ARM64 设备 +- type: textarea + id: log-info + attributes: + label: 日志记录 + description: 请在应用设置中打开 日志记录 面板,打开日志文件夹,选取问题发生当日的日志记录作为 Issue 的附件上传 +- type: textarea + id: additional-context + attributes: + label: 备注 + description: 添加你认为有必要的信息 + +- type: dropdown + id: contribution + attributes: + label: 人人为我,我为人人 + description: 哔哩变得更好,我们所有参与者都将受益,如果可以的话,你是否愿意帮忙解决这个问题? + options: + - 是的,我希望我的代码出现在哔哩中 + - 我更擅长发现问题 \ No newline at end of file diff --git a/.github/ISSUE_TEMPLATE/doc.md b/.github/ISSUE_TEMPLATE/doc.md deleted file mode 100644 index 47765858c..000000000 --- a/.github/ISSUE_TEMPLATE/doc.md +++ /dev/null @@ -1,27 +0,0 @@ ---- -name: 文档库 -about: 我有关于文档的建议或问题 -title: '[Doc]' -labels: 文档, 待分配 -assignees: Richasy - ---- - - - -## 关联文档 - - - - -## 描述 - - - -## 预期的内容 - - - -## 备注 - - diff --git a/.github/ISSUE_TEMPLATE/doc_change.yml b/.github/ISSUE_TEMPLATE/doc_change.yml new file mode 100644 index 000000000..7cb182984 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/doc_change.yml @@ -0,0 +1,55 @@ +name: 文档修改 +description: 关于文档的建议或问题 +title: "[Doc] 我的标题" +labels: + - "文档 📃" +assignees: Richasy +body: +- type: markdown + attributes: + value: | + ## 写在开头 + 🚨 请务必完整填写下面的内容,如果缺少必要的信息,开发者可能会在未调查的情况下直接关闭问题 🚨 + +- type: textarea + id: problem-description + validations: + required: true + attributes: + label: 问题描述 + description: 描述当前文档存在的问题 + placeholder: | + 请描述你遇到的问题,以及为什么会对你造成困扰 + +- type: textarea + id: expect-document + validations: + required: true + attributes: + label: 预期内容 + description: 说说对于该问题, 你预期的文档内容是怎样的 + placeholder: | + 尽量清晰地描述你所预期的文档内容,这有助于开发者做出符合你预期的修改 + +- type: textarea + id: alternatives + attributes: + label: 对应文档 + description: 如果文档已存在,请添加文档链接 + placeholder: | + ![文档名称](文档链接) + +- type: textarea + id: additional-info + attributes: + label: 附加信息 + description: 添加一些你认为有必要的备注信息,或者展示一些能表现你思路的草图 + +- type: dropdown + id: contribution + attributes: + label: 人人为我,我为人人 + description: 哔哩变得更好,我们所有参与者都将受益,你是否愿意帮助完善文档? + options: + - 是的,我希望其他人看到我写的文档 + - 我还没想好 diff --git a/.github/ISSUE_TEMPLATE/feature.md b/.github/ISSUE_TEMPLATE/feature.md deleted file mode 100644 index 12b39d2e5..000000000 --- a/.github/ISSUE_TEMPLATE/feature.md +++ /dev/null @@ -1,30 +0,0 @@ ---- -name: 功能需求 -about: 给项目提供建议或点子 -title: '[Feature]' -labels: 功能, 待分配 -assignees: Richasy - ---- - - - -## 描述你想要解决的问题或者功能的使用场景 - - - -## 描述解决方案 - - - -## 描述可能的替代方案 - - - -## 备注和截图 - - diff --git a/.github/ISSUE_TEMPLATE/feature_request.yml b/.github/ISSUE_TEMPLATE/feature_request.yml new file mode 100644 index 000000000..fb711a41d --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature_request.yml @@ -0,0 +1,57 @@ +name: 功能需求 +description: 给项目提供建议或点子 +title: "[Feature] 我的标题" +labels: + - "功能 💡" +assignees: Richasy +body: +- type: markdown + attributes: + value: | + ## 写在开头 + 🚨 请务必完整填写下面的内容,如果缺少必要的信息,开发者可能会在未调查的情况下直接关闭问题 🚨 + +- type: textarea + id: problem-description + validations: + required: true + attributes: + label: 问题描述 + description: 描述你想要解决的问题或者功能的使用场景 + placeholder: | + 请描述你遇到的问题或者链接到已存在的 issue. + 从用户故事 (User Story) 开始, 简明扼要的说明该功能的适用场景。 + 说明当前应用在没有该功能情况下对你造成了哪些困扰,而有了这项功能后又能为用户创造怎样的价值 + +- type: textarea + id: solution-description + validations: + required: true + attributes: + label: 描述解决方案 + description: 说说对于该问题, 你预期的解决方案是怎样的 + placeholder: | + 尽量清晰地描述你所预期的解决方案,它会以怎样的方式解决你所遇到的问题,这有助于开发者做出符合你预期的更新 + +- type: textarea + id: alternatives + attributes: + label: 备选方案 + description: 问题的解决方法不止一种,也许你有一个退而求其次的方案 + placeholder: | + 当前一种解决方案难以实现时,你是否考虑了其他的替代方案?是否有一个最低的,可接受的实现目标? + +- type: textarea + id: additional-info + attributes: + label: 附加信息 + description: 添加一些你认为有必要的备注信息,或者展示一些能表现你思路的草图 + +- type: dropdown + id: contribution + attributes: + label: 人人为我,我为人人 + description: 哔哩变得更好,我们所有参与者都将受益,如果可以的话,你是否愿意帮忙解决这个问题? + options: + - 是的,我希望我的代码出现在哔哩中 + - 提出问题就是我对哔哩的期许 \ No newline at end of file diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index 02f747068..687ebf978 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -4,9 +4,9 @@ -## 修复 +## Close - + @@ -40,8 +40,7 @@ - [ ] 新组件 - [ ] 已经准备了关于新组件的说明文档,文档链接:[link]() - [ ] 对于控件,已将控件放在主项目的 Controls 文件夹内 -- [ ] 主要的功能修改已经添加至 [Wiki](https://github.com/Richasy/Bili.Uwp/wiki) e.g. 新的功能,管道更新,快捷键增加... -- [ ] 单元测试更新 (对于 Bug 修复及新功能添加) (如果适用) +- [ ] 主要的功能修改已经添加至 [Wiki](https://github.com/Richasy/Bili.Uwp/wiki) - [ ] 文件头已经被添加至所有源文件中 - [ ] **不**包含破坏式更新 diff --git a/.github/labeler.yml b/.github/labeler.yml deleted file mode 100644 index d145aa014..000000000 --- a/.github/labeler.yml +++ /dev/null @@ -1,25 +0,0 @@ -# Number of labels to fetch (optional). Defaults to 100 -numLabels: 100 -# These labels will not be used even if the issue contains them (optional). -# Pass a blank array if no labels are to be excluded. -# excludeLabels: [] -excludeLabels: - - 通知 📣 -# custom configuration to override default behaviour -# control explicitly what gets added and when -custom: - - location: title - keywords: - - 'Feature' - labels: - - 功能 💡 - - location: title - keywords: - - 'Bug' - labels: - - bug 🐛 - - location: title - keywords: - - 'Doc' - labels: - - 文档 📃 diff --git a/.github/release-drafter.yml b/.github/release-drafter.yml index 1b246f0b6..d443f3bea 100644 --- a/.github/release-drafter.yml +++ b/.github/release-drafter.yml @@ -11,8 +11,6 @@ categories: - title: '🐛 Bug 修复' labels: - 'bug 🐛' - - '操作与体验 🧤' - - 'bug' - title: '🧰 管理与维护' label: '维护 ⚒️' change-template: '- $TITLE @$AUTHOR (#$NUMBER)' diff --git a/.github/workflows/release-builder.yml b/.github/workflows/release-builder.yml index 50700d118..7f76ae7b6 100644 --- a/.github/workflows/release-builder.yml +++ b/.github/workflows/release-builder.yml @@ -14,7 +14,7 @@ jobs: env: SigningCertificate: App_TemporaryKey.pfx - App_Path: .\src\App\App.csproj + Solution_Path: .\Bili.Uwp.sln UWP_Project_Directory: src\App GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} @@ -46,31 +46,31 @@ jobs: $manifest.save(".\$env:UWP_Project_Directory\Package.appxmanifest") - name: Build x86 - run: msbuild $env:App_Path /p:Platform=x86 /p:AppxBundlePlatforms="x86" /p:AppxPackageDir=C:\Package\x86 /p:PackageCertificateKeyFile=$env:SigningCertificate /restore + run: msbuild $env:Solution_Path /p:Platform=x86 /p:AppxBundlePlatforms="x86" /p:AppxPackageDir=C:\Package\x86 /p:PackageCertificateKeyFile=$env:SigningCertificate /restore env: BuildMode: SideloadOnly Configuration: Release - name: Build x64 - run: msbuild $env:App_Path /p:Platform=x64 /p:AppxBundlePlatforms="x64" /p:AppxPackageDir=C:\Package\x64 /p:PackageCertificateKeyFile=$env:SigningCertificate /restore + run: msbuild $env:Solution_Path /p:Platform=x64 /p:AppxBundlePlatforms="x64" /p:AppxPackageDir=C:\Package\x64 /p:PackageCertificateKeyFile=$env:SigningCertificate /restore env: BuildMode: SideloadOnly Configuration: Release - name: Build ARM64 - run: msbuild $env:App_Path /p:Platform=ARM64 /p:AppxBundlePlatforms="ARM64" /p:AppxPackageDir=C:\Package\ARM64 /p:PackageCertificateKeyFile=$env:SigningCertificate /restore + run: msbuild $env:Solution_Path /p:Platform=ARM64 /p:AppxBundlePlatforms="ARM64" /p:AppxPackageDir=C:\Package\ARM64 /p:PackageCertificateKeyFile=$env:SigningCertificate /restore env: BuildMode: SideloadOnly Configuration: Release - name: Create x86 archive - run: Compress-Archive -Path C:\Package\x86 -DestinationPath C:\Package\Bili.Uwp_${{github.event.inputs.version}}_x86.zip + run: Compress-Archive -Path C:\Package\x86\App_${{github.event.inputs.version}}_Test -DestinationPath C:\Package\Bili.Uwp_${{github.event.inputs.version}}_x86.zip - name: Create x64 archive - run: Compress-Archive -Path C:\Package\x64 -DestinationPath C:\Package\Bili.Uwp_${{github.event.inputs.version}}_x64.zip + run: Compress-Archive -Path C:\Package\x64\App_${{github.event.inputs.version}}_Test -DestinationPath C:\Package\Bili.Uwp_${{github.event.inputs.version}}_x64.zip - name: Create ARM64 archive - run: Compress-Archive -Path C:\Package\ARM64 -DestinationPath C:\Package\Bili.Uwp_${{github.event.inputs.version}}_ARM64.zip + run: Compress-Archive -Path C:\Package\ARM64\App_${{github.event.inputs.version}}_Test -DestinationPath C:\Package\Bili.Uwp_${{github.event.inputs.version}}_ARM64.zip - name: Update x86 release asset id: upload-release-asset-x86 diff --git a/.gitignore b/.gitignore index c241d5a05..07d1768fb 100644 --- a/.gitignore +++ b/.gitignore @@ -56,7 +56,6 @@ BenchmarkDotNet.Artifacts/ project.lock.json project.fragment.lock.json artifacts/ -**/Properties/launchSettings.json # StyleCop StyleCopReport.xml @@ -173,7 +172,6 @@ publish/ *.azurePubxml # Note: Comment the next line if you want to checkin your web deploy settings, # but database connection strings (with potential passwords) will be unencrypted -*.pubxml *.publishproj # Microsoft Azure Web App publish settings. Comment the next line if you want to diff --git a/.gitmodules b/.gitmodules deleted file mode 100644 index 6a2085a99..000000000 --- a/.gitmodules +++ /dev/null @@ -1,3 +0,0 @@ -[submodule "custom"] - path = custom - url = custom_module_url diff --git a/App.ruleset b/App.ruleset index 08aab49fc..dd042dce9 100644 --- a/App.ruleset +++ b/App.ruleset @@ -1,7 +1,6 @@  - @@ -22,7 +21,6 @@ - @@ -68,17 +66,28 @@ + + + + + + + + + + + diff --git a/Bili.UWP.sln b/Bili.UWP.sln deleted file mode 100644 index 3e50ad412..000000000 --- a/Bili.UWP.sln +++ /dev/null @@ -1,402 +0,0 @@ - -Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio Version 17 -VisualStudioVersion = 17.0.31410.414 -MinimumVisualStudioVersion = 10.0.40219.1 -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "App", "src\App\App.csproj", "{1C288BC0-72F3-4C4C-90AD-A05B52E937B0}" -EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Utilities", "Utilities", "{810B7F1B-97AD-4422-93C8-562AD50CE4DB}" -EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Locator", "Locator", "{64837010-4A71-4BF6-8ADF-66A251650A11}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Locator.Uwp", "src\Utilities\Locator\Locator.Uwp\Locator.Uwp.csproj", "{793EC923-D704-4C6F-9506-EB6A32BFBB8D}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Locator.Uwp.UnitTests", "src\Utilities\Locator\Locator.Uwp.UnitTests\Locator.Uwp.UnitTests.csproj", "{FD825FC2-7745-43CF-82A9-E0717CCCC830}" -EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Toolkit", "Toolkit", "{48DF06B5-90AD-4AC6-81F7-B381DB093B09}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Toolkit.Uwp", "src\Utilities\Toolkit\Toolkit.Uwp\Toolkit.Uwp.csproj", "{07B6D3B7-0AC0-489B-BF27-588AB6226B1C}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Toolkit.Uwp.UnitTests", "src\Utilities\Toolkit\Toolkit.Uwp.UnitTests\Toolkit.Uwp.UnitTests.csproj", "{17C29B7C-8EAB-4C0C-A1B6-87EEA99FA426}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Toolkit.Interfaces", "src\Utilities\Toolkit\Toolkit.Interfaces\Toolkit.Interfaces.csproj", "{CA4FE39C-2379-4966-9940-DE738D94E982}" -EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Models", "Models", "{232C0E31-606A-48CD-B860-2E9155EAB4C6}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Models.Enums", "src\Models\Models.Enums\Models.Enums.csproj", "{88314412-B020-415B-AEAB-57ADC43B273E}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Models.BiliBili", "src\Models\Models.BiliBili\Models.BiliBili.csproj", "{8776A0BD-DBD1-4F11-A022-400D044FF618}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Models.App", "src\Models\Models.App\Models.App.csproj", "{9E61CC56-43F9-489C-AAAF-BD5EC69367C4}" -EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "ViewModels", "ViewModels", "{82E6DD11-BBC1-4BFC-A244-593EE27BF250}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ViewModels.Uwp", "src\ViewModels\ViewModels.Uwp\ViewModels.Uwp.csproj", "{1AF20FDC-36D5-450E-9461-0F78AEB89716}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ViewModels.Uwp.UnitTests", "src\ViewModels\ViewModels.Uwp.UnitTests\ViewModels.Uwp.UnitTests.csproj", "{C492CCBB-CDFD-403C-A981-1BC5EAA934D1}" -EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Lib", "Lib", "{45B5DFF9-9F13-4BF3-B561-8ABBDFE5C237}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Lib.Uwp", "src\Lib\Lib.Uwp\Lib.Uwp.csproj", "{C8AB398F-10C2-4B97-87F0-F09B922947D6}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Lib.Interfaces", "src\Lib\Lib.Interfaces\Lib.Interfaces.csproj", "{CAE9B987-6DB2-42A8-B3F2-3BB700CC4DD8}" -EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Controller", "Controller", "{65F17D44-343F-4B87-A726-3BE3E48168B4}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Controller.Uwp", "src\Controller\Controller.Uwp\Controller.Uwp.csproj", "{5213E830-43F5-491E-A8EF-D75083578EF3}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Controller.Uwp.UnitTests", "src\Controller\Controller.Uwp.UnitTests\Controller.Uwp.UnitTests.csproj", "{3B66D3B1-4B4C-4AAF-8556-B3B51999F177}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Models.gRPC", "src\Models\Models.gRPC\Models.gRPC.csproj", "{0187840F-8D1A-4198-B5BF-8B05CADB4554}" -EndProject -Global - GlobalSection(SolutionConfigurationPlatforms) = preSolution - Debug|Any CPU = Debug|Any CPU - Debug|ARM = Debug|ARM - Debug|ARM64 = Debug|ARM64 - Debug|x64 = Debug|x64 - Debug|x86 = Debug|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 - {1C288BC0-72F3-4C4C-90AD-A05B52E937B0}.Debug|Any CPU.ActiveCfg = Debug|x86 - {1C288BC0-72F3-4C4C-90AD-A05B52E937B0}.Debug|ARM.ActiveCfg = Debug|x86 - {1C288BC0-72F3-4C4C-90AD-A05B52E937B0}.Debug|ARM64.ActiveCfg = Debug|ARM64 - {1C288BC0-72F3-4C4C-90AD-A05B52E937B0}.Debug|ARM64.Build.0 = Debug|ARM64 - {1C288BC0-72F3-4C4C-90AD-A05B52E937B0}.Debug|ARM64.Deploy.0 = Debug|ARM64 - {1C288BC0-72F3-4C4C-90AD-A05B52E937B0}.Debug|x64.ActiveCfg = Debug|x64 - {1C288BC0-72F3-4C4C-90AD-A05B52E937B0}.Debug|x64.Build.0 = Debug|x64 - {1C288BC0-72F3-4C4C-90AD-A05B52E937B0}.Debug|x64.Deploy.0 = Debug|x64 - {1C288BC0-72F3-4C4C-90AD-A05B52E937B0}.Debug|x86.ActiveCfg = Debug|x86 - {1C288BC0-72F3-4C4C-90AD-A05B52E937B0}.Debug|x86.Build.0 = Debug|x86 - {1C288BC0-72F3-4C4C-90AD-A05B52E937B0}.Debug|x86.Deploy.0 = Debug|x86 - {1C288BC0-72F3-4C4C-90AD-A05B52E937B0}.Release|Any CPU.ActiveCfg = Release|x86 - {1C288BC0-72F3-4C4C-90AD-A05B52E937B0}.Release|ARM.ActiveCfg = Release|x86 - {1C288BC0-72F3-4C4C-90AD-A05B52E937B0}.Release|ARM64.ActiveCfg = Release|ARM64 - {1C288BC0-72F3-4C4C-90AD-A05B52E937B0}.Release|ARM64.Build.0 = Release|ARM64 - {1C288BC0-72F3-4C4C-90AD-A05B52E937B0}.Release|ARM64.Deploy.0 = Release|ARM64 - {1C288BC0-72F3-4C4C-90AD-A05B52E937B0}.Release|x64.ActiveCfg = Release|x64 - {1C288BC0-72F3-4C4C-90AD-A05B52E937B0}.Release|x64.Build.0 = Release|x64 - {1C288BC0-72F3-4C4C-90AD-A05B52E937B0}.Release|x64.Deploy.0 = Release|x64 - {1C288BC0-72F3-4C4C-90AD-A05B52E937B0}.Release|x86.ActiveCfg = Release|x86 - {1C288BC0-72F3-4C4C-90AD-A05B52E937B0}.Release|x86.Build.0 = Release|x86 - {1C288BC0-72F3-4C4C-90AD-A05B52E937B0}.Release|x86.Deploy.0 = Release|x86 - {793EC923-D704-4C6F-9506-EB6A32BFBB8D}.Debug|Any CPU.ActiveCfg = Debug|x86 - {793EC923-D704-4C6F-9506-EB6A32BFBB8D}.Debug|ARM.ActiveCfg = Debug|x86 - {793EC923-D704-4C6F-9506-EB6A32BFBB8D}.Debug|ARM64.ActiveCfg = Debug|ARM64 - {793EC923-D704-4C6F-9506-EB6A32BFBB8D}.Debug|ARM64.Build.0 = Debug|ARM64 - {793EC923-D704-4C6F-9506-EB6A32BFBB8D}.Debug|x64.ActiveCfg = Debug|x64 - {793EC923-D704-4C6F-9506-EB6A32BFBB8D}.Debug|x64.Build.0 = Debug|x64 - {793EC923-D704-4C6F-9506-EB6A32BFBB8D}.Debug|x86.ActiveCfg = Debug|x86 - {793EC923-D704-4C6F-9506-EB6A32BFBB8D}.Debug|x86.Build.0 = Debug|x86 - {793EC923-D704-4C6F-9506-EB6A32BFBB8D}.Release|Any CPU.ActiveCfg = Release|x86 - {793EC923-D704-4C6F-9506-EB6A32BFBB8D}.Release|ARM.ActiveCfg = Release|x86 - {793EC923-D704-4C6F-9506-EB6A32BFBB8D}.Release|ARM64.ActiveCfg = Release|ARM64 - {793EC923-D704-4C6F-9506-EB6A32BFBB8D}.Release|ARM64.Build.0 = Release|ARM64 - {793EC923-D704-4C6F-9506-EB6A32BFBB8D}.Release|x64.ActiveCfg = Release|x64 - {793EC923-D704-4C6F-9506-EB6A32BFBB8D}.Release|x64.Build.0 = Release|x64 - {793EC923-D704-4C6F-9506-EB6A32BFBB8D}.Release|x86.ActiveCfg = Release|x86 - {793EC923-D704-4C6F-9506-EB6A32BFBB8D}.Release|x86.Build.0 = Release|x86 - {FD825FC2-7745-43CF-82A9-E0717CCCC830}.Debug|Any CPU.ActiveCfg = Debug|x86 - {FD825FC2-7745-43CF-82A9-E0717CCCC830}.Debug|ARM.ActiveCfg = Debug|x86 - {FD825FC2-7745-43CF-82A9-E0717CCCC830}.Debug|ARM64.ActiveCfg = Debug|ARM64 - {FD825FC2-7745-43CF-82A9-E0717CCCC830}.Debug|ARM64.Build.0 = Debug|ARM64 - {FD825FC2-7745-43CF-82A9-E0717CCCC830}.Debug|ARM64.Deploy.0 = Debug|ARM64 - {FD825FC2-7745-43CF-82A9-E0717CCCC830}.Debug|x64.ActiveCfg = Debug|x64 - {FD825FC2-7745-43CF-82A9-E0717CCCC830}.Debug|x64.Build.0 = Debug|x64 - {FD825FC2-7745-43CF-82A9-E0717CCCC830}.Debug|x64.Deploy.0 = Debug|x64 - {FD825FC2-7745-43CF-82A9-E0717CCCC830}.Debug|x86.ActiveCfg = Debug|x86 - {FD825FC2-7745-43CF-82A9-E0717CCCC830}.Debug|x86.Build.0 = Debug|x86 - {FD825FC2-7745-43CF-82A9-E0717CCCC830}.Debug|x86.Deploy.0 = Debug|x86 - {FD825FC2-7745-43CF-82A9-E0717CCCC830}.Release|Any CPU.ActiveCfg = Release|x86 - {FD825FC2-7745-43CF-82A9-E0717CCCC830}.Release|ARM.ActiveCfg = Release|x86 - {FD825FC2-7745-43CF-82A9-E0717CCCC830}.Release|ARM64.ActiveCfg = Release|ARM64 - {FD825FC2-7745-43CF-82A9-E0717CCCC830}.Release|ARM64.Build.0 = Release|ARM64 - {FD825FC2-7745-43CF-82A9-E0717CCCC830}.Release|ARM64.Deploy.0 = Release|ARM64 - {FD825FC2-7745-43CF-82A9-E0717CCCC830}.Release|x64.ActiveCfg = Release|x64 - {FD825FC2-7745-43CF-82A9-E0717CCCC830}.Release|x64.Build.0 = Release|x64 - {FD825FC2-7745-43CF-82A9-E0717CCCC830}.Release|x64.Deploy.0 = Release|x64 - {FD825FC2-7745-43CF-82A9-E0717CCCC830}.Release|x86.ActiveCfg = Release|x86 - {FD825FC2-7745-43CF-82A9-E0717CCCC830}.Release|x86.Build.0 = Release|x86 - {FD825FC2-7745-43CF-82A9-E0717CCCC830}.Release|x86.Deploy.0 = Release|x86 - {07B6D3B7-0AC0-489B-BF27-588AB6226B1C}.Debug|Any CPU.ActiveCfg = Debug|x86 - {07B6D3B7-0AC0-489B-BF27-588AB6226B1C}.Debug|ARM.ActiveCfg = Debug|x86 - {07B6D3B7-0AC0-489B-BF27-588AB6226B1C}.Debug|ARM64.ActiveCfg = Debug|ARM64 - {07B6D3B7-0AC0-489B-BF27-588AB6226B1C}.Debug|ARM64.Build.0 = Debug|ARM64 - {07B6D3B7-0AC0-489B-BF27-588AB6226B1C}.Debug|x64.ActiveCfg = Debug|x64 - {07B6D3B7-0AC0-489B-BF27-588AB6226B1C}.Debug|x64.Build.0 = Debug|x64 - {07B6D3B7-0AC0-489B-BF27-588AB6226B1C}.Debug|x86.ActiveCfg = Debug|x86 - {07B6D3B7-0AC0-489B-BF27-588AB6226B1C}.Debug|x86.Build.0 = Debug|x86 - {07B6D3B7-0AC0-489B-BF27-588AB6226B1C}.Release|Any CPU.ActiveCfg = Release|x86 - {07B6D3B7-0AC0-489B-BF27-588AB6226B1C}.Release|ARM.ActiveCfg = Release|x86 - {07B6D3B7-0AC0-489B-BF27-588AB6226B1C}.Release|ARM64.ActiveCfg = Release|ARM64 - {07B6D3B7-0AC0-489B-BF27-588AB6226B1C}.Release|ARM64.Build.0 = Release|ARM64 - {07B6D3B7-0AC0-489B-BF27-588AB6226B1C}.Release|x64.ActiveCfg = Release|x64 - {07B6D3B7-0AC0-489B-BF27-588AB6226B1C}.Release|x64.Build.0 = Release|x64 - {07B6D3B7-0AC0-489B-BF27-588AB6226B1C}.Release|x86.ActiveCfg = Release|x86 - {07B6D3B7-0AC0-489B-BF27-588AB6226B1C}.Release|x86.Build.0 = Release|x86 - {17C29B7C-8EAB-4C0C-A1B6-87EEA99FA426}.Debug|Any CPU.ActiveCfg = Debug|x86 - {17C29B7C-8EAB-4C0C-A1B6-87EEA99FA426}.Debug|ARM.ActiveCfg = Debug|x86 - {17C29B7C-8EAB-4C0C-A1B6-87EEA99FA426}.Debug|ARM64.ActiveCfg = Debug|ARM64 - {17C29B7C-8EAB-4C0C-A1B6-87EEA99FA426}.Debug|ARM64.Build.0 = Debug|ARM64 - {17C29B7C-8EAB-4C0C-A1B6-87EEA99FA426}.Debug|ARM64.Deploy.0 = Debug|ARM64 - {17C29B7C-8EAB-4C0C-A1B6-87EEA99FA426}.Debug|x64.ActiveCfg = Debug|x64 - {17C29B7C-8EAB-4C0C-A1B6-87EEA99FA426}.Debug|x64.Build.0 = Debug|x64 - {17C29B7C-8EAB-4C0C-A1B6-87EEA99FA426}.Debug|x64.Deploy.0 = Debug|x64 - {17C29B7C-8EAB-4C0C-A1B6-87EEA99FA426}.Debug|x86.ActiveCfg = Debug|x86 - {17C29B7C-8EAB-4C0C-A1B6-87EEA99FA426}.Debug|x86.Build.0 = Debug|x86 - {17C29B7C-8EAB-4C0C-A1B6-87EEA99FA426}.Debug|x86.Deploy.0 = Debug|x86 - {17C29B7C-8EAB-4C0C-A1B6-87EEA99FA426}.Release|Any CPU.ActiveCfg = Release|x86 - {17C29B7C-8EAB-4C0C-A1B6-87EEA99FA426}.Release|ARM.ActiveCfg = Release|x86 - {17C29B7C-8EAB-4C0C-A1B6-87EEA99FA426}.Release|ARM64.ActiveCfg = Release|ARM64 - {17C29B7C-8EAB-4C0C-A1B6-87EEA99FA426}.Release|ARM64.Build.0 = Release|ARM64 - {17C29B7C-8EAB-4C0C-A1B6-87EEA99FA426}.Release|ARM64.Deploy.0 = Release|ARM64 - {17C29B7C-8EAB-4C0C-A1B6-87EEA99FA426}.Release|x64.ActiveCfg = Release|x64 - {17C29B7C-8EAB-4C0C-A1B6-87EEA99FA426}.Release|x64.Build.0 = Release|x64 - {17C29B7C-8EAB-4C0C-A1B6-87EEA99FA426}.Release|x64.Deploy.0 = Release|x64 - {17C29B7C-8EAB-4C0C-A1B6-87EEA99FA426}.Release|x86.ActiveCfg = Release|x86 - {17C29B7C-8EAB-4C0C-A1B6-87EEA99FA426}.Release|x86.Build.0 = Release|x86 - {17C29B7C-8EAB-4C0C-A1B6-87EEA99FA426}.Release|x86.Deploy.0 = Release|x86 - {CA4FE39C-2379-4966-9940-DE738D94E982}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {CA4FE39C-2379-4966-9940-DE738D94E982}.Debug|Any CPU.Build.0 = Debug|Any CPU - {CA4FE39C-2379-4966-9940-DE738D94E982}.Debug|ARM.ActiveCfg = Debug|Any CPU - {CA4FE39C-2379-4966-9940-DE738D94E982}.Debug|ARM.Build.0 = Debug|Any CPU - {CA4FE39C-2379-4966-9940-DE738D94E982}.Debug|ARM64.ActiveCfg = Debug|Any CPU - {CA4FE39C-2379-4966-9940-DE738D94E982}.Debug|ARM64.Build.0 = Debug|Any CPU - {CA4FE39C-2379-4966-9940-DE738D94E982}.Debug|x64.ActiveCfg = Debug|Any CPU - {CA4FE39C-2379-4966-9940-DE738D94E982}.Debug|x64.Build.0 = Debug|Any CPU - {CA4FE39C-2379-4966-9940-DE738D94E982}.Debug|x86.ActiveCfg = Debug|Any CPU - {CA4FE39C-2379-4966-9940-DE738D94E982}.Debug|x86.Build.0 = Debug|Any CPU - {CA4FE39C-2379-4966-9940-DE738D94E982}.Release|Any CPU.ActiveCfg = Release|Any CPU - {CA4FE39C-2379-4966-9940-DE738D94E982}.Release|Any CPU.Build.0 = Release|Any CPU - {CA4FE39C-2379-4966-9940-DE738D94E982}.Release|ARM.ActiveCfg = Release|Any CPU - {CA4FE39C-2379-4966-9940-DE738D94E982}.Release|ARM.Build.0 = Release|Any CPU - {CA4FE39C-2379-4966-9940-DE738D94E982}.Release|ARM64.ActiveCfg = Release|Any CPU - {CA4FE39C-2379-4966-9940-DE738D94E982}.Release|ARM64.Build.0 = Release|Any CPU - {CA4FE39C-2379-4966-9940-DE738D94E982}.Release|x64.ActiveCfg = Release|Any CPU - {CA4FE39C-2379-4966-9940-DE738D94E982}.Release|x64.Build.0 = Release|Any CPU - {CA4FE39C-2379-4966-9940-DE738D94E982}.Release|x86.ActiveCfg = Release|Any CPU - {CA4FE39C-2379-4966-9940-DE738D94E982}.Release|x86.Build.0 = Release|Any CPU - {88314412-B020-415B-AEAB-57ADC43B273E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {88314412-B020-415B-AEAB-57ADC43B273E}.Debug|Any CPU.Build.0 = Debug|Any CPU - {88314412-B020-415B-AEAB-57ADC43B273E}.Debug|ARM.ActiveCfg = Debug|Any CPU - {88314412-B020-415B-AEAB-57ADC43B273E}.Debug|ARM.Build.0 = Debug|Any CPU - {88314412-B020-415B-AEAB-57ADC43B273E}.Debug|ARM64.ActiveCfg = Debug|Any CPU - {88314412-B020-415B-AEAB-57ADC43B273E}.Debug|ARM64.Build.0 = Debug|Any CPU - {88314412-B020-415B-AEAB-57ADC43B273E}.Debug|x64.ActiveCfg = Debug|Any CPU - {88314412-B020-415B-AEAB-57ADC43B273E}.Debug|x64.Build.0 = Debug|Any CPU - {88314412-B020-415B-AEAB-57ADC43B273E}.Debug|x86.ActiveCfg = Debug|Any CPU - {88314412-B020-415B-AEAB-57ADC43B273E}.Debug|x86.Build.0 = Debug|Any CPU - {88314412-B020-415B-AEAB-57ADC43B273E}.Release|Any CPU.ActiveCfg = Release|Any CPU - {88314412-B020-415B-AEAB-57ADC43B273E}.Release|Any CPU.Build.0 = Release|Any CPU - {88314412-B020-415B-AEAB-57ADC43B273E}.Release|ARM.ActiveCfg = Release|Any CPU - {88314412-B020-415B-AEAB-57ADC43B273E}.Release|ARM.Build.0 = Release|Any CPU - {88314412-B020-415B-AEAB-57ADC43B273E}.Release|ARM64.ActiveCfg = Release|Any CPU - {88314412-B020-415B-AEAB-57ADC43B273E}.Release|ARM64.Build.0 = Release|Any CPU - {88314412-B020-415B-AEAB-57ADC43B273E}.Release|x64.ActiveCfg = Release|Any CPU - {88314412-B020-415B-AEAB-57ADC43B273E}.Release|x64.Build.0 = Release|Any CPU - {88314412-B020-415B-AEAB-57ADC43B273E}.Release|x86.ActiveCfg = Release|Any CPU - {88314412-B020-415B-AEAB-57ADC43B273E}.Release|x86.Build.0 = Release|Any CPU - {8776A0BD-DBD1-4F11-A022-400D044FF618}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {8776A0BD-DBD1-4F11-A022-400D044FF618}.Debug|Any CPU.Build.0 = Debug|Any CPU - {8776A0BD-DBD1-4F11-A022-400D044FF618}.Debug|ARM.ActiveCfg = Debug|Any CPU - {8776A0BD-DBD1-4F11-A022-400D044FF618}.Debug|ARM.Build.0 = Debug|Any CPU - {8776A0BD-DBD1-4F11-A022-400D044FF618}.Debug|ARM64.ActiveCfg = Debug|Any CPU - {8776A0BD-DBD1-4F11-A022-400D044FF618}.Debug|ARM64.Build.0 = Debug|Any CPU - {8776A0BD-DBD1-4F11-A022-400D044FF618}.Debug|x64.ActiveCfg = Debug|Any CPU - {8776A0BD-DBD1-4F11-A022-400D044FF618}.Debug|x64.Build.0 = Debug|Any CPU - {8776A0BD-DBD1-4F11-A022-400D044FF618}.Debug|x86.ActiveCfg = Debug|Any CPU - {8776A0BD-DBD1-4F11-A022-400D044FF618}.Debug|x86.Build.0 = Debug|Any CPU - {8776A0BD-DBD1-4F11-A022-400D044FF618}.Release|Any CPU.ActiveCfg = Release|Any CPU - {8776A0BD-DBD1-4F11-A022-400D044FF618}.Release|Any CPU.Build.0 = Release|Any CPU - {8776A0BD-DBD1-4F11-A022-400D044FF618}.Release|ARM.ActiveCfg = Release|Any CPU - {8776A0BD-DBD1-4F11-A022-400D044FF618}.Release|ARM.Build.0 = Release|Any CPU - {8776A0BD-DBD1-4F11-A022-400D044FF618}.Release|ARM64.ActiveCfg = Release|Any CPU - {8776A0BD-DBD1-4F11-A022-400D044FF618}.Release|ARM64.Build.0 = Release|Any CPU - {8776A0BD-DBD1-4F11-A022-400D044FF618}.Release|x64.ActiveCfg = Release|Any CPU - {8776A0BD-DBD1-4F11-A022-400D044FF618}.Release|x64.Build.0 = Release|Any CPU - {8776A0BD-DBD1-4F11-A022-400D044FF618}.Release|x86.ActiveCfg = Release|Any CPU - {8776A0BD-DBD1-4F11-A022-400D044FF618}.Release|x86.Build.0 = Release|Any CPU - {9E61CC56-43F9-489C-AAAF-BD5EC69367C4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {9E61CC56-43F9-489C-AAAF-BD5EC69367C4}.Debug|Any CPU.Build.0 = Debug|Any CPU - {9E61CC56-43F9-489C-AAAF-BD5EC69367C4}.Debug|ARM.ActiveCfg = Debug|Any CPU - {9E61CC56-43F9-489C-AAAF-BD5EC69367C4}.Debug|ARM.Build.0 = Debug|Any CPU - {9E61CC56-43F9-489C-AAAF-BD5EC69367C4}.Debug|ARM64.ActiveCfg = Debug|Any CPU - {9E61CC56-43F9-489C-AAAF-BD5EC69367C4}.Debug|ARM64.Build.0 = Debug|Any CPU - {9E61CC56-43F9-489C-AAAF-BD5EC69367C4}.Debug|x64.ActiveCfg = Debug|Any CPU - {9E61CC56-43F9-489C-AAAF-BD5EC69367C4}.Debug|x64.Build.0 = Debug|Any CPU - {9E61CC56-43F9-489C-AAAF-BD5EC69367C4}.Debug|x86.ActiveCfg = Debug|Any CPU - {9E61CC56-43F9-489C-AAAF-BD5EC69367C4}.Debug|x86.Build.0 = Debug|Any CPU - {9E61CC56-43F9-489C-AAAF-BD5EC69367C4}.Release|Any CPU.ActiveCfg = Release|Any CPU - {9E61CC56-43F9-489C-AAAF-BD5EC69367C4}.Release|Any CPU.Build.0 = Release|Any CPU - {9E61CC56-43F9-489C-AAAF-BD5EC69367C4}.Release|ARM.ActiveCfg = Release|Any CPU - {9E61CC56-43F9-489C-AAAF-BD5EC69367C4}.Release|ARM.Build.0 = Release|Any CPU - {9E61CC56-43F9-489C-AAAF-BD5EC69367C4}.Release|ARM64.ActiveCfg = Release|Any CPU - {9E61CC56-43F9-489C-AAAF-BD5EC69367C4}.Release|ARM64.Build.0 = Release|Any CPU - {9E61CC56-43F9-489C-AAAF-BD5EC69367C4}.Release|x64.ActiveCfg = Release|Any CPU - {9E61CC56-43F9-489C-AAAF-BD5EC69367C4}.Release|x64.Build.0 = Release|Any CPU - {9E61CC56-43F9-489C-AAAF-BD5EC69367C4}.Release|x86.ActiveCfg = Release|Any CPU - {9E61CC56-43F9-489C-AAAF-BD5EC69367C4}.Release|x86.Build.0 = Release|Any CPU - {1AF20FDC-36D5-450E-9461-0F78AEB89716}.Debug|Any CPU.ActiveCfg = Debug|x86 - {1AF20FDC-36D5-450E-9461-0F78AEB89716}.Debug|ARM.ActiveCfg = Debug|x86 - {1AF20FDC-36D5-450E-9461-0F78AEB89716}.Debug|ARM64.ActiveCfg = Debug|ARM64 - {1AF20FDC-36D5-450E-9461-0F78AEB89716}.Debug|ARM64.Build.0 = Debug|ARM64 - {1AF20FDC-36D5-450E-9461-0F78AEB89716}.Debug|x64.ActiveCfg = Debug|x64 - {1AF20FDC-36D5-450E-9461-0F78AEB89716}.Debug|x64.Build.0 = Debug|x64 - {1AF20FDC-36D5-450E-9461-0F78AEB89716}.Debug|x86.ActiveCfg = Debug|x86 - {1AF20FDC-36D5-450E-9461-0F78AEB89716}.Debug|x86.Build.0 = Debug|x86 - {1AF20FDC-36D5-450E-9461-0F78AEB89716}.Release|Any CPU.ActiveCfg = Release|x86 - {1AF20FDC-36D5-450E-9461-0F78AEB89716}.Release|ARM.ActiveCfg = Release|x86 - {1AF20FDC-36D5-450E-9461-0F78AEB89716}.Release|ARM64.ActiveCfg = Release|ARM64 - {1AF20FDC-36D5-450E-9461-0F78AEB89716}.Release|ARM64.Build.0 = Release|ARM64 - {1AF20FDC-36D5-450E-9461-0F78AEB89716}.Release|x64.ActiveCfg = Release|x64 - {1AF20FDC-36D5-450E-9461-0F78AEB89716}.Release|x64.Build.0 = Release|x64 - {1AF20FDC-36D5-450E-9461-0F78AEB89716}.Release|x86.ActiveCfg = Release|x86 - {1AF20FDC-36D5-450E-9461-0F78AEB89716}.Release|x86.Build.0 = Release|x86 - {C492CCBB-CDFD-403C-A981-1BC5EAA934D1}.Debug|Any CPU.ActiveCfg = Debug|x86 - {C492CCBB-CDFD-403C-A981-1BC5EAA934D1}.Debug|ARM.ActiveCfg = Debug|x86 - {C492CCBB-CDFD-403C-A981-1BC5EAA934D1}.Debug|ARM64.ActiveCfg = Debug|ARM64 - {C492CCBB-CDFD-403C-A981-1BC5EAA934D1}.Debug|ARM64.Build.0 = Debug|ARM64 - {C492CCBB-CDFD-403C-A981-1BC5EAA934D1}.Debug|ARM64.Deploy.0 = Debug|ARM64 - {C492CCBB-CDFD-403C-A981-1BC5EAA934D1}.Debug|x64.ActiveCfg = Debug|x64 - {C492CCBB-CDFD-403C-A981-1BC5EAA934D1}.Debug|x64.Build.0 = Debug|x64 - {C492CCBB-CDFD-403C-A981-1BC5EAA934D1}.Debug|x64.Deploy.0 = Debug|x64 - {C492CCBB-CDFD-403C-A981-1BC5EAA934D1}.Debug|x86.ActiveCfg = Debug|x86 - {C492CCBB-CDFD-403C-A981-1BC5EAA934D1}.Debug|x86.Build.0 = Debug|x86 - {C492CCBB-CDFD-403C-A981-1BC5EAA934D1}.Debug|x86.Deploy.0 = Debug|x86 - {C492CCBB-CDFD-403C-A981-1BC5EAA934D1}.Release|Any CPU.ActiveCfg = Release|x86 - {C492CCBB-CDFD-403C-A981-1BC5EAA934D1}.Release|ARM.ActiveCfg = Release|x86 - {C492CCBB-CDFD-403C-A981-1BC5EAA934D1}.Release|ARM64.ActiveCfg = Release|ARM64 - {C492CCBB-CDFD-403C-A981-1BC5EAA934D1}.Release|ARM64.Build.0 = Release|ARM64 - {C492CCBB-CDFD-403C-A981-1BC5EAA934D1}.Release|ARM64.Deploy.0 = Release|ARM64 - {C492CCBB-CDFD-403C-A981-1BC5EAA934D1}.Release|x64.ActiveCfg = Release|x64 - {C492CCBB-CDFD-403C-A981-1BC5EAA934D1}.Release|x64.Build.0 = Release|x64 - {C492CCBB-CDFD-403C-A981-1BC5EAA934D1}.Release|x64.Deploy.0 = Release|x64 - {C492CCBB-CDFD-403C-A981-1BC5EAA934D1}.Release|x86.ActiveCfg = Release|x86 - {C492CCBB-CDFD-403C-A981-1BC5EAA934D1}.Release|x86.Build.0 = Release|x86 - {C492CCBB-CDFD-403C-A981-1BC5EAA934D1}.Release|x86.Deploy.0 = Release|x86 - {C8AB398F-10C2-4B97-87F0-F09B922947D6}.Debug|Any CPU.ActiveCfg = Debug|x86 - {C8AB398F-10C2-4B97-87F0-F09B922947D6}.Debug|ARM.ActiveCfg = Debug|x86 - {C8AB398F-10C2-4B97-87F0-F09B922947D6}.Debug|ARM64.ActiveCfg = Debug|ARM64 - {C8AB398F-10C2-4B97-87F0-F09B922947D6}.Debug|ARM64.Build.0 = Debug|ARM64 - {C8AB398F-10C2-4B97-87F0-F09B922947D6}.Debug|x64.ActiveCfg = Debug|x64 - {C8AB398F-10C2-4B97-87F0-F09B922947D6}.Debug|x64.Build.0 = Debug|x64 - {C8AB398F-10C2-4B97-87F0-F09B922947D6}.Debug|x86.ActiveCfg = Debug|x86 - {C8AB398F-10C2-4B97-87F0-F09B922947D6}.Debug|x86.Build.0 = Debug|x86 - {C8AB398F-10C2-4B97-87F0-F09B922947D6}.Release|Any CPU.ActiveCfg = Release|x86 - {C8AB398F-10C2-4B97-87F0-F09B922947D6}.Release|ARM.ActiveCfg = Release|x86 - {C8AB398F-10C2-4B97-87F0-F09B922947D6}.Release|ARM64.ActiveCfg = Release|ARM64 - {C8AB398F-10C2-4B97-87F0-F09B922947D6}.Release|ARM64.Build.0 = Release|ARM64 - {C8AB398F-10C2-4B97-87F0-F09B922947D6}.Release|x64.ActiveCfg = Release|x64 - {C8AB398F-10C2-4B97-87F0-F09B922947D6}.Release|x64.Build.0 = Release|x64 - {C8AB398F-10C2-4B97-87F0-F09B922947D6}.Release|x86.ActiveCfg = Release|x86 - {C8AB398F-10C2-4B97-87F0-F09B922947D6}.Release|x86.Build.0 = Release|x86 - {CAE9B987-6DB2-42A8-B3F2-3BB700CC4DD8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {CAE9B987-6DB2-42A8-B3F2-3BB700CC4DD8}.Debug|Any CPU.Build.0 = Debug|Any CPU - {CAE9B987-6DB2-42A8-B3F2-3BB700CC4DD8}.Debug|ARM.ActiveCfg = Debug|ARM - {CAE9B987-6DB2-42A8-B3F2-3BB700CC4DD8}.Debug|ARM.Build.0 = Debug|ARM - {CAE9B987-6DB2-42A8-B3F2-3BB700CC4DD8}.Debug|ARM64.ActiveCfg = Debug|ARM64 - {CAE9B987-6DB2-42A8-B3F2-3BB700CC4DD8}.Debug|ARM64.Build.0 = Debug|ARM64 - {CAE9B987-6DB2-42A8-B3F2-3BB700CC4DD8}.Debug|x64.ActiveCfg = Debug|x64 - {CAE9B987-6DB2-42A8-B3F2-3BB700CC4DD8}.Debug|x64.Build.0 = Debug|x64 - {CAE9B987-6DB2-42A8-B3F2-3BB700CC4DD8}.Debug|x86.ActiveCfg = Debug|x86 - {CAE9B987-6DB2-42A8-B3F2-3BB700CC4DD8}.Debug|x86.Build.0 = Debug|x86 - {CAE9B987-6DB2-42A8-B3F2-3BB700CC4DD8}.Release|Any CPU.ActiveCfg = Release|Any CPU - {CAE9B987-6DB2-42A8-B3F2-3BB700CC4DD8}.Release|Any CPU.Build.0 = Release|Any CPU - {CAE9B987-6DB2-42A8-B3F2-3BB700CC4DD8}.Release|ARM.ActiveCfg = Release|ARM - {CAE9B987-6DB2-42A8-B3F2-3BB700CC4DD8}.Release|ARM.Build.0 = Release|ARM - {CAE9B987-6DB2-42A8-B3F2-3BB700CC4DD8}.Release|ARM64.ActiveCfg = Release|ARM64 - {CAE9B987-6DB2-42A8-B3F2-3BB700CC4DD8}.Release|ARM64.Build.0 = Release|ARM64 - {CAE9B987-6DB2-42A8-B3F2-3BB700CC4DD8}.Release|x64.ActiveCfg = Release|x64 - {CAE9B987-6DB2-42A8-B3F2-3BB700CC4DD8}.Release|x64.Build.0 = Release|x64 - {CAE9B987-6DB2-42A8-B3F2-3BB700CC4DD8}.Release|x86.ActiveCfg = Release|x86 - {CAE9B987-6DB2-42A8-B3F2-3BB700CC4DD8}.Release|x86.Build.0 = Release|x86 - {5213E830-43F5-491E-A8EF-D75083578EF3}.Debug|Any CPU.ActiveCfg = Debug|x86 - {5213E830-43F5-491E-A8EF-D75083578EF3}.Debug|ARM.ActiveCfg = Debug|x86 - {5213E830-43F5-491E-A8EF-D75083578EF3}.Debug|ARM64.ActiveCfg = Debug|ARM64 - {5213E830-43F5-491E-A8EF-D75083578EF3}.Debug|ARM64.Build.0 = Debug|ARM64 - {5213E830-43F5-491E-A8EF-D75083578EF3}.Debug|x64.ActiveCfg = Debug|x64 - {5213E830-43F5-491E-A8EF-D75083578EF3}.Debug|x64.Build.0 = Debug|x64 - {5213E830-43F5-491E-A8EF-D75083578EF3}.Debug|x86.ActiveCfg = Debug|x86 - {5213E830-43F5-491E-A8EF-D75083578EF3}.Debug|x86.Build.0 = Debug|x86 - {5213E830-43F5-491E-A8EF-D75083578EF3}.Release|Any CPU.ActiveCfg = Release|x86 - {5213E830-43F5-491E-A8EF-D75083578EF3}.Release|ARM.ActiveCfg = Release|x86 - {5213E830-43F5-491E-A8EF-D75083578EF3}.Release|ARM64.ActiveCfg = Release|ARM64 - {5213E830-43F5-491E-A8EF-D75083578EF3}.Release|ARM64.Build.0 = Release|ARM64 - {5213E830-43F5-491E-A8EF-D75083578EF3}.Release|x64.ActiveCfg = Release|x64 - {5213E830-43F5-491E-A8EF-D75083578EF3}.Release|x64.Build.0 = Release|x64 - {5213E830-43F5-491E-A8EF-D75083578EF3}.Release|x86.ActiveCfg = Release|x86 - {5213E830-43F5-491E-A8EF-D75083578EF3}.Release|x86.Build.0 = Release|x86 - {3B66D3B1-4B4C-4AAF-8556-B3B51999F177}.Debug|Any CPU.ActiveCfg = Debug|x86 - {3B66D3B1-4B4C-4AAF-8556-B3B51999F177}.Debug|ARM.ActiveCfg = Debug|x86 - {3B66D3B1-4B4C-4AAF-8556-B3B51999F177}.Debug|ARM64.ActiveCfg = Debug|ARM64 - {3B66D3B1-4B4C-4AAF-8556-B3B51999F177}.Debug|ARM64.Build.0 = Debug|ARM64 - {3B66D3B1-4B4C-4AAF-8556-B3B51999F177}.Debug|ARM64.Deploy.0 = Debug|ARM64 - {3B66D3B1-4B4C-4AAF-8556-B3B51999F177}.Debug|x64.ActiveCfg = Debug|x64 - {3B66D3B1-4B4C-4AAF-8556-B3B51999F177}.Debug|x64.Build.0 = Debug|x64 - {3B66D3B1-4B4C-4AAF-8556-B3B51999F177}.Debug|x64.Deploy.0 = Debug|x64 - {3B66D3B1-4B4C-4AAF-8556-B3B51999F177}.Debug|x86.ActiveCfg = Debug|x86 - {3B66D3B1-4B4C-4AAF-8556-B3B51999F177}.Debug|x86.Build.0 = Debug|x86 - {3B66D3B1-4B4C-4AAF-8556-B3B51999F177}.Debug|x86.Deploy.0 = Debug|x86 - {3B66D3B1-4B4C-4AAF-8556-B3B51999F177}.Release|Any CPU.ActiveCfg = Release|x86 - {3B66D3B1-4B4C-4AAF-8556-B3B51999F177}.Release|ARM.ActiveCfg = Release|x86 - {3B66D3B1-4B4C-4AAF-8556-B3B51999F177}.Release|ARM64.ActiveCfg = Release|ARM64 - {3B66D3B1-4B4C-4AAF-8556-B3B51999F177}.Release|ARM64.Build.0 = Release|ARM64 - {3B66D3B1-4B4C-4AAF-8556-B3B51999F177}.Release|ARM64.Deploy.0 = Release|ARM64 - {3B66D3B1-4B4C-4AAF-8556-B3B51999F177}.Release|x64.ActiveCfg = Release|x64 - {3B66D3B1-4B4C-4AAF-8556-B3B51999F177}.Release|x64.Build.0 = Release|x64 - {3B66D3B1-4B4C-4AAF-8556-B3B51999F177}.Release|x64.Deploy.0 = Release|x64 - {3B66D3B1-4B4C-4AAF-8556-B3B51999F177}.Release|x86.ActiveCfg = Release|x86 - {3B66D3B1-4B4C-4AAF-8556-B3B51999F177}.Release|x86.Build.0 = Release|x86 - {3B66D3B1-4B4C-4AAF-8556-B3B51999F177}.Release|x86.Deploy.0 = Release|x86 - {0187840F-8D1A-4198-B5BF-8B05CADB4554}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {0187840F-8D1A-4198-B5BF-8B05CADB4554}.Debug|Any CPU.Build.0 = Debug|Any CPU - {0187840F-8D1A-4198-B5BF-8B05CADB4554}.Debug|ARM.ActiveCfg = Debug|Any CPU - {0187840F-8D1A-4198-B5BF-8B05CADB4554}.Debug|ARM.Build.0 = Debug|Any CPU - {0187840F-8D1A-4198-B5BF-8B05CADB4554}.Debug|ARM64.ActiveCfg = Debug|Any CPU - {0187840F-8D1A-4198-B5BF-8B05CADB4554}.Debug|ARM64.Build.0 = Debug|Any CPU - {0187840F-8D1A-4198-B5BF-8B05CADB4554}.Debug|x64.ActiveCfg = Debug|Any CPU - {0187840F-8D1A-4198-B5BF-8B05CADB4554}.Debug|x64.Build.0 = Debug|Any CPU - {0187840F-8D1A-4198-B5BF-8B05CADB4554}.Debug|x86.ActiveCfg = Debug|Any CPU - {0187840F-8D1A-4198-B5BF-8B05CADB4554}.Debug|x86.Build.0 = Debug|Any CPU - {0187840F-8D1A-4198-B5BF-8B05CADB4554}.Release|Any CPU.ActiveCfg = Release|Any CPU - {0187840F-8D1A-4198-B5BF-8B05CADB4554}.Release|Any CPU.Build.0 = Release|Any CPU - {0187840F-8D1A-4198-B5BF-8B05CADB4554}.Release|ARM.ActiveCfg = Release|Any CPU - {0187840F-8D1A-4198-B5BF-8B05CADB4554}.Release|ARM.Build.0 = Release|Any CPU - {0187840F-8D1A-4198-B5BF-8B05CADB4554}.Release|ARM64.ActiveCfg = Release|Any CPU - {0187840F-8D1A-4198-B5BF-8B05CADB4554}.Release|ARM64.Build.0 = Release|Any CPU - {0187840F-8D1A-4198-B5BF-8B05CADB4554}.Release|x64.ActiveCfg = Release|Any CPU - {0187840F-8D1A-4198-B5BF-8B05CADB4554}.Release|x64.Build.0 = Release|Any CPU - {0187840F-8D1A-4198-B5BF-8B05CADB4554}.Release|x86.ActiveCfg = Release|Any CPU - {0187840F-8D1A-4198-B5BF-8B05CADB4554}.Release|x86.Build.0 = Release|Any CPU - EndGlobalSection - GlobalSection(SolutionProperties) = preSolution - HideSolutionNode = FALSE - EndGlobalSection - GlobalSection(NestedProjects) = preSolution - {64837010-4A71-4BF6-8ADF-66A251650A11} = {810B7F1B-97AD-4422-93C8-562AD50CE4DB} - {793EC923-D704-4C6F-9506-EB6A32BFBB8D} = {64837010-4A71-4BF6-8ADF-66A251650A11} - {FD825FC2-7745-43CF-82A9-E0717CCCC830} = {64837010-4A71-4BF6-8ADF-66A251650A11} - {48DF06B5-90AD-4AC6-81F7-B381DB093B09} = {810B7F1B-97AD-4422-93C8-562AD50CE4DB} - {07B6D3B7-0AC0-489B-BF27-588AB6226B1C} = {48DF06B5-90AD-4AC6-81F7-B381DB093B09} - {17C29B7C-8EAB-4C0C-A1B6-87EEA99FA426} = {48DF06B5-90AD-4AC6-81F7-B381DB093B09} - {CA4FE39C-2379-4966-9940-DE738D94E982} = {48DF06B5-90AD-4AC6-81F7-B381DB093B09} - {88314412-B020-415B-AEAB-57ADC43B273E} = {232C0E31-606A-48CD-B860-2E9155EAB4C6} - {8776A0BD-DBD1-4F11-A022-400D044FF618} = {232C0E31-606A-48CD-B860-2E9155EAB4C6} - {9E61CC56-43F9-489C-AAAF-BD5EC69367C4} = {232C0E31-606A-48CD-B860-2E9155EAB4C6} - {1AF20FDC-36D5-450E-9461-0F78AEB89716} = {82E6DD11-BBC1-4BFC-A244-593EE27BF250} - {C492CCBB-CDFD-403C-A981-1BC5EAA934D1} = {82E6DD11-BBC1-4BFC-A244-593EE27BF250} - {C8AB398F-10C2-4B97-87F0-F09B922947D6} = {45B5DFF9-9F13-4BF3-B561-8ABBDFE5C237} - {CAE9B987-6DB2-42A8-B3F2-3BB700CC4DD8} = {45B5DFF9-9F13-4BF3-B561-8ABBDFE5C237} - {5213E830-43F5-491E-A8EF-D75083578EF3} = {65F17D44-343F-4B87-A726-3BE3E48168B4} - {3B66D3B1-4B4C-4AAF-8556-B3B51999F177} = {65F17D44-343F-4B87-A726-3BE3E48168B4} - {0187840F-8D1A-4198-B5BF-8B05CADB4554} = {232C0E31-606A-48CD-B860-2E9155EAB4C6} - EndGlobalSection - GlobalSection(ExtensibilityGlobals) = postSolution - SolutionGuid = {563225C5-4D56-446B-9ADE-09D3F0DE7963} - EndGlobalSection -EndGlobal diff --git a/Bili.Uwp.sln b/Bili.Uwp.sln new file mode 100644 index 000000000..b6f9e7b29 --- /dev/null +++ b/Bili.Uwp.sln @@ -0,0 +1,563 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 17 +VisualStudioVersion = 17.0.31410.414 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "App", "src\App\App.csproj", "{1C288BC0-72F3-4C4C-90AD-A05B52E937B0}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Utilities", "Utilities", "{810B7F1B-97AD-4422-93C8-562AD50CE4DB}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Toolkit", "Toolkit", "{48DF06B5-90AD-4AC6-81F7-B381DB093B09}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Toolkit.Uwp", "src\Utilities\Toolkit\Toolkit.Uwp\Toolkit.Uwp.csproj", "{07B6D3B7-0AC0-489B-BF27-588AB6226B1C}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Toolkit.Interfaces", "src\Utilities\Toolkit\Toolkit.Interfaces\Toolkit.Interfaces.csproj", "{CA4FE39C-2379-4966-9940-DE738D94E982}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Models", "Models", "{232C0E31-606A-48CD-B860-2E9155EAB4C6}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Models.Enums", "src\Models\Models.Enums\Models.Enums.csproj", "{88314412-B020-415B-AEAB-57ADC43B273E}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Models.BiliBili", "src\Models\Models.BiliBili\Models.BiliBili.csproj", "{8776A0BD-DBD1-4F11-A022-400D044FF618}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Models.App", "src\Models\Models.App\Models.App.csproj", "{9E61CC56-43F9-489C-AAAF-BD5EC69367C4}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "ViewModels", "ViewModels", "{82E6DD11-BBC1-4BFC-A244-593EE27BF250}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ViewModels.Uwp", "src\ViewModels\ViewModels.Uwp\ViewModels.Uwp.csproj", "{1AF20FDC-36D5-450E-9461-0F78AEB89716}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Lib", "Lib", "{45B5DFF9-9F13-4BF3-B561-8ABBDFE5C237}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Models.gRPC", "src\Models\Models.gRPC\Models.gRPC.csproj", "{0187840F-8D1A-4198-B5BF-8B05CADB4554}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "FrostMaster.Uwp", "src\Lib\FrostMaster.Uwp\FrostMaster.Uwp.csproj", "{B9CD5845-2DE3-40FC-BC0F-27C406929551}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Tasks", "src\Tasks\Tasks.csproj", "{C7BEA810-0F31-4176-99A0-4481733918DE}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Models.Data", "src\Models\Models.Data\Models.Data.csproj", "{C4DBF0B2-C71A-4528-BF15-B0A7A25B2510}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Adapter", "Adapter", "{8B044ABB-C591-44E5-B7C6-B2F7262F5C1D}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Adapter.Interfaces", "src\Adapter\Adapter.Interfaces\Adapter.Interfaces.csproj", "{3046F10C-456E-43D3-8F13-3855EE2D9344}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Adapter.Implementation", "src\Adapter\Adapter.Implementation\Adapter.Implementation.csproj", "{9C3E4744-E870-490B-A7DB-1473A0DB355F}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Adapter.UnitTests", "src\Adapter\Adapter.UnitTests\Adapter.UnitTests.csproj", "{B9B87F56-8AFE-4F6E-B2A9-AFA8D118FC67}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ViewModels.Interfaces", "src\ViewModels\ViewModels.Interfaces\ViewModels.Interfaces.csproj", "{CD9930DC-36A8-45FC-8824-C8B3FA873FAD}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SignIn.Uwp", "src\Lib\SignIn.Uwp\SignIn.Uwp.csproj", "{1E466E0C-5218-4892-9FDD-0E777795C545}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Lib.Implementation", "src\Lib\Lib.Implementation\Lib.Implementation.csproj", "{EE5C62AF-B6B7-4CF9-895F-60ADC1801175}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Lib.Interfaces", "src\Lib\Lib.Interfaces\Lib.Interfaces.csproj", "{83DD339B-EA58-4380-8994-02FA6708CC7A}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Toolkit.Fake", "src\Utilities\Toolkit\Toolkit.Fake\Toolkit.Fake.csproj", "{6832083A-A832-4DF4-B3F7-C83D1F51BB95}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DI.App", "src\Lib\DI.App\DI.App.csproj", "{18D92F2F-046E-4A35-83FC-476B097BB8B7}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DI.Task", "src\Lib\DI.Task\DI.Task.csproj", "{A87244E1-94AB-40E6-A32B-1972429FBCB2}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DI.Container", "src\Lib\DI.Container\DI.Container.csproj", "{81B36A8C-E9A5-46F1-B803-B4D329C6E4B4}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Models.Workspace", "src\Models\Models.Workspace\Models.Workspace.csproj", "{47666400-8C76-4DEC-9387-385DA31187C9}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Debug|ARM = Debug|ARM + Debug|ARM64 = Debug|ARM64 + Debug|x64 = Debug|x64 + Debug|x86 = Debug|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 + {1C288BC0-72F3-4C4C-90AD-A05B52E937B0}.Debug|Any CPU.ActiveCfg = Debug|x86 + {1C288BC0-72F3-4C4C-90AD-A05B52E937B0}.Debug|ARM.ActiveCfg = Debug|x86 + {1C288BC0-72F3-4C4C-90AD-A05B52E937B0}.Debug|ARM64.ActiveCfg = Debug|ARM64 + {1C288BC0-72F3-4C4C-90AD-A05B52E937B0}.Debug|ARM64.Build.0 = Debug|ARM64 + {1C288BC0-72F3-4C4C-90AD-A05B52E937B0}.Debug|ARM64.Deploy.0 = Debug|ARM64 + {1C288BC0-72F3-4C4C-90AD-A05B52E937B0}.Debug|x64.ActiveCfg = Debug|x64 + {1C288BC0-72F3-4C4C-90AD-A05B52E937B0}.Debug|x64.Build.0 = Debug|x64 + {1C288BC0-72F3-4C4C-90AD-A05B52E937B0}.Debug|x64.Deploy.0 = Debug|x64 + {1C288BC0-72F3-4C4C-90AD-A05B52E937B0}.Debug|x86.ActiveCfg = Debug|x86 + {1C288BC0-72F3-4C4C-90AD-A05B52E937B0}.Debug|x86.Build.0 = Debug|x86 + {1C288BC0-72F3-4C4C-90AD-A05B52E937B0}.Debug|x86.Deploy.0 = Debug|x86 + {1C288BC0-72F3-4C4C-90AD-A05B52E937B0}.Release|Any CPU.ActiveCfg = Release|x86 + {1C288BC0-72F3-4C4C-90AD-A05B52E937B0}.Release|ARM.ActiveCfg = Release|x86 + {1C288BC0-72F3-4C4C-90AD-A05B52E937B0}.Release|ARM64.ActiveCfg = Release|ARM64 + {1C288BC0-72F3-4C4C-90AD-A05B52E937B0}.Release|ARM64.Build.0 = Release|ARM64 + {1C288BC0-72F3-4C4C-90AD-A05B52E937B0}.Release|ARM64.Deploy.0 = Release|ARM64 + {1C288BC0-72F3-4C4C-90AD-A05B52E937B0}.Release|x64.ActiveCfg = Release|x64 + {1C288BC0-72F3-4C4C-90AD-A05B52E937B0}.Release|x64.Build.0 = Release|x64 + {1C288BC0-72F3-4C4C-90AD-A05B52E937B0}.Release|x64.Deploy.0 = Release|x64 + {1C288BC0-72F3-4C4C-90AD-A05B52E937B0}.Release|x86.ActiveCfg = Release|x86 + {1C288BC0-72F3-4C4C-90AD-A05B52E937B0}.Release|x86.Build.0 = Release|x86 + {1C288BC0-72F3-4C4C-90AD-A05B52E937B0}.Release|x86.Deploy.0 = Release|x86 + {07B6D3B7-0AC0-489B-BF27-588AB6226B1C}.Debug|Any CPU.ActiveCfg = Debug|x86 + {07B6D3B7-0AC0-489B-BF27-588AB6226B1C}.Debug|ARM.ActiveCfg = Debug|x86 + {07B6D3B7-0AC0-489B-BF27-588AB6226B1C}.Debug|ARM64.ActiveCfg = Debug|ARM64 + {07B6D3B7-0AC0-489B-BF27-588AB6226B1C}.Debug|ARM64.Build.0 = Debug|ARM64 + {07B6D3B7-0AC0-489B-BF27-588AB6226B1C}.Debug|x64.ActiveCfg = Debug|x64 + {07B6D3B7-0AC0-489B-BF27-588AB6226B1C}.Debug|x64.Build.0 = Debug|x64 + {07B6D3B7-0AC0-489B-BF27-588AB6226B1C}.Debug|x86.ActiveCfg = Debug|x86 + {07B6D3B7-0AC0-489B-BF27-588AB6226B1C}.Debug|x86.Build.0 = Debug|x86 + {07B6D3B7-0AC0-489B-BF27-588AB6226B1C}.Release|Any CPU.ActiveCfg = Release|x86 + {07B6D3B7-0AC0-489B-BF27-588AB6226B1C}.Release|ARM.ActiveCfg = Release|x86 + {07B6D3B7-0AC0-489B-BF27-588AB6226B1C}.Release|ARM64.ActiveCfg = Release|ARM64 + {07B6D3B7-0AC0-489B-BF27-588AB6226B1C}.Release|ARM64.Build.0 = Release|ARM64 + {07B6D3B7-0AC0-489B-BF27-588AB6226B1C}.Release|x64.ActiveCfg = Release|x64 + {07B6D3B7-0AC0-489B-BF27-588AB6226B1C}.Release|x64.Build.0 = Release|x64 + {07B6D3B7-0AC0-489B-BF27-588AB6226B1C}.Release|x86.ActiveCfg = Release|x86 + {07B6D3B7-0AC0-489B-BF27-588AB6226B1C}.Release|x86.Build.0 = Release|x86 + {CA4FE39C-2379-4966-9940-DE738D94E982}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {CA4FE39C-2379-4966-9940-DE738D94E982}.Debug|Any CPU.Build.0 = Debug|Any CPU + {CA4FE39C-2379-4966-9940-DE738D94E982}.Debug|ARM.ActiveCfg = Debug|Any CPU + {CA4FE39C-2379-4966-9940-DE738D94E982}.Debug|ARM.Build.0 = Debug|Any CPU + {CA4FE39C-2379-4966-9940-DE738D94E982}.Debug|ARM64.ActiveCfg = Debug|Any CPU + {CA4FE39C-2379-4966-9940-DE738D94E982}.Debug|ARM64.Build.0 = Debug|Any CPU + {CA4FE39C-2379-4966-9940-DE738D94E982}.Debug|x64.ActiveCfg = Debug|Any CPU + {CA4FE39C-2379-4966-9940-DE738D94E982}.Debug|x64.Build.0 = Debug|Any CPU + {CA4FE39C-2379-4966-9940-DE738D94E982}.Debug|x86.ActiveCfg = Debug|Any CPU + {CA4FE39C-2379-4966-9940-DE738D94E982}.Debug|x86.Build.0 = Debug|Any CPU + {CA4FE39C-2379-4966-9940-DE738D94E982}.Release|Any CPU.ActiveCfg = Release|Any CPU + {CA4FE39C-2379-4966-9940-DE738D94E982}.Release|Any CPU.Build.0 = Release|Any CPU + {CA4FE39C-2379-4966-9940-DE738D94E982}.Release|ARM.ActiveCfg = Release|Any CPU + {CA4FE39C-2379-4966-9940-DE738D94E982}.Release|ARM.Build.0 = Release|Any CPU + {CA4FE39C-2379-4966-9940-DE738D94E982}.Release|ARM64.ActiveCfg = Release|Any CPU + {CA4FE39C-2379-4966-9940-DE738D94E982}.Release|ARM64.Build.0 = Release|Any CPU + {CA4FE39C-2379-4966-9940-DE738D94E982}.Release|x64.ActiveCfg = Release|Any CPU + {CA4FE39C-2379-4966-9940-DE738D94E982}.Release|x64.Build.0 = Release|Any CPU + {CA4FE39C-2379-4966-9940-DE738D94E982}.Release|x86.ActiveCfg = Release|Any CPU + {CA4FE39C-2379-4966-9940-DE738D94E982}.Release|x86.Build.0 = Release|Any CPU + {88314412-B020-415B-AEAB-57ADC43B273E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {88314412-B020-415B-AEAB-57ADC43B273E}.Debug|Any CPU.Build.0 = Debug|Any CPU + {88314412-B020-415B-AEAB-57ADC43B273E}.Debug|ARM.ActiveCfg = Debug|Any CPU + {88314412-B020-415B-AEAB-57ADC43B273E}.Debug|ARM.Build.0 = Debug|Any CPU + {88314412-B020-415B-AEAB-57ADC43B273E}.Debug|ARM64.ActiveCfg = Debug|Any CPU + {88314412-B020-415B-AEAB-57ADC43B273E}.Debug|ARM64.Build.0 = Debug|Any CPU + {88314412-B020-415B-AEAB-57ADC43B273E}.Debug|x64.ActiveCfg = Debug|Any CPU + {88314412-B020-415B-AEAB-57ADC43B273E}.Debug|x64.Build.0 = Debug|Any CPU + {88314412-B020-415B-AEAB-57ADC43B273E}.Debug|x86.ActiveCfg = Debug|Any CPU + {88314412-B020-415B-AEAB-57ADC43B273E}.Debug|x86.Build.0 = Debug|Any CPU + {88314412-B020-415B-AEAB-57ADC43B273E}.Release|Any CPU.ActiveCfg = Release|Any CPU + {88314412-B020-415B-AEAB-57ADC43B273E}.Release|Any CPU.Build.0 = Release|Any CPU + {88314412-B020-415B-AEAB-57ADC43B273E}.Release|ARM.ActiveCfg = Release|Any CPU + {88314412-B020-415B-AEAB-57ADC43B273E}.Release|ARM.Build.0 = Release|Any CPU + {88314412-B020-415B-AEAB-57ADC43B273E}.Release|ARM64.ActiveCfg = Release|Any CPU + {88314412-B020-415B-AEAB-57ADC43B273E}.Release|ARM64.Build.0 = Release|Any CPU + {88314412-B020-415B-AEAB-57ADC43B273E}.Release|x64.ActiveCfg = Release|Any CPU + {88314412-B020-415B-AEAB-57ADC43B273E}.Release|x64.Build.0 = Release|Any CPU + {88314412-B020-415B-AEAB-57ADC43B273E}.Release|x86.ActiveCfg = Release|Any CPU + {88314412-B020-415B-AEAB-57ADC43B273E}.Release|x86.Build.0 = Release|Any CPU + {8776A0BD-DBD1-4F11-A022-400D044FF618}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {8776A0BD-DBD1-4F11-A022-400D044FF618}.Debug|Any CPU.Build.0 = Debug|Any CPU + {8776A0BD-DBD1-4F11-A022-400D044FF618}.Debug|ARM.ActiveCfg = Debug|Any CPU + {8776A0BD-DBD1-4F11-A022-400D044FF618}.Debug|ARM.Build.0 = Debug|Any CPU + {8776A0BD-DBD1-4F11-A022-400D044FF618}.Debug|ARM64.ActiveCfg = Debug|Any CPU + {8776A0BD-DBD1-4F11-A022-400D044FF618}.Debug|ARM64.Build.0 = Debug|Any CPU + {8776A0BD-DBD1-4F11-A022-400D044FF618}.Debug|x64.ActiveCfg = Debug|Any CPU + {8776A0BD-DBD1-4F11-A022-400D044FF618}.Debug|x64.Build.0 = Debug|Any CPU + {8776A0BD-DBD1-4F11-A022-400D044FF618}.Debug|x86.ActiveCfg = Debug|Any CPU + {8776A0BD-DBD1-4F11-A022-400D044FF618}.Debug|x86.Build.0 = Debug|Any CPU + {8776A0BD-DBD1-4F11-A022-400D044FF618}.Release|Any CPU.ActiveCfg = Release|Any CPU + {8776A0BD-DBD1-4F11-A022-400D044FF618}.Release|Any CPU.Build.0 = Release|Any CPU + {8776A0BD-DBD1-4F11-A022-400D044FF618}.Release|ARM.ActiveCfg = Release|Any CPU + {8776A0BD-DBD1-4F11-A022-400D044FF618}.Release|ARM.Build.0 = Release|Any CPU + {8776A0BD-DBD1-4F11-A022-400D044FF618}.Release|ARM64.ActiveCfg = Release|Any CPU + {8776A0BD-DBD1-4F11-A022-400D044FF618}.Release|ARM64.Build.0 = Release|Any CPU + {8776A0BD-DBD1-4F11-A022-400D044FF618}.Release|x64.ActiveCfg = Release|Any CPU + {8776A0BD-DBD1-4F11-A022-400D044FF618}.Release|x64.Build.0 = Release|Any CPU + {8776A0BD-DBD1-4F11-A022-400D044FF618}.Release|x86.ActiveCfg = Release|Any CPU + {8776A0BD-DBD1-4F11-A022-400D044FF618}.Release|x86.Build.0 = Release|Any CPU + {9E61CC56-43F9-489C-AAAF-BD5EC69367C4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {9E61CC56-43F9-489C-AAAF-BD5EC69367C4}.Debug|Any CPU.Build.0 = Debug|Any CPU + {9E61CC56-43F9-489C-AAAF-BD5EC69367C4}.Debug|ARM.ActiveCfg = Debug|Any CPU + {9E61CC56-43F9-489C-AAAF-BD5EC69367C4}.Debug|ARM.Build.0 = Debug|Any CPU + {9E61CC56-43F9-489C-AAAF-BD5EC69367C4}.Debug|ARM64.ActiveCfg = Debug|Any CPU + {9E61CC56-43F9-489C-AAAF-BD5EC69367C4}.Debug|ARM64.Build.0 = Debug|Any CPU + {9E61CC56-43F9-489C-AAAF-BD5EC69367C4}.Debug|x64.ActiveCfg = Debug|Any CPU + {9E61CC56-43F9-489C-AAAF-BD5EC69367C4}.Debug|x64.Build.0 = Debug|Any CPU + {9E61CC56-43F9-489C-AAAF-BD5EC69367C4}.Debug|x86.ActiveCfg = Debug|Any CPU + {9E61CC56-43F9-489C-AAAF-BD5EC69367C4}.Debug|x86.Build.0 = Debug|Any CPU + {9E61CC56-43F9-489C-AAAF-BD5EC69367C4}.Release|Any CPU.ActiveCfg = Release|Any CPU + {9E61CC56-43F9-489C-AAAF-BD5EC69367C4}.Release|Any CPU.Build.0 = Release|Any CPU + {9E61CC56-43F9-489C-AAAF-BD5EC69367C4}.Release|ARM.ActiveCfg = Release|Any CPU + {9E61CC56-43F9-489C-AAAF-BD5EC69367C4}.Release|ARM.Build.0 = Release|Any CPU + {9E61CC56-43F9-489C-AAAF-BD5EC69367C4}.Release|ARM64.ActiveCfg = Release|Any CPU + {9E61CC56-43F9-489C-AAAF-BD5EC69367C4}.Release|ARM64.Build.0 = Release|Any CPU + {9E61CC56-43F9-489C-AAAF-BD5EC69367C4}.Release|x64.ActiveCfg = Release|Any CPU + {9E61CC56-43F9-489C-AAAF-BD5EC69367C4}.Release|x64.Build.0 = Release|Any CPU + {9E61CC56-43F9-489C-AAAF-BD5EC69367C4}.Release|x86.ActiveCfg = Release|Any CPU + {9E61CC56-43F9-489C-AAAF-BD5EC69367C4}.Release|x86.Build.0 = Release|Any CPU + {1AF20FDC-36D5-450E-9461-0F78AEB89716}.Debug|Any CPU.ActiveCfg = Debug|x86 + {1AF20FDC-36D5-450E-9461-0F78AEB89716}.Debug|ARM.ActiveCfg = Debug|x86 + {1AF20FDC-36D5-450E-9461-0F78AEB89716}.Debug|ARM64.ActiveCfg = Debug|ARM64 + {1AF20FDC-36D5-450E-9461-0F78AEB89716}.Debug|ARM64.Build.0 = Debug|ARM64 + {1AF20FDC-36D5-450E-9461-0F78AEB89716}.Debug|x64.ActiveCfg = Debug|x64 + {1AF20FDC-36D5-450E-9461-0F78AEB89716}.Debug|x64.Build.0 = Debug|x64 + {1AF20FDC-36D5-450E-9461-0F78AEB89716}.Debug|x86.ActiveCfg = Debug|x86 + {1AF20FDC-36D5-450E-9461-0F78AEB89716}.Debug|x86.Build.0 = Debug|x86 + {1AF20FDC-36D5-450E-9461-0F78AEB89716}.Release|Any CPU.ActiveCfg = Release|x86 + {1AF20FDC-36D5-450E-9461-0F78AEB89716}.Release|ARM.ActiveCfg = Release|x86 + {1AF20FDC-36D5-450E-9461-0F78AEB89716}.Release|ARM64.ActiveCfg = Release|ARM64 + {1AF20FDC-36D5-450E-9461-0F78AEB89716}.Release|ARM64.Build.0 = Release|ARM64 + {1AF20FDC-36D5-450E-9461-0F78AEB89716}.Release|x64.ActiveCfg = Release|x64 + {1AF20FDC-36D5-450E-9461-0F78AEB89716}.Release|x64.Build.0 = Release|x64 + {1AF20FDC-36D5-450E-9461-0F78AEB89716}.Release|x86.ActiveCfg = Release|x86 + {1AF20FDC-36D5-450E-9461-0F78AEB89716}.Release|x86.Build.0 = Release|x86 + {0187840F-8D1A-4198-B5BF-8B05CADB4554}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {0187840F-8D1A-4198-B5BF-8B05CADB4554}.Debug|Any CPU.Build.0 = Debug|Any CPU + {0187840F-8D1A-4198-B5BF-8B05CADB4554}.Debug|ARM.ActiveCfg = Debug|Any CPU + {0187840F-8D1A-4198-B5BF-8B05CADB4554}.Debug|ARM.Build.0 = Debug|Any CPU + {0187840F-8D1A-4198-B5BF-8B05CADB4554}.Debug|ARM64.ActiveCfg = Debug|Any CPU + {0187840F-8D1A-4198-B5BF-8B05CADB4554}.Debug|ARM64.Build.0 = Debug|Any CPU + {0187840F-8D1A-4198-B5BF-8B05CADB4554}.Debug|x64.ActiveCfg = Debug|Any CPU + {0187840F-8D1A-4198-B5BF-8B05CADB4554}.Debug|x64.Build.0 = Debug|Any CPU + {0187840F-8D1A-4198-B5BF-8B05CADB4554}.Debug|x86.ActiveCfg = Debug|Any CPU + {0187840F-8D1A-4198-B5BF-8B05CADB4554}.Debug|x86.Build.0 = Debug|Any CPU + {0187840F-8D1A-4198-B5BF-8B05CADB4554}.Release|Any CPU.ActiveCfg = Release|Any CPU + {0187840F-8D1A-4198-B5BF-8B05CADB4554}.Release|Any CPU.Build.0 = Release|Any CPU + {0187840F-8D1A-4198-B5BF-8B05CADB4554}.Release|ARM.ActiveCfg = Release|Any CPU + {0187840F-8D1A-4198-B5BF-8B05CADB4554}.Release|ARM.Build.0 = Release|Any CPU + {0187840F-8D1A-4198-B5BF-8B05CADB4554}.Release|ARM64.ActiveCfg = Release|Any CPU + {0187840F-8D1A-4198-B5BF-8B05CADB4554}.Release|ARM64.Build.0 = Release|Any CPU + {0187840F-8D1A-4198-B5BF-8B05CADB4554}.Release|x64.ActiveCfg = Release|Any CPU + {0187840F-8D1A-4198-B5BF-8B05CADB4554}.Release|x64.Build.0 = Release|Any CPU + {0187840F-8D1A-4198-B5BF-8B05CADB4554}.Release|x86.ActiveCfg = Release|Any CPU + {0187840F-8D1A-4198-B5BF-8B05CADB4554}.Release|x86.Build.0 = Release|Any CPU + {B9CD5845-2DE3-40FC-BC0F-27C406929551}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {B9CD5845-2DE3-40FC-BC0F-27C406929551}.Debug|Any CPU.Build.0 = Debug|Any CPU + {B9CD5845-2DE3-40FC-BC0F-27C406929551}.Debug|ARM.ActiveCfg = Debug|ARM + {B9CD5845-2DE3-40FC-BC0F-27C406929551}.Debug|ARM.Build.0 = Debug|ARM + {B9CD5845-2DE3-40FC-BC0F-27C406929551}.Debug|ARM64.ActiveCfg = Debug|ARM64 + {B9CD5845-2DE3-40FC-BC0F-27C406929551}.Debug|ARM64.Build.0 = Debug|ARM64 + {B9CD5845-2DE3-40FC-BC0F-27C406929551}.Debug|x64.ActiveCfg = Debug|x64 + {B9CD5845-2DE3-40FC-BC0F-27C406929551}.Debug|x64.Build.0 = Debug|x64 + {B9CD5845-2DE3-40FC-BC0F-27C406929551}.Debug|x86.ActiveCfg = Debug|x86 + {B9CD5845-2DE3-40FC-BC0F-27C406929551}.Debug|x86.Build.0 = Debug|x86 + {B9CD5845-2DE3-40FC-BC0F-27C406929551}.Release|Any CPU.ActiveCfg = Release|Any CPU + {B9CD5845-2DE3-40FC-BC0F-27C406929551}.Release|Any CPU.Build.0 = Release|Any CPU + {B9CD5845-2DE3-40FC-BC0F-27C406929551}.Release|ARM.ActiveCfg = Release|ARM + {B9CD5845-2DE3-40FC-BC0F-27C406929551}.Release|ARM.Build.0 = Release|ARM + {B9CD5845-2DE3-40FC-BC0F-27C406929551}.Release|ARM64.ActiveCfg = Release|ARM64 + {B9CD5845-2DE3-40FC-BC0F-27C406929551}.Release|ARM64.Build.0 = Release|ARM64 + {B9CD5845-2DE3-40FC-BC0F-27C406929551}.Release|x64.ActiveCfg = Release|x64 + {B9CD5845-2DE3-40FC-BC0F-27C406929551}.Release|x64.Build.0 = Release|x64 + {B9CD5845-2DE3-40FC-BC0F-27C406929551}.Release|x86.ActiveCfg = Release|x86 + {B9CD5845-2DE3-40FC-BC0F-27C406929551}.Release|x86.Build.0 = Release|x86 + {C7BEA810-0F31-4176-99A0-4481733918DE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {C7BEA810-0F31-4176-99A0-4481733918DE}.Debug|Any CPU.Build.0 = Debug|Any CPU + {C7BEA810-0F31-4176-99A0-4481733918DE}.Debug|ARM.ActiveCfg = Debug|ARM + {C7BEA810-0F31-4176-99A0-4481733918DE}.Debug|ARM.Build.0 = Debug|ARM + {C7BEA810-0F31-4176-99A0-4481733918DE}.Debug|ARM64.ActiveCfg = Debug|ARM64 + {C7BEA810-0F31-4176-99A0-4481733918DE}.Debug|ARM64.Build.0 = Debug|ARM64 + {C7BEA810-0F31-4176-99A0-4481733918DE}.Debug|x64.ActiveCfg = Debug|x64 + {C7BEA810-0F31-4176-99A0-4481733918DE}.Debug|x64.Build.0 = Debug|x64 + {C7BEA810-0F31-4176-99A0-4481733918DE}.Debug|x86.ActiveCfg = Debug|x86 + {C7BEA810-0F31-4176-99A0-4481733918DE}.Debug|x86.Build.0 = Debug|x86 + {C7BEA810-0F31-4176-99A0-4481733918DE}.Release|Any CPU.ActiveCfg = Release|Any CPU + {C7BEA810-0F31-4176-99A0-4481733918DE}.Release|Any CPU.Build.0 = Release|Any CPU + {C7BEA810-0F31-4176-99A0-4481733918DE}.Release|ARM.ActiveCfg = Release|ARM + {C7BEA810-0F31-4176-99A0-4481733918DE}.Release|ARM.Build.0 = Release|ARM + {C7BEA810-0F31-4176-99A0-4481733918DE}.Release|ARM64.ActiveCfg = Release|ARM64 + {C7BEA810-0F31-4176-99A0-4481733918DE}.Release|ARM64.Build.0 = Release|ARM64 + {C7BEA810-0F31-4176-99A0-4481733918DE}.Release|x64.ActiveCfg = Release|x64 + {C7BEA810-0F31-4176-99A0-4481733918DE}.Release|x64.Build.0 = Release|x64 + {C7BEA810-0F31-4176-99A0-4481733918DE}.Release|x86.ActiveCfg = Release|x86 + {C7BEA810-0F31-4176-99A0-4481733918DE}.Release|x86.Build.0 = Release|x86 + {C4DBF0B2-C71A-4528-BF15-B0A7A25B2510}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {C4DBF0B2-C71A-4528-BF15-B0A7A25B2510}.Debug|Any CPU.Build.0 = Debug|Any CPU + {C4DBF0B2-C71A-4528-BF15-B0A7A25B2510}.Debug|ARM.ActiveCfg = Debug|Any CPU + {C4DBF0B2-C71A-4528-BF15-B0A7A25B2510}.Debug|ARM.Build.0 = Debug|Any CPU + {C4DBF0B2-C71A-4528-BF15-B0A7A25B2510}.Debug|ARM64.ActiveCfg = Debug|Any CPU + {C4DBF0B2-C71A-4528-BF15-B0A7A25B2510}.Debug|ARM64.Build.0 = Debug|Any CPU + {C4DBF0B2-C71A-4528-BF15-B0A7A25B2510}.Debug|x64.ActiveCfg = Debug|Any CPU + {C4DBF0B2-C71A-4528-BF15-B0A7A25B2510}.Debug|x64.Build.0 = Debug|Any CPU + {C4DBF0B2-C71A-4528-BF15-B0A7A25B2510}.Debug|x86.ActiveCfg = Debug|Any CPU + {C4DBF0B2-C71A-4528-BF15-B0A7A25B2510}.Debug|x86.Build.0 = Debug|Any CPU + {C4DBF0B2-C71A-4528-BF15-B0A7A25B2510}.Release|Any CPU.ActiveCfg = Release|Any CPU + {C4DBF0B2-C71A-4528-BF15-B0A7A25B2510}.Release|Any CPU.Build.0 = Release|Any CPU + {C4DBF0B2-C71A-4528-BF15-B0A7A25B2510}.Release|ARM.ActiveCfg = Release|Any CPU + {C4DBF0B2-C71A-4528-BF15-B0A7A25B2510}.Release|ARM.Build.0 = Release|Any CPU + {C4DBF0B2-C71A-4528-BF15-B0A7A25B2510}.Release|ARM64.ActiveCfg = Release|Any CPU + {C4DBF0B2-C71A-4528-BF15-B0A7A25B2510}.Release|ARM64.Build.0 = Release|Any CPU + {C4DBF0B2-C71A-4528-BF15-B0A7A25B2510}.Release|x64.ActiveCfg = Release|Any CPU + {C4DBF0B2-C71A-4528-BF15-B0A7A25B2510}.Release|x64.Build.0 = Release|Any CPU + {C4DBF0B2-C71A-4528-BF15-B0A7A25B2510}.Release|x86.ActiveCfg = Release|Any CPU + {C4DBF0B2-C71A-4528-BF15-B0A7A25B2510}.Release|x86.Build.0 = Release|Any CPU + {3046F10C-456E-43D3-8F13-3855EE2D9344}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {3046F10C-456E-43D3-8F13-3855EE2D9344}.Debug|Any CPU.Build.0 = Debug|Any CPU + {3046F10C-456E-43D3-8F13-3855EE2D9344}.Debug|ARM.ActiveCfg = Debug|Any CPU + {3046F10C-456E-43D3-8F13-3855EE2D9344}.Debug|ARM.Build.0 = Debug|Any CPU + {3046F10C-456E-43D3-8F13-3855EE2D9344}.Debug|ARM64.ActiveCfg = Debug|Any CPU + {3046F10C-456E-43D3-8F13-3855EE2D9344}.Debug|ARM64.Build.0 = Debug|Any CPU + {3046F10C-456E-43D3-8F13-3855EE2D9344}.Debug|x64.ActiveCfg = Debug|Any CPU + {3046F10C-456E-43D3-8F13-3855EE2D9344}.Debug|x64.Build.0 = Debug|Any CPU + {3046F10C-456E-43D3-8F13-3855EE2D9344}.Debug|x86.ActiveCfg = Debug|Any CPU + {3046F10C-456E-43D3-8F13-3855EE2D9344}.Debug|x86.Build.0 = Debug|Any CPU + {3046F10C-456E-43D3-8F13-3855EE2D9344}.Release|Any CPU.ActiveCfg = Release|Any CPU + {3046F10C-456E-43D3-8F13-3855EE2D9344}.Release|Any CPU.Build.0 = Release|Any CPU + {3046F10C-456E-43D3-8F13-3855EE2D9344}.Release|ARM.ActiveCfg = Release|Any CPU + {3046F10C-456E-43D3-8F13-3855EE2D9344}.Release|ARM.Build.0 = Release|Any CPU + {3046F10C-456E-43D3-8F13-3855EE2D9344}.Release|ARM64.ActiveCfg = Release|Any CPU + {3046F10C-456E-43D3-8F13-3855EE2D9344}.Release|ARM64.Build.0 = Release|Any CPU + {3046F10C-456E-43D3-8F13-3855EE2D9344}.Release|x64.ActiveCfg = Release|Any CPU + {3046F10C-456E-43D3-8F13-3855EE2D9344}.Release|x64.Build.0 = Release|Any CPU + {3046F10C-456E-43D3-8F13-3855EE2D9344}.Release|x86.ActiveCfg = Release|Any CPU + {3046F10C-456E-43D3-8F13-3855EE2D9344}.Release|x86.Build.0 = Release|Any CPU + {9C3E4744-E870-490B-A7DB-1473A0DB355F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {9C3E4744-E870-490B-A7DB-1473A0DB355F}.Debug|Any CPU.Build.0 = Debug|Any CPU + {9C3E4744-E870-490B-A7DB-1473A0DB355F}.Debug|ARM.ActiveCfg = Debug|Any CPU + {9C3E4744-E870-490B-A7DB-1473A0DB355F}.Debug|ARM.Build.0 = Debug|Any CPU + {9C3E4744-E870-490B-A7DB-1473A0DB355F}.Debug|ARM64.ActiveCfg = Debug|Any CPU + {9C3E4744-E870-490B-A7DB-1473A0DB355F}.Debug|ARM64.Build.0 = Debug|Any CPU + {9C3E4744-E870-490B-A7DB-1473A0DB355F}.Debug|x64.ActiveCfg = Debug|Any CPU + {9C3E4744-E870-490B-A7DB-1473A0DB355F}.Debug|x64.Build.0 = Debug|Any CPU + {9C3E4744-E870-490B-A7DB-1473A0DB355F}.Debug|x86.ActiveCfg = Debug|Any CPU + {9C3E4744-E870-490B-A7DB-1473A0DB355F}.Debug|x86.Build.0 = Debug|Any CPU + {9C3E4744-E870-490B-A7DB-1473A0DB355F}.Release|Any CPU.ActiveCfg = Release|Any CPU + {9C3E4744-E870-490B-A7DB-1473A0DB355F}.Release|Any CPU.Build.0 = Release|Any CPU + {9C3E4744-E870-490B-A7DB-1473A0DB355F}.Release|ARM.ActiveCfg = Release|Any CPU + {9C3E4744-E870-490B-A7DB-1473A0DB355F}.Release|ARM.Build.0 = Release|Any CPU + {9C3E4744-E870-490B-A7DB-1473A0DB355F}.Release|ARM64.ActiveCfg = Release|Any CPU + {9C3E4744-E870-490B-A7DB-1473A0DB355F}.Release|ARM64.Build.0 = Release|Any CPU + {9C3E4744-E870-490B-A7DB-1473A0DB355F}.Release|x64.ActiveCfg = Release|Any CPU + {9C3E4744-E870-490B-A7DB-1473A0DB355F}.Release|x64.Build.0 = Release|Any CPU + {9C3E4744-E870-490B-A7DB-1473A0DB355F}.Release|x86.ActiveCfg = Release|Any CPU + {9C3E4744-E870-490B-A7DB-1473A0DB355F}.Release|x86.Build.0 = Release|Any CPU + {B9B87F56-8AFE-4F6E-B2A9-AFA8D118FC67}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {B9B87F56-8AFE-4F6E-B2A9-AFA8D118FC67}.Debug|Any CPU.Build.0 = Debug|Any CPU + {B9B87F56-8AFE-4F6E-B2A9-AFA8D118FC67}.Debug|ARM.ActiveCfg = Debug|Any CPU + {B9B87F56-8AFE-4F6E-B2A9-AFA8D118FC67}.Debug|ARM.Build.0 = Debug|Any CPU + {B9B87F56-8AFE-4F6E-B2A9-AFA8D118FC67}.Debug|ARM64.ActiveCfg = Debug|Any CPU + {B9B87F56-8AFE-4F6E-B2A9-AFA8D118FC67}.Debug|ARM64.Build.0 = Debug|Any CPU + {B9B87F56-8AFE-4F6E-B2A9-AFA8D118FC67}.Debug|x64.ActiveCfg = Debug|Any CPU + {B9B87F56-8AFE-4F6E-B2A9-AFA8D118FC67}.Debug|x64.Build.0 = Debug|Any CPU + {B9B87F56-8AFE-4F6E-B2A9-AFA8D118FC67}.Debug|x86.ActiveCfg = Debug|Any CPU + {B9B87F56-8AFE-4F6E-B2A9-AFA8D118FC67}.Debug|x86.Build.0 = Debug|Any CPU + {B9B87F56-8AFE-4F6E-B2A9-AFA8D118FC67}.Release|Any CPU.ActiveCfg = Release|Any CPU + {B9B87F56-8AFE-4F6E-B2A9-AFA8D118FC67}.Release|Any CPU.Build.0 = Release|Any CPU + {B9B87F56-8AFE-4F6E-B2A9-AFA8D118FC67}.Release|ARM.ActiveCfg = Release|Any CPU + {B9B87F56-8AFE-4F6E-B2A9-AFA8D118FC67}.Release|ARM.Build.0 = Release|Any CPU + {B9B87F56-8AFE-4F6E-B2A9-AFA8D118FC67}.Release|ARM64.ActiveCfg = Release|Any CPU + {B9B87F56-8AFE-4F6E-B2A9-AFA8D118FC67}.Release|ARM64.Build.0 = Release|Any CPU + {B9B87F56-8AFE-4F6E-B2A9-AFA8D118FC67}.Release|x64.ActiveCfg = Release|Any CPU + {B9B87F56-8AFE-4F6E-B2A9-AFA8D118FC67}.Release|x64.Build.0 = Release|Any CPU + {B9B87F56-8AFE-4F6E-B2A9-AFA8D118FC67}.Release|x86.ActiveCfg = Release|Any CPU + {B9B87F56-8AFE-4F6E-B2A9-AFA8D118FC67}.Release|x86.Build.0 = Release|Any CPU + {CD9930DC-36A8-45FC-8824-C8B3FA873FAD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {CD9930DC-36A8-45FC-8824-C8B3FA873FAD}.Debug|Any CPU.Build.0 = Debug|Any CPU + {CD9930DC-36A8-45FC-8824-C8B3FA873FAD}.Debug|ARM.ActiveCfg = Debug|Any CPU + {CD9930DC-36A8-45FC-8824-C8B3FA873FAD}.Debug|ARM.Build.0 = Debug|Any CPU + {CD9930DC-36A8-45FC-8824-C8B3FA873FAD}.Debug|ARM64.ActiveCfg = Debug|Any CPU + {CD9930DC-36A8-45FC-8824-C8B3FA873FAD}.Debug|ARM64.Build.0 = Debug|Any CPU + {CD9930DC-36A8-45FC-8824-C8B3FA873FAD}.Debug|x64.ActiveCfg = Debug|Any CPU + {CD9930DC-36A8-45FC-8824-C8B3FA873FAD}.Debug|x64.Build.0 = Debug|Any CPU + {CD9930DC-36A8-45FC-8824-C8B3FA873FAD}.Debug|x86.ActiveCfg = Debug|Any CPU + {CD9930DC-36A8-45FC-8824-C8B3FA873FAD}.Debug|x86.Build.0 = Debug|Any CPU + {CD9930DC-36A8-45FC-8824-C8B3FA873FAD}.Release|Any CPU.ActiveCfg = Release|Any CPU + {CD9930DC-36A8-45FC-8824-C8B3FA873FAD}.Release|Any CPU.Build.0 = Release|Any CPU + {CD9930DC-36A8-45FC-8824-C8B3FA873FAD}.Release|ARM.ActiveCfg = Release|Any CPU + {CD9930DC-36A8-45FC-8824-C8B3FA873FAD}.Release|ARM.Build.0 = Release|Any CPU + {CD9930DC-36A8-45FC-8824-C8B3FA873FAD}.Release|ARM64.ActiveCfg = Release|Any CPU + {CD9930DC-36A8-45FC-8824-C8B3FA873FAD}.Release|ARM64.Build.0 = Release|Any CPU + {CD9930DC-36A8-45FC-8824-C8B3FA873FAD}.Release|x64.ActiveCfg = Release|Any CPU + {CD9930DC-36A8-45FC-8824-C8B3FA873FAD}.Release|x64.Build.0 = Release|Any CPU + {CD9930DC-36A8-45FC-8824-C8B3FA873FAD}.Release|x86.ActiveCfg = Release|Any CPU + {CD9930DC-36A8-45FC-8824-C8B3FA873FAD}.Release|x86.Build.0 = Release|Any CPU + {1E466E0C-5218-4892-9FDD-0E777795C545}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {1E466E0C-5218-4892-9FDD-0E777795C545}.Debug|Any CPU.Build.0 = Debug|Any CPU + {1E466E0C-5218-4892-9FDD-0E777795C545}.Debug|ARM.ActiveCfg = Debug|ARM + {1E466E0C-5218-4892-9FDD-0E777795C545}.Debug|ARM.Build.0 = Debug|ARM + {1E466E0C-5218-4892-9FDD-0E777795C545}.Debug|ARM64.ActiveCfg = Debug|ARM64 + {1E466E0C-5218-4892-9FDD-0E777795C545}.Debug|ARM64.Build.0 = Debug|ARM64 + {1E466E0C-5218-4892-9FDD-0E777795C545}.Debug|x64.ActiveCfg = Debug|x64 + {1E466E0C-5218-4892-9FDD-0E777795C545}.Debug|x64.Build.0 = Debug|x64 + {1E466E0C-5218-4892-9FDD-0E777795C545}.Debug|x86.ActiveCfg = Debug|x86 + {1E466E0C-5218-4892-9FDD-0E777795C545}.Debug|x86.Build.0 = Debug|x86 + {1E466E0C-5218-4892-9FDD-0E777795C545}.Release|Any CPU.ActiveCfg = Release|Any CPU + {1E466E0C-5218-4892-9FDD-0E777795C545}.Release|Any CPU.Build.0 = Release|Any CPU + {1E466E0C-5218-4892-9FDD-0E777795C545}.Release|ARM.ActiveCfg = Release|ARM + {1E466E0C-5218-4892-9FDD-0E777795C545}.Release|ARM.Build.0 = Release|ARM + {1E466E0C-5218-4892-9FDD-0E777795C545}.Release|ARM64.ActiveCfg = Release|ARM64 + {1E466E0C-5218-4892-9FDD-0E777795C545}.Release|ARM64.Build.0 = Release|ARM64 + {1E466E0C-5218-4892-9FDD-0E777795C545}.Release|x64.ActiveCfg = Release|x64 + {1E466E0C-5218-4892-9FDD-0E777795C545}.Release|x64.Build.0 = Release|x64 + {1E466E0C-5218-4892-9FDD-0E777795C545}.Release|x86.ActiveCfg = Release|x86 + {1E466E0C-5218-4892-9FDD-0E777795C545}.Release|x86.Build.0 = Release|x86 + {EE5C62AF-B6B7-4CF9-895F-60ADC1801175}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {EE5C62AF-B6B7-4CF9-895F-60ADC1801175}.Debug|Any CPU.Build.0 = Debug|Any CPU + {EE5C62AF-B6B7-4CF9-895F-60ADC1801175}.Debug|ARM.ActiveCfg = Debug|Any CPU + {EE5C62AF-B6B7-4CF9-895F-60ADC1801175}.Debug|ARM.Build.0 = Debug|Any CPU + {EE5C62AF-B6B7-4CF9-895F-60ADC1801175}.Debug|ARM64.ActiveCfg = Debug|Any CPU + {EE5C62AF-B6B7-4CF9-895F-60ADC1801175}.Debug|ARM64.Build.0 = Debug|Any CPU + {EE5C62AF-B6B7-4CF9-895F-60ADC1801175}.Debug|x64.ActiveCfg = Debug|Any CPU + {EE5C62AF-B6B7-4CF9-895F-60ADC1801175}.Debug|x64.Build.0 = Debug|Any CPU + {EE5C62AF-B6B7-4CF9-895F-60ADC1801175}.Debug|x86.ActiveCfg = Debug|Any CPU + {EE5C62AF-B6B7-4CF9-895F-60ADC1801175}.Debug|x86.Build.0 = Debug|Any CPU + {EE5C62AF-B6B7-4CF9-895F-60ADC1801175}.Release|Any CPU.ActiveCfg = Release|Any CPU + {EE5C62AF-B6B7-4CF9-895F-60ADC1801175}.Release|Any CPU.Build.0 = Release|Any CPU + {EE5C62AF-B6B7-4CF9-895F-60ADC1801175}.Release|ARM.ActiveCfg = Release|Any CPU + {EE5C62AF-B6B7-4CF9-895F-60ADC1801175}.Release|ARM.Build.0 = Release|Any CPU + {EE5C62AF-B6B7-4CF9-895F-60ADC1801175}.Release|ARM64.ActiveCfg = Release|Any CPU + {EE5C62AF-B6B7-4CF9-895F-60ADC1801175}.Release|ARM64.Build.0 = Release|Any CPU + {EE5C62AF-B6B7-4CF9-895F-60ADC1801175}.Release|x64.ActiveCfg = Release|Any CPU + {EE5C62AF-B6B7-4CF9-895F-60ADC1801175}.Release|x64.Build.0 = Release|Any CPU + {EE5C62AF-B6B7-4CF9-895F-60ADC1801175}.Release|x86.ActiveCfg = Release|Any CPU + {EE5C62AF-B6B7-4CF9-895F-60ADC1801175}.Release|x86.Build.0 = Release|Any CPU + {83DD339B-EA58-4380-8994-02FA6708CC7A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {83DD339B-EA58-4380-8994-02FA6708CC7A}.Debug|Any CPU.Build.0 = Debug|Any CPU + {83DD339B-EA58-4380-8994-02FA6708CC7A}.Debug|ARM.ActiveCfg = Debug|Any CPU + {83DD339B-EA58-4380-8994-02FA6708CC7A}.Debug|ARM.Build.0 = Debug|Any CPU + {83DD339B-EA58-4380-8994-02FA6708CC7A}.Debug|ARM64.ActiveCfg = Debug|Any CPU + {83DD339B-EA58-4380-8994-02FA6708CC7A}.Debug|ARM64.Build.0 = Debug|Any CPU + {83DD339B-EA58-4380-8994-02FA6708CC7A}.Debug|x64.ActiveCfg = Debug|Any CPU + {83DD339B-EA58-4380-8994-02FA6708CC7A}.Debug|x64.Build.0 = Debug|Any CPU + {83DD339B-EA58-4380-8994-02FA6708CC7A}.Debug|x86.ActiveCfg = Debug|Any CPU + {83DD339B-EA58-4380-8994-02FA6708CC7A}.Debug|x86.Build.0 = Debug|Any CPU + {83DD339B-EA58-4380-8994-02FA6708CC7A}.Release|Any CPU.ActiveCfg = Release|Any CPU + {83DD339B-EA58-4380-8994-02FA6708CC7A}.Release|Any CPU.Build.0 = Release|Any CPU + {83DD339B-EA58-4380-8994-02FA6708CC7A}.Release|ARM.ActiveCfg = Release|Any CPU + {83DD339B-EA58-4380-8994-02FA6708CC7A}.Release|ARM.Build.0 = Release|Any CPU + {83DD339B-EA58-4380-8994-02FA6708CC7A}.Release|ARM64.ActiveCfg = Release|Any CPU + {83DD339B-EA58-4380-8994-02FA6708CC7A}.Release|ARM64.Build.0 = Release|Any CPU + {83DD339B-EA58-4380-8994-02FA6708CC7A}.Release|x64.ActiveCfg = Release|Any CPU + {83DD339B-EA58-4380-8994-02FA6708CC7A}.Release|x64.Build.0 = Release|Any CPU + {83DD339B-EA58-4380-8994-02FA6708CC7A}.Release|x86.ActiveCfg = Release|Any CPU + {83DD339B-EA58-4380-8994-02FA6708CC7A}.Release|x86.Build.0 = Release|Any CPU + {6832083A-A832-4DF4-B3F7-C83D1F51BB95}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {6832083A-A832-4DF4-B3F7-C83D1F51BB95}.Debug|Any CPU.Build.0 = Debug|Any CPU + {6832083A-A832-4DF4-B3F7-C83D1F51BB95}.Debug|ARM.ActiveCfg = Debug|Any CPU + {6832083A-A832-4DF4-B3F7-C83D1F51BB95}.Debug|ARM.Build.0 = Debug|Any CPU + {6832083A-A832-4DF4-B3F7-C83D1F51BB95}.Debug|ARM64.ActiveCfg = Debug|Any CPU + {6832083A-A832-4DF4-B3F7-C83D1F51BB95}.Debug|ARM64.Build.0 = Debug|Any CPU + {6832083A-A832-4DF4-B3F7-C83D1F51BB95}.Debug|x64.ActiveCfg = Debug|Any CPU + {6832083A-A832-4DF4-B3F7-C83D1F51BB95}.Debug|x64.Build.0 = Debug|Any CPU + {6832083A-A832-4DF4-B3F7-C83D1F51BB95}.Debug|x86.ActiveCfg = Debug|Any CPU + {6832083A-A832-4DF4-B3F7-C83D1F51BB95}.Debug|x86.Build.0 = Debug|Any CPU + {6832083A-A832-4DF4-B3F7-C83D1F51BB95}.Release|Any CPU.ActiveCfg = Release|Any CPU + {6832083A-A832-4DF4-B3F7-C83D1F51BB95}.Release|Any CPU.Build.0 = Release|Any CPU + {6832083A-A832-4DF4-B3F7-C83D1F51BB95}.Release|ARM.ActiveCfg = Release|Any CPU + {6832083A-A832-4DF4-B3F7-C83D1F51BB95}.Release|ARM.Build.0 = Release|Any CPU + {6832083A-A832-4DF4-B3F7-C83D1F51BB95}.Release|ARM64.ActiveCfg = Release|Any CPU + {6832083A-A832-4DF4-B3F7-C83D1F51BB95}.Release|ARM64.Build.0 = Release|Any CPU + {6832083A-A832-4DF4-B3F7-C83D1F51BB95}.Release|x64.ActiveCfg = Release|Any CPU + {6832083A-A832-4DF4-B3F7-C83D1F51BB95}.Release|x64.Build.0 = Release|Any CPU + {6832083A-A832-4DF4-B3F7-C83D1F51BB95}.Release|x86.ActiveCfg = Release|Any CPU + {6832083A-A832-4DF4-B3F7-C83D1F51BB95}.Release|x86.Build.0 = Release|Any CPU + {18D92F2F-046E-4A35-83FC-476B097BB8B7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {18D92F2F-046E-4A35-83FC-476B097BB8B7}.Debug|Any CPU.Build.0 = Debug|Any CPU + {18D92F2F-046E-4A35-83FC-476B097BB8B7}.Debug|ARM.ActiveCfg = Debug|ARM + {18D92F2F-046E-4A35-83FC-476B097BB8B7}.Debug|ARM.Build.0 = Debug|ARM + {18D92F2F-046E-4A35-83FC-476B097BB8B7}.Debug|ARM64.ActiveCfg = Debug|ARM64 + {18D92F2F-046E-4A35-83FC-476B097BB8B7}.Debug|ARM64.Build.0 = Debug|ARM64 + {18D92F2F-046E-4A35-83FC-476B097BB8B7}.Debug|x64.ActiveCfg = Debug|x64 + {18D92F2F-046E-4A35-83FC-476B097BB8B7}.Debug|x64.Build.0 = Debug|x64 + {18D92F2F-046E-4A35-83FC-476B097BB8B7}.Debug|x86.ActiveCfg = Debug|x86 + {18D92F2F-046E-4A35-83FC-476B097BB8B7}.Debug|x86.Build.0 = Debug|x86 + {18D92F2F-046E-4A35-83FC-476B097BB8B7}.Release|Any CPU.ActiveCfg = Release|Any CPU + {18D92F2F-046E-4A35-83FC-476B097BB8B7}.Release|Any CPU.Build.0 = Release|Any CPU + {18D92F2F-046E-4A35-83FC-476B097BB8B7}.Release|ARM.ActiveCfg = Release|ARM + {18D92F2F-046E-4A35-83FC-476B097BB8B7}.Release|ARM.Build.0 = Release|ARM + {18D92F2F-046E-4A35-83FC-476B097BB8B7}.Release|ARM64.ActiveCfg = Release|ARM64 + {18D92F2F-046E-4A35-83FC-476B097BB8B7}.Release|ARM64.Build.0 = Release|ARM64 + {18D92F2F-046E-4A35-83FC-476B097BB8B7}.Release|x64.ActiveCfg = Release|x64 + {18D92F2F-046E-4A35-83FC-476B097BB8B7}.Release|x64.Build.0 = Release|x64 + {18D92F2F-046E-4A35-83FC-476B097BB8B7}.Release|x86.ActiveCfg = Release|x86 + {18D92F2F-046E-4A35-83FC-476B097BB8B7}.Release|x86.Build.0 = Release|x86 + {A87244E1-94AB-40E6-A32B-1972429FBCB2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {A87244E1-94AB-40E6-A32B-1972429FBCB2}.Debug|Any CPU.Build.0 = Debug|Any CPU + {A87244E1-94AB-40E6-A32B-1972429FBCB2}.Debug|ARM.ActiveCfg = Debug|ARM + {A87244E1-94AB-40E6-A32B-1972429FBCB2}.Debug|ARM.Build.0 = Debug|ARM + {A87244E1-94AB-40E6-A32B-1972429FBCB2}.Debug|ARM64.ActiveCfg = Debug|ARM64 + {A87244E1-94AB-40E6-A32B-1972429FBCB2}.Debug|ARM64.Build.0 = Debug|ARM64 + {A87244E1-94AB-40E6-A32B-1972429FBCB2}.Debug|x64.ActiveCfg = Debug|x64 + {A87244E1-94AB-40E6-A32B-1972429FBCB2}.Debug|x64.Build.0 = Debug|x64 + {A87244E1-94AB-40E6-A32B-1972429FBCB2}.Debug|x86.ActiveCfg = Debug|x86 + {A87244E1-94AB-40E6-A32B-1972429FBCB2}.Debug|x86.Build.0 = Debug|x86 + {A87244E1-94AB-40E6-A32B-1972429FBCB2}.Release|Any CPU.ActiveCfg = Release|Any CPU + {A87244E1-94AB-40E6-A32B-1972429FBCB2}.Release|Any CPU.Build.0 = Release|Any CPU + {A87244E1-94AB-40E6-A32B-1972429FBCB2}.Release|ARM.ActiveCfg = Release|ARM + {A87244E1-94AB-40E6-A32B-1972429FBCB2}.Release|ARM.Build.0 = Release|ARM + {A87244E1-94AB-40E6-A32B-1972429FBCB2}.Release|ARM64.ActiveCfg = Release|ARM64 + {A87244E1-94AB-40E6-A32B-1972429FBCB2}.Release|ARM64.Build.0 = Release|ARM64 + {A87244E1-94AB-40E6-A32B-1972429FBCB2}.Release|x64.ActiveCfg = Release|x64 + {A87244E1-94AB-40E6-A32B-1972429FBCB2}.Release|x64.Build.0 = Release|x64 + {A87244E1-94AB-40E6-A32B-1972429FBCB2}.Release|x86.ActiveCfg = Release|x86 + {A87244E1-94AB-40E6-A32B-1972429FBCB2}.Release|x86.Build.0 = Release|x86 + {81B36A8C-E9A5-46F1-B803-B4D329C6E4B4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {81B36A8C-E9A5-46F1-B803-B4D329C6E4B4}.Debug|Any CPU.Build.0 = Debug|Any CPU + {81B36A8C-E9A5-46F1-B803-B4D329C6E4B4}.Debug|ARM.ActiveCfg = Debug|Any CPU + {81B36A8C-E9A5-46F1-B803-B4D329C6E4B4}.Debug|ARM.Build.0 = Debug|Any CPU + {81B36A8C-E9A5-46F1-B803-B4D329C6E4B4}.Debug|ARM64.ActiveCfg = Debug|Any CPU + {81B36A8C-E9A5-46F1-B803-B4D329C6E4B4}.Debug|ARM64.Build.0 = Debug|Any CPU + {81B36A8C-E9A5-46F1-B803-B4D329C6E4B4}.Debug|x64.ActiveCfg = Debug|Any CPU + {81B36A8C-E9A5-46F1-B803-B4D329C6E4B4}.Debug|x64.Build.0 = Debug|Any CPU + {81B36A8C-E9A5-46F1-B803-B4D329C6E4B4}.Debug|x86.ActiveCfg = Debug|Any CPU + {81B36A8C-E9A5-46F1-B803-B4D329C6E4B4}.Debug|x86.Build.0 = Debug|Any CPU + {81B36A8C-E9A5-46F1-B803-B4D329C6E4B4}.Release|Any CPU.ActiveCfg = Release|Any CPU + {81B36A8C-E9A5-46F1-B803-B4D329C6E4B4}.Release|Any CPU.Build.0 = Release|Any CPU + {81B36A8C-E9A5-46F1-B803-B4D329C6E4B4}.Release|ARM.ActiveCfg = Release|Any CPU + {81B36A8C-E9A5-46F1-B803-B4D329C6E4B4}.Release|ARM.Build.0 = Release|Any CPU + {81B36A8C-E9A5-46F1-B803-B4D329C6E4B4}.Release|ARM64.ActiveCfg = Release|Any CPU + {81B36A8C-E9A5-46F1-B803-B4D329C6E4B4}.Release|ARM64.Build.0 = Release|Any CPU + {81B36A8C-E9A5-46F1-B803-B4D329C6E4B4}.Release|x64.ActiveCfg = Release|Any CPU + {81B36A8C-E9A5-46F1-B803-B4D329C6E4B4}.Release|x64.Build.0 = Release|Any CPU + {81B36A8C-E9A5-46F1-B803-B4D329C6E4B4}.Release|x86.ActiveCfg = Release|Any CPU + {81B36A8C-E9A5-46F1-B803-B4D329C6E4B4}.Release|x86.Build.0 = Release|Any CPU + {47666400-8C76-4DEC-9387-385DA31187C9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {47666400-8C76-4DEC-9387-385DA31187C9}.Debug|Any CPU.Build.0 = Debug|Any CPU + {47666400-8C76-4DEC-9387-385DA31187C9}.Debug|ARM.ActiveCfg = Debug|Any CPU + {47666400-8C76-4DEC-9387-385DA31187C9}.Debug|ARM.Build.0 = Debug|Any CPU + {47666400-8C76-4DEC-9387-385DA31187C9}.Debug|ARM64.ActiveCfg = Debug|Any CPU + {47666400-8C76-4DEC-9387-385DA31187C9}.Debug|ARM64.Build.0 = Debug|Any CPU + {47666400-8C76-4DEC-9387-385DA31187C9}.Debug|x64.ActiveCfg = Debug|Any CPU + {47666400-8C76-4DEC-9387-385DA31187C9}.Debug|x64.Build.0 = Debug|Any CPU + {47666400-8C76-4DEC-9387-385DA31187C9}.Debug|x86.ActiveCfg = Debug|Any CPU + {47666400-8C76-4DEC-9387-385DA31187C9}.Debug|x86.Build.0 = Debug|Any CPU + {47666400-8C76-4DEC-9387-385DA31187C9}.Release|Any CPU.ActiveCfg = Release|Any CPU + {47666400-8C76-4DEC-9387-385DA31187C9}.Release|Any CPU.Build.0 = Release|Any CPU + {47666400-8C76-4DEC-9387-385DA31187C9}.Release|ARM.ActiveCfg = Release|Any CPU + {47666400-8C76-4DEC-9387-385DA31187C9}.Release|ARM.Build.0 = Release|Any CPU + {47666400-8C76-4DEC-9387-385DA31187C9}.Release|ARM64.ActiveCfg = Release|Any CPU + {47666400-8C76-4DEC-9387-385DA31187C9}.Release|ARM64.Build.0 = Release|Any CPU + {47666400-8C76-4DEC-9387-385DA31187C9}.Release|x64.ActiveCfg = Release|Any CPU + {47666400-8C76-4DEC-9387-385DA31187C9}.Release|x64.Build.0 = Release|Any CPU + {47666400-8C76-4DEC-9387-385DA31187C9}.Release|x86.ActiveCfg = Release|Any CPU + {47666400-8C76-4DEC-9387-385DA31187C9}.Release|x86.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(NestedProjects) = preSolution + {48DF06B5-90AD-4AC6-81F7-B381DB093B09} = {810B7F1B-97AD-4422-93C8-562AD50CE4DB} + {07B6D3B7-0AC0-489B-BF27-588AB6226B1C} = {48DF06B5-90AD-4AC6-81F7-B381DB093B09} + {CA4FE39C-2379-4966-9940-DE738D94E982} = {48DF06B5-90AD-4AC6-81F7-B381DB093B09} + {88314412-B020-415B-AEAB-57ADC43B273E} = {232C0E31-606A-48CD-B860-2E9155EAB4C6} + {8776A0BD-DBD1-4F11-A022-400D044FF618} = {232C0E31-606A-48CD-B860-2E9155EAB4C6} + {9E61CC56-43F9-489C-AAAF-BD5EC69367C4} = {232C0E31-606A-48CD-B860-2E9155EAB4C6} + {1AF20FDC-36D5-450E-9461-0F78AEB89716} = {82E6DD11-BBC1-4BFC-A244-593EE27BF250} + {0187840F-8D1A-4198-B5BF-8B05CADB4554} = {232C0E31-606A-48CD-B860-2E9155EAB4C6} + {B9CD5845-2DE3-40FC-BC0F-27C406929551} = {45B5DFF9-9F13-4BF3-B561-8ABBDFE5C237} + {C4DBF0B2-C71A-4528-BF15-B0A7A25B2510} = {232C0E31-606A-48CD-B860-2E9155EAB4C6} + {3046F10C-456E-43D3-8F13-3855EE2D9344} = {8B044ABB-C591-44E5-B7C6-B2F7262F5C1D} + {9C3E4744-E870-490B-A7DB-1473A0DB355F} = {8B044ABB-C591-44E5-B7C6-B2F7262F5C1D} + {B9B87F56-8AFE-4F6E-B2A9-AFA8D118FC67} = {8B044ABB-C591-44E5-B7C6-B2F7262F5C1D} + {CD9930DC-36A8-45FC-8824-C8B3FA873FAD} = {82E6DD11-BBC1-4BFC-A244-593EE27BF250} + {1E466E0C-5218-4892-9FDD-0E777795C545} = {45B5DFF9-9F13-4BF3-B561-8ABBDFE5C237} + {EE5C62AF-B6B7-4CF9-895F-60ADC1801175} = {45B5DFF9-9F13-4BF3-B561-8ABBDFE5C237} + {83DD339B-EA58-4380-8994-02FA6708CC7A} = {45B5DFF9-9F13-4BF3-B561-8ABBDFE5C237} + {6832083A-A832-4DF4-B3F7-C83D1F51BB95} = {48DF06B5-90AD-4AC6-81F7-B381DB093B09} + {18D92F2F-046E-4A35-83FC-476B097BB8B7} = {45B5DFF9-9F13-4BF3-B561-8ABBDFE5C237} + {A87244E1-94AB-40E6-A32B-1972429FBCB2} = {45B5DFF9-9F13-4BF3-B561-8ABBDFE5C237} + {81B36A8C-E9A5-46F1-B803-B4D329C6E4B4} = {45B5DFF9-9F13-4BF3-B561-8ABBDFE5C237} + {47666400-8C76-4DEC-9387-385DA31187C9} = {232C0E31-606A-48CD-B860-2E9155EAB4C6} + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {563225C5-4D56-446B-9ADE-09D3F0DE7963} + EndGlobalSection +EndGlobal diff --git a/Bili.Workspace.sln b/Bili.Workspace.sln new file mode 100644 index 000000000..f0941329f --- /dev/null +++ b/Bili.Workspace.sln @@ -0,0 +1,384 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 17 +VisualStudioVersion = 17.5.33103.201 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Workspace", "src\Workspace\Workspace.csproj", "{A45EA818-8113-4B71-9AC6-F24A6DA1A3FA}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Lib", "Lib", "{1E90315C-2A79-4D73-9685-0B20BAFAB5B1}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DI.Container", "src\Lib\DI.Container\DI.Container.csproj", "{48FD15F1-014C-40CF-A047-272E88C3E0BC}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DI.Workspace", "src\Lib\DI.Workspace\DI.Workspace.csproj", "{B14A04D3-50C8-4772-BC47-A1915BE035BA}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Lib.Interfaces", "src\Lib\Lib.Interfaces\Lib.Interfaces.csproj", "{A658BF84-5475-4281-AB3F-3FA0E50C9448}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Lib.Implementation", "src\Lib\Lib.Implementation\Lib.Implementation.csproj", "{535E46AD-2A7D-4F5E-AA73-A838BD56EB0C}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SignIn.Workspace", "src\Lib\SignIn.Workspace\SignIn.Workspace.csproj", "{A43A39E8-645D-4C2C-8DAD-9848F43D71B0}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Adapter", "Adapter", "{3283AE12-D01B-4686-9E58-6D08AACB7481}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Adapter.Interfaces", "src\Adapter\Adapter.Interfaces\Adapter.Interfaces.csproj", "{E0B7CCA5-5943-4249-89D7-113A71B11C12}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Adapter.Implementation", "src\Adapter\Adapter.Implementation\Adapter.Implementation.csproj", "{3DA8880C-6F8B-4CC1-96FE-4E65C848C671}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Models", "Models", "{793C8FAF-1F4A-4A71-B757-56FD88C5BD36}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Models.BiliBili", "src\Models\Models.BiliBili\Models.BiliBili.csproj", "{61D3B473-1A52-4CC3-9067-5BEADEEC9ABF}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Models.Data", "src\Models\Models.Data\Models.Data.csproj", "{9F7CB0AD-6DC9-4B48-857D-47E445D84147}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Models.Enums", "src\Models\Models.Enums\Models.Enums.csproj", "{17933DBF-2508-4CB3-BB02-586F54DB631A}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Models.gRPC", "src\Models\Models.gRPC\Models.gRPC.csproj", "{59565E33-FDDA-4F92-95DB-026FCA848010}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Models.Workspace", "src\Models\Models.Workspace\Models.Workspace.csproj", "{501963E1-1D15-46CC-9257-96C7B286E6EF}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Models.App", "src\Models\Models.App\Models.App.csproj", "{1C5BCF47-E378-48EB-8EB4-F8981B188BE0}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Toolkit", "Toolkit", "{0CF4A361-CB4D-4178-B474-0FB3271C80CC}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Toolkit.Interfaces", "src\Utilities\Toolkit\Toolkit.Interfaces\Toolkit.Interfaces.csproj", "{94030FE5-8385-4C79-A105-78CA2133CEB4}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Toolkit.Workspace", "src\Utilities\Toolkit\Toolkit.Workspace\Toolkit.Workspace.csproj", "{7C6062A5-B55D-453A-B2B8-ECEC6D32DCD5}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "ViewModels", "ViewModels", "{4A9E18B1-B52E-4A8E-AF0E-792D24752D16}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ViewModels.Interfaces", "src\ViewModels\ViewModels.Interfaces\ViewModels.Interfaces.csproj", "{6E98CB19-57D6-4DD7-A8AE-6E098166D307}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ViewModels.Workspace", "src\ViewModels\ViewModels.Workspace\ViewModels.Workspace.csproj", "{4EF06119-5D9C-43D5-836D-8F307C271132}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Debug|ARM64 = Debug|ARM64 + Debug|x64 = Debug|x64 + Debug|x86 = Debug|x86 + Release|Any CPU = Release|Any CPU + Release|ARM64 = Release|ARM64 + Release|x64 = Release|x64 + Release|x86 = Release|x86 + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {A45EA818-8113-4B71-9AC6-F24A6DA1A3FA}.Debug|Any CPU.ActiveCfg = Debug|x64 + {A45EA818-8113-4B71-9AC6-F24A6DA1A3FA}.Debug|Any CPU.Build.0 = Debug|x64 + {A45EA818-8113-4B71-9AC6-F24A6DA1A3FA}.Debug|Any CPU.Deploy.0 = Debug|x64 + {A45EA818-8113-4B71-9AC6-F24A6DA1A3FA}.Debug|ARM64.ActiveCfg = Debug|ARM64 + {A45EA818-8113-4B71-9AC6-F24A6DA1A3FA}.Debug|ARM64.Build.0 = Debug|ARM64 + {A45EA818-8113-4B71-9AC6-F24A6DA1A3FA}.Debug|ARM64.Deploy.0 = Debug|ARM64 + {A45EA818-8113-4B71-9AC6-F24A6DA1A3FA}.Debug|x64.ActiveCfg = Debug|x64 + {A45EA818-8113-4B71-9AC6-F24A6DA1A3FA}.Debug|x64.Build.0 = Debug|x64 + {A45EA818-8113-4B71-9AC6-F24A6DA1A3FA}.Debug|x64.Deploy.0 = Debug|x64 + {A45EA818-8113-4B71-9AC6-F24A6DA1A3FA}.Debug|x86.ActiveCfg = Debug|x86 + {A45EA818-8113-4B71-9AC6-F24A6DA1A3FA}.Debug|x86.Build.0 = Debug|x86 + {A45EA818-8113-4B71-9AC6-F24A6DA1A3FA}.Release|Any CPU.ActiveCfg = Release|x64 + {A45EA818-8113-4B71-9AC6-F24A6DA1A3FA}.Release|Any CPU.Build.0 = Release|x64 + {A45EA818-8113-4B71-9AC6-F24A6DA1A3FA}.Release|Any CPU.Deploy.0 = Release|x64 + {A45EA818-8113-4B71-9AC6-F24A6DA1A3FA}.Release|ARM64.ActiveCfg = Release|ARM64 + {A45EA818-8113-4B71-9AC6-F24A6DA1A3FA}.Release|ARM64.Build.0 = Release|ARM64 + {A45EA818-8113-4B71-9AC6-F24A6DA1A3FA}.Release|ARM64.Deploy.0 = Release|ARM64 + {A45EA818-8113-4B71-9AC6-F24A6DA1A3FA}.Release|x64.ActiveCfg = Release|x64 + {A45EA818-8113-4B71-9AC6-F24A6DA1A3FA}.Release|x64.Build.0 = Release|x64 + {A45EA818-8113-4B71-9AC6-F24A6DA1A3FA}.Release|x64.Deploy.0 = Release|x64 + {A45EA818-8113-4B71-9AC6-F24A6DA1A3FA}.Release|x86.ActiveCfg = Release|x86 + {A45EA818-8113-4B71-9AC6-F24A6DA1A3FA}.Release|x86.Build.0 = Release|x86 + {48FD15F1-014C-40CF-A047-272E88C3E0BC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {48FD15F1-014C-40CF-A047-272E88C3E0BC}.Debug|Any CPU.Build.0 = Debug|Any CPU + {48FD15F1-014C-40CF-A047-272E88C3E0BC}.Debug|ARM64.ActiveCfg = Debug|Any CPU + {48FD15F1-014C-40CF-A047-272E88C3E0BC}.Debug|ARM64.Build.0 = Debug|Any CPU + {48FD15F1-014C-40CF-A047-272E88C3E0BC}.Debug|x64.ActiveCfg = Debug|Any CPU + {48FD15F1-014C-40CF-A047-272E88C3E0BC}.Debug|x64.Build.0 = Debug|Any CPU + {48FD15F1-014C-40CF-A047-272E88C3E0BC}.Debug|x86.ActiveCfg = Debug|Any CPU + {48FD15F1-014C-40CF-A047-272E88C3E0BC}.Debug|x86.Build.0 = Debug|Any CPU + {48FD15F1-014C-40CF-A047-272E88C3E0BC}.Release|Any CPU.ActiveCfg = Release|Any CPU + {48FD15F1-014C-40CF-A047-272E88C3E0BC}.Release|Any CPU.Build.0 = Release|Any CPU + {48FD15F1-014C-40CF-A047-272E88C3E0BC}.Release|ARM64.ActiveCfg = Release|Any CPU + {48FD15F1-014C-40CF-A047-272E88C3E0BC}.Release|ARM64.Build.0 = Release|Any CPU + {48FD15F1-014C-40CF-A047-272E88C3E0BC}.Release|x64.ActiveCfg = Release|Any CPU + {48FD15F1-014C-40CF-A047-272E88C3E0BC}.Release|x64.Build.0 = Release|Any CPU + {48FD15F1-014C-40CF-A047-272E88C3E0BC}.Release|x86.ActiveCfg = Release|Any CPU + {48FD15F1-014C-40CF-A047-272E88C3E0BC}.Release|x86.Build.0 = Release|Any CPU + {B14A04D3-50C8-4772-BC47-A1915BE035BA}.Debug|Any CPU.ActiveCfg = Debug|x64 + {B14A04D3-50C8-4772-BC47-A1915BE035BA}.Debug|Any CPU.Build.0 = Debug|x64 + {B14A04D3-50C8-4772-BC47-A1915BE035BA}.Debug|ARM64.ActiveCfg = Debug|arm64 + {B14A04D3-50C8-4772-BC47-A1915BE035BA}.Debug|ARM64.Build.0 = Debug|arm64 + {B14A04D3-50C8-4772-BC47-A1915BE035BA}.Debug|x64.ActiveCfg = Debug|x64 + {B14A04D3-50C8-4772-BC47-A1915BE035BA}.Debug|x64.Build.0 = Debug|x64 + {B14A04D3-50C8-4772-BC47-A1915BE035BA}.Debug|x86.ActiveCfg = Debug|x86 + {B14A04D3-50C8-4772-BC47-A1915BE035BA}.Debug|x86.Build.0 = Debug|x86 + {B14A04D3-50C8-4772-BC47-A1915BE035BA}.Release|Any CPU.ActiveCfg = Release|x64 + {B14A04D3-50C8-4772-BC47-A1915BE035BA}.Release|Any CPU.Build.0 = Release|x64 + {B14A04D3-50C8-4772-BC47-A1915BE035BA}.Release|ARM64.ActiveCfg = Release|arm64 + {B14A04D3-50C8-4772-BC47-A1915BE035BA}.Release|ARM64.Build.0 = Release|arm64 + {B14A04D3-50C8-4772-BC47-A1915BE035BA}.Release|x64.ActiveCfg = Release|x64 + {B14A04D3-50C8-4772-BC47-A1915BE035BA}.Release|x64.Build.0 = Release|x64 + {B14A04D3-50C8-4772-BC47-A1915BE035BA}.Release|x86.ActiveCfg = Release|x86 + {B14A04D3-50C8-4772-BC47-A1915BE035BA}.Release|x86.Build.0 = Release|x86 + {A658BF84-5475-4281-AB3F-3FA0E50C9448}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {A658BF84-5475-4281-AB3F-3FA0E50C9448}.Debug|Any CPU.Build.0 = Debug|Any CPU + {A658BF84-5475-4281-AB3F-3FA0E50C9448}.Debug|ARM64.ActiveCfg = Debug|Any CPU + {A658BF84-5475-4281-AB3F-3FA0E50C9448}.Debug|ARM64.Build.0 = Debug|Any CPU + {A658BF84-5475-4281-AB3F-3FA0E50C9448}.Debug|x64.ActiveCfg = Debug|Any CPU + {A658BF84-5475-4281-AB3F-3FA0E50C9448}.Debug|x64.Build.0 = Debug|Any CPU + {A658BF84-5475-4281-AB3F-3FA0E50C9448}.Debug|x86.ActiveCfg = Debug|Any CPU + {A658BF84-5475-4281-AB3F-3FA0E50C9448}.Debug|x86.Build.0 = Debug|Any CPU + {A658BF84-5475-4281-AB3F-3FA0E50C9448}.Release|Any CPU.ActiveCfg = Release|Any CPU + {A658BF84-5475-4281-AB3F-3FA0E50C9448}.Release|Any CPU.Build.0 = Release|Any CPU + {A658BF84-5475-4281-AB3F-3FA0E50C9448}.Release|ARM64.ActiveCfg = Release|Any CPU + {A658BF84-5475-4281-AB3F-3FA0E50C9448}.Release|ARM64.Build.0 = Release|Any CPU + {A658BF84-5475-4281-AB3F-3FA0E50C9448}.Release|x64.ActiveCfg = Release|Any CPU + {A658BF84-5475-4281-AB3F-3FA0E50C9448}.Release|x64.Build.0 = Release|Any CPU + {A658BF84-5475-4281-AB3F-3FA0E50C9448}.Release|x86.ActiveCfg = Release|Any CPU + {A658BF84-5475-4281-AB3F-3FA0E50C9448}.Release|x86.Build.0 = Release|Any CPU + {535E46AD-2A7D-4F5E-AA73-A838BD56EB0C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {535E46AD-2A7D-4F5E-AA73-A838BD56EB0C}.Debug|Any CPU.Build.0 = Debug|Any CPU + {535E46AD-2A7D-4F5E-AA73-A838BD56EB0C}.Debug|ARM64.ActiveCfg = Debug|Any CPU + {535E46AD-2A7D-4F5E-AA73-A838BD56EB0C}.Debug|ARM64.Build.0 = Debug|Any CPU + {535E46AD-2A7D-4F5E-AA73-A838BD56EB0C}.Debug|x64.ActiveCfg = Debug|Any CPU + {535E46AD-2A7D-4F5E-AA73-A838BD56EB0C}.Debug|x64.Build.0 = Debug|Any CPU + {535E46AD-2A7D-4F5E-AA73-A838BD56EB0C}.Debug|x86.ActiveCfg = Debug|Any CPU + {535E46AD-2A7D-4F5E-AA73-A838BD56EB0C}.Debug|x86.Build.0 = Debug|Any CPU + {535E46AD-2A7D-4F5E-AA73-A838BD56EB0C}.Release|Any CPU.ActiveCfg = Release|Any CPU + {535E46AD-2A7D-4F5E-AA73-A838BD56EB0C}.Release|Any CPU.Build.0 = Release|Any CPU + {535E46AD-2A7D-4F5E-AA73-A838BD56EB0C}.Release|ARM64.ActiveCfg = Release|Any CPU + {535E46AD-2A7D-4F5E-AA73-A838BD56EB0C}.Release|ARM64.Build.0 = Release|Any CPU + {535E46AD-2A7D-4F5E-AA73-A838BD56EB0C}.Release|x64.ActiveCfg = Release|Any CPU + {535E46AD-2A7D-4F5E-AA73-A838BD56EB0C}.Release|x64.Build.0 = Release|Any CPU + {535E46AD-2A7D-4F5E-AA73-A838BD56EB0C}.Release|x86.ActiveCfg = Release|Any CPU + {535E46AD-2A7D-4F5E-AA73-A838BD56EB0C}.Release|x86.Build.0 = Release|Any CPU + {A43A39E8-645D-4C2C-8DAD-9848F43D71B0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {A43A39E8-645D-4C2C-8DAD-9848F43D71B0}.Debug|Any CPU.Build.0 = Debug|Any CPU + {A43A39E8-645D-4C2C-8DAD-9848F43D71B0}.Debug|ARM64.ActiveCfg = Debug|Any CPU + {A43A39E8-645D-4C2C-8DAD-9848F43D71B0}.Debug|ARM64.Build.0 = Debug|Any CPU + {A43A39E8-645D-4C2C-8DAD-9848F43D71B0}.Debug|x64.ActiveCfg = Debug|Any CPU + {A43A39E8-645D-4C2C-8DAD-9848F43D71B0}.Debug|x64.Build.0 = Debug|Any CPU + {A43A39E8-645D-4C2C-8DAD-9848F43D71B0}.Debug|x86.ActiveCfg = Debug|Any CPU + {A43A39E8-645D-4C2C-8DAD-9848F43D71B0}.Debug|x86.Build.0 = Debug|Any CPU + {A43A39E8-645D-4C2C-8DAD-9848F43D71B0}.Release|Any CPU.ActiveCfg = Release|Any CPU + {A43A39E8-645D-4C2C-8DAD-9848F43D71B0}.Release|Any CPU.Build.0 = Release|Any CPU + {A43A39E8-645D-4C2C-8DAD-9848F43D71B0}.Release|ARM64.ActiveCfg = Release|Any CPU + {A43A39E8-645D-4C2C-8DAD-9848F43D71B0}.Release|ARM64.Build.0 = Release|Any CPU + {A43A39E8-645D-4C2C-8DAD-9848F43D71B0}.Release|x64.ActiveCfg = Release|Any CPU + {A43A39E8-645D-4C2C-8DAD-9848F43D71B0}.Release|x64.Build.0 = Release|Any CPU + {A43A39E8-645D-4C2C-8DAD-9848F43D71B0}.Release|x86.ActiveCfg = Release|Any CPU + {A43A39E8-645D-4C2C-8DAD-9848F43D71B0}.Release|x86.Build.0 = Release|Any CPU + {E0B7CCA5-5943-4249-89D7-113A71B11C12}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {E0B7CCA5-5943-4249-89D7-113A71B11C12}.Debug|Any CPU.Build.0 = Debug|Any CPU + {E0B7CCA5-5943-4249-89D7-113A71B11C12}.Debug|ARM64.ActiveCfg = Debug|Any CPU + {E0B7CCA5-5943-4249-89D7-113A71B11C12}.Debug|ARM64.Build.0 = Debug|Any CPU + {E0B7CCA5-5943-4249-89D7-113A71B11C12}.Debug|x64.ActiveCfg = Debug|Any CPU + {E0B7CCA5-5943-4249-89D7-113A71B11C12}.Debug|x64.Build.0 = Debug|Any CPU + {E0B7CCA5-5943-4249-89D7-113A71B11C12}.Debug|x86.ActiveCfg = Debug|Any CPU + {E0B7CCA5-5943-4249-89D7-113A71B11C12}.Debug|x86.Build.0 = Debug|Any CPU + {E0B7CCA5-5943-4249-89D7-113A71B11C12}.Release|Any CPU.ActiveCfg = Release|Any CPU + {E0B7CCA5-5943-4249-89D7-113A71B11C12}.Release|Any CPU.Build.0 = Release|Any CPU + {E0B7CCA5-5943-4249-89D7-113A71B11C12}.Release|ARM64.ActiveCfg = Release|Any CPU + {E0B7CCA5-5943-4249-89D7-113A71B11C12}.Release|ARM64.Build.0 = Release|Any CPU + {E0B7CCA5-5943-4249-89D7-113A71B11C12}.Release|x64.ActiveCfg = Release|Any CPU + {E0B7CCA5-5943-4249-89D7-113A71B11C12}.Release|x64.Build.0 = Release|Any CPU + {E0B7CCA5-5943-4249-89D7-113A71B11C12}.Release|x86.ActiveCfg = Release|Any CPU + {E0B7CCA5-5943-4249-89D7-113A71B11C12}.Release|x86.Build.0 = Release|Any CPU + {3DA8880C-6F8B-4CC1-96FE-4E65C848C671}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {3DA8880C-6F8B-4CC1-96FE-4E65C848C671}.Debug|Any CPU.Build.0 = Debug|Any CPU + {3DA8880C-6F8B-4CC1-96FE-4E65C848C671}.Debug|ARM64.ActiveCfg = Debug|Any CPU + {3DA8880C-6F8B-4CC1-96FE-4E65C848C671}.Debug|ARM64.Build.0 = Debug|Any CPU + {3DA8880C-6F8B-4CC1-96FE-4E65C848C671}.Debug|x64.ActiveCfg = Debug|Any CPU + {3DA8880C-6F8B-4CC1-96FE-4E65C848C671}.Debug|x64.Build.0 = Debug|Any CPU + {3DA8880C-6F8B-4CC1-96FE-4E65C848C671}.Debug|x86.ActiveCfg = Debug|Any CPU + {3DA8880C-6F8B-4CC1-96FE-4E65C848C671}.Debug|x86.Build.0 = Debug|Any CPU + {3DA8880C-6F8B-4CC1-96FE-4E65C848C671}.Release|Any CPU.ActiveCfg = Release|Any CPU + {3DA8880C-6F8B-4CC1-96FE-4E65C848C671}.Release|Any CPU.Build.0 = Release|Any CPU + {3DA8880C-6F8B-4CC1-96FE-4E65C848C671}.Release|ARM64.ActiveCfg = Release|Any CPU + {3DA8880C-6F8B-4CC1-96FE-4E65C848C671}.Release|ARM64.Build.0 = Release|Any CPU + {3DA8880C-6F8B-4CC1-96FE-4E65C848C671}.Release|x64.ActiveCfg = Release|Any CPU + {3DA8880C-6F8B-4CC1-96FE-4E65C848C671}.Release|x64.Build.0 = Release|Any CPU + {3DA8880C-6F8B-4CC1-96FE-4E65C848C671}.Release|x86.ActiveCfg = Release|Any CPU + {3DA8880C-6F8B-4CC1-96FE-4E65C848C671}.Release|x86.Build.0 = Release|Any CPU + {61D3B473-1A52-4CC3-9067-5BEADEEC9ABF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {61D3B473-1A52-4CC3-9067-5BEADEEC9ABF}.Debug|Any CPU.Build.0 = Debug|Any CPU + {61D3B473-1A52-4CC3-9067-5BEADEEC9ABF}.Debug|ARM64.ActiveCfg = Debug|Any CPU + {61D3B473-1A52-4CC3-9067-5BEADEEC9ABF}.Debug|ARM64.Build.0 = Debug|Any CPU + {61D3B473-1A52-4CC3-9067-5BEADEEC9ABF}.Debug|x64.ActiveCfg = Debug|Any CPU + {61D3B473-1A52-4CC3-9067-5BEADEEC9ABF}.Debug|x64.Build.0 = Debug|Any CPU + {61D3B473-1A52-4CC3-9067-5BEADEEC9ABF}.Debug|x86.ActiveCfg = Debug|Any CPU + {61D3B473-1A52-4CC3-9067-5BEADEEC9ABF}.Debug|x86.Build.0 = Debug|Any CPU + {61D3B473-1A52-4CC3-9067-5BEADEEC9ABF}.Release|Any CPU.ActiveCfg = Release|Any CPU + {61D3B473-1A52-4CC3-9067-5BEADEEC9ABF}.Release|Any CPU.Build.0 = Release|Any CPU + {61D3B473-1A52-4CC3-9067-5BEADEEC9ABF}.Release|ARM64.ActiveCfg = Release|Any CPU + {61D3B473-1A52-4CC3-9067-5BEADEEC9ABF}.Release|ARM64.Build.0 = Release|Any CPU + {61D3B473-1A52-4CC3-9067-5BEADEEC9ABF}.Release|x64.ActiveCfg = Release|Any CPU + {61D3B473-1A52-4CC3-9067-5BEADEEC9ABF}.Release|x64.Build.0 = Release|Any CPU + {61D3B473-1A52-4CC3-9067-5BEADEEC9ABF}.Release|x86.ActiveCfg = Release|Any CPU + {61D3B473-1A52-4CC3-9067-5BEADEEC9ABF}.Release|x86.Build.0 = Release|Any CPU + {9F7CB0AD-6DC9-4B48-857D-47E445D84147}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {9F7CB0AD-6DC9-4B48-857D-47E445D84147}.Debug|Any CPU.Build.0 = Debug|Any CPU + {9F7CB0AD-6DC9-4B48-857D-47E445D84147}.Debug|ARM64.ActiveCfg = Debug|Any CPU + {9F7CB0AD-6DC9-4B48-857D-47E445D84147}.Debug|ARM64.Build.0 = Debug|Any CPU + {9F7CB0AD-6DC9-4B48-857D-47E445D84147}.Debug|x64.ActiveCfg = Debug|Any CPU + {9F7CB0AD-6DC9-4B48-857D-47E445D84147}.Debug|x64.Build.0 = Debug|Any CPU + {9F7CB0AD-6DC9-4B48-857D-47E445D84147}.Debug|x86.ActiveCfg = Debug|Any CPU + {9F7CB0AD-6DC9-4B48-857D-47E445D84147}.Debug|x86.Build.0 = Debug|Any CPU + {9F7CB0AD-6DC9-4B48-857D-47E445D84147}.Release|Any CPU.ActiveCfg = Release|Any CPU + {9F7CB0AD-6DC9-4B48-857D-47E445D84147}.Release|Any CPU.Build.0 = Release|Any CPU + {9F7CB0AD-6DC9-4B48-857D-47E445D84147}.Release|ARM64.ActiveCfg = Release|Any CPU + {9F7CB0AD-6DC9-4B48-857D-47E445D84147}.Release|ARM64.Build.0 = Release|Any CPU + {9F7CB0AD-6DC9-4B48-857D-47E445D84147}.Release|x64.ActiveCfg = Release|Any CPU + {9F7CB0AD-6DC9-4B48-857D-47E445D84147}.Release|x64.Build.0 = Release|Any CPU + {9F7CB0AD-6DC9-4B48-857D-47E445D84147}.Release|x86.ActiveCfg = Release|Any CPU + {9F7CB0AD-6DC9-4B48-857D-47E445D84147}.Release|x86.Build.0 = Release|Any CPU + {17933DBF-2508-4CB3-BB02-586F54DB631A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {17933DBF-2508-4CB3-BB02-586F54DB631A}.Debug|Any CPU.Build.0 = Debug|Any CPU + {17933DBF-2508-4CB3-BB02-586F54DB631A}.Debug|ARM64.ActiveCfg = Debug|Any CPU + {17933DBF-2508-4CB3-BB02-586F54DB631A}.Debug|ARM64.Build.0 = Debug|Any CPU + {17933DBF-2508-4CB3-BB02-586F54DB631A}.Debug|x64.ActiveCfg = Debug|Any CPU + {17933DBF-2508-4CB3-BB02-586F54DB631A}.Debug|x64.Build.0 = Debug|Any CPU + {17933DBF-2508-4CB3-BB02-586F54DB631A}.Debug|x86.ActiveCfg = Debug|Any CPU + {17933DBF-2508-4CB3-BB02-586F54DB631A}.Debug|x86.Build.0 = Debug|Any CPU + {17933DBF-2508-4CB3-BB02-586F54DB631A}.Release|Any CPU.ActiveCfg = Release|Any CPU + {17933DBF-2508-4CB3-BB02-586F54DB631A}.Release|Any CPU.Build.0 = Release|Any CPU + {17933DBF-2508-4CB3-BB02-586F54DB631A}.Release|ARM64.ActiveCfg = Release|Any CPU + {17933DBF-2508-4CB3-BB02-586F54DB631A}.Release|ARM64.Build.0 = Release|Any CPU + {17933DBF-2508-4CB3-BB02-586F54DB631A}.Release|x64.ActiveCfg = Release|Any CPU + {17933DBF-2508-4CB3-BB02-586F54DB631A}.Release|x64.Build.0 = Release|Any CPU + {17933DBF-2508-4CB3-BB02-586F54DB631A}.Release|x86.ActiveCfg = Release|Any CPU + {17933DBF-2508-4CB3-BB02-586F54DB631A}.Release|x86.Build.0 = Release|Any CPU + {59565E33-FDDA-4F92-95DB-026FCA848010}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {59565E33-FDDA-4F92-95DB-026FCA848010}.Debug|Any CPU.Build.0 = Debug|Any CPU + {59565E33-FDDA-4F92-95DB-026FCA848010}.Debug|ARM64.ActiveCfg = Debug|Any CPU + {59565E33-FDDA-4F92-95DB-026FCA848010}.Debug|ARM64.Build.0 = Debug|Any CPU + {59565E33-FDDA-4F92-95DB-026FCA848010}.Debug|x64.ActiveCfg = Debug|Any CPU + {59565E33-FDDA-4F92-95DB-026FCA848010}.Debug|x64.Build.0 = Debug|Any CPU + {59565E33-FDDA-4F92-95DB-026FCA848010}.Debug|x86.ActiveCfg = Debug|Any CPU + {59565E33-FDDA-4F92-95DB-026FCA848010}.Debug|x86.Build.0 = Debug|Any CPU + {59565E33-FDDA-4F92-95DB-026FCA848010}.Release|Any CPU.ActiveCfg = Release|Any CPU + {59565E33-FDDA-4F92-95DB-026FCA848010}.Release|Any CPU.Build.0 = Release|Any CPU + {59565E33-FDDA-4F92-95DB-026FCA848010}.Release|ARM64.ActiveCfg = Release|Any CPU + {59565E33-FDDA-4F92-95DB-026FCA848010}.Release|ARM64.Build.0 = Release|Any CPU + {59565E33-FDDA-4F92-95DB-026FCA848010}.Release|x64.ActiveCfg = Release|Any CPU + {59565E33-FDDA-4F92-95DB-026FCA848010}.Release|x64.Build.0 = Release|Any CPU + {59565E33-FDDA-4F92-95DB-026FCA848010}.Release|x86.ActiveCfg = Release|Any CPU + {59565E33-FDDA-4F92-95DB-026FCA848010}.Release|x86.Build.0 = Release|Any CPU + {501963E1-1D15-46CC-9257-96C7B286E6EF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {501963E1-1D15-46CC-9257-96C7B286E6EF}.Debug|Any CPU.Build.0 = Debug|Any CPU + {501963E1-1D15-46CC-9257-96C7B286E6EF}.Debug|ARM64.ActiveCfg = Debug|Any CPU + {501963E1-1D15-46CC-9257-96C7B286E6EF}.Debug|ARM64.Build.0 = Debug|Any CPU + {501963E1-1D15-46CC-9257-96C7B286E6EF}.Debug|x64.ActiveCfg = Debug|Any CPU + {501963E1-1D15-46CC-9257-96C7B286E6EF}.Debug|x64.Build.0 = Debug|Any CPU + {501963E1-1D15-46CC-9257-96C7B286E6EF}.Debug|x86.ActiveCfg = Debug|Any CPU + {501963E1-1D15-46CC-9257-96C7B286E6EF}.Debug|x86.Build.0 = Debug|Any CPU + {501963E1-1D15-46CC-9257-96C7B286E6EF}.Release|Any CPU.ActiveCfg = Release|Any CPU + {501963E1-1D15-46CC-9257-96C7B286E6EF}.Release|Any CPU.Build.0 = Release|Any CPU + {501963E1-1D15-46CC-9257-96C7B286E6EF}.Release|ARM64.ActiveCfg = Release|Any CPU + {501963E1-1D15-46CC-9257-96C7B286E6EF}.Release|ARM64.Build.0 = Release|Any CPU + {501963E1-1D15-46CC-9257-96C7B286E6EF}.Release|x64.ActiveCfg = Release|Any CPU + {501963E1-1D15-46CC-9257-96C7B286E6EF}.Release|x64.Build.0 = Release|Any CPU + {501963E1-1D15-46CC-9257-96C7B286E6EF}.Release|x86.ActiveCfg = Release|Any CPU + {501963E1-1D15-46CC-9257-96C7B286E6EF}.Release|x86.Build.0 = Release|Any CPU + {1C5BCF47-E378-48EB-8EB4-F8981B188BE0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {1C5BCF47-E378-48EB-8EB4-F8981B188BE0}.Debug|Any CPU.Build.0 = Debug|Any CPU + {1C5BCF47-E378-48EB-8EB4-F8981B188BE0}.Debug|ARM64.ActiveCfg = Debug|Any CPU + {1C5BCF47-E378-48EB-8EB4-F8981B188BE0}.Debug|ARM64.Build.0 = Debug|Any CPU + {1C5BCF47-E378-48EB-8EB4-F8981B188BE0}.Debug|x64.ActiveCfg = Debug|Any CPU + {1C5BCF47-E378-48EB-8EB4-F8981B188BE0}.Debug|x64.Build.0 = Debug|Any CPU + {1C5BCF47-E378-48EB-8EB4-F8981B188BE0}.Debug|x86.ActiveCfg = Debug|Any CPU + {1C5BCF47-E378-48EB-8EB4-F8981B188BE0}.Debug|x86.Build.0 = Debug|Any CPU + {1C5BCF47-E378-48EB-8EB4-F8981B188BE0}.Release|Any CPU.ActiveCfg = Release|Any CPU + {1C5BCF47-E378-48EB-8EB4-F8981B188BE0}.Release|Any CPU.Build.0 = Release|Any CPU + {1C5BCF47-E378-48EB-8EB4-F8981B188BE0}.Release|ARM64.ActiveCfg = Release|Any CPU + {1C5BCF47-E378-48EB-8EB4-F8981B188BE0}.Release|ARM64.Build.0 = Release|Any CPU + {1C5BCF47-E378-48EB-8EB4-F8981B188BE0}.Release|x64.ActiveCfg = Release|Any CPU + {1C5BCF47-E378-48EB-8EB4-F8981B188BE0}.Release|x64.Build.0 = Release|Any CPU + {1C5BCF47-E378-48EB-8EB4-F8981B188BE0}.Release|x86.ActiveCfg = Release|Any CPU + {1C5BCF47-E378-48EB-8EB4-F8981B188BE0}.Release|x86.Build.0 = Release|Any CPU + {94030FE5-8385-4C79-A105-78CA2133CEB4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {94030FE5-8385-4C79-A105-78CA2133CEB4}.Debug|Any CPU.Build.0 = Debug|Any CPU + {94030FE5-8385-4C79-A105-78CA2133CEB4}.Debug|ARM64.ActiveCfg = Debug|Any CPU + {94030FE5-8385-4C79-A105-78CA2133CEB4}.Debug|ARM64.Build.0 = Debug|Any CPU + {94030FE5-8385-4C79-A105-78CA2133CEB4}.Debug|x64.ActiveCfg = Debug|Any CPU + {94030FE5-8385-4C79-A105-78CA2133CEB4}.Debug|x64.Build.0 = Debug|Any CPU + {94030FE5-8385-4C79-A105-78CA2133CEB4}.Debug|x86.ActiveCfg = Debug|Any CPU + {94030FE5-8385-4C79-A105-78CA2133CEB4}.Debug|x86.Build.0 = Debug|Any CPU + {94030FE5-8385-4C79-A105-78CA2133CEB4}.Release|Any CPU.ActiveCfg = Release|Any CPU + {94030FE5-8385-4C79-A105-78CA2133CEB4}.Release|Any CPU.Build.0 = Release|Any CPU + {94030FE5-8385-4C79-A105-78CA2133CEB4}.Release|ARM64.ActiveCfg = Release|Any CPU + {94030FE5-8385-4C79-A105-78CA2133CEB4}.Release|ARM64.Build.0 = Release|Any CPU + {94030FE5-8385-4C79-A105-78CA2133CEB4}.Release|x64.ActiveCfg = Release|Any CPU + {94030FE5-8385-4C79-A105-78CA2133CEB4}.Release|x64.Build.0 = Release|Any CPU + {94030FE5-8385-4C79-A105-78CA2133CEB4}.Release|x86.ActiveCfg = Release|Any CPU + {94030FE5-8385-4C79-A105-78CA2133CEB4}.Release|x86.Build.0 = Release|Any CPU + {7C6062A5-B55D-453A-B2B8-ECEC6D32DCD5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {7C6062A5-B55D-453A-B2B8-ECEC6D32DCD5}.Debug|Any CPU.Build.0 = Debug|Any CPU + {7C6062A5-B55D-453A-B2B8-ECEC6D32DCD5}.Debug|ARM64.ActiveCfg = Debug|Any CPU + {7C6062A5-B55D-453A-B2B8-ECEC6D32DCD5}.Debug|ARM64.Build.0 = Debug|Any CPU + {7C6062A5-B55D-453A-B2B8-ECEC6D32DCD5}.Debug|x64.ActiveCfg = Debug|Any CPU + {7C6062A5-B55D-453A-B2B8-ECEC6D32DCD5}.Debug|x64.Build.0 = Debug|Any CPU + {7C6062A5-B55D-453A-B2B8-ECEC6D32DCD5}.Debug|x86.ActiveCfg = Debug|Any CPU + {7C6062A5-B55D-453A-B2B8-ECEC6D32DCD5}.Debug|x86.Build.0 = Debug|Any CPU + {7C6062A5-B55D-453A-B2B8-ECEC6D32DCD5}.Release|Any CPU.ActiveCfg = Release|Any CPU + {7C6062A5-B55D-453A-B2B8-ECEC6D32DCD5}.Release|Any CPU.Build.0 = Release|Any CPU + {7C6062A5-B55D-453A-B2B8-ECEC6D32DCD5}.Release|ARM64.ActiveCfg = Release|Any CPU + {7C6062A5-B55D-453A-B2B8-ECEC6D32DCD5}.Release|ARM64.Build.0 = Release|Any CPU + {7C6062A5-B55D-453A-B2B8-ECEC6D32DCD5}.Release|x64.ActiveCfg = Release|Any CPU + {7C6062A5-B55D-453A-B2B8-ECEC6D32DCD5}.Release|x64.Build.0 = Release|Any CPU + {7C6062A5-B55D-453A-B2B8-ECEC6D32DCD5}.Release|x86.ActiveCfg = Release|Any CPU + {7C6062A5-B55D-453A-B2B8-ECEC6D32DCD5}.Release|x86.Build.0 = Release|Any CPU + {6E98CB19-57D6-4DD7-A8AE-6E098166D307}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {6E98CB19-57D6-4DD7-A8AE-6E098166D307}.Debug|Any CPU.Build.0 = Debug|Any CPU + {6E98CB19-57D6-4DD7-A8AE-6E098166D307}.Debug|ARM64.ActiveCfg = Debug|Any CPU + {6E98CB19-57D6-4DD7-A8AE-6E098166D307}.Debug|ARM64.Build.0 = Debug|Any CPU + {6E98CB19-57D6-4DD7-A8AE-6E098166D307}.Debug|x64.ActiveCfg = Debug|Any CPU + {6E98CB19-57D6-4DD7-A8AE-6E098166D307}.Debug|x64.Build.0 = Debug|Any CPU + {6E98CB19-57D6-4DD7-A8AE-6E098166D307}.Debug|x86.ActiveCfg = Debug|Any CPU + {6E98CB19-57D6-4DD7-A8AE-6E098166D307}.Debug|x86.Build.0 = Debug|Any CPU + {6E98CB19-57D6-4DD7-A8AE-6E098166D307}.Release|Any CPU.ActiveCfg = Release|Any CPU + {6E98CB19-57D6-4DD7-A8AE-6E098166D307}.Release|Any CPU.Build.0 = Release|Any CPU + {6E98CB19-57D6-4DD7-A8AE-6E098166D307}.Release|ARM64.ActiveCfg = Release|Any CPU + {6E98CB19-57D6-4DD7-A8AE-6E098166D307}.Release|ARM64.Build.0 = Release|Any CPU + {6E98CB19-57D6-4DD7-A8AE-6E098166D307}.Release|x64.ActiveCfg = Release|Any CPU + {6E98CB19-57D6-4DD7-A8AE-6E098166D307}.Release|x64.Build.0 = Release|Any CPU + {6E98CB19-57D6-4DD7-A8AE-6E098166D307}.Release|x86.ActiveCfg = Release|Any CPU + {6E98CB19-57D6-4DD7-A8AE-6E098166D307}.Release|x86.Build.0 = Release|Any CPU + {4EF06119-5D9C-43D5-836D-8F307C271132}.Debug|Any CPU.ActiveCfg = Debug|x64 + {4EF06119-5D9C-43D5-836D-8F307C271132}.Debug|Any CPU.Build.0 = Debug|x64 + {4EF06119-5D9C-43D5-836D-8F307C271132}.Debug|ARM64.ActiveCfg = Debug|arm64 + {4EF06119-5D9C-43D5-836D-8F307C271132}.Debug|ARM64.Build.0 = Debug|arm64 + {4EF06119-5D9C-43D5-836D-8F307C271132}.Debug|x64.ActiveCfg = Debug|x64 + {4EF06119-5D9C-43D5-836D-8F307C271132}.Debug|x64.Build.0 = Debug|x64 + {4EF06119-5D9C-43D5-836D-8F307C271132}.Debug|x86.ActiveCfg = Debug|x86 + {4EF06119-5D9C-43D5-836D-8F307C271132}.Debug|x86.Build.0 = Debug|x86 + {4EF06119-5D9C-43D5-836D-8F307C271132}.Release|Any CPU.ActiveCfg = Release|x64 + {4EF06119-5D9C-43D5-836D-8F307C271132}.Release|Any CPU.Build.0 = Release|x64 + {4EF06119-5D9C-43D5-836D-8F307C271132}.Release|ARM64.ActiveCfg = Release|arm64 + {4EF06119-5D9C-43D5-836D-8F307C271132}.Release|ARM64.Build.0 = Release|arm64 + {4EF06119-5D9C-43D5-836D-8F307C271132}.Release|x64.ActiveCfg = Release|x64 + {4EF06119-5D9C-43D5-836D-8F307C271132}.Release|x64.Build.0 = Release|x64 + {4EF06119-5D9C-43D5-836D-8F307C271132}.Release|x86.ActiveCfg = Release|x86 + {4EF06119-5D9C-43D5-836D-8F307C271132}.Release|x86.Build.0 = Release|x86 + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(NestedProjects) = preSolution + {48FD15F1-014C-40CF-A047-272E88C3E0BC} = {1E90315C-2A79-4D73-9685-0B20BAFAB5B1} + {B14A04D3-50C8-4772-BC47-A1915BE035BA} = {1E90315C-2A79-4D73-9685-0B20BAFAB5B1} + {A658BF84-5475-4281-AB3F-3FA0E50C9448} = {1E90315C-2A79-4D73-9685-0B20BAFAB5B1} + {535E46AD-2A7D-4F5E-AA73-A838BD56EB0C} = {1E90315C-2A79-4D73-9685-0B20BAFAB5B1} + {A43A39E8-645D-4C2C-8DAD-9848F43D71B0} = {1E90315C-2A79-4D73-9685-0B20BAFAB5B1} + {E0B7CCA5-5943-4249-89D7-113A71B11C12} = {3283AE12-D01B-4686-9E58-6D08AACB7481} + {3DA8880C-6F8B-4CC1-96FE-4E65C848C671} = {3283AE12-D01B-4686-9E58-6D08AACB7481} + {61D3B473-1A52-4CC3-9067-5BEADEEC9ABF} = {793C8FAF-1F4A-4A71-B757-56FD88C5BD36} + {9F7CB0AD-6DC9-4B48-857D-47E445D84147} = {793C8FAF-1F4A-4A71-B757-56FD88C5BD36} + {17933DBF-2508-4CB3-BB02-586F54DB631A} = {793C8FAF-1F4A-4A71-B757-56FD88C5BD36} + {59565E33-FDDA-4F92-95DB-026FCA848010} = {793C8FAF-1F4A-4A71-B757-56FD88C5BD36} + {501963E1-1D15-46CC-9257-96C7B286E6EF} = {793C8FAF-1F4A-4A71-B757-56FD88C5BD36} + {1C5BCF47-E378-48EB-8EB4-F8981B188BE0} = {793C8FAF-1F4A-4A71-B757-56FD88C5BD36} + {94030FE5-8385-4C79-A105-78CA2133CEB4} = {0CF4A361-CB4D-4178-B474-0FB3271C80CC} + {7C6062A5-B55D-453A-B2B8-ECEC6D32DCD5} = {0CF4A361-CB4D-4178-B474-0FB3271C80CC} + {6E98CB19-57D6-4DD7-A8AE-6E098166D307} = {4A9E18B1-B52E-4A8E-AF0E-792D24752D16} + {4EF06119-5D9C-43D5-836D-8F307C271132} = {4A9E18B1-B52E-4A8E-AF0E-792D24752D16} + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {A0F66913-EF6B-407F-A362-3C0F40871515} + EndGlobalSection +EndGlobal diff --git a/Directory.Build.props b/Directory.Build.props index 48156e9fb..ff498394d 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -10,7 +10,7 @@ - 3.3.0 + 3.3.3 runtime; build; native; contentfiles; analyzers; buildtransitive all diff --git a/README.md b/README.md index 3f3bc7cf1..da0bf1f04 100644 --- a/README.md +++ b/README.md @@ -9,16 +9,30 @@ [![GitHub release (latest by date)](https://img.shields.io/github/v/release/Richasy/Bili.Uwp)](https://github.com/Richasy/Bili.Uwp/releases) ![GitHub Release Date](https://img.shields.io/github/release-date/Richasy/Bili.Uwp) ![GitHub All Releases](https://img.shields.io/github/downloads/Richasy/Bili.Uwp/total) ![GitHub stars](https://img.shields.io/github/stars/Richasy/Bili.Uwp?style=flat) ![GitHub forks](https://img.shields.io/github/forks/Richasy/Bili.Uwp) `哔哩` 现在为 Windows 11 设计! + +[![Release Builder](https://github.com/Richasy/Bili.Uwp/actions/workflows/release-builder.yml/badge.svg)](https://github.com/Richasy/Bili.Uwp/actions/workflows/release-builder.yml) --- -`哔哩` 是一款 [哔哩哔哩](https://www.bilibili.com) 的第三方应用,使用 UWP 框架开发,是原生的 Windows 应用,支持 Windows 10/11 系统。主打设计和易用性,广受用户好评。 +### [哔哩助理](https://github.com/Richasy/Bili.Copilot) 已经上线,将逐步替代现在的哔哩 UWP + +--- + +`哔哩` 是一款 [哔哩哔哩](https://www.bilibili.com) 的第三方应用,使用 UWP 框架开发,是原生的 Windows 应用,支持 Windows 10/11 桌面系统。主打设计和易用性,~~广受用户好评~~。 ## 🙌 简单的开始 -如果想安装哔哩,请打开右侧的 [Release](https://github.com/Richasy/Bili.Uwp/releases) 页面,找到最新版本,并选择适用于当前系统的安装包下载。 +~~### 从商店安装 (不可用)~~ + +~~将链接 `ms-windows-store://pdp/?productid=9mvn4nslt150` 复制到浏览器地址栏打开,从 Microsoft Store 下载。~~ + +由于名称、功能、图像与官方应用重合度太高,哔哩 UWP 的后续更新已经被商店阻止。目前仅提供 Github 下载 + +### 侧加载 (Sideload) + +如果你想本地安装哔哩,或者尝试当月的最新功能。请打开右侧的 [Release](https://github.com/Richasy/Bili.Uwp/releases) 页面,找到最新版本,并选择适用于当前系统的安装包下载。 然后打开 [系统设置](ms-settings:developers),打开 `开发者模式` ,并等待系统安装一些必要的扩展项。 @@ -26,11 +40,15 @@ **Watch** 项目,以获取应用的更新动态。 -关于如何一步步地使用侧加载 (Sideload) 方式安装 UWP 应用及订阅应用更新,请参见 [下载并安装哔哩的详细说明](./how_to_install.md) 。 +关于如何一步步地使用侧加载 (Sideload) 方式安装 UWP 应用及订阅应用更新,请参见 [下载并安装哔哩的详细说明](https://github.com/Richasy/Bili.Uwp/wiki/%E4%B8%8B%E8%BD%BD%E5%B9%B6%E5%AE%89%E8%A3%85%E5%93%94%E5%93%A9%E7%9A%84%E8%AF%A6%E7%BB%86%E8%AF%B4%E6%98%8E) 。 + +## ❓ 常见问题 + +在应用的安装使用过程中,你可能会碰到一些问题,这篇文档也许可以帮助你解决遇到的困难:[常见问题](https://github.com/Richasy/Bili.Uwp/wiki/%E5%B8%B8%E8%A7%81%E9%97%AE%E9%A2%98) ## 📃 文档 -所有关于 `哔哩` 的文档,包括架构、使用说明等,都放在仓库的 [Wiki](https://github.com/Richasy/Bili.Uwp/wiki) 中,如果你发现有文档缺失或错误,请提交 [Issue](https://github.com/Richasy/Bili.Uwp/issue/new/choose) 说明错漏的内容。 +所有关于 `哔哩` 的文档,包括架构、使用说明等,都放在仓库的 [Wiki](https://github.com/Richasy/Bili.Uwp/wiki) 中,如果你发现有文档缺失或错误,请提交 [Issue](https://github.com/Richasy/Bili.Uwp/issues/new/choose) 说明错漏的内容。 ## 🚀 协作 @@ -40,10 +58,14 @@ 借助 Github 平台提供的 Discussions 功能,对于一般讨论、提议或分享,我们都可以在 [哔哩论坛](https://github.com/Richasy/Bili.Uwp/discussions) 中进行,欢迎来这里进行讨论。 -## 🌏 路线图 +## ~~🌏 路线图~~ -哔哩会逐步完善,请查看 [哔哩里程碑](https://github.com/Richasy/Bili.Uwp/milestones) 来了解哔哩下一步打算做的事情。于此同时,欢迎各位开发者加入,让我们一起打造哔哩的未来。 +~~哔哩会逐步完善,请查看 [哔哩里程碑](https://github.com/Richasy/Bili.Uwp/milestones) 来了解哔哩下一步打算做的事情。与此同时,欢迎各位开发者加入,让我们一起打造哔哩的未来。~~ ## 🧩 截图 -![截图](./assets/image/guide/screenshot.png) \ No newline at end of file +*桌面* +![桌面截图](./assets/screenshot_desktop.png) + +*XBOX* +![XBOX截图](./assets/screenshot_xbox.png) diff --git a/TestUtils.targets b/TestUtils.targets index df486e628..f0bf06df1 100644 --- a/TestUtils.targets +++ b/TestUtils.targets @@ -5,7 +5,7 @@ - + diff --git a/UnitTests.targets b/UnitTests.targets index 09038c98c..c1f4b3c78 100644 --- a/UnitTests.targets +++ b/UnitTests.targets @@ -5,9 +5,9 @@ - + - + diff --git a/Uwp.props b/Uwp.props index e5d3bacde..e4b5afb31 100644 --- a/Uwp.props +++ b/Uwp.props @@ -8,9 +8,10 @@ Properties en-US UAP - 10.0.19041.0 - 10.0.17763.0 + 10.0.22000.0 + 10.0.18362.0 14 + 10 512 @@ -40,6 +41,7 @@ false true + /ExtraNutcArguments:"-d2disableloopopt" false diff --git a/assets/image/guide/cert_confirm.png b/assets/image/guide/cert_confirm.png deleted file mode 100644 index 4cdef42d5..000000000 Binary files a/assets/image/guide/cert_confirm.png and /dev/null differ diff --git a/assets/image/guide/cert_detail.png b/assets/image/guide/cert_detail.png deleted file mode 100644 index a8ad1d2f8..000000000 Binary files a/assets/image/guide/cert_detail.png and /dev/null differ diff --git a/assets/image/guide/cert_file.png b/assets/image/guide/cert_file.png deleted file mode 100644 index d08f60bf5..000000000 Binary files a/assets/image/guide/cert_file.png and /dev/null differ diff --git a/assets/image/guide/cert_locate.png b/assets/image/guide/cert_locate.png deleted file mode 100644 index 320386048..000000000 Binary files a/assets/image/guide/cert_locate.png and /dev/null differ diff --git a/assets/image/guide/cert_save.png b/assets/image/guide/cert_save.png deleted file mode 100644 index 551398eb1..000000000 Binary files a/assets/image/guide/cert_save.png and /dev/null differ diff --git a/assets/image/guide/cert_uac.png b/assets/image/guide/cert_uac.png deleted file mode 100644 index 79d88692d..000000000 Binary files a/assets/image/guide/cert_uac.png and /dev/null differ diff --git a/assets/image/guide/install_process.png b/assets/image/guide/install_process.png deleted file mode 100644 index d13f6f8bb..000000000 Binary files a/assets/image/guide/install_process.png and /dev/null differ diff --git a/assets/image/guide/msixbundle_file.png b/assets/image/guide/msixbundle_file.png deleted file mode 100644 index e4b38fff6..000000000 Binary files a/assets/image/guide/msixbundle_file.png and /dev/null differ diff --git a/assets/image/guide/open_with_powershell.png b/assets/image/guide/open_with_powershell.png deleted file mode 100644 index 21814e673..000000000 Binary files a/assets/image/guide/open_with_powershell.png and /dev/null differ diff --git a/assets/image/guide/package_install.png b/assets/image/guide/package_install.png deleted file mode 100644 index 569af1e03..000000000 Binary files a/assets/image/guide/package_install.png and /dev/null differ diff --git a/assets/image/guide/release_page.png b/assets/image/guide/release_page.png deleted file mode 100644 index 08decd41c..000000000 Binary files a/assets/image/guide/release_page.png and /dev/null differ diff --git a/assets/image/guide/screenshot.png b/assets/image/guide/screenshot.png deleted file mode 100644 index 001d370ad..000000000 Binary files a/assets/image/guide/screenshot.png and /dev/null differ diff --git a/assets/image/guide/watch_release.png b/assets/image/guide/watch_release.png deleted file mode 100644 index 1f9a1f4be..000000000 Binary files a/assets/image/guide/watch_release.png and /dev/null differ diff --git a/assets/image/guide/watch_repo.png b/assets/image/guide/watch_repo.png deleted file mode 100644 index 656e19f7f..000000000 Binary files a/assets/image/guide/watch_repo.png and /dev/null differ diff --git a/assets/screenshot_desktop.png b/assets/screenshot_desktop.png new file mode 100644 index 000000000..67fdd8ddf Binary files /dev/null and b/assets/screenshot_desktop.png differ diff --git a/assets/screenshot_xbox.png b/assets/screenshot_xbox.png new file mode 100644 index 000000000..563db936b Binary files /dev/null and b/assets/screenshot_xbox.png differ diff --git a/ffmpeg/ARM64/avcodec-58.dll b/ffmpeg/ARM64/avcodec-58.dll deleted file mode 100644 index e8461d202..000000000 Binary files a/ffmpeg/ARM64/avcodec-58.dll and /dev/null differ diff --git a/ffmpeg/ARM64/avdevice-58.dll b/ffmpeg/ARM64/avdevice-58.dll deleted file mode 100644 index f6e79efca..000000000 Binary files a/ffmpeg/ARM64/avdevice-58.dll and /dev/null differ diff --git a/ffmpeg/ARM64/avfilter-7.dll b/ffmpeg/ARM64/avfilter-7.dll deleted file mode 100644 index d850ce117..000000000 Binary files a/ffmpeg/ARM64/avfilter-7.dll and /dev/null differ diff --git a/ffmpeg/ARM64/avformat-58.dll b/ffmpeg/ARM64/avformat-58.dll deleted file mode 100644 index ad82fa644..000000000 Binary files a/ffmpeg/ARM64/avformat-58.dll and /dev/null differ diff --git a/ffmpeg/ARM64/avutil-56.dll b/ffmpeg/ARM64/avutil-56.dll deleted file mode 100644 index 959929a8e..000000000 Binary files a/ffmpeg/ARM64/avutil-56.dll and /dev/null differ diff --git a/ffmpeg/ARM64/libcrypto-1_1-arm64.dll b/ffmpeg/ARM64/libcrypto-1_1-arm64.dll deleted file mode 100644 index 80b56f895..000000000 Binary files a/ffmpeg/ARM64/libcrypto-1_1-arm64.dll and /dev/null differ diff --git a/ffmpeg/ARM64/libssl-1_1-arm64.dll b/ffmpeg/ARM64/libssl-1_1-arm64.dll deleted file mode 100644 index ec723f821..000000000 Binary files a/ffmpeg/ARM64/libssl-1_1-arm64.dll and /dev/null differ diff --git a/ffmpeg/ARM64/postproc-55.dll b/ffmpeg/ARM64/postproc-55.dll deleted file mode 100644 index 69b03de77..000000000 Binary files a/ffmpeg/ARM64/postproc-55.dll and /dev/null differ diff --git a/ffmpeg/ARM64/swresample-3.dll b/ffmpeg/ARM64/swresample-3.dll deleted file mode 100644 index 5f689a031..000000000 Binary files a/ffmpeg/ARM64/swresample-3.dll and /dev/null differ diff --git a/ffmpeg/ARM64/swscale-5.dll b/ffmpeg/ARM64/swscale-5.dll deleted file mode 100644 index 99c9fcc30..000000000 Binary files a/ffmpeg/ARM64/swscale-5.dll and /dev/null differ diff --git a/ffmpeg/x64/avcodec-58.dll b/ffmpeg/x64/avcodec-58.dll deleted file mode 100644 index 7aa44ea12..000000000 Binary files a/ffmpeg/x64/avcodec-58.dll and /dev/null differ diff --git a/ffmpeg/x64/avdevice-58.dll b/ffmpeg/x64/avdevice-58.dll deleted file mode 100644 index 20ce52d56..000000000 Binary files a/ffmpeg/x64/avdevice-58.dll and /dev/null differ diff --git a/ffmpeg/x64/avfilter-7.dll b/ffmpeg/x64/avfilter-7.dll deleted file mode 100644 index 0f86c2618..000000000 Binary files a/ffmpeg/x64/avfilter-7.dll and /dev/null differ diff --git a/ffmpeg/x64/avformat-58.dll b/ffmpeg/x64/avformat-58.dll deleted file mode 100644 index 04ed614df..000000000 Binary files a/ffmpeg/x64/avformat-58.dll and /dev/null differ diff --git a/ffmpeg/x64/avutil-56.dll b/ffmpeg/x64/avutil-56.dll deleted file mode 100644 index a598bdcdd..000000000 Binary files a/ffmpeg/x64/avutil-56.dll and /dev/null differ diff --git a/ffmpeg/x64/libcrypto-1_1-x64.dll b/ffmpeg/x64/libcrypto-1_1-x64.dll deleted file mode 100644 index f221a73ab..000000000 Binary files a/ffmpeg/x64/libcrypto-1_1-x64.dll and /dev/null differ diff --git a/ffmpeg/x64/libssl-1_1-x64.dll b/ffmpeg/x64/libssl-1_1-x64.dll deleted file mode 100644 index 2d93b0a59..000000000 Binary files a/ffmpeg/x64/libssl-1_1-x64.dll and /dev/null differ diff --git a/ffmpeg/x64/postproc-55.dll b/ffmpeg/x64/postproc-55.dll deleted file mode 100644 index 84c0725ef..000000000 Binary files a/ffmpeg/x64/postproc-55.dll and /dev/null differ diff --git a/ffmpeg/x64/swresample-3.dll b/ffmpeg/x64/swresample-3.dll deleted file mode 100644 index 7bbf7666e..000000000 Binary files a/ffmpeg/x64/swresample-3.dll and /dev/null differ diff --git a/ffmpeg/x64/swscale-5.dll b/ffmpeg/x64/swscale-5.dll deleted file mode 100644 index d3d4a6a8d..000000000 Binary files a/ffmpeg/x64/swscale-5.dll and /dev/null differ diff --git a/ffmpeg/x86/avcodec-58.dll b/ffmpeg/x86/avcodec-58.dll deleted file mode 100644 index e68b96ba4..000000000 Binary files a/ffmpeg/x86/avcodec-58.dll and /dev/null differ diff --git a/ffmpeg/x86/avdevice-58.dll b/ffmpeg/x86/avdevice-58.dll deleted file mode 100644 index 0d2bfe41e..000000000 Binary files a/ffmpeg/x86/avdevice-58.dll and /dev/null differ diff --git a/ffmpeg/x86/avfilter-7.dll b/ffmpeg/x86/avfilter-7.dll deleted file mode 100644 index 29597eb65..000000000 Binary files a/ffmpeg/x86/avfilter-7.dll and /dev/null differ diff --git a/ffmpeg/x86/avformat-58.dll b/ffmpeg/x86/avformat-58.dll deleted file mode 100644 index d8bf424c6..000000000 Binary files a/ffmpeg/x86/avformat-58.dll and /dev/null differ diff --git a/ffmpeg/x86/avresample-4.dll b/ffmpeg/x86/avresample-4.dll deleted file mode 100644 index 5e5340590..000000000 Binary files a/ffmpeg/x86/avresample-4.dll and /dev/null differ diff --git a/ffmpeg/x86/avutil-56.dll b/ffmpeg/x86/avutil-56.dll deleted file mode 100644 index 71f310bb3..000000000 Binary files a/ffmpeg/x86/avutil-56.dll and /dev/null differ diff --git a/ffmpeg/x86/libcrypto-1_1.dll b/ffmpeg/x86/libcrypto-1_1.dll deleted file mode 100644 index e8c78a1f1..000000000 Binary files a/ffmpeg/x86/libcrypto-1_1.dll and /dev/null differ diff --git a/ffmpeg/x86/libssl-1_1.dll b/ffmpeg/x86/libssl-1_1.dll deleted file mode 100644 index 2cea4817e..000000000 Binary files a/ffmpeg/x86/libssl-1_1.dll and /dev/null differ diff --git a/ffmpeg/x86/postproc-55.dll b/ffmpeg/x86/postproc-55.dll deleted file mode 100644 index 0309e647f..000000000 Binary files a/ffmpeg/x86/postproc-55.dll and /dev/null differ diff --git a/ffmpeg/x86/swresample-3.dll b/ffmpeg/x86/swresample-3.dll deleted file mode 100644 index 11d923fa7..000000000 Binary files a/ffmpeg/x86/swresample-3.dll and /dev/null differ diff --git a/ffmpeg/x86/swscale-5.dll b/ffmpeg/x86/swscale-5.dll deleted file mode 100644 index b81b9761d..000000000 Binary files a/ffmpeg/x86/swscale-5.dll and /dev/null differ diff --git a/how_to_install.md b/how_to_install.md deleted file mode 100644 index 23537ebb6..000000000 --- a/how_to_install.md +++ /dev/null @@ -1,108 +0,0 @@ -# 下载并安装哔哩的详细说明 - -本文将详细讲述 `哔哩` 的侧加载安装过程,请按照下文所述一步步操作,在安装中遇到的问题,可以在 [哔哩论坛](https://github.com/Richasy/Bili.Uwp/discussions) 中讨论。 - -## 准备 - -首先,你需要打开系统的开发者模式,这是通过侧加载方式安装 UWP 应用的前置条件,否则,您的设备只能安装来自 Microsoft Store 的 UWP 应用。 - -点击 [链接](ms-settings:developers) 打开系统设置的 `开发者选项`,如果浏览器无法打开,请参考以下步骤进入 `开发者选项` 页面. - -1. 进入 `设置`。 -2. 打开 `隐私和安全性` 页面。 -3. 选择 `开发者选项` 选项。 - -进入后打开 `开发者模式`,等待系统安装一些必要组件。 - -## 下载应用 - -1. 进入 Github 的 [Release](https://github.com/Richasy/Bili.Uwp/releases) 页面。 - - ![Release 页面](./assets/image/guide/release_page.png) - -2. 找到标识 `Latest release` 标识的版本,该版本意味着最新发布版本。在该版本中下的 `Assets` 面板中找到适合自己设备平台的包并点击下载。具体平台版本对应如下: - - |架构|说明|示例包名| - |-|-|-| - |x64|64 位平台,是现在的主流平台
在设置的 [关于](ms-settings:about) 页面中的 `设备规格` 区块可以查看当前的系统类型|Bili.Uwp_{版本号}_x64.zip| - |x86|32 位平台,确认方法可以参照 x64 进行|Bili.Uwp_{版本号}_x86.zip| - |ARM64|64 位 ARM 架构,Surface Pro X 是典型设备|Bili.Uwp_{版本号}_ARM64.zip| - - *注:ARM 架构在 UWP 中通常指 Windows Phone 设备,目前已不再支持。* - -## 推荐:使用 PowerShell 安装应用 - -> 需要特别注意的是,应使用 Windows PowerShell 而不是 PowerShell Core 来安装。 -> 在涉及到本机操作时,PowerShell Core 并没有提供相应的功能。 -> 关联问题 [#62](https://github.com/Richasy/Bili.Uwp/issues/62) - -优势:通过脚本安装,可以自动安装证书,方便快捷。 - -1. 解压上一步骤下载的应用压缩包。 -2. 在解压后的文件夹中找到 `install.ps1` 这个脚本文件。 -3. 右键单击该文件,并选择 `使用 PowerShell 运行`。 - - ![使用 PowerShell 运行](./assets/image/guide/open_with_powershell.png) - -4. 在初次运行时,安装脚本会在安装证书时提示你提升权限,请按 `回车 (Enter)` 键继续。 - - ![提权安装证书](./assets/image/guide/cert_uac.png) - -5. 在安装证书时,请输入 `Y` 以继续 - - ![确认安装证书](./assets/image/guide/cert_confirm.png) - -6. 等待安装完成 - - ![安装过程](./assets/image/guide/install_process.png) - -7. 现在你可以在你的开始菜单中找到 `哔哩` 了。 - -## 常规:使用应用安装程序 (App Installer) - -优势:图形化操作界面,易于理解 - -劣势:需要手动安装证书 - -1. (可选)在 Microsoft Store 中下载或更新 [应用安装程序](ms-windows-store://pdp/?productId=9NBLGGH4NNS1)。 -2. 解压之前下载的应用压缩包。 -3. 在解压后的文件夹中找到后缀名为 `.cer` 的证书文件。 - - ![证书文件](./assets/image/guide/cert_file.png) - -4. 双击运行,点击 `安装证书`。 - - ![证书详情](./assets/image/guide/cert_detail.png) - -5. 存储位置选择 `本地计算机`,点击 `下一页`,并通过 UAC 验证 - - ![证书存储位置](./assets/image/guide/cert_locate.png) - -6. 选择 `将所有的证书都放入下列存储`,并点击 `浏览`,选择 `受信任的根证书颁发机构`,点击 `确定`,再点击 `下一页`。 - - ![证书安装](./assets/image/guide/cert_save.png) - -7. 证书已导入,点击 `完成` 即可结束证书导入。 -8. 在解压的应用包文件夹中,双击打开后缀名为 `.msixbundle` 的安装包。 - - ![msixbundle 文件](./assets/image/guide/msixbundle_file.png) - -9. 到了这一步就简单了,确认安装包显示的是 `Trusted App`,这表示我们的证书导入正确,点击 `Install` 按钮,等待应用安装完成即可。 - - ![安装界面](./assets/image/guide/package_install.png) - -## 订阅更新 - -为了能在 `哔哩` 更新时收到通知,请在 Github 的 `哔哩` 仓库中,点击右上角的 `Watch` 以进行追踪,详细步骤如下: - -1. 点击 `Watch` 按钮,再点击旁边的三角符号。 - - ![追踪项目](./assets/image/guide/watch_repo.png) - -2. 选择 `Custom`,选中 `Release`,并点击 `Apply`。 - - ![追踪 Release](./assets/image/guide/watch_release.png) - -这样,在之后 `哔哩` 的 `Release` 页面更新时,你就能在你的注册邮箱中收到订阅邮件,从而获知最新的版本。 - -有条件的小伙伴也可以下载 Github 的 Android 或 iOS 客户端,能得到更直接的通知推送。 \ No newline at end of file diff --git a/nuget.config b/nuget.config index ad1f6a333..8e5c6118e 100644 --- a/nuget.config +++ b/nuget.config @@ -3,6 +3,7 @@ + diff --git a/src/Adapter/Adapter.Implementation/Adapter.Implementation.csproj b/src/Adapter/Adapter.Implementation/Adapter.Implementation.csproj new file mode 100644 index 000000000..2f58f376f --- /dev/null +++ b/src/Adapter/Adapter.Implementation/Adapter.Implementation.csproj @@ -0,0 +1,21 @@ + + + + netstandard2.0 + Bili.Adapter + Bili.$(MSBuildProjectName) + 10 + + + + + + + + + + + + + + diff --git a/src/Adapter/Adapter.Implementation/ArticleAdapter.cs b/src/Adapter/Adapter.Implementation/ArticleAdapter.cs new file mode 100644 index 000000000..9669c6eeb --- /dev/null +++ b/src/Adapter/Adapter.Implementation/ArticleAdapter.cs @@ -0,0 +1,164 @@ +// Copyright (c) Richasy. All rights reserved. + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text.RegularExpressions; +using Bili.Adapter.Interfaces; +using Bili.Models.BiliBili; +using Bili.Models.Data.Article; +using Bili.Models.Data.Community; +using Bili.Toolkit.Interfaces; +using Bilibili.App.Dynamic.V2; +using Humanizer; + +namespace Bili.Adapter +{ + /// + /// 文章数据适配器. + /// + public sealed class ArticleAdapter : IArticleAdapter + { + private readonly IImageAdapter _imageAdapter; + private readonly IUserAdapter _userAdapter; + private readonly ICommunityAdapter _communityAdapter; + private readonly ITextToolkit _textToolkit; + + /// + /// Initializes a new instance of the class. + /// + /// 图片数据适配器. + /// 用户数据适配器. + /// 社区数据适配器. + /// 文本工具. + public ArticleAdapter( + IImageAdapter imageAdapter, + IUserAdapter userAdapter, + ICommunityAdapter communityAdapter, + ITextToolkit textToolkit) + { + _imageAdapter = imageAdapter; + _userAdapter = userAdapter; + _communityAdapter = communityAdapter; + _textToolkit = textToolkit; + } + + /// + public ArticleInformation ConvertToArticleInformation(Article article) + { + var id = article.Id.ToString(); + var title = _textToolkit.ConvertToTraditionalChineseIfNeeded(article.Title); + var summary = _textToolkit.ConvertToTraditionalChineseIfNeeded(article.Summary); + var cover = article.CoverUrls?.Any() ?? false + ? _imageAdapter.ConvertToArticleCardCover(article.CoverUrls.First()) + : null; + var partition = _communityAdapter.ConvertToPartition(article.Category); + var relatedPartitions = article.RelatedCategories?.Any() ?? false + ? article.RelatedCategories.Select(p => _communityAdapter.ConvertToPartition(p)).ToList() + : null; + var publishTime = DateTimeOffset.FromUnixTimeSeconds(article.PublishTime).ToLocalTime().DateTime; + var user = _userAdapter.ConvertToRoleProfile(article.Publisher, Models.Enums.App.AvatarSize.Size48); + var subtitle = $"{user.User.Name} · {_textToolkit.ConvertToTraditionalChineseIfNeeded(publishTime.Humanize())}"; + var wordCount = article.WordCount; + var communityInfo = _communityAdapter.ConvertToArticleCommunityInformation(article.Stats, id); + var identifier = new ArticleIdentifier(id, title, summary, cover); + return new ArticleInformation( + identifier, + subtitle, + partition, + relatedPartitions, + user, + publishTime, + communityInfo, + wordCount); + } + + /// + public ArticleInformation ConvertToArticleInformation(ArticleSearchItem item) + { + var id = item.Id.ToString(); + var title = Regex.Replace(item.Title, "<[^>]+>", string.Empty); + title = _textToolkit.ConvertToTraditionalChineseIfNeeded(title); + var summary = _textToolkit.ConvertToTraditionalChineseIfNeeded(item.Description); + var cover = item.CoverUrls?.Any() ?? false + ? _imageAdapter.ConvertToArticleCardCover(item.CoverUrls.First()) + : null; + var subtitle = item.Name; + var communityInfo = _communityAdapter.ConvertToArticleCommunityInformation(item); + var identifier = new ArticleIdentifier(id, title, summary, cover); + return new ArticleInformation( + identifier, + subtitle, + communityInformation: communityInfo); + } + + /// + public ArticleInformation ConvertToArticleInformation(FavoriteArticleItem item) + { + var id = item.Id.ToString(); + var title = _textToolkit.ConvertToTraditionalChineseIfNeeded(item.Title); + var summary = _textToolkit.ConvertToTraditionalChineseIfNeeded(item.Summary); + var cover = item.Images?.Any() ?? false + ? _imageAdapter.ConvertToArticleCardCover(item.Images.First()) + : null; + var collectTime = DateTimeOffset.FromUnixTimeSeconds(item.CollectTime).DateTime; + var subtitle = _textToolkit.ConvertToTraditionalChineseIfNeeded($"{collectTime.Humanize()}收藏"); + var identifier = new ArticleIdentifier(id, title, summary, cover); + return new ArticleInformation( + identifier, + subtitle); + } + + /// + public ArticleInformation ConvertToArticleInformation(MdlDynArticle article) + { + var id = article.Id.ToString(); + var title = _textToolkit.ConvertToTraditionalChineseIfNeeded(article.Title); + var summary = _textToolkit.ConvertToTraditionalChineseIfNeeded(article.Desc); + var cover = article.Covers?.Any() ?? false + ? _imageAdapter.ConvertToArticleCardCover(article.Covers.First()) + : null; + var identifier = new ArticleIdentifier(id, title, summary, cover); + return new ArticleInformation( + identifier, + article.Label); + } + + /// + public ArticlePartitionView ConvertToArticlePartitionView(ArticleRecommendResponse response) + { + var articles = response.Articles?.Any() ?? false + ? response.Articles.Select(p => ConvertToArticleInformation(p)) + : null; + + var ranks = response.Ranks?.Any() ?? false + ? response.Ranks.Select(p => ConvertToArticleInformation(p)) + : null; + + IEnumerable banners = null; + if (response.Banners?.Any() ?? false) + { + var tempBanners = response.Banners.ToList(); + tempBanners.ForEach(p => p.NavigateUri = $"https://www.bilibili.com/read/cv{p.Id}"); + banners = tempBanners.Select(p => _communityAdapter.ConvertToBannerIdentifier(p)); + } + + return new ArticlePartitionView(articles, banners, ranks); + } + + /// + public ArticlePartitionView ConvertToArticlePartitionView(IEnumerable
articles) + { + var items = articles.Select(p => ConvertToArticleInformation(p)); + return new ArticlePartitionView(items); + } + + /// + public ArticleSet ConvertToArticleSet(ArticleFavoriteListResponse response) + { + var count = response.Count; + var items = response.Items.Select(p => ConvertToArticleInformation(p)); + return new ArticleSet(items, count); + } + } +} diff --git a/src/Adapter/Adapter.Implementation/CommentAdapter.cs b/src/Adapter/Adapter.Implementation/CommentAdapter.cs new file mode 100644 index 000000000..42066991c --- /dev/null +++ b/src/Adapter/Adapter.Implementation/CommentAdapter.cs @@ -0,0 +1,66 @@ +// Copyright (c) Richasy. All rights reserved. + +using System; +using System.Linq; +using Bili.Adapter.Interfaces; +using Bili.Models.Data.Community; +using Bilibili.Main.Community.Reply.V1; + +namespace Bili.Adapter +{ + /// + /// 评论数据适配器. + /// + public sealed class CommentAdapter : ICommentAdapter + { + private readonly IUserAdapter _userAdapter; + private readonly IImageAdapter _imageAdapter; + + /// + /// Initializes a new instance of the class. + /// + /// 用户数据适配器. + /// 图片数据适配器. + public CommentAdapter( + IUserAdapter userAdapter, + IImageAdapter imageAdapter) + { + _userAdapter = userAdapter; + _imageAdapter = imageAdapter; + } + + /// + public CommentInformation ConvertToCommentInformation(ReplyInfo info) + { + var id = info.Id.ToString(); + var rootId = info.Root.ToString(); + var isTop = info.ReplyControl.IsAdminTop || info.ReplyControl.IsUpTop; + var publishTime = DateTimeOffset.FromUnixTimeSeconds(info.Ctime).DateTime; + var user = _userAdapter.ConvertToAccountInformation(info.Member); + var communityInfo = new CommentCommunityInformation(id, info.Like, Convert.ToInt32(info.Count), info.ReplyControl.Action == 1); + var content = _imageAdapter.ConvertToEmoteText(info.Content); + return new CommentInformation(id, content, rootId, isTop, user, publishTime, communityInfo); + } + + /// + public CommentView ConvertToCommentView(MainListReply reply, string targetId) + { + var comments = reply.Replies.Select(p => ConvertToCommentInformation(p)).ToList(); + var top = reply.UpTop ?? reply.VoteTop; + var topComment = top == null + ? null + : ConvertToCommentInformation(top); + var isEnd = reply.Cursor.IsEnd; + return new CommentView(comments, targetId, topComment, isEnd); + } + + /// + public CommentView ConvertToCommentView(DetailListReply reply, string targetId) + { + var comments = reply.Root.Replies.Select(p => ConvertToCommentInformation(p)).ToList(); + var topComment = ConvertToCommentInformation(reply.Root); + var isEnd = reply.Cursor.IsEnd; + return new CommentView(comments, targetId, topComment, isEnd); + } + } +} diff --git a/src/Adapter/Adapter.Implementation/CommunityAdapter.cs b/src/Adapter/Adapter.Implementation/CommunityAdapter.cs new file mode 100644 index 000000000..1b256df50 --- /dev/null +++ b/src/Adapter/Adapter.Implementation/CommunityAdapter.cs @@ -0,0 +1,593 @@ +// Copyright (c) Richasy. All rights reserved. + +using System; +using System.Collections.Generic; +using System.Linq; +using Bili.Adapter.Interfaces; +using Bili.Models.BiliBili; +using Bili.Models.Data.Community; +using Bili.Models.Enums.Community; +using Bili.Toolkit.Interfaces; +using Bilibili.App.Archive.V1; +using Bilibili.App.Card.V1; +using Bilibili.App.Dynamic.V2; +using Bilibili.App.Interfaces.V1; +using Bilibili.App.Show.V1; + +namespace Bili.Adapter +{ + /// + /// 社区数据适配器. + /// + public sealed class CommunityAdapter : ICommunityAdapter + { + private readonly INumberToolkit _numberToolkit; + private readonly IResourceToolkit _resourceToolkit; + private readonly IImageAdapter _imageAdapter; + private readonly ITextToolkit _textToolkit; + + /// + /// Initializes a new instance of the class. + /// + /// 数字处理工具. + /// 资源管理工具. + /// 图片数据适配器. + /// 文本工具. + public CommunityAdapter( + INumberToolkit numberToolkit, + IResourceToolkit resourceToolkit, + IImageAdapter imageAdapter, + ITextToolkit textToolkit) + { + _numberToolkit = numberToolkit; + _imageAdapter = imageAdapter; + _resourceToolkit = resourceToolkit; + _textToolkit = textToolkit; + } + + /// + public BannerIdentifier ConvertToBannerIdentifier(PartitionBanner banner) + { + var id = banner.Id.ToString(); + var title = _textToolkit.ConvertToTraditionalChineseIfNeeded(banner.Title); + var image = _imageAdapter.ConvertToImage(banner.Image, 600, 180); + var uri = banner.NavigateUri; + return new BannerIdentifier(id, title, image, uri); + } + + /// + public BannerIdentifier ConvertToBannerIdentifier(LiveFeedBanner banner) + { + var id = banner.Id.ToString(); + var title = _textToolkit.ConvertToTraditionalChineseIfNeeded(banner.Title); + var image = _imageAdapter.ConvertToImage(banner.Cover, 600, 180); + var uri = banner.Link; + return new BannerIdentifier(id, title, image, uri); + } + + /// + public BannerIdentifier ConvertToBannerIdentifier(PgcModuleItem item) + { + var id = item.OriginId.ToString(); + var title = _textToolkit.ConvertToTraditionalChineseIfNeeded(item.Title); + var image = _imageAdapter.ConvertToImage(item.Cover, 600, 320); + var uri = item.WebLink; + return new BannerIdentifier(id, title, image, uri); + } + + /// + public Models.Data.Community.Partition ConvertToPartition(Models.BiliBili.Partition partition) + { + var id = partition.Tid.ToString(); + var name = _textToolkit.ConvertToTraditionalChineseIfNeeded(partition.Name); + var logo = string.IsNullOrEmpty(partition.Logo) + ? null + : _imageAdapter.ConvertToImage(partition.Logo); + var children = partition.Children?.Select(p => ConvertToPartition(p)).ToList(); + if (children?.Count > 0) + { + children.Insert(0, new Models.Data.Community.Partition(partition.Tid.ToString(), _textToolkit.ConvertToTraditionalChineseIfNeeded("推荐"))); + children.ForEach(p => p.ParentId = id); + } + + return new Models.Data.Community.Partition(id, name, logo, children); + } + + /// + public Models.Data.Community.Partition ConvertToPartition(LiveFeedHotArea area) + { + var id = area.AreaId.ToString(); + var parentId = area.ParentAreaId.ToString(); + var name = _textToolkit.ConvertToTraditionalChineseIfNeeded(area.Title); + var logo = string.IsNullOrEmpty(area.Cover) + ? null + : _imageAdapter.ConvertToImage(area.Cover); + + return new Models.Data.Community.Partition(id, name, logo, parentId: parentId); + } + + /// + public Models.Data.Community.Partition ConvertToPartition(LiveAreaGroup group) + { + var id = group.Id.ToString(); + var name = _textToolkit.ConvertToTraditionalChineseIfNeeded(group.Name); + var children = group.AreaList.Select(p => ConvertToPartition(p)).ToList(); + + return new Models.Data.Community.Partition(id, name, children: children); + } + + /// + public Models.Data.Community.Partition ConvertToPartition(LiveArea area) + { + var id = area.Id.ToString(); + var parentId = area.ParentId.ToString(); + var name = _textToolkit.ConvertToTraditionalChineseIfNeeded(area.Name); + var logo = string.IsNullOrEmpty(area.Cover) + ? null + : _imageAdapter.ConvertToImage(area.Cover); + + return new Models.Data.Community.Partition(id, name, logo, parentId: parentId); + } + + /// + public Models.Data.Community.Partition ConvertToPartition(PgcTab tab) + => new Models.Data.Community.Partition(tab.Id.ToString(), _textToolkit.ConvertToTraditionalChineseIfNeeded(tab.Title)); + + /// + public Models.Data.Community.Partition ConvertToPartition(ArticleCategory category) + { + var id = category.Id.ToString(); + var name = _textToolkit.ConvertToTraditionalChineseIfNeeded(category.Name); + var children = category.Children?.Any() ?? false + ? category.Children.Select(p => ConvertToPartition(p)).ToList() + : null; + var parentId = category.ParentId.ToString(); + + return new Models.Data.Community.Partition(id, name, children: children, parentId: parentId); + } + + /// + public ArticleCommunityInformation ConvertToArticleCommunityInformation(ArticleStats stats, string articleId) + { + return new ArticleCommunityInformation( + articleId, + stats.ViewCount, + stats.FavoriteCount, + stats.LikeCount, + stats.ReplyCount, + stats.ShareCount, + stats.CoinCount); + } + + /// + public ArticleCommunityInformation ConvertToArticleCommunityInformation(ArticleSearchItem item) + { + return new ArticleCommunityInformation( + item.Id.ToString(), + item.ViewCount, + likeCount: item.LikeCount, + commentCount: item.ReplyCount); + } + + /// + public UserCommunityInformation ConvertToUserCommunityInformation(Mine mine) + => new UserCommunityInformation( + mine.Mid.ToString(), + mine.FollowCount, + mine.FollowerCount, + mine.CoinNumber, + dynamicCount: mine.DynamicCount); + + /// + public UserCommunityInformation ConvertToUserCommunityInformation(UserSpaceInformation spaceInfo) + => new UserCommunityInformation( + spaceInfo.UserId, + spaceInfo.FollowCount, + spaceInfo.FollowerCount, + likeCount: spaceInfo.LikeInformation.LikeCount, + relation: (UserRelationStatus)spaceInfo.Relation.Status); + + /// + public UserCommunityInformation ConvertToUserCommunityInformation(MyInfo mine) + => new UserCommunityInformation( + mine.Mid.ToString(), + coinCount: mine.Coins); + + /// + public UserCommunityInformation ConvertToUserCommunityInformation(RelatedUser user) + { + var relation = user.Attribute switch + { + 0 => UserRelationStatus.Unfollow, + 2 => UserRelationStatus.Following, + 3 => UserRelationStatus.Friends, + _ => UserRelationStatus.Unknown, + }; + return new UserCommunityInformation( + user.Mid.ToString(), + relation: relation); + } + + /// + public UserCommunityInformation ConvertToUserCommunityInformation(UserSearchItem item) + { + return new UserCommunityInformation( + item.UserId.ToString(), + -1, + item.FollowerCount, + relation: (UserRelationStatus)item.Relation.Status); + } + + /// + public VideoCommunityInformation ConvertToVideoCommunityInformation(RecommendCard videoCard) + { + var playCount = _numberToolkit.GetCountNumber(videoCard.PlayCountText, "观看"); + var danmakuCount = -1d; + var trackCount = -1d; + + if (videoCard.SubStatusText.Contains("弹幕")) + { + danmakuCount = _numberToolkit.GetCountNumber(videoCard.SubStatusText, "弹幕"); + } + else + { + var tempText = videoCard.SubStatusText + .Replace("追剧", string.Empty) + .Replace("追番", string.Empty); + trackCount = _numberToolkit.GetCountNumber(tempText); + } + + return new VideoCommunityInformation( + videoCard.Parameter, + playCount: playCount, + danmakuCount: danmakuCount, + trackCount: trackCount); + } + + /// + public VideoCommunityInformation ConvertToVideoCommunityInformation(PartitionVideo video) + { + return new VideoCommunityInformation( + video.Parameter, + video.PlayCount, + video.DanmakuCount, + video.LikeCount, + commentCount: video.ReplyCount, + favoriteCount: video.FavouriteCount); + } + + /// + public VideoCommunityInformation ConvertToVideoCommunityInformation(MdlDynArchive video) + { + var danmakuCount = _numberToolkit.GetCountNumber(video.CoverLeftText3, "弹幕"); + var dynamicVideoPlayCount = _numberToolkit.GetCountNumber(video.CoverLeftText2, "观看"); + return new VideoCommunityInformation(video.Avid.ToString(), dynamicVideoPlayCount, danmakuCount); + } + + /// + public VideoCommunityInformation ConvertToVideoCommunityInformation(VideoStatusInfo statusInfo) + { + return new VideoCommunityInformation( + statusInfo.Aid.ToString(), + statusInfo.PlayCount, + statusInfo.DanmakuCount, + statusInfo.LikeCount, + favoriteCount: statusInfo.FavoriteCount, + coinCount: statusInfo.CoinCount, + commentCount: statusInfo.ReplyCount); + } + + /// + public VideoCommunityInformation ConvertToVideoCommunityInformation(Item rankItem) + { + return new VideoCommunityInformation( + rankItem.Param, + rankItem.Play, + rankItem.Danmaku, + rankItem.Like, + rankItem.Pts, + rankItem.Favourite, + commentCount: rankItem.Reply); + } + + /// + public VideoCommunityInformation ConvertToVideoCommunityInformation(Card hotVideo) + { + var share = hotVideo.SmallCoverV5.Base.ThreePointV4.SharePlane; + var playCount = _numberToolkit.GetCountNumber(share.PlayNumber, "次"); + return new VideoCommunityInformation( + share.Aid.ToString(), + playCount); + } + + /// + public VideoCommunityInformation ConvertToVideoCommunityInformation(Stat videoStat) + { + return new VideoCommunityInformation( + videoStat.Aid.ToString(), + videoStat.View, + videoStat.Danmaku, + videoStat.Like, + favoriteCount: videoStat.Fav, + coinCount: videoStat.Coin, + commentCount: videoStat.Reply, + shareCount: videoStat.Share); + } + + /// + public VideoCommunityInformation ConvertToVideoCommunityInformation(VideoSearchItem searchVideo) + { + return new VideoCommunityInformation( + searchVideo.Parameter, + searchVideo.PlayCount, + searchVideo.DanmakuCount); + } + + /// + public VideoCommunityInformation ConvertToVideoCommunityInformation(UserSpaceVideoItem video) + { + return new VideoCommunityInformation( + video.Id, + video.PlayCount, + video.DanmakuCount); + } + + /// + public VideoCommunityInformation ConvertToVideoCommunityInformation(FavoriteMedia video) + { + return new VideoCommunityInformation( + video.Id.ToString(), + video.Stat.PlayCount, + video.Stat.DanmakuCount, + favoriteCount: video.Stat.FavoriteCount); + } + + /// + public VideoCommunityInformation ConvertToVideoCommunityInformation(PgcEpisodeStat stat) + { + return new VideoCommunityInformation( + default, + stat.PlayCount, + stat.DanmakuCount, + stat.LikeCount, + coinCount: stat.CoinCount, + commentCount: stat.ReplyCount); + } + + /// + public VideoCommunityInformation ConvertToVideoCommunityInformation(PgcInformationStat stat) + { + var tracingCount = _numberToolkit.GetCountNumber(stat.FollowerDisplayText); + return new VideoCommunityInformation( + default, + stat.PlayCount, + stat.DanmakuCount, + stat.LikeCount, + -1, + stat.FavoriteCount, + stat.CoinCount, + stat.ReplyCount, + stat.ShareCount); + } + + /// + public VideoCommunityInformation ConvertToVideoCommunityInformation(PgcItemStat stat) + { + return new VideoCommunityInformation( + default, + stat.ViewCount, + stat.DanmakuCount, + trackCount: stat.FollowCount); + } + + /// + public VideoCommunityInformation ConvertToVideoCommunityInformation(PgcSearchItem item) + { + return new VideoCommunityInformation( + item.SeasonId.ToString(), + score: item.Rating); + } + + /// + public VideoCommunityInformation ConvertToVideoCommunityInformation(PgcPlayListItemStat stat) + { + return new VideoCommunityInformation( + default, + stat.PlayCount, + stat.DanmakuCount, + favoriteCount: stat.FavoriteCount); + } + + /// + public VideoCommunityInformation ConvertToVideoCommunityInformation(CardUGC ugc) + => new VideoCommunityInformation(default, ugc.View); + + /// + public VideoCommunityInformation CovnertToVideoCommunityInformation(VideoStatusInfo info) + { + return new VideoCommunityInformation( + info.Aid.ToString(), + info.PlayCount, + info.DanmakuCount, + info.LikeCount, + favoriteCount: info.FavoriteCount, + coinCount: info.CoinCount, + commentCount: info.ReplyCount, + shareCount: info.ShareCount); + } + + /// + public UnreadInformation ConvertToUnreadInformation(UnreadMessage message) + => new UnreadInformation(message.At, message.Reply, message.Like); + + /// + public MessageInformation ConvertToMessageInformation(LikeMessageItem messageItem) + { + var isMultiple = messageItem.Users.Count > 1; + var firstUser = messageItem.Users[0]; + var userName = firstUser.UserName; + var avatar = _imageAdapter.ConvertToImage(firstUser.Avatar, 48, 48); + string message; + if (isMultiple) + { + var secondUser = messageItem.Users[1]; + message = string.Format( + _resourceToolkit.GetLocaleString(Models.Enums.LanguageNames.LikeMessageMultipleDescription), + userName, + secondUser.UserName, + messageItem.Count, + messageItem.Item.Business); + } + else + { + message = string.Format( + _resourceToolkit.GetLocaleString(Models.Enums.LanguageNames.LikeMessageSingleDescription), + userName, + messageItem.Item.Business); + } + + var publishTime = DateTimeOffset.FromUnixTimeSeconds(messageItem.LikeTime).DateTime; + var id = messageItem.Id.ToString(); + var sourceContent = string.IsNullOrEmpty(messageItem.Item.Title) + ? messageItem.Item.Description + : messageItem.Item.Title; + sourceContent = _textToolkit.ConvertToTraditionalChineseIfNeeded(sourceContent); + var sourceId = messageItem.Item.Uri; + + return new MessageInformation( + id, + Models.Enums.App.MessageType.Like, + avatar, + string.Empty, + isMultiple, + publishTime, + string.Empty, + message, + sourceContent, + sourceId); + } + + /// + public MessageInformation ConvertToMessageInformation(AtMessageItem messageItem) + { + var user = messageItem.User; + var userName = user.UserName; + var avatar = _imageAdapter.ConvertToImage(user.Avatar, 48, 48); + var subtitle = string.Format( + _resourceToolkit.GetLocaleString(Models.Enums.LanguageNames.AtMessageTypeDescription), + messageItem.Item.Business); + var message = _textToolkit.ConvertToTraditionalChineseIfNeeded(messageItem.Item.SourceContent); + var sourceContent = string.IsNullOrEmpty(messageItem.Item.Title) + ? _resourceToolkit.GetLocaleString(Models.Enums.LanguageNames.NoSpecificData) + : messageItem.Item.Title; + sourceContent = _textToolkit.ConvertToTraditionalChineseIfNeeded(sourceContent); + var publishTime = DateTimeOffset.FromUnixTimeSeconds(messageItem.AtTime).DateTime; + var id = messageItem.Id.ToString(); + var sourceId = messageItem.Item.Uri; + + return new MessageInformation( + id, + Models.Enums.App.MessageType.At, + avatar, + userName, + false, + publishTime, + subtitle, + message, + sourceContent, + sourceId); + } + + /// + public MessageInformation ConvertToMessageInformation(ReplyMessageItem messageItem) + { + var user = messageItem.User; + var userName = user.UserName; + var avatar = _imageAdapter.ConvertToImage(user.Avatar, 48, 48); + var isMultiple = messageItem.IsMultiple == 1; + var subtitle = string.Format( + _resourceToolkit.GetLocaleString(Models.Enums.LanguageNames.ReplyMessageTypeDescription), + messageItem.Item.Business, + messageItem.Counts); + var message = _textToolkit.ConvertToTraditionalChineseIfNeeded(messageItem.Item.SourceContent); + var sourceContent = string.IsNullOrEmpty(messageItem.Item.Title) + ? messageItem.Item.Description + : messageItem.Item.Title; + sourceContent = _textToolkit.ConvertToTraditionalChineseIfNeeded(sourceContent); + var publishTime = DateTimeOffset.FromUnixTimeSeconds(messageItem.ReplyTime).DateTime; + var id = messageItem.Id.ToString(); + var sourceId = messageItem.Item.SubjectId.ToString(); + var properties = new Dictionary() + { + { "type", messageItem.Item.BusinessId }, + }; + + return new MessageInformation( + id, + Models.Enums.App.MessageType.Reply, + avatar, + userName, + isMultiple, + publishTime, + subtitle, + message, + sourceContent, + sourceId, + properties); + } + + /// + public MessageView ConvertToMessageView(LikeMessageResponse messageResponse) + { + var cursor = messageResponse.Total.Cursor; + var items = new List(); + if (messageResponse.Latest != null) + { + items = items + .Concat(messageResponse.Latest.Items.Select(p => ConvertToMessageInformation(p))) + .ToList(); + } + + if (messageResponse.Total != null) + { + items = items + .Concat(messageResponse.Total.Items.Select(p => ConvertToMessageInformation(p))) + .ToList(); + } + + return new MessageView(items, cursor.IsEnd); + } + + /// + public MessageView ConvertToMessageView(AtMessageResponse messageResponse) + { + var cursor = messageResponse.Cursor; + var items = messageResponse.Items.Select(p => ConvertToMessageInformation(p)).ToList(); + return new MessageView(items, cursor.IsEnd); + } + + /// + public MessageView ConvertToMessageView(ReplyMessageResponse messageResponse) + { + var cursor = messageResponse.Cursor; + var items = messageResponse.Items.Select(p => ConvertToMessageInformation(p)).ToList(); + return new MessageView(items, cursor.IsEnd); + } + + /// + public FollowGroup ConvertToFollowGroup(RelatedTag tag) + => new FollowGroup(tag.TagId.ToString(), _textToolkit.ConvertToTraditionalChineseIfNeeded(tag.Name), tag.Count); + + /// + public DynamicCommunityInformation ConvertToDynamicCommunityInformation(ModuleStat stat, string dynamicId) + => new DynamicCommunityInformation(dynamicId, stat.Like, stat.Reply, stat.LikeInfo.IsLike); + + /// + public TripleInformation ConvertToTripleInformation(TripleResult result, string id) + => new TripleInformation(id, result.IsLike, result.IsCoin, result.IsFavorite); + + /// + public EpisodeInteractionInformation ConvertToEpisodeInteractionInformation(EpisodeInteraction interaction) + => new EpisodeInteractionInformation(interaction.IsLike == 1, interaction.CoinNumber > 0, interaction.IsFavorite == 1); + } +} diff --git a/src/Adapter/Adapter.Implementation/DynamicAdapter.cs b/src/Adapter/Adapter.Implementation/DynamicAdapter.cs new file mode 100644 index 000000000..27a35a83d --- /dev/null +++ b/src/Adapter/Adapter.Implementation/DynamicAdapter.cs @@ -0,0 +1,242 @@ +// Copyright (c) Richasy. All rights reserved. + +using System; +using System.Linq; +using Bili.Adapter.Interfaces; +using Bili.Models.Data.Appearance; +using Bili.Models.Data.Community; +using Bili.Models.Data.Dynamic; +using Bili.Models.Data.User; +using Bili.Models.Enums.Bili; +using Bili.Models.Enums.Community; +using Bili.Toolkit.Interfaces; +using Bilibili.App.Dynamic.V2; + +namespace Bili.Adapter +{ + /// + /// 动态数据适配器. + /// + public sealed class DynamicAdapter : IDynamicAdapter + { + private readonly IUserAdapter _userAdapter; + private readonly IImageAdapter _imageAdapter; + private readonly IVideoAdapter _videoAdapter; + private readonly IPgcAdapter _pgcAdapter; + private readonly IArticleAdapter _articleAdapter; + private readonly ICommunityAdapter _communityAdapter; + private readonly ITextToolkit _textToolkit; + + /// + /// Initializes a new instance of the class. + /// + /// 用户数据适配器. + /// 图片数据适配器. + /// 视频数据适配器. + /// PGC 数据适配器. + /// 文章数据适配器. + /// 社区数据适配器. + /// 文本工具. + public DynamicAdapter( + IUserAdapter userAdapter, + IImageAdapter imageAdapter, + IVideoAdapter videoAdapter, + IPgcAdapter pgcAdapter, + IArticleAdapter articleAdapter, + ICommunityAdapter communityAdapter, + ITextToolkit textToolkit) + { + _userAdapter = userAdapter; + _imageAdapter = imageAdapter; + _videoAdapter = videoAdapter; + _pgcAdapter = pgcAdapter; + _articleAdapter = articleAdapter; + _communityAdapter = communityAdapter; + _textToolkit = textToolkit; + } + + /// + public DynamicInformation ConvertToDynamicInformation(DynamicItem item) + { + var modules = item.Modules; + var userModule = modules.Where(p => p.ModuleType == DynModuleType.ModuleAuthor).FirstOrDefault()?.ModuleAuthor; + var descModule = modules.Where(p => p.ModuleType == DynModuleType.ModuleDesc).FirstOrDefault()?.ModuleDesc; + var mainModule = modules.Where(p => p.ModuleType == DynModuleType.ModuleDynamic).FirstOrDefault()?.ModuleDynamic; + var dataModule = modules.Where(p => p.ModuleType == DynModuleType.ModuleStat).FirstOrDefault()?.ModuleStat; + + UserProfile user = default; + string tip = default; + EmoteText description = default; + DynamicCommunityInformation communityInfo = default; + var dynamicId = item.Extend.DynIdStr; + var replyType = GetReplyTypeFromDynamicType(item.CardType); + var replyId = replyType == CommentType.Dynamic + ? dynamicId + : item.Extend.BusinessId; + var dynamicType = GetDynamicItemType(mainModule); + var dynamicData = GetDynamicContent(mainModule); + if (userModule != null) + { + var author = userModule.Author; + user = _userAdapter.ConvertToUserProfile(author.Mid, author.Name, author.Face, Models.Enums.App.AvatarSize.Size64); + tip = userModule.PtimeLabelText; + } + else + { + var forwardUserModule = modules.Where(p => p.ModuleType == DynModuleType.ModuleAuthorForward).FirstOrDefault()?.ModuleAuthorForward; + if (forwardUserModule != null) + { + var name = forwardUserModule.Title.FirstOrDefault()?.Text ?? "--"; + user = _userAdapter.ConvertToUserProfile(forwardUserModule.Uid, name, forwardUserModule.FaceUrl, Models.Enums.App.AvatarSize.Size32); + tip = forwardUserModule.PtimeLabelText; + } + } + + tip = _textToolkit.ConvertToTraditionalChineseIfNeeded(tip); + + if (descModule != null) + { + description = _imageAdapter.ConvertToEmoteText(descModule); + } + + if (dataModule != null) + { + communityInfo = _communityAdapter.ConvertToDynamicCommunityInformation(dataModule, dynamicId); + } + + return new DynamicInformation( + dynamicId, + user, + tip, + communityInfo, + replyType, + replyId, + dynamicType, + dynamicData, + description); + } + + /// + public DynamicInformation ConvertToDynamicInformation(MdlDynForward forward) + { + var item = forward.Item; + return ConvertToDynamicInformation(item); + } + + /// + public DynamicView ConvertToDynamicView(DynVideoReply reply) + { + var list = reply.DynamicList.List.Where(p => + p.CardType == DynamicType.Av + || p.CardType == DynamicType.Pgc + || p.CardType == DynamicType.UgcSeason) + .Select(p => ConvertToDynamicInformation(p)) + .ToList(); + + return new DynamicView(list); + } + + /// + public DynamicView ConvertToDynamicView(DynAllReply reply) + { + var list = reply.DynamicList.List.Where(p => + p.CardType != DynamicType.DynNone + && p.CardType != DynamicType.Ad) + .Select(p => ConvertToDynamicInformation(p)) + .ToList(); + + return new DynamicView(list); + } + + private CommentType GetReplyTypeFromDynamicType(DynamicType type) + { + var replyType = CommentType.None; + switch (type) + { + case DynamicType.Forward: + case DynamicType.Word: + case DynamicType.Live: + replyType = CommentType.Dynamic; + break; + case DynamicType.Draw: + replyType = CommentType.Album; + break; + case DynamicType.Av: + case DynamicType.Pgc: + case DynamicType.UgcSeason: + case DynamicType.Medialist: + replyType = CommentType.Video; + break; + case DynamicType.Courses: + case DynamicType.CoursesSeason: + replyType = CommentType.Course; + break; + case DynamicType.Article: + replyType = CommentType.Article; + break; + case DynamicType.Music: + replyType = CommentType.Music; + break; + default: + break; + } + + return replyType; + } + + private DynamicItemType GetDynamicItemType(ModuleDynamic dynamic) + { + if (dynamic == null) + { + return DynamicItemType.PlainText; + } + + var type = dynamic.Type switch + { + ModuleDynamicType.MdlDynArchive => dynamic.DynArchive.IsPGC + ? DynamicItemType.Pgc + : DynamicItemType.Video, + ModuleDynamicType.MdlDynPgc => DynamicItemType.Pgc, + ModuleDynamicType.MdlDynForward => DynamicItemType.Forward, + ModuleDynamicType.MdlDynDraw => DynamicItemType.Image, + ModuleDynamicType.MdlDynArticle => DynamicItemType.Article, + _ => DynamicItemType.Unsupported, + }; + + return type; + } + + private object GetDynamicContent(ModuleDynamic dynamic) + { + if (dynamic == null) + { + return null; + } + + if (dynamic.Type == ModuleDynamicType.MdlDynPgc) + { + return _pgcAdapter.ConvertToEpisodeInformation(dynamic.DynPgc); + } + else if (dynamic.Type == ModuleDynamicType.MdlDynArchive) + { + return dynamic.DynArchive.IsPGC + ? _pgcAdapter.ConvertToEpisodeInformation(dynamic.DynArchive) + : _videoAdapter.ConvertToVideoInformation(dynamic.DynArchive); + } + else if (dynamic.Type == ModuleDynamicType.MdlDynForward) + { + return ConvertToDynamicInformation(dynamic.DynForward); + } + else if (dynamic.Type == ModuleDynamicType.MdlDynDraw) + { + return dynamic.DynDraw.Items.Select(p => _imageAdapter.ConvertToImage(p.Src, 100d, 100d)).ToList(); + } + else if (dynamic.Type == ModuleDynamicType.MdlDynArticle) + { + return _articleAdapter.ConvertToArticleInformation(dynamic.DynArticle); + } + + return null; + } + } +} diff --git a/src/Adapter/Adapter.Implementation/FavoriteAdapter.cs b/src/Adapter/Adapter.Implementation/FavoriteAdapter.cs new file mode 100644 index 000000000..18162b340 --- /dev/null +++ b/src/Adapter/Adapter.Implementation/FavoriteAdapter.cs @@ -0,0 +1,100 @@ +// Copyright (c) Richasy. All rights reserved. + +using System.Collections.Generic; +using System.Linq; +using Bili.Adapter.Interfaces; +using Bili.Models.BiliBili; +using Bili.Models.Data.Video; +using Bili.Toolkit.Interfaces; + +namespace Bili.Adapter +{ + /// + /// 收藏夹数据适配器. + /// + public sealed class FavoriteAdapter : IFavoriteAdapter + { + private readonly IImageAdapter _imageAdapter; + private readonly IUserAdapter _userAdapter; + private readonly IVideoAdapter _videoAdapter; + private readonly ITextToolkit _textToolkit; + + /// + /// Initializes a new instance of the class. + /// + /// 图片数据适配器. + /// 用户数据适配器. + /// 视频数据适配器. + /// 文本工具. + public FavoriteAdapter( + IImageAdapter imageAdapter, + IUserAdapter userAdapter, + IVideoAdapter videoAdapter, + ITextToolkit textToolkit) + { + _imageAdapter = imageAdapter; + _userAdapter = userAdapter; + _videoAdapter = videoAdapter; + _textToolkit = textToolkit; + } + + /// + public VideoFavoriteFolder ConvertToVideoFavoriteFolder(FavoriteListDetail detail) + { + var id = detail.Id.ToString(); + var title = _textToolkit.ConvertToTraditionalChineseIfNeeded(detail.Title); + var cover = string.IsNullOrEmpty(detail.Cover) + ? null + : _imageAdapter.ConvertToImage(detail.Cover, 160, 120); + var user = string.IsNullOrEmpty(detail.Publisher?.Publisher) + ? null + : _userAdapter.ConvertToRoleProfile(detail.Publisher, Models.Enums.App.AvatarSize.Size48).User; + var desc = _textToolkit.ConvertToTraditionalChineseIfNeeded(detail.Description); + var count = detail.MediaCount; + + return new VideoFavoriteFolder(id, title, cover, user, desc, count); + } + + /// + public VideoFavoriteFolder ConvertToVideoFavoriteFolder(FavoriteMeta meta) + { + var id = meta.Id.ToString(); + var title = _textToolkit.ConvertToTraditionalChineseIfNeeded(meta.Title); + var count = meta.MediaCount; + + return new VideoFavoriteFolder(id, title, default, default, default, count); + } + + /// + public VideoFavoriteFolderDetail ConvertToVideoFavoriteFolderDetail(VideoFavoriteListResponse response) + { + var folder = ConvertToVideoFavoriteFolder(response.Detail ?? response.Information); + var videos = response.Medias?.Select(p => _videoAdapter.ConvertToVideoInformation(p)) ?? new List(); + var videoSet = new VideoSet(videos, folder.TotalCount); + return new VideoFavoriteFolderDetail(folder, videoSet); + } + + /// + public VideoFavoriteFolderGroup ConvertToVideoFavoriteFolderGroup(FavoriteFolder folder) + { + var id = folder.Id; + var name = _textToolkit.ConvertToTraditionalChineseIfNeeded(folder.Name); + var isMine = id == 1; + var folders = folder.MediaList?.List?.Select(p => ConvertToVideoFavoriteFolder(p)) ?? new List(); + var set = new VideoFavoriteSet(folders, folder.MediaList?.Count ?? 0); + return new VideoFavoriteFolderGroup(id.ToString(), name, isMine, set); + } + + /// + public VideoFavoriteView ConvertToVideoFavoriteView(VideoFavoriteGalleryResponse response) + { + var defaultFolder = ConvertToVideoFavoriteFolderDetail(response.DefaultFavoriteList); + + // 过滤稍后再看的内容,稍后再看列表的Id为3. + var favoriteSets = response.FavoriteFolderList? + .Where(p => p.Id != 3) + .Select(p => ConvertToVideoFavoriteFolderGroup(p)); + return new VideoFavoriteView(favoriteSets, defaultFolder); + } + } +} diff --git a/src/Adapter/Adapter.Implementation/ImageAdapter.cs b/src/Adapter/Adapter.Implementation/ImageAdapter.cs new file mode 100644 index 000000000..f97e0f491 --- /dev/null +++ b/src/Adapter/Adapter.Implementation/ImageAdapter.cs @@ -0,0 +1,103 @@ +// Copyright (c) Richasy. All rights reserved. + +using System.Collections.Generic; +using System.Linq; +using Bili.Adapter.Interfaces; +using Bili.Models.App.Constants; +using Bili.Models.Data.Appearance; +using Bili.Toolkit.Interfaces; +using Bilibili.App.Dynamic.V2; +using Bilibili.Main.Community.Reply.V1; + +namespace Bili.Adapter +{ + /// + /// 图片适配器,将视频封面、用户头像等转换为 . + /// + public class ImageAdapter : IImageAdapter + { + private readonly ITextToolkit _textToolkit; + + /// + /// Initializes a new instance of the class. + /// + /// 文本工具. + public ImageAdapter(ITextToolkit textToolkit) + => _textToolkit = textToolkit; + + /// + public Image ConvertToImage(string uri) + => new Image(uri); + + /// + public Image ConvertToImage(string uri, double width, double height) + => new Image(uri, width, height, (w, h) => $"@{w}w_{h}h_1c_100q.jpg"); + + /// + public Image ConvertToVideoCardCover(string uri) + => ConvertToImage(uri, AppConstants.VideoCardCoverWidth, AppConstants.VideoCardCoverHeight); + + /// + public Image ConvertToPgcCover(string uri) + => ConvertToImage(uri, AppConstants.PgcCoverWidth, AppConstants.PgcCoverHeight); + + /// + public Image ConvertToArticleCardCover(string uri) + => ConvertToImage(uri, AppConstants.ArticleCardCoverWidth, AppConstants.ArticleCardCoverHeight); + + /// + public EmoteText ConvertToEmoteText(ModuleDesc description) + { + var text = _textToolkit.ConvertToTraditionalChineseIfNeeded(description.Text); + var descs = description.Desc; + var emoteDict = new Dictionary(); + + // 判断是否有表情存在. + if (descs.Count > 0 && descs.Any(p => p.Type == DescType.Emoji)) + { + var emotes = descs.Where(p => p.Type == DescType.Emoji); + foreach (var item in emotes) + { + var t = _textToolkit.ConvertToTraditionalChineseIfNeeded(item.Text); + if (!emoteDict.ContainsKey(t)) + { + emoteDict.Add(t, ConvertToImage(item.Uri)); + } + } + } + else + { + emoteDict = null; + } + + return new EmoteText(text, emoteDict); + } + + /// + public EmoteText ConvertToEmoteText(Content content) + { + var text = _textToolkit.ConvertToTraditionalChineseIfNeeded(content.Message); + var emotes = content.Emote; + var emoteDict = new Dictionary(); + + // 判断是否有表情存在. + if (emotes?.Count > 0) + { + foreach (var item in emotes) + { + var k = _textToolkit.ConvertToTraditionalChineseIfNeeded(item.Key); + if (!emoteDict.ContainsKey(k)) + { + emoteDict.Add(k, ConvertToImage(item.Value.Url)); + } + } + } + else + { + emoteDict = null; + } + + return new EmoteText(text, emoteDict); + } + } +} diff --git a/src/Adapter/Adapter.Implementation/LiveAdapter.cs b/src/Adapter/Adapter.Implementation/LiveAdapter.cs new file mode 100644 index 000000000..6b5423e4b --- /dev/null +++ b/src/Adapter/Adapter.Implementation/LiveAdapter.cs @@ -0,0 +1,190 @@ +// Copyright (c) Richasy. All rights reserved. + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Net; +using System.Text.RegularExpressions; +using Bili.Adapter.Interfaces; +using Bili.Models.BiliBili; +using Bili.Models.Data.Live; +using Bili.Models.Data.Player; +using Bili.Models.Data.Video; +using Bili.Toolkit.Interfaces; + +namespace Bili.Adapter +{ + /// + /// 直播数据适配器. + /// + public sealed class LiveAdapter : ILiveAdapter + { + private readonly IUserAdapter _userAdapter; + private readonly IImageAdapter _imageAdapter; + private readonly ICommunityAdapter _communityAdapter; + private readonly INumberToolkit _numberToolkit; + private readonly ITextToolkit _textToolkit; + + /// + /// Initializes a new instance of the class. + /// + /// 用户数据适配器. + /// 图片数据适配器. + /// 社区数据适配器. + /// 数字转换工具. + /// 文本工具. + public LiveAdapter( + IUserAdapter userAdapter, + IImageAdapter imageAdapter, + ICommunityAdapter communityAdapter, + INumberToolkit numberToolkit, + ITextToolkit textToolkit) + { + _userAdapter = userAdapter; + _imageAdapter = imageAdapter; + _communityAdapter = communityAdapter; + _numberToolkit = numberToolkit; + _textToolkit = textToolkit; + } + + /// + public LiveInformation ConvertToLiveInformation(LiveFeedRoom room) + { + var title = _textToolkit.ConvertToTraditionalChineseIfNeeded(room.Title); + var id = room.RoomId.ToString(); + var viewerCount = room.ViewerCount; + var user = _userAdapter.ConvertToUserProfile(room.UserId, room.UserName, room.UserAvatar, Models.Enums.App.AvatarSize.Size48); + var cover = _imageAdapter.ConvertToVideoCardCover(room.Cover); + var subtitle = _textToolkit.ConvertToTraditionalChineseIfNeeded(room.DisplayAreaName); + + var identifier = new VideoIdentifier(id, title, -1, cover); + return new LiveInformation( + identifier, + user, + viewerCount, + subtitle: subtitle); + } + + /// + public LiveInformation ConvertToLiveInformation(LiveRoomCard card) + { + var title = _textToolkit.ConvertToTraditionalChineseIfNeeded(card.Title); + var id = card.RoomId.ToString(); + var viewerCount = _numberToolkit.GetCountNumber(card.CoverRightContent.Text); + var subtitle = card.CoverLeftContent.Text; + var cover = _imageAdapter.ConvertToVideoCardCover(card.Cover); + var identifier = new VideoIdentifier(id, title, -1, cover); + return new LiveInformation(identifier, default, viewerCount, subtitle: subtitle); + } + + /// + public LiveInformation ConvertToLiveInformation(LiveSearchItem item) + { + var title = _textToolkit.ConvertToTraditionalChineseIfNeeded(item.Title); + var id = item.RoomId.ToString(); + var viewerCount = item.ViewerCount; + var cover = _imageAdapter.ConvertToVideoCardCover(item.Cover); + var subtitle = item.Name; + + var identifier = new VideoIdentifier(id, title, -1, cover); + return new LiveInformation(identifier, null, viewerCount, subtitle: subtitle); + } + + /// + public LivePlayerView ConvertToLivePlayerView(LiveRoomDetail detail) + { + var roomInfo = detail.RoomInformation; + var title = _textToolkit.ConvertToTraditionalChineseIfNeeded(roomInfo.Title); + var id = roomInfo.RoomId.ToString(); + var description = string.IsNullOrEmpty(roomInfo.Description) + ? string.Empty + : Regex.Replace(roomInfo.Description, @"<[^>]*>", string.Empty); + + if (!string.IsNullOrEmpty(description)) + { + description = WebUtility.HtmlDecode(description).Trim(); + } + + if (string.IsNullOrEmpty(description)) + { + description = "暂无直播间介绍"; + } + + description = _textToolkit.ConvertToTraditionalChineseIfNeeded(description); + + var viewerCount = roomInfo.ViewerCount; + var cover = _imageAdapter.ConvertToImage(roomInfo.Cover ?? roomInfo.Keyframe); + var userInfo = detail.AnchorInformation.UserBasicInformation; + var userProfile = _userAdapter.ConvertToUserProfile(roomInfo.UserId, userInfo.UserName, userInfo.Avatar, Models.Enums.App.AvatarSize.Size48); + var partition = $"{roomInfo.ParentAreaName} · {roomInfo.AreaName}"; + var subtitle = DateTimeOffset.FromUnixTimeSeconds(detail.RoomInformation.LiveStartTime).ToLocalTime().ToString("yyyy/MM/dd HH:mm"); + + var identifier = new VideoIdentifier(id, title, -1, cover); + var info = new LiveInformation(identifier, userProfile, viewerCount, subtitle: subtitle, description: description); + return new LivePlayerView(info, partition); + } + + /// + public LiveFeedView ConvertToLiveFeedView(LiveFeedResponse response) + { + var recommendRooms = response.CardList.Where(p => p.CardType.Contains("small_card")) + .Where(p => p.CardData?.LiveCard != null) + .Select(p => ConvertToLiveInformation(p.CardData.LiveCard)) + .ToList(); + var followRooms = response.CardList.Where(p => p.CardType.Contains("idol")) + .SelectMany(p => p.CardData?.FollowList?.List) + .Select(p => ConvertToLiveInformation(p)) + .ToList(); + var banners = response.CardList.Where(p => p.CardType.Contains("banner")) + .SelectMany(p => p.CardData?.Banners?.List) + .Select(p => _communityAdapter.ConvertToBannerIdentifier(p)) + .ToList(); + var partitions = response.CardList.Where(p => p.CardType.Contains("area")) + .SelectMany(p => p.CardData?.HotAreas?.List) + .Where(p => p.Id != 0) + .Select(p => _communityAdapter.ConvertToPartition(p)) + .ToList(); + + return new LiveFeedView(banners, partitions, followRooms, recommendRooms); + } + + /// + public LivePartitionView ConvertToLivePartitionView(LiveAreaDetailResponse response) + { + var lives = response.List.Select(p => ConvertToLiveInformation(p)).ToList(); + var tags = response.Tags?.Count > 0 + ? response.Tags.Select(p => new LiveTag(p.Id.ToString(), p.Name, p.SortType)).ToList() + : new List() { new LiveTag(string.Empty, _textToolkit.ConvertToTraditionalChineseIfNeeded("全部"), string.Empty) }; + return new LivePartitionView(response.Count, lives, tags); + } + + /// + public LiveMediaInformation ConvertToLiveMediaInformation(LiveAppPlayInformation information) + { + var id = information.RoomId.ToString(); + var playInfo = information.PlayUrlInfo.PlayUrl; + var formats = new List(); + foreach (var item in playInfo.Descriptions) + { + var desc = _textToolkit.ConvertToTraditionalChineseIfNeeded(item.Description); + formats.Add(new FormatInformation(item.Quality, desc, false)); + } + + var lines = new List(); + foreach (var stream in playInfo.StreamList) + { + foreach (var format in stream.FormatList) + { + foreach (var codec in format.CodecList) + { + var name = codec.CodecName; + var urls = codec.Urls.Select(p => new LivePlayUrl(p.Host, codec.BaseUrl, p.Extra)); + lines.Add(new LivePlaylineInformation(name, codec.CurrentQuality, codec.AcceptQualities, urls)); + } + } + } + + return new LiveMediaInformation(id, formats, lines); + } + } +} diff --git a/src/Adapter/Adapter.Implementation/PgcAdapter.cs b/src/Adapter/Adapter.Implementation/PgcAdapter.cs new file mode 100644 index 000000000..fd479c597 --- /dev/null +++ b/src/Adapter/Adapter.Implementation/PgcAdapter.cs @@ -0,0 +1,589 @@ +// Copyright (c) Richasy. All rights reserved. + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text.RegularExpressions; +using Bili.Adapter.Interfaces; +using Bili.Models.App.Constants; +using Bili.Models.BiliBili; +using Bili.Models.Data.Appearance; +using Bili.Models.Data.Pgc; +using Bili.Models.Data.Player; +using Bili.Models.Data.Video; +using Bili.Models.Enums; +using Bili.Models.Enums.Player; +using Bili.Toolkit.Interfaces; +using Bilibili.App.Dynamic.V2; + +namespace Bili.Adapter +{ + /// + /// PGC 内容适配器. + /// + public sealed class PgcAdapter : IPgcAdapter + { + private readonly IImageAdapter _imageAdapter; + private readonly IUserAdapter _userAdapter; + private readonly ICommunityAdapter _communityAdapter; + private readonly ITextToolkit _textToolkit; + + /// + /// Initializes a new instance of the class. + /// + /// 图片适配器. + /// 社区信息适配器. + /// 用户适配器. + /// 文本工具. + public PgcAdapter( + IImageAdapter imageAdapter, + ICommunityAdapter communityAdapter, + IUserAdapter userAdapter, + ITextToolkit textToolkit) + { + _imageAdapter = imageAdapter; + _communityAdapter = communityAdapter; + _userAdapter = userAdapter; + _textToolkit = textToolkit; + } + + /// + public EpisodeInformation ConvertToEpisodeInformation(PgcEpisodeDetail episode) + { + var epid = episode.Report.EpisodeId; + var title = string.IsNullOrEmpty(episode.LongTitle) + ? episode.ShareTitle + : episode.LongTitle; + title = _textToolkit.ConvertToTraditionalChineseIfNeeded(title); + var subtitle = _textToolkit.ConvertToTraditionalChineseIfNeeded(episode.Subtitle); + var duration = episode.Duration / 1000; + var cover = _imageAdapter.ConvertToVideoCardCover(episode.Cover); + var seasonId = episode.Report.SeasonId; + var aid = episode.Aid.ToString(); + var cid = episode.PartId.ToString(); + var index = episode.Index; + var isPv = episode.IsPV == 1; + var isVip = episode.BadgeText.Contains("会员"); + var publishTime = DateTimeOffset.FromUnixTimeSeconds(episode.PublishTime).ToLocalTime().DateTime; + var communityInfo = _communityAdapter.ConvertToVideoCommunityInformation(episode.Stat); + var seasonType = episode.Report.SeasonType; + + var identifier = new VideoIdentifier(epid, title, duration, cover); + return new EpisodeInformation( + identifier, + seasonId, + aid, + subtitle, + index, + isVip, + isPv, + publishTime, + communityInfo, + episode.BadgeText, + cid, + seasonType); + } + + /// + public EpisodeInformation ConvertToEpisodeInformation(RecommendCard card) + { + if (card.CardGoto != ServiceConstants.Bangumi) + { + throw new ArgumentException($"推荐卡片的 CardGoTo 属性应该是 {ServiceConstants.Bangumi},这里是 {card.Goto},不符合要求,请检查分配条件", nameof(card)); + } + + var epid = card.Parameter; + var title = _textToolkit.ConvertToTraditionalChineseIfNeeded(card.Title); + var subtitle = _textToolkit.ConvertToTraditionalChineseIfNeeded(card.Description); + var communityInfo = _communityAdapter.ConvertToVideoCommunityInformation(card); + var cover = _imageAdapter.ConvertToVideoCardCover(card.Cover); + + var identifier = new VideoIdentifier(epid, title, -1, cover); + return new EpisodeInformation(identifier, subtitle: subtitle, communityInformation: communityInfo); + } + + /// + public EpisodeInformation ConvertToEpisodeInformation(PgcModuleItem item) + { + var title = _textToolkit.ConvertToTraditionalChineseIfNeeded(item.Title); + var epid = item.Aid.ToString(); + var ssid = item.OriginId.ToString(); + var cover = _imageAdapter.ConvertToVideoCardCover(item.Cover); + var highlight = _textToolkit.ConvertToTraditionalChineseIfNeeded(item.Badge); + var communityInfo = _communityAdapter.ConvertToVideoCommunityInformation(item.Stat); + communityInfo.Id = epid; + var subtitle = item.Stat?.FollowDisplayText ?? item.DisplayScoreText; + subtitle = _textToolkit.ConvertToTraditionalChineseIfNeeded(subtitle); + + var identifier = new VideoIdentifier(epid, title, -1, cover); + return new EpisodeInformation( + identifier, + ssid, + subtitle: subtitle, + communityInformation: communityInfo, + highlight: highlight); + } + + /// + public EpisodeInformation ConvertToEpisodeInformation(MdlDynPGC pgc) + { + var title = _textToolkit.ConvertToTraditionalChineseIfNeeded(pgc.Title); + var ssid = pgc.SeasonId.ToString(); + var epid = pgc.Epid.ToString(); + var aid = pgc.Aid.ToString(); + var isPv = pgc.IsPreview; + var cover = _imageAdapter.ConvertToVideoCardCover(pgc.Cover); + var duration = pgc.Duration; + + var identifier = new VideoIdentifier(epid, title, duration, cover); + return new EpisodeInformation(identifier, ssid, aid, isPv: isPv); + } + + /// + public EpisodeInformation ConvertToEpisodeInformation(MdlDynArchive archive) + { + var title = _textToolkit.ConvertToTraditionalChineseIfNeeded(archive.Title); + var ssid = archive.PgcSeasonId.ToString(); + var epid = archive.EpisodeId.ToString(); + var aid = archive.Avid.ToString(); + var isPv = archive.IsPreview; + var cover = _imageAdapter.ConvertToVideoCardCover(archive.Cover); + var duration = archive.Duration; + + var identifier = new VideoIdentifier(epid, title, duration, cover); + return new EpisodeInformation(identifier, ssid, aid, isPv: isPv); + } + + /// + public SeasonInformation ConvertToSeasonInformation(PgcModuleItem item, PgcType type) + { + var title = _textToolkit.ConvertToTraditionalChineseIfNeeded(item.Title); + var ssid = item.OriginId.ToString(); + var cover = _imageAdapter.ConvertToPgcCover(item.Cover); + var tags = _textToolkit.ConvertToTraditionalChineseIfNeeded(item.SeasonTags); + var highlight = _textToolkit.ConvertToTraditionalChineseIfNeeded(item.Badge); + var communityInfo = _communityAdapter.ConvertToVideoCommunityInformation(item.Stat); + communityInfo.Id = ssid; + var subtitle = _textToolkit.ConvertToTraditionalChineseIfNeeded(item.Description); + var description = item.Stat?.FollowDisplayText ?? item.DisplayScoreText; + description = _textToolkit.ConvertToTraditionalChineseIfNeeded(description); + var isTracking = item.Status?.IsFollow == 1; + + var identifier = new VideoIdentifier(ssid, title, -1, cover); + return new SeasonInformation( + identifier, + subtitle, + highlight, + tags, + type: type, + description: description, + communityInformation: communityInfo, + isTracking: isTracking); + } + + /// + public SeasonInformation ConvertToSeasonInformation(PgcSearchItem item) + { + var ssid = item.SeasonId.ToString(); + var title = Regex.Replace(item.Title, "<[^>]+>", string.Empty); + title = _textToolkit.ConvertToTraditionalChineseIfNeeded(title); + var subtitle = _textToolkit.ConvertToTraditionalChineseIfNeeded(item.Label); + var tags = _textToolkit.ConvertToTraditionalChineseIfNeeded(item.SubTitle); + var cover = _imageAdapter.ConvertToPgcCover(item.Cover); + var highlight = _textToolkit.ConvertToTraditionalChineseIfNeeded(item.BadgeText); + var isTracking = item.IsFollow == 1; + var type = GetPgcTypeFromTypeText(item.SeasonTypeName); + var description = _textToolkit.ConvertToTraditionalChineseIfNeeded(item.Area); + var ratingCount = Convert.ToInt32(item.VoteNumber); + var communityInfo = _communityAdapter.ConvertToVideoCommunityInformation(item); + + var identifier = new VideoIdentifier(ssid, title, -1, cover); + return new SeasonInformation( + identifier, + subtitle, + highlight, + tags, + ratingCount, + description: description, + type: type, + communityInformation: communityInfo, + isTracking: isTracking); + } + + /// + public SeasonInformation ConvertToSeasonInformation(PgcIndexItem item) + { + var title = _textToolkit.ConvertToTraditionalChineseIfNeeded(item.Title); + var ssid = item.SeasonId.ToString(); + var tags = _textToolkit.ConvertToTraditionalChineseIfNeeded(item.OrderText); + var cover = _imageAdapter.ConvertToPgcCover(item.Cover); + var highlight = _textToolkit.ConvertToTraditionalChineseIfNeeded(item.BadgeText); + var description = _textToolkit.ConvertToTraditionalChineseIfNeeded(item.AdditionalText); + var subtitle = item.IsFinish == 1 + ? "已完结" + : "连载中"; + subtitle = _textToolkit.ConvertToTraditionalChineseIfNeeded(subtitle); + + var identifier = new VideoIdentifier(ssid, title, -1, cover); + return new SeasonInformation( + identifier, + subtitle, + highlight, + tags, + description: description); + } + + /// + public SeasonInformation ConvertToSeasonInformation(TimeLineEpisode item) + { + var title = _textToolkit.ConvertToTraditionalChineseIfNeeded(item.Title); + var ssid = item.SeasonId.ToString(); + var publishTime = DateTimeOffset.FromUnixTimeSeconds(item.PublishTimeStamp).ToLocalTime().DateTime; + var tags = _textToolkit.ConvertToTraditionalChineseIfNeeded(item.PublishIndex); + var cover = _imageAdapter.ConvertToPgcCover(item.Cover); + var description = item.IsPublished == 1 + ? "已更新" + : "待发布"; + description = _textToolkit.ConvertToTraditionalChineseIfNeeded(description); + var subtitle = publishTime.ToString("MM/dd HH:mm"); + + var identifier = new VideoIdentifier(ssid, title, -1, cover); + return new SeasonInformation(identifier, subtitle, tags: tags, description: description); + } + + /// + public SeasonInformation ConvertToSeasonInformation(PgcPlayListSeason season) + { + var title = _textToolkit.ConvertToTraditionalChineseIfNeeded(season.Title); + var ssid = season.SeasonId.ToString(); + var tags = _textToolkit.ConvertToTraditionalChineseIfNeeded(season.Styles); + var subtitle = _textToolkit.ConvertToTraditionalChineseIfNeeded(season.Subtitle); + var description = _textToolkit.ConvertToTraditionalChineseIfNeeded(season.Description); + var highlight = _textToolkit.ConvertToTraditionalChineseIfNeeded(season.BadgeText); + var cover = _imageAdapter.ConvertToPgcCover(season.Cover); + var communityInfo = _communityAdapter.ConvertToVideoCommunityInformation(season.Stat); + communityInfo.Id = ssid; + if (season.Rating != null) + { + communityInfo.Score = season.Rating.Score; + } + + var identifier = new VideoIdentifier(ssid, title, -1, cover); + return new SeasonInformation( + identifier, + subtitle, + highlight, + tags, + description: description, + communityInformation: communityInfo); + } + + /// + public SeasonInformation ConvertToSeasonInformation(FavoritePgcItem item) + { + var title = _textToolkit.ConvertToTraditionalChineseIfNeeded(item.Title); + var subtitle = item.NewEpisode?.DisplayText ?? string.Empty; + subtitle = _textToolkit.ConvertToTraditionalChineseIfNeeded(subtitle); + var ssid = item.SeasonId.ToString(); + var type = GetPgcTypeFromTypeText(item.SeasonTypeName); + var cover = _imageAdapter.ConvertToPgcCover(item.Cover); + var highlight = _textToolkit.ConvertToTraditionalChineseIfNeeded(item.BadgeText); + var description = _textToolkit.ConvertToTraditionalChineseIfNeeded(item.SeasonTypeName); + + var identifier = new VideoIdentifier(ssid, title, -1, cover); + return new SeasonInformation( + identifier, + subtitle, + highlight, + type: type, + description: description); + } + + /// + public PgcPlayerView ConvertToPgcPlayerView(PgcDisplayInformation display) + { + var seasonInfo = GetSeasonInformationFromDisplayInformation(display); + List seasons = null; + List episodes = null; + Dictionary> extras = null; + PlayedProgress history = null; + + if (display.Modules != null) + { + var seasonModule = display.Modules.FirstOrDefault(p => p.Style == ServiceConstants.Season); + if (seasonModule != null) + { + seasons = new List(); + foreach (var item in seasonModule.Data.Seasons) + { + var cover = _imageAdapter.ConvertToImage(item.Cover, 240, 320); + seasons.Add(new VideoIdentifier(item.SeasonId.ToString(), item.Title, -1, cover)); + } + } + + var episodeModule = display.Modules.FirstOrDefault(p => p.Style == ServiceConstants.Positive); + if (episodeModule != null) + { + episodes = episodeModule.Data.Episodes + .Select(p => ConvertToEpisodeInformation(p)) + .ToList(); + } + + var sectionModules = display.Modules.Where(p => p.Style == ServiceConstants.Section); + if (sectionModules.Any()) + { + extras = new Dictionary>(); + foreach (var section in sectionModules) + { + var title = section.Title; + if (extras.ContainsKey(title)) + { + var count = extras.Keys.Count(p => p.StartsWith(title)) + 1; + title = title + count; + } + + if ( + section.Data?.Episodes?.Any() ?? false) + { + extras.Add(title, section.Data.Episodes.Select(p => ConvertToEpisodeInformation(p))); + } + } + } + } + + if (display.UserStatus?.Progress != null && episodes != null) + { + var progress = display.UserStatus.Progress; + var historyEpid = progress.LastEpisodeId.ToString(); + var historyEp = episodes.Any(p => p.Identifier.Id == historyEpid) + ? episodes.FirstOrDefault(p => p.Identifier.Id == historyEpid) + : episodes.FirstOrDefault(p => p.Index.ToString() == progress.LastEpisodeIndex); + + if (historyEp != null) + { + var status = progress.LastTime switch + { + -1 => PlayedProgressStatus.Finish, + 0 => PlayedProgressStatus.NotStarted, + _ => PlayedProgressStatus.Playing + }; + history = new PlayedProgress(progress.LastTime, status, historyEp.Identifier); + } + } + + var warning = display.Warning?.Message; + + return new PgcPlayerView(seasonInfo, seasons, episodes, extras, history, warning); + } + + /// + public PgcPlaylist ConvertToPgcPlaylist(PgcModule module) + { + var title = _textToolkit.ConvertToTraditionalChineseIfNeeded(module.Title); + var id = string.Empty; + if (module.Headers?.Count > 0) + { + var header = module.Headers.First(); + if (header.Url.Contains("/sl")) + { + var uri = new Uri(header.Url); + id = uri.Segments.Where(p => p.Contains("sl")) + .Select(p => p.Replace("sl", string.Empty)) + .FirstOrDefault(); + } + } + + var seasons = module.Items.Select(p => ConvertToSeasonInformation(p, PgcType.Bangumi | PgcType.Domestic)); + return new PgcPlaylist(title, id, seasons: seasons); + } + + /// + public PgcPlaylist ConvertToPgcPlaylist(PgcPlayListResponse response) + { + var id = response.Id.ToString(); + var subtitle = $"{response.Total} · {response.Description}"; + subtitle = _textToolkit.ConvertToTraditionalChineseIfNeeded(subtitle); + var title = _textToolkit.ConvertToTraditionalChineseIfNeeded(response.Title); + var seasons = response.Seasons.Select(p => ConvertToSeasonInformation(p)); + return new PgcPlaylist(title, id, subtitle, seasons); + } + + /// + public PgcPageView ConvertToPgcPageView(PgcResponse response) + { + var banners = response.Modules.Where(p => p.Style.Contains("banner")) + .SelectMany(p => p.Items) + .Select(p => _communityAdapter.ConvertToBannerIdentifier(p)); + + var originRanks = response.Modules.Where(p => p.Style.Contains("rank")) + .SelectMany(p => p.Items); + + var ranks = new Dictionary>(); + foreach (var item in originRanks) + { + var title = _textToolkit.ConvertToTraditionalChineseIfNeeded(item.Title); + var subRanks = item.Cards.Take(3).Select(p => ConvertToEpisodeInformation(p)).ToList(); + ranks.Add(title, subRanks); + } + + var partitionId = response.FeedIdentifier == null + ? string.Empty + : response.FeedIdentifier.PartitionIds.First().ToString(); + + var playLists = response.Modules.Where(p => p.Style == "v_card" || p.Style.Contains("feed")) + .Select(p => ConvertToPgcPlaylist(p)) + .ToList(); + + var seasons = response.Modules.Where(p => p.Style == "common_feed") + .SelectMany(p => p.Items) + .Select(p => ConvertToSeasonInformation(p, PgcType.Documentary | PgcType.Movie | PgcType.TV)) + .ToList(); + + return new PgcPageView(banners, ranks, playLists, seasons, partitionId); + } + + /// + public IEnumerable ConvertToFilters(PgcIndexConditionResponse response) + { + var filters = response.FilterList + .Select(p => GetFilterFromIndexFilter(p)) + .ToList(); + if (response.OrderList?.Count > 0) + { + var name = _textToolkit.ConvertToTraditionalChineseIfNeeded("排序方式"); + var id = "order"; + var conditions = response.OrderList.Select(p => new Condition(_textToolkit.ConvertToTraditionalChineseIfNeeded(p.Name), p.Field)).ToList(); + filters.Insert(0, new Filter(name, id, conditions)); + } + + return filters; + } + + /// + public TimelineView ConvertToTimelineView(PgcTimeLineResponse response) + { + var title = _textToolkit.ConvertToTraditionalChineseIfNeeded(response.Title); + var desc = _textToolkit.ConvertToTraditionalChineseIfNeeded(response.Subtitle); + var timelines = new List(); + foreach (var item in response.Data) + { + var seasons = item.Episodes?.Count > 0 + ? item.Episodes.Select(p => ConvertToSeasonInformation(p)).ToList() + : null; + var info = new TimelineInformation(item.Date, ConvertDayOfWeek(item.DayOfWeek), item.IsToday == 1, seasons); + timelines.Add(info); + } + + return new TimelineView(title, desc, timelines); + } + + /// + public SeasonSet ConvertToSeasonSet(PgcFavoriteListResponse response) + { + var total = response.Total; + var items = response.FollowList != null + ? response.FollowList.Select(p => ConvertToSeasonInformation(p)) + : new List(); + return new SeasonSet(items, total); + } + + private SeasonInformation GetSeasonInformationFromDisplayInformation(PgcDisplayInformation display) + { + var ssid = display.SeasonId.ToString(); + var title = _textToolkit.ConvertToTraditionalChineseIfNeeded(display.Title); + var cover = _imageAdapter.ConvertToPgcCover(display.Cover); + var subtitle = _textToolkit.ConvertToTraditionalChineseIfNeeded(display.Subtitle); + var description = _textToolkit.ConvertToTraditionalChineseIfNeeded(display.Evaluate); + var highlight = _textToolkit.ConvertToTraditionalChineseIfNeeded(display.BadgeText); + var progress = display.PublishInformation.DisplayProgress; + var publishDate = display.PublishInformation.DisplayPublishTime; + var originName = display.OriginName; + var alias = display.Alias; + var tags = $"{display.TypeDescription}\n" + + $"{display.PublishInformation.DisplayReleaseDate}\n" + + $"{display.PublishInformation.DisplayProgress}"; + tags = _textToolkit.ConvertToTraditionalChineseIfNeeded(tags); + var isTracking = display.UserStatus.IsFollow == 1; + var ratingCount = display.Rating != null + ? display.Rating.Count + : -1; + var labors = new Dictionary(); + if (!string.IsNullOrEmpty(display.Actor?.Information)) + { + labors.Add( + _textToolkit.ConvertToTraditionalChineseIfNeeded(display.Actor.Title), + _textToolkit.ConvertToTraditionalChineseIfNeeded(display.Actor.Information)); + } + + if (!string.IsNullOrEmpty(display.Staff?.Information)) + { + labors.Add( + _textToolkit.ConvertToTraditionalChineseIfNeeded(display.Staff.Title), + _textToolkit.ConvertToTraditionalChineseIfNeeded(display.Staff.Information)); + } + + var celebrities = display.Celebrity?.Select(p => _userAdapter.ConvertToRoleProfile(p)); + var communityInfo = _communityAdapter.ConvertToVideoCommunityInformation(display.InformationStat); + communityInfo.Id = ssid; + if (display.Rating != null) + { + communityInfo.Score = display.Rating.Score; + } + + var type = GetPgcTypeFromTypeText(display.TypeName); + + var identifier = new VideoIdentifier(ssid, title, -1, cover); + return new SeasonInformation( + identifier, + subtitle, + highlight, + tags, + ratingCount, + originName, + alias, + publishDate, + progress, + description, + type, + labors, + communityInfo, + celebrities, + isTracking); + } + + private PgcType GetPgcTypeFromTypeText(string typeText) + { + return typeText switch + { + "国创" => PgcType.Domestic, + "电影" => PgcType.Movie, + "电视剧" => PgcType.TV, + "纪录片" => PgcType.Documentary, + _ => PgcType.Bangumi, + }; + } + + private Filter GetFilterFromIndexFilter(PgcIndexFilter filter) + { + var conditions = filter.Values.Select(p => new Condition(p.Name, p.Keyword)); + return new Filter(filter.Name, filter.Field, conditions); + } + + private string ConvertDayOfWeek(int day) + { + var dayOfWeek = day switch + { + 1 => "一", + 2 => "二", + 3 => "三", + 4 => "四", + 5 => "五", + 6 => "六", + 7 => "日", + _ => "-", + }; + + return _textToolkit.ConvertToTraditionalChineseIfNeeded($"周{dayOfWeek}"); + } + } +} diff --git a/src/Adapter/Adapter.Implementation/PlayerAdapter.cs b/src/Adapter/Adapter.Implementation/PlayerAdapter.cs new file mode 100644 index 000000000..1c256c9b6 --- /dev/null +++ b/src/Adapter/Adapter.Implementation/PlayerAdapter.cs @@ -0,0 +1,174 @@ +// Copyright (c) Richasy. All rights reserved. + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text.RegularExpressions; +using Bili.Adapter.Interfaces; +using Bili.Models.BiliBili; +using Bili.Models.Data.Player; +using Bili.Toolkit.Interfaces; + +namespace Bili.Adapter +{ + /// + /// 播放器数据适配器. + /// + public sealed class PlayerAdapter : IPlayerAdapter + { + private readonly ITextToolkit _textToolkit; + + /// + /// Initializes a new instance of the class. + /// + /// 文本工具. + public PlayerAdapter(ITextToolkit textToolkit) + => _textToolkit = textToolkit; + + /// + public FormatInformation ConvertToFormatInformation(VideoFormat format) + => new FormatInformation( + format.Quality, + _textToolkit.ConvertToTraditionalChineseIfNeeded(format.Description), + !string.IsNullOrEmpty(format.Superscript)); + + /// + public SegmentInformation ConvertToSegmentInformation(DashItem item) + { + return new SegmentInformation( + item.Id.ToString(), + item.BaseUrl, + item.BackupUrl, + item.BandWidth, + item.MimeType, + item.Codecs, + item.Width, + item.Height, + item.SegmentBase.Initialization, + item.SegmentBase.IndexRange); + } + + /// + public MediaInformation ConvertToMediaInformation(PlayerInformation information) + { + var dash = information.VideoInformation; + if (dash == null) + { + return default; + } + + var minBuffer = dash.MinBufferTime; + var videos = dash.Video?.Count > 0 + ? dash.Video.Select(p => ConvertToSegmentInformation(p)) + : null; + var audios = dash.Audio?.Count > 0 + ? dash.Audio.Select(p => ConvertToSegmentInformation(p)) + : null; + var formats = information.SupportFormats.Select(p => ConvertToFormatInformation(p)).ToList(); + return new MediaInformation(minBuffer, videos, audios, formats); + } + + /// + public MediaInformation ConvertToMediaInformation(Bilibili.App.Playurl.V1.PlayViewReply reply) + { + var info = reply.VideoInfo; + if (info == null) + { + return default; + } + + var videoStreams = info.StreamList.ToList(); + var audioStreams = info.DashAudio != null + ? info.DashAudio.ToList() + : new List(); + var videos = new List(); + var audios = new List(); + var formats = new List(); + + foreach (var video in videoStreams) + { + if (video.DashVideo == null) + { + continue; + } + + var seg = new SegmentInformation( + video.StreamInfo.Quality.ToString(), + video.DashVideo.BaseUrl, + video.DashVideo.BackupUrl.ToList(), + default, + default, + video.DashVideo.Codecid.ToString(), + default, + default, + default, + default); + var format = new FormatInformation( + Convert.ToInt32(video.StreamInfo.Quality), + video.StreamInfo.Description, + video.StreamInfo.NeedVip); + videos.Add(seg); + formats.Add(format); + } + + foreach (var audio in audioStreams) + { + var seg = new SegmentInformation( + audio.Id.ToString(), + audio.BaseUrl, + audio.BackupUrl.ToList(), + default, + default, + audio.Codecid.ToString(), + default, + default, + default, + default); + + audios.Add(seg); + } + + return new MediaInformation(default, videos, audios, formats); + } + + /// + public SubtitleMeta ConvertToSubtitleMeta(SubtitleIndexItem item) + => new SubtitleMeta(item.Id.ToString(), item.DisplayLanguage, item.Url); + + /// + public SubtitleInformation ConvertToSubtitleInformation(SubtitleItem item) + => new SubtitleInformation(item.From, item.To, item.Content); + + /// + public DanmakuInformation ConvertToDanmakuInformation(Bilibili.Community.Service.Dm.V1.DanmakuElem danmaku) + => new DanmakuInformation( + danmaku.Id.ToString(), + _textToolkit.ConvertToTraditionalChineseIfNeeded(danmaku.Content), + danmaku.Mode, + danmaku.Progress / 1000.0, + danmaku.Color, + danmaku.Fontsize); + + /// + public InteractionInformation ConvertToInteractionInformation(InteractionChoice choice, IEnumerable variables) + { + var id = choice.Id.ToString(); + var condition = choice.Condition ?? string.Empty; + var partId = choice.PartId.ToString(); + var text = _textToolkit.ConvertToTraditionalChineseIfNeeded(choice.Option); + var isValid = true; + + if (!string.IsNullOrEmpty(condition) && variables != null) + { + var v = variables.FirstOrDefault(p => condition.Contains(p.Id)); + var minString = Regex.Match(condition, ">=([0-9]{1,}[.][0-9]*)").Value.Replace(">=", string.Empty); + var maxString = Regex.Match(condition, "<=([0-9]{1,}[.][0-9]*)").Value.Replace("<=", string.Empty); + var min = string.IsNullOrEmpty(minString) ? 0 : Convert.ToDouble(minString); + var max = string.IsNullOrEmpty(maxString) ? -1 : Convert.ToDouble(maxString); + isValid = v != null && v.Value >= min && (max == -1 || v.Value <= max); + } + + return new InteractionInformation(id, condition, partId, text, isValid); + } + } +} diff --git a/src/Adapter/Adapter.Implementation/SearchAdapter.cs b/src/Adapter/Adapter.Implementation/SearchAdapter.cs new file mode 100644 index 000000000..876a2229c --- /dev/null +++ b/src/Adapter/Adapter.Implementation/SearchAdapter.cs @@ -0,0 +1,55 @@ +// Copyright (c) Richasy. All rights reserved. + +using System.Collections.Generic; +using System.Linq; +using Bili.Adapter.Interfaces; +using Bili.Models.App.Constants; +using Bili.Models.BiliBili; +using Bili.Models.Data.Search; +using Bili.Models.Data.Video; +using Bili.Models.Enums; +using Bilibili.App.Interfaces.V1; + +namespace Bili.Adapter +{ + /// + /// 搜索数据适配器. + /// + public sealed class SearchAdapter : ISearchAdapter + { + private readonly IVideoAdapter _videoAdapter; + + /// + /// Initializes a new instance of the class. + /// + /// 视频数据适配器. + public SearchAdapter(IVideoAdapter videoAdapter) + => _videoAdapter = videoAdapter; + + /// + public SearchSuggest ConvertToSearchSuggest(SearchRecommendItem item) + => new SearchSuggest(item.Position, item.DisplayName, item.Keyword, item.Icon); + + /// + public SearchSuggest ConvertToSearchSuggest(ResultItem item) + => new SearchSuggest(item.Position, item.Title, item.Keyword); + + /// + public ComprehensiveSet ConvertToComprehensiveSet(ComprehensiveSearchResultResponse response) + { + var metaList = new Dictionary(); + foreach (var item in response.SubModuleList) + { + metaList.Add((SearchModuleType)item.Type, item.Total); + } + + var isEnd = response.ItemList == null; + var videos = isEnd + ? new List() + : response.ItemList.Where(p => p.Goto == ServiceConstants.Av).Select(p => _videoAdapter.ConvertToVideoInformation(p)).ToList(); + var videoSet = new SearchSet(videos, isEnd); + + return new ComprehensiveSet(videoSet, metaList); + } + } +} diff --git a/src/Adapter/Adapter.Implementation/UserAdapter.cs b/src/Adapter/Adapter.Implementation/UserAdapter.cs new file mode 100644 index 000000000..f934e7613 --- /dev/null +++ b/src/Adapter/Adapter.Implementation/UserAdapter.cs @@ -0,0 +1,172 @@ +// Copyright (c) Richasy. All rights reserved. + +using System; +using System.Linq; +using System.Text.RegularExpressions; +using Bili.Adapter.Interfaces; +using Bili.Models.BiliBili; +using Bili.Models.Data.User; +using Bili.Models.Enums.App; +using Bili.Toolkit.Interfaces; +using Bilibili.App.Archive.V1; +using Bilibili.App.View.V1; +using Bilibili.Main.Community.Reply.V1; + +namespace Bili.Adapter +{ + /// + /// 用户资料适配器,将来自 BiliBili 的用户数据转换为 , . + /// + public sealed class UserAdapter : IUserAdapter + { + private readonly IImageAdapter _imageAdapter; + private readonly ICommunityAdapter _communityAdapter; + private readonly ITextToolkit _textToolkit; + + /// + /// Initializes a new instance of the class. + /// + /// 图片适配器. + /// 社区数据适配器. + /// 文本工具. + public UserAdapter( + IImageAdapter imageAdapter, + ICommunityAdapter communityAdapter, + ITextToolkit textToolkit) + { + _imageAdapter = imageAdapter; + _communityAdapter = communityAdapter; + _textToolkit = textToolkit; + } + + /// + public AccountInformation ConvertToAccountInformation(MyInfo myInfo, AvatarSize avatarSize) + { + var user = ConvertToUserProfile(myInfo.Mid, myInfo.Name, myInfo.Avatar, avatarSize); + var communityInfo = _communityAdapter.ConvertToUserCommunityInformation(myInfo); + return new AccountInformation( + user, + myInfo.Sign, + myInfo.Level, + myInfo.VIP.Status == 1, + communityInfo); + } + + /// + public AccountInformation ConvertToAccountInformation(Mine myInfo, AvatarSize avatarSize) + { + var user = ConvertToUserProfile(myInfo.Mid, myInfo.Name, myInfo.Avatar, avatarSize); + var communityInfo = _communityAdapter.ConvertToUserCommunityInformation(myInfo); + return new AccountInformation( + user, + string.Empty, + myInfo.Level, + false, + communityInfo); + } + + /// + public AccountInformation ConvertToAccountInformation(UserSpaceInformation spaceInfo, AvatarSize avatarSize) + { + var user = ConvertToUserProfile(Convert.ToInt64(spaceInfo.UserId), spaceInfo.UserName, spaceInfo.Avatar, avatarSize); + var communityInfo = _communityAdapter.ConvertToUserCommunityInformation(spaceInfo); + return new AccountInformation( + user, + spaceInfo.Sign, + spaceInfo.LevelInformation.CurrentLevel, + spaceInfo.Vip.Status == 1, + communityInfo); + } + + /// + public AccountInformation ConvertToAccountInformation(RelatedUser user, AvatarSize avatarSize = AvatarSize.Size64) + { + var profile = ConvertToUserProfile(user.Mid, user.Name, user.Avatar, avatarSize); + var communityInfo = _communityAdapter.ConvertToUserCommunityInformation(user); + return new AccountInformation( + profile, + user.Sign, + -1, + user.Vip.Status == 1, + communityInfo); + } + + /// + public AccountInformation ConvertToAccountInformation(UserSearchItem item, AvatarSize avatarSize = AvatarSize.Size64) + { + var profile = ConvertToUserProfile(item.UserId, Regex.Replace(item.Title, "<[^>]+>", string.Empty), item.Cover, avatarSize); + var communityInfo = _communityAdapter.ConvertToUserCommunityInformation(item); + return new AccountInformation( + profile, + item.Sign, + item.Level, + item.Vip.Status == 1, + communityInfo); + } + + /// + public AccountInformation ConvertToAccountInformation(Member member, AvatarSize avatarSize = AvatarSize.Size64) + { + var profile = ConvertToUserProfile(member.Mid, member.Name, member.Face, avatarSize); + return new AccountInformation(profile, default, Convert.ToInt32(member.Level), default); + } + + /// + public RoleProfile ConvertToRoleProfile(PublisherInfo publisher, AvatarSize avatarSize) + { + var user = ConvertToUserProfile(publisher.Mid, publisher.Publisher, publisher.PublisherAvatar, avatarSize); + return new RoleProfile(user); + } + + /// + public RoleProfile ConvertToRoleProfile(Staff staff, AvatarSize avatarSize) + { + var user = ConvertToUserProfile(staff.Mid, staff.Name, staff.Face, avatarSize); + return new RoleProfile( + user, + _textToolkit.ConvertToTraditionalChineseIfNeeded(staff.Title)); + } + + /// + public RoleProfile ConvertToRoleProfile(RecommendAvatar avatar, AvatarSize avatarSize = AvatarSize.Size48) + { + var user = ConvertToUserProfile(avatar.UserId, avatar.UserName, avatar.Cover, avatarSize); + return new RoleProfile(user); + } + + /// + public RoleProfile ConvertToRoleProfile(Author author, AvatarSize avatarSize = AvatarSize.Size32) + { + var user = ConvertToUserProfile(author.Mid, author.Name, author.Face, avatarSize); + return new RoleProfile(user); + } + + /// + public RoleProfile ConvertToRoleProfile(PgcCelebrity celebrity, AvatarSize avatarSize = AvatarSize.Size96) + { + var user = ConvertToUserProfile(celebrity.Id, celebrity.Name, celebrity.Avatar, avatarSize); + return new RoleProfile( + user, + _textToolkit.ConvertToTraditionalChineseIfNeeded(celebrity.ShortDescription)); + } + + /// + public UserProfile ConvertToUserProfile(long userId, string userName, string avatar, AvatarSize avatarSize) + { + var size = int.Parse(avatarSize.ToString().Replace("Size", string.Empty)); + var image = string.IsNullOrEmpty(avatar) + ? default + : _imageAdapter.ConvertToImage(avatar, size, size); + var profile = new UserProfile(userId.ToString(), userName, image); + return profile; + } + + /// + public RelationView ConvertToRelationView(RelatedUserResponse response) + { + var count = response.TotalCount; + var accounts = response.UserList.Select(p => ConvertToAccountInformation(p)).ToList(); + return new RelationView(accounts, count); + } + } +} diff --git a/src/Adapter/Adapter.Implementation/VideoAdapter.cs b/src/Adapter/Adapter.Implementation/VideoAdapter.cs new file mode 100644 index 000000000..b4b8daec6 --- /dev/null +++ b/src/Adapter/Adapter.Implementation/VideoAdapter.cs @@ -0,0 +1,597 @@ +// Copyright (c) Richasy. All rights reserved. + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text.RegularExpressions; +using Bili.Adapter.Interfaces; +using Bili.Models.App.Constants; +using Bili.Models.BiliBili; +using Bili.Models.Data.Community; +using Bili.Models.Data.Player; +using Bili.Models.Data.User; +using Bili.Models.Data.Video; +using Bili.Models.Enums.Community; +using Bili.Models.Enums.Player; +using Bili.Toolkit.Interfaces; +using Bilibili.App.Card.V1; +using Bilibili.App.Dynamic.V2; +using Bilibili.App.Interfaces.V1; +using Bilibili.App.Show.V1; +using Bilibili.App.View.V1; +using Humanizer; + +namespace Bili.Adapter +{ + /// + /// 视频信息适配器. + /// + public sealed class VideoAdapter : IVideoAdapter + { + private readonly ICommunityAdapter _communityAdapter; + private readonly IUserAdapter _userAdapter; + private readonly IImageAdapter _imageAdapter; + private readonly INumberToolkit _numberToolkit; + private readonly ITextToolkit _textToolkit; + + /// + /// Initializes a new instance of the class. + /// + /// 社区数据适配器. + /// 用户数据适配器. + /// 图片适配器. + /// 数字工具. + /// 文本工具. + public VideoAdapter( + ICommunityAdapter communityAdapter, + IUserAdapter userAdapter, + IImageAdapter imageAdapter, + INumberToolkit numberToolkit, + ITextToolkit textToolkit) + { + _communityAdapter = communityAdapter; + _userAdapter = userAdapter; + _imageAdapter = imageAdapter; + _numberToolkit = numberToolkit; + _textToolkit = textToolkit; + } + + /// + public VideoInformation ConvertToVideoInformation(RecommendCard videoCard) + { + if (videoCard.CardGoto != ServiceConstants.Av) + { + throw new ArgumentException($"推荐卡片的 CardGoTo 属性应该是 {ServiceConstants.Av},这里是 {videoCard.Goto},不符合要求,请检查分配条件", nameof(videoCard)); + } + + var communityInfo = _communityAdapter.ConvertToVideoCommunityInformation(videoCard); + var publisher = _userAdapter.ConvertToRoleProfile(videoCard.Mask.Avatar); + var title = _textToolkit.ConvertToTraditionalChineseIfNeeded(videoCard.Title); + var id = videoCard.Parameter; + var duration = (videoCard.PlayerArgs?.Duration).HasValue + ? videoCard.PlayerArgs.Duration + : 0; + var subtitle = videoCard.Description; + var cover = _imageAdapter.ConvertToVideoCardCover(videoCard.Cover); + var highlight = _textToolkit.ConvertToTraditionalChineseIfNeeded(videoCard.RecommendReason); + + var identifier = new VideoIdentifier(id, title, duration, cover); + return new VideoInformation( + identifier, + publisher, + subtitle: subtitle, + highlight: highlight, + communityInformation: communityInfo); + } + + /// + public VideoInformation ConvertToVideoInformation(PartitionVideo video) + { + var title = _textToolkit.ConvertToTraditionalChineseIfNeeded(video.Title); + var id = video.Parameter; + var duration = video.Duration; + var subtitle = video.Publisher; + var cover = _imageAdapter.ConvertToVideoCardCover(video.Cover); + var communityInfo = _communityAdapter.ConvertToVideoCommunityInformation(video); + var publishTime = DateTimeOffset.FromUnixTimeSeconds(video.PublishDateTime).ToLocalTime(); + var identifier = new VideoIdentifier(id, title, duration, cover); + return new VideoInformation( + identifier, + null, + subtitle: subtitle, + publishTime: publishTime.DateTime, + communityInformation: communityInfo); + } + + /// + public VideoInformation ConvertToVideoInformation(MdlDynArchive dynamicVideo) + { + if (dynamicVideo.IsPGC) + { + throw new ArgumentException($"该动态视频是 PGC 内容,不符合要求,请检查分配条件", nameof(dynamicVideo)); + } + + var title = _textToolkit.ConvertToTraditionalChineseIfNeeded(dynamicVideo.Title); + var id = dynamicVideo.Avid.ToString(); + var bvid = dynamicVideo.Bvid; + var duration = dynamicVideo.Duration; + var cover = _imageAdapter.ConvertToImage(dynamicVideo.Cover, AppConstants.DynamicCoverWidth, AppConstants.DynamicCoverHeight); + var communityInfo = _communityAdapter.ConvertToVideoCommunityInformation(dynamicVideo); + var identifier = new VideoIdentifier(id, title, duration, cover); + return new VideoInformation( + identifier, + null, + bvid, + communityInformation: communityInfo); + } + + /// + public VideoInformation ConvertToVideoInformation(ViewLaterVideo video) + { + var title = _textToolkit.ConvertToTraditionalChineseIfNeeded(video.Title); + var duration = video.Duration; + var id = video.VideoId.ToString(); + var bvid = video.BvId; + var description = _textToolkit.ConvertToTraditionalChineseIfNeeded(video.Description); + var publishTime = DateTimeOffset.FromUnixTimeSeconds(video.PublishDateTime); + var cover = _imageAdapter.ConvertToVideoCardCover(video.Cover); + var publisher = _userAdapter.ConvertToRoleProfile(video.Publisher, Models.Enums.App.AvatarSize.Size48); + var communityInfo = _communityAdapter.ConvertToVideoCommunityInformation(video.StatusInfo); + var identifier = new VideoIdentifier(id, title, duration, cover); + var subtitle = $"{publisher.User.Name} · {_textToolkit.ConvertToTraditionalChineseIfNeeded(publishTime.Humanize())}"; + return new VideoInformation( + identifier, + publisher, + bvid, + description: description, + subtitle: subtitle, + publishTime: publishTime.DateTime, + communityInformation: communityInfo); + } + + /// + public VideoInformation ConvertToVideoInformation(Item rankVideo) + { + var id = rankVideo.Param; + var title = _textToolkit.ConvertToTraditionalChineseIfNeeded(rankVideo.Title); + var duration = rankVideo.Duration; + var publishTime = DateTimeOffset.FromUnixTimeSeconds(rankVideo.PubDate).ToLocalTime(); + + var user = _userAdapter.ConvertToUserProfile(rankVideo.Mid, rankVideo.Name, rankVideo.Face, Models.Enums.App.AvatarSize.Size48); + var publisher = new RoleProfile(user); + var cover = _imageAdapter.ConvertToVideoCardCover(rankVideo.Cover); + var communityInfo = _communityAdapter.ConvertToVideoCommunityInformation(rankVideo); + var subtitle = $"{user.Name} · {_textToolkit.ConvertToTraditionalChineseIfNeeded(publishTime.Humanize())}"; + + var identifier = new VideoIdentifier(id, title, duration, cover); + return new VideoInformation( + identifier, + publisher, + subtitle: subtitle, + publishTime: publishTime.DateTime, + communityInformation: communityInfo); + } + + /// + public VideoInformation ConvertToVideoInformation(Card hotVideo) + { + var v5 = hotVideo.SmallCoverV5; + var card = v5.Base; + var shareInfo = card.ThreePointV4.SharePlane; + var title = _textToolkit.ConvertToTraditionalChineseIfNeeded(card.Title); + var id = shareInfo.Aid.ToString(); + var bvId = shareInfo.Bvid; + var subtitle = shareInfo.Author; + var description = _textToolkit.ConvertToTraditionalChineseIfNeeded(shareInfo.Desc); + + // 对于热门视频来说,它会把观看数和发布时间揉在一起,比如 "13.5万观看 · 21小时前", + // 考虑到我们的需求,这里需要把它拆开,让发布时间和作者名一起作为副标题存在,就像推荐视频一样. + var descSplit = v5.RightDesc2.Split('·'); + if (descSplit.Length > 1) + { + var publishTimeText = descSplit[1].Trim(); + subtitle += $" · {_textToolkit.ConvertToTraditionalChineseIfNeeded(publishTimeText)}"; + } + + var duration = _numberToolkit.GetDurationSeconds(v5.CoverRightText1); + var cover = _imageAdapter.ConvertToVideoCardCover(card.Cover); + var communityInfo = _communityAdapter.ConvertToVideoCommunityInformation(hotVideo); + var highlight = hotVideo.SmallCoverV5.RcmdReasonStyle?.Text ?? string.Empty; + highlight = _textToolkit.ConvertToTraditionalChineseIfNeeded(highlight); + var publisher = _userAdapter.ConvertToUserProfile(shareInfo.AuthorId, shareInfo.Author, string.Empty, Models.Enums.App.AvatarSize.Size48); + + var identifier = new VideoIdentifier(id, title, duration, cover); + + return new VideoInformation( + identifier, + null, + bvId, + description: description, + subtitle: subtitle, + highlight: highlight, + communityInformation: communityInfo); + } + + /// + public VideoInformation ConvertToVideoInformation(Relate relatedVideo) + { + var title = _textToolkit.ConvertToTraditionalChineseIfNeeded(relatedVideo.Title); + var id = relatedVideo.Aid.ToString(); + var duration = relatedVideo.Duration; + var description = _textToolkit.ConvertToTraditionalChineseIfNeeded(relatedVideo.Desc); + var publisher = _userAdapter.ConvertToRoleProfile(relatedVideo.Author); + var cover = _imageAdapter.ConvertToVideoCardCover(relatedVideo.Pic); + var communityInfo = _communityAdapter.ConvertToVideoCommunityInformation(relatedVideo.Stat); + var identifier = new VideoIdentifier(id, title, duration, cover); + var subtitle = relatedVideo.Badge ?? relatedVideo.RcmdReason ?? relatedVideo.TagName ?? "视频"; + subtitle = _textToolkit.ConvertToTraditionalChineseIfNeeded(subtitle); + return new VideoInformation( + identifier, + publisher, + subtitle: subtitle, + description: description, + communityInformation: communityInfo); + } + + /// + public VideoInformation ConvertToVideoInformation(VideoSearchItem searchVideo) + { + var title = Regex.Replace(searchVideo.Title, @"<[^<>]+>", string.Empty); + title = _textToolkit.ConvertToTraditionalChineseIfNeeded(title); + var id = searchVideo.Parameter; + var duration = string.IsNullOrEmpty(searchVideo.Duration) + ? 0 + : _numberToolkit.GetDurationSeconds(searchVideo.Duration); + var cover = _imageAdapter.ConvertToVideoCardCover(searchVideo.Cover); + var description = _textToolkit.ConvertToTraditionalChineseIfNeeded(searchVideo.Description); + var user = _userAdapter.ConvertToUserProfile(searchVideo.UserId, searchVideo.Author, searchVideo.Avatar, Models.Enums.App.AvatarSize.Size48); + var publisher = new RoleProfile(user); + var communityInfo = _communityAdapter.ConvertToVideoCommunityInformation(searchVideo); + var subtitle = user.Name; + + var identifier = new VideoIdentifier(id, title, duration, cover); + return new VideoInformation( + identifier, + publisher, + subtitle: subtitle, + description: description, + communityInformation: communityInfo); + } + + /// + public VideoInformation ConvertToVideoInformation(UserSpaceVideoItem spaceVideo) + { + var title = _textToolkit.ConvertToTraditionalChineseIfNeeded(spaceVideo.Title); + var id = spaceVideo.Id; + var publishDate = DateTimeOffset.FromUnixTimeSeconds(spaceVideo.CreateTime); + var duration = spaceVideo.Duration; + var cover = _imageAdapter.ConvertToVideoCardCover(spaceVideo.Cover); + var communityInfo = _communityAdapter.ConvertToVideoCommunityInformation(spaceVideo); + var subtitle = _textToolkit.ConvertToTraditionalChineseIfNeeded(publishDate.DateTime.Humanize()); + + var identifier = new VideoIdentifier(id, title, duration, cover); + return new VideoInformation( + identifier, + null, + subtitle: subtitle, + publishTime: publishDate.DateTime, + communityInformation: communityInfo); + } + + /// + public VideoInformation ConvertToVideoInformation(CursorItem historyVideo) + { + if (historyVideo.CardItemCase != CursorItem.CardItemOneofCase.CardUgc) + { + throw new ArgumentException($"该历史记录不是视频内容,是 {historyVideo.CardItemCase}, 不符合要求,请检查分配条件", nameof(historyVideo)); + } + + var video = historyVideo.CardUgc; + var title = _textToolkit.ConvertToTraditionalChineseIfNeeded(historyVideo.Title); + var id = historyVideo.Kid.ToString(); + var bvid = video.Bvid; + var subtitle = $"{video.Name} · {TimeSpan.FromSeconds(video.Progress)}"; + var duration = video.Duration; + var cover = _imageAdapter.ConvertToVideoCardCover(video.Cover); + + var identifier = new VideoIdentifier(id, title, duration, cover); + var communityInfo = _communityAdapter.ConvertToVideoCommunityInformation(video); + communityInfo.Id = id; + return new VideoInformation( + identifier, + default, + bvid, + subtitle: subtitle, + communityInformation: communityInfo); + } + + /// + public VideoInformation ConvertToVideoInformation(FavoriteMedia video) + { + var title = _textToolkit.ConvertToTraditionalChineseIfNeeded(video.Title); + var id = video.Id.ToString(); + var publisher = _userAdapter.ConvertToRoleProfile(video.Publisher, Models.Enums.App.AvatarSize.Size48); + var duration = video.Duration; + var cover = _imageAdapter.ConvertToVideoCardCover(video.Cover); + var communityInfo = _communityAdapter.ConvertToVideoCommunityInformation(video); + var collectTime = DateTimeOffset.FromUnixTimeSeconds(video.FavoriteTime).ToLocalTime().DateTime; + var subtitle = _textToolkit.ConvertToTraditionalChineseIfNeeded($"{collectTime.Humanize()}收藏"); + + var identifier = new VideoIdentifier(id, title, duration, cover); + return new VideoInformation(identifier, publisher, subtitle: subtitle, communityInformation: communityInfo); + } + + /// + public VideoInformation ConvertToVideoInformation(Arc video) + { + var archive = video.Archive; + var title = Regex.Replace(archive.Title, @"<[^<>]+>", string.Empty); + title = _textToolkit.ConvertToTraditionalChineseIfNeeded(title); + var id = archive.Aid.ToString(); + var publisher = _userAdapter.ConvertToRoleProfile(archive.Author); + var duration = archive.Duration; + var cover = _imageAdapter.ConvertToVideoCardCover(archive.Pic); + var communityInfo = _communityAdapter.ConvertToVideoCommunityInformation(archive.Stat); + var publishTime = DateTimeOffset.FromUnixTimeSeconds(archive.Pubdate).DateTime; + var description = _textToolkit.ConvertToTraditionalChineseIfNeeded(archive.Desc); + var subtitle = _textToolkit.ConvertToTraditionalChineseIfNeeded(publishTime.Humanize()); + + var identifier = new VideoIdentifier(id, title, duration, cover); + return new VideoInformation( + identifier, + publisher, + subtitle: subtitle, + description: description, + publishTime: publishTime, + communityInformation: communityInfo); + } + + /// + public VideoPlayerView ConvertToVideoView(ViewReply videoDetail) + { + var videoInfo = GetVideoInformationFromViewReply(videoDetail); + var subVideos = GetSubVideosFromViewReply(videoDetail); + var interaction = GetInteractionRecordFromViewReply(videoDetail); + var publisherCommunity = GetPublisherCommunityFromViewReply(videoDetail); + var operation = GetOperationInformationFromViewReply(videoDetail); + var relatedVideos = GetRelatedVideosFromViewReply(videoDetail); + var sections = GetVideoSectionsFromViewReply(videoDetail); + var history = GetHistoryFromViewReply(videoDetail); + + if (history != null) + { + if (subVideos.Count() > 0) + { + var historyVideo = subVideos.FirstOrDefault(p => p.Id.Equals(history.Identifier.Id)); + history.Identifier = historyVideo; + } + else if (sections.Count() > 0) + { + history.Identifier = sections + .SelectMany(p => p.Videos) + .FirstOrDefault(p => p.AlternateId == history.Identifier.Id) + .Identifier; + } + } + + var tags = videoDetail.Tag.Select(p => new Models.Data.Community.Tag(p.Id.ToString(), p.Name.TrimStart('#'), p.Uri)); + + return new VideoPlayerView( + videoInfo, + publisherCommunity, + subVideos, + sections, + relatedVideos, + history, + operation, + interaction, + tags); + } + + /// + public VideoSet ConvertToVideoSet(ViewLaterResponse response) + { + var count = response.Count; + var items = response.List == null + ? new List() + : response.List.Select(p => ConvertToVideoInformation(p)).ToList(); + return new VideoSet(items, count); + } + + /// + public VideoSet ConvertToVideoSet(UserSpaceVideoSet set) + { + var count = set.Count; + var items = set.List == null + ? new List() + : set.List.Select(p => ConvertToVideoInformation(p)).ToList(); + return new VideoSet(items, count); + } + + /// + public VideoSet ConvertToVideoSet(SearchArchiveReply reply) + { + var count = Convert.ToInt32(reply.Total); + var items = reply.Archives == null + ? new List() + : reply.Archives.Select(p => ConvertToVideoInformation(p)).ToList(); + return new VideoSet(items, count); + } + + /// + public VideoHistoryView ConvertToVideoHistoryView(CursorV2Reply reply) + { + var isFinished = !reply.HasMore; + var items = reply.Items.Where(p => p.CardItemCase == CursorItem.CardItemOneofCase.CardUgc).Select(p => ConvertToVideoInformation(p)).ToList(); + return new VideoHistoryView(items, isFinished); + } + + private VideoInformation GetVideoInformationFromEpisode(Episode episode) + { + var id = episode.Aid.ToString(); + var cid = episode.Cid.ToString(); + var title = Regex.Replace(episode.Title, "<[^>]+>", string.Empty); + var duration = episode.Page.Duration; + var publisher = _userAdapter.ConvertToRoleProfile(episode.Author); + var communityInfo = _communityAdapter.ConvertToVideoCommunityInformation(episode.Stat); + var cover = _imageAdapter.ConvertToVideoCardCover(episode.Cover); + var subtitle = episode.CoverRightText; + + var identifier = new VideoIdentifier(id, title, duration, cover); + return new VideoInformation(identifier, publisher, cid, subtitle: subtitle, communityInformation: communityInfo); + } + + private VideoInformation GetVideoInformationFromViewReply(ViewReply videoDetail) + { + var arc = videoDetail.Arc; + var title = _textToolkit.ConvertToTraditionalChineseIfNeeded(arc.Title); + var id = arc.Aid.ToString(); + var bvid = videoDetail.Bvid; + var duration = arc.Duration; + var cover = _imageAdapter.ConvertToImage(arc.Pic); + var collaborators = videoDetail.Staff.Count > 0 + ? videoDetail.Staff.Select(p => _userAdapter.ConvertToRoleProfile(p, Models.Enums.App.AvatarSize.Size32)) + : null; + var publisher = videoDetail.Staff.Count > 0 + ? null + : _userAdapter.ConvertToRoleProfile(arc.Author, Models.Enums.App.AvatarSize.Size32); + var description = _textToolkit.ConvertToTraditionalChineseIfNeeded(arc.Desc); + var publishTime = DateTimeOffset.FromUnixTimeSeconds(arc.Pubdate).DateTime; + var communityInfo = _communityAdapter.ConvertToVideoCommunityInformation(arc.Stat); + var isOriginal = videoDetail.Arc.Copyright == 1; + + var identifier = new VideoIdentifier(id, title, duration, cover); + return new VideoInformation( + identifier, + publisher, + bvid, + description, + publishTime: publishTime, + collaborators: collaborators, + communityInformation: communityInfo, + isOriginal: isOriginal); + } + + private IEnumerable GetSubVideosFromViewReply(ViewReply videoDetail) + { + var subVideos = new List(); + foreach (var page in videoDetail.Pages.OrderBy(p => p.Page.Page_)) + { + var cid = page.Page.Cid.ToString(); + var title = _textToolkit.ConvertToTraditionalChineseIfNeeded(page.Page.Part); + var duration = page.Page.Duration; + var identifier = new VideoIdentifier(cid, title, duration, null); + subVideos.Add(identifier); + } + + return subVideos; + } + + private InteractionVideoRecord GetInteractionRecordFromViewReply(ViewReply videoDetail) + { + var interaction = videoDetail.Interaction; + if (interaction == null) + { + return null; + } + + var partId = interaction.HistoryNode != null + ? interaction.HistoryNode.Cid.ToString() + : videoDetail.Pages.First().Page.Cid.ToString(); + var nodeId = interaction.HistoryNode != null + ? interaction.HistoryNode.NodeId.ToString() + : string.Empty; + var graphVersion = interaction.GraphVersion.ToString(); + + return new InteractionVideoRecord(graphVersion, partId, nodeId); + } + + private UserCommunityInformation GetPublisherCommunityFromViewReply(ViewReply videoDetail) + { + var relation = UserRelationStatus.Unfollow; + if (videoDetail.ReqUser.Attention == 1) + { + relation = videoDetail.ReqUser.GuestAttention == 1 + ? UserRelationStatus.Friends + : UserRelationStatus.Following; + } + else if (videoDetail.ReqUser.GuestAttention == 1) + { + relation = UserRelationStatus.BeFollowed; + } + + return new UserCommunityInformation() + { + Id = videoDetail.Arc.Author.Mid.ToString(), + Relation = relation, + }; + } + + private VideoOpeartionInformation GetOperationInformationFromViewReply(ViewReply videoDetail) + { + var reqUser = videoDetail.ReqUser; + var isLiked = reqUser.Like == 1; + var isCoined = reqUser.Coin == 1; + var isFavorited = reqUser.Favorite == 1; + return new VideoOpeartionInformation( + videoDetail.Arc.Aid.ToString(), + isLiked, + isCoined, + isFavorited, + false); + } + + private IEnumerable GetRelatedVideosFromViewReply(ViewReply videoDetail) + { + // 将非视频内容过滤掉. + var relates = videoDetail.Relates.Where(p => p.Goto.Equals(ServiceConstants.Av, StringComparison.OrdinalIgnoreCase)); + var relatedVideos = relates.Select(p => ConvertToVideoInformation(p)); + return relatedVideos; + } + + private IEnumerable GetVideoSectionsFromViewReply(ViewReply videoDetail) + { + if (videoDetail.UgcSeason == null || videoDetail.UgcSeason.Sections?.Count == 0) + { + return null; + } + + var sections = new List(); + foreach (var item in videoDetail.UgcSeason.Sections) + { + var id = item.Id.ToString(); + var title = _textToolkit.ConvertToTraditionalChineseIfNeeded(item.Title); + var videos = item.Episodes.Select(p => GetVideoInformationFromEpisode(p)); + var section = new VideoSeason(id, title, videos); + sections.Add(section); + } + + return sections; + } + + private PlayedProgress GetHistoryFromViewReply(ViewReply videoDetail) + { + // 当历史记录为空,或者当前视频为交互视频时(交互视频按照节点记录历史),返回 null. + if (videoDetail.History == null || videoDetail.Interaction != null) + { + return null; + } + + var history = videoDetail.History; + var historyStatus = history.Progress switch + { + 0 => PlayedProgressStatus.NotStarted, + -1 => PlayedProgressStatus.Finish, + _ => PlayedProgressStatus.Playing + }; + + var progress = historyStatus == PlayedProgressStatus.Playing + ? history.Progress + : 0; + + var id = history.Cid.ToString(); + var identifier = new VideoIdentifier(id, default, default, default); + return new PlayedProgress(progress, historyStatus, identifier); + } + } +} diff --git a/src/Adapter/Adapter.Interfaces/Adapter.Interfaces.csproj b/src/Adapter/Adapter.Interfaces/Adapter.Interfaces.csproj new file mode 100644 index 000000000..387f0953c --- /dev/null +++ b/src/Adapter/Adapter.Interfaces/Adapter.Interfaces.csproj @@ -0,0 +1,15 @@ + + + + netstandard2.0 + Bili.Adapter.Interfaces + Bili.$(MSBuildProjectName) + + + + + + + + + diff --git a/src/Adapter/Adapter.Interfaces/IArticleAdapter.cs b/src/Adapter/Adapter.Interfaces/IArticleAdapter.cs new file mode 100644 index 000000000..fbcd5cd1c --- /dev/null +++ b/src/Adapter/Adapter.Interfaces/IArticleAdapter.cs @@ -0,0 +1,64 @@ +// Copyright (c) Richasy. All rights reserved. + +using System.Collections.Generic; +using Bili.Models.BiliBili; +using Bili.Models.Data.Article; +using Bilibili.App.Dynamic.V2; + +namespace Bili.Adapter.Interfaces +{ + /// + /// 文章数据适配器接口. + /// + public interface IArticleAdapter + { + /// + /// 将专栏文章 转换成文章信息. + /// + /// 专栏文章. + /// . + ArticleInformation ConvertToArticleInformation(Article article); + + /// + /// 将专栏文章搜索结果 转换成文章信息. + /// + /// 专栏文章搜索结果. + /// . + ArticleInformation ConvertToArticleInformation(ArticleSearchItem item); + + /// + /// 将收藏的专栏文章 转换成文章信息. + /// + /// 收藏的专栏文章. + /// . + ArticleInformation ConvertToArticleInformation(FavoriteArticleItem item); + + /// + /// 将动态文章 转换成文章信息. + /// + /// 动态文章. + /// . + ArticleInformation ConvertToArticleInformation(MdlDynArticle article); + + /// + /// 将专栏推荐响应结果 转换成分区文章视图. + /// + /// 专栏推荐响应结果. + /// . + ArticlePartitionView ConvertToArticlePartitionView(ArticleRecommendResponse response); + + /// + /// 将专栏文章列表转换成分区文章视图. + /// + /// 文章列表. + /// . + ArticlePartitionView ConvertToArticlePartitionView(IEnumerable
articles); + + /// + /// 将收藏的专栏文章响应 转换为文章集. + /// + /// 收藏的专栏文章响应. + /// . + ArticleSet ConvertToArticleSet(ArticleFavoriteListResponse response); + } +} diff --git a/src/Adapter/Adapter.Interfaces/ICommentAdapter.cs b/src/Adapter/Adapter.Interfaces/ICommentAdapter.cs new file mode 100644 index 000000000..86f05f787 --- /dev/null +++ b/src/Adapter/Adapter.Interfaces/ICommentAdapter.cs @@ -0,0 +1,36 @@ +// Copyright (c) Richasy. All rights reserved. + +using Bili.Models.Data.Community; +using Bilibili.Main.Community.Reply.V1; + +namespace Bili.Adapter.Interfaces +{ + /// + /// 评论数据适配器的接口定义. + /// + public interface ICommentAdapter + { + /// + /// 将回复内容 转换为评论信息. + /// + /// 回复内容. + /// . + CommentInformation ConvertToCommentInformation(ReplyInfo info); + + /// + /// 将主评论响应 转换为评论视图. + /// + /// 主评论响应. + /// 评论区Id. + /// . + CommentView ConvertToCommentView(MainListReply reply, string targetId); + + /// + /// 将二级评论响应 转换为评论视图. + /// + /// 二级评论响应. + /// 目标评论Id. + /// . + CommentView ConvertToCommentView(DetailListReply reply, string targetId); + } +} diff --git a/src/Adapter/Adapter.Interfaces/ICommunityAdapter.cs b/src/Adapter/Adapter.Interfaces/ICommunityAdapter.cs new file mode 100644 index 000000000..2f26cb4db --- /dev/null +++ b/src/Adapter/Adapter.Interfaces/ICommunityAdapter.cs @@ -0,0 +1,330 @@ +// Copyright (c) Richasy. All rights reserved. + +using Bili.Models.BiliBili; +using Bili.Models.Data.Community; +using Bilibili.App.Dynamic.V2; +using Bilibili.App.Interfaces.V1; +using Bilibili.Main.Community.Reply.V1; + +namespace Bili.Adapter.Interfaces +{ + /// + /// 社区数据适配器接口. + /// + public interface ICommunityAdapter + { + /// + /// 将个人信息 转换为用户社区交互信息. + /// + /// 个人信息. + /// . + UserCommunityInformation ConvertToUserCommunityInformation(Mine mine); + + /// + /// 将用户空间信息 转换为用户社区交互信息. + /// + /// 用户空间信息. + /// . + UserCommunityInformation ConvertToUserCommunityInformation(UserSpaceInformation spaceInfo); + + /// + /// 将个人信息 转换为用户社区交互信息. + /// + /// 个人信息. + /// . + UserCommunityInformation ConvertToUserCommunityInformation(MyInfo mine); + + /// + /// 将关系用户 转换为用户社区交互信息. + /// + /// 关系用户. + /// . + UserCommunityInformation ConvertToUserCommunityInformation(RelatedUser user); + + /// + /// 将用户搜索结果条目 转换为用户社区交互信息. + /// + /// 用户搜索结果条目. + /// . + UserCommunityInformation ConvertToUserCommunityInformation(UserSearchItem item); + + /// + /// 将推荐卡片 转换为视频交互信息. + /// + /// 推荐卡片信息. + /// . + /// + /// 这里的转换只是将 中关于社区交互的信息提取出来,其它的视频信息交由 来处理. + /// + VideoCommunityInformation ConvertToVideoCommunityInformation(RecommendCard videoCard); + + /// + /// 将分区视频 转换为视频交互信息. + /// + /// 分区视频信息. + /// . + VideoCommunityInformation ConvertToVideoCommunityInformation(PartitionVideo video); + + /// + /// 将动态视频 转换为视频交互信息. + /// + /// 动态视频信息. + /// . + VideoCommunityInformation ConvertToVideoCommunityInformation(MdlDynArchive video); + + /// + /// 将视频状态信息 转换为视频交互信息. + /// + /// 状态信息. + /// . + VideoCommunityInformation ConvertToVideoCommunityInformation(VideoStatusInfo statusInfo); + + /// + /// 将排行榜视频 转换为视频交互信息. + /// + /// 排行榜视频. + /// . + VideoCommunityInformation ConvertToVideoCommunityInformation(Bilibili.App.Show.V1.Item rankItem); + + /// + /// 将热门视频 转换为视频交互信息. + /// + /// 热门视频. + /// . + VideoCommunityInformation ConvertToVideoCommunityInformation(Bilibili.App.Card.V1.Card hotVideo); + + /// + /// 将视频状态数据 转换为视频交互信息. + /// + /// 视频状态数据. + /// . + VideoCommunityInformation ConvertToVideoCommunityInformation(Bilibili.App.Archive.V1.Stat videoStat); + + /// + /// 将视频搜索条目 转换为视频交互信息. + /// + /// 搜索视频条目. + /// . + VideoCommunityInformation ConvertToVideoCommunityInformation(Models.BiliBili.VideoSearchItem searchVideo); + + /// + /// 将用户空间视频条目 转换为视频交互信息. + /// + /// 用户空间视频条目. + /// . + VideoCommunityInformation ConvertToVideoCommunityInformation(UserSpaceVideoItem video); + + /// + /// 将收藏夹视频条目 转换为视频交互信息. + /// + /// 收藏夹视频条目. + /// . + VideoCommunityInformation ConvertToVideoCommunityInformation(FavoriteMedia video); + + /// + /// 将剧集单集社区信息 转换为视频交互信息. + /// + /// 剧集单集社区信息. + /// . + VideoCommunityInformation ConvertToVideoCommunityInformation(PgcEpisodeStat stat); + + /// + /// 将剧集社区信息 转换为视频交互信息. + /// + /// 剧集社区信息. + /// . + VideoCommunityInformation ConvertToVideoCommunityInformation(PgcInformationStat stat); + + /// + /// 将 PGC 条目社区信息 转换为视频交互信息. + /// + /// PGC 条目社区信息. + /// . + VideoCommunityInformation ConvertToVideoCommunityInformation(PgcItemStat stat); + + /// + /// 将 PGC 搜索条目 转换为视频交互信息. + /// + /// PGC 搜索条目. + /// . + VideoCommunityInformation ConvertToVideoCommunityInformation(PgcSearchItem item); + + /// + /// 将 PGC 播放列表条目社区信息 转换为视频交互信息. + /// + /// PGC 搜索条目. + /// . + VideoCommunityInformation ConvertToVideoCommunityInformation(PgcPlayListItemStat stat); + + /// + /// 将 UGC 视频卡片 转换为视频交互信息. + /// + /// UGC 条目信息. + /// . + VideoCommunityInformation ConvertToVideoCommunityInformation(CardUGC ugc); + + /// + /// 将视频状态信息 转换为视频交互信息. + /// + /// 视频状态信息. + /// . + VideoCommunityInformation CovnertToVideoCommunityInformation(VideoStatusInfo info); + + /// + /// 将文章状态 转换为文章社区交互信息. + /// + /// 文章状态. + /// 文章Id. + /// . + ArticleCommunityInformation ConvertToArticleCommunityInformation(ArticleStats stats, string articleId); + + /// + /// 将文章搜索结果 转换为文章社区交互信息. + /// + /// 文章搜索结果. + /// . + ArticleCommunityInformation ConvertToArticleCommunityInformation(Models.BiliBili.ArticleSearchItem item); + + /// + /// 将动态状态数据 转换为动态社区信息. + /// + /// 动态状态数据. + /// 动态 Id. + /// . + DynamicCommunityInformation ConvertToDynamicCommunityInformation(ModuleStat stat, string dynamicId); + + /// + /// 将分区实例 转换为自定义的分区信息. + /// + /// 分区实例. + /// . + Models.Data.Community.Partition ConvertToPartition(Models.BiliBili.Partition partition); + + /// + /// 将直播数据流中的热门区域 转换为自定义的分区信息. + /// + /// 热门区域. + /// . + Models.Data.Community.Partition ConvertToPartition(LiveFeedHotArea area); + + /// + /// 将直播分区 转换为自定义的分区信息. + /// + /// 直播分区. + /// . + Models.Data.Community.Partition ConvertToPartition(LiveArea area); + + /// + /// 将直播分区组 转换为自定义的分区信息. + /// + /// 直播分区组. + /// . + Models.Data.Community.Partition ConvertToPartition(LiveAreaGroup group); + + /// + /// 将PGC标签 转换为自定义的分区信息. + /// + /// PGC标签. + /// . + Models.Data.Community.Partition ConvertToPartition(PgcTab tab); + + /// + /// 将文章分类 转换为自定义的分区信息. + /// + /// 文章分类. + /// . + Models.Data.Community.Partition ConvertToPartition(ArticleCategory category); + + /// + /// 将分区横幅 转换为横幅信息. + /// + /// 分区横幅条目. + /// . + BannerIdentifier ConvertToBannerIdentifier(PartitionBanner banner); + + /// + /// 将直播数据流横幅 转换为横幅信息. + /// + /// 直播数据流横幅条目. + /// . + BannerIdentifier ConvertToBannerIdentifier(LiveFeedBanner banner); + + /// + /// 将PGC模块条目 转换为横幅信息. + /// + /// PGC模块条目. + /// . + BannerIdentifier ConvertToBannerIdentifier(PgcModuleItem item); + + /// + /// 将未读消息 转换为未读信息. + /// + /// 未读消息. + /// . + UnreadInformation ConvertToUnreadInformation(UnreadMessage message); + + /// + /// 将点赞消息条目 转换为消息信息. + /// + /// 消息条目. + /// . + MessageInformation ConvertToMessageInformation(LikeMessageItem messageItem); + + /// + /// 将提及消息条目 转换为消息信息. + /// + /// 消息条目. + /// . + MessageInformation ConvertToMessageInformation(AtMessageItem messageItem); + + /// + /// 将回复消息条目 转换为消息信息. + /// + /// 消息条目. + /// . + MessageInformation ConvertToMessageInformation(ReplyMessageItem messageItem); + + /// + /// 将点赞消息响应 转换为消息信息. + /// + /// 消息响应. + /// . + MessageView ConvertToMessageView(LikeMessageResponse messageResponse); + + /// + /// 将提及消息响应 转换为消息信息. + /// + /// 消息响应. + /// . + MessageView ConvertToMessageView(AtMessageResponse messageResponse); + + /// + /// 将回复消息响应 转换为消息信息. + /// + /// 消息响应. + /// . + MessageView ConvertToMessageView(ReplyMessageResponse messageResponse); + + /// + /// 将关联标签 转换为关注分组. + /// + /// 关联标签. + /// . + FollowGroup ConvertToFollowGroup(RelatedTag tag); + + /// + /// 将一键三连的结果 转换为一键三连信息. + /// + /// 一键三连的结果. + /// 视频 Id. + /// . + TripleInformation ConvertToTripleInformation(TripleResult result, string id); + + /// + /// 将单集交互响应 转换为单集交互信息. + /// + /// 单集交互响应. + /// . + EpisodeInteractionInformation ConvertToEpisodeInteractionInformation(EpisodeInteraction interaction); + } +} diff --git a/src/Adapter/Adapter.Interfaces/IDynamicAdapter.cs b/src/Adapter/Adapter.Interfaces/IDynamicAdapter.cs new file mode 100644 index 000000000..13aa2d516 --- /dev/null +++ b/src/Adapter/Adapter.Interfaces/IDynamicAdapter.cs @@ -0,0 +1,41 @@ +// Copyright (c) Richasy. All rights reserved. + +using Bili.Models.Data.Dynamic; +using Bilibili.App.Dynamic.V2; + +namespace Bili.Adapter.Interfaces +{ + /// + /// 动态数据适配器接口定义. + /// + public interface IDynamicAdapter + { + /// + /// 将动态条目 转换为动态信息. + /// + /// 动态条目. + /// . + DynamicInformation ConvertToDynamicInformation(DynamicItem item); + + /// + /// 将动态转发条目 转换为动态信息. + /// + /// 动态转发条目. + /// . + DynamicInformation ConvertToDynamicInformation(MdlDynForward forward); + + /// + /// 将视频动态响应 转换为动态视图. + /// + /// 视频动态响应. + /// . + DynamicView ConvertToDynamicView(DynVideoReply reply); + + /// + /// 将综合动态响应 转换为动态视图. + /// + /// 综合动态响应. + /// . + DynamicView ConvertToDynamicView(DynAllReply reply); + } +} diff --git a/src/Adapter/Adapter.Interfaces/IFavoriteAdapter.cs b/src/Adapter/Adapter.Interfaces/IFavoriteAdapter.cs new file mode 100644 index 000000000..0a1af31c8 --- /dev/null +++ b/src/Adapter/Adapter.Interfaces/IFavoriteAdapter.cs @@ -0,0 +1,48 @@ +// Copyright (c) Richasy. All rights reserved. + +using Bili.Models.BiliBili; +using Bili.Models.Data.Video; + +namespace Bili.Adapter.Interfaces +{ + /// + /// 收藏夹数据适配器接口. + /// + public interface IFavoriteAdapter + { + /// + /// 将收藏夹列表详情 转换为收藏夹信息. + /// + /// 收藏夹列表详情. + /// . + VideoFavoriteFolder ConvertToVideoFavoriteFolder(FavoriteListDetail detail); + + /// + /// 将收藏夹元数据 转换为收藏夹信息. + /// + /// 收藏夹元数据. + /// . + VideoFavoriteFolder ConvertToVideoFavoriteFolder(FavoriteMeta meta); + + /// + /// 将收藏夹组 转换为收藏夹分组. + /// + /// 收藏夹组. + /// . + VideoFavoriteFolderGroup ConvertToVideoFavoriteFolderGroup(FavoriteFolder folder); + + /// + /// 将视频收藏夹列表响应 转换为视频收藏夹详情. + /// + /// 视频收藏夹列表响应. + /// . + VideoFavoriteFolderDetail ConvertToVideoFavoriteFolderDetail(VideoFavoriteListResponse response); + + /// + /// 将视频收藏夹概览响应 转换为视频收藏视图. + /// + /// 视频收藏夹概览响应. + /// . + VideoFavoriteView ConvertToVideoFavoriteView(VideoFavoriteGalleryResponse response); + } +} diff --git a/src/Adapter/Adapter.Interfaces/IImageAdapter.cs b/src/Adapter/Adapter.Interfaces/IImageAdapter.cs new file mode 100644 index 000000000..ba7648870 --- /dev/null +++ b/src/Adapter/Adapter.Interfaces/IImageAdapter.cs @@ -0,0 +1,65 @@ +// Copyright (c) Richasy. All rights reserved. + +using Bili.Models.Data.Appearance; +using Bilibili.App.Dynamic.V2; +using Bilibili.Main.Community.Reply.V1; + +namespace Bili.Adapter.Interfaces +{ + /// + /// 图片适配器接口,将视频封面、用户头像等转换为 . + /// + public interface IImageAdapter + { + /// + /// 将图片地址转换为 . + /// + /// 图片地址. + /// . + Image ConvertToImage(string uri); + + /// + /// 根据图片地址及宽高信息生成缩略图地址. + /// + /// 图片地址. + /// 图片宽度. + /// 图片高度. + /// . + Image ConvertToImage(string uri, double width, double height); + + /// + /// 根据图片地址生成适用于视频卡片尺寸的缩略图地址. + /// + /// 图片地址. + /// . + Image ConvertToVideoCardCover(string uri); + + /// + /// 根据图片地址生成适用于 PGC 的竖式封面. + /// + /// 图片地址. + /// . + Image ConvertToPgcCover(string uri); + + /// + /// 根据图片地址生成适用于文章卡片尺寸的缩略图地址. + /// + /// 图片地址. + /// . + Image ConvertToArticleCardCover(string uri); + + /// + /// 将动态的描述模块 转换为表情文本. + /// + /// 动态的描述模块. + /// . + EmoteText ConvertToEmoteText(ModuleDesc description); + + /// + /// 将评论内容 转换为表情文本. + /// + /// 评论内容. + /// . + EmoteText ConvertToEmoteText(Content content); + } +} diff --git a/src/Adapter/Adapter.Interfaces/ILiveAdapter.cs b/src/Adapter/Adapter.Interfaces/ILiveAdapter.cs new file mode 100644 index 000000000..494a1b8ab --- /dev/null +++ b/src/Adapter/Adapter.Interfaces/ILiveAdapter.cs @@ -0,0 +1,62 @@ +// Copyright (c) Richasy. All rights reserved. + +using Bili.Models.BiliBili; +using Bili.Models.Data.Live; + +namespace Bili.Adapter.Interfaces +{ + /// + /// 直播数据适配器接口. + /// + public interface ILiveAdapter + { + /// + /// 将关注的直播间 转换为直播间信息. + /// + /// 关注的直播间. + /// . + LiveInformation ConvertToLiveInformation(LiveFeedRoom room); + + /// + /// 从直播间卡片 转换为直播间信息. + /// + /// 直播间卡片. + /// . + LiveInformation ConvertToLiveInformation(LiveRoomCard card); + + /// + /// 从直播搜索结果 转换为直播间信息. + /// + /// 直播搜索结果. + /// . + LiveInformation ConvertToLiveInformation(LiveSearchItem item); + + /// + /// 将直播间详情 转换为直播间播放视图. + /// + /// 直播间详情. + /// . + LivePlayerView ConvertToLivePlayerView(LiveRoomDetail detail); + + /// + /// 将直播首页数据流信息 转换为直播流视图. + /// + /// 直播首页数据流信息. + /// . + LiveFeedView ConvertToLiveFeedView(LiveFeedResponse response); + + /// + /// 将直播分区详情信息 转换为直播分区详情视图. + /// + /// 直播分区详情视图. + /// . + LivePartitionView ConvertToLivePartitionView(LiveAreaDetailResponse response); + + /// + /// 将直播播放信息 转换为直播媒体信息. + /// + /// 直播播放信息. + /// . + LiveMediaInformation ConvertToLiveMediaInformation(LiveAppPlayInformation information); + } +} diff --git a/src/Adapter/Adapter.Interfaces/IPgcAdapter.cs b/src/Adapter/Adapter.Interfaces/IPgcAdapter.cs new file mode 100644 index 000000000..67b2b2506 --- /dev/null +++ b/src/Adapter/Adapter.Interfaces/IPgcAdapter.cs @@ -0,0 +1,144 @@ +// Copyright (c) Richasy. All rights reserved. + +using System.Collections.Generic; +using Bili.Models.BiliBili; +using Bili.Models.Data.Appearance; +using Bili.Models.Data.Pgc; +using Bili.Models.Enums; +using Bilibili.App.Dynamic.V2; + +namespace Bili.Adapter.Interfaces +{ + /// + /// PGC 内容适配器接口. + /// + public interface IPgcAdapter + { + /// + /// 将单集详情 转换为单集信息. + /// + /// 单集详情. + /// . + EpisodeInformation ConvertToEpisodeInformation(PgcEpisodeDetail episode); + + /// + /// 将推荐视频卡片 转换为单集信息. + /// + /// 推荐卡片. + /// . + EpisodeInformation ConvertToEpisodeInformation(RecommendCard card); + + /// + /// 将 PGC 模块条目 转换为单集信息. + /// + /// PGC 模块条目. + /// . + EpisodeInformation ConvertToEpisodeInformation(PgcModuleItem item); + + /// + /// 将 PGC 动态条目 转换为单集信息. + /// + /// PGC 动态条目. + /// . + EpisodeInformation ConvertToEpisodeInformation(MdlDynPGC pgc); + + /// + /// 将视频动态条目 转换为单集信息. + /// + /// 视频动态条目. + /// . + EpisodeInformation ConvertToEpisodeInformation(MdlDynArchive archive); + + /// + /// 将 PGC 模块条目 转换为剧集信息. + /// + /// PGC 模块条目. + /// PGC 内容类型. + /// . + SeasonInformation ConvertToSeasonInformation(PgcModuleItem item, PgcType type); + + /// + /// 将 PGC 搜索条目 转换为剧集信息. + /// + /// PGC 搜索条目. + /// . + SeasonInformation ConvertToSeasonInformation(PgcSearchItem item); + + /// + /// 将 PGC 索引条目 转换为剧集信息. + /// + /// PGC 索引条目. + /// . + SeasonInformation ConvertToSeasonInformation(PgcIndexItem item); + + /// + /// 将时间线剧集 转换为剧集信息. + /// + /// 时间线剧集. + /// . + SeasonInformation ConvertToSeasonInformation(TimeLineEpisode item); + + /// + /// 将 PGC 播放清单的剧集条目 转换为剧集信息. + /// + /// PGC 播放清单条目. + /// . + SeasonInformation ConvertToSeasonInformation(PgcPlayListSeason season); + + /// + /// 将收藏的 PGC 条目 转换为剧集信息. + /// + /// 收藏的 PGC 条目. + /// . + SeasonInformation ConvertToSeasonInformation(FavoritePgcItem item); + + /// + /// 将 PGC 展示信息 封装成视图信息. + /// + /// PGC 展示信息. + /// . + PgcPlayerView ConvertToPgcPlayerView(PgcDisplayInformation display); + + /// + /// 将 PGC 模块 转换为播放列表. + /// + /// PGC 模块. + /// . + PgcPlaylist ConvertToPgcPlaylist(PgcModule module); + + /// + /// 将 PGC 播放列表响应 转换为播放列表. + /// + /// PGC 播放列表响应. + /// . + PgcPlaylist ConvertToPgcPlaylist(PgcPlayListResponse response); + + /// + /// 将 PGC 页面响应 转换为 PGC 页面视图信息. + /// + /// PGC 页面响应. + /// . + PgcPageView ConvertToPgcPageView(PgcResponse response); + + /// + /// 将 PGC 索引条件响应 转换为筛选条件列表. + /// + /// PGC 索引条件响应. + /// 筛选条件列表. + IEnumerable ConvertToFilters(PgcIndexConditionResponse response); + + /// + /// 将 PGC 时间线响应结果 转换为时间线视图. + /// + /// 时间线响应结果. + /// . + TimelineView ConvertToTimelineView(PgcTimeLineResponse response); + + /// + /// 将PGC收藏内容响应 转换为剧集集合. + /// + /// PGC收藏内容响应. + /// . + SeasonSet ConvertToSeasonSet(PgcFavoriteListResponse response); + } +} diff --git a/src/Adapter/Adapter.Interfaces/IPlayerAdapter.cs b/src/Adapter/Adapter.Interfaces/IPlayerAdapter.cs new file mode 100644 index 000000000..7ad908688 --- /dev/null +++ b/src/Adapter/Adapter.Interfaces/IPlayerAdapter.cs @@ -0,0 +1,73 @@ +// Copyright (c) Richasy. All rights reserved. + +using System.Collections.Generic; +using Bili.Models.BiliBili; +using Bili.Models.Data.Player; +using Bilibili.App.Playurl.V1; +using Bilibili.Community.Service.Dm.V1; + +namespace Bili.Adapter.Interfaces +{ + /// + /// 播放器数据适配器. + /// + public interface IPlayerAdapter + { + /// + /// 将视频分段条目 转换成分片信息. + /// + /// 视频分段条目. + /// . + SegmentInformation ConvertToSegmentInformation(Models.BiliBili.DashItem item); + + /// + /// 将视频格式 转换成格式信息. + /// + /// 视频格式. + /// . + FormatInformation ConvertToFormatInformation(VideoFormat format); + + /// + /// 将播放器信息 转换成媒体播放信息. + /// + /// 播放器信息. + /// . + MediaInformation ConvertToMediaInformation(PlayerInformation information); + + /// + /// 将视频播放器信息 转换成媒体播放信息. + /// + /// 播放器信息. + /// . + MediaInformation ConvertToMediaInformation(PlayViewReply reply); + + /// + /// 将字幕索引条目 转换成字幕元数据. + /// + /// 索引条目. + /// . + SubtitleMeta ConvertToSubtitleMeta(SubtitleIndexItem item); + + /// + /// 将字幕条目 转换成字幕信息. + /// + /// 字幕条目. + /// . + SubtitleInformation ConvertToSubtitleInformation(Models.BiliBili.SubtitleItem item); + + /// + /// 将弹幕条目 转化成弹幕信息. + /// + /// 弹幕条目. + /// . + DanmakuInformation ConvertToDanmakuInformation(DanmakuElem danmaku); + + /// + /// 将互动选项 转换成互动条目信息. + /// + /// 互动选项. + /// 关联变量. + /// . + InteractionInformation ConvertToInteractionInformation(InteractionChoice choice, IEnumerable variables); + } +} diff --git a/src/Adapter/Adapter.Interfaces/ISearchAdapter.cs b/src/Adapter/Adapter.Interfaces/ISearchAdapter.cs new file mode 100644 index 000000000..f9102a03a --- /dev/null +++ b/src/Adapter/Adapter.Interfaces/ISearchAdapter.cs @@ -0,0 +1,35 @@ +// Copyright (c) Richasy. All rights reserved. + +using Bili.Models.BiliBili; +using Bili.Models.Data.Search; +using Bilibili.App.Interfaces.V1; + +namespace Bili.Adapter.Interfaces +{ + /// + /// 搜索数据适配器接口定义. + /// + public interface ISearchAdapter + { + /// + /// 将热搜条目 转换为搜索建议条目. + /// + /// 热搜条目. + /// . + SearchSuggest ConvertToSearchSuggest(SearchRecommendItem item); + + /// + /// 将来自 Web 的搜索建议条目 转换为本地搜索建议条目. + /// + /// 来自 Web 的搜索建议条目. + /// . + SearchSuggest ConvertToSearchSuggest(ResultItem item); + + /// + /// 将综合搜索结果响应 转换为综合数据集. + /// + /// 综合搜索结果响应. + /// . + ComprehensiveSet ConvertToComprehensiveSet(ComprehensiveSearchResultResponse response); + } +} diff --git a/src/Adapter/Adapter.Interfaces/IUserAdapter.cs b/src/Adapter/Adapter.Interfaces/IUserAdapter.cs new file mode 100644 index 000000000..ba6e3d6ce --- /dev/null +++ b/src/Adapter/Adapter.Interfaces/IUserAdapter.cs @@ -0,0 +1,122 @@ +// Copyright (c) Richasy. All rights reserved. + +using Bili.Models.BiliBili; +using Bili.Models.Data.User; +using Bili.Models.Enums.App; +using Bilibili.App.Archive.V1; +using Bilibili.App.View.V1; +using Bilibili.Main.Community.Reply.V1; + +namespace Bili.Adapter.Interfaces +{ + /// + /// 用户资料适配器接口,将来自源网站的用户数据转换为 , . + /// + public interface IUserAdapter + { + /// + /// 将数据整合为用户资料. + /// + /// 用户Id. + /// 用户名. + /// 封面. + /// 头像尺寸. + /// . + UserProfile ConvertToUserProfile(long userId, string userName, string avatar, AvatarSize avatarSize); + + /// + /// 将 转换为发布者资料. + /// + /// BiliBili的视频发布者信息. + /// 头像尺寸. + /// . + RoleProfile ConvertToRoleProfile(PublisherInfo publisher, AvatarSize avatarSize); + + /// + /// 将视频合作者信息 转换为发布者资料. + /// + /// 视频合作者信息. + /// 头像尺寸. + /// . + RoleProfile ConvertToRoleProfile(Staff staff, AvatarSize avatarSize); + + /// + /// 将作者信息 转换为发布者资料. + /// + /// 作者信息. + /// 头像大小. + /// . + RoleProfile ConvertToRoleProfile(Author author, AvatarSize avatarSize = AvatarSize.Size32); + + /// + /// 将个人信息 转换为 . + /// + /// 我的资料. + /// 头像尺寸. + /// . + AccountInformation ConvertToAccountInformation(MyInfo myInfo, AvatarSize avatarSize); + + /// + /// 将用户搜索条目 转换为 . + /// + /// 用户搜索条目. + /// 头像尺寸. + /// . + AccountInformation ConvertToAccountInformation(UserSearchItem item, AvatarSize avatarSize = AvatarSize.Size64); + + /// + /// 将个人信息 转换为 . + /// + /// 我的资料. + /// 头像尺寸. + /// . + AccountInformation ConvertToAccountInformation(Mine myInfo, AvatarSize avatarSize); + + /// + /// 将用户空间资料 转换为 . + /// + /// 用户空间资料. + /// 头像尺寸. + /// . + AccountInformation ConvertToAccountInformation(UserSpaceInformation spaceInfo, AvatarSize avatarSize); + + /// + /// 将关系用户信息 转换为 . + /// + /// 关系用户信息. + /// 头像大小. + /// . + AccountInformation ConvertToAccountInformation(RelatedUser user, AvatarSize avatarSize = AvatarSize.Size64); + + /// + /// 将用户信息 转换为 . + /// + /// 用户信息. + /// 头像尺寸. + /// . + AccountInformation ConvertToAccountInformation(Member member, AvatarSize avatarSize = AvatarSize.Size64); + + /// + /// 将推荐卡片的头像信息 转换为角色资料. + /// + /// 推荐卡片的头像信息. + /// 头像尺寸. + /// . + RoleProfile ConvertToRoleProfile(RecommendAvatar avatar, AvatarSize avatarSize = AvatarSize.Size48); + + /// + /// 将明星信息 转换为角色资料. + /// + /// 明星信息. + /// 头像大小. + /// . + RoleProfile ConvertToRoleProfile(PgcCelebrity celebrity, AvatarSize avatarSize = AvatarSize.Size96); + + /// + /// 将关系用户响应结果 转换为 . + /// + /// 关系用户响应结果. + /// . + RelationView ConvertToRelationView(RelatedUserResponse response); + } +} diff --git a/src/Adapter/Adapter.Interfaces/IVideoAdapter.cs b/src/Adapter/Adapter.Interfaces/IVideoAdapter.cs new file mode 100644 index 000000000..0459e5596 --- /dev/null +++ b/src/Adapter/Adapter.Interfaces/IVideoAdapter.cs @@ -0,0 +1,144 @@ +// Copyright (c) Richasy. All rights reserved. + +using System; +using Bili.Models.BiliBili; +using Bili.Models.Data.Video; +using Bilibili.App.Dynamic.V2; +using Bilibili.App.Interfaces.V1; +using Bilibili.App.View.V1; + +namespace Bili.Adapter.Interfaces +{ + /// + /// 视频数据适配器接口. + /// + public interface IVideoAdapter + { + /// + /// 将推荐视频卡片 转换为视频信息. + /// + /// 推荐视频卡片. + /// . + /// 传入的数据不是预期的视频类型. + VideoInformation ConvertToVideoInformation(RecommendCard videoCard); + + /// + /// 将分区视频 转换为视频信息. + /// + /// 分区视频. + /// . + /// + /// 分区视频指的是网站按内容类型分的区域下的视频,比如舞蹈区视频、科技区视频等. + /// + VideoInformation ConvertToVideoInformation(PartitionVideo video); + + /// + /// 将动态里的视频内容 转换为视频信息. + /// + /// 动态里的视频内容. + /// . + /// + /// 有些番剧的更新可能也是以 类型发布,需要提前根据 IsPGC 属性来判断它是不是视频内容,如果不是,执行该方法会抛出异常. + /// + /// 传入的数据不是预期的视频类型. + VideoInformation ConvertToVideoInformation(MdlDynArchive dynamicVideo); + + /// + /// 将稍后再看里的视频内容 转换为视频信息. + /// + /// 稍后再看的视频. + /// . + VideoInformation ConvertToVideoInformation(ViewLaterVideo video); + + /// + /// 将排行榜里的视频内容 转换为视频信息. + /// + /// 排行榜视频. + /// . + VideoInformation ConvertToVideoInformation(Bilibili.App.Show.V1.Item rankVideo); + + /// + /// 将热门视频内容 转换为视频信息. + /// + /// 热门视频. + /// . + VideoInformation ConvertToVideoInformation(Bilibili.App.Card.V1.Card hotVideo); + + /// + /// 将关联视频内容 转换为视频信息. + /// + /// 关联视频. + /// . + VideoInformation ConvertToVideoInformation(Bilibili.App.View.V1.Relate relatedVideo); + + /// + /// 将视频搜索结果 转换为视频信息. + /// + /// 视频搜索结果. + /// . + VideoInformation ConvertToVideoInformation(VideoSearchItem searchVideo); + + /// + /// 将用户空间内的视频内容 转换为视频信息. + /// + /// 用户空间内的视频内容. + /// . + VideoInformation ConvertToVideoInformation(UserSpaceVideoItem spaceVideo); + + /// + /// 将历史记录里的视频内容 转换为视频信息. + /// + /// 历史记录里的视频内容. + /// . + VideoInformation ConvertToVideoInformation(CursorItem historyVideo); + + /// + /// 将收藏夹内的视频内容 转换为视频信息. + /// + /// 收藏夹视频. + /// . + VideoInformation ConvertToVideoInformation(FavoriteMedia video); + + /// + /// 将用户空间里的视频搜索结果 转换为视频信息. + /// + /// 用户空间里的视频搜索结果. + /// . + VideoInformation ConvertToVideoInformation(Arc video); + + /// + /// 将视频详情 转换为视频信息. + /// + /// 视频详情. + /// . + VideoPlayerView ConvertToVideoView(ViewReply videoDetail); + + /// + /// 将稍后再看响应 转换为视频集. + /// + /// 稍后在看响应结果. + /// . + VideoSet ConvertToVideoSet(ViewLaterResponse response); + + /// + /// 将用户空间视频集 转换为视频集. + /// + /// 稍后在看响应结果. + /// . + VideoSet ConvertToVideoSet(UserSpaceVideoSet set); + + /// + /// 将视频搜索结果 转换为视频集. + /// + /// 稍后在看响应结果. + /// . + VideoSet ConvertToVideoSet(SearchArchiveReply reply); + + /// + /// 将历史记录响应结果 转换为历史记录视图. + /// + /// 历史记录响应结果. + /// . + VideoHistoryView ConvertToVideoHistoryView(CursorV2Reply reply); + } +} diff --git a/src/Adapter/Adapter.UnitTests/Adapter.UnitTests.csproj b/src/Adapter/Adapter.UnitTests/Adapter.UnitTests.csproj new file mode 100644 index 000000000..0bef67580 --- /dev/null +++ b/src/Adapter/Adapter.UnitTests/Adapter.UnitTests.csproj @@ -0,0 +1,30 @@ + + + + net6.0 + Bili.Adapter.UnitTests + Bili.$(MSBuildProjectName) + + + + + + + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + diff --git a/src/Adapter/Adapter.UnitTests/ImageAdapterTests.cs b/src/Adapter/Adapter.UnitTests/ImageAdapterTests.cs new file mode 100644 index 000000000..94a0cc9a5 --- /dev/null +++ b/src/Adapter/Adapter.UnitTests/ImageAdapterTests.cs @@ -0,0 +1,26 @@ +// Copyright (c) Richasy. All rights reserved. + +namespace Bili.Adapter.UnitTests +{ + public class ImageAdapterTests + { + // private const string ImageUrl = "https://xxx.com/11111.png"; + // [Fact] + // public void ConvertToImage_SingleUrl_Valid() + // { + // var adapter = new ImageAdapter(); + // var image = adapter.ConvertToImage(ImageUrl); + // image.Uri.ToString().Should().Be(ImageUrl); + // } + // [Fact] + // public void ConvertToImage_WithSize_Valid() + // { + // var adapter = new ImageAdapter(); + // var width = 400; + // var height = 300; + // var image = adapter.ConvertToImage(ImageUrl, width, height); + // image.Uri.ToString().Should().Contain("400w"); + // image.Uri.ToString().Should().Contain("300h"); + // } + } +} diff --git a/src/App/App.csproj b/src/App/App.csproj index 28b84754f..431b28d3b 100644 --- a/src/App/App.csproj +++ b/src/App/App.csproj @@ -4,8 +4,8 @@ {1C288BC0-72F3-4C4C-90AD-A05B52E937B0} AppContainerExe - Richasy.Bili.App - Richasy + Bili.App + Bili.App {A5A43C5B-DE2A-4C0C-9213-0A381AF9435A};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} true false @@ -22,41 +22,157 @@ D:\Package\Bili True Always - arm64 + x64 0 App_TemporaryKey.pfx + False + C3B9A35BB185E430F381F524A644B2598DDF2500 false + + 2008,8305 + false + Properties\SharedAssemblyInfo.cs + + + + + + PageHeader.xaml + + + XboxNavigationView.xaml + + + + XboxAnimePageView.xaml + + + XboxPgcPageView.xaml + + + + + + + + + + + + PlayRateButton.xaml + + + + + TipPopup.xaml + + + + + CommentBox.xaml + + + + + + + CommentPopup.xaml + + + CommentRepeater.xaml + + + + + RelationPageView.xaml + + + DynamicImageItem.xaml + + + + DynamicModule.xaml + + + DynamicPresenter.xaml + + + DynamicNotSupportItem.xaml + + + ImageViewer.xaml + + + + + + + DesktopAnimePageView.xaml + + + + DesktopPgcPageView.xaml + + + + + + NotificationSettingSection.xaml + + + + UgcSeasonView.xaml + + + VideoPlaylistView.xaml + + + RoamingSettingSection.xaml + + + ScreenshotSettingSection.xaml + + + + TraditionalChineseSettingSection.xaml + + + CoverDownloaderView.xaml + + + AvBvConverterView.xaml + + + + CacheSettingSection.xaml + DanmakuSendOptions.xaml - - - AtMessageView.xaml - - - AtMessageItem.xaml + + ContinuePlayDialog.xaml - - DynamicItem.xaml + + UpgradeDialog.xaml - - DynamicPgcItem.xaml + + InteractionChoicePanel.xaml - - DynamicVideoItem.xaml + + DownloadOptionsPanel.xaml ArticleFavoritePanel.xaml @@ -64,59 +180,32 @@ ConfirmDialog.xaml - - FavoriteVideoView.xaml - - PgcFavoritePanel.xaml - - LikeMessageItem.xaml - - - LikeMessageView.xaml - - - - PgcDetailView.xaml + + PgcSeasonDetailView.xaml - - PgcPlayListView.xaml + + PgcPlaylistDetailView.xaml - - PlayerReplyView.xaml - - - ReplyItem.xaml - - - ReplyView.xaml - - - ReplyDetailView.xaml + + PlayerCommentView.xaml TrimTextBlock.xaml - - ReaderView.xaml - - - - - - - ReplyMessageItem.xaml - - - ReplyMessageView.xaml + + ArticleReaderView.xaml QuestionPanel.xaml - - ReplyModuleView.xaml + + SubtitleConfigPanel.xaml + + + InitialCheckSection.xaml AccountPanel.xaml @@ -124,31 +213,19 @@ - - CommonImageEx.xaml - DanmakuBox.xaml - - DanmakuDisplayOptions.xaml - - - - - - ArticleItem.xaml - HorizontalRepeaterView.xaml @@ -157,50 +234,28 @@ - - FollowLiveItem.xaml - - - FollowLiveView.xaml - - - PgcItem.xaml - - - PlayerDashboard.xaml - - - - PlayerDescriptor.xaml - - - PlayerRelatedView.xaml - - - EpisodeView.xaml + + PgcEpisodeView.xaml - + LiveMessageView.xaml - - PgcSectionView.xaml + + PgcExtraView.xaml - + RelatedVideoView.xaml - - SeasonView.xaml + + PgcSeasonView.xaml - + VideoPartView.xaml SearchSuggestBox.xaml - - SearchArticleFilter.xaml - SearchArticleView.xaml @@ -211,21 +266,12 @@ SearchPgcView.xaml - - SearchUserFilter.xaml - SearchUserView.xaml - - SearchVideoFilter.xaml - SearchVideoView.xaml - - MTCSettingSection.xaml - PlayerControlSettingSection.xaml @@ -242,12 +288,7 @@ ThemeSettingSection.xaml - - UserSlimCard.xaml - - - UserView.xaml - + AccountAvatar.xaml @@ -259,27 +300,15 @@ ErrorPanel.xaml - - IconTextBlock.xaml - App.xaml AppTitleBar.xaml - - PartitionItem.xaml - - - RootNavigationView.xaml - - - VideoItem.xaml - - - VerticalRepeaterView.xaml + + DesktopNavigationView.xaml UserAvatar.xaml @@ -287,117 +316,243 @@ VideoFavoritePanel.xaml + + - + + + + + + + + + + + + + + + + + + + + + + + + + LivePartitionPage.xaml + + + LivePlayerPage.xaml + + + MyFollowsPage.xaml + + FansPage.xaml - + FavoritePage.xaml - + FollowsPage.xaml - + HistoryPage.xaml - + + LivePartitionDetailPage.xaml + + MessagePage.xaml - - PlayerPage.xaml + + PgcPlayerPage.xaml - + SearchPage.xaml - + PgcIndexPage.xaml - + TimeLinePage.xaml - - AnimePage.xaml - - + DocumentaryPage.xaml - - DomesticAnimePage.xaml + + DomesticPage.xaml - - DynamicFeedPage.xaml + + DynamicPage.xaml - + BangumiPage.xaml - - FeedPage.xaml - - + HelpPage.xaml - + + UserSpacePage.xaml + + + VideoFavoriteDetailPage.xaml + + + VideoPlayerPage.xaml + + PopularPage.xaml - + RecommendPage.xaml - - - LivePage.xaml + + LiveFeedPage.xaml - + MoviePage.xaml - - PartitionDetailPage.xaml + + VideoPartitionDetailPage.xaml - - PartitionPage.xaml + + VideoPartitionPage.xaml - + RankPage.xaml RootPage.xaml - + ViewLaterPage.xaml - + SettingPage.xaml - - SpecialColumnPage.xaml + + ArticlePartitionPage.xaml + + + ToolboxPage.xaml + + + TvPage.xaml + + + BangumiPage.xaml + + + DocumentaryPage.xaml + + + DomesticPage.xaml + + + DynamicPage.xaml + + + LiveFeedPage.xaml + + + MoviePage.xaml + + + FavoritePage.xaml + + + LivePartitionDetailPage.xaml + + + LivePartitionPage.xaml - - TVPage.xaml + + LivePlayerPage.xaml + + + PgcPlayerPage.xaml + + + SearchPage.xaml + + + UserSpacePage.xaml + + + VideoFavoriteDetailPage.xaml + + + VideoPartitionDetailPage.xaml + + + VideoPlayerPage.xaml + + + PreSearchPage.xaml + + + SettingsPage.xaml + + + TvPage.xaml + + + VideoPartitionPage.xaml + + + PopularPage.xaml + + + RankPage.xaml + + + RecommendPage.xaml + + + XboxAccountPage.xaml + - + - + + + + + - + + + + + + + + - - + + @@ -409,7 +564,9 @@ + + @@ -417,6 +574,7 @@ + @@ -467,9 +625,9 @@ + + - - @@ -481,27 +639,7 @@ - - - - - - - - - - - - - - - - - - - - MSBuild:Compile @@ -511,6 +649,46 @@ Designer MSBuild:Compile + + Designer + MSBuild:Compile + + + Designer + MSBuild:Compile + + + Designer + MSBuild:Compile + + + Designer + MSBuild:Compile + + + Designer + MSBuild:Compile + + + Designer + MSBuild:Compile + + + Designer + MSBuild:Compile + + + Designer + MSBuild:Compile + + + Designer + MSBuild:Compile + + + Designer + MSBuild:Compile + Designer MSBuild:Compile @@ -519,243 +697,291 @@ Designer MSBuild:Compile - + Designer MSBuild:Compile - + Designer MSBuild:Compile - + Designer MSBuild:Compile - + Designer MSBuild:Compile - + Designer MSBuild:Compile - + Designer MSBuild:Compile - + Designer MSBuild:Compile - + Designer MSBuild:Compile - + Designer MSBuild:Compile - + Designer MSBuild:Compile - + Designer MSBuild:Compile - + Designer MSBuild:Compile - + Designer MSBuild:Compile - + Designer MSBuild:Compile - + Designer MSBuild:Compile - + Designer MSBuild:Compile - + Designer MSBuild:Compile - + Designer MSBuild:Compile - + Designer MSBuild:Compile - + Designer MSBuild:Compile - + Designer MSBuild:Compile - + Designer MSBuild:Compile - + Designer MSBuild:Compile - + Designer MSBuild:Compile - + Designer MSBuild:Compile - + Designer MSBuild:Compile - + Designer MSBuild:Compile - + Designer MSBuild:Compile - + Designer MSBuild:Compile - + Designer MSBuild:Compile - + Designer MSBuild:Compile - + Designer MSBuild:Compile - + Designer MSBuild:Compile - + Designer MSBuild:Compile - + Designer MSBuild:Compile - + Designer MSBuild:Compile - + Designer MSBuild:Compile - + Designer MSBuild:Compile - + Designer MSBuild:Compile - + Designer MSBuild:Compile - + Designer MSBuild:Compile - + Designer MSBuild:Compile - + Designer MSBuild:Compile - + Designer MSBuild:Compile - + Designer MSBuild:Compile - + Designer MSBuild:Compile - + Designer MSBuild:Compile - + Designer MSBuild:Compile - + Designer MSBuild:Compile - + Designer MSBuild:Compile - + Designer MSBuild:Compile - + Designer MSBuild:Compile - + Designer MSBuild:Compile - + Designer MSBuild:Compile - + Designer MSBuild:Compile - + Designer MSBuild:Compile - + + Designer + MSBuild:Compile + + Designer MSBuild:Compile - + Designer MSBuild:Compile - + + Designer + MSBuild:Compile + + + Designer + MSBuild:Compile + + + Designer + MSBuild:Compile + + + Designer + MSBuild:Compile + + + Designer + MSBuild:Compile + + + Designer + MSBuild:Compile + + + Designer + MSBuild:Compile + + + Designer + MSBuild:Compile + + + Designer + MSBuild:Compile + + + Designer + MSBuild:Compile + + + Designer + MSBuild:Compile + + Designer MSBuild:Compile - + Designer MSBuild:Compile @@ -779,11 +1005,7 @@ Designer MSBuild:Compile - - Designer - MSBuild:Compile - - + Designer MSBuild:Compile @@ -799,131 +1021,139 @@ Designer MSBuild:Compile - + Designer MSBuild:Compile - + Designer MSBuild:Compile - + Designer MSBuild:Compile - + Designer MSBuild:Compile - + Designer MSBuild:Compile - + Designer MSBuild:Compile - + Designer MSBuild:Compile - + Designer MSBuild:Compile - + + Designer + MSBuild:Compile + + Designer MSBuild:Compile - + Designer MSBuild:Compile - + Designer MSBuild:Compile - + Designer MSBuild:Compile - + Designer MSBuild:Compile - + Designer MSBuild:Compile - + Designer MSBuild:Compile - + Designer MSBuild:Compile - + Designer MSBuild:Compile - + Designer MSBuild:Compile - + Designer MSBuild:Compile - + Designer MSBuild:Compile - + Designer MSBuild:Compile - + Designer MSBuild:Compile - + Designer MSBuild:Compile - + Designer MSBuild:Compile - + Designer MSBuild:Compile - + Designer MSBuild:Compile - + Designer MSBuild:Compile - + Designer MSBuild:Compile - + Designer MSBuild:Compile - + Designer MSBuild:Compile - + Designer MSBuild:Compile - + + Designer + MSBuild:Compile + + Designer MSBuild:Compile @@ -931,19 +1161,119 @@ MSBuild:Compile Designer - + + Designer + MSBuild:Compile + + + Designer + MSBuild:Compile + + + Designer + MSBuild:Compile + + + Designer + MSBuild:Compile + + + Designer + MSBuild:Compile + + + Designer + MSBuild:Compile + + + Designer + MSBuild:Compile + + + Designer + MSBuild:Compile + + + Designer + MSBuild:Compile + + + Designer + MSBuild:Compile + + + Designer + MSBuild:Compile + + + Designer + MSBuild:Compile + + + Designer + MSBuild:Compile + + + Designer + MSBuild:Compile + + + Designer + MSBuild:Compile + + + Designer + MSBuild:Compile + + + Designer + MSBuild:Compile + + + Designer + MSBuild:Compile + + + Designer + MSBuild:Compile + + + Designer + MSBuild:Compile + + + Designer + MSBuild:Compile + + + Designer + MSBuild:Compile + + + Designer + MSBuild:Compile + + Designer MSBuild:Compile - + Designer MSBuild:Compile - + Designer MSBuild:Compile - + + Designer + MSBuild:Compile + + + Designer + MSBuild:Compile + + Designer MSBuild:Compile @@ -973,99 +1303,90 @@ - - 0.9.2 + + 0.0.13 - - 1.0.33 - - - 3.3.0 + + 2.14.1 - 6.2.12 + 6.2.14 - 7.1.0-rc2 + 7.1.3 - 7.1.0-rc2 + 7.1.3 + + + 7.1.3 - 7.1.0-rc2 + 7.1.3 - 7.1.0-rc2 + 7.1.3 - 2.7.0 + 2.8.2-prerelease.220830001 - 13.0.1 - - - 5.1.0 - - - 1.0.4 + 13.0.3 - 1.1.135 - - - 1.0.0 - - - 1.1.118 - runtime; build; native; contentfiles; analyzers; buildtransitive - all + 1.1.150 1.26.0 + + 5.0.0 + - - - - + + - - {5213E830-43F5-491E-A8EF-D75083578EF3} - Controller.Uwp + + {18d92f2f-046e-4a35-83fc-476b097bb8b7} + DI.App + + + {81b36a8c-e9a5-46f1-b803-b4d329c6e4b4} + DI.Container + + + {b9cd5845-2de3-40fc-bc0f-27c406929551} + FrostMaster.Uwp + + + {1e466e0c-5218-4892-9fdd-0e777795c545} + SignIn.Uwp {9e61cc56-43f9-489c-aaaf-bd5ec69367c4} Models.App - - {8776a0bd-dbd1-4f11-a022-400d044ff618} - Models.BiliBili + + {C4DBF0B2-C71A-4528-BF15-B0A7A25B2510} + Models.Data {88314412-b020-415b-aeab-57adc43b273e} Models.Enums - - {0187840F-8D1A-4198-B5BF-8B05CADB4554} - Models.gRPC - - - {793ec923-d704-4c6f-9506-eb6a32bfbb8d} - Locator.Uwp + + {c7bea810-0f31-4176-99a0-4481733918de} + Tasks {ca4fe39c-2379-4966-9940-de738d94e982} Toolkit.Interfaces - - {07b6d3b7-0ac0-489b-bf27-588ab6226b1c} - Toolkit.Uwp - - - {1af20fdc-36d5-450e-9461-0f78aeb89716} - ViewModels.Uwp + + {CD9930DC-36A8-45FC-8824-C8B3FA873FAD} + ViewModels.Interfaces @@ -1076,6 +1397,8 @@ PreserveNewest + + + + @@ -81,12 +64,16 @@ - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Text="{loc:Locale Name=AppName}" /> - + + VerticalAlignment="Center" + TabIndex="4" /> - + diff --git a/src/App/Controls/App/AppTitleBar.xaml.cs b/src/App/Controls/App/AppTitleBar.xaml.cs index 499e3a795..e63207735 100644 --- a/src/App/Controls/App/AppTitleBar.xaml.cs +++ b/src/App/Controls/App/AppTitleBar.xaml.cs @@ -1,77 +1,83 @@ // Copyright (c) Richasy. All rights reserved. using System.ComponentModel; -using Richasy.Bili.ViewModels.Uwp; +using Bili.DI.Container; +using Bili.Models.Data.Local; +using Bili.ViewModels.Interfaces.Core; +using CommunityToolkit.Mvvm.ComponentModel; using Windows.UI.Xaml; -using Windows.UI.Xaml.Controls; -namespace Richasy.Bili.App.Controls +namespace Bili.App.Controls { /// /// 应用标题栏. /// - public sealed partial class AppTitleBar : UserControl + public sealed partial class AppTitleBar : AppTitleBarBase { - /// - /// 的依赖属性. - /// - public static readonly DependencyProperty ViewModelProperty = - DependencyProperty.Register(nameof(ViewModel), typeof(AppViewModel), typeof(AppTitleBar), new PropertyMetadata(AppViewModel.Instance)); + private readonly INavigationViewModel _navigationViewModel; + private readonly IRecordViewModel _recordViewModel; /// /// Initializes a new instance of the class. /// public AppTitleBar() { - this.InitializeComponent(); - this.Loaded += OnLoaded; - } - - /// - /// 视图模型. - /// - public AppViewModel ViewModel - { - get { return (AppViewModel)GetValue(ViewModelProperty); } - set { SetValue(ViewModelProperty, value); } - } - - private void OnLoaded(object sender, RoutedEventArgs e) - { - Window.Current.SetTitleBar(TitleBarHost); - CheckBackButtonVisibility(); - ViewModel.PropertyChanged += OnViewModelPropertyChanged; + InitializeComponent(); + ViewModel = Locator.Instance.GetService(); + _navigationViewModel = Locator.Instance.GetService(); + _recordViewModel = Locator.Instance.GetService(); + (ViewModel as ObservableObject).PropertyChanged += OnViewModelPropertyChanged; + Loaded += OnLoaded; } private void OnViewModelPropertyChanged(object sender, PropertyChangedEventArgs e) { - if (e.PropertyName == nameof(ViewModel.IsOpenPlayer) || e.PropertyName == nameof(ViewModel.IsShowOverlay)) + if (e.PropertyName == nameof(ViewModel.IsShowTitleBar)) { - CheckBackButtonVisibility(); + ChangeTitleBarVisibility(); } } + private void OnLoaded(object sender, RoutedEventArgs e) + => ChangeTitleBarVisibility(); + private void OnMenuButtonClick(object sender, RoutedEventArgs e) - { - ViewModel.IsNavigatePaneOpen = !ViewModel.IsNavigatePaneOpen; - } + => ViewModel.IsNavigatePaneOpen = !ViewModel.IsNavigatePaneOpen; - private void OnBackButtonClick(object sender, RoutedEventArgs e) + private void ChangeTitleBarVisibility() { - if (ViewModel.IsOpenPlayer) + if (ViewModel.IsShowTitleBar) { - ViewModel.IsOpenPlayer = false; + Height = 48; + Window.Current.SetTitleBar(TitleBarHost); } - else if (ViewModel.IsShowOverlay) + else { - ViewModel.SetMainContentId(ViewModel.CurrentMainContentId); + Height = 0; + Window.Current.SetTitleBar(null); } } - private void CheckBackButtonVisibility() + private void OnRemoveRecordButtonClick(object sender, RoutedEventArgs e) + { + var context = (sender as FrameworkElement).DataContext as PlayRecord; + _recordViewModel.RemovePlayRecordCommand.Execute(context); + } + + private void OnPlayRecordItemClick(object sender, Windows.UI.Xaml.Controls.ItemClickEventArgs e) { - BackButton.Visibility = (ViewModel.IsShowOverlay || ViewModel.IsOpenPlayer) ? - Visibility.Visible : Visibility.Collapsed; + var context = e.ClickedItem as PlayRecord; + _navigationViewModel.NavigateToPlayView(context.Snapshot); } + + private void OnClearRecordsButtonClick(object sender, RoutedEventArgs e) + => RecordsFlyout.Hide(); + } + + /// + /// 的基类. + /// + public class AppTitleBarBase : ReactiveUserControl + { } } diff --git a/src/App/Controls/App/BubbleView/Bubble.cs b/src/App/Controls/App/BubbleView/Bubble.cs index 5d844c3de..a4764920a 100644 --- a/src/App/Controls/App/BubbleView/Bubble.cs +++ b/src/App/Controls/App/BubbleView/Bubble.cs @@ -8,7 +8,7 @@ using Windows.UI; using Windows.UI.Composition; -namespace Richasy.Bili.App.Controls +namespace Bili.App.Controls { /// /// 气泡. @@ -64,28 +64,27 @@ public Bubble( if (size.HasValue) { - this._size = size.Value.ToVector2(); + _size = size.Value.ToVector2(); } else { var maxRadius = (int)Math.Min(targetSize.Width, targetSize.Height); if (isFill.Value) { - this._size = new Vector2(_rnd.Next(maxRadius / 7, maxRadius / 4)); + _size = new Vector2(_rnd.Next(maxRadius / 7, maxRadius / 4)); } else { - this._size = new Vector2(_rnd.Next(maxRadius / 6, maxRadius / 3)); + _size = new Vector2(_rnd.Next(maxRadius / 6, maxRadius / 3)); } } Draw(isFill.Value, color); _offset = new Vector3((float)targetSize.Width / 2, (float)targetSize.Height / 2, 0f); - _visual.Size = this._size; + _visual.Size = _size; _visual.Offset = _offset; _visual.Scale = Vector3.Zero; - _visual.BindCenterPoint(); CreateAnimation(targetSize, _visual.Offset, onTop, duration); } @@ -111,15 +110,15 @@ public void AddTo(ContainerVisual container) /// public void Start() { - _visual.StopAnimationGroup(_animations); - _visual.StartAnimationGroup(_animations); + if (_animations != null) + { + _visual.StopAnimationGroup(_animations); + _visual.StartAnimationGroup(_animations); + } } /// - public void Dispose() - { - Dispose(true); - } + public void Dispose() => Dispose(true); private void Draw(bool isFill, Color color) { diff --git a/src/App/Controls/App/BubbleView/BubbleView.Properties.cs b/src/App/Controls/App/BubbleView/BubbleView.Properties.cs index 14fe69125..e0bca050c 100644 --- a/src/App/Controls/App/BubbleView/BubbleView.Properties.cs +++ b/src/App/Controls/App/BubbleView/BubbleView.Properties.cs @@ -7,7 +7,7 @@ using Windows.UI.Xaml; using Windows.UI.Xaml.Shapes; -namespace Richasy.Bili.App.Controls +namespace Bili.App.Controls { /// /// 气泡发生器. diff --git a/src/App/Controls/App/BubbleView/BubbleView.cs b/src/App/Controls/App/BubbleView/BubbleView.cs index 5aa877c68..c0788e89e 100644 --- a/src/App/Controls/App/BubbleView/BubbleView.cs +++ b/src/App/Controls/App/BubbleView/BubbleView.cs @@ -15,7 +15,7 @@ using Windows.UI.Xaml.Media; using Windows.UI.Xaml.Shapes; -namespace Richasy.Bili.App.Controls +namespace Bili.App.Controls { /// /// 气泡发生器. @@ -33,10 +33,10 @@ public partial class BubbleView : Control /// public BubbleView() { - this.DefaultStyleKey = typeof(BubbleView); + DefaultStyleKey = typeof(BubbleView); _foregroundPropertyChangedToken = RegisterPropertyChangedCallback(ForegroundProperty, ForegroundPropertyChanged); - this.Unloaded += OnBubbleViewUnloaded; - this.Loaded += OnBubbleViewLoaded; + Unloaded += OnBubbleViewUnloaded; + Loaded += OnBubbleViewLoaded; } /// @@ -170,7 +170,7 @@ private void CreateBubbles() return; } - if (_foregroundColor != Colors.Transparent && this.ActualWidth > 0 && this.ActualHeight > 0) + if (_foregroundColor != Colors.Transparent && ActualWidth > 0 && ActualHeight > 0) { var count = 20; _bubbles = new List(); @@ -182,7 +182,7 @@ private void CreateBubbles() _compositor, _canvasDevice, _graphicsDevice, - new Size(this.ActualWidth, this.ActualHeight), + new Size(ActualWidth, ActualHeight), _foregroundColor, duration, true); @@ -196,7 +196,7 @@ private void CreateBubbles() _compositor, _canvasDevice, _graphicsDevice, - new Size(this.ActualWidth, this.ActualHeight), + new Size(ActualWidth, ActualHeight), _foregroundColor, duration, false); diff --git a/src/App/Controls/App/BubbleView/BubbleView.xaml b/src/App/Controls/App/BubbleView/BubbleView.xaml index b25d75933..e31316a87 100644 --- a/src/App/Controls/App/BubbleView/BubbleView.xaml +++ b/src/App/Controls/App/BubbleView/BubbleView.xaml @@ -1,7 +1,7 @@  + xmlns:local="using:Bili.App.Controls"> + diff --git a/src/App/Controls/App/DesktopNavigationView.xaml b/src/App/Controls/App/DesktopNavigationView.xaml new file mode 100644 index 000000000..55f09eb9b --- /dev/null +++ b/src/App/Controls/App/DesktopNavigationView.xaml @@ -0,0 +1,190 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/App/Controls/App/DesktopNavigationView.xaml.cs b/src/App/Controls/App/DesktopNavigationView.xaml.cs new file mode 100644 index 000000000..57faafd51 --- /dev/null +++ b/src/App/Controls/App/DesktopNavigationView.xaml.cs @@ -0,0 +1,178 @@ +// Copyright (c) Richasy. All rights reserved. + +using System; +using System.ComponentModel; +using System.Linq; +using Bili.App.Controls.Base; +using Bili.App.Pages.Desktop; +using Bili.App.Resources.Extension; +using Bili.Models.App.Args; +using Bili.Models.Data.Local; +using Bili.Models.Enums; +using Windows.UI.Xaml; +using Windows.UI.Xaml.Media.Animation; + +namespace Bili.App.Controls.App +{ + /// + /// 桌面平台的主视图导航框架. + /// + public sealed partial class DesktopNavigationView : NavigationViewBase + { + /// + /// Initializes a new instance of the class. + /// + public DesktopNavigationView() + : base() + { + InitializeComponent(); + Loaded += OnLoaded; + Unloaded += OnUnloaded; + } + + private void OnLoaded(object sender, RoutedEventArgs e) + { + ViewModel.Navigating += OnNavigating; + ViewModel.PropertyChanged += OnViewModelPropertyChanged; + } + + private void OnUnloaded(object sender, RoutedEventArgs e) + { + ViewModel.Navigating -= OnNavigating; + ViewModel.PropertyChanged -= OnViewModelPropertyChanged; + } + + private void OnNavigating(object sender, AppNavigationEventArgs e) + { + if (e.Type == Models.Enums.App.NavigationType.Main) + { + NavigateToMainView(e.PageId); + } + + CheckMenuButtonVisibility(); + } + + private void OnRootNavViewItemInvoked(Microsoft.UI.Xaml.Controls.NavigationView sender, Microsoft.UI.Xaml.Controls.NavigationViewItemInvokedEventArgs args) + { + if (args.IsSettingsInvoked) + { + ViewModel.NavigateToMainView(PageIds.Settings); + } + else + { + var pageId = NavigationExtension.GetPageId(args.InvokedItemContainer); + ViewModel.Navigate(pageId); + } + } + + private void NavigateToMainView(PageIds pageId, object parameter = null) + { + Type pageType = null; + switch (pageId) + { + case PageIds.None: + default: + break; + case PageIds.Recommend: + pageType = typeof(RecommendPage); + break; + case PageIds.Rank: + pageType = typeof(RankPage); + break; + case PageIds.VideoPartition: + pageType = typeof(VideoPartitionPage); + break; + case PageIds.Popular: + pageType = typeof(PopularPage); + break; + case PageIds.SpecialColumn: + pageType = typeof(ArticlePartitionPage); + break; + case PageIds.Bangumi: + pageType = typeof(BangumiPage); + break; + case PageIds.DomesticAnime: + pageType = typeof(DomesticPage); + break; + case PageIds.Movie: + pageType = typeof(MoviePage); + break; + case PageIds.TV: + pageType = typeof(TvPage); + break; + case PageIds.Documentary: + pageType = typeof(DocumentaryPage); + break; + case PageIds.Live: + pageType = typeof(LiveFeedPage); + break; + case PageIds.Dynamic: + pageType = typeof(DynamicPage); + break; + case PageIds.Help: + pageType = typeof(HelpPage); + break; + case PageIds.Toolbox: + pageType = typeof(ToolboxPage); + break; + case PageIds.Settings: + pageType = typeof(SettingsPage); + break; + } + + if (pageType != null) + { + MainFrame.Navigate(pageType, parameter, new DrillInNavigationTransitionInfo()); + } + + if (RootNavView.SelectedItem == null || + (RootNavView.SelectedItem is Microsoft.UI.Xaml.Controls.NavigationViewItem navItem && + NavigationExtension.GetPageId(navItem) != pageId)) + { + var shouldSelectedItem = pageId == PageIds.Settings + ? RootNavView.SettingsItem as Microsoft.UI.Xaml.Controls.NavigationViewItem + : RootNavView.MenuItems.Concat(RootNavView.FooterMenuItems).OfType() + .Where(p => NavigationExtension.GetPageId(p) == pageId).FirstOrDefault(); + + RootNavView.SelectedItem = shouldSelectedItem; + } + + if (RootNavView.SelectedItem != null && RootNavView.SelectedItem is Microsoft.UI.Xaml.Controls.NavigationViewItem selectItem) + { + AppViewModel.HeaderText = selectItem.Content.ToString(); + } + } + + private void OnFixedItemClick(object sender, RoutedEventArgs e) + { + var context = (sender as FrameworkElement).DataContext as FixedItem; + HandleFixItemClicked(context); + } + + private void OnFrameLoaded(object sender, RoutedEventArgs e) + { + if (!IsFirstLoaded) + { + FireFirstLoadedEvent(); + IsFirstLoaded = true; + } + } + + private void OnDisplayModeChanged(Microsoft.UI.Xaml.Controls.NavigationView sender, Microsoft.UI.Xaml.Controls.NavigationViewDisplayModeChangedEventArgs args) + => CheckMenuButtonVisibility(); + + private void OnViewModelPropertyChanged(object sender, PropertyChangedEventArgs e) + { + if (e.PropertyName == nameof(ViewModel.IsMainViewShown)) + { + CheckMenuButtonVisibility(); + } + } + + private void CheckMenuButtonVisibility() + { + AppViewModel.IsShowMenuButton = RootNavView.DisplayMode != Microsoft.UI.Xaml.Controls.NavigationViewDisplayMode.Expanded + && ViewModel.IsMainViewShown; + } + } +} diff --git a/src/App/Controls/App/EmoteTextBlock/EmoteTextBlock.cs b/src/App/Controls/App/EmoteTextBlock/EmoteTextBlock.cs new file mode 100644 index 000000000..b68b931c1 --- /dev/null +++ b/src/App/Controls/App/EmoteTextBlock/EmoteTextBlock.cs @@ -0,0 +1,182 @@ +// Copyright (c) Richasy. All rights reserved. + +using System; +using System.Linq; +using System.Text.RegularExpressions; +using Bili.Models.Data.Appearance; +using Windows.UI.Xaml; +using Windows.UI.Xaml.Controls; +using Windows.UI.Xaml.Documents; +using Windows.UI.Xaml.Media.Imaging; + +namespace Bili.App.Controls.App +{ + /// + /// 带表情的文本. + /// + public sealed class EmoteTextBlock : Control + { + /// + /// 的依赖属性. + /// + public static readonly DependencyProperty MaxLinesProperty = + DependencyProperty.Register(nameof(MaxLines), typeof(int), typeof(EmoteTextBlock), new PropertyMetadata(4)); + + /// + /// 的依赖属性. + /// + public static readonly DependencyProperty TextProperty = + DependencyProperty.Register(nameof(Text), typeof(EmoteText), typeof(EmoteTextBlock), new PropertyMetadata(default, new PropertyChangedCallback(OnTextChanged))); + + private RichTextBlock _richBlock; + private RichTextBlock _flyoutRichBlock; + private Button _overflowButton; + private bool _isOverflowInitialized; + + /// + /// Initializes a new instance of the class. + /// + public EmoteTextBlock() => DefaultStyleKey = typeof(EmoteTextBlock); + + /// + /// 最大行数. + /// + public int MaxLines + { + get { return (int)GetValue(MaxLinesProperty); } + set { SetValue(MaxLinesProperty, value); } + } + + /// + /// 输入的文本. + /// + public EmoteText Text + { + get { return (EmoteText)GetValue(TextProperty); } + set { SetValue(TextProperty, value); } + } + + /// + /// 重置状态. + /// + public void Reset() => _richBlock?.Blocks?.Clear(); + + /// + protected override void OnApplyTemplate() + { + _richBlock = GetTemplateChild("RichBlock") as RichTextBlock; + _flyoutRichBlock = GetTemplateChild("FlyoutRichBlock") as RichTextBlock; + _overflowButton = GetTemplateChild("OverflowButton") as Button; + + _richBlock.IsTextTrimmedChanged += OnIsTextTrimmedChanged; + _overflowButton.Click += OnOverflowButtonClick; + + InitializeContent(); + base.OnApplyTemplate(); + } + + private static void OnReplyInfoChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) + { + var instance = d as EmoteTextBlock; + instance.Text = null; + if (e.NewValue != null) + { + instance.InitializeContent(); + } + } + + private static void OnTextChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) + { + var instance = d as EmoteTextBlock; + if (e.NewValue != null) + { + instance.InitializeContent(); + } + } + + private void OnIsTextTrimmedChanged(RichTextBlock sender, IsTextTrimmedChangedEventArgs args) + => _overflowButton.Visibility = sender.IsTextTrimmed ? Visibility.Visible : Visibility.Collapsed; + + private void InitializeContent() + { + _isOverflowInitialized = false; + if (_richBlock != null) + { + _richBlock.Blocks.Clear(); + Paragraph para = null; + if (Text != null) + { + para = ParseText(); + } + + if (para != null) + { + _richBlock.Blocks.Add(para); + } + } + + if (_overflowButton != null && _richBlock != null) + { + _overflowButton.Visibility = _richBlock.IsTextTrimmed ? Visibility.Visible : Visibility.Collapsed; + } + } + + private void OnOverflowButtonClick(object sender, RoutedEventArgs e) + { + if (!_isOverflowInitialized) + { + _flyoutRichBlock.Blocks.Clear(); + + if (Text != null) + { + var para = ParseText(); + _flyoutRichBlock.Blocks.Add(para); + } + } + } + + private Paragraph ParseText() + { + var text = Text.Text; + var emotes = Text.Emotes; + var para = new Paragraph(); + + if (emotes != null && emotes.Count > 0) + { + // 有表情存在,进行处理. + var emotiRegex = new Regex(@"(\[.*?\])"); + var splitCotents = emotiRegex.Split(text).Where(p => p.Length > 0).ToArray(); + foreach (var content in splitCotents) + { + if (emotiRegex.IsMatch(content)) + { + emotes.TryGetValue(content, out var emoji); + if (emoji != null) + { + var inlineCon = new InlineUIContainer(); + var img = new Windows.UI.Xaml.Controls.Image() { Width = 20, VerticalAlignment = VerticalAlignment.Center, Margin = new Thickness(2, 0, 2, -4) }; + var bitmap = new BitmapImage(new Uri(emoji.Uri)) { DecodePixelWidth = 40 }; + img.Source = bitmap; + inlineCon.Child = img; + para.Inlines.Add(inlineCon); + } + else + { + para.Inlines.Add(new Run { Text = content }); + } + } + else + { + para.Inlines.Add(new Run { Text = content }); + } + } + } + else + { + para.Inlines.Add(new Run { Text = text }); + } + + return para; + } + } +} diff --git a/src/App/Controls/App/EmoteTextBlock/EmoteTextBlock.xaml b/src/App/Controls/App/EmoteTextBlock/EmoteTextBlock.xaml new file mode 100644 index 000000000..67db1437d --- /dev/null +++ b/src/App/Controls/App/EmoteTextBlock/EmoteTextBlock.xaml @@ -0,0 +1,49 @@ + + + diff --git a/src/App/Controls/App/ErrorPanel.xaml b/src/App/Controls/App/ErrorPanel.xaml index 42e9e9fa7..3daa444b5 100644 --- a/src/App/Controls/App/ErrorPanel.xaml +++ b/src/App/Controls/App/ErrorPanel.xaml @@ -1,19 +1,20 @@  /// 错误面板,用于显示指定的错误内容. /// - public sealed partial class ErrorPanel : UserControl + public sealed partial class ErrorPanel : UserControl, IActivatableControl { /// /// 的依赖属性. @@ -27,7 +28,7 @@ public sealed partial class ErrorPanel : UserControl /// public ErrorPanel() { - this.InitializeComponent(); + InitializeComponent(); } /// @@ -35,6 +36,9 @@ public ErrorPanel() /// public event RoutedEventHandler ActionButtonClick; + /// + public event EventHandler Activated; + /// /// 错误文本. /// @@ -56,6 +60,7 @@ public string ActionContent private void OnActionButtonClick(object sender, RoutedEventArgs e) { ActionButtonClick?.Invoke(sender, e); + Activated?.Invoke(sender, EventArgs.Empty); } } } diff --git a/src/App/Controls/App/IconTextBlock.xaml b/src/App/Controls/App/IconTextBlock.xaml deleted file mode 100644 index beb1946a7..000000000 --- a/src/App/Controls/App/IconTextBlock.xaml +++ /dev/null @@ -1,38 +0,0 @@ - - - - - - - - - - - - - - - - - diff --git a/src/App/Controls/App/IconTextBlock.xaml.cs b/src/App/Controls/App/IconTextBlock.xaml.cs deleted file mode 100644 index 8268cddf3..000000000 --- a/src/App/Controls/App/IconTextBlock.xaml.cs +++ /dev/null @@ -1,82 +0,0 @@ -// Copyright (c) Richasy. All rights reserved. - -using Richasy.FluentIcon.Uwp; -using Windows.UI.Xaml; -using Windows.UI.Xaml.Controls; - -namespace Richasy.Bili.App.Controls -{ - /// - /// 带图标的文本. - /// - public sealed partial class IconTextBlock : UserControl - { - /// - /// 的依赖属性. - /// - public static readonly DependencyProperty IconFontSizeProperty = - DependencyProperty.Register(nameof(IconFontSize), typeof(double), typeof(IconTextBlock), new PropertyMetadata(14d)); - - /// - /// 的依赖属性. - /// - public static readonly DependencyProperty SpacingProperty = - DependencyProperty.Register(nameof(Spacing), typeof(double), typeof(IconTextBlock), new PropertyMetadata(8d)); - - /// - /// 的依赖属性. - /// - public static readonly DependencyProperty SymbolProperty = - DependencyProperty.Register(nameof(Symbol), typeof(RegularFluentSymbol), typeof(IconTextBlock), new PropertyMetadata(default)); - - /// - /// 的依赖属性. - /// - public static readonly DependencyProperty TextProperty = - DependencyProperty.Register(nameof(Text), typeof(string), typeof(IconTextBlock), new PropertyMetadata(string.Empty)); - - /// - /// Initializes a new instance of the class. - /// - public IconTextBlock() - { - this.InitializeComponent(); - } - - /// - /// 图标字体大小. - /// - public double IconFontSize - { - get { return (double)GetValue(IconFontSizeProperty); } - set { SetValue(IconFontSizeProperty, value); } - } - - /// - /// 图标与文本的间距. - /// - public double Spacing - { - get { return (double)GetValue(SpacingProperty); } - set { SetValue(SpacingProperty, value); } - } - - /// - /// 图标. - /// - public RegularFluentSymbol Symbol - { - get { return (RegularFluentSymbol)GetValue(SymbolProperty); } - set { SetValue(SymbolProperty, value); } - } - - /// - /// 文本. - /// - public string Text - { - get { return (string)GetValue(TextProperty); } - set { SetValue(TextProperty, value); } - } - } -} diff --git a/src/App/Controls/App/IconTextBlock/IconTextBlock.cs b/src/App/Controls/App/IconTextBlock/IconTextBlock.cs new file mode 100644 index 000000000..e41d677d7 --- /dev/null +++ b/src/App/Controls/App/IconTextBlock/IconTextBlock.cs @@ -0,0 +1,79 @@ +// Copyright (c) Richasy. All rights reserved. + +using Richasy.FluentIcon.Uwp; +using Windows.UI.Xaml; +using Windows.UI.Xaml.Controls; + +namespace Bili.App.Controls +{ + /// + /// 带图标的文本. + /// + public sealed class IconTextBlock : Control + { + /// + /// 的依赖属性. + /// + public static readonly DependencyProperty IconFontSizeProperty = + DependencyProperty.Register(nameof(IconFontSize), typeof(double), typeof(IconTextBlock), new PropertyMetadata(14d)); + + /// + /// 的依赖属性. + /// + public static readonly DependencyProperty SpacingProperty = + DependencyProperty.Register(nameof(Spacing), typeof(double), typeof(IconTextBlock), new PropertyMetadata(8d)); + + /// + /// 的依赖属性. + /// + public static readonly DependencyProperty SymbolProperty = + DependencyProperty.Register(nameof(Symbol), typeof(RegularFluentSymbol), typeof(IconTextBlock), new PropertyMetadata(default)); + + /// + /// 的依赖属性. + /// + public static readonly DependencyProperty TextProperty = + DependencyProperty.Register(nameof(Text), typeof(string), typeof(IconTextBlock), new PropertyMetadata(string.Empty)); + + /// + /// Initializes a new instance of the class. + /// + public IconTextBlock() => DefaultStyleKey = typeof(IconTextBlock); + + /// + /// 图标字体大小. + /// + public double IconFontSize + { + get { return (double)GetValue(IconFontSizeProperty); } + set { SetValue(IconFontSizeProperty, value); } + } + + /// + /// 图标与文本的间距. + /// + public double Spacing + { + get { return (double)GetValue(SpacingProperty); } + set { SetValue(SpacingProperty, value); } + } + + /// + /// 图标. + /// + public RegularFluentSymbol Symbol + { + get { return (RegularFluentSymbol)GetValue(SymbolProperty); } + set { SetValue(SymbolProperty, value); } + } + + /// + /// 文本. + /// + public string Text + { + get { return (string)GetValue(TextProperty); } + set { SetValue(TextProperty, value); } + } + } +} diff --git a/src/App/Controls/App/IconTextBlock/IconTextBlock.xaml b/src/App/Controls/App/IconTextBlock/IconTextBlock.xaml new file mode 100644 index 000000000..92d162f57 --- /dev/null +++ b/src/App/Controls/App/IconTextBlock/IconTextBlock.xaml @@ -0,0 +1,39 @@ + + + diff --git a/src/App/Controls/App/ImageViewer.xaml b/src/App/Controls/App/ImageViewer.xaml new file mode 100644 index 000000000..db0c5149a --- /dev/null +++ b/src/App/Controls/App/ImageViewer.xaml @@ -0,0 +1,248 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/App/Controls/App/ImageViewer.xaml.cs b/src/App/Controls/App/ImageViewer.xaml.cs new file mode 100644 index 000000000..54c3de304 --- /dev/null +++ b/src/App/Controls/App/ImageViewer.xaml.cs @@ -0,0 +1,361 @@ +// Copyright (c) Richasy. All rights reserved. + +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.IO; +using System.Linq; +using System.Net.Http; +using System.Threading.Tasks; +using Bili.DI.Container; +using Bili.Toolkit.Interfaces; +using Bili.ViewModels.Interfaces.Core; +using Windows.ApplicationModel.DataTransfer; +using Windows.Storage; +using Windows.Storage.Pickers; +using Windows.Storage.Streams; +using Windows.System.UserProfile; +using Windows.UI.Xaml; +using Windows.UI.Xaml.Controls; +using Windows.UI.Xaml.Input; +using Windows.UI.Xaml.Media.Imaging; + +namespace Bili.App.Controls +{ + /// + /// 图片查看器. + /// + public sealed partial class ImageViewer : UserControl + { + private readonly Dictionary _images; + private readonly ICallerViewModel _callerViewModel; + private readonly IResourceToolkit _resourceToolkit; + private int _currentIndex; + private int _currentImageHeight; + private bool _isControlShown; + + /// + /// Initializes a new instance of the class. + /// + public ImageViewer() + { + InitializeComponent(); + _images = new Dictionary(); + _callerViewModel = Locator.Instance.GetService(); + _resourceToolkit = Locator.Instance.GetService(); + Instance = this; + Images = new ObservableCollection(); + } + + /// + /// 实例. + /// + public static ImageViewer Instance { get; private set; } + + /// + /// 图片地址. + /// + public ObservableCollection Images { get; } + + /// + /// 加载图片. + /// + /// 图片列表. + /// 初始加载的图片索引. + /// . + public async Task LoadImagesAsync(IEnumerable images, int firstLoadImage = 0) + { + Container.Visibility = Visibility.Visible; + _images.Clear(); + Images.Clear(); + images.ToList().ForEach(url => Images.Add(url)); + FactoryBlock.Text = 1.ToString("p00"); + ShowControls(); + await ShowImageAsync(firstLoadImage); + } + + /// + /// 显示图片. + /// + /// 图片索引. + /// . + public async Task ShowImageAsync(int index) + { + if (index >= 0 && Images.Count > index) + { + _currentIndex = index; + if (ImageRepeater == null || !ImageRepeater.IsLoaded) + { + await Task.Delay(200); + } + + SetSelectedItem(index); + await LoadImageAsync(Images[index]); + } + } + + private async Task LoadImageAsync(Models.Data.Appearance.Image image) + { + _currentImageHeight = 0; + RotateTransform.Angle = 0; + ImageScrollViewer.ChangeView(null, null, 1f); + + if (Image.Source != null) + { + Image.Source = null; + } + + var imageUrl = image.GetSourceUri().ToString(); + var hasCache = _images.TryGetValue(imageUrl, out var imageBytes); + + if (!hasCache) + { + using var client = new HttpClient(); + imageBytes = await client.GetByteArrayAsync(imageUrl); + + // 避免重复多次请求下插入同源数据. + if (!_images.ContainsKey(imageUrl)) + { + _images.Add(imageUrl, imageBytes); + } + } + + var bitmapImage = new BitmapImage(); + Image.Source = bitmapImage; + var stream = new MemoryStream(imageBytes); + await bitmapImage.SetSourceAsync(stream.AsRandomAccessStream()); + _currentImageHeight = bitmapImage.PixelHeight; + + UpdateLayout(); + var factor = ImageScrollViewer.ViewportHeight / _currentImageHeight; + if (factor > 1) + { + factor = 1; + } + + ImageScrollViewer.ChangeView(null, null, (float)factor); + } + + private void CheckButtonStatus() + { + ZoomOutButton.IsEnabled = ImageScrollViewer.ZoomFactor > 0.2; + ZoomInButton.IsEnabled = ImageScrollViewer.ZoomFactor < 1.5; + } + + private void SetSelectedItem(int index) + { + if (Images.Count <= 1) + { + return; + } + + for (var i = 0; i < Images.Count; i++) + { + var element = ImageRepeater.GetOrCreateElement(i); + if (element is CardPanel panel) + { + var image = panel.DataContext as Models.Data.Appearance.Image; + panel.IsEnableCheck = true; + panel.IsChecked = image == Images[index]; + panel.IsEnableCheck = false; + } + } + } + + private void OnScrollViewerTapped(object sender, TappedRoutedEventArgs e) + { + if (_isControlShown) + { + HideControls(); + } + else + { + ShowControls(); + } + } + + private void OnScrollViewerViewChanged(object sender, ScrollViewerViewChangedEventArgs e) + { + if (e.IsIntermediate) + { + FactoryBlock.Text = ImageScrollViewer.ZoomFactor.ToString("p00"); + CheckButtonStatus(); + } + } + + private void OnRotateButtonClick(object sender, Windows.UI.Xaml.RoutedEventArgs e) + => RotateTransform.Angle += 90; + + private void OnZoomOutButtonClick(object sender, Windows.UI.Xaml.RoutedEventArgs e) + { + if (_currentImageHeight == 0) + { + return; + } + + ImageScrollViewer.ChangeView(null, null, ImageScrollViewer.ZoomFactor - 0.1f); + } + + private void OnZoomInButtonClick(object sender, Windows.UI.Xaml.RoutedEventArgs e) + { + if (_currentImageHeight == 0) + { + return; + } + + ImageScrollViewer.ChangeView(null, null, ImageScrollViewer.ZoomFactor + 0.1f); + } + + private async void OnImageItemClickAsync(object sender, RoutedEventArgs e) + { + var image = (sender as FrameworkElement).DataContext as Models.Data.Appearance.Image; + var index = Images.IndexOf(image); + await ShowImageAsync(index); + } + + private async void OnNextButtonClickAsync(object sender, RoutedEventArgs e) + { + if (Images.Count - 1 <= _currentIndex) + { + return; + } + + await ShowImageAsync(_currentIndex + 1); + } + + private async void OnPrevButtonClickAsync(object sender, RoutedEventArgs e) + { + if (_currentIndex <= 0) + { + return; + } + + await ShowImageAsync(_currentIndex - 1); + } + + private void OnCopyButtonClickAysnc(object sender, RoutedEventArgs e) + { + if (_currentIndex < 0 || _currentIndex > Images.Count - 1) + { + return; + } + + var image = Images[_currentIndex]; + var dp = new DataPackage(); + dp.SetBitmap(RandomAccessStreamReference.CreateFromUri(image.GetSourceUri())); + Clipboard.SetContent(dp); + _callerViewModel.ShowTip(_resourceToolkit.GetLocaleString(Models.Enums.LanguageNames.Copied), Models.Enums.App.InfoType.Success); + } + + private async void OnSaveButtonClickAsync(object sender, RoutedEventArgs e) + { + if (_currentIndex < 0 || _currentIndex > Images.Count - 1) + { + return; + } + + var image = Images[_currentIndex]; + var imageUrl = image.GetSourceUri().ToString(); + var hasCache = _images.TryGetValue(imageUrl, out var cache); + if (!hasCache) + { + return; + } + + var savePicker = new FileSavePicker(); + savePicker.SuggestedStartLocation = PickerLocationId.PicturesLibrary; + var fileName = Path.GetFileName(imageUrl); + var extension = Path.GetExtension(imageUrl); + savePicker.FileTypeChoices.Add($"{extension.TrimStart('.').ToUpper()} 图片", new string[] { extension }); + savePicker.SuggestedFileName = fileName; + var file = await savePicker.PickSaveFileAsync(); + if (file != null) + { + await FileIO.WriteBytesAsync(file, cache); + _callerViewModel.ShowTip(_resourceToolkit.GetLocaleString(Models.Enums.LanguageNames.Saved), Models.Enums.App.InfoType.Success); + } + } + + private async void OnSettingToBackgroundClickAsync(object sender, RoutedEventArgs e) + => await SetWallpaperOrLockScreenAsync(true); + + private async void OnSettingToLockScreenClickAsync(object sender, RoutedEventArgs e) + => await SetWallpaperOrLockScreenAsync(false); + + private async Task SetWallpaperOrLockScreenAsync(bool isWallpaper) + { + if (_currentIndex < 0 || _currentIndex > Images.Count - 1) + { + return; + } + + var image = Images[_currentIndex]; + var imageUrl = image.GetSourceUri().ToString(); + var hasCache = _images.TryGetValue(imageUrl, out var cache); + if (!hasCache) + { + return; + } + + var profileSettings = UserProfilePersonalizationSettings.Current; + var fileName = Path.GetFileName(imageUrl); + var file = await ApplicationData.Current.LocalFolder.CreateFileAsync(fileName, CreationCollisionOption.ReplaceExisting); + await FileIO.WriteBytesAsync(file, cache); + var result = isWallpaper + ? await profileSettings.TrySetWallpaperImageAsync(file).AsTask() + : await profileSettings.TrySetLockScreenImageAsync(file).AsTask(); + + if (result) + { + _callerViewModel.ShowTip(_resourceToolkit.GetLocaleString(Models.Enums.LanguageNames.SetSuccess), Models.Enums.App.InfoType.Success); + } + else + { + _callerViewModel.ShowTip(_resourceToolkit.GetLocaleString(Models.Enums.LanguageNames.SetFailed), Models.Enums.App.InfoType.Error); + } + + await Task.Delay(1000); + await file.DeleteAsync(); + } + + private void OnShareButtonClick(object sender, RoutedEventArgs e) + { + var dataTransferManager = DataTransferManager.GetForCurrentView(); + dataTransferManager.DataRequested += OnDataRequested; + DataTransferManager.ShowShareUI(); + } + + private void OnDataRequested(DataTransferManager sender, DataRequestedEventArgs args) + { + var data = args.Request.Data; + var image = Images[_currentIndex]; + data.Properties.Title = "分享自哔哩的图片"; + data.SetWebLink(image.GetSourceUri()); + data.SetBitmap(RandomAccessStreamReference.CreateFromUri(image.GetSourceUri())); + } + + private void OnCloseButtonClick(object sender, RoutedEventArgs e) + { + // 关闭控件. + _images.Clear(); + Images.Clear(); + _currentIndex = 0; + Image.Source = null; + Container.Visibility = Visibility.Collapsed; + _callerViewModel.ShowImages(null, -1); + } + + private void ShowControls() + { + TopContainer.Visibility = Visibility.Visible; + ImageListContainer.Visibility = Images.Count > 1 ? Visibility.Visible : Visibility.Collapsed; + _isControlShown = true; + } + + private void HideControls() + { + TopContainer.Visibility = ImageListContainer.Visibility = Visibility.Collapsed; + _isControlShown = false; + } + } +} diff --git a/src/App/Controls/App/OffsetButton/OffsetButton.cs b/src/App/Controls/App/OffsetButton/OffsetButton.cs index c904094ab..86a2760c4 100644 --- a/src/App/Controls/App/OffsetButton/OffsetButton.cs +++ b/src/App/Controls/App/OffsetButton/OffsetButton.cs @@ -3,7 +3,7 @@ using Windows.UI.Xaml; using Windows.UI.Xaml.Controls; -namespace Richasy.Bili.App.Controls +namespace Bili.App.Controls { /// /// 用于ScrollViewer中的偏移按钮. @@ -21,7 +21,7 @@ public class OffsetButton : Button /// public OffsetButton() { - this.DefaultStyleKey = typeof(OffsetButton); + DefaultStyleKey = typeof(OffsetButton); } /// diff --git a/src/App/Controls/App/OffsetButton/OffsetButton.xaml b/src/App/Controls/App/OffsetButton/OffsetButton.xaml index bcb69f899..a2bc2f82e 100644 --- a/src/App/Controls/App/OffsetButton/OffsetButton.xaml +++ b/src/App/Controls/App/OffsetButton/OffsetButton.xaml @@ -1,7 +1,7 @@  + xmlns:local="using:Bili.App.Controls"> + + diff --git a/src/App/Controls/App/QuestionPanel.xaml b/src/App/Controls/App/QuestionPanel.xaml index d6ffc472a..166e94ea7 100644 --- a/src/App/Controls/App/QuestionPanel.xaml +++ b/src/App/Controls/App/QuestionPanel.xaml @@ -1,13 +1,13 @@ - @@ -36,7 +36,7 @@ IsBackButtonVisible="Collapsed" IsPaneToggleButtonVisible="False" IsSettingsVisible="False" - MenuItemsSource="{x:Bind ViewModel.QuestionCollection, Mode=OneWay}" + MenuItemsSource="{x:Bind ViewModel.QuestionCollection}" PaneDisplayMode="Top" SelectedItem="{x:Bind ViewModel.CurrentQuestionModule, Mode=TwoWay}"> @@ -54,29 +54,20 @@ ItemsSource="{x:Bind ViewModel.CurrentQuestionModule.Questions, Mode=OneWay}"> - - - - - - - - - - - + + + + - - - - + + + + @@ -87,4 +78,4 @@ - + diff --git a/src/App/Controls/App/QuestionPanel.xaml.cs b/src/App/Controls/App/QuestionPanel.xaml.cs index 730b2ea9c..f3aa4d54a 100644 --- a/src/App/Controls/App/QuestionPanel.xaml.cs +++ b/src/App/Controls/App/QuestionPanel.xaml.cs @@ -1,46 +1,29 @@ // Copyright (c) Richasy. All rights reserved. -using Richasy.Bili.ViewModels.Uwp; -using Windows.UI.Xaml; -using Windows.UI.Xaml.Controls; +using Bili.DI.Container; +using Bili.ViewModels.Interfaces.Home; -namespace Richasy.Bili.App.Controls +namespace Bili.App.Controls { /// /// 问题面板. /// - public sealed partial class QuestionPanel : UserControl + public sealed partial class QuestionPanel : QuestionPanelBase { - /// - /// 的依赖属性. - /// - public static readonly DependencyProperty ViewModelProperty = - DependencyProperty.Register(nameof(ViewModel), typeof(HelpViewModel), typeof(QuestionPanel), new PropertyMetadata(HelpViewModel.Instance)); - /// /// Initializes a new instance of the class. /// public QuestionPanel() { - this.InitializeComponent(); - this.Loaded += OnLoadedAsync; - } - - /// - /// 视图模型. - /// - public HelpViewModel ViewModel - { - get { return (HelpViewModel)GetValue(ViewModelProperty); } - set { SetValue(ViewModelProperty, value); } + InitializeComponent(); + ViewModel = Locator.Instance.GetService(); } + } - private async void OnLoadedAsync(object sender, RoutedEventArgs e) - { - if (ViewModel.CurrentQuestionModule == null) - { - await ViewModel.InitializeQuestionsAsync(); - } - } + /// + /// 的基类. + /// + public class QuestionPanelBase : ReactiveUserControl + { } } diff --git a/src/App/Controls/App/RootNavigationView.xaml b/src/App/Controls/App/RootNavigationView.xaml deleted file mode 100644 index b043beeca..000000000 --- a/src/App/Controls/App/RootNavigationView.xaml +++ /dev/null @@ -1,175 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/src/App/Controls/App/RootNavigationView.xaml.cs b/src/App/Controls/App/RootNavigationView.xaml.cs deleted file mode 100644 index 8f3f638b9..000000000 --- a/src/App/Controls/App/RootNavigationView.xaml.cs +++ /dev/null @@ -1,245 +0,0 @@ -// Copyright (c) Richasy. All rights reserved. - -using System; -using System.ComponentModel; -using System.Linq; -using Richasy.Bili.App.Pages; -using Richasy.Bili.App.Pages.Overlay; -using Richasy.Bili.App.Resources.Extension; -using Richasy.Bili.Models.Enums; -using Richasy.Bili.ViewModels.Uwp; -using Windows.UI.Xaml; -using Windows.UI.Xaml.Controls; -using Windows.UI.Xaml.Media.Animation; - -namespace Richasy.Bili.App.Controls -{ - /// - /// Root navigation view. - /// - public sealed partial class RootNavigationView : UserControl - { - /// - /// 的依赖属性. - /// - public static readonly DependencyProperty ViewModelProperty = - DependencyProperty.Register(nameof(ViewModel), typeof(AppViewModel), typeof(RootNavigationView), new PropertyMetadata(AppViewModel.Instance)); - - /// - /// Initializes a new instance of the class. - /// - public RootNavigationView() - { - this.InitializeComponent(); - this.Loaded += this.OnLoaded; - this.Unloaded += this.OnUnloaded; - } - - /// - /// 视图模型. - /// - public AppViewModel ViewModel - { - get { return (AppViewModel)GetValue(ViewModelProperty); } - set { SetValue(ViewModelProperty, value); } - } - - private void OnUnloaded(object sender, RoutedEventArgs e) - { - ViewModel.PropertyChanged -= this.OnAppViewModelPropertyChanged; - ViewModel.RequestOverlayNavigation -= this.OnRequestOverlayNavigation; - } - - private void OnLoaded(object sender, RoutedEventArgs e) - { - ViewModel.PropertyChanged += this.OnAppViewModelPropertyChanged; - ViewModel.RequestOverlayNavigation += this.OnRequestOverlayNavigation; - CheckMainContentNavigation(); - } - - private void OnRequestOverlayNavigation(object sender, object e) - { - CheckOverlayContentNavigation(e); - } - - private void OnAppViewModelPropertyChanged(object sender, PropertyChangedEventArgs e) - { - if (e.PropertyName == nameof(ViewModel.CurrentMainContentId)) - { - CheckMainContentNavigation(); - } - else if (e.PropertyName == nameof(ViewModel.IsShowOverlay)) - { - if (!ViewModel.IsShowOverlay) - { - if (OverlayFrame.Content != null) - { - OverlayFrame.Navigate(typeof(Page)); - } - - if (MainFrame.Content != null && MainFrame.Content is IConnectedAnimationPage animatePage) - { - animatePage.TryStartConnectedAnimation(); - } - } - } - } - - private void OnRootNavViewItemInvoked(Microsoft.UI.Xaml.Controls.NavigationView sender, Microsoft.UI.Xaml.Controls.NavigationViewItemInvokedEventArgs args) - { - if (args.IsSettingsInvoked) - { - ViewModel.SetMainContentId(PageIds.Settings); - } - else - { - var pageId = NavigationExtension.GetPageId(args.InvokedItemContainer); - ViewModel.SetMainContentId(pageId); - } - } - - private void CheckMainContentNavigation() - { - var pageId = ViewModel.CurrentMainContentId; - Type pageType = null; - switch (pageId) - { - case PageIds.None: - default: - break; - case PageIds.Recommend: - pageType = typeof(RecommendPage); - break; - case PageIds.Rank: - pageType = typeof(RankPage); - break; - case PageIds.Partition: - pageType = typeof(PartitionPage); - break; - case PageIds.Popular: - pageType = typeof(PopularPage); - break; - case PageIds.SpecialColumn: - pageType = typeof(SpecialColumnPage); - break; - case PageIds.Bangumi: - pageType = typeof(BangumiPage); - break; - case PageIds.DomesticAnime: - pageType = typeof(DomesticAnimePage); - break; - case PageIds.Movie: - pageType = typeof(MoviePage); - break; - case PageIds.TV: - pageType = typeof(TVPage); - break; - case PageIds.Documentary: - pageType = typeof(DocumentaryPage); - break; - case PageIds.Live: - pageType = typeof(LivePage); - break; - case PageIds.DynamicFeed: - pageType = typeof(DynamicFeedPage); - break; - case PageIds.Help: - pageType = typeof(HelpPage); - break; - case PageIds.Settings: - pageType = typeof(SettingPage); - break; - } - - if (pageType != null) - { - MainFrame.Navigate(pageType, null, new DrillInNavigationTransitionInfo()); - } - - if (RootNavView.SelectedItem == null || - (RootNavView.SelectedItem is Microsoft.UI.Xaml.Controls.NavigationViewItem navItem && - NavigationExtension.GetPageId(navItem) != pageId)) - { - Microsoft.UI.Xaml.Controls.NavigationViewItem shouldSelectedItem = null; - if (pageId == PageIds.Settings) - { - shouldSelectedItem = RootNavView.SettingsItem as Microsoft.UI.Xaml.Controls.NavigationViewItem; - } - else - { - shouldSelectedItem = RootNavView.MenuItems.Concat(RootNavView.FooterMenuItems).OfType() - .Where(p => NavigationExtension.GetPageId(p) == pageId).FirstOrDefault(); - } - - RootNavView.SelectedItem = shouldSelectedItem; - } - - if (RootNavView.SelectedItem != null && RootNavView.SelectedItem is Microsoft.UI.Xaml.Controls.NavigationViewItem selectItem) - { - ViewModel.HeaderText = selectItem.Content.ToString(); - } - } - - private void CheckOverlayContentNavigation(object param) - { - var pageId = ViewModel.CurrentOverlayContentId; - Type pageType = null; - - switch (pageId) - { - case PageIds.PartitionDetail: - pageType = typeof(PartitionDetailPage); - break; - case PageIds.Search: - pageType = typeof(SearchPage); - break; - case PageIds.ViewHistory: - pageType = typeof(HistoryPage); - break; - case PageIds.Favorite: - pageType = typeof(FavoritePage); - break; - case PageIds.ViewLater: - pageType = typeof(ViewLaterPage); - break; - case PageIds.Fans: - pageType = typeof(FansPage); - break; - case PageIds.Follows: - pageType = typeof(FollowsPage); - break; - case PageIds.PgcIndex: - pageType = typeof(PgcIndexPage); - break; - case PageIds.TimeLine: - pageType = typeof(TimeLinePage); - break; - case PageIds.Message: - pageType = typeof(MessagePage); - break; - default: - break; - } - - if (pageType != null) - { - OverlayFrame.Navigate(pageType, param, new EntranceNavigationTransitionInfo()); - } - } - - private async void OnRefreshButtonClickAsync(object sender, RoutedEventArgs e) - { - if (MainFrame.Content is IRefreshPage page) - { - RefreshButton.IsEnabled = false; - await page.RefreshAsync(); - RefreshButton.IsEnabled = true; - } - } - - private void OnMainFrameNavigated(object sender, Windows.UI.Xaml.Navigation.NavigationEventArgs e) - { - RefreshButton.Visibility = MainFrame.Content is IRefreshPage ? Visibility.Visible : Visibility.Collapsed; - } - } -} diff --git a/src/App/Controls/App/StaggeredLayout/StaggeredColumnLayout.cs b/src/App/Controls/App/StaggeredLayout/StaggeredColumnLayout.cs index ce1314b95..6e4b88236 100644 --- a/src/App/Controls/App/StaggeredLayout/StaggeredColumnLayout.cs +++ b/src/App/Controls/App/StaggeredLayout/StaggeredColumnLayout.cs @@ -2,7 +2,7 @@ using System.Collections.Generic; -namespace Richasy.Bili.App.Controls +namespace Bili.App.Controls { [System.Diagnostics.DebuggerDisplay("Count = {Count}, Height = {Height}")] internal class StaggeredColumnLayout : List diff --git a/src/App/Controls/App/StaggeredLayout/StaggeredItem.cs b/src/App/Controls/App/StaggeredLayout/StaggeredItem.cs index 0ffb8acc5..a0f24e6bb 100644 --- a/src/App/Controls/App/StaggeredLayout/StaggeredItem.cs +++ b/src/App/Controls/App/StaggeredLayout/StaggeredItem.cs @@ -2,13 +2,13 @@ using Windows.UI.Xaml; -namespace Richasy.Bili.App.Controls +namespace Bili.App.Controls { internal class StaggeredItem { public StaggeredItem(int index) { - this.Index = index; + Index = index; } public double Top { get; internal set; } diff --git a/src/App/Controls/App/StaggeredLayout/StaggeredLayout.cs b/src/App/Controls/App/StaggeredLayout/StaggeredLayout.cs index c8c3f5759..e3deb2f4a 100644 --- a/src/App/Controls/App/StaggeredLayout/StaggeredLayout.cs +++ b/src/App/Controls/App/StaggeredLayout/StaggeredLayout.cs @@ -7,7 +7,7 @@ using Windows.Foundation; using Windows.UI.Xaml; -namespace Richasy.Bili.App.Controls +namespace Bili.App.Controls { /// /// Arranges child elements into a staggered grid pattern where items are added to the column that has used least amount of space. diff --git a/src/App/Controls/App/StaggeredLayout/StaggeredLayoutState.cs b/src/App/Controls/App/StaggeredLayout/StaggeredLayoutState.cs index 2955c8749..b5eeb140b 100644 --- a/src/App/Controls/App/StaggeredLayout/StaggeredLayoutState.cs +++ b/src/App/Controls/App/StaggeredLayout/StaggeredLayoutState.cs @@ -5,7 +5,7 @@ using System.Linq; using Microsoft.UI.Xaml.Controls; -namespace Richasy.Bili.App.Controls +namespace Bili.App.Controls { internal class StaggeredLayoutState { diff --git a/src/App/Controls/App/TipPopup.xaml b/src/App/Controls/App/TipPopup.xaml index 870a51dd9..c4ce399d3 100644 --- a/src/App/Controls/App/TipPopup.xaml +++ b/src/App/Controls/App/TipPopup.xaml @@ -1,11 +1,11 @@  /// 消息提醒. @@ -23,20 +21,14 @@ public sealed partial class TipPopup : UserControl /// /// Initializes a new instance of the class. /// - public TipPopup() - { - this.InitializeComponent(); - } + public TipPopup() => InitializeComponent(); /// /// Initializes a new instance of the class. /// /// 要显示的文本. public TipPopup(string text) - : this() - { - Text = text; - } + : this() => Text = text; /// /// 显示文本. @@ -54,7 +46,6 @@ public string Text /// 显示的时间. public async void ShowAsync(InfoType type = InfoType.Information, double displaySeconds = 2) { - ((Window.Current.Content as Frame).Content as RootPage).ShowOnHolder(this, false); switch (type) { case InfoType.Information: @@ -73,10 +64,7 @@ public async void ShowAsync(InfoType type = InfoType.Information, double display break; } - PopupContainer.Visibility = Visibility.Visible; - await Task.Delay(TimeSpan.FromSeconds(displaySeconds)); - PopupContainer.Visibility = Visibility.Collapsed; - ((Window.Current.Content as Frame).Content as RootPage).RemoveFromHolder(this); + await RootPage.Current.ShowTipAsync(this, displaySeconds); } } } diff --git a/src/App/Controls/App/TrimTextBlock.xaml b/src/App/Controls/App/TrimTextBlock.xaml index 750f6216a..178c6280d 100644 --- a/src/App/Controls/App/TrimTextBlock.xaml +++ b/src/App/Controls/App/TrimTextBlock.xaml @@ -1,10 +1,10 @@  /// 显示额外省略号的文本控件. @@ -27,7 +27,7 @@ public sealed partial class TrimTextBlock : UserControl /// public TrimTextBlock() { - this.InitializeComponent(); + InitializeComponent(); } /// diff --git a/src/App/Controls/App/TwoLineButton/TwoLineButton.cs b/src/App/Controls/App/TwoLineButton/TwoLineButton.cs index 9af168a28..97b40e392 100644 --- a/src/App/Controls/App/TwoLineButton/TwoLineButton.cs +++ b/src/App/Controls/App/TwoLineButton/TwoLineButton.cs @@ -3,7 +3,7 @@ using Windows.UI.Xaml; using Windows.UI.Xaml.Controls; -namespace Richasy.Bili.App.Controls +namespace Bili.App.Controls { /// /// 包含双行文本的按钮. @@ -27,7 +27,7 @@ public sealed class TwoLineButton : Button /// public TwoLineButton() { - this.DefaultStyleKey = typeof(TwoLineButton); + DefaultStyleKey = typeof(TwoLineButton); } /// diff --git a/src/App/Controls/App/TwoLineButton/TwoLineButton.xaml b/src/App/Controls/App/TwoLineButton/TwoLineButton.xaml index 72c806793..b3d1f0288 100644 --- a/src/App/Controls/App/TwoLineButton/TwoLineButton.xaml +++ b/src/App/Controls/App/TwoLineButton/TwoLineButton.xaml @@ -1,7 +1,7 @@  + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/App/Controls/App/XboxNavigationView.xaml.cs b/src/App/Controls/App/XboxNavigationView.xaml.cs new file mode 100644 index 000000000..5dd1cd425 --- /dev/null +++ b/src/App/Controls/App/XboxNavigationView.xaml.cs @@ -0,0 +1,152 @@ +// Copyright (c) Richasy. All rights reserved. + +using System.Linq; +using Bili.App.Controls.Base; +using Bili.App.Pages.Xbox; +using Bili.App.Resources.Extension; +using Bili.Models.App.Args; +using Bili.Models.Enums; +using Windows.UI.Xaml; +using Windows.UI.Xaml.Controls; +using Windows.UI.Xaml.Input; +using Windows.UI.Xaml.Media.Animation; + +namespace Bili.App.Controls.App +{ + /// + /// 适用于 XBOX 的导航框架. + /// + public sealed partial class XboxNavigationView : NavigationViewBase + { + /// + /// Initializes a new instance of the class. + /// + public XboxNavigationView() + : base() + { + InitializeComponent(); + Loaded += OnLoaded; + Unloaded += OnUnloaded; + } + + private void OnLoaded(object sender, RoutedEventArgs e) + => ViewModel.Navigating += OnNavigating; + + private void OnUnloaded(object sender, RoutedEventArgs e) + => ViewModel.Navigating -= OnNavigating; + + private void OnNavigating(object sender, AppNavigationEventArgs e) + { + if (e.Type == Models.Enums.App.NavigationType.Main) + { + NavigateToMainView(e.PageId); + } + } + + private void NavigateToMainView(PageIds pageId, object parameter = null) + { + var pageType = pageId switch + { + PageIds.XboxAccount => typeof(XboxAccountPage), + PageIds.PreSearch => typeof(PreSearchPage), + PageIds.Recommend => typeof(RecommendPage), + PageIds.Popular => typeof(PopularPage), + PageIds.Rank => typeof(RankPage), + PageIds.VideoPartition => typeof(VideoPartitionPage), + PageIds.Bangumi => typeof(BangumiPage), + PageIds.DomesticAnime => typeof(DomesticPage), + PageIds.Documentary => typeof(DocumentaryPage), + PageIds.Movie => typeof(MoviePage), + PageIds.TV => typeof(TvPage), + PageIds.Live => typeof(LiveFeedPage), + PageIds.Dynamic => typeof(DynamicPage), + PageIds.Settings => typeof(SettingsPage), + _ => typeof(Page), + }; + + if (pageType != null) + { + MainFrame.Navigate(pageType, parameter, new DrillInNavigationTransitionInfo()); + } + + if (RootNavView.SelectedItem == null || + (RootNavView.SelectedItem is Microsoft.UI.Xaml.Controls.NavigationViewItem navItem && + NavigationExtension.GetPageId(navItem) != pageId)) + { + var shouldSelectedItem = pageId == PageIds.Settings + ? RootNavView.SettingsItem as Microsoft.UI.Xaml.Controls.NavigationViewItem + : RootNavView.MenuItems.Concat(RootNavView.FooterMenuItems).OfType() + .Where(p => NavigationExtension.GetPageId(p) == pageId).FirstOrDefault(); + + RootNavView.SelectedItem = shouldSelectedItem; + } + + if (RootNavView.SelectedItem != null && RootNavView.SelectedItem is Microsoft.UI.Xaml.Controls.NavigationViewItem selectItem) + { + AppViewModel.HeaderText = selectItem.Content.ToString(); + } + } + + private async void OnRootNavViewItemInvokedAsync(Microsoft.UI.Xaml.Controls.NavigationView sender, Microsoft.UI.Xaml.Controls.NavigationViewItemInvokedEventArgs args) + { + if (args.IsSettingsInvoked) + { + ViewModel.NavigateToMainView(PageIds.Settings); + } + else + { + if (args.InvokedItemContainer == SignInItem) + { + await AccountViewModel.TrySignInCommand.ExecuteAsync(false); + if (AccountViewModel.State == AuthorizeState.SignedIn) + { + ViewModel.Navigate(PageIds.XboxAccount); + } + + return; + } + + var pageId = NavigationExtension.GetPageId(args.InvokedItemContainer); + + if (pageId != PageIds.None) + { + ViewModel.Navigate(pageId); + } + } + } + + private void OnFrameLoaded(object sender, RoutedEventArgs e) + { + if (!IsFirstLoaded) + { + FireFirstLoadedEvent(); + IsFirstLoaded = true; + } + } + + private void OnRootNavViewNoFocusCandidateFound(UIElement sender, NoFocusCandidateFoundEventArgs args) + { + if (args.Direction == FocusNavigationDirection.Left) + { + if (args.InputDevice == FocusInputDeviceKind.Keyboard || + args.InputDevice == FocusInputDeviceKind.GameController) + { + RootNavView.PaneDisplayMode = Microsoft.UI.Xaml.Controls.NavigationViewPaneDisplayMode.Left; + } + + args.Handled = true; + } + } + + private void OnRootNavViewLosingFocus(UIElement sender, LosingFocusEventArgs args) + { + if (RootNavView.PaneDisplayMode == Microsoft.UI.Xaml.Controls.NavigationViewPaneDisplayMode.Left + && args.NewFocusedElement is not Microsoft.UI.Xaml.Controls.NavigationViewItem) + { + RootNavView.PaneDisplayMode = Microsoft.UI.Xaml.Controls.NavigationViewPaneDisplayMode.LeftCompact; + } + + args.Handled = true; + } + } +} diff --git a/src/App/Controls/Article/ArticleItem/ArticleItem.cs b/src/App/Controls/Article/ArticleItem/ArticleItem.cs new file mode 100644 index 000000000..559c450d1 --- /dev/null +++ b/src/App/Controls/Article/ArticleItem/ArticleItem.cs @@ -0,0 +1,34 @@ +// Copyright (c) Richasy. All rights reserved. + +using Bili.DI.Container; +using Bili.Toolkit.Interfaces; +using Bili.ViewModels.Interfaces.Article; +using Windows.Foundation; +using Windows.UI.Xaml; +using Windows.UI.Xaml.Controls; + +namespace Bili.App.Controls.Article +{ + /// + /// 文章条目. + /// + public sealed class ArticleItem : ReactiveControl, IRepeaterItem, IOrientationControl + { + /// + /// Initializes a new instance of the class. + /// + public ArticleItem() => DefaultStyleKey = typeof(ArticleItem); + + /// + public Size GetHolderSize() => new(210, 248); + + /// + public void ChangeLayout(Orientation orientation) + { + var resourceToolkit = Locator.Instance.GetService(); + Style = orientation == Orientation.Horizontal + ? resourceToolkit.GetResource + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/src/App/Controls/Common/ArticleItem.xaml.cs b/src/App/Controls/Common/ArticleItem.xaml.cs deleted file mode 100644 index 9d86ce072..000000000 --- a/src/App/Controls/Common/ArticleItem.xaml.cs +++ /dev/null @@ -1,125 +0,0 @@ -// Copyright (c) Richasy. All rights reserved. - -using Richasy.Bili.ViewModels.Uwp; -using Windows.Foundation; -using Windows.UI.Xaml; -using Windows.UI.Xaml.Controls; - -namespace Richasy.Bili.App.Controls -{ - /// - /// 文章条目. - /// - public sealed partial class ArticleItem : UserControl, IRepeaterItem, IDynamicLayoutItem - { - /// - /// 的依赖属性. - /// - public static readonly DependencyProperty ViewModelProperty = - DependencyProperty.Register(nameof(ViewModel), typeof(ArticleViewModel), typeof(ArticleItem), new PropertyMetadata(null)); - - /// - /// 的依赖属性. - /// - public static readonly DependencyProperty OrientationProperty = - DependencyProperty.Register(nameof(Orientation), typeof(Orientation), typeof(ArticleItem), new PropertyMetadata(default(Orientation), new PropertyChangedCallback(OnOrientationChanged))); - - /// - /// 的依赖属性. - /// - public static readonly DependencyProperty CardPanelStyleProperty = - DependencyProperty.Register(nameof(CardPanelStyle), typeof(Style), typeof(ArticleItem), new PropertyMetadata(null)); - - /// - /// 的依赖属性. - /// - public static readonly DependencyProperty DescriptionVisibilityProperty = - DependencyProperty.Register(nameof(DescriptionVisibility), typeof(Visibility), typeof(ArticleItem), new PropertyMetadata(Visibility.Visible)); - - /// - /// Initializes a new instance of the class. - /// - public ArticleItem() - { - this.InitializeComponent(); - this.Loaded += OnLoaded; - } - - /// - /// 视图模型. - /// - public ArticleViewModel ViewModel - { - get { return (ArticleViewModel)GetValue(ViewModelProperty); } - set { SetValue(ViewModelProperty, value); } - } - - /// - /// 文章的布局方式. - /// - public Orientation Orientation - { - get { return (Orientation)GetValue(OrientationProperty); } - set { SetValue(OrientationProperty, value); } - } - - /// - /// 内部容器的样式. - /// - public Style CardPanelStyle - { - get { return (Style)GetValue(CardPanelStyleProperty); } - set { SetValue(CardPanelStyleProperty, value); } - } - - /// - /// 描述的可见性. - /// - public Visibility DescriptionVisibility - { - get { return (Visibility)GetValue(DescriptionVisibilityProperty); } - set { SetValue(DescriptionVisibilityProperty, value); } - } - - /// - public Size GetHolderSize() - { - if (Orientation == Orientation.Horizontal) - { - return new Size(double.PositiveInfinity, 180); - } - else - { - return new Size(210, 248); - } - } - - private static void OnOrientationChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) - { - var instance = d as ArticleItem; - instance.CheckOrientation(); - } - - private async void OnContainerClickAsync(object sender, RoutedEventArgs e) - { - await ReaderView.Instance.ShowAsync(ViewModel); - } - - private void OnLoaded(object sender, RoutedEventArgs e) => CheckOrientation(); - - private void CheckOrientation() - { - switch (Orientation) - { - case Orientation.Vertical: - VisualStateManager.GoToState(this, nameof(VerticalState), false); - break; - case Orientation.Horizontal: - VisualStateManager.GoToState(this, nameof(HorizontalState), false); - break; - default: - break; - } - } - } -} diff --git a/src/App/Controls/Common/HorizontalRepeaterView.xaml b/src/App/Controls/Common/HorizontalRepeaterView.xaml index 3069d493b..817e37a32 100644 --- a/src/App/Controls/Common/HorizontalRepeaterView.xaml +++ b/src/App/Controls/Common/HorizontalRepeaterView.xaml @@ -1,17 +1,18 @@  - + @@ -48,6 +49,7 @@ x:Name="WideScrollViewer" HorizontalScrollBarVisibility="Hidden" HorizontalScrollMode="Enabled" + IsTabStop="False" VerticalScrollBarVisibility="Hidden" VerticalScrollMode="Disabled" ViewChanged="OnWideScrollViewerChanged"> @@ -55,8 +57,7 @@ x:Name="BannerRepeater" Margin="0,4" ItemTemplate="{x:Bind WideItemTemplate, Mode=OneWay}" - ItemsSource="{x:Bind ItemsSource, Mode=OneWay}" - TabFocusNavigation="Local"> + ItemsSource="{x:Bind ItemsSource, Mode=OneWay}"> diff --git a/src/App/Controls/Common/HorizontalRepeaterView.xaml.cs b/src/App/Controls/Common/HorizontalRepeaterView.xaml.cs index c161f1c2a..b8b082645 100644 --- a/src/App/Controls/Common/HorizontalRepeaterView.xaml.cs +++ b/src/App/Controls/Common/HorizontalRepeaterView.xaml.cs @@ -1,11 +1,12 @@ // Copyright (c) Richasy. All rights reserved. -using Richasy.Bili.ViewModels.Uwp; +using Bili.DI.Container; +using Bili.Toolkit.Interfaces; using Windows.UI.Xaml; using Windows.UI.Xaml.Controls; using Windows.UI.Xaml.Input; -namespace Richasy.Bili.App.Controls +namespace Bili.App.Controls { /// /// 水平列表视图. @@ -54,14 +55,18 @@ public sealed partial class HorizontalRepeaterView : UserControl public static readonly DependencyProperty AdditionalContentProperty = DependencyProperty.Register(nameof(AdditionalContent), typeof(object), typeof(HorizontalRepeaterView), new PropertyMetadata(null)); + private const string MediumWindowWidthKey = "MediumWindowThresholdWidth"; + private readonly IResourceToolkit _resourceToolkit; + /// /// Initializes a new instance of the class. /// public HorizontalRepeaterView() { - this.InitializeComponent(); - this.SizeChanged += OnSizeChanged; - this.Loaded += OnLoaded; + InitializeComponent(); + _resourceToolkit = Locator.Instance.GetService(); + SizeChanged += OnSizeChanged; + Loaded += OnLoaded; } /// @@ -158,7 +163,8 @@ private void CheckOffsetButtonStatus() private void CheckLayout() { var windowWidth = Window.Current.Bounds.Width; - if (windowWidth < AppViewModel.Instance.NarrowWindowThresholdWidth && NarrowItemTemplate != null) + + if (windowWidth < _resourceToolkit.GetResource(MediumWindowWidthKey) && NarrowItemTemplate != null) { WideContainer.Visibility = Visibility.Collapsed; NarrowContainer.Visibility = Visibility.Visible; diff --git a/src/App/Controls/Common/PartitionItem.xaml b/src/App/Controls/Common/PartitionItem.xaml deleted file mode 100644 index 06697b20c..000000000 --- a/src/App/Controls/Common/PartitionItem.xaml +++ /dev/null @@ -1,41 +0,0 @@ - - - - - - - - - - - - - - - - - diff --git a/src/App/Controls/Common/PartitionItem.xaml.cs b/src/App/Controls/Common/PartitionItem.xaml.cs deleted file mode 100644 index d308033d9..000000000 --- a/src/App/Controls/Common/PartitionItem.xaml.cs +++ /dev/null @@ -1,62 +0,0 @@ -// Copyright (c) Richasy. All rights reserved. - -using System; -using Richasy.Bili.ViewModels.Uwp; -using Windows.UI.Xaml; -using Windows.UI.Xaml.Controls; -using Windows.UI.Xaml.Media.Animation; -using Windows.UI.Xaml.Media.Imaging; - -namespace Richasy.Bili.App.Controls -{ - /// - /// 分区条目. - /// - public sealed partial class PartitionItem : UserControl - { - /// - /// 的依赖属性. - /// - public static readonly DependencyProperty DataProperty = - DependencyProperty.Register(nameof(Data), typeof(PartitionViewModel), typeof(PartitionItem), new PropertyMetadata(null, new PropertyChangedCallback(OnDataChanged))); - - /// - /// Initializes a new instance of the class. - /// - public PartitionItem() - { - this.InitializeComponent(); - } - - /// - /// 在条目被点击时发生. - /// - public event EventHandler ItemClick; - - /// - /// 分区数据. - /// - public PartitionViewModel Data - { - get { return (PartitionViewModel)GetValue(DataProperty); } - set { SetValue(DataProperty, value); } - } - - private static void OnDataChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) - { - var instance = d as PartitionItem; - if (e.NewValue != null && e.NewValue is PartitionViewModel data) - { - instance.PartitionLogo.Source = new BitmapImage(new Uri(data.ImageUrl)); - instance.PartitionName.Text = data.Title; - } - } - - private void OnItemClick(object sender, RoutedEventArgs e) - { - var animationService = ConnectedAnimationService.GetForCurrentView(); - animationService.PrepareToAnimate("PartitionAnimate", this.ContentContainer); - ItemClick?.Invoke(this, Data); - } - } -} diff --git a/src/App/Controls/Common/ReaderView.xaml b/src/App/Controls/Common/ReaderView.xaml deleted file mode 100644 index ae14fa588..000000000 --- a/src/App/Controls/Common/ReaderView.xaml +++ /dev/null @@ -1,30 +0,0 @@ - - - - - - - - diff --git a/src/App/Controls/Common/ReaderView.xaml.cs b/src/App/Controls/Common/ReaderView.xaml.cs deleted file mode 100644 index c97d95e3a..000000000 --- a/src/App/Controls/Common/ReaderView.xaml.cs +++ /dev/null @@ -1,115 +0,0 @@ -// Copyright (c) Richasy. All rights reserved. - -using System; -using System.Threading.Tasks; -using Newtonsoft.Json; -using Richasy.Bili.Locator.Uwp; -using Richasy.Bili.Models.App.Constants; -using Richasy.Bili.Models.App.Other; -using Richasy.Bili.Toolkit.Interfaces; -using Richasy.Bili.ViewModels.Uwp; -using Windows.System; -using Windows.UI.Xaml; - -namespace Richasy.Bili.App.Controls -{ - /// - /// 阅读器视图. - /// - public partial class ReaderView : CenterPopup - { - /// - /// 的依赖属性. - /// - public static readonly DependencyProperty ViewModelProperty = - DependencyProperty.Register(nameof(ViewModel), typeof(ArticleViewModel), typeof(ReaderView), new PropertyMetadata(null)); - - private bool _isPreLoaded; - - /// - /// Initializes a new instance of the class. - /// - protected ReaderView() - { - this.InitializeComponent(); - } - - /// - /// 实例. - /// - public static ReaderView Instance { get; } = new Lazy(() => new ReaderView()).Value; - - /// - /// 视图模型. - /// - public ArticleViewModel ViewModel - { - get { return (ArticleViewModel)GetValue(ViewModelProperty); } - set { SetValue(ViewModelProperty, value); } - } - - /// - /// 显示阅读器. - /// - /// 文章视图模型. - /// . - public async Task ShowAsync(ArticleViewModel vm) - { - Show(); - ViewModel = vm; - Title = vm.Title; - if (!_isPreLoaded) - { - ReaderWebView.NavigateToString(string.Empty); - _isPreLoaded = true; - } - - if (string.IsNullOrEmpty(vm.ArticleContent)) - { - await ViewModel.InitializeArticleContentAsync(); - await LoadContentAsync(); - } - else - { - await LoadContentAsync(); - } - } - - private async Task LoadContentAsync() - { - if (ViewModel != null && !string.IsNullOrEmpty(ViewModel.ArticleContent)) - { - var fileToolkit = ServiceLocator.Instance.GetService(); - var content = ViewModel.ArticleContent.Replace("=\"//", "=\"http://") - .Replace("data-src", "src"); - var readerContainerStr = await fileToolkit.ReadPackageFile("ms-appx:///Resources/Html/ReaderPage.html"); - var theme = App.Current.RequestedTheme.ToString(); - var css = await fileToolkit.ReadPackageFile($"ms-appx:///Resources/Html/{theme}.css"); - - var html = readerContainerStr.Replace("$theme$", theme.ToLower()) - .Replace("$style$", css) - .Replace("$body$", content) - .Replace("$noscroll$", "style=\"-ms-overflow-style: none;\"") - .Replace("$return$", string.Empty); - ReaderWebView.NavigateToString(html); - } - } - - private async void OnArticleRefreshButtonClickAsync(object sender, RoutedEventArgs e) - { - await ViewModel.InitializeArticleContentAsync(); - await LoadContentAsync(); - } - - private void OnClosed(object sender, EventArgs e) => ReaderWebView.NavigateToString(string.Empty); - - private async void OnScriptNotifyAsync(object sender, Windows.UI.Xaml.Controls.NotifyEventArgs e) - { - var data = JsonConvert.DeserializeObject>(e.Value); - if (data.Key == AppConstants.LinkClickEvent) - { - await Launcher.LaunchUriAsync(new Uri(data.Value)); - } - } - } -} diff --git a/src/App/Controls/Common/ReplyDetailView.xaml b/src/App/Controls/Common/ReplyDetailView.xaml deleted file mode 100644 index afdbfccce..000000000 --- a/src/App/Controls/Common/ReplyDetailView.xaml +++ /dev/null @@ -1,18 +0,0 @@ - - - - - - diff --git a/src/App/Controls/Common/ReplyDetailView.xaml.cs b/src/App/Controls/Common/ReplyDetailView.xaml.cs deleted file mode 100644 index 0bc16d005..000000000 --- a/src/App/Controls/Common/ReplyDetailView.xaml.cs +++ /dev/null @@ -1,37 +0,0 @@ -// Copyright (c) Richasy. All rights reserved. - -using System; -using System.Threading.Tasks; - -namespace Richasy.Bili.App.Controls -{ - /// - /// 评论回复详情. - /// - public partial class ReplyDetailView : CenterPopup - { - /// - /// Initializes a new instance of the class. - /// - protected ReplyDetailView() - { - this.InitializeComponent(); - } - - /// - /// 单例. - /// - public static ReplyDetailView Instance { get; } = new Lazy(() => new ReplyDetailView()).Value; - - /// - /// 显示. - /// - /// 数据. - /// . - public async Task ShowAsync(object data) - { - Container.Show(); - await ModuleView.InitializeAsync(data); - } - } -} diff --git a/src/App/Controls/Common/ReplyItem.xaml b/src/App/Controls/Common/ReplyItem.xaml deleted file mode 100644 index b82fb787d..000000000 --- a/src/App/Controls/Common/ReplyItem.xaml +++ /dev/null @@ -1,109 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/src/App/Controls/Common/ReplyItem.xaml.cs b/src/App/Controls/Common/ReplyItem.xaml.cs deleted file mode 100644 index 2b4b1f3ec..000000000 --- a/src/App/Controls/Common/ReplyItem.xaml.cs +++ /dev/null @@ -1,129 +0,0 @@ -// Copyright (c) Richasy. All rights reserved. - -using System; -using Bilibili.Main.Community.Reply.V1; -using Richasy.Bili.Locator.Uwp; -using Richasy.Bili.Toolkit.Interfaces; -using Richasy.Bili.ViewModels.Uwp; -using Windows.Foundation; -using Windows.UI.Xaml; -using Windows.UI.Xaml.Controls; -using Windows.UI.Xaml.Media.Imaging; - -namespace Richasy.Bili.App.Controls -{ - /// - /// 评论条目. - /// - public sealed partial class ReplyItem : UserControl, IRepeaterItem, IDynamicLayoutItem - { - /// - /// 的依赖属性. - /// - public static readonly DependencyProperty DataProperty = - DependencyProperty.Register(nameof(Data), typeof(ReplyInfo), typeof(ReplyItem), new PropertyMetadata(null, new PropertyChangedCallback(OnDataChanged))); - - /// - /// 的依赖属性. - /// - public static readonly DependencyProperty DetailCountVisibilityProperty = - DependencyProperty.Register(nameof(DetailCountVisibility), typeof(Visibility), typeof(ReplyItem), new PropertyMetadata(Visibility.Visible)); - - /// - /// Initializes a new instance of the class. - /// - public ReplyItem() - { - this.InitializeComponent(); - Orientation = Orientation.Horizontal; - } - - public event EventHandler MoreButtonClick; - - /// - /// 条目被点击时发生. - /// - public event EventHandler Click; - - /// - /// 数据源. - /// - public ReplyInfo Data - { - get { return (ReplyInfo)GetValue(DataProperty); } - set { SetValue(DataProperty, value); } - } - - /// - /// 子评论数目显示或隐藏. - /// - public Visibility DetailCountVisibility - { - get { return (Visibility)GetValue(DetailCountVisibilityProperty); } - set { SetValue(DetailCountVisibilityProperty, value); } - } - - /// - /// 布局方式(附加项). - /// - public Orientation Orientation { get; set; } - - /// - public Size GetHolderSize() - { - return new Size(350, 280); - } - - private static void OnDataChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) - { - if (e.NewValue is ReplyInfo data) - { - var instance = d as ReplyItem; - var isTop = data.ReplyControl.IsAdminTop || data.ReplyControl.IsUpTop; - instance.TopContainer.Visibility = isTop ? Visibility.Visible : Visibility.Collapsed; - instance.UserAvatar.UserName = instance.UserNameBlock.Text = data.Member.Name; - instance.UserAvatar.Avatar = data.Member.Face; - instance.LevelImage.Source = new BitmapImage(new Uri($"ms-appx:///Assets/Level/level_{data.Member.Level}.png")); - instance.ReplyContentBlock.Text = data.Content.Message; - var time = DateTimeOffset.FromUnixTimeSeconds(data.Ctime).ToLocalTime(); - instance.PublishTimeBlock.Text = time.ToString("HH:mm"); - ToolTipService.SetToolTip(instance.PublishTimeBlock, time.ToString("yyyy/MM/dd HH:mm:ss")); - instance.LikeButton.IsChecked = data.ReplyControl.Action == 1; - instance.LikeCountBlock.Text = ServiceLocator.Instance.GetService().GetCountText(data.Like); - instance.MoreButton.Visibility = data.Count > 0 ? Visibility.Visible : Visibility.Collapsed; - instance.MoreBlock.Text = string.Format(ServiceLocator.Instance.GetService().GetLocaleString(Models.Enums.LanguageNames.MoreReplyDisplay), data.Count); - } - } - - private void OnMoreButtonClick(object sender, RoutedEventArgs e) - { - MoreButtonClick?.Invoke(this, Data); - } - - private async void OnLikeButtonClickAsync(object sender, RoutedEventArgs e) - { - var isLike = !(Data.ReplyControl.Action == 1); - this.Focus(FocusState.Programmatic); - LikeButton.IsEnabled = false; - var result = await ReplyModuleViewModel.Instance.LikeReplyAysnc(isLike, Data.Id); - LikeButton.IsEnabled = true; - if (result) - { - LikeButton.IsChecked = isLike; - Data.ReplyControl.Action = isLike ? 1 : 0; - Data.Like = isLike ? Data.Like + 1 : Data.Like - 1; - } - else - { - LikeButton.IsChecked = Data.ReplyControl.Action == 1; - } - - LikeCountBlock.Text = ServiceLocator.Instance.GetService().GetCountText(Data.Like); - } - - private void OnCardClick(object sender, RoutedEventArgs e) - { - Click?.Invoke(this, EventArgs.Empty); - } - } -} diff --git a/src/App/Controls/Common/ReplyModuleView.xaml b/src/App/Controls/Common/ReplyModuleView.xaml deleted file mode 100644 index 2e9be52a9..000000000 --- a/src/App/Controls/Common/ReplyModuleView.xaml +++ /dev/null @@ -1,42 +0,0 @@ - - - - - - - - - - - - - - - - - - diff --git a/src/App/Controls/Common/ReplyModuleView.xaml.cs b/src/App/Controls/Common/ReplyModuleView.xaml.cs deleted file mode 100644 index 2d26ac963..000000000 --- a/src/App/Controls/Common/ReplyModuleView.xaml.cs +++ /dev/null @@ -1,79 +0,0 @@ -// Copyright (c) Richasy. All rights reserved. - -using System.Threading.Tasks; -using Bilibili.Main.Community.Reply.V1; -using Richasy.Bili.ViewModels.Uwp; -using Windows.UI.Xaml; -using Windows.UI.Xaml.Controls; - -namespace Richasy.Bili.App.Controls -{ - /// - /// 回复模块视图. - /// - public sealed partial class ReplyModuleView : UserControl - { - /// - /// 的依赖属性. - /// - public static readonly DependencyProperty CanShowBackButtonProperty = - DependencyProperty.Register(nameof(CanShowBackButton), typeof(bool), typeof(ReplyModuleView), new PropertyMetadata(false)); - - /// - /// Initializes a new instance of the class. - /// - public ReplyModuleView() - { - this.InitializeComponent(); - MainView.ViewModel = ReplyModuleViewModel.Instance; - DetailView.ViewModel = ReplyDetailViewModel.Instance; - } - - /// - /// 是否可以显示返回主列表的按钮. - /// - public bool CanShowBackButton - { - get { return (bool)GetValue(CanShowBackButtonProperty); } - set { SetValue(CanShowBackButtonProperty, value); } - } - - /// - /// 初始化. - /// - /// 数据. - /// . - public async Task InitializeAsync(object item) - { - if (item is ReplyModuleViewModel) - { - MainContainer.Visibility = Visibility.Visible; - DetailContainer.Visibility = Visibility.Collapsed; - CanShowBackButton = true; - await MainView.CheckInitializeAsync(); - } - else if (item is ReplyInfo info) - { - MainContainer.Visibility = Visibility.Collapsed; - DetailContainer.Visibility = Visibility.Visible; - CanShowBackButton = false; - await DetailView.CheckInitializeAsync(); - } - } - - private void OnBackButtonClick(object sender, RoutedEventArgs e) - { - DetailContainer.Visibility = Visibility.Collapsed; - MainContainer.Visibility = Visibility.Visible; - } - - private async void OnRequestDetailViewAsync(object sender, ReplyInfo e) - { - MainContainer.Visibility = Visibility.Collapsed; - DetailContainer.Visibility = Visibility.Visible; - RootReplyItem.Data = e; - ReplyDetailViewModel.Instance.SetRootReply(e); - await DetailView.CheckInitializeAsync(); - } - } -} diff --git a/src/App/Controls/Common/ReplyView.xaml b/src/App/Controls/Common/ReplyView.xaml deleted file mode 100644 index b087dfaad..000000000 --- a/src/App/Controls/Common/ReplyView.xaml +++ /dev/null @@ -1,111 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/src/App/Controls/Common/ReplyView.xaml.cs b/src/App/Controls/Common/ReplyView.xaml.cs deleted file mode 100644 index e2fe0fb76..000000000 --- a/src/App/Controls/Common/ReplyView.xaml.cs +++ /dev/null @@ -1,200 +0,0 @@ -// Copyright (c) Richasy. All rights reserved. - -using System; -using System.Threading.Tasks; -using Bilibili.Main.Community.Reply.V1; -using Richasy.Bili.Locator.Uwp; -using Richasy.Bili.Toolkit.Interfaces; -using Richasy.Bili.ViewModels.Uwp; -using Windows.UI.Xaml; -using Windows.UI.Xaml.Controls; - -namespace Richasy.Bili.App.Controls -{ - /// - /// 评论回复视图. - /// - public sealed partial class ReplyView : UserControl - { - /// - /// 的依赖属性. - /// - public static readonly DependencyProperty ViewModelProperty = - DependencyProperty.Register(nameof(ViewModel), typeof(ReplyViewModelBase), typeof(ReplyView), new PropertyMetadata(null)); - - /// - /// 的依赖属性. - /// - public static readonly DependencyProperty HeaderVisibilityProperty = - DependencyProperty.Register(nameof(HeaderVisibility), typeof(Visibility), typeof(ReplyView), new PropertyMetadata(Visibility.Visible)); - - private ReplyInfo _selectData; - - /// - /// Initializes a new instance of the class. - /// - public ReplyView() - { - this.InitializeComponent(); - } - - public event EventHandler RequestDetailView; - - /// - /// 依赖属性. - /// - public ReplyViewModelBase ViewModel - { - get { return (ReplyViewModelBase)GetValue(ViewModelProperty); } - set { SetValue(ViewModelProperty, value); } - } - - /// - /// 头部可见性. - /// - public Visibility HeaderVisibility - { - get { return (Visibility)GetValue(HeaderVisibilityProperty); } - set { SetValue(HeaderVisibilityProperty, value); } - } - - /// - /// 检查评论初始化. - /// - /// . - public async Task CheckInitializeAsync() - { - if (ViewModel == null) - { - return; - } - - OrderTypeComboBox.SelectedIndex = ViewModel.CurrentMode == Mode.MainListHot ? 0 : 1; - if (!ViewModel.IsRequested) - { - await ViewModel.InitializeRequestAsync(); - ContentScrollViewer.ChangeView(0, 0, 1); - } - } - - private async void OnReplyRequestLoadMoreAsync(object sender, System.EventArgs e) - { - await ViewModel.RequestDataAsync(); - } - - private async void OnOrderTypeSelectionChangedAsync(object sender, SelectionChangedEventArgs e) - { - if (ViewModel.IsRequested) - { - var item = OrderTypeComboBox.SelectedItem; - Mode mode; - if (item == HotItem) - { - mode = Mode.MainListHot; - } - else - { - mode = Mode.MainListTime; - } - - if (ViewModel.CurrentMode != mode) - { - ViewModel.CurrentMode = mode; - await ViewModel.InitializeRequestAsync(); - } - } - } - - private async void OnReplyRefreshButtonClickAsync(object sender, RoutedEventArgs e) - { - await ViewModel.InitializeRequestAsync(); - } - - private void OnReplyItemClick(object sender, System.EventArgs e) - { - _selectData = (sender as ReplyItem).Data; - ReplyBox.Focus(FocusState.Programmatic); - CheckReplyPlaceholder(); - } - - private void OnReplyBoxLostFocus(object sender, RoutedEventArgs e) - { - if (string.IsNullOrEmpty(ReplyBox.Text)) - { - _selectData = null; - CheckReplyPlaceholder(); - } - } - - private async void OnSendReplyButtonClickAsync(object sender, RoutedEventArgs e) - { - await SendReplyAsync(); - } - - private async Task SendReplyAsync() - { - var text = ReplyBox.Text?.Trim(); - if (string.IsNullOrEmpty(text)) - { - return; - } - - long rootId = 0; - long parentId = 0; - var resourceToolkit = ServiceLocator.Instance.GetService(); - if (_selectData != null) - { - rootId = _selectData.Root; - parentId = _selectData.Id; - text = string.Format(resourceToolkit.GetLocaleString(Models.Enums.LanguageNames.ReplySomeone), _selectData.Member.Name) + ":" + text; - } - else if (ViewModel is ReplyDetailViewModel detailVM) - { - rootId = detailVM.RootReply.Id; - parentId = detailVM.RootReply.Id; - } - - var result = await ReplyModuleViewModel.Instance.AddReplyAsync(text, rootId, parentId); - if (result) - { - ReplyBox.Text = string.Empty; - _selectData = null; - CheckReplyPlaceholder(); - - if (ViewModel is ReplyDetailViewModel) - { - await ViewModel.InitializeRequestAsync(); - } - } - } - - private void CheckReplyPlaceholder() - { - var resourceToolkit = ServiceLocator.Instance.GetService(); - var text = string.Empty; - if (_selectData != null) - { - text = string.Format(resourceToolkit.GetLocaleString(Models.Enums.LanguageNames.ReplySomeone), _selectData.Member.Name); - } - else - { - text = resourceToolkit.GetLocaleString(Models.Enums.LanguageNames.ReplyPlaceholderText); - } - - ReplyBox.PlaceholderText = text; - } - - private async void OnReplyBoxKeyDownAsync(object sender, Windows.UI.Xaml.Input.KeyRoutedEventArgs e) - { - if (e.Key == Windows.System.VirtualKey.Enter) - { - await SendReplyAsync(); - } - } - - private void OnMoreButtonClick(object sender, ReplyInfo e) - { - RequestDetailView?.Invoke(this, e); - } - } -} diff --git a/src/App/Controls/Common/VerticalRepeaterView.xaml b/src/App/Controls/Common/VerticalRepeaterView.xaml deleted file mode 100644 index 8005116c1..000000000 --- a/src/App/Controls/Common/VerticalRepeaterView.xaml +++ /dev/null @@ -1,71 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/src/App/Controls/Common/VerticalRepeaterView.xaml.cs b/src/App/Controls/Common/VerticalRepeaterView.xaml.cs deleted file mode 100644 index 8a003fdc2..000000000 --- a/src/App/Controls/Common/VerticalRepeaterView.xaml.cs +++ /dev/null @@ -1,329 +0,0 @@ -// Copyright (c) Richasy. All rights reserved. - -using System; -using System.Collections; -using Microsoft.UI.Xaml.Controls; -using Richasy.Bili.App.Resources.Extension; -using Windows.UI.Xaml; -using Windows.UI.Xaml.Controls; - -namespace Richasy.Bili.App.Controls -{ - /// - /// 视频视图. - /// - public sealed partial class VerticalRepeaterView : UserControl - { - /// - /// 的依赖属性. - /// - public static readonly DependencyProperty ItemsSourceProperty = - DependencyProperty.Register(nameof(ItemsSource), typeof(object), typeof(VerticalRepeaterView), new PropertyMetadata(null)); - - /// - /// 的依赖属性. - /// - public static readonly DependencyProperty ItemOrientationProperty = - DependencyProperty.Register(nameof(ItemOrientation), typeof(Orientation), typeof(VerticalRepeaterView), new PropertyMetadata(default(Orientation), new PropertyChangedCallback(OnOrientationChanged))); - - /// - /// 的依赖属性. - /// - public static readonly DependencyProperty HeaderTextProperty = - DependencyProperty.Register(nameof(HeaderText), typeof(string), typeof(VerticalRepeaterView), new PropertyMetadata(string.Empty)); - - /// - /// 的依赖属性. - /// - public static readonly DependencyProperty ItemTemplateProperty = - DependencyProperty.Register(nameof(ItemTemplate), typeof(DataTemplate), typeof(VerticalRepeaterView), new PropertyMetadata(null)); - - /// - /// 的依赖属性. - /// - public static readonly DependencyProperty AdditionalContentProperty = - DependencyProperty.Register(nameof(AdditionalContent), typeof(object), typeof(VerticalRepeaterView), new PropertyMetadata(null)); - - /// - /// 的依赖属性. - /// - public static readonly DependencyProperty IsAutoFillEnableProperty = - DependencyProperty.Register(nameof(IsAutoFillEnable), typeof(bool), typeof(VerticalRepeaterView), new PropertyMetadata(true)); - - /// - /// 的依赖属性. - /// - public static readonly DependencyProperty HeaderVisibilityProperty = - DependencyProperty.Register(nameof(HeaderVisibility), typeof(Visibility), typeof(VerticalRepeaterView), new PropertyMetadata(Visibility.Visible)); - - /// - /// 的依赖属性. - /// - public static readonly DependencyProperty MinWideItemHeightProperty = - DependencyProperty.Register(nameof(MinWideItemHeight), typeof(double), typeof(VerticalRepeaterView), new PropertyMetadata(232d)); - - /// - /// 的依赖属性. - /// - public static readonly DependencyProperty MinWideItemWidthProperty = - DependencyProperty.Register(nameof(MinWideItemWidth), typeof(double), typeof(VerticalRepeaterView), new PropertyMetadata(222d)); - - /// - /// 的依赖属性. - /// - public static readonly DependencyProperty EnableDetectParentScrollViewerProperty = - DependencyProperty.Register(nameof(EnableDetectParentScrollViewer), typeof(bool), typeof(VerticalRepeaterView), new PropertyMetadata(true)); - - /// - /// 的依赖属性. - /// - public static readonly DependencyProperty IsStaggeredProperty = - DependencyProperty.Register(nameof(IsStaggered), typeof(bool), typeof(VerticalRepeaterView), new PropertyMetadata(false, new PropertyChangedCallback(OnIsStaggeredChanged))); - - private ScrollViewer _parentScrollViewer; - private double _itemHolderHeight = 0d; - - /// - /// Initializes a new instance of the class. - /// - public VerticalRepeaterView() - { - this.InitializeComponent(); - this.Loaded += OnLoaded; - this.Unloaded += OnUnloaded; - } - - /// - /// 在外部的ScrollViewer滚动到接近底部时发生. - /// - public event EventHandler RequestLoadMore; - - /// - /// 条目模板. - /// - public DataTemplate ItemTemplate - { - get { return (DataTemplate)GetValue(ItemTemplateProperty); } - set { SetValue(ItemTemplateProperty, value); } - } - - /// - /// 数据源. - /// - public object ItemsSource - { - get { return (object)GetValue(ItemsSourceProperty); } - set { SetValue(ItemsSourceProperty, value); } - } - - /// - /// 子项的布局方式. - /// - public Orientation ItemOrientation - { - get { return (Orientation)GetValue(ItemOrientationProperty); } - set { SetValue(ItemOrientationProperty, value); } - } - - /// - /// 标题文本. - /// - public string HeaderText - { - get { return (string)GetValue(HeaderTextProperty); } - set { SetValue(HeaderTextProperty, value); } - } - - /// - /// 附加视觉元素,在顶部右上角. - /// - public object AdditionalContent - { - get { return (object)GetValue(AdditionalContentProperty); } - set { SetValue(AdditionalContentProperty, value); } - } - - /// - /// 是否允许根据容器剩余空间自行计算视频条目容量,并主动发起请求填满整个容器. - /// - public bool IsAutoFillEnable - { - get { return (bool)GetValue(IsAutoFillEnableProperty); } - set { SetValue(IsAutoFillEnableProperty, value); } - } - - /// - /// 标题的可见性. - /// - public Visibility HeaderVisibility - { - get { return (Visibility)GetValue(HeaderVisibilityProperty); } - set { SetValue(HeaderVisibilityProperty, value); } - } - - /// - /// 在网格布局下,单个条目的最小宽度. - /// - public double MinWideItemWidth - { - get { return (double)GetValue(MinWideItemWidthProperty); } - set { SetValue(MinWideItemWidthProperty, value); } - } - - /// - /// 在网格布局下,单个条目的最小高度. - /// - public double MinWideItemHeight - { - get { return (double)GetValue(MinWideItemHeightProperty); } - set { SetValue(MinWideItemHeightProperty, value); } - } - - /// - /// 是否启用自动检测父滚动视图. - /// - public bool EnableDetectParentScrollViewer - { - get { return (bool)GetValue(EnableDetectParentScrollViewerProperty); } - set { SetValue(EnableDetectParentScrollViewerProperty, value); } - } - - /// - /// 是否为流式布局. - /// - public bool IsStaggered - { - get { return (bool)GetValue(IsStaggeredProperty); } - set { SetValue(IsStaggeredProperty, value); } - } - - private static void OnIsStaggeredChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) - { - var instance = d as VerticalRepeaterView; - instance.CheckOrientationStatus(); - } - - private static void OnOrientationChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) - { - var instance = d as VerticalRepeaterView; - if (e.NewValue is Orientation type) - { - instance.CheckOrientationStatus(); - } - } - - private void CheckOrientationStatus() - { - if (IsStaggered) - { - ItemsRepeater.Layout = StaggeredLayout; - } - else - { - switch (ItemOrientation) - { - case Orientation.Vertical: - ItemsRepeater.Layout = GridLayout; - break; - case Orientation.Horizontal: - ItemsRepeater.Layout = ListLayout; - break; - default: - break; - } - } - - ChangeInitializedItemOrientation(); - } - - private void OnLoaded(object sender, RoutedEventArgs e) - { - if (EnableDetectParentScrollViewer) - { - _parentScrollViewer = this.FindAscendantElementByType(); - if (_parentScrollViewer != null) - { - _parentScrollViewer.ViewChanged += OnParentScrollViewerViewChanged; - } - } - } - - private void OnUnloaded(object sender, RoutedEventArgs e) - { - if (_parentScrollViewer != null) - { - _parentScrollViewer.ViewChanged -= OnParentScrollViewerViewChanged; - _parentScrollViewer = null; - } - } - - private void OnParentScrollViewerViewChanged(object sender, ScrollViewerViewChangedEventArgs e) - { - if (!e.IsIntermediate && _parentScrollViewer != null) - { - var currentPosition = _parentScrollViewer.VerticalOffset; - if (_parentScrollViewer.ScrollableHeight - currentPosition <= _itemHolderHeight && - this.Visibility == Visibility.Visible) - { - RequestLoadMore?.Invoke(this, EventArgs.Empty); - } - } - } - - private void ChangeInitializedItemOrientation() - { - if (ItemsSource is ICollection items) - { - for (var i = 0; i < items.Count; i++) - { - var element = ItemsRepeater.TryGetElement(i); - if (element != null && element is IDynamicLayoutItem vi) - { - if (vi.Orientation != ItemOrientation) - { - vi.Orientation = ItemOrientation; - } - } - } - } - } - - private void OnElementPrepared(ItemsRepeater sender, ItemsRepeaterElementPreparedEventArgs args) - { - if (args.Element != null) - { - if (args.Element is IDynamicLayoutItem dynamicLayoutItem) - { - dynamicLayoutItem.Orientation = ItemOrientation; - } - - if (IsAutoFillEnable && - args.Element is IRepeaterItem repeaterItem && - ItemsSource is ICollection collectionSource && - (_parentScrollViewer != null) && - args.Index >= collectionSource.Count - 1) - { - var size = repeaterItem.GetHolderSize(); - _itemHolderHeight = size.Height; - var viewportWidth = _parentScrollViewer.ViewportWidth; - var viewportHeight = _parentScrollViewer.ViewportHeight; - bool isNeedLoadMore; - if (double.IsInfinity(size.Width)) - { - isNeedLoadMore = (args.Index + 1) * size.Height <= viewportHeight; - } - else - { - var rowCount = args.Index / (viewportWidth / size.Width); - isNeedLoadMore = rowCount * size.Height <= viewportHeight; - } - - if (isNeedLoadMore) - { - RequestLoadMore?.Invoke(this, EventArgs.Empty); - } - } - } - } - } -} diff --git a/src/App/Controls/Common/VerticalRepeaterView/VerticalRepeaterView.Properties.cs b/src/App/Controls/Common/VerticalRepeaterView/VerticalRepeaterView.Properties.cs new file mode 100644 index 000000000..99bdfc79a --- /dev/null +++ b/src/App/Controls/Common/VerticalRepeaterView/VerticalRepeaterView.Properties.cs @@ -0,0 +1,202 @@ +// Copyright (c) Richasy. All rights reserved. + +using System; +using Windows.UI.Xaml; +using Windows.UI.Xaml.Controls; + +namespace Bili.App.Controls +{ + /// + /// 视频视图. + /// + public sealed partial class VerticalRepeaterView + { + /// + /// 的依赖属性. + /// + public static readonly DependencyProperty ItemsSourceProperty = + DependencyProperty.Register(nameof(ItemsSource), typeof(object), typeof(VerticalRepeaterView), new PropertyMetadata(null)); + + /// + /// 的依赖属性. + /// + public static readonly DependencyProperty ItemOrientationProperty = + DependencyProperty.Register(nameof(ItemOrientation), typeof(Orientation), typeof(VerticalRepeaterView), new PropertyMetadata(default(Orientation), new PropertyChangedCallback(OnOrientationChanged))); + + /// + /// 的依赖属性. + /// + public static readonly DependencyProperty HeaderTextProperty = + DependencyProperty.Register(nameof(HeaderText), typeof(string), typeof(VerticalRepeaterView), new PropertyMetadata(string.Empty)); + + /// + /// 的依赖属性. + /// + public static readonly DependencyProperty ItemTemplateProperty = + DependencyProperty.Register(nameof(ItemTemplate), typeof(object), typeof(VerticalRepeaterView), new PropertyMetadata(null)); + + /// + /// 的依赖属性. + /// + public static readonly DependencyProperty AdditionalContentProperty = + DependencyProperty.Register(nameof(AdditionalContent), typeof(object), typeof(VerticalRepeaterView), new PropertyMetadata(null)); + + /// + /// 的依赖属性. + /// + public static readonly DependencyProperty IsAutoFillEnableProperty = + DependencyProperty.Register(nameof(IsAutoFillEnable), typeof(bool), typeof(VerticalRepeaterView), new PropertyMetadata(true)); + + /// + /// 的依赖属性. + /// + public static readonly DependencyProperty HeaderVisibilityProperty = + DependencyProperty.Register(nameof(HeaderVisibility), typeof(Visibility), typeof(VerticalRepeaterView), new PropertyMetadata(Visibility.Visible)); + + /// + /// 的依赖属性. + /// + public static readonly DependencyProperty MinWideItemHeightProperty = + DependencyProperty.Register(nameof(MinWideItemHeight), typeof(double), typeof(VerticalRepeaterView), new PropertyMetadata(232d)); + + /// + /// 的依赖属性. + /// + public static readonly DependencyProperty MinWideItemWidthProperty = + DependencyProperty.Register(nameof(MinWideItemWidth), typeof(double), typeof(VerticalRepeaterView), new PropertyMetadata(222d)); + + /// + /// 的依赖属性. + /// + public static readonly DependencyProperty EnableDetectParentScrollViewerProperty = + DependencyProperty.Register(nameof(EnableDetectParentScrollViewer), typeof(bool), typeof(VerticalRepeaterView), new PropertyMetadata(true)); + + /// + /// 的依赖属性. + /// + public static readonly DependencyProperty IsStaggeredProperty = + DependencyProperty.Register(nameof(IsStaggered), typeof(bool), typeof(VerticalRepeaterView), new PropertyMetadata(false, new PropertyChangedCallback(OnIsStaggeredChanged))); + + /// + /// 的依赖属性. + /// + public static readonly DependencyProperty VerticalCacheSizeProperty = + DependencyProperty.Register(nameof(VerticalCacheSize), typeof(int), typeof(VerticalRepeaterView), new PropertyMetadata(10)); + + /// + /// 在外部的ScrollViewer滚动到接近底部时发生. + /// + public event EventHandler RequestLoadMore; + + /// + public event EventHandler IncrementalTriggered; + + /// + /// 条目模板. + /// + public object ItemTemplate + { + get { return (DataTemplate)GetValue(ItemTemplateProperty); } + set { SetValue(ItemTemplateProperty, value); } + } + + /// + /// 数据源. + /// + public object ItemsSource + { + get { return (object)GetValue(ItemsSourceProperty); } + set { SetValue(ItemsSourceProperty, value); } + } + + /// + /// 子项的布局方式. + /// + public Orientation ItemOrientation + { + get { return (Orientation)GetValue(ItemOrientationProperty); } + set { SetValue(ItemOrientationProperty, value); } + } + + /// + /// 标题文本. + /// + public string HeaderText + { + get { return (string)GetValue(HeaderTextProperty); } + set { SetValue(HeaderTextProperty, value); } + } + + /// + /// 附加视觉元素,在顶部右上角. + /// + public object AdditionalContent + { + get { return (object)GetValue(AdditionalContentProperty); } + set { SetValue(AdditionalContentProperty, value); } + } + + /// + /// 是否允许根据容器剩余空间自行计算视频条目容量,并主动发起请求填满整个容器. + /// + public bool IsAutoFillEnable + { + get { return (bool)GetValue(IsAutoFillEnableProperty); } + set { SetValue(IsAutoFillEnableProperty, value); } + } + + /// + /// 标题的可见性. + /// + public Visibility HeaderVisibility + { + get { return (Visibility)GetValue(HeaderVisibilityProperty); } + set { SetValue(HeaderVisibilityProperty, value); } + } + + /// + /// 在网格布局下,单个条目的最小宽度. + /// + public double MinWideItemWidth + { + get { return (double)GetValue(MinWideItemWidthProperty); } + set { SetValue(MinWideItemWidthProperty, value); } + } + + /// + /// 在网格布局下,单个条目的最小高度. + /// + public double MinWideItemHeight + { + get { return (double)GetValue(MinWideItemHeightProperty); } + set { SetValue(MinWideItemHeightProperty, value); } + } + + /// + /// 是否启用自动检测父滚动视图. + /// + public bool EnableDetectParentScrollViewer + { + get { return (bool)GetValue(EnableDetectParentScrollViewerProperty); } + set { SetValue(EnableDetectParentScrollViewerProperty, value); } + } + + /// + /// 是否为流式布局. + /// + public bool IsStaggered + { + get { return (bool)GetValue(IsStaggeredProperty); } + set { SetValue(IsStaggeredProperty, value); } + } + + /// + /// 垂直方向缓存大小. + /// + public int VerticalCacheSize + { + get { return (int)GetValue(VerticalCacheSizeProperty); } + set { SetValue(VerticalCacheSizeProperty, value); } + } + } +} diff --git a/src/App/Controls/Common/VerticalRepeaterView/VerticalRepeaterView.cs b/src/App/Controls/Common/VerticalRepeaterView/VerticalRepeaterView.cs new file mode 100644 index 000000000..98e43c2b3 --- /dev/null +++ b/src/App/Controls/Common/VerticalRepeaterView/VerticalRepeaterView.cs @@ -0,0 +1,268 @@ +// Copyright (c) Richasy. All rights reserved. + +using System; +using System.Collections; +using System.Threading.Tasks; +using Bili.App.Resources.Extension; +using Microsoft.UI.Xaml.Controls; +using Windows.UI.Xaml; +using Windows.UI.Xaml.Controls; +using Windows.UI.Xaml.Input; + +namespace Bili.App.Controls +{ + /// + /// 视频视图. + /// + public sealed partial class VerticalRepeaterView : Control, IIncrementalControl + { + private ScrollViewer _parentScrollViewer; + private ItemsRepeater _itemsRepeater; + + private double _itemHolderHeight = 0d; + private bool _isTempalteApplied = false; + private bool _isLoaded = false; + + /// + /// Initializes a new instance of the class. + /// + public VerticalRepeaterView() + { + DefaultStyleKey = typeof(VerticalRepeaterView); + Loaded += OnLoaded; + Unloaded += OnUnloaded; + SizeChanged += OnSizeChanged; + } + + /// + /// 滚动到条目. + /// + /// 条目索引值. + public void ScrollToItem(int index) + { + if (_itemsRepeater != null) + { + var element = _itemsRepeater.GetOrCreateElement(index); + var options = new BringIntoViewOptions + { + VerticalAlignmentRatio = 0f, + }; + element.StartBringIntoView(options); + } + } + + /// + protected override void OnApplyTemplate() + { + _itemsRepeater = GetTemplateChild("ItemsRepeater") as ItemsRepeater; + + if (_itemsRepeater != null) + { + _itemsRepeater.ElementPrepared += OnElementPreparedAsync; + } + + _isTempalteApplied = true; + if (_isTempalteApplied && _isLoaded) + { + CheckOrientationStatus(); + } + } + + private static void OnIsStaggeredChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) + { + var instance = d as VerticalRepeaterView; + instance.CheckOrientationStatus(); + } + + private static void OnOrientationChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) + { + var instance = d as VerticalRepeaterView; + if (e.NewValue is Orientation) + { + instance.CheckOrientationStatus(); + } + } + + private void CheckOrientationStatus() + { + if (_itemsRepeater != null) + { + _itemsRepeater.Layout = GetLayout(); + } + + ChangeInitializedItemOrientation(); + } + + private void OnLoaded(object sender, RoutedEventArgs e) + { + if (EnableDetectParentScrollViewer) + { + _parentScrollViewer = this.FindAscendantElementByType(); + if (_parentScrollViewer != null) + { + _parentScrollViewer.ViewChanged += OnParentScrollViewerViewChanged; + } + } + + _isLoaded = true; + if (_isLoaded && _isTempalteApplied) + { + CheckOrientationStatus(); + } + } + + private void OnUnloaded(object sender, RoutedEventArgs e) + { + if (_parentScrollViewer != null) + { + _parentScrollViewer.ViewChanged -= OnParentScrollViewerViewChanged; + _parentScrollViewer = null; + } + } + + private void OnSizeChanged(object sender, SizeChangedEventArgs e) + { + if (_parentScrollViewer != null) + { + var currentPosition = _parentScrollViewer.VerticalOffset; + if (_parentScrollViewer.ScrollableHeight - currentPosition <= _itemHolderHeight && + Visibility == Visibility.Visible) + { + RequestLoadMore?.Invoke(this, EventArgs.Empty); + IncrementalTriggered?.Invoke(this, EventArgs.Empty); + } + } + } + + private void OnParentScrollViewerViewChanged(object sender, ScrollViewerViewChangedEventArgs e) + { + if (!e.IsIntermediate && _parentScrollViewer != null) + { + var currentPosition = _parentScrollViewer.VerticalOffset; + if (_parentScrollViewer.ScrollableHeight - currentPosition <= _itemHolderHeight && + Visibility == Visibility.Visible) + { + RequestLoadMore?.Invoke(this, EventArgs.Empty); + IncrementalTriggered?.Invoke(this, EventArgs.Empty); + } + } + } + + private void ChangeInitializedItemOrientation() + { + if (ItemsSource is ICollection items) + { + for (var i = 0; i < items.Count; i++) + { + var element = _itemsRepeater?.TryGetElement(i); + if (element != null) + { + if (element is IDynamicLayoutItem vi) + { + if (vi.Orientation != ItemOrientation) + { + vi.Orientation = ItemOrientation; + } + } + else if (element is IOrientationControl oc) + { + oc.ChangeLayout(ItemOrientation); + } + } + } + } + } + + private async void OnElementPreparedAsync(ItemsRepeater sender, ItemsRepeaterElementPreparedEventArgs args) + { + if (args.Element != null) + { + if (args.Element is IOrientationControl orientationControl) + { + orientationControl.ChangeLayout(ItemOrientation); + } + + if (args.Element is IDynamicLayoutItem dynamicLayoutItem) + { + dynamicLayoutItem.Orientation = ItemOrientation; + } + + if (IsAutoFillEnable && + args.Element is IRepeaterItem repeaterItem && + ItemsSource is ICollection collectionSource && + (_parentScrollViewer != null) && + args.Index >= collectionSource.Count - 1) + { + var size = repeaterItem.GetHolderSize(); + _itemHolderHeight = size.Height; + var viewportWidth = _parentScrollViewer.ViewportWidth; + var viewportHeight = _parentScrollViewer.ViewportHeight; + bool isNeedLoadMore; + if (double.IsInfinity(size.Width)) + { + isNeedLoadMore = (args.Index + 1) * size.Height <= viewportHeight; + } + else + { + var rowCount = args.Index / (viewportWidth / size.Width); + isNeedLoadMore = rowCount * size.Height <= viewportHeight; + } + + if (isNeedLoadMore) + { + RequestLoadMore?.Invoke(this, EventArgs.Empty); + IncrementalTriggered?.Invoke(this, EventArgs.Empty); + } + } + + if (args.Index == 0) + { + await Task.Delay(200); + await FocusManager.TryFocusAsync(args.Element, FocusState.Programmatic); + } + } + } + + private VirtualizingLayout GetLayout() + { + VirtualizingLayout layout = null; + if (IsStaggered) + { + layout = new StaggeredLayout + { + ColumnSpacing = 16, + DesiredColumnWidth = MinWideItemWidth, + RowSpacing = 16, + }; + } + else + { + switch (ItemOrientation) + { + case Orientation.Vertical: + layout = new UniformGridLayout() + { + ItemsStretch = UniformGridLayoutItemsStretch.Fill, + MinColumnSpacing = 16, + MinItemHeight = MinWideItemHeight, + MinItemWidth = MinWideItemWidth, + MinRowSpacing = 16, + Orientation = Orientation.Horizontal, + }; + break; + case Orientation.Horizontal: + layout = new StackLayout() + { + Orientation = Orientation.Vertical, + Spacing = 8, + }; + break; + default: + break; + } + } + + return layout; + } + } +} diff --git a/src/App/Controls/Common/VerticalRepeaterView/VerticalRepeaterView.xaml b/src/App/Controls/Common/VerticalRepeaterView/VerticalRepeaterView.xaml new file mode 100644 index 000000000..df6a7155b --- /dev/null +++ b/src/App/Controls/Common/VerticalRepeaterView/VerticalRepeaterView.xaml @@ -0,0 +1,53 @@ + + + + diff --git a/src/App/Controls/Common/VideoItem.xaml b/src/App/Controls/Common/VideoItem.xaml deleted file mode 100644 index d0a5c2f59..000000000 --- a/src/App/Controls/Common/VideoItem.xaml +++ /dev/null @@ -1,231 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/src/App/Controls/Common/VideoItem.xaml.cs b/src/App/Controls/Common/VideoItem.xaml.cs deleted file mode 100644 index 3a8a58453..000000000 --- a/src/App/Controls/Common/VideoItem.xaml.cs +++ /dev/null @@ -1,306 +0,0 @@ -// Copyright (c) Richasy. All rights reserved. - -using System; -using Richasy.Bili.ViewModels.Uwp; -using Windows.Foundation; -using Windows.UI.Xaml; -using Windows.UI.Xaml.Controls; - -namespace Richasy.Bili.App.Controls -{ - /// - /// 视频条目. - /// - public sealed partial class VideoItem : UserControl, IRepeaterItem, IDynamicLayoutItem - { - /// - /// 的依赖属性. - /// - public static readonly DependencyProperty ViewModelProperty = - DependencyProperty.Register(nameof(ViewModel), typeof(VideoViewModel), typeof(VideoItem), new PropertyMetadata(null)); - - /// - /// 的依赖属性. - /// - public static readonly DependencyProperty OrientationProperty = - DependencyProperty.Register(nameof(Orientation), typeof(Orientation), typeof(VideoItem), new PropertyMetadata(default(Orientation), new PropertyChangedCallback(OnOrientationChanged))); - - /// - /// 的依赖属性. - /// - public static readonly DependencyProperty IsShowPublishDateTimeProperty = - DependencyProperty.Register(nameof(IsShowPublishDateTime), typeof(bool), typeof(VideoItem), new PropertyMetadata(false)); - - /// - /// 的依赖属性. - /// - public static readonly DependencyProperty IsShowReplayCountProperty = - DependencyProperty.Register(nameof(IsShowReplayCount), typeof(bool), typeof(VideoItem), new PropertyMetadata(false)); - - /// - /// 的依赖属性. - /// - public static readonly DependencyProperty IsShowDanmakuCountProperty = - DependencyProperty.Register(nameof(IsShowDanmakuCount), typeof(bool), typeof(VideoItem), new PropertyMetadata(false)); - - /// - /// 的依赖属性. - /// - public static readonly DependencyProperty IsShowPlayCountProperty = - DependencyProperty.Register(nameof(IsShowPlayCount), typeof(bool), typeof(VideoItem), new PropertyMetadata(false)); - - /// - /// 的依赖属性. - /// - public static readonly DependencyProperty IsShowLikeCountProperty = - DependencyProperty.Register(nameof(IsShowLikeCount), typeof(bool), typeof(VideoItem), new PropertyMetadata(false)); - - /// - /// 的依赖属性. - /// - public static readonly DependencyProperty IsShowViewerCountProperty = - DependencyProperty.Register(nameof(IsShowViewerCount), typeof(bool), typeof(VideoItem), new PropertyMetadata(false)); - - /// - /// 的依赖属性. - /// - public static readonly DependencyProperty IsShowDurationProperty = - DependencyProperty.Register(nameof(IsShowDuration), typeof(bool), typeof(VideoItem), new PropertyMetadata(true)); - - /// - /// 的依赖属性. - /// - public static readonly DependencyProperty IsShowAvatarProperty = - DependencyProperty.Register(nameof(IsShowAvatar), typeof(bool), typeof(VideoItem), new PropertyMetadata(true)); - - /// - /// 的依赖属性. - /// - public static readonly DependencyProperty AdditionalFooterContentProperty = - DependencyProperty.Register(nameof(AdditionalFooterContent), typeof(object), typeof(VideoItem), new PropertyMetadata(null)); - - /// - /// 的依赖属性. - /// - public static readonly DependencyProperty AdditionalOverlayContentProperty = - DependencyProperty.Register(nameof(AdditionalOverlayContent), typeof(object), typeof(VideoItem), new PropertyMetadata(null)); - - /// - /// 的依赖属性. - /// - public static readonly DependencyProperty AdditionalOverlayContentVisibilityProperty = - DependencyProperty.Register(nameof(AdditionalOverlayContentVisibility), typeof(Visibility), typeof(VideoItem), new PropertyMetadata(Visibility.Collapsed)); - - /// - /// 的依赖属性. - /// - public static readonly DependencyProperty HorizontalCoverWidthProperty = - DependencyProperty.Register(nameof(HorizontalCoverWidth), typeof(double), typeof(VideoItem), new PropertyMetadata(160d)); - - /// - /// Initializes a new instance of the class. - /// - public VideoItem() - { - this.InitializeComponent(); - this.Loaded += OnLoaded; - this.AppViewModel = AppViewModel.Instance; - } - - /// - /// 条目被点击时触发. - /// - public event EventHandler ItemClick; - - /// - /// 应用视图模型. - /// - public AppViewModel AppViewModel { get; private set; } - - /// - /// 视频视图模型. - /// - public VideoViewModel ViewModel - { - get { return (VideoViewModel)GetValue(ViewModelProperty); } - set { SetValue(ViewModelProperty, value); } - } - - /// - /// 视频的布局方式. - /// - public Orientation Orientation - { - get { return (Orientation)GetValue(OrientationProperty); } - set { SetValue(OrientationProperty, value); } - } - - /// - /// 是否显示播放数. - /// - public bool IsShowPlayCount - { - get { return (bool)GetValue(IsShowPlayCountProperty); } - set { SetValue(IsShowPlayCountProperty, value); } - } - - /// - /// 是否显示弹幕数. - /// - public bool IsShowDanmakuCount - { - get { return (bool)GetValue(IsShowDanmakuCountProperty); } - set { SetValue(IsShowDanmakuCountProperty, value); } - } - - /// - /// 是否显示回复数. - /// - public bool IsShowReplayCount - { - get { return (bool)GetValue(IsShowReplayCountProperty); } - set { SetValue(IsShowReplayCountProperty, value); } - } - - /// - /// 是否显示发布时间. - /// - public bool IsShowPublishDateTime - { - get { return (bool)GetValue(IsShowPublishDateTimeProperty); } - set { SetValue(IsShowPublishDateTimeProperty, value); } - } - - /// - /// 是否显示点赞数. - /// - public bool IsShowLikeCount - { - get { return (bool)GetValue(IsShowLikeCountProperty); } - set { SetValue(IsShowLikeCountProperty, value); } - } - - /// - /// 是否显示点赞数. - /// - public bool IsShowDuration - { - get { return (bool)GetValue(IsShowDurationProperty); } - set { SetValue(IsShowDurationProperty, value); } - } - - /// - /// 是否显示观看人数. - /// - public bool IsShowViewerCount - { - get { return (bool)GetValue(IsShowViewerCountProperty); } - set { SetValue(IsShowViewerCountProperty, value); } - } - - /// - /// 是否显示头像. - /// - public bool IsShowAvatar - { - get { return (bool)GetValue(IsShowAvatarProperty); } - set { SetValue(IsShowAvatarProperty, value); } - } - - /// - /// 附加的底部内容,显示在内容区底部. - /// - public object AdditionalFooterContent - { - get { return (object)GetValue(AdditionalFooterContentProperty); } - set { SetValue(AdditionalFooterContentProperty, value); } - } - - /// - /// 附加的Overlay内容,在竖向排版时显示在视频封面上,横向排版时显示在标题下方. - /// - public object AdditionalOverlayContent - { - get { return (object)GetValue(AdditionalOverlayContentProperty); } - set { SetValue(AdditionalOverlayContentProperty, value); } - } - - /// - /// 是否显示附加的Overlay内容. - /// - public Visibility AdditionalOverlayContentVisibility - { - get { return (Visibility)GetValue(AdditionalOverlayContentVisibilityProperty); } - set { SetValue(AdditionalOverlayContentVisibilityProperty, value); } - } - - /// - /// 水平状态下的封面宽度. - /// - public double HorizontalCoverWidth - { - get { return (double)GetValue(HorizontalCoverWidthProperty); } - set { SetValue(HorizontalCoverWidthProperty, value); } - } - - /// - public Size GetHolderSize() - { - if (Orientation == Orientation.Horizontal) - { - return new Size(double.PositiveInfinity, 180); - } - else - { - return new Size(210, 248); - } - } - - private static void OnOrientationChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) - { - var instance = d as VideoItem; - instance.CheckOrientation(); - } - - private void OnLoaded(object sender, RoutedEventArgs e) => CheckOrientation(); - - private void CheckOrientation() - { - switch (Orientation) - { - case Orientation.Vertical: - VisualStateManager.GoToState(this, nameof(VerticalState), false); - CoverContainer.Width = double.NaN; - break; - case Orientation.Horizontal: - VisualStateManager.GoToState(this, nameof(HorizontalState), false); - CoverContainer.Width = this.HorizontalCoverWidth; - break; - default: - break; - } - } - - private void OnContainerClickAsync(object sender, RoutedEventArgs e) - { - AppViewModel.OpenPlayer(ViewModel); - ItemClick?.Invoke(this, this.ViewModel); - } - - private async void OnAddToViewLaterItemClickAsync(object sender, RoutedEventArgs e) - { - if (ViewModel.VideoType == Models.Enums.VideoType.Video) - { - await ViewLaterViewModel.Instance.AddAsync(ViewModel); - } - } - - private void OnFlyoutOpening(object sender, object e) - { - if (ViewModel.VideoType != Models.Enums.VideoType.Video) - { - ContextFlyout.Hide(); - } - } - } -} diff --git a/src/App/Controls/Community/CommentBox.xaml b/src/App/Controls/Community/CommentBox.xaml new file mode 100644 index 000000000..747de6e1f --- /dev/null +++ b/src/App/Controls/Community/CommentBox.xaml @@ -0,0 +1,84 @@ + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/App/Controls/Community/CommentBox.xaml.cs b/src/App/Controls/Community/CommentBox.xaml.cs new file mode 100644 index 000000000..3d04c2278 --- /dev/null +++ b/src/App/Controls/Community/CommentBox.xaml.cs @@ -0,0 +1,79 @@ +// Copyright (c) Richasy. All rights reserved. + +using System.Windows.Input; +using Windows.UI.Xaml; +using Windows.UI.Xaml.Controls; + +namespace Bili.App.Controls.Community +{ + /// + /// 评论文本. + /// + public sealed partial class CommentBox : UserControl + { + /// + /// 的依赖属性. + /// + public static readonly DependencyProperty TextProperty = + DependencyProperty.Register(nameof(Text), typeof(string), typeof(CommentBox), new PropertyMetadata(default)); + + /// + /// 的依赖属性. + /// + public static readonly DependencyProperty SendCommandProperty = + DependencyProperty.Register(nameof(SendCommand), typeof(ICommand), typeof(CommentBox), new PropertyMetadata(default)); + + /// + /// 的依赖属性. + /// + public static readonly DependencyProperty ReplyTipProperty = + DependencyProperty.Register(nameof(ReplyTip), typeof(string), typeof(CommentBox), new PropertyMetadata(default)); + + /// + /// 的依赖属性. + /// + public static readonly DependencyProperty ResetSelectedCommandProperty = + DependencyProperty.Register(nameof(ResetSelectedCommand), typeof(ICommand), typeof(CommentBox), new PropertyMetadata(default)); + + /// + /// Initializes a new instance of the class. + /// + public CommentBox() => InitializeComponent(); + + /// + /// 回复文本. + /// + public string Text + { + get { return (string)GetValue(TextProperty); } + set { SetValue(TextProperty, value); } + } + + /// + /// 发送评论命令. + /// + public ICommand SendCommand + { + get { return (ICommand)GetValue(SendCommandProperty); } + set { SetValue(SendCommandProperty, value); } + } + + /// + /// 评论提示. + /// + public string ReplyTip + { + get { return (string)GetValue(ReplyTipProperty); } + set { SetValue(ReplyTipProperty, value); } + } + + /// + /// 重置已选择评论的命令. + /// + public ICommand ResetSelectedCommand + { + get { return (ICommand)GetValue(ResetSelectedCommandProperty); } + set { SetValue(ResetSelectedCommandProperty, value); } + } + } +} diff --git a/src/App/Controls/Community/CommentDetailView/CommentDetailView.cs b/src/App/Controls/Community/CommentDetailView/CommentDetailView.cs new file mode 100644 index 000000000..e59bc2589 --- /dev/null +++ b/src/App/Controls/Community/CommentDetailView/CommentDetailView.cs @@ -0,0 +1,17 @@ +// Copyright (c) Richasy. All rights reserved. + +using Bili.ViewModels.Interfaces.Community; + +namespace Bili.App.Controls.Community +{ + /// + /// 评论区详情视图. + /// + public sealed class CommentDetailView : ReactiveControl + { + /// + /// Initializes a new instance of the class. + /// + public CommentDetailView() => DefaultStyleKey = typeof(CommentDetailView); + } +} diff --git a/src/App/Controls/Community/CommentDetailView/CommentDetailView.xaml b/src/App/Controls/Community/CommentDetailView/CommentDetailView.xaml new file mode 100644 index 000000000..c421d3153 --- /dev/null +++ b/src/App/Controls/Community/CommentDetailView/CommentDetailView.xaml @@ -0,0 +1,80 @@ + + + + + + + + + + + + diff --git a/src/App/Controls/Community/RelationPageView.xaml b/src/App/Controls/Community/RelationPageView.xaml new file mode 100644 index 000000000..eae4cd321 --- /dev/null +++ b/src/App/Controls/Community/RelationPageView.xaml @@ -0,0 +1,84 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/App/Controls/Community/RelationPageView.xaml.cs b/src/App/Controls/Community/RelationPageView.xaml.cs new file mode 100644 index 000000000..8aea9bc4e --- /dev/null +++ b/src/App/Controls/Community/RelationPageView.xaml.cs @@ -0,0 +1,38 @@ +// Copyright (c) Richasy. All rights reserved. + +using Bili.DI.Container; +using Bili.ViewModels.Interfaces; +using Bili.ViewModels.Interfaces.Core; +using Windows.UI.Xaml; +using Windows.UI.Xaml.Controls; + +namespace Bili.App.Controls.Community +{ + /// + /// 关系页面视图. + /// + public sealed partial class RelationPageView : UserControl + { + /// + /// 的依赖属性. + /// + public static readonly DependencyProperty ViewModelProperty = + DependencyProperty.Register(nameof(ViewModel), typeof(IRelationPageViewModel), typeof(RelationPageView), new PropertyMetadata(default)); + + private readonly IAppViewModel _appViewModel = Locator.Instance.GetService(); + + /// + /// Initializes a new instance of the class. + /// + public RelationPageView() => InitializeComponent(); + + /// + /// 视图模型. + /// + public IRelationPageViewModel ViewModel + { + get { return (IRelationPageViewModel)GetValue(ViewModelProperty); } + set { SetValue(ViewModelProperty, value); } + } + } +} diff --git a/src/App/Controls/Danmaku/DanmakuBox.xaml b/src/App/Controls/Danmaku/DanmakuBox.xaml index 877b0a8d0..fe1b2bea6 100644 --- a/src/App/Controls/Danmaku/DanmakuBox.xaml +++ b/src/App/Controls/Danmaku/DanmakuBox.xaml @@ -1,11 +1,11 @@  + ToolTipService.ToolTip="{loc:Locale Name=DanmakuSwitch}" /> + Spacing="8"> diff --git a/src/App/Controls/Danmaku/DanmakuBox.xaml.cs b/src/App/Controls/Danmaku/DanmakuBox.xaml.cs index be7b72628..da61f3ac2 100644 --- a/src/App/Controls/Danmaku/DanmakuBox.xaml.cs +++ b/src/App/Controls/Danmaku/DanmakuBox.xaml.cs @@ -1,47 +1,60 @@ // Copyright (c) Richasy. All rights reserved. -using Richasy.Bili.ViewModels.Uwp.Common; +using System; +using Bili.ViewModels.Interfaces.Common; +using Windows.UI.Xaml; using Windows.UI.Xaml.Controls; -namespace Richasy.Bili.App.Controls +namespace Bili.App.Controls.Danmaku { /// /// 弹幕输入控件. /// public sealed partial class DanmakuBox : UserControl { + /// + /// 的依赖属性. + /// + public static readonly DependencyProperty ViewModelProperty = + DependencyProperty.Register(nameof(ViewModel), typeof(IDanmakuModuleViewModel), typeof(DanmakuBox), new PropertyMetadata(default)); + /// /// Initializes a new instance of the class. /// - public DanmakuBox() + public DanmakuBox() => InitializeComponent(); + + /// + /// 视图模型. + /// + public IDanmakuModuleViewModel ViewModel { - this.InitializeComponent(); - ViewModel = DanmakuViewModel.Instance; + get { return (IDanmakuModuleViewModel)GetValue(ViewModelProperty); } + set { SetValue(ViewModelProperty, value); } } /// - /// 视图模型. + /// 是否正在聚焦输入框. /// - public DanmakuViewModel ViewModel { get; private set; } + public bool IsInputFocused { get; private set; } private async void OnDanmakuInputBoxSubmittedAsync(AutoSuggestBox sender, AutoSuggestBoxQuerySubmittedEventArgs args) { if (!string.IsNullOrEmpty(args.QueryText)) { sender.IsEnabled = false; - var result = await ViewModel.SendDanmakuAsync(args.QueryText); - sender.IsEnabled = true; - - if (result) + await ViewModel.SendDanmakuCommand.ExecuteAsync(args.QueryText); + await Dispatcher.RunAsync(Windows.UI.Core.CoreDispatcherPriority.Normal, () => { + sender.IsEnabled = true; sender.Text = string.Empty; - } + }); } } - private void OnSendFlyoutOpened(object sender, object e) - { - SendOptions.Initialize(); - } + private void OnDanmakuInputBoxGotFocus(object sender, RoutedEventArgs e) + => IsInputFocused = true; + + private void OnDanmakuInputBoxLostFocus(object sender, RoutedEventArgs e) + => IsInputFocused = false; } } diff --git a/src/App/Controls/Danmaku/DanmakuBuilder.cs b/src/App/Controls/Danmaku/DanmakuBuilder.cs deleted file mode 100644 index bea6d31c7..000000000 --- a/src/App/Controls/Danmaku/DanmakuBuilder.cs +++ /dev/null @@ -1,259 +0,0 @@ -// Copyright (c) Richasy. All rights reserved. - -using System; -using System.Threading.Tasks; -using Microsoft.Graphics.Canvas; -using Microsoft.Graphics.Canvas.Geometry; -using Microsoft.Graphics.Canvas.Text; -using Microsoft.Toolkit.Uwp.UI; -using Windows.Storage.Streams; -using Windows.UI; -using Windows.UI.Text; -using Windows.UI.Xaml; -using Windows.UI.Xaml.Controls; -using Windows.UI.Xaml.Media; -using Windows.UI.Xaml.Media.Imaging; - -namespace Richasy.Bili.App.Controls -{ - /// - /// 弹幕组件构建器. - /// - public class DanmakuBuilder - { - private double _sizeZoom = 1d; - private bool _isBold = false; - private string _fontFamily = "Segoe UI"; - private DanmakuModel _model = null; - - /// - /// 设置缩放比例. - /// - /// 缩放比例. - /// 构造器. - public DanmakuBuilder WithSizeZoom(double sizeZoom) - { - _sizeZoom = sizeZoom; - return this; - } - - /// - /// 设置字体是否加粗. - /// - /// 是否加粗. - /// 构造器. - public DanmakuBuilder WithBold(bool isBold) - { - _isBold = isBold; - return this; - } - - /// - /// 设置字体. - /// - /// 字体名. - /// 构造器. - public DanmakuBuilder WithFontFamily(string fontFamily) - { - _fontFamily = fontFamily; - return this; - } - - /// - /// 设置弹幕模型. - /// - /// 模型. - /// 构造器. - public DanmakuBuilder WithDanmakuModel(DanmakuModel model) - { - _model = model; - return this; - } - - /// - /// 创建重叠弹幕. - /// - /// 弹幕容器. - public Grid CreateOverlapDanamku() - { - if (_model == null) - { - throw new ArgumentNullException("未传入弹幕模型."); - } - - // 创建基础控件 - var tx = new TextBlock(); - var tx2 = new TextBlock(); - var grid = new Grid(); - - tx2.Text = _model.Text; - tx.Text = _model.Text; - - if (_isBold) - { - tx.FontWeight = FontWeights.Bold; - tx2.FontWeight = FontWeights.Bold; - } - - if (!string.IsNullOrEmpty(_fontFamily)) - { - tx.FontFamily = new FontFamily(_fontFamily); - tx2.FontFamily = new FontFamily(_fontFamily); - } - - tx2.Foreground = _model.Foreground; - tx.Foreground = _model.Foreground; - - // 弹幕大小 - var size = _model.Size * _sizeZoom; - - tx2.FontSize = size; - tx.FontSize = size; - - tx2.Margin = new Thickness(1); - - // Grid包含弹幕文本信息 - grid.Children.Add(tx2); - grid.Children.Add(tx); - grid.Tag = _model; - return grid; - } - - /// - /// 创建描边弹幕. - /// - /// 弹幕容器. - public async Task CreateStrokeDanmakuAsync() - { - if (_model == null) - { - throw new ArgumentNullException("未传入弹幕模型."); - } - - var size = _model.Size * _sizeZoom; - var container = new Grid(); - var device = CanvasDevice.GetSharedDevice(); - var format = new CanvasTextFormat() { FontSize = (float)size, WordWrapping = CanvasWordWrapping.NoWrap, FontFamily = _fontFamily }; - if (_isBold) - { - format.FontWeight = FontWeights.Bold; - } - - var holderBlock = new TextBlock() - { - Text = _model.Text, - FontSize = size, - FontFamily = new FontFamily(_fontFamily), - }; - holderBlock.Measure(new Windows.Foundation.Size(double.PositiveInfinity, double.PositiveInfinity)); - - var renderTarget = new CanvasRenderTarget(device, (float)holderBlock.DesiredSize.Width, (float)holderBlock.DesiredSize.Height, 96); - var layout = new CanvasTextLayout(device, _model.Text, format, (float)holderBlock.DesiredSize.Width, (float)holderBlock.DesiredSize.Height); - var textGeo = CanvasGeometry.CreateText(layout); - - using (var session = renderTarget.CreateDrawingSession()) - { - session.Clear(Colors.Transparent); - var borderColor = _model.Color.R <= 80 ? Color.FromArgb(125, 255, 255, 255) : Color.FromArgb(125, 0, 0, 0); - session.DrawGeometry(textGeo, borderColor, 1f, new CanvasStrokeStyle() { DashStyle = CanvasDashStyle.Solid }); - session.FillGeometry(textGeo, _model.Color); - } - - using (var stream = new InMemoryRandomAccessStream()) - { - var image = new Image(); - var bitmap = new BitmapImage(); - await renderTarget.SaveAsync(stream, CanvasBitmapFileFormat.Png, 1.0f); - await bitmap.SetSourceAsync(stream); - image.Source = bitmap; - image.Stretch = Stretch.None; - container.Children.Add(image); - } - - container.Tag = _model; - return container; - } - - /// - /// 创建无边框弹幕. - /// - /// 弹幕容器. - public Grid CreateNoStrokeDanmaku() - { - if (_model == null) - { - throw new ArgumentNullException("未传入弹幕模型."); - } - - // 创建基础控件 - var tx = new TextBlock(); - var grid = new Grid(); - - tx.Text = _model.Text; - if (_isBold) - { - tx.FontWeight = FontWeights.Bold; - } - - if (string.IsNullOrEmpty(_fontFamily)) - { - tx.FontFamily = new FontFamily(_fontFamily); - } - - tx.Foreground = _model.Foreground; - var size = _model.Size * _sizeZoom; - tx.FontSize = size; - grid.Children.Add(tx); - grid.Tag = _model; - return grid; - } - - /// - /// 创建阴影弹幕. - /// - /// 弹幕容器. - public Grid CreateShadowDanmaku() - { - if (_model == null) - { - throw new ArgumentNullException("未传入弹幕模型."); - } - - // 创建基础控件 - var tx = new TextBlock(); - tx.Text = _model.Text; - if (_isBold) - { - tx.FontWeight = FontWeights.Bold; - } - - if (string.IsNullOrEmpty(_fontFamily)) - { - tx.FontFamily = new FontFamily(_fontFamily); - } - - tx.Foreground = _model.Foreground; - var size = _model.Size * _sizeZoom; - tx.FontSize = size; - - var grid = new Grid(); - var hostGrid = new Grid(); - - var shadow = new AttachedDropShadow(); - shadow.BlurRadius = 2; - shadow.Opacity = 0.8; - shadow.Offset = "1,1,0"; - shadow.Color = _model.Color.R <= 80 ? Colors.White : Colors.Black; - grid.Children.Add(hostGrid); - grid.Children.Add(tx); - grid.Loaded += (s, e) => - { - shadow.CastTo = hostGrid; - }; - - Effects.SetShadow(tx, shadow); - grid.Tag = _model; - return grid; - } - } -} diff --git a/src/App/Controls/Danmaku/DanmakuDisplayOptions.xaml b/src/App/Controls/Danmaku/DanmakuDisplayOptions.xaml index 8ab165063..7118b93a0 100644 --- a/src/App/Controls/Danmaku/DanmakuDisplayOptions.xaml +++ b/src/App/Controls/Danmaku/DanmakuDisplayOptions.xaml @@ -1,61 +1,63 @@  + + + + + StepFrequency="0.5" + ThumbToolTipValueConverter="{StaticResource FontSizeConverter}" + Value="{x:Bind ViewModel.DanmakuFontSize, Mode=TwoWay}" /> - - - - - - - + x:Name="AreaSlider" + Header="{loc:Locale Name=DanmakuArea}" + Maximum="1" + Minimum="0.25" + StepFrequency="0.25" + Value="{x:Bind ViewModel.DanmakuArea, Mode=TwoWay}" /> + - - - + + + + diff --git a/src/App/Controls/Danmaku/DanmakuDisplayOptions.xaml.cs b/src/App/Controls/Danmaku/DanmakuDisplayOptions.xaml.cs index bdc814dc1..9f9fc327b 100644 --- a/src/App/Controls/Danmaku/DanmakuDisplayOptions.xaml.cs +++ b/src/App/Controls/Danmaku/DanmakuDisplayOptions.xaml.cs @@ -1,10 +1,10 @@ // Copyright (c) Richasy. All rights reserved. -using Richasy.Bili.ViewModels.Uwp.Common; +using Bili.ViewModels.Interfaces.Common; using Windows.UI.Xaml; using Windows.UI.Xaml.Controls; -namespace Richasy.Bili.App.Controls +namespace Bili.App.Controls.Danmaku { /// /// 弹幕显示设置. @@ -15,22 +15,22 @@ public sealed partial class DanmakuDisplayOptions : UserControl /// 的视图模型. /// public static readonly DependencyProperty ViewModelProperty = - DependencyProperty.Register(nameof(ViewModel), typeof(DanmakuViewModel), typeof(DanmakuDisplayOptions), new PropertyMetadata(DanmakuViewModel.Instance)); + DependencyProperty.Register(nameof(ViewModel), typeof(IDanmakuModuleViewModel), typeof(DanmakuDisplayOptions), new PropertyMetadata(default)); /// /// Initializes a new instance of the class. /// public DanmakuDisplayOptions() { - this.InitializeComponent(); + InitializeComponent(); } /// /// 视图模型. /// - public DanmakuViewModel ViewModel + public IDanmakuModuleViewModel ViewModel { - get { return (DanmakuViewModel)GetValue(ViewModelProperty); } + get { return (IDanmakuModuleViewModel)GetValue(ViewModelProperty); } set { SetValue(ViewModelProperty, value); } } } diff --git a/src/App/Controls/Danmaku/DanmakuModel.cs b/src/App/Controls/Danmaku/DanmakuModel.cs deleted file mode 100644 index 7c44d158b..000000000 --- a/src/App/Controls/Danmaku/DanmakuModel.cs +++ /dev/null @@ -1,83 +0,0 @@ -// Copyright (c) Richasy. All rights reserved. - -using Richasy.Bili.Models.Enums.App; -using Windows.UI; -using Windows.UI.Xaml.Media; - -namespace Richasy.Bili.App.Controls -{ - /// - /// 弹幕模型. - /// - public class DanmakuModel - { - /// - /// 文本. - /// - public string Text { get; set; } - - /// - /// 弹幕大小. - /// - public double Size { get; set; } - - /// - /// 弹幕颜色. - /// - public Color Color { get; set; } - - /// - /// 弹幕出现时间. - /// - public int Time { get; set; } - - /// - /// 弹幕发送时间. - /// - public string SendTime { get; set; } - - /// - /// 弹幕池. - /// - public string Pool { get; set; } - - /// - /// 弹幕发送人ID. - /// - public string SendId { get; set; } - - /// - /// 弹幕ID. - /// - public string Id { get; set; } - - /// - /// 弹幕出现位置. - /// - public DanmakuLocation Location - { - get; set; - } - - /// - /// 来源. - /// - public string Source { get; set; } - - /// - /// 弹幕字重. - /// - public int Weight { get; set; } - - /// - /// 前景色. - /// - public SolidColorBrush Foreground - { - get - { - return new SolidColorBrush(Color); - } - } - } -} diff --git a/src/App/Controls/Danmaku/DanmakuSendOptions.xaml b/src/App/Controls/Danmaku/DanmakuSendOptions.xaml index 1e8bb4cbf..6153a41fa 100644 --- a/src/App/Controls/Danmaku/DanmakuSendOptions.xaml +++ b/src/App/Controls/Danmaku/DanmakuSendOptions.xaml @@ -1,15 +1,17 @@  - + + Click="OnSizeItemClick" + Content="{loc:Locale Name=Standard}" + IsChecked="{x:Bind ViewModel.IsStandardSize, Mode=OneWay}" /> + Click="OnSizeItemClick" + Content="{loc:Locale Name=Small}" + IsChecked="{x:Bind ViewModel.IsStandardSize, Mode=OneWay, Converter={StaticResource ObjectToBoolReverseConverter}}" /> @@ -45,7 +51,7 @@ - + @@ -79,7 +85,6 @@ Background="{Binding Value, Converter={StaticResource BrushConverter}}" BorderThickness="0" Click="OnColorItemClick" - IsEnableShadow="False" PointerOverBackground="{Binding Value, Converter={StaticResource BrushConverter}}" PressedBackground="{Binding Value, Converter={StaticResource BrushConverter}}" ToolTipService.ToolTip="{Binding Key}" /> diff --git a/src/App/Controls/Danmaku/DanmakuSendOptions.xaml.cs b/src/App/Controls/Danmaku/DanmakuSendOptions.xaml.cs index 23f11cddf..1ceddfe32 100644 --- a/src/App/Controls/Danmaku/DanmakuSendOptions.xaml.cs +++ b/src/App/Controls/Danmaku/DanmakuSendOptions.xaml.cs @@ -1,11 +1,11 @@ // Copyright (c) Richasy. All rights reserved. -using Richasy.Bili.Models.App.Other; -using Richasy.Bili.ViewModels.Uwp.Common; +using Bili.Models.Data.Local; +using Bili.ViewModels.Interfaces.Common; using Windows.UI.Xaml; using Windows.UI.Xaml.Controls; -namespace Richasy.Bili.App.Controls +namespace Bili.App.Controls.Danmaku { /// /// 弹幕发送设置. @@ -16,46 +16,30 @@ public sealed partial class DanmakuSendOptions : UserControl /// 的视图模型. /// public static readonly DependencyProperty ViewModelProperty = - DependencyProperty.Register(nameof(ViewModel), typeof(DanmakuViewModel), typeof(DanmakuSendOptions), new PropertyMetadata(DanmakuViewModel.Instance)); + DependencyProperty.Register(nameof(ViewModel), typeof(IDanmakuModuleViewModel), typeof(DanmakuSendOptions), new PropertyMetadata(default)); /// /// Initializes a new instance of the class. /// public DanmakuSendOptions() - { - this.InitializeComponent(); - } + => InitializeComponent(); /// /// 视图模型. /// - public DanmakuViewModel ViewModel + public IDanmakuModuleViewModel ViewModel { - get { return (DanmakuViewModel)GetValue(ViewModelProperty); } + get { return (IDanmakuModuleViewModel)GetValue(ViewModelProperty); } set { SetValue(ViewModelProperty, value); } } - /// - /// 初始化. - /// - public void Initialize() - { - if (ViewModel.IsStandardSize) - { - StandardItem.IsChecked = true; - SmallItem.IsChecked = false; - } - else - { - StandardItem.IsChecked = false; - SmallItem.IsChecked = true; - } - } - private void OnColorItemClick(object sender, RoutedEventArgs e) { var item = (sender as FrameworkElement).DataContext as KeyValue; ViewModel.Color = item.Value; } + + private void OnSizeItemClick(object sender, RoutedEventArgs e) + => ViewModel.IsStandardSize = !ViewModel.IsStandardSize; } } diff --git a/src/App/Controls/Danmaku/DanmakuView/DanmakuView.Methods.cs b/src/App/Controls/Danmaku/DanmakuView/DanmakuView.Methods.cs index b940bc3ce..63e88e355 100644 --- a/src/App/Controls/Danmaku/DanmakuView/DanmakuView.Methods.cs +++ b/src/App/Controls/Danmaku/DanmakuView/DanmakuView.Methods.cs @@ -2,16 +2,12 @@ using System; using System.Collections.Generic; -using System.Linq; -using Richasy.Bili.App.Resources.Extension; -using Richasy.Bili.Models.Enums.App; -using Windows.UI; +using System.Threading.Tasks; +using Atelier39; +using Bili.Models.Data.Live; using Windows.UI.Xaml; -using Windows.UI.Xaml.Controls; -using Windows.UI.Xaml.Media; -using Windows.UI.Xaml.Media.Animation; -namespace Richasy.Bili.App.Controls +namespace Bili.App.Controls.Danmaku { /// /// 弹幕视图. @@ -19,280 +15,70 @@ namespace Richasy.Bili.App.Controls public sealed partial class DanmakuView { /// - /// 添加直播滚动弹幕. + /// 发送弹幕. /// - /// 参数. - /// 是否自己发送的. - /// 颜色. - public async void AddLiveDanmakuAsync(string text, bool own, Color? color) + /// 弹幕列表. + /// 是否为添加弹幕. + public void Prepare(List items, bool isAdd = false) { - if (color == null) + if (isAdd) { - color = Colors.White; + _danmakuController?.AddDanmakuList(items); } - - var m = new DanmakuModel() - { - Text = text, - Color = color.Value, - Location = DanmakuLocation.Scroll, - Size = 25, - }; - - var grid = await CreateNewDanmuControlAsync(m); - if (own) - { - grid.BorderBrush = new SolidColorBrush(color.Value); - grid.BorderThickness = new Thickness(1); - } - - var r = GetScrollAvailableRow(grid); - if (r == -1) + else { - return; + _danmakuController?.SetDanmakuList(items); } - Grid.SetRow(grid, r); - grid.HorizontalAlignment = HorizontalAlignment.Left; - grid.VerticalAlignment = VerticalAlignment.Center; - _scrollContainer.Children.Add(grid); - _scrollContainer.UpdateLayout(); - - var moveTransform = new TranslateTransform(); - moveTransform.X = _rootGrid.ActualWidth; - grid.RenderTransform = moveTransform; - - // 创建动画 - var duration = new Duration(TimeSpan.FromSeconds(DanmakuDuration)); - var myDoubleAnimationX = new DoubleAnimation(); - myDoubleAnimationX.Duration = duration; - - // 创建故事版 - var moveStoryboard = new Storyboard(); - moveStoryboard.Duration = duration; - myDoubleAnimationX.To = -grid.ActualWidth; - moveStoryboard.Children.Add(myDoubleAnimationX); - Storyboard.SetTarget(myDoubleAnimationX, moveTransform); - - // 故事版加入动画 - Storyboard.SetTargetProperty(myDoubleAnimationX, "X"); - _scrollStoryList.Add(moveStoryboard); - - moveStoryboard.Completed += new EventHandler((senders, obj) => - { - _scrollContainer.Children.Remove(grid); - grid.Children.Clear(); - grid = null; - _scrollStoryList.Remove(moveStoryboard); - moveStoryboard.Stop(); - moveStoryboard = null; - }); - - moveStoryboard.Begin(); + _cachedDanmakus = items; } /// - /// 添加一条弹幕. + /// 发送弹幕. /// - /// 弹幕实例. - /// 是否是自己所发. - public void AddScreenDanmaku(DanmakuModel m, bool isOwn) - { - switch (m.Location) - { - case DanmakuLocation.Scroll: - case DanmakuLocation.Top: - case DanmakuLocation.Bottom: - AddDanmakuInternalAsync(m, isOwn); - break; - case DanmakuLocation.Position: - AddPositionDanmakuAsync(m); - break; - default: - AddDanmakuInternalAsync(m, isOwn); - break; - } - } + /// 弹幕条目. + public void SendDanmu(DanmakuItem item) + => _danmakuController?.AddRealtimeDanmaku(item, insertToList: false); /// - /// 添加定位弹幕. + /// 更新内部计时. /// - /// 参数. - public async void AddPositionDanmakuAsync(DanmakuModel m) + /// 毫秒时间戳. + public void UpdateTime(uint milliseconds) { - var data = Newtonsoft.Json.JsonConvert.DeserializeObject(m.Text); - m.Text = data[4].ToString().Replace("/n", "\r\n"); - var danmaku = await CreateNewDanmuControlAsync(m); - var danmakuFontFamily = data[data.Length - 2].ToString(); - - danmaku.Tag = m; - danmaku.HorizontalAlignment = HorizontalAlignment.Left; - danmaku.VerticalAlignment = VerticalAlignment.Center; - - double toX = 0; - double toY = 0; - - double x = 0, y = 0; - double dur = 0; - - if (data.Length > 7) - { - x = data[0].ToDouble(); - y = data[1].ToDouble(); - - toX = data[7].ToDouble(); - toY = data[8].ToDouble(); - - dur = data[10].ToDouble(); - } - else - { - toX = data[0].ToDouble(); - toY = data[1].ToDouble(); - } - - if (toX < 1 && toY < 1) - { - toX = this.ActualWidth * toX; - toY = this.ActualHeight * toY; - } - - if (x < 1 && y < 1) - { - x = this.ActualWidth * x; - y = this.ActualHeight * y; - } - - if (data.Length >= 7) - { - var rotateZ = data[5].ToDouble(); - var rotateY = data[6].ToDouble(); - var projection = new PlaneProjection(); - projection.RotationZ = -rotateZ; - projection.RotationY = rotateY; - danmaku.Projection = projection; - } - - _canvas.Children.Add(danmaku); - - var dmDuration = data[3].ToDouble(); - var opacitys = data[2].ToString().Split('-'); - var opacityFrom = opacitys[0].ToDouble(); - var opacityTo = opacitys[1].ToDouble(); - - // 创建故事版 - var moveStoryboard = new Storyboard(); - - var duration = new Duration(TimeSpan.FromMilliseconds(dur)); - - var myDoubleAnimationY = new DoubleAnimation(); - myDoubleAnimationY.Duration = duration; - myDoubleAnimationY.From = y; - myDoubleAnimationY.To = toY; - Storyboard.SetTarget(myDoubleAnimationY, danmaku); - Storyboard.SetTargetProperty(myDoubleAnimationY, "(Canvas.Top)"); - moveStoryboard.Children.Add(myDoubleAnimationY); - - var myDoubleAnimationX = new DoubleAnimation(); - myDoubleAnimationX.Duration = duration; - myDoubleAnimationX.From = x; - myDoubleAnimationX.To = toX; - Storyboard.SetTarget(myDoubleAnimationX, danmaku); - Storyboard.SetTargetProperty(myDoubleAnimationX, "(Canvas.Left)"); - moveStoryboard.Children.Add(myDoubleAnimationX); - - // 透明度动画 - var opacityAnimation = new DoubleAnimation() - { - Duration = new Duration(TimeSpan.FromSeconds(dmDuration)), - From = opacityFrom, - To = opacityTo, - }; - - Storyboard.SetTarget(opacityAnimation, danmaku); - Storyboard.SetTargetProperty(opacityAnimation, "Opacity"); - moveStoryboard.Children.Add(opacityAnimation); - - _positionStoryList.Add(moveStoryboard); - - moveStoryboard.Completed += new EventHandler((senders, obj) => - { - _canvas.Children.Remove(danmaku); - _positionStoryList.Remove(moveStoryboard); - }); - - moveStoryboard.Begin(); + _canvas?.UpdateLayout(); + _danmakuController?.UpdateTime(milliseconds); + _currentTs = milliseconds; } /// /// 暂停弹幕. /// - public void PauseDanmaku() - { - foreach (var item in _topBottomStoryList.Concat(_scrollStoryList).Concat(_positionStoryList)) - { - item.Pause(); - } - } + public void PauseDanmaku() => _danmakuController?.Pause(); /// /// 继续弹幕. /// - public void ResumeDanmaku() - { - foreach (var item in _topBottomStoryList.Concat(_scrollStoryList).Concat(_positionStoryList)) - { - item.Resume(); - } - } + public void ResumeDanmaku() => _danmakuController?.Resume(); /// - /// 移除指定弹幕. + /// 重新绘制弹幕区域. /// - /// 参数. - public void Remove(DanmakuModel danmaku) + /// . + public async Task RedrawAsync() { - if (!_isApplyTemplate) + await Dispatcher.RunAsync(Windows.UI.Core.CoreDispatcherPriority.Normal, () => { - return; - } - - switch (danmaku.Location) - { - case DanmakuLocation.Top: - - foreach (var item in _topContainer.Children) - { - if ((((FrameworkElement)item).Tag as DanmakuModel) == danmaku) - { - _topContainer.Children.Remove(item); - } - } - - break; - case DanmakuLocation.Bottom: - foreach (var item in _bottomContainer.Children) - { - if ((((FrameworkElement)item).Tag as DanmakuModel) == danmaku) - { - _bottomContainer.Children.Remove(item); - } - } + _danmakuController?.Close(); - break; - case DanmakuLocation.Other: - foreach (var item in _scrollContainer.Children) - { - if ((((FrameworkElement)item).Tag as DanmakuModel) == danmaku) - { - _scrollContainer.Children.Remove(item); - } - } - - break; - default: - break; - } + InitializeController(); + if (_cachedDanmakus?.Count > 0) + { + Prepare(_cachedDanmakus); + _danmakuController?.Resume(); + _danmakuController?.Seek(_currentTs); + } + }); } /// @@ -300,110 +86,57 @@ public void Remove(DanmakuModel danmaku) /// public void ClearAll() { - if (!_isApplyTemplate) - { - return; - } - - _topBottomStoryList.Clear(); - _scrollStoryList.Clear(); - _bottomContainer.Children.Clear(); - _topContainer.Children.Clear(); - _scrollContainer.Children.Clear(); + _cachedDanmakus?.Clear(); + _currentTs = 0; + _danmakuController?.Clear(); } - /// - /// 读取屏幕上的全部弹幕. - /// - /// 类型. - /// 弹幕列表. - public List GetDanmakus(DanmakuLocation? danmakuLocation = null) + private static void OnProgressPositionChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) { - if (!_isApplyTemplate) - { - return null; - } - - var danmakus = new List(); - if (danmakuLocation == null || danmakuLocation == DanmakuLocation.Top) - { - foreach (Grid item in _topContainer.Children) - { - danmakus.Add(item.Tag as DanmakuModel); - } - } - - if (danmakuLocation == null || danmakuLocation == DanmakuLocation.Bottom) - { - foreach (Grid item in _bottomContainer.Children) - { - danmakus.Add(item.Tag as DanmakuModel); - } - } - - if (danmakuLocation == null || danmakuLocation == DanmakuLocation.Scroll) - { - foreach (Grid item in _scrollContainer.Children) - { - danmakus.Add(item.Tag as DanmakuModel); - } - } - - return danmakus; + var instance = d as DanmakuView; + var sec = (double)e.NewValue; + instance.UpdateTime(Convert.ToUInt32(sec * 1000)); } /// - /// 隐藏弹幕. + /// 关闭控制器. /// - /// 需要隐藏的位置. - public void HideDanmaku(DanmakuLocation location) + private void Close() + => _danmakuController?.Close(); + + private void OnLiveDanmakuAdded(object sender, LiveDanmakuInformation e) { - if (!_isApplyTemplate) + if (!ViewModel.IsShowDanmaku) { return; } - switch (location) + var model = new DanmakuItem { - case DanmakuLocation.Scroll: - _scrollContainer.Visibility = Visibility.Collapsed; - break; - case DanmakuLocation.Top: - _topContainer.Visibility = Visibility.Collapsed; - break; - case DanmakuLocation.Bottom: - _bottomContainer.Visibility = Visibility.Collapsed; - break; - default: - break; - } + StartMs = 0, + Mode = DanmakuMode.Rolling, + TextColor = Microsoft.Toolkit.Uwp.Helpers.ColorHelper.ToColor(e.TextColor ?? "#FFFFFF"), + BaseFontSize = ViewModel.IsStandardSize ? 20 : 24, + Text = e.Text, + HasOutline = true, + }; + + SendDanmu(model); } - /// - /// 显示弹幕. - /// - /// 需要显示的位置. - public void ShowDanmaku(DanmakuLocation location) + private void OnSendDanmakuSucceeded(object sender, string e) { - if (!_isApplyTemplate) - { - return; - } + var model = new DanmakuItem + { + StartMs = _currentTs, + Mode = (DanmakuMode)((int)ViewModel.Location), + TextColor = Microsoft.Toolkit.Uwp.Helpers.ColorHelper.ToColor(ViewModel.Color), + BaseFontSize = ViewModel.IsStandardSize ? 20 : 24, + Text = e, + HasOutline = true, + }; - switch (location) - { - case DanmakuLocation.Scroll: - _scrollContainer.Visibility = Visibility.Visible; - break; - case DanmakuLocation.Top: - _topContainer.Visibility = Visibility.Visible; - break; - case DanmakuLocation.Bottom: - _bottomContainer.Visibility = Visibility.Visible; - break; - default: - break; - } + SendDanmu(model); } } } diff --git a/src/App/Controls/Danmaku/DanmakuView/DanmakuView.Properties.cs b/src/App/Controls/Danmaku/DanmakuView/DanmakuView.Properties.cs index 8e3c8513a..33b7af376 100644 --- a/src/App/Controls/Danmaku/DanmakuView/DanmakuView.Properties.cs +++ b/src/App/Controls/Danmaku/DanmakuView/DanmakuView.Properties.cs @@ -1,12 +1,12 @@ // Copyright (c) Richasy. All rights reserved. using System.Collections.Generic; -using Richasy.Bili.Models.Enums.App; +using Atelier39; +using Microsoft.Graphics.Canvas.UI.Xaml; using Windows.UI.Xaml; using Windows.UI.Xaml.Controls; -using Windows.UI.Xaml.Media.Animation; -namespace Richasy.Bili.App.Controls +namespace Bili.App.Controls.Danmaku { /// /// 弹幕视图. @@ -14,111 +14,27 @@ namespace Richasy.Bili.App.Controls public sealed partial class DanmakuView { /// - /// 的依赖属性. + /// 的依赖属性. /// - public static readonly DependencyProperty DanmakuSizeZoomProperty = - DependencyProperty.Register(nameof(DanmakuSizeZoom), typeof(double), typeof(DanmakuView), new PropertyMetadata(1.0, OnDanmakuSizeZoomChanged)); - - /// - /// 的依赖属性. - /// - public static readonly DependencyProperty DanmakuDurationProperty = - DependencyProperty.Register(nameof(DanmakuDuration), typeof(int), typeof(DanmakuView), new PropertyMetadata(10, OnDanmakuDurationChanged)); - - /// - /// 的依赖属性. - /// - public static readonly DependencyProperty DanmakuBoldProperty = - DependencyProperty.Register(nameof(DanmakuBold), typeof(bool), typeof(DanmakuView), new PropertyMetadata(false)); - - /// - /// 的依赖属性. - /// - public static readonly DependencyProperty DanmakuFontFamilyProperty = - DependencyProperty.Register(nameof(DanmakuFontFamily), typeof(string), typeof(DanmakuView), new PropertyMetadata(default)); - - /// - /// 的依赖属性. - /// - public static readonly DependencyProperty DanmakuStyleProperty = - DependencyProperty.Register(nameof(DanmakuStyle), typeof(DanmakuStyle), typeof(DanmakuView), new PropertyMetadata(DanmakuStyle.Stroke)); - - /// - /// 的依赖属性. - /// - public static readonly DependencyProperty DanmakuAreaProperty = - DependencyProperty.Register(nameof(DanmakuArea), typeof(double), typeof(DanmakuView), new PropertyMetadata(1, OnDanmakuAreaChanged)); + public static readonly DependencyProperty ProgressPositionProperty = + DependencyProperty.Register(nameof(ProgressPosition), typeof(double), typeof(DanmakuView), new PropertyMetadata(0d, new PropertyChangedCallback(OnProgressPositionChanged))); private const string RootGridName = "RootGrid"; - private const string CanvasName = "Canvas"; - private const string ScrollContainerName = "ScrollContainer"; - private const string TopContainerName = "TopContainer"; - private const string BottomContainerName = "BottomContainer"; - - private readonly List _topBottomStoryList = new List(); - private readonly List _scrollStoryList = new List(); - private readonly List _positionStoryList = new List(); private Grid _rootGrid; - private Canvas _canvas; - private Grid _topContainer; - private Grid _bottomContainer; - private Grid _scrollContainer; - - private bool _isApplyTemplate; - - /// - /// 字体大小缩放,电脑推荐默认1.0,手机推荐0.5. - /// - public double DanmakuSizeZoom - { - get { return (double)GetValue(DanmakuSizeZoomProperty); } - set { SetValue(DanmakuSizeZoomProperty, value); } - } - - /// - /// 滚动弹幕动画持续时间,单位:秒,越小弹幕移动速度越快. - /// - public int DanmakuDuration - { - get { return (int)GetValue(DanmakuDurationProperty); } - set { SetValue(DanmakuDurationProperty, value); } - } - - /// - /// 弹幕是否加粗. - /// - public bool DanmakuBold - { - get { return (bool)GetValue(DanmakuBoldProperty); } - set { SetValue(DanmakuBoldProperty, value); } - } - - /// - /// 弹幕字体名称. - /// - public string DanmakuFontFamily - { - get { return (string)GetValue(DanmakuFontFamilyProperty); } - set { SetValue(DanmakuFontFamilyProperty, value); } - } - - /// - /// 弹幕样式. - /// - public DanmakuStyle DanmakuStyle - { - get { return (DanmakuStyle)GetValue(DanmakuStyleProperty); } - set { SetValue(DanmakuStyleProperty, value); } - } + private CanvasAnimatedControl _canvas; + private DanmakuFrostMaster _danmakuController; + private List _cachedDanmakus; + private uint _currentTs; + private bool _isInitialized; /// - /// 弹幕显示区域,取值0.1-1.0. + /// 播放进度位置. /// - public double DanmakuArea + public double ProgressPosition { - get { return (double)GetValue(DanmakuAreaProperty); } - set { SetValue(DanmakuAreaProperty, value); } + get { return (double)GetValue(ProgressPositionProperty); } + set { SetValue(ProgressPositionProperty, value); } } } } diff --git a/src/App/Controls/Danmaku/DanmakuView/DanmakuView.cs b/src/App/Controls/Danmaku/DanmakuView/DanmakuView.cs index 3861da2db..ae350ae59 100644 --- a/src/App/Controls/Danmaku/DanmakuView/DanmakuView.cs +++ b/src/App/Controls/Danmaku/DanmakuView/DanmakuView.cs @@ -1,23 +1,21 @@ // Copyright (c) Richasy. All rights reserved. using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; -using Richasy.Bili.App.Resources.Extension; -using Richasy.Bili.Models.Enums.App; -using Windows.Foundation; +using System.ComponentModel; +using System.Diagnostics; +using Atelier39; +using Bili.ViewModels.Interfaces.Common; +using Microsoft.Graphics.Canvas.UI.Xaml; +using Windows.UI; using Windows.UI.Xaml; using Windows.UI.Xaml.Controls; -using Windows.UI.Xaml.Media; -using Windows.UI.Xaml.Media.Animation; -namespace Richasy.Bili.App.Controls +namespace Bili.App.Controls.Danmaku { /// /// 弹幕控件. /// - public sealed partial class DanmakuView : Control + public sealed partial class DanmakuView : ReactiveControl { /// /// Initializes a new instance of the class. @@ -25,356 +23,144 @@ public sealed partial class DanmakuView : Control public DanmakuView() { DefaultStyleKey = typeof(DanmakuView); - DanmakuArea = 1.0; - DanmakuBold = false; - DanmakuFontFamily = string.Empty; + Loaded += OnLoadedAsync; + Unloaded += OnUnloaded; } - /// - protected override void OnApplyTemplate() - { - _rootGrid = GetTemplateChild(RootGridName) as Grid; - _scrollContainer = GetTemplateChild(ScrollContainerName) as Grid; - _topContainer = GetTemplateChild(TopContainerName) as Grid; - _bottomContainer = GetTemplateChild(BottomContainerName) as Grid; - _canvas = GetTemplateChild(CanvasName) as Canvas; - _isApplyTemplate = true; - } - - /// - protected override Size MeasureOverride(Size availableSize) - { - SetRows(availableSize.Height); - return base.MeasureOverride(availableSize); - } - - private static void OnDanmakuSizeZoomChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) + internal override async void OnViewModelChanged(DependencyPropertyChangedEventArgs e) { - var value = Convert.ToDouble(e.NewValue); - if (value > 3) + if (e.NewValue is IDanmakuModuleViewModel viewModel) { - value = 3; + viewModel.PropertyChanged += OnViewModelProeprtyChangedAsync; + viewModel.LiveDanmakuAdded += OnLiveDanmakuAdded; + viewModel.SendDanmakuSucceeded += OnSendDanmakuSucceeded; + await RedrawAsync(); } - - if (value < 0.1) - { - value = 0.1; - } - - ((DanmakuView)d).SetDanmakuSizeZoom(value); } - private static void OnDanmakuDurationChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) + /// + protected override async void OnApplyTemplate() { - var value = Convert.ToInt32(e.NewValue); - if (value <= 0) + _rootGrid = GetTemplateChild(RootGridName) as Grid; + if (!_isInitialized) { - value = 1; - ((DanmakuView)d).DanmakuDuration = value; + await RedrawAsync(); } } - private static void OnDanmakuAreaChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) + private DanmakuFontSize GetFontSize(double fontSize) { - var value = Convert.ToDouble(e.NewValue); - if (value <= 0) - { - value = 0.1; - } - - if (value > 1) + return fontSize switch { - value = 1; - } - - ((DanmakuView)d).DanmakuArea = value; + 0.5 => DanmakuFontSize.Smallest, + 1 => DanmakuFontSize.Smaller, + 2.0 => DanmakuFontSize.Larger, + 2.5 => DanmakuFontSize.Largest, + _ => DanmakuFontSize.Normal, + }; } - private async void AddDanmakuInternalAsync(DanmakuModel m, bool isOwn) + private void InitializeController() { - if (!_isApplyTemplate) - { - return; - } - - var danmaku = await CreateNewDanmuControlAsync(m); - - if (isOwn) - { - danmaku.BorderBrush = new SolidColorBrush(m.Color); - danmaku.BorderThickness = new Thickness(1); - danmaku.CornerRadius = new CornerRadius(2); - } - - var rowNumber = -1; - Grid container = null; - List locationStoryList = null; - - switch (m.Location) + if (_rootGrid == null) { - case DanmakuLocation.Top: - rowNumber = GetTopAvailableRow(); - container = _topContainer; - locationStoryList = _topBottomStoryList; - break; - case DanmakuLocation.Bottom: - rowNumber = GetBottomAvailableRow(); - container = _bottomContainer; - locationStoryList = _topBottomStoryList; - break; - default: - rowNumber = GetScrollAvailableRow(danmaku); - container = _scrollContainer; - locationStoryList = _scrollStoryList; - break; - } - - if (rowNumber == -1) - { - danmaku = null; return; } - danmaku.HorizontalAlignment = m.Location == DanmakuLocation.Scroll ? - HorizontalAlignment.Left : HorizontalAlignment.Center; - danmaku.VerticalAlignment = m.Location == DanmakuLocation.Scroll ? - VerticalAlignment.Center : VerticalAlignment.Top; - container.Children.Add(danmaku); - Grid.SetRow(danmaku, rowNumber); + _rootGrid.Children.Clear(); + _canvas = new CanvasAnimatedControl(); + _rootGrid.Children.Add(_canvas); - var moveTransform = new TranslateTransform(); - var ts = TimeSpan.Zero; - if (m.Location == DanmakuLocation.Scroll) + if (_canvas != null && ViewModel != null) { - moveTransform.X = _rootGrid.ActualWidth; - danmaku.RenderTransform = moveTransform; - ts = TimeSpan.FromSeconds(DanmakuDuration); + _danmakuController = new DanmakuFrostMaster(_canvas); + _danmakuController.SetAutoControlDensity(ViewModel.IsDanmakuLimit); + _danmakuController.SetRollingDensity(-1); + _danmakuController.SetOpacity(ViewModel.DanmakuOpacity); + _danmakuController.SetBorderColor(Colors.Gray); + _danmakuController.SetRollingAreaRatio(Convert.ToInt32(ViewModel.DanmakuArea * 10)); + _danmakuController.SetDanmakuFontSizeOffset(GetFontSize(ViewModel.DanmakuFontSize)); + _danmakuController.SetFontFamilyName(ViewModel.DanmakuFont ?? "Segoe UI"); + _danmakuController.SetRollingSpeed(Convert.ToInt32(ViewModel.DanmakuSpeed * 5)); + _danmakuController.SetIsTextBold(ViewModel.IsDanmakuBold); + _danmakuController.SetRenderState(true, false); + _isInitialized = true; } else { - ts = TimeSpan.FromSeconds(5); - } - - // 创建动画 - var duration = new Duration(ts); - var danmakuAnimationX = new DoubleAnimation(); - danmakuAnimationX.Duration = duration; - - // 创建故事版 - var moveStoryboard = new Storyboard(); - moveStoryboard.Duration = duration; - - if (m.Location == DanmakuLocation.Scroll) - { - danmaku.Measure(_rootGrid.DesiredSize); - danmakuAnimationX.To = -danmaku.ActualWidth - danmaku.DesiredSize.Width - 40; + Debug.WriteLine("初始化失败"); } - - moveStoryboard.Children.Add(danmakuAnimationX); - Storyboard.SetTarget(danmakuAnimationX, moveTransform); - - // 故事版加入动画 - Storyboard.SetTargetProperty(danmakuAnimationX, "X"); - locationStoryList.Add(moveStoryboard); - - moveStoryboard.Completed += new EventHandler((senders, obj) => - { - container.Children.Remove(danmaku); - danmaku.Children.Clear(); - danmaku = null; - locationStoryList.Remove(moveStoryboard); - moveStoryboard.Stop(); - moveStoryboard = null; - }); - - moveStoryboard.Begin(); } - private void SetDanmakuSizeZoom(double value) + private async void OnLoadedAsync(object sender, RoutedEventArgs e) { - if (!_isApplyTemplate) - { - return; - } - - SetRows(this.ActualHeight); - foreach (var item in _scrollContainer.Children) - { - SetItemZoom(item); - } - - foreach (var item in _topContainer.Children) - { - SetItemZoom(item); - } - - foreach (var item in _bottomContainer.Children) + if (!_isInitialized) { - SetItemZoom(item); + await RedrawAsync(); } } - private void SetItemZoom(UIElement element) + private void OnUnloaded(object sender, RoutedEventArgs e) { - if (element is Grid grid) - { - var model = grid.Tag as DanmakuModel; - var textBlock = grid.FindDescendantElementByType(); - if (textBlock != null) - { - textBlock.FontSize = Convert.ToInt32(model.Size) * DanmakuSizeZoom; - } - } + Close(); + ViewModel.PropertyChanged -= OnViewModelProeprtyChangedAsync; + ViewModel.LiveDanmakuAdded -= OnLiveDanmakuAdded; + ViewModel.SendDanmakuSucceeded -= OnSendDanmakuSucceeded; } - private void SetRows(double height) + private async void OnViewModelProeprtyChangedAsync(object sender, PropertyChangedEventArgs e) { - if (!_isApplyTemplate) - { - return; - } - - var txt = new TextBlock() - { - Text = "测试test", - FontSize = 25 * DanmakuSizeZoom, - }; - txt.Measure(new Size(double.PositiveInfinity, double.PositiveInfinity)); - var rowHieght = txt.DesiredSize.Height; - - // 将全部行去除 - _topContainer.RowDefinitions.Clear(); - _bottomContainer.RowDefinitions.Clear(); - _scrollContainer.RowDefinitions.Clear(); - - if (double.IsInfinity(height)) + if (e.PropertyName == nameof(ViewModel.DanmakuSpeed)) { - return; + var speed = (double)ViewModel.DanmakuSpeed * 5; + _danmakuController?.SetRollingSpeed(Convert.ToInt32(speed)); } - - var num = Convert.ToInt32(height / rowHieght); - for (var i = 0; i < num; i++) - { - _bottomContainer.RowDefinitions.Add(new RowDefinition()); - _topContainer.RowDefinitions.Add(new RowDefinition()); - _scrollContainer.RowDefinitions.Add(new RowDefinition()); - } - } - - private int GetTopAvailableRow() - { - if (!_isApplyTemplate) + else if (e.PropertyName == nameof(ViewModel.DanmakuOpacity)) { - return 0; + _danmakuController?.SetOpacity(ViewModel.DanmakuOpacity); } - - var max = _topContainer.RowDefinitions.Count / 2; - - for (var i = 0; i < max; i++) + else if (e.PropertyName == nameof(ViewModel.DanmakuArea)) { - var row = _topContainer.Children.FirstOrDefault(x => Grid.GetRow(x as Grid) == i); - if (row != null) + var value = ViewModel.DanmakuArea; + if (value <= 0) { - continue; + value = 0.1; } - else + else if (value > 1) { - return i; + value = 1; } - } - - return -1; - } - private int GetBottomAvailableRow() - { - var max = _bottomContainer.RowDefinitions.Count / 2; - for (var i = 1; i <= max; i++) + _danmakuController?.SetRollingAreaRatio(Convert.ToInt32(value * 10)); + } + else if (e.PropertyName == nameof(ViewModel.DanmakuFontSize)) { - var rowNum = _bottomContainer.RowDefinitions.Count - i; - var row = _bottomContainer.Children.FirstOrDefault(x => Grid.GetRow(x as Grid) == rowNum); - if (row != null) - { - continue; - } - else - { - return rowNum; - } + _danmakuController?.SetDanmakuFontSizeOffset(GetFontSize(ViewModel.DanmakuFontSize)); } - - return -1; - } - - private int GetScrollAvailableRow(Grid item) - { - var width = _scrollContainer.ActualWidth; - - // 计算弹幕尺寸 - item.Measure(new Size(double.PositiveInfinity, double.PositiveInfinity)); - var newWidth = item.DesiredSize.Width; - if (newWidth <= 0) + else if (e.PropertyName == nameof(ViewModel.DanmakuFont)) { - return -1; + _danmakuController?.SetFontFamilyName(ViewModel.DanmakuFont?.ToString() ?? "Segoe UI"); } - - var max = _scrollContainer.RowDefinitions.Count * DanmakuArea; - - for (var i = 0; i < max; i++) + else if (e.PropertyName == nameof(ViewModel.IsDanmakuBold)) { - // 1、检查当前行是否存在弹幕 - var lastItem = _scrollContainer.Children.LastOrDefault(x => Grid.GetRow(x as Grid) == i); - if (lastItem == null) - { - return i; - } - - var lastWidth = (lastItem as Grid).ActualWidth; - var lastX = (lastItem.RenderTransform as TranslateTransform).X; - - // 2、前弹幕必须已经完全从右侧移动完毕 - if (lastX > width - lastWidth) - { - continue; - } - - // 3、后弹幕速度小于等于前弹幕速度 - var lastSpeed = (lastWidth + width) / DanmakuDuration; - var newSpeed = (newWidth + width) / DanmakuDuration; - if (newSpeed <= lastSpeed) + _danmakuController?.SetIsTextBold(ViewModel.IsDanmakuBold); + } + else if (e.PropertyName == nameof(ViewModel.IsDanmakuLimit)) + { + _danmakuController?.SetAutoControlDensity(ViewModel.IsDanmakuLimit); + } + else if (e.PropertyName == nameof(ViewModel.IsShowDanmaku)) + { + if (!ViewModel.IsShowDanmaku) { - return i; + ViewModel.ResetCommand.Execute(null); + ClearAll(); } - - // 4、弹幕移动期间不会重叠 - var runDistance = width - lastX; - var t1 = (runDistance - newWidth) / (newSpeed - lastSpeed); - var t2 = lastX / lastSpeed; - if (t1 > t2) + else { - return i; + await RedrawAsync(); } } - - return -1; - } - - private async Task CreateNewDanmuControlAsync(DanmakuModel m) - { - var builder = new DanmakuBuilder() - .WithSizeZoom(DanmakuSizeZoom) - .WithBold(DanmakuBold) - .WithFontFamily(DanmakuFontFamily) - .WithDanmakuModel(m); - switch (DanmakuStyle) - { - case DanmakuStyle.NoStroke: - return builder.CreateNoStrokeDanmaku(); - case DanmakuStyle.Shadow: - return builder.CreateShadowDanmaku(); - default: - return await builder.CreateStrokeDanmakuAsync(); - } } } } diff --git a/src/App/Controls/Danmaku/DanmakuView/DanmakuView.xaml b/src/App/Controls/Danmaku/DanmakuView/DanmakuView.xaml index 4eaa23e44..d6b3c51de 100644 --- a/src/App/Controls/Danmaku/DanmakuView/DanmakuView.xaml +++ b/src/App/Controls/Danmaku/DanmakuView/DanmakuView.xaml @@ -1,20 +1,17 @@  + xmlns:local="using:Bili.App.Controls.Danmaku"> - + + + + + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/src/App/Controls/Dynamic/DynamicPgcItem.xaml.cs b/src/App/Controls/Dynamic/DynamicPgcItem.xaml.cs deleted file mode 100644 index d5ab7650c..000000000 --- a/src/App/Controls/Dynamic/DynamicPgcItem.xaml.cs +++ /dev/null @@ -1,76 +0,0 @@ -// Copyright (c) Richasy. All rights reserved. - -using Richasy.Bili.ViewModels.Uwp; -using Windows.UI.Xaml; -using Windows.UI.Xaml.Controls; - -namespace Richasy.Bili.App.Controls -{ - /// - /// 动态PGC条目. - /// - public sealed partial class DynamicPgcItem : UserControl, IDynamicLayoutItem - { - /// - /// 的依赖属性. - /// - public static readonly DependencyProperty ViewModelProperty = - DependencyProperty.Register(nameof(ViewModel), typeof(SeasonViewModel), typeof(DynamicPgcItem), new PropertyMetadata(null)); - - /// - /// 的依赖属性. - /// - public static readonly DependencyProperty OrientationProperty = - DependencyProperty.Register(nameof(Orientation), typeof(Orientation), typeof(DynamicPgcItem), new PropertyMetadata(default(Orientation), new PropertyChangedCallback(OnOrientationChanged))); - - /// - /// Initializes a new instance of the class. - /// - public DynamicPgcItem() - { - this.InitializeComponent(); - this.Loaded += OnLoaded; - } - - /// - /// 剧集视图模型. - /// - public SeasonViewModel ViewModel - { - get { return (SeasonViewModel)GetValue(ViewModelProperty); } - set { SetValue(ViewModelProperty, value); } - } - - /// - /// 视频的布局方式. - /// - public Orientation Orientation - { - get { return (Orientation)GetValue(OrientationProperty); } - set { SetValue(OrientationProperty, value); } - } - - private static void OnOrientationChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) - { - var instance = d as DynamicPgcItem; - instance.CheckOrientation(); - } - - private void OnLoaded(object sender, RoutedEventArgs e) => CheckOrientation(); - - private void CheckOrientation() - { - switch (Orientation) - { - case Orientation.Vertical: - VisualStateManager.GoToState(this, nameof(VerticalState), false); - break; - case Orientation.Horizontal: - VisualStateManager.GoToState(this, nameof(HorizontalState), false); - break; - default: - break; - } - } - } -} diff --git a/src/App/Controls/Dynamic/DynamicPresenter.xaml b/src/App/Controls/Dynamic/DynamicPresenter.xaml new file mode 100644 index 000000000..6f97db91d --- /dev/null +++ b/src/App/Controls/Dynamic/DynamicPresenter.xaml @@ -0,0 +1,50 @@ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/App/Controls/Dynamic/DynamicPresenter.xaml.cs b/src/App/Controls/Dynamic/DynamicPresenter.xaml.cs new file mode 100644 index 000000000..0a3cf9cc9 --- /dev/null +++ b/src/App/Controls/Dynamic/DynamicPresenter.xaml.cs @@ -0,0 +1,102 @@ +// Copyright (c) Richasy. All rights reserved. + +using System.Collections.Generic; +using Bili.DI.Container; +using Bili.Models.Data.Article; +using Bili.Models.Data.Dynamic; +using Bili.Models.Data.Pgc; +using Bili.Models.Data.Video; +using Bili.ViewModels.Interfaces.Article; +using Bili.ViewModels.Interfaces.Community; +using Bili.ViewModels.Interfaces.Pgc; +using Bili.ViewModels.Interfaces.Video; +using Windows.UI.Xaml; +using Windows.UI.Xaml.Controls; +using Windows.UI.Xaml.Media; + +namespace Bili.App.Controls.Dynamic +{ + /// + /// 动态展示. + /// + public sealed partial class DynamicPresenter : UserControl, IOrientationControl + { + /// + /// 的依赖属性. + /// + public static readonly DependencyProperty DataProperty = + DependencyProperty.Register(nameof(Data), typeof(object), typeof(DynamicPresenter), new PropertyMetadata(default, new PropertyChangedCallback(DataChanged))); + + /// + /// Initializes a new instance of the class. + /// + public DynamicPresenter() => InitializeComponent(); + + /// + /// 数据. + /// + public object Data + { + get { return (object)GetValue(DataProperty); } + set { SetValue(DataProperty, value); } + } + + /// + public void ChangeLayout(Orientation orientation) + { + if (VisualTreeHelper.GetChildrenCount(MainPresenter) > 0) + { + var child = VisualTreeHelper.GetChild(MainPresenter, 0); + if (child is IOrientationControl item) + { + item.ChangeLayout(orientation); + } + } + } + + private static void DataChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) + { + var instance = d as DynamicPresenter; + var data = e.NewValue; + + if (data is VideoInformation videoInfo) + { + var videoVM = Locator.Instance.GetService(); + videoVM.InjectData(videoInfo); + instance.MainPresenter.Content = videoVM; + instance.MainPresenter.ContentTemplate = instance.VideoTemplate; + } + else if (data is EpisodeInformation episodeInfo) + { + var episodeVM = Locator.Instance.GetService(); + episodeVM.InjectData(episodeInfo); + instance.MainPresenter.Content = episodeVM; + instance.MainPresenter.ContentTemplate = instance.EpisodeTemplate; + } + else if (data is DynamicInformation dynamicInfo) + { + var dynamicVM = Locator.Instance.GetService(); + dynamicVM.InjectData(dynamicInfo); + instance.MainPresenter.Content = dynamicVM; + instance.MainPresenter.ContentTemplate = instance.ForwardTemplate; + } + else if (data is List images) + { + instance.MainPresenter.Content = images; + instance.MainPresenter.ContentTemplate = instance.ImageTemplate; + } + else if (data is ArticleInformation article) + { + var articleVM = Locator.Instance.GetService(); + articleVM.InjectData(article); + instance.MainPresenter.Content = articleVM; + instance.MainPresenter.ContentTemplate = instance.ArticleTemplate; + } + else + { + instance.MainPresenter.Content = null; + instance.MainPresenter.ContentTemplate = instance.NoneTemplate; + } + } + } +} diff --git a/src/App/Controls/Dynamic/DynamicVideoItem.xaml b/src/App/Controls/Dynamic/DynamicVideoItem.xaml deleted file mode 100644 index f038c09bf..000000000 --- a/src/App/Controls/Dynamic/DynamicVideoItem.xaml +++ /dev/null @@ -1,138 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/src/App/Controls/Dynamic/DynamicVideoItem.xaml.cs b/src/App/Controls/Dynamic/DynamicVideoItem.xaml.cs deleted file mode 100644 index f19384d99..000000000 --- a/src/App/Controls/Dynamic/DynamicVideoItem.xaml.cs +++ /dev/null @@ -1,76 +0,0 @@ -// Copyright (c) Richasy. All rights reserved. - -using Richasy.Bili.ViewModels.Uwp; -using Windows.UI.Xaml; -using Windows.UI.Xaml.Controls; - -namespace Richasy.Bili.App.Controls -{ - /// - /// 动态视频条目. - /// - public sealed partial class DynamicVideoItem : UserControl, IDynamicLayoutItem - { - /// - /// 的依赖属性. - /// - public static readonly DependencyProperty ViewModelProperty = - DependencyProperty.Register(nameof(ViewModel), typeof(VideoViewModel), typeof(DynamicVideoItem), new PropertyMetadata(null)); - - /// - /// 的依赖属性. - /// - public static readonly DependencyProperty OrientationProperty = - DependencyProperty.Register(nameof(Orientation), typeof(Orientation), typeof(DynamicVideoItem), new PropertyMetadata(default(Orientation), new PropertyChangedCallback(OnOrientationChanged))); - - /// - /// Initializes a new instance of the class. - /// - public DynamicVideoItem() - { - this.InitializeComponent(); - this.Loaded += OnLoaded; - } - - /// - /// 视频视图模型. - /// - public VideoViewModel ViewModel - { - get { return (VideoViewModel)GetValue(ViewModelProperty); } - set { SetValue(ViewModelProperty, value); } - } - - /// - /// 视频的布局方式. - /// - public Orientation Orientation - { - get { return (Orientation)GetValue(OrientationProperty); } - set { SetValue(OrientationProperty, value); } - } - - private static void OnOrientationChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) - { - var instance = d as DynamicVideoItem; - instance.CheckOrientation(); - } - - private void OnLoaded(object sender, RoutedEventArgs e) => CheckOrientation(); - - private void CheckOrientation() - { - switch (Orientation) - { - case Orientation.Vertical: - VisualStateManager.GoToState(this, nameof(VerticalState), false); - break; - case Orientation.Horizontal: - VisualStateManager.GoToState(this, nameof(HorizontalState), false); - break; - default: - break; - } - } - } -} diff --git a/src/App/Controls/Favorite/ArticleFavoritePanel.xaml b/src/App/Controls/Favorite/ArticleFavoritePanel.xaml index 099ecc5e4..fb6eb5fda 100644 --- a/src/App/Controls/Favorite/ArticleFavoritePanel.xaml +++ b/src/App/Controls/Favorite/ArticleFavoritePanel.xaml @@ -1,18 +1,25 @@ - + + + + @@ -26,50 +33,53 @@ - + - + - - - - - - - - - - - - - - - + ItemsSource="{x:Bind ViewModel.Items, Mode=OneWay}" + MinWideItemHeight="240"> + + + + + + + + + + + + + + + + - - + + - - + - + Visibility="{x:Bind ViewModel.IsError, Mode=OneWay}"> + + + + + - + diff --git a/src/App/Controls/Favorite/ArticleFavoritePanel.xaml.cs b/src/App/Controls/Favorite/ArticleFavoritePanel.xaml.cs index 20f1b9b42..631339f4c 100644 --- a/src/App/Controls/Favorite/ArticleFavoritePanel.xaml.cs +++ b/src/App/Controls/Favorite/ArticleFavoritePanel.xaml.cs @@ -1,58 +1,37 @@ // Copyright (c) Richasy. All rights reserved. -using Richasy.Bili.ViewModels.Uwp; -using Windows.UI.Xaml; -using Windows.UI.Xaml.Controls; +using Bili.DI.Container; +using Bili.ViewModels.Interfaces.Article; +using Bili.ViewModels.Interfaces.Core; -namespace Richasy.Bili.App.Controls +namespace Bili.App.Controls.Favorite { /// /// 文章收藏夹. /// - public sealed partial class ArticleFavoritePanel : UserControl + public sealed partial class ArticleFavoritePanel : ArticleFavoritePanelBase { - /// - /// 的依赖属性. - /// - public static readonly DependencyProperty ViewModelProperty = - DependencyProperty.Register(nameof(ViewModel), typeof(FavoriteArticleViewModel), typeof(ArticleFavoritePanel), new PropertyMetadata(FavoriteArticleViewModel.Instance)); - /// /// Initializes a new instance of the class. /// public ArticleFavoritePanel() { - this.InitializeComponent(); + InitializeComponent(); + var vm = Locator.Instance.GetService(); + ViewModel = vm; + DataContext = vm; } /// - /// 视图模型. + /// 核心视图模型. /// - public FavoriteArticleViewModel ViewModel - { - get { return (FavoriteArticleViewModel)GetValue(ViewModelProperty); } - set { SetValue(ViewModelProperty, value); } - } - - private async void OnViewRequestLoadMoreAsync(object sender, System.EventArgs e) - { - await ViewModel.RequestDataAsync(); - } - - private async void OnArticleRefreshButtonClickAsync(object sender, RoutedEventArgs e) - { - await ViewModel.InitializeRequestAsync(); - } - - private async void OnUnFavoriteArticleButtonClickAsync(object sender, RoutedEventArgs e) - { - var vm = (sender as FrameworkElement).DataContext as ArticleViewModel; - await ViewModel.RemoveFavoriteArticleAsync(vm); - } + public IAppViewModel CoreViewModel { get; } = Locator.Instance.GetService(); + } - private async void OnRefreshRequestedAsync(Microsoft.UI.Xaml.Controls.RefreshContainer sender, Microsoft.UI.Xaml.Controls.RefreshRequestedEventArgs args) - { - await ViewModel.InitializeRequestAsync(); - } + /// + /// 的基类. + /// + public class ArticleFavoritePanelBase : ReactiveUserControl + { } } diff --git a/src/App/Controls/Favorite/FavoriteComponent.cs b/src/App/Controls/Favorite/FavoriteComponent.cs deleted file mode 100644 index 8d0628db6..000000000 --- a/src/App/Controls/Favorite/FavoriteComponent.cs +++ /dev/null @@ -1,18 +0,0 @@ -// Copyright (c) Richasy. All rights reserved. - -using Richasy.Bili.ViewModels.Uwp; -using Windows.UI.Xaml.Controls; - -namespace Richasy.Bili.App.Controls -{ - /// - /// 收藏夹组件. - /// - public class FavoriteComponent : UserControl - { - /// - /// 视图模型. - /// - public FavoriteViewModel ViewModel { get; } = FavoriteViewModel.Instance; - } -} diff --git a/src/App/Controls/Favorite/FavoriteVideoView.xaml b/src/App/Controls/Favorite/FavoriteVideoView.xaml deleted file mode 100644 index c7df7a66b..000000000 --- a/src/App/Controls/Favorite/FavoriteVideoView.xaml +++ /dev/null @@ -1,130 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/src/App/Controls/Favorite/FavoriteVideoView.xaml.cs b/src/App/Controls/Favorite/FavoriteVideoView.xaml.cs deleted file mode 100644 index 1b32f7212..000000000 --- a/src/App/Controls/Favorite/FavoriteVideoView.xaml.cs +++ /dev/null @@ -1,98 +0,0 @@ -// Copyright (c) Richasy. All rights reserved. - -using System; -using System.Linq; -using System.Threading.Tasks; -using Richasy.Bili.ViewModels.Uwp; -using Windows.UI.Xaml; -using Windows.UI.Xaml.Controls; - -namespace Richasy.Bili.App.Controls -{ - /// - /// 视频收藏夹视图. - /// - public sealed partial class FavoriteVideoView : CenterPopup - { - /// - /// 的依赖属性. - /// - public static readonly DependencyProperty ViewModelProperty = - DependencyProperty.Register(nameof(ViewModel), typeof(FavoriteVideoViewModel), typeof(FavoriteVideoView), new PropertyMetadata(null)); - - /// - /// Initializes a new instance of the class. - /// - /// 视图模型. - public FavoriteVideoView() - { - this.InitializeComponent(); - } - - /// - /// 视图模型. - /// - public FavoriteVideoViewModel ViewModel - { - get { return (FavoriteVideoViewModel)GetValue(ViewModelProperty); } - set { SetValue(ViewModelProperty, value); } - } - - /// - /// 显示. - /// - /// 视频收藏夹数据模型. - /// . - public async Task ShowAsync(FavoriteVideoViewModel vm) - { - Show(); - if (ViewModel == null || ViewModel.Id != vm.Id) - { - // 请求用户数据. - ViewModel = vm; - if (!vm.IsRequested) - { - await ViewModel.InitializeRequestAsync(); - } - } - } - - private async void OnRefreshButtonClickAsync(object sender, RoutedEventArgs e) - { - await ViewModel.InitializeRequestAsync(); - } - - private async void OnVideoViewRequestLoadMoreAsync(object sender, System.EventArgs e) - { - await ViewModel.RequestDataAsync(); - } - - private void OnVideoItemClick(object sender, VideoViewModel e) - { - Hide(); - } - - private async void OnAddToViewLaterButtonClickAsync(object sender, RoutedEventArgs e) - { - var vm = (sender as FrameworkElement).DataContext as VideoViewModel; - await ViewLaterViewModel.Instance.AddAsync(vm); - } - - private async void OnUnFavoriteVideoButtonClickAsync(object sender, RoutedEventArgs e) - { - var vm = (sender as FrameworkElement).DataContext as VideoViewModel; - var result = await FavoriteViewModel.Instance.RemoveFavoriteVideoAsync(ViewModel.Id, Convert.ToInt32(vm.VideoId)); - if (result) - { - ViewModel.VideoCollection.Remove(vm); - } - } - - private void OnVideoFlyoutOpening(object sender, object e) - { - var flyout = sender as Microsoft.UI.Xaml.Controls.CommandBarFlyout; - var element = flyout.SecondaryCommands.OfType().Last(); - element.IsEnabled = ViewModel.IsMine; - } - } -} diff --git a/src/App/Controls/Favorite/PgcFavoritePanel.xaml b/src/App/Controls/Favorite/PgcFavoritePanel.xaml index 2b19a5cf1..fd8d4c98f 100644 --- a/src/App/Controls/Favorite/PgcFavoritePanel.xaml +++ b/src/App/Controls/Favorite/PgcFavoritePanel.xaml @@ -1,19 +1,34 @@  - + + + + + + + + + + + + + @@ -26,53 +41,108 @@ - - + + + + + + + + + - + - - - - - - - - - - - - - - - + MinWideItemWidth="300"> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - + + - - + - + Visibility="{x:Bind ViewModel.IsError, Mode=OneWay}"> + + + + + diff --git a/src/App/Controls/Favorite/PgcFavoritePanel.xaml.cs b/src/App/Controls/Favorite/PgcFavoritePanel.xaml.cs index 5b6973de6..8dfdffa10 100644 --- a/src/App/Controls/Favorite/PgcFavoritePanel.xaml.cs +++ b/src/App/Controls/Favorite/PgcFavoritePanel.xaml.cs @@ -1,10 +1,13 @@ // Copyright (c) Richasy. All rights reserved. -using Richasy.Bili.ViewModels.Uwp; +using System.Linq; +using Bili.DI.Container; +using Bili.ViewModels.Interfaces.Core; +using Bili.ViewModels.Interfaces.Pgc; using Windows.UI.Xaml; using Windows.UI.Xaml.Controls; -namespace Richasy.Bili.App.Controls +namespace Bili.App.Controls.Favorite { /// /// PGC收藏夹视图. @@ -15,39 +18,54 @@ public sealed partial class PgcFavoritePanel : UserControl /// 的依赖属性. /// public static readonly DependencyProperty ViewModelProperty = - DependencyProperty.Register(nameof(ViewModel), typeof(PgcFavoriteViewModelBase), typeof(PgcFavoritePanel), new PropertyMetadata(null)); + DependencyProperty.Register(nameof(ViewModel), typeof(IPgcFavoriteModuleViewModel), typeof(PgcFavoritePanel), new PropertyMetadata(default)); /// /// Initializes a new instance of the class. /// - public PgcFavoritePanel() - { - this.InitializeComponent(); - } + public PgcFavoritePanel() => InitializeComponent(); + + /// + /// 核心数据模型. + /// + public IAppViewModel CoreViewModel { get; } = Locator.Instance.GetService(); /// /// 视图模型. /// - public PgcFavoriteViewModelBase ViewModel + public IPgcFavoriteModuleViewModel ViewModel { - get { return (PgcFavoriteViewModelBase)GetValue(ViewModelProperty); } + get { return (IPgcFavoriteModuleViewModel)GetValue(ViewModelProperty); } set { SetValue(ViewModelProperty, value); } } - private async void OnViewRequestLoadMoreAsync(object sender, System.EventArgs e) + private void OnItemFlyoutOpened(object sender, object e) { - await ViewModel.RequestDataAsync(); + var items = (sender as MenuFlyout).Items.OfType().Take(3); + foreach (var item in items) + { + if (item is MenuFlyoutItem btn) + { + var status = int.Parse(btn.Tag.ToString()); + item.IsEnabled = status != ViewModel.Status; + } + } } - private async void OnPgcRefreshButtonClickAsync(object sender, RoutedEventArgs e) + private void OnMarkStatusButtonClick(object sender, RoutedEventArgs e) { - await ViewModel.InitializeRequestAsync(); + var item = sender as MenuFlyoutItem; + var context = item.DataContext as ISeasonItemViewModel; + var status = int.Parse(item.Tag.ToString()); + context.ChangeFavoriteStatusCommand.Execute(status); } - private async void OnUnFavoritePgcButtonClickAsync(object sender, RoutedEventArgs e) + private void OnStatusSelectionChanged(object sender, SelectionChangedEventArgs e) { - var vm = (sender as FrameworkElement).DataContext as SeasonViewModel; - await ViewModel.RemoveFavoritePgcAsync(vm); + if (StatusComboBox.SelectedItem is int status && status != ViewModel.Status) + { + ViewModel.SetStatusCommand.Execute(status); + } } } } diff --git a/src/App/Controls/Favorite/VideoFavoritePanel.xaml b/src/App/Controls/Favorite/VideoFavoritePanel.xaml index 433fd0f21..7e7226e6b 100644 --- a/src/App/Controls/Favorite/VideoFavoritePanel.xaml +++ b/src/App/Controls/Favorite/VideoFavoritePanel.xaml @@ -1,252 +1,177 @@ - + + + + - - - - - - - - - - - - - - - - - - + VerticalScrollBarVisibility="Auto" + Visibility="{x:Bind ViewModel.IsReloading, Mode=OneWay, Converter={StaticResource BoolToVisibilityReverseConverter}}"> + XYFocusKeyboardNavigation="Enabled"> - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/App/Controls/Pgc/DesktopAnimePageView.xaml.cs b/src/App/Controls/Pgc/DesktopAnimePageView.xaml.cs new file mode 100644 index 000000000..1016e5ddf --- /dev/null +++ b/src/App/Controls/Pgc/DesktopAnimePageView.xaml.cs @@ -0,0 +1,56 @@ +// Copyright (c) Richasy. All rights reserved. + +using Bili.DI.Container; +using Bili.Models.Data.Community; +using Bili.ViewModels.Interfaces.Core; +using Bili.ViewModels.Interfaces.Pgc; +using Windows.UI.Xaml; +using Windows.UI.Xaml.Controls; + +namespace Bili.App.Controls.Pgc +{ + /// + /// 动漫视图. + /// + public sealed partial class DesktopAnimePageView : UserControl + { + /// + /// 的依赖属性. + /// + public static readonly DependencyProperty ViewModelProperty = + DependencyProperty.Register(nameof(ViewModel), typeof(IAnimePageViewModel), typeof(DesktopAnimePageView), new PropertyMetadata(default, new PropertyChangedCallback(OnViewModelChanged))); + + /// + /// Initializes a new instance of the class. + /// + public DesktopAnimePageView() + => InitializeComponent(); + + /// + /// 视图模型. + /// + public IAnimePageViewModel ViewModel + { + get { return (IAnimePageViewModel)GetValue(ViewModelProperty); } + set { SetValue(ViewModelProperty, value); } + } + + /// + /// 核心视图模型. + /// + public IAppViewModel CoreViewModel { get; } = Locator.Instance.GetService(); + + private static void OnViewModelChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) + { + var instance = d as DesktopAnimePageView; + instance.DataContext = e.NewValue; + } + + private void OnRootNavViewItemInvoked(Microsoft.UI.Xaml.Controls.NavigationView sender, Microsoft.UI.Xaml.Controls.NavigationViewItemInvokedEventArgs args) + { + var data = args.InvokedItem as Partition; + ContentScrollViewer.ChangeView(0, 0, 1); + ViewModel.SelectPartitionCommand.ExecuteAsync(data); + } + } +} diff --git a/src/App/Controls/Pgc/DesktopPgcPageView.xaml b/src/App/Controls/Pgc/DesktopPgcPageView.xaml new file mode 100644 index 000000000..04e58ed53 --- /dev/null +++ b/src/App/Controls/Pgc/DesktopPgcPageView.xaml @@ -0,0 +1,114 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/App/Controls/Pgc/DesktopPgcPageView.xaml.cs b/src/App/Controls/Pgc/DesktopPgcPageView.xaml.cs new file mode 100644 index 000000000..70ab0c5fe --- /dev/null +++ b/src/App/Controls/Pgc/DesktopPgcPageView.xaml.cs @@ -0,0 +1,47 @@ +// Copyright (c) Richasy. All rights reserved. + +using Bili.DI.Container; +using Bili.ViewModels.Interfaces.Core; +using Bili.ViewModels.Interfaces.Pgc; +using Windows.UI.Xaml; +using Windows.UI.Xaml.Controls; + +namespace Bili.App.Controls.Pgc +{ + /// + /// PGC 页面视图. + /// + public sealed partial class DesktopPgcPageView : UserControl + { + /// + /// 的依赖属性. + /// + public static readonly DependencyProperty ViewModelProperty = + DependencyProperty.Register(nameof(ViewModel), typeof(IPgcPageViewModel), typeof(DesktopPgcPageView), new PropertyMetadata(default, new PropertyChangedCallback(OnViewModelChanged))); + + /// + /// Initializes a new instance of the class. + /// + public DesktopPgcPageView() => InitializeComponent(); + + /// + /// 视图模型. + /// + public IPgcPageViewModel ViewModel + { + get { return (IPgcPageViewModel)GetValue(ViewModelProperty); } + set { SetValue(ViewModelProperty, value); } + } + + /// + /// 核心视图模型. + /// + public IAppViewModel CoreViewModel { get; } = Locator.Instance.GetService(); + + private static void OnViewModelChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) + { + var instance = d as DesktopPgcPageView; + instance.DataContext = e.NewValue; + } + } +} diff --git a/src/App/Controls/Pgc/EpisodeItem/EpisodeItem.cs b/src/App/Controls/Pgc/EpisodeItem/EpisodeItem.cs new file mode 100644 index 000000000..409875bda --- /dev/null +++ b/src/App/Controls/Pgc/EpisodeItem/EpisodeItem.cs @@ -0,0 +1,50 @@ +// Copyright (c) Richasy. All rights reserved. + +using Bili.DI.Container; +using Bili.Toolkit.Interfaces; +using Bili.ViewModels.Interfaces.Pgc; +using Windows.Foundation; +using Windows.UI.Xaml; +using Windows.UI.Xaml.Controls; + +namespace Bili.App.Controls.Pgc +{ + /// + /// 剧集单集条目视图. + /// + public sealed class EpisodeItem : ReactiveControl, IRepeaterItem, IOrientationControl + { + /// + /// 的依赖属性. + /// + public static readonly DependencyProperty IsDynamicProperty = + DependencyProperty.Register(nameof(IsDynamic), typeof(bool), typeof(EpisodeItem), new PropertyMetadata(false)); + + /// + /// Initializes a new instance of the class. + /// + public EpisodeItem() + => DefaultStyleKey = typeof(EpisodeItem); + + /// + /// 是否是动态剧集. + /// + public bool IsDynamic + { + get { return (bool)GetValue(IsDynamicProperty); } + set { SetValue(IsDynamicProperty, value); } + } + + /// + public Size GetHolderSize() => new Size(210, 248); + + /// + public void ChangeLayout(Orientation orientation) + { + var resourceToolkit = Locator.Instance.GetService(); + Style = orientation == Orientation.Horizontal + ? IsDynamic ? resourceToolkit.GetResource + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/App/Controls/Player/BiliMediaTransportControls/BiliMediaTransportControls.Fields.cs b/src/App/Controls/Player/BiliMediaTransportControls/BiliMediaTransportControls.Fields.cs new file mode 100644 index 000000000..31885f4ab --- /dev/null +++ b/src/App/Controls/Player/BiliMediaTransportControls/BiliMediaTransportControls.Fields.cs @@ -0,0 +1,25 @@ +// Copyright (c) Richasy. All rights reserved. + +using Bili.App.Controls.Danmaku; +using Windows.UI.Xaml.Controls; + +namespace Bili.App.Controls.Player +{ + /// + /// 媒体传输控制器. + /// + public sealed partial class BiliMediaTransportControls + { + private const string VolumeSliderName = "VolumeSlider"; + private const string FormatListViewName = "FormatListView"; + private const string PlayPauseButtonName = "PlayPauseButton"; + private const string DanmakuBoxName = "DanmakuBox"; + private const string RootGridName = "RootGrid"; + + private Slider _volumeSlider; + private ListView _formatListView; + private Button _playPauseButton; + private DanmakuBox _danmakuBox; + private Grid _rootGrid; + } +} diff --git a/src/App/Controls/Player/BiliMediaTransportControls/BiliMediaTransportControls.Handlers.cs b/src/App/Controls/Player/BiliMediaTransportControls/BiliMediaTransportControls.Handlers.cs new file mode 100644 index 000000000..fdc1b0ba4 --- /dev/null +++ b/src/App/Controls/Player/BiliMediaTransportControls/BiliMediaTransportControls.Handlers.cs @@ -0,0 +1,93 @@ +// Copyright (c) Richasy. All rights reserved. + +using System.ComponentModel; +using Bili.Models.Data.Player; +using Windows.UI.Xaml; +using Windows.UI.Xaml.Controls; +using Windows.UI.Xaml.Controls.Primitives; + +namespace Bili.App.Controls.Player +{ + /// + /// 媒体传输控件. + /// + public sealed partial class BiliMediaTransportControls + { + private static void OnTransportVisibilityChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) + { + var instance = d as BiliMediaTransportControls; + if (e.NewValue is Visibility vis) + { + if (vis == Visibility.Visible) + { + VisualStateManager.GoToState(instance, "ControlPanelFadeInState", false); + } + else + { + VisualStateManager.GoToState(instance, "ControlPanelFadeOutState", false); + } + } + } + + private void OnLoaded(object sender, RoutedEventArgs e) + { + ChangeVisualStateFromStatus(); + ChangeVisualStateFromDisplayMode(); + _playPauseButton?.Focus(FocusState.Programmatic); + } + + private void OnUnloaded(object sender, RoutedEventArgs e) + { + ViewModel.PropertyChanged -= OnViewModelPropertyChanged; + if (_formatListView != null) + { + _formatListView.SelectionChanged -= OnFormatListViewSelectionChanged; + } + + if (_volumeSlider != null) + { + _volumeSlider.ValueChanged -= OnVolumeSliderValueChanged; + } + + _formatListView = null; + _volumeSlider = null; + _playPauseButton = null; + _danmakuBox = null; + _rootGrid?.Children?.Clear(); + _rootGrid = null; + } + + private void OnViewModelPropertyChanged(object sender, PropertyChangedEventArgs e) + { + if (e.PropertyName == nameof(ViewModel.Status)) + { + ChangeVisualStateFromStatus(); + } + else if (e.PropertyName == nameof(ViewModel.DisplayMode)) + { + ChangeVisualStateFromDisplayMode(); + } + else if (e.PropertyName == nameof(ViewModel.IsShowMediaTransport)) + { + _playPauseButton?.Focus(FocusState.Programmatic); + } + } + + private void OnVolumeSliderValueChanged(object sender, RangeBaseValueChangedEventArgs e) + { + if (e.NewValue != e.OldValue) + { + ViewModel.ChangeVolumeCommand.Execute(e.NewValue); + } + } + + private void OnFormatListViewSelectionChanged(object sender, SelectionChangedEventArgs e) + { + if (_formatListView.SelectedItem is FormatInformation info + && ViewModel.CurrentFormat != info) + { + ViewModel.ChangeFormatCommand.Execute(info); + } + } + } +} diff --git a/src/App/Controls/Player/BiliMediaTransportControls/BiliMediaTransportControls.Properties.cs b/src/App/Controls/Player/BiliMediaTransportControls/BiliMediaTransportControls.Properties.cs new file mode 100644 index 000000000..70f033534 --- /dev/null +++ b/src/App/Controls/Player/BiliMediaTransportControls/BiliMediaTransportControls.Properties.cs @@ -0,0 +1,32 @@ +// Copyright (c) Richasy. All rights reserved. + +using Windows.UI.Xaml; + +namespace Bili.App.Controls.Player +{ + /// + /// 媒体传输控件. + /// + public sealed partial class BiliMediaTransportControls + { + /// + /// 的依赖属性. + /// + public static readonly DependencyProperty TransportVisibilityProperty = + DependencyProperty.Register(nameof(TransportVisibility), typeof(Visibility), typeof(BiliMediaTransportControls), new PropertyMetadata(default, new PropertyChangedCallback(OnTransportVisibilityChanged))); + + /// + /// 传输控件是否可见. + /// + public Visibility TransportVisibility + { + get { return (Visibility)GetValue(TransportVisibilityProperty); } + set { SetValue(TransportVisibilityProperty, value); } + } + + /// + /// 弹幕输入框是否获得了焦点. + /// + public bool IsDanmakuBoxFocused => _danmakuBox?.IsInputFocused ?? false; + } +} diff --git a/src/App/Controls/Player/BiliMediaTransportControls/BiliMediaTransportControls.cs b/src/App/Controls/Player/BiliMediaTransportControls/BiliMediaTransportControls.cs new file mode 100644 index 000000000..908d906b9 --- /dev/null +++ b/src/App/Controls/Player/BiliMediaTransportControls/BiliMediaTransportControls.cs @@ -0,0 +1,83 @@ +// Copyright (c) Richasy. All rights reserved. + +using Bili.App.Controls.Danmaku; +using Bili.ViewModels.Interfaces.Core; +using Windows.UI.Xaml; +using Windows.UI.Xaml.Controls; + +namespace Bili.App.Controls.Player +{ + /// + /// 媒体传输控件. + /// + public sealed partial class BiliMediaTransportControls : ReactiveControl + { + /// + /// Initializes a new instance of the class. + /// + public BiliMediaTransportControls() + { + DefaultStyleKey = typeof(BiliMediaTransportControls); + Loaded += OnLoaded; + Unloaded += OnUnloaded; + } + + internal override void OnViewModelChanged(DependencyPropertyChangedEventArgs e) + { + if (e.OldValue is IMediaPlayerViewModel oldVM) + { + oldVM.PropertyChanged -= OnViewModelPropertyChanged; + } + + var vm = e.NewValue as IMediaPlayerViewModel; + vm.PropertyChanged -= OnViewModelPropertyChanged; + vm.PropertyChanged += OnViewModelPropertyChanged; + } + + /// + protected override void OnApplyTemplate() + { + _volumeSlider = GetTemplateChild(VolumeSliderName) as Slider; + _formatListView = GetTemplateChild(FormatListViewName) as ListView; + _playPauseButton = GetTemplateChild(PlayPauseButtonName) as Button; + _danmakuBox = GetTemplateChild(DanmakuBoxName) as DanmakuBox; + _rootGrid = GetTemplateChild(RootGridName) as Grid; + + if (_formatListView != null) + { + _formatListView.SelectionChanged += OnFormatListViewSelectionChanged; + } + + if (_volumeSlider != null) + { + _volumeSlider.ValueChanged += OnVolumeSliderValueChanged; + } + } + + private void ChangeVisualStateFromStatus() + { + if (ViewModel.Status == Models.Enums.PlayerStatus.Playing) + { + VisualStateManager.GoToState(this, "PlayingState", false); + } + else if (ViewModel.Status == Models.Enums.PlayerStatus.Pause + || ViewModel.Status == Models.Enums.PlayerStatus.End) + { + VisualStateManager.GoToState(this, "PauseState", false); + } + } + + private void ChangeVisualStateFromDisplayMode() + { + switch (ViewModel.DisplayMode) + { + case Models.Enums.PlayerDisplayMode.FullScreen: + VisualStateManager.GoToState(this, "FullScreenState", false); + break; + default: + VisualStateManager.GoToState(this, "NormalState", false); + break; + } + } + } +} diff --git a/src/App/Controls/Player/BiliMediaTransportControls/BiliMediaTransportControls.xaml b/src/App/Controls/Player/BiliMediaTransportControls/BiliMediaTransportControls.xaml new file mode 100644 index 000000000..e07801560 --- /dev/null +++ b/src/App/Controls/Player/BiliMediaTransportControls/BiliMediaTransportControls.xaml @@ -0,0 +1,1373 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/App/Controls/Player/BiliPlayer/BiliPlayer.Properties.cs b/src/App/Controls/Player/BiliPlayer/BiliPlayer.Properties.cs deleted file mode 100644 index ea483b286..000000000 --- a/src/App/Controls/Player/BiliPlayer/BiliPlayer.Properties.cs +++ /dev/null @@ -1,47 +0,0 @@ -// Copyright (c) Richasy. All rights reserved. - -using Richasy.Bili.ViewModels.Uwp; -using Windows.UI.Xaml; - -namespace Richasy.Bili.App.Controls -{ - /// - /// 视频播放器. - /// - public partial class BiliPlayer - { - /// - /// 的依赖属性. - /// - public static readonly DependencyProperty CoverUrlProperty = - DependencyProperty.Register(nameof(CoverUrl), typeof(string), typeof(BiliPlayer), new PropertyMetadata(string.Empty)); - - /// - /// 的依赖属性. - /// - public static readonly DependencyProperty ViewModelProperty = - DependencyProperty.Register(nameof(ViewModel), typeof(PlayerViewModel), typeof(BiliPlayer), new PropertyMetadata(PlayerViewModel.Instance)); - - private const string MTCName = "MTC"; - - private BiliPlayerTransportControls _mediaTransport; - - /// - /// 视图模型. - /// - public PlayerViewModel ViewModel - { - get { return (PlayerViewModel)GetValue(ViewModelProperty); } - set { SetValue(ViewModelProperty, value); } - } - - /// - /// 封面地址. - /// - public string CoverUrl - { - get { return (string)GetValue(CoverUrlProperty); } - set { SetValue(CoverUrlProperty, value); } - } - } -} diff --git a/src/App/Controls/Player/BiliPlayer/BiliPlayer.cs b/src/App/Controls/Player/BiliPlayer/BiliPlayer.cs deleted file mode 100644 index a995e3f0d..000000000 --- a/src/App/Controls/Player/BiliPlayer/BiliPlayer.cs +++ /dev/null @@ -1,56 +0,0 @@ -// Copyright (c) Richasy. All rights reserved. - -using System.ComponentModel; -using Richasy.Bili.Locator.Uwp; -using Richasy.Bili.Toolkit.Interfaces; -using Windows.UI.Xaml; -using Windows.UI.Xaml.Controls; - -namespace Richasy.Bili.App.Controls -{ - /// - /// 视频播放器. - /// - public partial class BiliPlayer : Control - { - /// - /// Initializes a new instance of the class. - /// - public BiliPlayer() - { - this.DefaultStyleKey = typeof(BiliPlayer); - ViewModel.PropertyChanged += OnViewModelPropertyChanged; - } - - /// - protected override void OnApplyTemplate() - { - if (ViewModel.BiliPlayer == null) - { - var mediaPlayerElement = GetTemplateChild("MediaPlayerElement") as MediaPlayerElement; - ViewModel.ApplyMediaControl(mediaPlayerElement); - } - - _mediaTransport = GetTemplateChild(MTCName) as BiliPlayerTransportControls; - CheckMTCStyle(); - } - - private void OnViewModelPropertyChanged(object sender, PropertyChangedEventArgs e) - { - if (e.PropertyName == nameof(ViewModel.IsLive)) - { - CheckMTCStyle(); - } - } - - private void CheckMTCStyle() - { - var styleName = ViewModel.IsLive ? "LiveMTCStyle" : "DefaultMTCStyle"; - var style = ServiceLocator.Instance.GetService().GetResource - diff --git a/src/App/Controls/Player/BiliPlayerTransportControls/BiliPlayerTransportControls.Properties.cs b/src/App/Controls/Player/BiliPlayerTransportControls/BiliPlayerTransportControls.Properties.cs deleted file mode 100644 index 8407387fb..000000000 --- a/src/App/Controls/Player/BiliPlayerTransportControls/BiliPlayerTransportControls.Properties.cs +++ /dev/null @@ -1,97 +0,0 @@ -// Copyright (c) Richasy. All rights reserved. - -using System.Collections.Generic; -using Richasy.Bili.ViewModels.Uwp; -using Richasy.Bili.ViewModels.Uwp.Common; -using Windows.UI.Xaml; -using Windows.UI.Xaml.Controls; -using Windows.UI.Xaml.Controls.Primitives; -using Windows.UI.Xaml.Shapes; - -namespace Richasy.Bili.App.Controls -{ - /// - /// 哔哩播放器的媒体传输控件. - /// - public partial class BiliPlayerTransportControls - { - /// - /// 的依赖属性. - /// - public static readonly DependencyProperty ViewModelProperty = - DependencyProperty.Register(nameof(ViewModel), typeof(PlayerViewModel), typeof(BiliPlayerTransportControls), new PropertyMetadata(PlayerViewModel.Instance)); - - /// - /// 的依赖属性. - /// - public static readonly DependencyProperty DanmakuViewModelProperty = - DependencyProperty.Register(nameof(DanmakuViewModel), typeof(DanmakuViewModel), typeof(BiliPlayerTransportControls), new PropertyMetadata(DanmakuViewModel.Instance)); - - /// - /// 的依赖属性. - /// - public static readonly DependencyProperty SettingViewModelProperty = - DependencyProperty.Register(nameof(SettingViewModel), typeof(SettingViewModel), typeof(BiliPlayerTransportControls), new PropertyMetadata(SettingViewModel.Instance)); - - private const string DanmakuViewName = "DanmakuView"; - private const string FullWindowPlayModeButtonName = "FullWindowModeButton"; - private const string FullScreenPlayModeButtonName = "FullScreenModeButton"; - private const string CompactOverlayPlayModeButtonName = "CompactOverlayModeButton"; - private const string InteractionControlName = "InteractionControl"; - private const string ControlPanelName = "ControlPanel_ControlPanelVisibilityStates_Border"; - private const string FormatListViewName = "FormatListView"; - private const string LiveQualityListViewName = "LiveQualityListView"; - private const string LivePlayLineListViewName = "LivePlayLineListView"; - private const string BackButtonName = "BackButton"; - private const string BackSkipButtonName = "BackSkipButton"; - private const string ForwardSkipButtonName = "ForwardSkipButton"; - private const string PlayPauseButtonName = "PlayPauseButton"; - - private readonly Dictionary> _danmakuDictionary; - - private DispatcherTimer _danmakuTimer; - private DispatcherTimer _cursorTimer; - private DanmakuView _danmakuView; - private ToggleButton _fullWindowPlayModeButton; - private ToggleButton _fullScreenPlayModeButton; - private ToggleButton _compactOverlayPlayModeButton; - private Rectangle _interactionControl; - private Border _controlPanel; - private ListView _formatListView; - private ListView _liveQualityListView; - private ListView _livePlayLineListView; - private Button _backButton; - private Button _backSkipButton; - private Button _forwardSkipButton; - private Button _playPauseButton; - private int _segmentIndex; - private double _cursorStayTime; - - /// - /// 视图模型. - /// - public PlayerViewModel ViewModel - { - get { return (PlayerViewModel)GetValue(ViewModelProperty); } - set { SetValue(ViewModelProperty, value); } - } - - /// - /// 弹幕视图模型. - /// - public DanmakuViewModel DanmakuViewModel - { - get { return (DanmakuViewModel)GetValue(DanmakuViewModelProperty); } - set { SetValue(DanmakuViewModelProperty, value); } - } - - /// - /// 设置视图模型. - /// - public SettingViewModel SettingViewModel - { - get { return (SettingViewModel)GetValue(SettingViewModelProperty); } - set { SetValue(SettingViewModelProperty, value); } - } - } -} diff --git a/src/App/Controls/Player/BiliPlayerTransportControls/BiliPlayerTransportControls.cs b/src/App/Controls/Player/BiliPlayerTransportControls/BiliPlayerTransportControls.cs deleted file mode 100644 index a8e5a4e09..000000000 --- a/src/App/Controls/Player/BiliPlayerTransportControls/BiliPlayerTransportControls.cs +++ /dev/null @@ -1,563 +0,0 @@ -// Copyright (c) Richasy. All rights reserved. - -using System; -using System.Collections.Generic; -using System.ComponentModel; -using System.Linq; -using Bilibili.Community.Service.Dm.V1; -using Richasy.Bili.App.Resources.Extension; -using Richasy.Bili.Models.BiliBili; -using Richasy.Bili.Models.Enums; -using Richasy.Bili.Models.Enums.App; -using Richasy.Bili.ViewModels.Uwp; -using Windows.Media.Playback; -using Windows.UI.Xaml; -using Windows.UI.Xaml.Controls; -using Windows.UI.Xaml.Controls.Primitives; -using Windows.UI.Xaml.Input; -using Windows.UI.Xaml.Shapes; - -namespace Richasy.Bili.App.Controls -{ - /// - /// 哔哩播放器的媒体传输控件. - /// - public partial class BiliPlayerTransportControls : MediaTransportControls - { - /// - /// Initializes a new instance of the class. - /// - public BiliPlayerTransportControls() - { - this.DefaultStyleKey = typeof(BiliPlayerTransportControls); - this._danmakuDictionary = new Dictionary>(); - this._segmentIndex = 1; - this.DanmakuViewModel.DanmakuListAdded += OnDanmakuListAdded; - this.DanmakuViewModel.RequestClearDanmaku += OnRequestClearDanmaku; - this.DanmakuViewModel.PropertyChanged += OnDanmakuViewModelPropertyChanged; - this.DanmakuViewModel.SendDanmakuSucceeded += OnSendDanmakuSucceeded; - this.ViewModel.MediaPlayerUpdated += OnMediaPlayerUdpated; - this.SettingViewModel.PropertyChanged += OnSettingViewModelPropertyChanged; - this.ViewModel.PropertyChanged += OnViewModelPropertyChanged; - this.ViewModel.NewLiveDanmakuAdded += OnNewLiveDanmakuAdded; - this.SizeChanged += OnSizeChanged; - InitializeDanmakuTimer(); - InitializeCursorTimer(); - } - - /// - protected override void OnApplyTemplate() - { - _danmakuView = GetTemplateChild(DanmakuViewName) as DanmakuView; - _fullWindowPlayModeButton = GetTemplateChild(FullWindowPlayModeButtonName) as ToggleButton; - _fullScreenPlayModeButton = GetTemplateChild(FullScreenPlayModeButtonName) as ToggleButton; - _compactOverlayPlayModeButton = GetTemplateChild(CompactOverlayPlayModeButtonName) as ToggleButton; - _interactionControl = GetTemplateChild(InteractionControlName) as Rectangle; - _controlPanel = GetTemplateChild(ControlPanelName) as Border; - _formatListView = GetTemplateChild(FormatListViewName) as ListView; - _livePlayLineListView = GetTemplateChild(LivePlayLineListViewName) as ListView; - _liveQualityListView = GetTemplateChild(LiveQualityListViewName) as ListView; - _backButton = GetTemplateChild(BackButtonName) as Button; - _backSkipButton = GetTemplateChild(BackSkipButtonName) as Button; - _forwardSkipButton = GetTemplateChild(ForwardSkipButtonName) as Button; - _playPauseButton = GetTemplateChild(PlayPauseButtonName) as Button; - - _fullWindowPlayModeButton.Click += OnPlayModeButtonClick; - _fullScreenPlayModeButton.Click += OnPlayModeButtonClick; - _compactOverlayPlayModeButton.Click += OnPlayModeButtonClick; - _interactionControl.Tapped += OnInteractionControlTapped; - _interactionControl.DoubleTapped += OnInteractionControlDoubleTapped; - _backButton.Click += OnBackButtonClick; - - if (_formatListView != null) - { - _formatListView.SelectionChanged += OnFormatListViewSelectionChangedAsync; - } - - if (_liveQualityListView != null) - { - _liveQualityListView.SelectionChanged += OnLiveQualityListViewSelectionChangedAsync; - } - - if (_livePlayLineListView != null) - { - _livePlayLineListView.SelectionChanged += OnLivePlayLineListViewSelectionChangedAsync; - } - - if (_backSkipButton != null) - { - _backSkipButton.Click += OnBackSkipButtonClick; - } - - if (_forwardSkipButton != null) - { - _forwardSkipButton.Click += OnForwardSkipButtonClick; - } - - CheckCurrentPlayerMode(); - CheckDanmakuZoom(); - CheckMTCControlMode(); - base.OnApplyTemplate(); - } - - /// - protected override void OnPointerMoved(PointerRoutedEventArgs e) - { - _cursorTimer.Start(); - Window.Current.CoreWindow.PointerCursor = new Windows.UI.Core.CoreCursor(Windows.UI.Core.CoreCursorType.Arrow, 0); - _cursorStayTime = 0; - } - - /// - protected override void OnPointerExited(PointerRoutedEventArgs e) - { - _cursorTimer.Stop(); - Window.Current.CoreWindow.PointerCursor = new Windows.UI.Core.CoreCursor(Windows.UI.Core.CoreCursorType.Arrow, 0); - _cursorStayTime = 0; - } - - private void OnSendDanmakuSucceeded(object sender, string e) - { - var model = new DanmakuModel - { - Color = Microsoft.Toolkit.Uwp.Helpers.ColorHelper.ToColor(DanmakuViewModel.Color), - Size = DanmakuViewModel.IsStandardSize ? 25 : 18, - Text = e, - Location = DanmakuViewModel.Location, - }; - - _danmakuView.AddScreenDanmaku(model, true); - } - - private void OnBackButtonClick(object sender, RoutedEventArgs e) - { - ViewModel.PlayerDisplayMode = PlayerDisplayMode.Default; - } - - private void OnNewLiveDanmakuAdded(object sender, LiveDanmakuMessage e) - { - if (_danmakuView != null) - { - var myName = AccountViewModel.Instance.DisplayName; - var isOwn = !string.IsNullOrEmpty(myName) && myName == e.UserName; - _danmakuView.AddLiveDanmakuAsync(e.Text, isOwn, e.ContentColor?.ToColor()); - } - } - - private async void OnFormatListViewSelectionChangedAsync(object sender, SelectionChangedEventArgs e) - { - if (_formatListView.SelectedItem is VideoFormatViewModel item && item.Data.Quality != ViewModel.CurrentFormat?.Quality) - { - await ViewModel.ChangeFormatAsync(item.Data.Quality); - } - } - - private async void OnLivePlayLineListViewSelectionChangedAsync(object sender, SelectionChangedEventArgs e) - { - if (_livePlayLineListView.SelectedItem is LivePlayLineViewModel data && ViewModel.CurrentPlayLine != data.Data) - { - await ViewModel.ChangeLivePlayLineAsync(data.Data.Order); - } - } - - private async void OnLiveQualityListViewSelectionChangedAsync(object sender, SelectionChangedEventArgs e) - { - if (_liveQualityListView.SelectedItem is LiveQualityViewModel data && ViewModel.CurrentLiveQuality != data.Data) - { - await ViewModel.ChangeLiveQualityAsync(data.Data.Quality); - } - } - - private void OnInteractionControlTapped(object sender, TappedRoutedEventArgs e) - { - if (this.ShowAndHideAutomatically) - { - _playPauseButton.Focus(FocusState.Programmatic); - return; - } - - if (_controlPanel.Opacity == 0d) - { - Show(); - } - else if (_controlPanel.Opacity == 1) - { - _playPauseButton.Focus(FocusState.Programmatic); - Hide(); - } - } - - private void OnInteractionControlDoubleTapped(object sender, DoubleTappedRoutedEventArgs e) - { - var playerStatus = ViewModel.PlayerStatus; - var canDoubleTapped = playerStatus == PlayerStatus.Playing || playerStatus == PlayerStatus.Pause; - if (canDoubleTapped) - { - ViewModel.TogglePlayPause(); - } - } - - private void OnForwardSkipButtonClick(object sender, RoutedEventArgs e) - { - var forwardSeconds = SettingViewModel.SingleFastForwardAndRewindSpan; - ViewModel.ForwardSkip(forwardSeconds); - } - - private void OnBackSkipButtonClick(object sender, RoutedEventArgs e) - { - var backSeconds = SettingViewModel.SingleFastForwardAndRewindSpan; - ViewModel.BackSkip(backSeconds); - } - - private void OnPlayModeButtonClick(object sender, RoutedEventArgs e) - { - var btn = sender as ToggleButton; - PlayerDisplayMode mode = default; - switch (btn.Name) - { - case FullWindowPlayModeButtonName: - _fullScreenPlayModeButton.IsChecked = false; - _compactOverlayPlayModeButton.IsChecked = false; - mode = _fullWindowPlayModeButton.IsChecked.Value ? - PlayerDisplayMode.FullWindow : PlayerDisplayMode.Default; - break; - case FullScreenPlayModeButtonName: - _fullWindowPlayModeButton.IsChecked = false; - _compactOverlayPlayModeButton.IsChecked = false; - mode = _fullScreenPlayModeButton.IsChecked.Value ? - PlayerDisplayMode.FullScreen : PlayerDisplayMode.Default; - break; - case CompactOverlayPlayModeButtonName: - _fullScreenPlayModeButton.IsChecked = false; - _fullWindowPlayModeButton.IsChecked = false; - mode = _compactOverlayPlayModeButton.IsChecked.Value ? - PlayerDisplayMode.CompactOverlay : PlayerDisplayMode.Default; - break; - default: - break; - } - - ViewModel.PlayerDisplayMode = mode; - } - - private void CheckCurrentPlayerMode() - { - switch (ViewModel.PlayerDisplayMode) - { - case PlayerDisplayMode.Default: - _fullWindowPlayModeButton.IsChecked = false; - _fullScreenPlayModeButton.IsChecked = false; - _compactOverlayPlayModeButton.IsChecked = false; - _backButton.Visibility = Visibility.Collapsed; - break; - case PlayerDisplayMode.FullWindow: - _fullWindowPlayModeButton.IsChecked = true; - _fullScreenPlayModeButton.IsChecked = false; - _compactOverlayPlayModeButton.IsChecked = false; - _backButton.Visibility = Visibility.Visible; - break; - case PlayerDisplayMode.FullScreen: - _fullWindowPlayModeButton.IsChecked = false; - _fullScreenPlayModeButton.IsChecked = true; - _compactOverlayPlayModeButton.IsChecked = false; - _backButton.Visibility = Visibility.Visible; - break; - case PlayerDisplayMode.CompactOverlay: - _fullWindowPlayModeButton.IsChecked = false; - _fullScreenPlayModeButton.IsChecked = false; - _compactOverlayPlayModeButton.IsChecked = true; - _backButton.Visibility = Visibility.Collapsed; - break; - default: - break; - } - } - - private void OnDanmakuListAdded(object sender, List e) - { - InitializeDanmaku(e); - this._danmakuTimer.Start(); - } - - private void OnRequestClearDanmaku(object sender, EventArgs e) - { - this._segmentIndex = 1; - this._danmakuDictionary.Clear(); - this._danmakuTimer.Stop(); - _danmakuView.ClearAll(); - } - - private void OnMediaPlayerUdpated(object sender, EventArgs e) - { - var player = ViewModel.BiliPlayer.MediaPlayer; - if (player != null && player.PlaybackSession != null) - { - player.PlaybackSession.PlaybackStateChanged += OnPlaybackStateChangedAsync; - } - } - - private void OnSettingViewModelPropertyChanged(object sender, PropertyChangedEventArgs e) - { - if (e.PropertyName == nameof(SettingViewModel.DefaultMTCControlMode)) - { - CheckMTCControlMode(); - } - } - - private void OnDanmakuViewModelPropertyChanged(object sender, PropertyChangedEventArgs e) - { - if (e.PropertyName == nameof(DanmakuViewModel.DanmakuZoom)) - { - CheckDanmakuZoom(); - } - } - - private void OnViewModelPropertyChanged(object sender, PropertyChangedEventArgs e) - { - if (e.PropertyName == nameof(ViewModel.CurrentFormat)) - { - if (ViewModel.CurrentFormat != null && - (_formatListView.SelectedItem == null || - (_formatListView.SelectedItem as VideoFormatViewModel).Data.Quality != ViewModel.CurrentFormat.Quality)) - { - _formatListView.SelectedItem = ViewModel.FormatCollection.Where(p => p.Data.Quality == ViewModel.CurrentFormat.Quality).FirstOrDefault(); - } - } - else if (e.PropertyName == nameof(ViewModel.CurrentLiveQuality)) - { - if (ViewModel.CurrentLiveQuality != null && - (_liveQualityListView.SelectedItem == null || - (_liveQualityListView.SelectedItem as LiveQualityViewModel).Data.Quality != ViewModel.CurrentLiveQuality.Quality)) - { - _liveQualityListView.SelectedItem = ViewModel.LiveQualityCollection.Where(p => p.Data.Quality == ViewModel.CurrentLiveQuality.Quality).FirstOrDefault(); - } - } - else if (e.PropertyName == nameof(ViewModel.CurrentPlayLine)) - { - if (ViewModel.CurrentPlayLine != null && - (_livePlayLineListView.SelectedItem == null || - (_livePlayLineListView.SelectedItem as LivePlayLineViewModel).Data.Order != ViewModel.CurrentPlayLine.Order)) - { - _livePlayLineListView.SelectedItem = ViewModel.LivePlayLineCollection.Where(p => p.Data.Order == ViewModel.CurrentPlayLine.Order).FirstOrDefault(); - } - } - else if (e.PropertyName == nameof(ViewModel.PlayerDisplayMode)) - { - CheckCurrentPlayerMode(); - } - } - - private async void OnPlaybackStateChangedAsync(MediaPlaybackSession sender, object args) - { - await Dispatcher.RunAsync(Windows.UI.Core.CoreDispatcherPriority.Normal, () => - { - if (sender.PlaybackState == MediaPlaybackState.Buffering) - { - _danmakuView.PauseDanmaku(); - } - else if (sender.PlaybackState == MediaPlaybackState.Paused && sender.Position < sender.NaturalDuration) - { - _danmakuView.PauseDanmaku(); - } - else if (sender.PlaybackState == MediaPlaybackState.Playing) - { - _danmakuView.ResumeDanmaku(); - this.Hide(); - } - }); - } - - private void InitializeDanmakuTimer() - { - if (_danmakuTimer == null) - { - _danmakuTimer = new DispatcherTimer(); - _danmakuTimer.Interval = TimeSpan.FromSeconds(1); - _danmakuTimer.Tick += OnDanmkuTimerTickAsync; - } - } - - private void InitializeCursorTimer() - { - if (_cursorTimer == null) - { - _cursorTimer = new DispatcherTimer(); - _cursorTimer.Interval = TimeSpan.FromSeconds(0.5); - _cursorTimer.Tick += OnCursorTimerTickAsync; - } - } - - private void InitializeDanmaku(List elements) - { - var list = new List(); - foreach (var item in elements) - { - var location = DanmakuLocation.Scroll; - if (item.Mode == 4) - { - location = DanmakuLocation.Bottom; - } - else if (item.Mode == 5) - { - location = DanmakuLocation.Top; - } - - var newDm = new DanmakuModel() - { - Color = item.Color.ToString().ToColor(), - Location = location, - Pool = item.Pool.ToString(), - Id = item.IdStr, - SendId = item.MidHash, - Size = item.Fontsize, - Weight = item.Weight, - Text = item.Content, - SendTime = item.Ctime.ToString(), - Time = item.Progress / 1000, - }; - - list.Add(newDm); - } - - var group = list.GroupBy(p => p.Time).ToDictionary(x => x.Key, x => x.ToList()); - foreach (var g in group) - { - if (_danmakuDictionary.ContainsKey(g.Key)) - { - _danmakuDictionary[g.Key] = _danmakuDictionary[g.Key].Concat(g.Value).ToList(); - } - else - { - _danmakuDictionary.Add(g.Key, g.Value); - } - } - } - - private void OnSizeChanged(object sender, SizeChangedEventArgs e) - { - CheckDanmakuZoom(); - } - - private void CheckDanmakuZoom() - { - if (this.ActualWidth == 0 || this.ActualHeight == 0 || _danmakuView == null) - { - return; - } - - var baseWidth = 800d; - var baseHeight = 600d; - var scale = Math.Min(this.ActualWidth / baseWidth, ActualHeight / baseHeight); - if (scale > 1) - { - scale = 1; - } - else if (scale < 0.4) - { - scale = 0.4; - } - - scale *= DanmakuViewModel.DanmakuZoom; - _danmakuView.DanmakuSizeZoom = scale; - } - - private void CheckMTCControlMode() - { - switch (SettingViewModel.DefaultMTCControlMode) - { - case MTCControlMode.Automatic: - this.ShowAndHideAutomatically = true; - break; - case MTCControlMode.Manual: - this.ShowAndHideAutomatically = false; - break; - default: - break; - } - } - - private async void OnDanmkuTimerTickAsync(object sender, object e) - { - if (ViewModel.BiliPlayer == null || _danmakuView == null) - { - return; - } - - var player = ViewModel.BiliPlayer.MediaPlayer; - - if (player == null || player.PlaybackSession == null) - { - return; - } - - var position = player.PlaybackSession.Position.TotalSeconds; - - var segmentIndex = Convert.ToInt32(Math.Ceiling(position / 360d)); - if (segmentIndex > _segmentIndex) - { - _segmentIndex = segmentIndex; - DanmakuViewModel.RequestNewSegmentDanmakuAsync(segmentIndex); - } - - if (player.PlaybackSession.PlaybackState != MediaPlaybackState.Playing) - { - return; - } - - try - { - var positionInt = Convert.ToInt32(position); - if (_danmakuDictionary.ContainsKey(positionInt)) - { - var data = _danmakuDictionary[positionInt]; - - if (DanmakuViewModel.IsDanmakuMerge) - { - data = data.Distinct(new DanmakuModelComparer()).ToList(); - } - - if (DanmakuViewModel.UseCloudShieldSettings && DanmakuViewModel.DanmakuConfig != null) - { - var isUseDefault = DanmakuViewModel.DanmakuConfig.PlayerConfig.DanmukuPlayerConfig.PlayerDanmakuUseDefaultConfig; - var defaultConfig = DanmakuViewModel.DanmakuConfig.PlayerConfig.DanmukuDefaultPlayerConfig; - var customCofig = DanmakuViewModel.DanmakuConfig.PlayerConfig.DanmukuPlayerConfig; - - var isSheldLevel = isUseDefault ? - defaultConfig.PlayerDanmakuAiRecommendedSwitch : customCofig.PlayerDanmakuAiRecommendedSwitch; - - if (isSheldLevel) - { - var shieldLevel = isUseDefault ? - defaultConfig.PlayerDanmakuAiRecommendedLevel : customCofig.PlayerDanmakuAiRecommendedLevel; - data = data.Where(p => p.Weight >= shieldLevel).ToList(); - } - - var list = DanmakuViewModel.DanmakuConfig.ReportFilterContent.ToList(); - } - - await Dispatcher.RunAsync(Windows.UI.Core.CoreDispatcherPriority.Normal, () => - { - foreach (var item in data) - { - _danmakuView.AddScreenDanmaku(item, false); - } - }); - } - } - catch (Exception) - { - } - } - - private void OnCursorTimerTickAsync(object sender, object e) - { - _cursorStayTime += 500; - if (_cursorStayTime > 1500) - { - Window.Current.CoreWindow.PointerCursor = null; - _cursorTimer.Stop(); - _cursorStayTime = 0; - } - } - } -} diff --git a/src/App/Controls/Player/BiliPlayerTransportControls/BiliPlayerTransportControls.xaml b/src/App/Controls/Player/BiliPlayerTransportControls/BiliPlayerTransportControls.xaml deleted file mode 100644 index 98301f9bb..000000000 --- a/src/App/Controls/Player/BiliPlayerTransportControls/BiliPlayerTransportControls.xaml +++ /dev/null @@ -1,1095 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/src/App/Controls/Player/DownloadOptionsPanel.xaml b/src/App/Controls/Player/DownloadOptionsPanel.xaml new file mode 100644 index 000000000..55ba8f264 --- /dev/null +++ b/src/App/Controls/Player/DownloadOptionsPanel.xaml @@ -0,0 +1,142 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - - - - - - - - - - - - - - - - diff --git a/src/App/Controls/Player/PlayerDashboard.xaml.cs b/src/App/Controls/Player/PlayerDashboard.xaml.cs deleted file mode 100644 index fe20da23e..000000000 --- a/src/App/Controls/Player/PlayerDashboard.xaml.cs +++ /dev/null @@ -1,111 +0,0 @@ -// Copyright (c) Richasy. All rights reserved. - -using System; -using Richasy.Bili.ViewModels.Uwp; -using Windows.UI.Xaml; - -namespace Richasy.Bili.App.Controls -{ - /// - /// 播放数据面板. - /// - public sealed partial class PlayerDashboard : PlayerComponent - { - private bool _isLikeHoldCompleted; - private PgcDetailView _detailView; - - /// - /// Initializes a new instance of the class. - /// - public PlayerDashboard() - { - this.InitializeComponent(); - } - - private async void OnLikeButtonHoldingCompletedAsync(object sender, System.EventArgs e) - { - _isLikeHoldCompleted = true; - await ViewModel.TripleAsync(); - CoinButton.ShowBubbles(); - FavoriteButton.ShowBubbles(); - } - - private void OnShareButtonClick(object sender, RoutedEventArgs e) - { - ViewModel.Share(); - } - - private void OnPgcDetailButtonClick(object sender, RoutedEventArgs e) - { - if (_detailView == null) - { - _detailView = new PgcDetailView(); - } - - _detailView.Show(); - } - - private async void OnFollowButtonClickAsync(object sender, RoutedEventArgs e) - { - ViewModel.IsFollow = !ViewModel.IsFollow; - ViewModel.IsFollow = !ViewModel.IsFollow; - - await ViewModel.ToggleFollowAsync(); - } - - private async void OnLikeButtonClickAsync(object sender, RoutedEventArgs e) - { - if (_isLikeHoldCompleted) - { - _isLikeHoldCompleted = false; - return; - } - - ViewModel.IsLikeChecked = !ViewModel.IsLikeChecked; - ViewModel.IsLikeChecked = !ViewModel.IsLikeChecked; - await ViewModel.LikeAsync(); - } - - private async void OnGiveCoinButtonClickAsync(object sender, RoutedEventArgs e) - { - var coinNumber = Convert.ToInt32((sender as FrameworkElement).Tag); - CoinFlyout.Hide(); - await ViewModel.CoinAsync(coinNumber, AlsoLikeCheckBox.IsChecked.Value); - } - - private void OnCoinButtonClick(object sender, RoutedEventArgs e) - { - ViewModel.IsCoinChecked = !ViewModel.IsCoinChecked; - ViewModel.IsCoinChecked = !ViewModel.IsCoinChecked; - - if (!ViewModel.IsCoinChecked) - { - AlsoLikeCheckBox.IsChecked = true; - CoinFlyout.ShowAt(CoinButton); - } - } - - private async void OnRefreshFavoriteButtonClickAsync(object sender, RoutedEventArgs e) - { - await ViewModel.LoadFavoritesAsync(); - } - - private async void OnRequestFavoriteButtonClickAsync(object sender, RoutedEventArgs e) - { - FavoriteFlyout.Hide(); - await ViewModel.FavoriteAsync(); - } - - private async void OnFavoriteButtonClickAsync(object sender, RoutedEventArgs e) - { - ViewModel.IsFavoriteChecked = !ViewModel.IsFavoriteChecked; - ViewModel.IsFavoriteChecked = !ViewModel.IsFavoriteChecked; - - if (AccountViewModel.Instance.Status == AccountViewModelStatus.Login) - { - FavoriteFlyout.ShowAt(FavoriteButton); - await ViewModel.LoadFavoritesAsync(); - } - } - } -} diff --git a/src/App/Controls/Player/PlayerDescriptor.xaml b/src/App/Controls/Player/PlayerDescriptor.xaml deleted file mode 100644 index 66abfc451..000000000 --- a/src/App/Controls/Player/PlayerDescriptor.xaml +++ /dev/null @@ -1,147 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/src/App/Controls/Player/PlayerDescriptor.xaml.cs b/src/App/Controls/Player/PlayerDescriptor.xaml.cs deleted file mode 100644 index 6ec8e808f..000000000 --- a/src/App/Controls/Player/PlayerDescriptor.xaml.cs +++ /dev/null @@ -1,33 +0,0 @@ -// Copyright (c) Richasy. All rights reserved. - -using Windows.UI.Xaml.Controls; - -namespace Richasy.Bili.App.Controls -{ - /// - /// 播放器说明组件,包括发布者,标题和说明文本等. - /// - public sealed partial class PlayerDescriptor : PlayerComponent - { - /// - /// Initializes a new instance of the class. - /// - public PlayerDescriptor() - { - this.InitializeComponent(); - } - - private async void OnUserTappedAsync(object sender, Windows.UI.Xaml.Input.TappedRoutedEventArgs e) - { - await UserView.Instance.ShowAsync(ViewModel.Publisher); - } - - private async void OnFollowButtonClickAsync(object sender, Windows.UI.Xaml.RoutedEventArgs e) - { - var btn = sender as Button; - btn.IsEnabled = false; - await ViewModel.Publisher.ToggleFollowStateAsync(); - btn.IsEnabled = true; - } - } -} diff --git a/src/App/Controls/Player/PlayerPagePanel/PlayerPagePanel.cs b/src/App/Controls/Player/PlayerPagePanel/PlayerPagePanel.cs new file mode 100644 index 000000000..5650982d9 --- /dev/null +++ b/src/App/Controls/Player/PlayerPagePanel/PlayerPagePanel.cs @@ -0,0 +1,282 @@ +// Copyright (c) Richasy. All rights reserved. + +using System; +using System.ComponentModel; +using System.Threading.Tasks; +using Bili.DI.Container; +using Bili.Models.App.Constants; +using Bili.Models.App.Other; +using Bili.Toolkit.Interfaces; +using Bili.ViewModels.Interfaces; +using Windows.UI.ViewManagement; +using Windows.UI.Xaml; +using Windows.UI.Xaml.Controls; +using Windows.UI.Xaml.Controls.Primitives; +using Windows.UI.Xaml.Markup; + +namespace Bili.App.Controls.Player +{ + /// + /// 播放页面面板. + /// + [ContentProperty(Name = "Player")] + public sealed class PlayerPagePanel : ReactiveControl + { + /// + /// 的依赖属性. + /// + public static readonly DependencyProperty PlayerProperty = + DependencyProperty.Register(nameof(Player), typeof(object), typeof(PlayerPagePanel), new PropertyMetadata(default)); + + /// + /// 的依赖属性. + /// + public static readonly DependencyProperty DashboardProperty = + DependencyProperty.Register(nameof(Dashboard), typeof(object), typeof(PlayerPagePanel), new PropertyMetadata(default)); + + /// + /// 的依赖属性. + /// + public static readonly DependencyProperty SectionHeaderItemsSourceProperty = + DependencyProperty.Register(nameof(SectionHeaderItemsSource), typeof(object), typeof(PlayerPagePanel), new PropertyMetadata(default)); + + /// + /// 的依赖属性. + /// + public static readonly DependencyProperty SectionContentProperty = + DependencyProperty.Register(nameof(SectionContent), typeof(object), typeof(PlayerPagePanel), new PropertyMetadata(default)); + + /// + /// 的依赖属性. + /// + public static readonly DependencyProperty SectionHeaderSelectedItemProperty = + DependencyProperty.Register(nameof(SectionHeaderSelectedItem), typeof(object), typeof(PlayerPagePanel), new PropertyMetadata(default)); + + /// + /// 的依赖属性. + /// + public static readonly DependencyProperty DescriptorProperty = + DependencyProperty.Register(nameof(Descriptor), typeof(object), typeof(PlayerPagePanel), new PropertyMetadata(default)); + + private readonly double _mediumWindowWidth; + private SplitView _splitView; + private ButtonBase _expandButton; + + /// + /// Initializes a new instance of the class. + /// + public PlayerPagePanel() + { + DefaultStyleKey = typeof(PlayerPagePanel); + _mediumWindowWidth = Locator.Instance.GetService().GetResource(AppConstants.MediumWindowThresholdWidthKey); + SizeChanged += OnSizeChanged; + Loaded += OnLoadedAsync; + Unloaded += OnUnloaded; + } + + /// + /// 区块标头被点击. + /// + public event EventHandler SectionHeaderItemInvoked; + + /// + /// 播放器组件. + /// + public object Player + { + get { return (object)GetValue(PlayerProperty); } + set { SetValue(PlayerProperty, value); } + } + + /// + /// 信息面板组件. + /// + public object Dashboard + { + get { return (object)GetValue(DashboardProperty); } + set { SetValue(DashboardProperty, value); } + } + + /// + /// 描述组件. + /// + public object Descriptor + { + get { return (object)GetValue(DescriptorProperty); } + set { SetValue(DescriptorProperty, value); } + } + + /// + /// 侧面板的头部数据源. + /// + public object SectionHeaderItemsSource + { + get { return (object)GetValue(SectionHeaderItemsSourceProperty); } + set { SetValue(SectionHeaderItemsSourceProperty, value); } + } + + /// + /// 侧面板头部选中条目. + /// + public object SectionHeaderSelectedItem + { + get { return (object)GetValue(SectionHeaderSelectedItemProperty); } + set { SetValue(SectionHeaderSelectedItemProperty, value); } + } + + /// + /// 侧面板的内容. + /// + public object SectionContent + { + get { return (object)GetValue(SectionContentProperty); } + set { SetValue(SectionContentProperty, value); } + } + + internal override void OnViewModelChanged(DependencyPropertyChangedEventArgs e) + { + if (e.OldValue is IPlayerPageViewModel oldVM) + { + oldVM.MediaPlayerViewModel.PropertyChanged -= OnViewModelPropertyChangedAsync; + oldVM.MediaPlayerViewModel.MediaPlayerChanged -= OnMediaPlayerChangedAsync; + } + + if (e.NewValue is IPlayerPageViewModel vm) + { + vm.MediaPlayerViewModel.PropertyChanged -= OnViewModelPropertyChangedAsync; + vm.MediaPlayerViewModel.PropertyChanged += OnViewModelPropertyChangedAsync; + vm.MediaPlayerViewModel.MediaPlayerChanged += OnMediaPlayerChangedAsync; + } + } + + /// + protected override void OnApplyTemplate() + { + _splitView = GetTemplateChild("RootView") as SplitView; + _expandButton = GetTemplateChild("ExpandButton") as ButtonBase; + var sectionView = GetTemplateChild("SectionNavigationView") as Microsoft.UI.Xaml.Controls.NavigationView; + + _expandButton.Click += OnExpandButtonClick; + sectionView.ItemInvoked += OnSectionViewItemInvoked; + } + + private async void OnViewModelPropertyChangedAsync(object sender, PropertyChangedEventArgs e) + { + if (e.PropertyName == nameof(ViewModel.MediaPlayerViewModel.DisplayMode)) + { + await ChangeVisualStateFromDisplayModeAsync(); + } + } + + private void OnSizeChanged(object sender, SizeChangedEventArgs e) + { + if (ViewModel.MediaPlayerViewModel.DisplayMode != Models.Enums.PlayerDisplayMode.Default) + { + return; + } + + var width = Window.Current.Bounds.Width; + if (width >= _mediumWindowWidth) + { + VisualStateManager.GoToState(this, "NormalState", false); + } + else + { + VisualStateManager.GoToState(this, "NarrowState", false); + } + } + + private void OnUnloaded(object sender, RoutedEventArgs e) + { + if (ViewModel?.MediaPlayerViewModel != null) + { + ViewModel.MediaPlayerViewModel.PropertyChanged -= OnViewModelPropertyChangedAsync; + ViewModel.MediaPlayerViewModel.MediaPlayerChanged -= OnMediaPlayerChangedAsync; + } + } + + private async void OnLoadedAsync(object sender, RoutedEventArgs e) + => await ChangeVisualStateFromDisplayModeAsync(); + + private async void OnMediaPlayerChangedAsync(object sender, object e) + => await ChangeVisualStateFromDisplayModeAsync(); + + private async Task ChangeVisualStateFromDisplayModeAsync() + { + var mediaVM = ViewModel?.MediaPlayerViewModel; + if (mediaVM == null) + { + return; + } + + var appView = ApplicationView.GetForCurrentView(); + if (mediaVM.DisplayMode == Models.Enums.PlayerDisplayMode.Default) + { + ToggleFullPlayerState(false); + if (appView.IsFullScreenMode) + { + appView.ExitFullScreenMode(); + } + else if (appView.ViewMode != ApplicationViewMode.Default) + { + await appView.TryEnterViewModeAsync(ApplicationViewMode.Default).AsTask(); + } + } + else if (mediaVM.DisplayMode == Models.Enums.PlayerDisplayMode.FullWindow) + { + ToggleFullPlayerState(true); + if (appView.IsFullScreenMode) + { + appView.ExitFullScreenMode(); + } + } + else if (mediaVM.DisplayMode == Models.Enums.PlayerDisplayMode.FullScreen) + { + ToggleFullPlayerState(true); + if (!appView.IsFullScreenMode) + { + appView.TryEnterFullScreenMode(); + } + } + else if (mediaVM.DisplayMode == Models.Enums.PlayerDisplayMode.CompactOverlay) + { + ToggleFullPlayerState(true); + if (appView.ViewMode != ApplicationViewMode.CompactOverlay) + { + await appView.TryEnterViewModeAsync(ApplicationViewMode.CompactOverlay).AsTask(); + } + } + } + + /// + /// 切换全视窗播放器状态. + /// + /// 是否将播放器扩展到全视窗. + private void ToggleFullPlayerState(bool isFullPlayer) + { + if (isFullPlayer) + { + VisualStateManager.GoToState(this, "FullPlayerState", false); + } + else + { + VisualStateManager.GoToState(this, "StandardState", false); + } + } + + private void OnExpandButtonClick(object sender, RoutedEventArgs e) + { + _splitView.IsPaneOpen = true; + if (_expandButton is ToggleButton btn) + { + btn.IsChecked = false; + } + } + + private void OnSectionViewItemInvoked(Microsoft.UI.Xaml.Controls.NavigationView sender, Microsoft.UI.Xaml.Controls.NavigationViewItemInvokedEventArgs args) + { + var item = args.InvokedItem as PlayerSectionHeader; + SectionHeaderItemInvoked?.Invoke(this, item); + } + } +} diff --git a/src/App/Controls/Player/PlayerPagePanel/PlayerPagePanel.xaml b/src/App/Controls/Player/PlayerPagePanel/PlayerPagePanel.xaml new file mode 100644 index 000000000..8e088cb6f --- /dev/null +++ b/src/App/Controls/Player/PlayerPagePanel/PlayerPagePanel.xaml @@ -0,0 +1,233 @@ + + + + + diff --git a/src/App/Controls/Player/Related/EpisodeView.xaml b/src/App/Controls/Player/Related/EpisodeView.xaml deleted file mode 100644 index 00fa0d121..000000000 --- a/src/App/Controls/Player/Related/EpisodeView.xaml +++ /dev/null @@ -1,70 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/src/App/Controls/Player/Related/EpisodeView.xaml.cs b/src/App/Controls/Player/Related/EpisodeView.xaml.cs deleted file mode 100644 index 229935481..000000000 --- a/src/App/Controls/Player/Related/EpisodeView.xaml.cs +++ /dev/null @@ -1,35 +0,0 @@ -// Copyright (c) Richasy. All rights reserved. - -using Richasy.Bili.ViewModels.Uwp; -using Windows.UI.Xaml; - -namespace Richasy.Bili.App.Controls.Player.Related -{ - /// - /// 分集视图. - /// - public sealed partial class EpisodeView : PlayerComponent - { - /// - /// Initializes a new instance of the class. - /// - public EpisodeView() - { - this.InitializeComponent(); - } - - private async void OnEpisodeItemClickAsync(object sender, RoutedEventArgs e) - { - var card = sender as CardPanel; - var data = card.DataContext as PgcEpisodeViewModel; - if (!data.Data.Equals(ViewModel.CurrentPgcEpisode)) - { - await ViewModel.ChangePgcEpisodeAsync(data.Data.Id); - } - else - { - data.IsSelected = true; - } - } - } -} diff --git a/src/App/Controls/Player/Related/LiveMessageView.xaml b/src/App/Controls/Player/Related/LiveMessageView.xaml deleted file mode 100644 index d19378303..000000000 --- a/src/App/Controls/Player/Related/LiveMessageView.xaml +++ /dev/null @@ -1,138 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/src/App/Controls/Player/Related/LiveMessageView.xaml.cs b/src/App/Controls/Player/Related/LiveMessageView.xaml.cs deleted file mode 100644 index 3650125d3..000000000 --- a/src/App/Controls/Player/Related/LiveMessageView.xaml.cs +++ /dev/null @@ -1,38 +0,0 @@ -// Copyright (c) Richasy. All rights reserved. - -using System; -using System.Threading.Tasks; - -namespace Richasy.Bili.App.Controls.Player.Related -{ - /// - /// 直播消息视图. - /// - public sealed partial class LiveMessageView : PlayerComponent - { - /// - /// Initializes a new instance of the class. - /// - public LiveMessageView() - { - this.InitializeComponent(); - ViewModel.RequestLiveMessageScrollToBottom += OnRequestLiveMessageScrollToBottomAsync; - } - - private async void OnRequestLiveMessageScrollToBottomAsync(object sender, EventArgs e) - { - await Task.Delay(50); - ScrollViewer.ChangeView(0, double.MaxValue, 1); - } - - private void OnViewChanged(object sender, Windows.UI.Xaml.Controls.ScrollViewerViewChangedEventArgs e) - { - if (!e.IsIntermediate) - { - // 这里的逻辑是,如果滚动到了底部,则表示允许视图自动滚动, - // 如果不在底部,表示用户自己滚动了视图,此时则不再自动滚动. - ViewModel.IsLiveMessageAutoScroll = ScrollViewer.VerticalOffset + ScrollViewer.ViewportHeight >= ScrollViewer.ExtentHeight - 50; - } - } - } -} diff --git a/src/App/Controls/Player/Related/PgcSectionView.xaml b/src/App/Controls/Player/Related/PgcSectionView.xaml deleted file mode 100644 index e6fa0f3b2..000000000 --- a/src/App/Controls/Player/Related/PgcSectionView.xaml +++ /dev/null @@ -1,78 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/src/App/Controls/Player/Related/PgcSectionView.xaml.cs b/src/App/Controls/Player/Related/PgcSectionView.xaml.cs deleted file mode 100644 index f41114356..000000000 --- a/src/App/Controls/Player/Related/PgcSectionView.xaml.cs +++ /dev/null @@ -1,34 +0,0 @@ -// Copyright (c) Richasy. All rights reserved. - -using Richasy.Bili.ViewModels.Uwp; - -namespace Richasy.Bili.App.Controls.Player.Related -{ - /// - /// PGC区块视图. - /// - public sealed partial class PgcSectionView : PlayerComponent - { - /// - /// Initializes a new instance of the class. - /// - public PgcSectionView() - { - this.InitializeComponent(); - } - - private async void OnEpisodeItemClickAsync(object sender, Windows.UI.Xaml.RoutedEventArgs e) - { - var card = sender as CardPanel; - var data = card.DataContext as PgcEpisodeViewModel; - if (!data.Data.Id.ToString().Equals(ViewModel.EpisodeId)) - { - await ViewModel.LoadAsync(data.Data); - } - else - { - data.IsSelected = true; - } - } - } -} diff --git a/src/App/Controls/Player/Related/PlayerReplyView.xaml b/src/App/Controls/Player/Related/PlayerReplyView.xaml deleted file mode 100644 index 24d8c7ae3..000000000 --- a/src/App/Controls/Player/Related/PlayerReplyView.xaml +++ /dev/null @@ -1,57 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/src/App/Controls/Player/Related/PlayerReplyView.xaml.cs b/src/App/Controls/Player/Related/PlayerReplyView.xaml.cs deleted file mode 100644 index facc7722d..000000000 --- a/src/App/Controls/Player/Related/PlayerReplyView.xaml.cs +++ /dev/null @@ -1,47 +0,0 @@ -// Copyright (c) Richasy. All rights reserved. - -using System.Threading.Tasks; -using Richasy.Bili.ViewModels.Uwp; -using Windows.UI.Xaml; -using Windows.UI.Xaml.Controls; - -namespace Richasy.Bili.App.Controls.Player.Related -{ - /// - /// 评论视图. - /// - public sealed partial class PlayerReplyView : UserControl - { - /// - /// 的依赖属性. - /// - public static readonly DependencyProperty ViewModelProperty = - DependencyProperty.Register(nameof(ViewModel), typeof(ReplyModuleViewModel), typeof(PlayerReplyView), new PropertyMetadata(ReplyModuleViewModel.Instance)); - - /// - /// Initializes a new instance of the class. - /// - public PlayerReplyView() - { - this.InitializeComponent(); - } - - /// - /// 视图模型. - /// - public ReplyModuleViewModel ViewModel - { - get { return (ReplyModuleViewModel)GetValue(ViewModelProperty); } - set { SetValue(ViewModelProperty, value); } - } - - /// - /// 检查评论初始化. - /// - /// . - public Task CheckInitializeAsync() - { - return ReplyView.InitializeAsync(ReplyModuleViewModel.Instance); - } - } -} diff --git a/src/App/Controls/Player/Related/RelatedVideoView.xaml b/src/App/Controls/Player/Related/RelatedVideoView.xaml deleted file mode 100644 index c9982ac1d..000000000 --- a/src/App/Controls/Player/Related/RelatedVideoView.xaml +++ /dev/null @@ -1,36 +0,0 @@ - - - - - - - - - - - - diff --git a/src/App/Controls/Player/Related/RelatedVideoView.xaml.cs b/src/App/Controls/Player/Related/RelatedVideoView.xaml.cs deleted file mode 100644 index 2423e367c..000000000 --- a/src/App/Controls/Player/Related/RelatedVideoView.xaml.cs +++ /dev/null @@ -1,18 +0,0 @@ -// Copyright (c) Richasy. All rights reserved. - -namespace Richasy.Bili.App.Controls.Player.Related -{ - /// - /// 关联视频视图. - /// - public sealed partial class RelatedVideoView : PlayerComponent - { - /// - /// Initializes a new instance of the class. - /// - public RelatedVideoView() - { - this.InitializeComponent(); - } - } -} diff --git a/src/App/Controls/Player/Related/SeasonView.xaml b/src/App/Controls/Player/Related/SeasonView.xaml deleted file mode 100644 index 0b1a1ec62..000000000 --- a/src/App/Controls/Player/Related/SeasonView.xaml +++ /dev/null @@ -1,72 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/src/App/Controls/Player/Related/SeasonView.xaml.cs b/src/App/Controls/Player/Related/SeasonView.xaml.cs deleted file mode 100644 index 3e7a5d47a..000000000 --- a/src/App/Controls/Player/Related/SeasonView.xaml.cs +++ /dev/null @@ -1,35 +0,0 @@ -// Copyright (c) Richasy. All rights reserved. - -using Richasy.Bili.ViewModels.Uwp; -using Windows.UI.Xaml; - -namespace Richasy.Bili.App.Controls.Player.Related -{ - /// - /// 剧集/系列视图. - /// - public sealed partial class SeasonView : PlayerComponent - { - /// - /// Initializes a new instance of the class. - /// - public SeasonView() - { - this.InitializeComponent(); - } - - private async void OnSeasonItemClickAsync(object sender, RoutedEventArgs e) - { - var card = sender as CardPanel; - var data = card.DataContext as PgcSeasonViewModel; - if (!data.Data.SeasonId.ToString().Equals(ViewModel.SeasonId)) - { - await ViewModel.LoadAsync(data.Data); - } - else - { - data.IsSelected = true; - } - } - } -} diff --git a/src/App/Controls/Player/Related/VideoPartView.xaml b/src/App/Controls/Player/Related/VideoPartView.xaml deleted file mode 100644 index 93b288381..000000000 --- a/src/App/Controls/Player/Related/VideoPartView.xaml +++ /dev/null @@ -1,59 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/src/App/Controls/Player/Related/VideoPartView.xaml.cs b/src/App/Controls/Player/Related/VideoPartView.xaml.cs deleted file mode 100644 index 7a5dd1c15..000000000 --- a/src/App/Controls/Player/Related/VideoPartView.xaml.cs +++ /dev/null @@ -1,34 +0,0 @@ -// Copyright (c) Richasy. All rights reserved. - -using Richasy.Bili.ViewModels.Uwp; - -namespace Richasy.Bili.App.Controls.Player.Related -{ - /// - /// 视频分集. - /// - public sealed partial class VideoPartView : PlayerComponent - { - /// - /// Initializes a new instance of the class. - /// - public VideoPartView() - { - this.InitializeComponent(); - } - - private async void OnPartItemClickAsync(object sender, Windows.UI.Xaml.RoutedEventArgs e) - { - var card = sender as CardPanel; - var data = card.DataContext as VideoPartViewModel; - if (!data.Data.Equals(ViewModel.CurrentVideoPart)) - { - await ViewModel.ChangeVideoPartAsync(data.Data.Page.Cid); - } - else - { - data.IsSelected = true; - } - } - } -} diff --git a/src/App/Controls/Player/RelatedVideoView.xaml b/src/App/Controls/Player/RelatedVideoView.xaml new file mode 100644 index 000000000..a4b3e8bfb --- /dev/null +++ b/src/App/Controls/Player/RelatedVideoView.xaml @@ -0,0 +1,35 @@ + + + + + + + + + + + + diff --git a/src/App/Controls/Player/RelatedVideoView.xaml.cs b/src/App/Controls/Player/RelatedVideoView.xaml.cs new file mode 100644 index 000000000..0684d2db6 --- /dev/null +++ b/src/App/Controls/Player/RelatedVideoView.xaml.cs @@ -0,0 +1,24 @@ +// Copyright (c) Richasy. All rights reserved. + +using Bili.ViewModels.Interfaces.Video; + +namespace Bili.App.Controls.Player +{ + /// + /// 关联视频视图. + /// + public sealed partial class RelatedVideoView : RelatedVideoViewBase + { + /// + /// Initializes a new instance of the class. + /// + public RelatedVideoView() => InitializeComponent(); + } + + /// + /// 的基类. + /// + public class RelatedVideoViewBase : ReactiveUserControl + { + } +} diff --git a/src/App/Controls/Player/SubtitleConfigPanel.xaml b/src/App/Controls/Player/SubtitleConfigPanel.xaml new file mode 100644 index 000000000..ca4581203 --- /dev/null +++ b/src/App/Controls/Player/SubtitleConfigPanel.xaml @@ -0,0 +1,44 @@ + + + + + + + + + + + + + + + + + + + + diff --git a/src/App/Controls/Player/SubtitleConfigPanel.xaml.cs b/src/App/Controls/Player/SubtitleConfigPanel.xaml.cs new file mode 100644 index 000000000..72caa54f3 --- /dev/null +++ b/src/App/Controls/Player/SubtitleConfigPanel.xaml.cs @@ -0,0 +1,44 @@ +// Copyright (c) Richasy. All rights reserved. + +using Bili.Models.Data.Player; +using Bili.ViewModels.Interfaces.Common; +using Windows.UI.Xaml; +using Windows.UI.Xaml.Controls; + +namespace Bili.App.Controls.Player +{ + /// + /// 字幕配置面板. + /// + public sealed partial class SubtitleConfigPanel : UserControl + { + /// + /// 的依赖属性. + /// + public static readonly DependencyProperty ViewModelProperty = + DependencyProperty.Register(nameof(ViewModel), typeof(ISubtitleModuleViewModel), typeof(SubtitleConfigPanel), new PropertyMetadata(default)); + + /// + /// Initializes a new instance of the class. + /// + public SubtitleConfigPanel() => InitializeComponent(); + + /// + /// 视图模型. + /// + public ISubtitleModuleViewModel ViewModel + { + get { return (ISubtitleModuleViewModel)GetValue(ViewModelProperty); } + set { SetValue(ViewModelProperty, value); } + } + + private void OnMetaItemClick(object sender, ItemClickEventArgs e) + { + var data = e.ClickedItem as SubtitleMeta; + if (ViewModel.CurrentMeta != data) + { + ViewModel.ChangeMetaCommand.Execute(data); + } + } + } +} diff --git a/src/App/Controls/Player/UgcSeasonView.xaml b/src/App/Controls/Player/UgcSeasonView.xaml new file mode 100644 index 000000000..e263fef85 --- /dev/null +++ b/src/App/Controls/Player/UgcSeasonView.xaml @@ -0,0 +1,61 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/App/Controls/Player/UgcSeasonView.xaml.cs b/src/App/Controls/Player/UgcSeasonView.xaml.cs new file mode 100644 index 000000000..bb30947ec --- /dev/null +++ b/src/App/Controls/Player/UgcSeasonView.xaml.cs @@ -0,0 +1,52 @@ +// Copyright (c) Richasy. All rights reserved. + +using System.Linq; +using Bili.Models.Data.Video; +using Bili.ViewModels.Interfaces.Video; + +namespace Bili.App.Controls.Player +{ + /// + /// 合集视图. + /// + public sealed partial class UgcSeasonView : UgcSeasonViewBase + { + /// + /// Initializes a new instance of the class. + /// + public UgcSeasonView() => InitializeComponent(); + + private void OnSeasonComboBoxSelectionChanged(object sender, Windows.UI.Xaml.Controls.SelectionChangedEventArgs e) + { + var season = SeasonComboBox.SelectedItem as VideoSeason; + if (ViewModel.CurrentSeason != season) + { + ViewModel.SelectSeasonCommand.Execute(season); + } + } + + private async void OnRepeaterLoadedAsync(object sender, Windows.UI.Xaml.RoutedEventArgs e) + { + await System.Threading.Tasks.Task.Delay(200); + if (ViewModel.IsShowUgcSeason) + { + var selectedVideo = ViewModel.CurrentSeasonVideos.FirstOrDefault(p => p.IsSelected); + if (selectedVideo != null) + { + var index = ViewModel.CurrentSeasonVideos.IndexOf(selectedVideo); + if (index > 0) + { + (sender as VerticalRepeaterView).ScrollToItem(index); + } + } + } + } + } + + /// + /// 的基类. + /// + public class UgcSeasonViewBase : ReactiveUserControl + { + } +} diff --git a/src/App/Controls/Player/VideoPartView.xaml b/src/App/Controls/Player/VideoPartView.xaml new file mode 100644 index 000000000..3f2f39679 --- /dev/null +++ b/src/App/Controls/Player/VideoPartView.xaml @@ -0,0 +1,109 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/App/Controls/Player/VideoPartView.xaml.cs b/src/App/Controls/Player/VideoPartView.xaml.cs new file mode 100644 index 000000000..5d35d694e --- /dev/null +++ b/src/App/Controls/Player/VideoPartView.xaml.cs @@ -0,0 +1,64 @@ +// Copyright (c) Richasy. All rights reserved. + +using System.Linq; +using Bili.ViewModels.Interfaces.Video; +using Windows.UI.Xaml; + +namespace Bili.App.Controls.Player +{ + /// + /// 视频分集. + /// + public sealed partial class VideoPartView : VideoPartViewBase + { + /// + /// Initializes a new instance of the class. + /// + public VideoPartView() => InitializeComponent(); + + private void OnPartItemClick(object sender, RoutedEventArgs e) + { + var card = sender as CardPanel; + var data = card.DataContext as IVideoIdentifierSelectableViewModel; + if (!data.Data.Equals(ViewModel.CurrentVideoPart)) + { + ViewModel.ChangeVideoPartCommand.Execute(data.Data); + } + else + { + data.IsSelected = true; + } + } + + private void RelocateSelectedItem() + { + var vm = ViewModel.VideoParts.FirstOrDefault(p => p.IsSelected); + if (vm != null) + { + var index = ViewModel.VideoParts.IndexOf(vm); + if (index >= 0) + { + PartRepeater.ScrollToItem(index); + if (ViewModel.IsOnlyShowIndex) + { + var ele = IndexRepeater.GetOrCreateElement(index); + if (ele != null) + { + ele.StartBringIntoView(new BringIntoViewOptions { VerticalAlignmentRatio = 0f }); + } + } + } + } + } + + private void OnPartRepeaterLoaded(object sender, RoutedEventArgs e) + => RelocateSelectedItem(); + } + + /// + /// 的基类. + /// + public class VideoPartViewBase : ReactiveUserControl + { + } +} diff --git a/src/App/Controls/Player/VideoPlaylistView.xaml b/src/App/Controls/Player/VideoPlaylistView.xaml new file mode 100644 index 000000000..0c903834f --- /dev/null +++ b/src/App/Controls/Player/VideoPlaylistView.xaml @@ -0,0 +1,32 @@ + + + + + + + + + + + + diff --git a/src/App/Controls/Player/VideoPlaylistView.xaml.cs b/src/App/Controls/Player/VideoPlaylistView.xaml.cs new file mode 100644 index 000000000..9e030b53a --- /dev/null +++ b/src/App/Controls/Player/VideoPlaylistView.xaml.cs @@ -0,0 +1,24 @@ +// Copyright (c) Richasy. All rights reserved. + +using Bili.ViewModels.Interfaces.Video; + +namespace Bili.App.Controls.Player +{ + /// + /// 稍后再看视频列表. + /// + public sealed partial class VideoPlaylistView : VideoPlaylistViewBase + { + /// + /// Initializes a new instance of the class. + /// + public VideoPlaylistView() => InitializeComponent(); + } + + /// + /// 的基类. + /// + public class VideoPlaylistViewBase : ReactiveUserControl + { + } +} diff --git a/src/App/Controls/ReactiveControl.cs b/src/App/Controls/ReactiveControl.cs new file mode 100644 index 000000000..45eda6d51 --- /dev/null +++ b/src/App/Controls/ReactiveControl.cs @@ -0,0 +1,43 @@ +// Copyright (c) Richasy. All rights reserved. + +using Windows.UI.Xaml; +using Windows.UI.Xaml.Controls; + +namespace Bili.App.Controls +{ + /// + /// 响应式控件基类. + /// + /// 控件对应的视图模型. + public class ReactiveControl : Control + where TViewModel : class + { + /// + /// 的依赖属性. + /// + public static readonly DependencyProperty ViewModelProperty = DependencyProperty + .Register(nameof(ViewModel), typeof(TViewModel), typeof(ReactiveControl), new PropertyMetadata(default, new PropertyChangedCallback((dp, args) => + { + var instance = dp as ReactiveControl; + instance.OnViewModelChanged(args); + }))); + + /// + /// 视图模型. + /// + public TViewModel ViewModel + { + get => (TViewModel)GetValue(ViewModelProperty); + set => SetValue(ViewModelProperty, value); + } + + /// + /// 当 改变时调用该方法,可由派生类重写. + /// + /// 依赖属性更改事件参数. + internal virtual void OnViewModelChanged(DependencyPropertyChangedEventArgs e) + { + // 什么都不做. + } + } +} diff --git a/src/App/Controls/ReactiveUserControl.cs b/src/App/Controls/ReactiveUserControl.cs new file mode 100644 index 000000000..5a0dee6a5 --- /dev/null +++ b/src/App/Controls/ReactiveUserControl.cs @@ -0,0 +1,43 @@ +// Copyright (c) Richasy. All rights reserved. + +using Windows.UI.Xaml; +using Windows.UI.Xaml.Controls; + +namespace Bili.App.Controls +{ + /// + /// 响应式用户控件基类. + /// + /// 控件对应的视图模型. + public class ReactiveUserControl : UserControl + where TViewModel : class + { + /// + /// 的依赖属性. + /// + public static readonly DependencyProperty ViewModelProperty = DependencyProperty + .Register(nameof(ViewModel), typeof(TViewModel), typeof(ReactiveUserControl), new PropertyMetadata(default, new PropertyChangedCallback((dp, args) => + { + var instance = dp as ReactiveUserControl; + instance.OnViewModelChanged(args); + }))); + + /// + /// 视图模型. + /// + public TViewModel ViewModel + { + get => (TViewModel)GetValue(ViewModelProperty); + set => SetValue(ViewModelProperty, value); + } + + /// + /// 当 改变时调用该方法,可由派生类重写. + /// + /// 依赖属性更改事件参数. + internal virtual void OnViewModelChanged(DependencyPropertyChangedEventArgs e) + { + // 什么都不做. + } + } +} diff --git a/src/App/Controls/Search/SearchArticleFilter.xaml b/src/App/Controls/Search/SearchArticleFilter.xaml deleted file mode 100644 index 8c787ccff..000000000 --- a/src/App/Controls/Search/SearchArticleFilter.xaml +++ /dev/null @@ -1,29 +0,0 @@ - - - - - - - - - - - diff --git a/src/App/Controls/Search/SearchArticleFilter.xaml.cs b/src/App/Controls/Search/SearchArticleFilter.xaml.cs deleted file mode 100644 index 429d7759b..000000000 --- a/src/App/Controls/Search/SearchArticleFilter.xaml.cs +++ /dev/null @@ -1,18 +0,0 @@ -// Copyright (c) Richasy. All rights reserved. - -namespace Richasy.Bili.App.Controls -{ - /// - /// 文章搜索过滤. - /// - public sealed partial class SearchArticleFilter : SearchComponent - { - /// - /// Initializes a new instance of the class. - /// - public SearchArticleFilter() - { - this.InitializeComponent(); - } - } -} diff --git a/src/App/Controls/Search/SearchArticleView.xaml b/src/App/Controls/Search/SearchArticleView.xaml index fc0e371f1..30ef74418 100644 --- a/src/App/Controls/Search/SearchArticleView.xaml +++ b/src/App/Controls/Search/SearchArticleView.xaml @@ -1,17 +1,22 @@  - + @@ -32,21 +37,16 @@ + ItemsSource="{x:Bind ItemsSource, Mode=OneWay}" + MinWideItemHeight="248"> + + + - - + + - - - - + diff --git a/src/App/Controls/Search/SearchArticleView.xaml.cs b/src/App/Controls/Search/SearchArticleView.xaml.cs index c51c894c8..44544dd1f 100644 --- a/src/App/Controls/Search/SearchArticleView.xaml.cs +++ b/src/App/Controls/Search/SearchArticleView.xaml.cs @@ -1,6 +1,6 @@ // Copyright (c) Richasy. All rights reserved. -namespace Richasy.Bili.App.Controls +namespace Bili.App.Controls { /// /// 搜索文章视图. @@ -10,14 +10,6 @@ public sealed partial class SearchArticleView : SearchComponent /// /// Initializes a new instance of the class. /// - public SearchArticleView() - { - this.InitializeComponent(); - } - - private async void OnArticleRefreshButtonClickAsync(object sender, Windows.UI.Xaml.RoutedEventArgs e) - { - await ViewModel.ArticleModule.InitializeRequestAsync(); - } + public SearchArticleView() => InitializeComponent(); } } diff --git a/src/App/Controls/Search/SearchComponent.cs b/src/App/Controls/Search/SearchComponent.cs index 58e9c7bce..09d2c579d 100644 --- a/src/App/Controls/Search/SearchComponent.cs +++ b/src/App/Controls/Search/SearchComponent.cs @@ -1,9 +1,11 @@ // Copyright (c) Richasy. All rights reserved. -using Richasy.Bili.ViewModels.Uwp; +using Bili.DI.Container; +using Bili.ViewModels.Interfaces.Core; +using Windows.UI.Xaml; using Windows.UI.Xaml.Controls; -namespace Richasy.Bili.App.Controls +namespace Bili.App.Controls { /// /// 搜索组件. @@ -11,18 +13,23 @@ namespace Richasy.Bili.App.Controls public class SearchComponent : UserControl { /// - /// 播放器视图模型. + /// 的依赖属性. /// - public SearchModuleViewModel ViewModel { get; } = SearchModuleViewModel.Instance; + public static readonly DependencyProperty ItemsSourceProperty = + DependencyProperty.Register(nameof(ItemsSource), typeof(object), typeof(SearchComponent), new PropertyMetadata(default)); /// - /// 请求增量加载. + /// 核心视图模型. /// - /// 事件发送者. - /// 事件参数. - protected async void OnViewRequestLoadMoreAsync(object sender, System.EventArgs e) + public IAppViewModel CoreViewModel { get; } = Locator.Instance.GetService(); + + /// + /// 数据源. + /// + public object ItemsSource { - await ViewModel.RequestLoadMoreAsync(); + get { return (object)GetValue(ItemsSourceProperty); } + set { SetValue(ItemsSourceProperty, value); } } } } diff --git a/src/App/Controls/Search/SearchLiveView.xaml b/src/App/Controls/Search/SearchLiveView.xaml index 8bfc13960..bba200c07 100644 --- a/src/App/Controls/Search/SearchLiveView.xaml +++ b/src/App/Controls/Search/SearchLiveView.xaml @@ -1,17 +1,22 @@  - + @@ -32,26 +37,17 @@ + MinWideItemWidth="200"> + + + - - + + - - - - + diff --git a/src/App/Controls/Search/SearchLiveView.xaml.cs b/src/App/Controls/Search/SearchLiveView.xaml.cs index 79971ef09..b7703c770 100644 --- a/src/App/Controls/Search/SearchLiveView.xaml.cs +++ b/src/App/Controls/Search/SearchLiveView.xaml.cs @@ -1,6 +1,6 @@ // Copyright (c) Richasy. All rights reserved. -namespace Richasy.Bili.App.Controls +namespace Bili.App.Controls { /// /// 搜索直播视图. @@ -10,14 +10,6 @@ public sealed partial class SearchLiveView : SearchComponent /// /// Initializes a new instance of the class. /// - public SearchLiveView() - { - this.InitializeComponent(); - } - - private async void OnLiveRefreshButtonClickAsync(object sender, Windows.UI.Xaml.RoutedEventArgs e) - { - await ViewModel.LiveModule.InitializeRequestAsync(); - } + public SearchLiveView() => InitializeComponent(); } } diff --git a/src/App/Controls/Search/SearchPgcView.xaml b/src/App/Controls/Search/SearchPgcView.xaml index 864abb7c7..193deff51 100644 --- a/src/App/Controls/Search/SearchPgcView.xaml +++ b/src/App/Controls/Search/SearchPgcView.xaml @@ -1,17 +1,22 @@  - + @@ -32,22 +37,17 @@ + MinWideItemWidth="300"> + + + - - + + - - - - + diff --git a/src/App/Controls/Search/SearchPgcView.xaml.cs b/src/App/Controls/Search/SearchPgcView.xaml.cs index 841aa7f2d..999f6c3da 100644 --- a/src/App/Controls/Search/SearchPgcView.xaml.cs +++ b/src/App/Controls/Search/SearchPgcView.xaml.cs @@ -1,41 +1,15 @@ // Copyright (c) Richasy. All rights reserved. -using Richasy.Bili.ViewModels.Uwp; -using Windows.UI.Xaml; - -namespace Richasy.Bili.App.Controls +namespace Bili.App.Controls { /// /// PGC搜索视图. /// public sealed partial class SearchPgcView : SearchComponent { - /// - /// 的依赖属性. - /// - public static readonly DependencyProperty SubModuleViewModelProperty = - DependencyProperty.Register(nameof(SubModuleViewModel), typeof(SearchSubModuleViewModel), typeof(SearchPgcView), new PropertyMetadata(null)); - /// /// Initializes a new instance of the class. /// - public SearchPgcView() - { - this.InitializeComponent(); - } - - /// - /// 子模块视图模型. - /// - public SearchSubModuleViewModel SubModuleViewModel - { - get { return (SearchSubModuleViewModel)GetValue(SubModuleViewModelProperty); } - set { SetValue(SubModuleViewModelProperty, value); } - } - - private async void OnPgcRefreshButtonClickAsync(object sender, RoutedEventArgs e) - { - await SubModuleViewModel.InitializeRequestAsync(); - } + public SearchPgcView() => InitializeComponent(); } } diff --git a/src/App/Controls/Search/SearchSuggestBox.xaml b/src/App/Controls/Search/SearchSuggestBox.xaml index 6c5624924..fc1f118cd 100644 --- a/src/App/Controls/Search/SearchSuggestBox.xaml +++ b/src/App/Controls/Search/SearchSuggestBox.xaml @@ -1,23 +1,27 @@ - + + + + - + + + + + + - + diff --git a/src/App/Controls/Search/SearchSuggestBox.xaml.cs b/src/App/Controls/Search/SearchSuggestBox.xaml.cs index 149e7137f..2689a393b 100644 --- a/src/App/Controls/Search/SearchSuggestBox.xaml.cs +++ b/src/App/Controls/Search/SearchSuggestBox.xaml.cs @@ -1,79 +1,76 @@ // Copyright (c) Richasy. All rights reserved. -using Richasy.Bili.Models.BiliBili; -using Richasy.Bili.ViewModels.Uwp; +using Bili.DI.Container; +using Bili.Models.Data.Search; +using Bili.ViewModels.Interfaces.Search; using Windows.UI.Xaml; using Windows.UI.Xaml.Controls; -namespace Richasy.Bili.App.Controls +namespace Bili.App.Controls { /// /// 搜索框. /// - public sealed partial class SearchSuggestBox : UserControl + public sealed partial class SearchSuggestBox : SearchSuggestBoxBase { - /// - /// 的依赖属性. - /// - public static readonly DependencyProperty ViewModelProperty = - DependencyProperty.Register(nameof(ViewModel), typeof(SearchModuleViewModel), typeof(SearchSuggestBox), new PropertyMetadata(SearchModuleViewModel.Instance)); - /// /// Initializes a new instance of the class. /// public SearchSuggestBox() { - this.InitializeComponent(); - this.Loaded += OnLoadedAsync; - } - - /// - /// 视图模型. - /// - public SearchModuleViewModel ViewModel - { - get { return (SearchModuleViewModel)GetValue(ViewModelProperty); } - set { SetValue(ViewModelProperty, value); } - } - - private async void OnLoadedAsync(object sender, RoutedEventArgs e) - { - await ViewModel.LoadHostSearchAsync(); + InitializeComponent(); + ViewModel = Locator.Instance.GetService(); + DataContext = ViewModel; } private void OnHotItemClick(object sender, ItemClickEventArgs e) { - var item = e.ClickedItem as SearchRecommendItem; - ViewModel.InputWords = item.Keyword; - AppViewModel.Instance.SetOverlayContentId(Models.Enums.PageIds.Search); + SelectSuggestItem(e.ClickedItem); HotSearchFlyout.Hide(); } private void OnSizeChanged(object sender, SizeChangedEventArgs e) { var width = e.NewSize.Width; - HotSearchListView.Width = width - 16; - } - private void OnHotSearchOpening(object sender, object e) - { - if (!ViewModel.IsHotSearchFlyoutEnabled) + if (width > 16) { - (sender as Flyout).Hide(); + HotSearchListView.Width = width - 16; } } private void OnSearchBoxSubmitted(AutoSuggestBox sender, AutoSuggestBoxQuerySubmittedEventArgs args) { + if (args.ChosenSuggestion is SearchSuggest) + { + SelectSuggestItem(args.ChosenSuggestion); + } + if (!string.IsNullOrEmpty(sender.Text)) { - AppViewModel.Instance.SetOverlayContentId(Models.Enums.PageIds.Search); + ViewModel.SearchCommand.Execute(sender.Text); } } + private void SelectSuggestItem(object suggestObj) + { + var data = suggestObj as SearchSuggest; + ViewModel.SelectSuggestCommand.Execute(data); + } + private void OnHotSearchButtonClick(object sender, RoutedEventArgs e) { - HotSearchFlyout.ShowAt(AppSearchBox); + if (ViewModel.HotSearchCollection.Count > 0) + { + HotSearchFlyout.ShowAt(AppSearchBox); + } } } + + /// + /// 的基类. + /// + public class SearchSuggestBoxBase : ReactiveUserControl + { + } } diff --git a/src/App/Controls/Search/SearchUserFilter.xaml b/src/App/Controls/Search/SearchUserFilter.xaml deleted file mode 100644 index b8c0df730..000000000 --- a/src/App/Controls/Search/SearchUserFilter.xaml +++ /dev/null @@ -1,29 +0,0 @@ - - - - - - - - - - - diff --git a/src/App/Controls/Search/SearchUserFilter.xaml.cs b/src/App/Controls/Search/SearchUserFilter.xaml.cs deleted file mode 100644 index 363e378df..000000000 --- a/src/App/Controls/Search/SearchUserFilter.xaml.cs +++ /dev/null @@ -1,18 +0,0 @@ -// Copyright (c) Richasy. All rights reserved. - -namespace Richasy.Bili.App.Controls -{ - /// - /// 用户筛选. - /// - public sealed partial class SearchUserFilter : SearchComponent - { - /// - /// Initializes a new instance of the class. - /// - public SearchUserFilter() - { - this.InitializeComponent(); - } - } -} diff --git a/src/App/Controls/Search/SearchUserView.xaml b/src/App/Controls/Search/SearchUserView.xaml index cd3bc24aa..038a548c6 100644 --- a/src/App/Controls/Search/SearchUserView.xaml +++ b/src/App/Controls/Search/SearchUserView.xaml @@ -1,35 +1,37 @@  - + + MinWideItemWidth="180"> + + + - - + + - - - - + diff --git a/src/App/Controls/Search/SearchUserView.xaml.cs b/src/App/Controls/Search/SearchUserView.xaml.cs index 14d0318ab..f77f50f14 100644 --- a/src/App/Controls/Search/SearchUserView.xaml.cs +++ b/src/App/Controls/Search/SearchUserView.xaml.cs @@ -1,8 +1,6 @@ // Copyright (c) Richasy. All rights reserved. -using Windows.UI.Xaml; - -namespace Richasy.Bili.App.Controls +namespace Bili.App.Controls { /// /// 搜索用户视图. @@ -12,19 +10,6 @@ public sealed partial class SearchUserView : SearchComponent /// /// Initializes a new instance of the class. /// - public SearchUserView() - { - this.InitializeComponent(); - } - - private async void OnUserRefreshButtonClickAsync(object sender, RoutedEventArgs e) - { - await ViewModel.UserModule.InitializeRequestAsync(); - } - - private async void OnUserCardClickAsync(object sender, System.EventArgs e) - { - await UserView.Instance.ShowAsync((sender as UserSlimCard).ViewModel); - } + public SearchUserView() => InitializeComponent(); } } diff --git a/src/App/Controls/Search/SearchVideoFilter.xaml b/src/App/Controls/Search/SearchVideoFilter.xaml deleted file mode 100644 index 5a06bd1cb..000000000 --- a/src/App/Controls/Search/SearchVideoFilter.xaml +++ /dev/null @@ -1,36 +0,0 @@ - - - - - - - - - - - - - diff --git a/src/App/Controls/Search/SearchVideoFilter.xaml.cs b/src/App/Controls/Search/SearchVideoFilter.xaml.cs deleted file mode 100644 index 19eed851f..000000000 --- a/src/App/Controls/Search/SearchVideoFilter.xaml.cs +++ /dev/null @@ -1,18 +0,0 @@ -// Copyright (c) Richasy. All rights reserved. - -namespace Richasy.Bili.App.Controls -{ - /// - /// 视频筛选组件. - /// - public sealed partial class SearchVideoFilter : SearchComponent - { - /// - /// Initializes a new instance of the class. - /// - public SearchVideoFilter() - { - this.InitializeComponent(); - } - } -} diff --git a/src/App/Controls/Search/SearchVideoView.xaml b/src/App/Controls/Search/SearchVideoView.xaml index 29ae871ac..11d7a9d4c 100644 --- a/src/App/Controls/Search/SearchVideoView.xaml +++ b/src/App/Controls/Search/SearchVideoView.xaml @@ -1,17 +1,22 @@  - + @@ -32,23 +37,15 @@ + ItemsSource="{x:Bind ItemsSource, Mode=OneWay}"> + + + - - + + - - - - + diff --git a/src/App/Controls/Search/SearchVideoView.xaml.cs b/src/App/Controls/Search/SearchVideoView.xaml.cs index 80d39233e..1cbc9c78b 100644 --- a/src/App/Controls/Search/SearchVideoView.xaml.cs +++ b/src/App/Controls/Search/SearchVideoView.xaml.cs @@ -1,8 +1,6 @@ // Copyright (c) Richasy. All rights reserved. -using Windows.UI.Xaml; - -namespace Richasy.Bili.App.Controls +namespace Bili.App.Controls { /// /// 搜索视频视图. @@ -14,12 +12,7 @@ public sealed partial class SearchVideoView : SearchComponent /// public SearchVideoView() { - this.InitializeComponent(); - } - - private async void OnVideoRefreshButtonClickAsync(object sender, RoutedEventArgs e) - { - await ViewModel.VideoModule.InitializeRequestAsync(); + InitializeComponent(); } } } diff --git a/src/App/Controls/Settings/CacheSettingSection.xaml b/src/App/Controls/Settings/CacheSettingSection.xaml new file mode 100644 index 000000000..0dfdf1ae1 --- /dev/null +++ b/src/App/Controls/Settings/CacheSettingSection.xaml @@ -0,0 +1,36 @@ + + + + + + + + - - + @@ -110,14 +116,19 @@ + + + - + diff --git a/src/App/Controls/User/AccountAvatar.xaml.cs b/src/App/Controls/User/AccountAvatar.xaml.cs index 5ce159b49..538148b99 100644 --- a/src/App/Controls/User/AccountAvatar.xaml.cs +++ b/src/App/Controls/User/AccountAvatar.xaml.cs @@ -2,43 +2,34 @@ using System; using System.ComponentModel; -using Richasy.Bili.App.Resources.Extension; -using Richasy.Bili.ViewModels.Uwp; +using Bili.App.Resources.Extension; +using Bili.DI.Container; +using Bili.Models.Enums; +using Bili.ViewModels.Interfaces.Account; +using Bili.ViewModels.Interfaces.Core; using Windows.System; using Windows.UI.Xaml; -using Windows.UI.Xaml.Controls; using Windows.UI.Xaml.Controls.Primitives; -namespace Richasy.Bili.App.Controls +namespace Bili.App.Controls { /// /// 账户管理中枢. /// - public sealed partial class AccountAvatar : UserControl + public sealed partial class AccountAvatar : AccountAvatarBase { - /// - /// 的依赖属性. - /// - public static readonly DependencyProperty ViewModelProperty = - DependencyProperty.Register(nameof(ViewModel), typeof(AccountViewModel), typeof(AccountAvatar), new PropertyMetadata(AccountViewModel.Instance)); + private readonly INavigationViewModel _navigationViewModel; /// /// Initializes a new instance of the class. /// public AccountAvatar() { - this.InitializeComponent(); - this.Loaded += OnLoaded; - this.Unloaded += OnUnloaded; - } - - /// - /// 账户视图模型. - /// - public AccountViewModel ViewModel - { - get { return (AccountViewModel)GetValue(ViewModelProperty); } - set { SetValue(ViewModelProperty, value); } + InitializeComponent(); + ViewModel = Locator.Instance.GetService(); + _navigationViewModel = Locator.Instance.GetService(); + Loaded += OnLoaded; + Unloaded += OnUnloaded; } private void OnLoaded(object sender, RoutedEventArgs e) @@ -48,13 +39,11 @@ private void OnLoaded(object sender, RoutedEventArgs e) } private void OnUnloaded(object sender, RoutedEventArgs e) - { - ViewModel.PropertyChanged -= OnViewModelPropertyChanged; - } + => ViewModel.PropertyChanged -= OnViewModelPropertyChanged; private void OnViewModelPropertyChanged(object sender, PropertyChangedEventArgs e) { - if (e.PropertyName == nameof(ViewModel.Status)) + if (e.PropertyName == nameof(ViewModel.State)) { CheckStatus(); } @@ -62,13 +51,13 @@ private void OnViewModelPropertyChanged(object sender, PropertyChangedEventArgs private void CheckStatus() { - switch (ViewModel.Status) + switch (ViewModel.State) { - case AccountViewModelStatus.Logout: - case AccountViewModelStatus.Login: + case AuthorizeState.SignedOut: + case AuthorizeState.SignedIn: VisualStateManager.GoToState(this, nameof(NormalState), false); break; - case AccountViewModelStatus.Logging: + case AuthorizeState.Loading: VisualStateManager.GoToState(this, nameof(LoadingState), false); break; default: @@ -76,56 +65,49 @@ private void CheckStatus() } } - private async void OnUserAvatarTappedAsync(object sender, Windows.UI.Xaml.Input.TappedRoutedEventArgs e) - { - if (ViewModel.Status == AccountViewModelStatus.Logout) - { - await ViewModel.TrySignInAsync(); - } - else - { - FlyoutBase.ShowAttachedFlyout(UserAvatar); - } - } - private void OnNavigateButtonClick(object sender, RoutedEventArgs e) { var btn = sender as FrameworkElement; var pageId = NavigationExtension.GetPageId(btn); - if (pageId == Models.Enums.PageIds.Favorite) - { - FavoriteViewModel.Instance.SetUser(ViewModel.Mid.Value, ViewModel.DisplayName); - } - - AppViewModel.Instance.SetOverlayContentId(pageId); + _navigationViewModel.NavigateToSecondaryView(pageId); HideFlyout(); } - private async void OnSignOutButtonClickAsync(object sender, RoutedEventArgs e) - { - HideFlyout(); - await AccountViewModel.Instance.SignOutAsync(); - } + private void OnSignOutButtonClick(object sender, RoutedEventArgs e) + => HideFlyout(); private async void OnNavigateToMyHomePageButtonClickAsync(object sender, RoutedEventArgs e) { HideFlyout(); - await Launcher.LaunchUriAsync(new Uri($"https://space.bilibili.com/{AccountViewModel.Instance.Mid}/")).AsTask(); + await Launcher.LaunchUriAsync(new Uri($"https://space.bilibili.com/{ViewModel.Mid}/")).AsTask(); } private void OnRequestCloseFlyout(object sender, EventArgs e) - { - HideFlyout(); - } + => HideFlyout(); private void HideFlyout() - { - FlyoutBase.GetAttachedFlyout(UserAvatar).Hide(); - } + => FlyoutBase.GetAttachedFlyout(UserAvatar).Hide(); - private async void OnFlyoutOpenedAsync(object sender, object e) + private void OnFlyoutOpened(object sender, object e) + => ViewModel.InitializeCommunityCommand.ExecuteAsync(null); + + private void OnUserAvatarClick(object sender, EventArgs e) { - await ViewModel.InitCommunityInformationAsync(); + if (ViewModel.State == AuthorizeState.SignedOut) + { + ViewModel.TrySignInCommand.ExecuteAsync(false); + } + else + { + FlyoutBase.ShowAttachedFlyout(UserAvatar); + } } } + + /// + /// 的基类. + /// + public class AccountAvatarBase : ReactiveUserControl + { + } } diff --git a/src/App/Controls/User/AccountPanel.xaml b/src/App/Controls/User/AccountPanel.xaml index 96def7617..5f5f95eff 100644 --- a/src/App/Controls/User/AccountPanel.xaml +++ b/src/App/Controls/User/AccountPanel.xaml @@ -1,10 +1,10 @@  + Text="{loc:Locale Name=Vip}" /> - + + SecondLineText="{loc:Locale Name=DynamicCount}" /> + SecondLineText="{loc:Locale Name=FollowCount}" /> + SecondLineText="{loc:Locale Name=FansCount}" /> diff --git a/src/App/Controls/User/AccountPanel.xaml.cs b/src/App/Controls/User/AccountPanel.xaml.cs index 3c58c4702..0dfacd561 100644 --- a/src/App/Controls/User/AccountPanel.xaml.cs +++ b/src/App/Controls/User/AccountPanel.xaml.cs @@ -1,11 +1,13 @@ // Copyright (c) Richasy. All rights reserved. using System; -using Richasy.Bili.ViewModels.Uwp; +using Bili.DI.Container; +using Bili.ViewModels.Interfaces.Account; +using Bili.ViewModels.Interfaces.Core; using Windows.UI.Xaml; using Windows.UI.Xaml.Controls; -namespace Richasy.Bili.App.Controls +namespace Bili.App.Controls { /// /// 账户面板. @@ -16,14 +18,17 @@ public sealed partial class AccountPanel : UserControl /// 的依赖属性. /// public static readonly DependencyProperty ViewModelProperty = - DependencyProperty.Register(nameof(ViewModel), typeof(AccountViewModel), typeof(AccountAvatar), new PropertyMetadata(AccountViewModel.Instance)); + DependencyProperty.Register(nameof(ViewModel), typeof(IAccountViewModel), typeof(AccountAvatar), new PropertyMetadata(Locator.Instance.GetService())); + + private readonly INavigationViewModel _navigationViewModel; /// /// Initializes a new instance of the class. /// public AccountPanel() { - this.InitializeComponent(); + InitializeComponent(); + _navigationViewModel = Locator.Instance.GetService(); } /// @@ -34,27 +39,28 @@ public AccountPanel() /// /// 账户视图模型. /// - public AccountViewModel ViewModel + public IAccountViewModel ViewModel { - get { return (AccountViewModel)GetValue(ViewModelProperty); } + get { return (IAccountViewModel)GetValue(ViewModelProperty); } set { SetValue(ViewModelProperty, value); } } - private async void OnDynamicButtonClickAsync(object sender, RoutedEventArgs e) + private void OnDynamicButtonClick(object sender, RoutedEventArgs e) { - await UserView.Instance.ShowAsync(AccountViewModel.Instance.Mid.Value); + var user = ViewModel.AccountInformation.User; + _navigationViewModel.NavigateToSecondaryView(Models.Enums.PageIds.UserSpace, user); RequestCloseFlyout?.Invoke(this, EventArgs.Empty); } - private async void OnFollowButtonClickAsync(object sender, RoutedEventArgs e) + private void OnFollowButtonClick(object sender, RoutedEventArgs e) { - await AppViewModel.Instance.EnterRelatedUserViewAsync(Models.Enums.App.RelatedUserType.Follows, ViewModel.Mid.Value, ViewModel.DisplayName); + _navigationViewModel.NavigateToSecondaryView(Models.Enums.PageIds.MyFollows); RequestCloseFlyout?.Invoke(this, EventArgs.Empty); } - private async void OnFollowerButtonClickAsync(object sender, RoutedEventArgs e) + private void OnFollowerButtonClick(object sender, RoutedEventArgs e) { - await AppViewModel.Instance.EnterRelatedUserViewAsync(Models.Enums.App.RelatedUserType.Fans, ViewModel.Mid.Value, ViewModel.DisplayName); + _navigationViewModel.NavigateToSecondaryView(Models.Enums.PageIds.Fans, ViewModel.AccountInformation.User); RequestCloseFlyout?.Invoke(this, EventArgs.Empty); } } diff --git a/src/App/Controls/User/UserAvatar.xaml b/src/App/Controls/User/UserAvatar.xaml index 423731d57..3d9016dde 100644 --- a/src/App/Controls/User/UserAvatar.xaml +++ b/src/App/Controls/User/UserAvatar.xaml @@ -1,23 +1,39 @@  - - + + + + + + - + diff --git a/src/App/Controls/User/UserAvatar.xaml.cs b/src/App/Controls/User/UserAvatar.xaml.cs index 550f33dda..013b72274 100644 --- a/src/App/Controls/User/UserAvatar.xaml.cs +++ b/src/App/Controls/User/UserAvatar.xaml.cs @@ -1,17 +1,23 @@ // Copyright (c) Richasy. All rights reserved. using System; +using System.Windows.Input; using Windows.UI.Xaml; using Windows.UI.Xaml.Controls; -using Windows.UI.Xaml.Media.Imaging; -namespace Richasy.Bili.App.Controls +namespace Bili.App.Controls { /// /// 用户头像. /// public sealed partial class UserAvatar : UserControl { + /// + /// 的依赖属性. + /// + public static readonly DependencyProperty CommandProperty = + DependencyProperty.Register(nameof(Command), typeof(ICommand), typeof(UserAvatar), new PropertyMetadata(default)); + /// /// 的依赖属性. /// @@ -22,7 +28,7 @@ public sealed partial class UserAvatar : UserControl /// 的依赖属性. /// public static readonly DependencyProperty AvatarProperty = - DependencyProperty.Register(nameof(Avatar), typeof(string), typeof(UserAvatar), new PropertyMetadata(string.Empty, new PropertyChangedCallback(OnAvatarChanged))); + DependencyProperty.Register(nameof(Avatar), typeof(string), typeof(UserAvatar), new PropertyMetadata(string.Empty)); /// /// 的依赖属性. @@ -30,14 +36,25 @@ public sealed partial class UserAvatar : UserControl public static readonly DependencyProperty DecodeSizeProperty = DependencyProperty.Register(nameof(DecodeSize), typeof(int), typeof(UserAvatar), new PropertyMetadata(50)); + /// + /// 的依赖属性. + /// + public static readonly DependencyProperty AvatarRadiusProperty = + DependencyProperty.Register(nameof(AvatarRadius), typeof(CornerRadius), typeof(UserAvatar), new PropertyMetadata(new CornerRadius(25))); + /// /// Initializes a new instance of the class. /// public UserAvatar() { - this.InitializeComponent(); + InitializeComponent(); } + /// + /// 点击时触发. + /// + public event EventHandler Click; + /// /// 用户名. /// @@ -65,17 +82,25 @@ public int DecodeSize set { SetValue(DecodeSizeProperty, value); } } - private static void OnAvatarChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) + /// + /// 命令. + /// + public ICommand Command + { + get { return (ICommand)GetValue(CommandProperty); } + set { SetValue(CommandProperty, value); } + } + + /// + /// 头像的圆角弧度. + /// + public CornerRadius AvatarRadius { - var instance = d as UserAvatar; - if (e.NewValue is string url && !string.IsNullOrEmpty(url)) - { - instance.PersonPicture.ProfilePicture = new BitmapImage(new Uri(url)) { DecodePixelWidth = instance.DecodeSize }; - } - else - { - instance.PersonPicture.ProfilePicture = null; - } + get { return (CornerRadius)GetValue(AvatarRadiusProperty); } + set { SetValue(AvatarRadiusProperty, value); } } + + private void OnClick(object sender, RoutedEventArgs e) + => Click?.Invoke(this, EventArgs.Empty); } } diff --git a/src/App/Controls/User/UserItem/UserItem.cs b/src/App/Controls/User/UserItem/UserItem.cs new file mode 100644 index 000000000..7628df4ab --- /dev/null +++ b/src/App/Controls/User/UserItem/UserItem.cs @@ -0,0 +1,17 @@ +// Copyright (c) Richasy. All rights reserved. + +using Bili.ViewModels.Interfaces.Account; + +namespace Bili.App.Controls.User +{ + /// + /// 用户条目. + /// + public sealed class UserItem : ReactiveControl + { + /// + /// Initializes a new instance of the class. + /// + public UserItem() => DefaultStyleKey = typeof(UserItem); + } +} diff --git a/src/App/Controls/User/UserItem/UserItem.xaml b/src/App/Controls/User/UserItem/UserItem.xaml new file mode 100644 index 000000000..74bb46e09 --- /dev/null +++ b/src/App/Controls/User/UserItem/UserItem.xaml @@ -0,0 +1,73 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/App/Pages/Desktop/Overlay/LivePlayerPage.xaml.cs b/src/App/Pages/Desktop/Overlay/LivePlayerPage.xaml.cs new file mode 100644 index 000000000..3fc92891c --- /dev/null +++ b/src/App/Pages/Desktop/Overlay/LivePlayerPage.xaml.cs @@ -0,0 +1,49 @@ +// Copyright (c) Richasy. All rights reserved. + +using Bili.App.Pages.Base; +using Bili.Models.Data.Local; +using Windows.UI.Xaml.Navigation; + +namespace Bili.App.Pages.Desktop.Overlay +{ + /// + /// 直播播放页面. + /// + public sealed partial class LivePlayerPage : LivePlayerPageBase + { + /// + /// Initializes a new instance of the class. + /// + public LivePlayerPage() => InitializeComponent(); + + /// + protected override void OnNavigatedTo(NavigationEventArgs e) + { + if (e.Parameter is PlaySnapshot shot) + { + ViewModel.SetSnapshot(shot); + } + } + + /// + protected override void OnNavigatingFrom(NavigatingCancelEventArgs e) + => ViewModel.ClearCommand.Execute(null); + + /// + protected override void OnPageLoaded() + => Bindings.Update(); + + /// + protected override void OnPageUnloaded() + => Bindings.StopTracking(); + + private void OnLiveOnlyAudioToggledAsync(object sender, Windows.UI.Xaml.RoutedEventArgs e) + { + var isAudioOnly = LiveAudioOnlySwitch.IsOn; + if (ViewModel.MediaPlayerViewModel.IsLiveAudioOnly != isAudioOnly) + { + ViewModel.MediaPlayerViewModel.ChangeLiveAudioOnlyCommand.Execute(isAudioOnly); + } + } + } +} diff --git a/src/App/Pages/Desktop/Overlay/MessagePage.xaml b/src/App/Pages/Desktop/Overlay/MessagePage.xaml new file mode 100644 index 000000000..e2fb312d0 --- /dev/null +++ b/src/App/Pages/Desktop/Overlay/MessagePage.xaml @@ -0,0 +1,127 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/App/Pages/Desktop/Overlay/MessagePage.xaml.cs b/src/App/Pages/Desktop/Overlay/MessagePage.xaml.cs new file mode 100644 index 000000000..411b8988d --- /dev/null +++ b/src/App/Pages/Desktop/Overlay/MessagePage.xaml.cs @@ -0,0 +1,43 @@ +// Copyright (c) Richasy. All rights reserved. + +using Bili.ViewModels.Interfaces.Community; + +namespace Bili.App.Pages.Desktop.Overlay +{ + /// + /// 消息页面. + /// + public sealed partial class MessagePage : MessagePageBase + { + /// + /// Initializes a new instance of the class. + /// + public MessagePage() => InitializeComponent(); + + /// + protected override void OnPageLoaded() + => Bindings.Update(); + + /// + protected override void OnPageUnloaded() + => Bindings.StopTracking(); + + private void OnNavItemInvokedAsync(Microsoft.UI.Xaml.Controls.NavigationView sender, Microsoft.UI.Xaml.Controls.NavigationViewItemInvokedEventArgs args) + { + ContentScrollViewer.ChangeView(0, 0, 1, true); + var data = args.InvokedItem as IMessageHeaderViewModel; + + if (data != ViewModel.CurrentType) + { + ViewModel.SelectTypeCommand.Execute(data); + } + } + } + + /// + /// 的基类. + /// + public class MessagePageBase : AppPage + { + } +} diff --git a/src/App/Pages/Desktop/Overlay/MyFollowsPage.xaml b/src/App/Pages/Desktop/Overlay/MyFollowsPage.xaml new file mode 100644 index 000000000..3b6988a9b --- /dev/null +++ b/src/App/Pages/Desktop/Overlay/MyFollowsPage.xaml @@ -0,0 +1,106 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/App/Pages/Desktop/Overlay/MyFollowsPage.xaml.cs b/src/App/Pages/Desktop/Overlay/MyFollowsPage.xaml.cs new file mode 100644 index 000000000..24f6bebe8 --- /dev/null +++ b/src/App/Pages/Desktop/Overlay/MyFollowsPage.xaml.cs @@ -0,0 +1,42 @@ +// Copyright (c) Richasy. All rights reserved. + +using Bili.Models.Data.Community; +using Bili.ViewModels.Interfaces.Account; + +namespace Bili.App.Pages.Desktop +{ + /// + /// 我的关注页面. + /// + public sealed partial class MyFollowsPage : MyFollowsPageBase + { + /// + /// Initializes a new instance of the class. + /// + public MyFollowsPage() => InitializeComponent(); + + /// + protected override void OnPageLoaded() + => Bindings.Update(); + + /// + protected override void OnPageUnloaded() + => Bindings.StopTracking(); + + private void OnNavItemInvoked(Microsoft.UI.Xaml.Controls.NavigationView sender, Microsoft.UI.Xaml.Controls.NavigationViewItemInvokedEventArgs args) + { + var data = args.InvokedItem as FollowGroup; + if (data != ViewModel.CurrentGroup) + { + ViewModel.SelectGroupCommand.Execute(data); + } + } + } + + /// + /// 的基类. + /// + public class MyFollowsPageBase : AppPage + { + } +} diff --git a/src/App/Pages/Desktop/Overlay/PgcIndexPage.xaml b/src/App/Pages/Desktop/Overlay/PgcIndexPage.xaml new file mode 100644 index 000000000..0fe905bcc --- /dev/null +++ b/src/App/Pages/Desktop/Overlay/PgcIndexPage.xaml @@ -0,0 +1,137 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/App/Pages/Desktop/Overlay/PgcIndexPage.xaml.cs b/src/App/Pages/Desktop/Overlay/PgcIndexPage.xaml.cs new file mode 100644 index 000000000..167654044 --- /dev/null +++ b/src/App/Pages/Desktop/Overlay/PgcIndexPage.xaml.cs @@ -0,0 +1,61 @@ +// Copyright (c) Richasy. All rights reserved. + +using System.Linq; +using Bili.Models.Data.Appearance; +using Bili.Models.Enums; +using Bili.ViewModels.Interfaces.Pgc; +using Windows.UI.Xaml.Controls; +using Windows.UI.Xaml.Navigation; + +namespace Bili.App.Pages.Desktop +{ + /// + /// PGC索引页面. + /// + public sealed partial class PgcIndexPage : PgcIndexPageBase + { + /// + /// Initializes a new instance of the class. + /// + public PgcIndexPage() => InitializeComponent(); + + /// + protected override void OnNavigatedTo(NavigationEventArgs e) + { + if (e.Parameter is PgcType type) + { + ViewModel.SetType(type); + } + } + + /// + protected override void OnPageLoaded() + => Bindings.Update(); + + /// + protected override void OnPageUnloaded() + => Bindings.StopTracking(); + + private void OnConditionChangedAsync(object sender, SelectionChangedEventArgs e) + { + var comboBox = sender as ComboBox; + if (comboBox.DataContext is IIndexFilterViewModel source + && comboBox.SelectedItem is Condition item) + { + var index = source.Data.Conditions.ToList().IndexOf(item); + if (index >= 0 && index != source.SelectedIndex) + { + source.SelectedIndex = index; + ViewModel.ReloadCommand.ExecuteAsync(null); + } + } + } + } + + /// + /// 的基类. + /// + public class PgcIndexPageBase : AppPage + { + } +} diff --git a/src/App/Pages/Desktop/Overlay/PgcPlayerPage.xaml b/src/App/Pages/Desktop/Overlay/PgcPlayerPage.xaml new file mode 100644 index 000000000..c83b1bbe4 --- /dev/null +++ b/src/App/Pages/Desktop/Overlay/PgcPlayerPage.xaml @@ -0,0 +1,338 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/App/Pages/Desktop/Overlay/PgcPlayerPage.xaml.cs b/src/App/Pages/Desktop/Overlay/PgcPlayerPage.xaml.cs new file mode 100644 index 000000000..409ab2cd7 --- /dev/null +++ b/src/App/Pages/Desktop/Overlay/PgcPlayerPage.xaml.cs @@ -0,0 +1,111 @@ +// Copyright (c) Richasy. All rights reserved. + +using System; +using Bili.App.Pages.Base; +using Bili.Models.Data.Local; +using Windows.UI.Xaml; +using Windows.UI.Xaml.Navigation; + +namespace Bili.App.Pages.Desktop.Overlay +{ + /// + /// PGC 内容的播放页面. + /// + public sealed partial class PgcPlayerPage : PgcPlayerPageBase + { + private bool _isLikeHoldCompleted; + private bool _isLikeHoldSuspend; + + /// + /// Initializes a new instance of the class. + /// + public PgcPlayerPage() => InitializeComponent(); + + /// + protected override void OnNavigatedTo(NavigationEventArgs e) + { + if (e.Parameter is PlaySnapshot shot) + { + ViewModel.SetSnapshot(shot); + } + } + + /// + protected override void OnNavigatingFrom(NavigatingCancelEventArgs e) + => ViewModel.ClearCommand.Execute(null); + + /// + protected override void OnPageLoaded() + => Bindings.Update(); + + /// + protected override void OnPageUnloaded() + => Bindings.StopTracking(); + + private void OnSectionHeaderItemInvoked(object sender, Models.App.Other.PlayerSectionHeader e) + { + if (ViewModel.CurrentSection != e) + { + ViewModel.CurrentSection = e; + } + } + + private void OnRefreshFavoriteButtonClickAsync(object sender, RoutedEventArgs e) + => ViewModel.RequestFavoriteFoldersCommand.ExecuteAsync(null); + + private void OnGiveCoinButtonClickAsync(object sender, RoutedEventArgs e) + { + var num = int.Parse((sender as FrameworkElement).Tag.ToString()); + ViewModel.CoinCommand.Execute(num); + } + + private void OnLikeButtonHoldingCompleted(object sender, System.EventArgs e) + { + _isLikeHoldCompleted = true; + ViewModel.TripleCommand.ExecuteAsync(null); + CoinButton.ShowBubbles(); + FavoriteButton.ShowBubbles(); + } + + private void OnLikeButtonHoldingSuspend(object sender, EventArgs e) + { + _isLikeHoldSuspend = true; + } + + private void OnLikeButtonClick(object sender, RoutedEventArgs e) + { + if (_isLikeHoldCompleted || _isLikeHoldSuspend) + { + _isLikeHoldCompleted = false; + _isLikeHoldSuspend = false; + return; + } + + ViewModel.LikeCommand.ExecuteAsync(null); + } + + private void OnCoinButtonClick(object sender, RoutedEventArgs e) + { + ViewModel.IsCoined = !ViewModel.IsCoined; + ViewModel.IsCoined = !ViewModel.IsCoined; + + if (!ViewModel.IsCoined) + { + CoinFlyout.ShowAt(CoinButton); + } + } + + private void OnFavoriteButtonClickAsync(object sender, RoutedEventArgs e) + { + ViewModel.IsFavorited = !ViewModel.IsFavorited; + ViewModel.IsFavorited = !ViewModel.IsFavorited; + + if (ViewModel.FavoriteFolders.Count == 0) + { + ViewModel.RequestFavoriteFoldersCommand.ExecuteAsync(null); + } + + FavoriteFlyout.ShowAt(FavoriteButton); + } + } +} diff --git a/src/App/Pages/Desktop/Overlay/SearchPage.xaml b/src/App/Pages/Desktop/Overlay/SearchPage.xaml new file mode 100644 index 000000000..126255bb1 --- /dev/null +++ b/src/App/Pages/Desktop/Overlay/SearchPage.xaml @@ -0,0 +1,176 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/App/Pages/Desktop/Overlay/SearchPage.xaml.cs b/src/App/Pages/Desktop/Overlay/SearchPage.xaml.cs new file mode 100644 index 000000000..f0060bdb6 --- /dev/null +++ b/src/App/Pages/Desktop/Overlay/SearchPage.xaml.cs @@ -0,0 +1,64 @@ +// Copyright (c) Richasy. All rights reserved. + +using Bili.App.Pages.Base; +using Bili.Models.Data.Appearance; +using Bili.ViewModels.Interfaces.Search; +using Windows.UI.Xaml.Controls; +using Windows.UI.Xaml.Navigation; + +namespace Bili.App.Pages.Desktop.Overlay +{ + /// + /// 搜索页面. + /// + public sealed partial class SearchPage : SearchPageBase + { + /// + /// Initializes a new instance of the class. + /// + public SearchPage() => InitializeComponent(); + + /// + protected override void OnPageLoaded() + => Bindings.Update(); + + /// + protected override void OnPageUnloaded() + => Bindings.StopTracking(); + + /// + protected override void OnNavigatedTo(NavigationEventArgs e) + { + if (e.NavigationMode != NavigationMode.Back && e.Parameter is string keyword) + { + ViewModel.SetKeyword(keyword); + } + } + + private void OnNavItemInvokedAsync(Microsoft.UI.Xaml.Controls.NavigationView sender, Microsoft.UI.Xaml.Controls.NavigationViewItemInvokedEventArgs args) + { + var item = args.InvokedItem as ISearchModuleItemViewModel; + if (item != ViewModel.CurrentModule) + { + ViewModel.SelectModuleCommand.Execute(item); + } + } + + private void OnFilterItemSelectionChanged(object sender, SelectionChangedEventArgs e) + { + var comboBox = sender as ComboBox; + if (comboBox.DataContext is not ISearchFilterViewModel context) + { + return; + } + + var selectItem = comboBox.SelectedItem as Condition; + if (selectItem != context.CurrentCondition && selectItem != null) + { + // 条件变更,重载模块. + context.CurrentCondition = selectItem; + ViewModel.ReloadModuleCommand.ExecuteAsync(null); + } + } + } +} diff --git a/src/App/Pages/Desktop/Overlay/TimeLinePage.xaml b/src/App/Pages/Desktop/Overlay/TimeLinePage.xaml new file mode 100644 index 000000000..51d445c6e --- /dev/null +++ b/src/App/Pages/Desktop/Overlay/TimeLinePage.xaml @@ -0,0 +1,117 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/App/Pages/Desktop/Overlay/TimeLinePage.xaml.cs b/src/App/Pages/Desktop/Overlay/TimeLinePage.xaml.cs new file mode 100644 index 000000000..70431a822 --- /dev/null +++ b/src/App/Pages/Desktop/Overlay/TimeLinePage.xaml.cs @@ -0,0 +1,43 @@ +// Copyright (c) Richasy. All rights reserved. + +using Bili.Models.Enums; +using Bili.ViewModels.Interfaces.Pgc; +using Windows.UI.Xaml.Navigation; + +namespace Bili.App.Pages.Desktop.Overlay +{ + /// + /// 时间线页面. + /// + public sealed partial class TimelinePage : TimelinePageBase + { + /// + /// Initializes a new instance of the class. + /// + public TimelinePage() => InitializeComponent(); + + /// + protected override void OnNavigatedTo(NavigationEventArgs e) + { + if (e.Parameter is PgcType type) + { + ViewModel.SetType(type); + } + } + + /// + protected override void OnPageLoaded() + => Bindings.Update(); + + /// + protected override void OnPageUnloaded() + => Bindings.StopTracking(); + } + + /// + /// 的基类. + /// + public class TimelinePageBase : AppPage + { + } +} diff --git a/src/App/Pages/Desktop/Overlay/UserSpacePage.xaml b/src/App/Pages/Desktop/Overlay/UserSpacePage.xaml new file mode 100644 index 000000000..2f483fa40 --- /dev/null +++ b/src/App/Pages/Desktop/Overlay/UserSpacePage.xaml @@ -0,0 +1,245 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/App/Pages/Desktop/Overlay/VideoPlayerPage.xaml.cs b/src/App/Pages/Desktop/Overlay/VideoPlayerPage.xaml.cs new file mode 100644 index 000000000..b96699481 --- /dev/null +++ b/src/App/Pages/Desktop/Overlay/VideoPlayerPage.xaml.cs @@ -0,0 +1,148 @@ +// Copyright (c) Richasy. All rights reserved. + +using System; +using System.Collections.Generic; +using Bili.App.Controls.Dialogs; +using Bili.App.Pages.Base; +using Bili.DI.Container; +using Bili.Models.Data.Community; +using Bili.Models.Data.Local; +using Bili.Models.Data.Video; +using Bili.Toolkit.Interfaces; +using Windows.UI.Xaml; +using Windows.UI.Xaml.Controls; +using Windows.UI.Xaml.Navigation; + +namespace Bili.App.Pages.Desktop.Overlay +{ + /// + /// 视频播放页面. + /// + public sealed partial class VideoPlayerPage : VideoPlayerPageBase + { + private bool _isLikeHoldCompleted; + private bool _isLikeHoldSuspend; + + /// + /// Initializes a new instance of the class. + /// + public VideoPlayerPage() => InitializeComponent(); + + /// + protected override void OnNavigatedTo(NavigationEventArgs e) + { + if (e.Parameter is PlaySnapshot shot) + { + ViewModel.SetSnapshot(shot); + } + else if (e.Parameter is Tuple, int> playlist) + { + ViewModel.SetPlaylist(playlist.Item1, playlist.Item2); + } + } + + /// + protected override void OnNavigatingFrom(NavigatingCancelEventArgs e) + { + ViewModel.ClearCommand.Execute(null); + ViewModel.ClearPlaylistCommand.Execute(null); + } + + /// + protected override void OnPageLoaded() + => Bindings.Update(); + + /// + protected override void OnPageUnloaded() + => Bindings.StopTracking(); + + private void OnSectionHeaderItemInvoked(object sender, Models.App.Other.PlayerSectionHeader e) + { + if (ViewModel.CurrentSection != e) + { + ViewModel.CurrentSection = e; + } + } + + private void OnRefreshFavoriteButtonClick(object sender, RoutedEventArgs e) + => ViewModel.RequestFavoriteFoldersCommand.ExecuteAsync(null); + + private void OnGiveCoinButtonClick(object sender, RoutedEventArgs e) + { + var num = int.Parse((sender as FrameworkElement).Tag.ToString()); + ViewModel.CoinCommand.ExecuteAsync(num); + CoinFlyout.Hide(); + } + + private async void OnTagButtonClickAsync(object sender, RoutedEventArgs e) + { + var data = (sender as FrameworkElement).DataContext as Tag; + var settingsToolkit = Locator.Instance.GetService(); + var resourceToolkit = Locator.Instance.GetService(); + var isFirstClick = settingsToolkit.ReadLocalSetting(Models.Enums.SettingNames.IsFirstClickTag, true); + + if (isFirstClick) + { + var dialog = new ConfirmDialog(resourceToolkit.GetLocaleString(Models.Enums.LanguageNames.FirstClickTagTip)); + var result = await dialog.ShowAsync(); + if (result != ContentDialogResult.Primary) + { + return; + } + + settingsToolkit.WriteLocalSetting(Models.Enums.SettingNames.IsFirstClickTag, false); + } + + ViewModel.SearchTagCommand.Execute(data); + } + + private void OnLikeButtonHoldingCompleted(object sender, EventArgs e) + { + _isLikeHoldCompleted = true; + ViewModel.TripleCommand.ExecuteAsync(null); + CoinButton.ShowBubbles(); + FavoriteButton.ShowBubbles(); + } + + private void OnLikeButtonHoldingSuspend(object sender, EventArgs e) + { + _isLikeHoldSuspend = true; + } + + private void OnLikeButtonClick(object sender, RoutedEventArgs e) + { + if (_isLikeHoldCompleted || _isLikeHoldSuspend) + { + _isLikeHoldCompleted = false; + _isLikeHoldSuspend = false; + return; + } + + ViewModel.LikeCommand.ExecuteAsync(null); + } + + private void OnCoinButtonClick(object sender, RoutedEventArgs e) + { + ViewModel.IsCoined = !ViewModel.IsCoined; + ViewModel.IsCoined = !ViewModel.IsCoined; + + if (!ViewModel.IsCoined) + { + CoinFlyout.ShowAt(CoinButton); + } + } + + private void OnFavoriteButtonClick(object sender, RoutedEventArgs e) + { + ViewModel.IsFavorited = !ViewModel.IsFavorited; + ViewModel.IsFavorited = !ViewModel.IsFavorited; + + if (ViewModel.FavoriteFolders.Count == 0) + { + ViewModel.RequestFavoriteFoldersCommand.ExecuteAsync(null); + } + + FavoriteFlyout.ShowAt(FavoriteButton); + } + } +} diff --git a/src/App/Pages/Desktop/Overlay/ViewLaterPage.xaml b/src/App/Pages/Desktop/Overlay/ViewLaterPage.xaml new file mode 100644 index 000000000..3ce92f9a4 --- /dev/null +++ b/src/App/Pages/Desktop/Overlay/ViewLaterPage.xaml @@ -0,0 +1,130 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/App/Pages/Desktop/Overlay/ViewLaterPage.xaml.cs b/src/App/Pages/Desktop/Overlay/ViewLaterPage.xaml.cs new file mode 100644 index 000000000..7adee4337 --- /dev/null +++ b/src/App/Pages/Desktop/Overlay/ViewLaterPage.xaml.cs @@ -0,0 +1,62 @@ +// Copyright (c) Richasy. All rights reserved. + +using System; +using Bili.App.Controls.Dialogs; +using Bili.DI.Container; +using Bili.Models.Enums; +using Bili.Toolkit.Interfaces; +using Bili.ViewModels.Interfaces.Account; +using Windows.UI.Xaml; +using Windows.UI.Xaml.Controls; + +namespace Bili.App.Pages.Desktop.Overlay +{ + /// + /// 稍后再看页面. + /// + public sealed partial class ViewLaterPage : ViewLaterPageBase + { + /// + /// Initializes a new instance of the class. + /// + public ViewLaterPage() => InitializeComponent(); + + /// + protected override void OnPageLoaded() + => Bindings.Update(); + + /// + protected override void OnPageUnloaded() + => Bindings.StopTracking(); + + private async void OnClearButtonClickAsync(object sender, RoutedEventArgs e) + { + var isClear = false; + if (ViewModel.Items.Count > 0) + { + // Show dialog. + var msg = Locator.Instance + .GetService() + .GetLocaleString(LanguageNames.ClearViewLaterWarning); + var dialog = new ConfirmDialog(msg); + var result = await dialog.ShowAsync().AsTask(); + if (result == ContentDialogResult.Primary) + { + isClear = true; + } + } + + if (isClear) + { + _ = ViewModel.ClearCommand.ExecuteAsync(null); + } + } + } + + /// + /// 的基类. + /// + public class ViewLaterPageBase : AppPage + { + } +} diff --git a/src/App/Pages/Desktop/PopularPage.xaml b/src/App/Pages/Desktop/PopularPage.xaml new file mode 100644 index 000000000..bd3f3663a --- /dev/null +++ b/src/App/Pages/Desktop/PopularPage.xaml @@ -0,0 +1,81 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/App/Pages/Desktop/PopularPage.xaml.cs b/src/App/Pages/Desktop/PopularPage.xaml.cs new file mode 100644 index 000000000..8fe111377 --- /dev/null +++ b/src/App/Pages/Desktop/PopularPage.xaml.cs @@ -0,0 +1,26 @@ +// Copyright (c) Richasy. All rights reserved. + +using Bili.App.Pages.Base; + +namespace Bili.App.Pages.Desktop +{ + /// + /// 热门视频页面. + /// + public sealed partial class PopularPage : PopularPageBase + { + /// + /// Initializes a new instance of the class. + /// + public PopularPage() + : base() => InitializeComponent(); + + /// + protected override void OnPageLoaded() + => Bindings.Update(); + + /// + protected override void OnPageUnloaded() + => Bindings.StopTracking(); + } +} diff --git a/src/App/Pages/Desktop/RankPage.xaml b/src/App/Pages/Desktop/RankPage.xaml new file mode 100644 index 000000000..351c00969 --- /dev/null +++ b/src/App/Pages/Desktop/RankPage.xaml @@ -0,0 +1,118 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/App/Pages/Desktop/RankPage.xaml.cs b/src/App/Pages/Desktop/RankPage.xaml.cs new file mode 100644 index 000000000..951901196 --- /dev/null +++ b/src/App/Pages/Desktop/RankPage.xaml.cs @@ -0,0 +1,37 @@ +// Copyright (c) Richasy. All rights reserved. + +using Bili.App.Pages.Base; +using Bili.Models.Data.Community; + +namespace Bili.App.Pages.Desktop +{ + /// + /// 排行榜页面. + /// + public sealed partial class RankPage : RankPageBase + { + /// + /// Initializes a new instance of the class. + /// + public RankPage() => InitializeComponent(); + + /// + protected override void OnPageLoaded() + => Bindings.Update(); + + /// + protected override void OnPageUnloaded() + => Bindings.StopTracking(); + + private void OnDetailNavigationViewItemInvoked(Microsoft.UI.Xaml.Controls.NavigationView sender, Microsoft.UI.Xaml.Controls.NavigationViewItemInvokedEventArgs args) + { + ContentScrollViewer.ChangeView(0, 0, 1, true); + var data = args.InvokedItem as Partition; + + if (data != ViewModel.CurrentPartition) + { + ViewModel.SelectPartitionCommand.Execute(data); + } + } + } +} diff --git a/src/App/Pages/Desktop/RecommendPage.xaml b/src/App/Pages/Desktop/RecommendPage.xaml new file mode 100644 index 000000000..8496039d0 --- /dev/null +++ b/src/App/Pages/Desktop/RecommendPage.xaml @@ -0,0 +1,93 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/App/Pages/Desktop/RecommendPage.xaml.cs b/src/App/Pages/Desktop/RecommendPage.xaml.cs new file mode 100644 index 000000000..98201c14a --- /dev/null +++ b/src/App/Pages/Desktop/RecommendPage.xaml.cs @@ -0,0 +1,25 @@ +// Copyright (c) Richasy. All rights reserved. + +using Bili.App.Pages.Base; + +namespace Bili.App.Pages.Desktop +{ + /// + /// 推荐视频页面. + /// + public sealed partial class RecommendPage : RecommendPageBase + { + /// + /// Initializes a new instance of the class. + /// + public RecommendPage() => InitializeComponent(); + + /// + protected override void OnPageLoaded() + => Bindings.Update(); + + /// + protected override void OnPageUnloaded() + => Bindings.StopTracking(); + } +} diff --git a/src/App/Pages/Desktop/SettingPage.xaml b/src/App/Pages/Desktop/SettingPage.xaml new file mode 100644 index 000000000..82a0650e9 --- /dev/null +++ b/src/App/Pages/Desktop/SettingPage.xaml @@ -0,0 +1,93 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/App/Pages/Desktop/SettingPage.xaml.cs b/src/App/Pages/Desktop/SettingPage.xaml.cs new file mode 100644 index 000000000..f81d2ccaf --- /dev/null +++ b/src/App/Pages/Desktop/SettingPage.xaml.cs @@ -0,0 +1,25 @@ +// Copyright (c) Richasy. All rights reserved. + +using Bili.App.Pages.Base; + +namespace Bili.App.Pages.Desktop +{ + /// + /// 可用于自身或导航至 Frame 内部的空白页. + /// + public sealed partial class SettingsPage : SettingsPageBase + { + /// + /// Initializes a new instance of the class. + /// + public SettingsPage() => InitializeComponent(); + + /// + protected override void OnPageLoaded() + => Bindings.Update(); + + /// + protected override void OnPageUnloaded() + => Bindings.StopTracking(); + } +} diff --git a/src/App/Pages/Desktop/TVPage.xaml b/src/App/Pages/Desktop/TVPage.xaml new file mode 100644 index 000000000..6ec5d4c2c --- /dev/null +++ b/src/App/Pages/Desktop/TVPage.xaml @@ -0,0 +1,13 @@ + + + + + diff --git a/src/App/Pages/Desktop/TVPage.xaml.cs b/src/App/Pages/Desktop/TVPage.xaml.cs new file mode 100644 index 000000000..52af2d31d --- /dev/null +++ b/src/App/Pages/Desktop/TVPage.xaml.cs @@ -0,0 +1,25 @@ +// Copyright (c) Richasy. All rights reserved. + +using Bili.App.Pages.Base; + +namespace Bili.App.Pages.Desktop +{ + /// + /// 电视剧页面. + /// + public sealed partial class TvPage : TvPageBase + { + /// + /// Initializes a new instance of the class. + /// + public TvPage() => InitializeComponent(); + + /// + protected override void OnPageLoaded() + => Bindings.Update(); + + /// + protected override void OnPageUnloaded() + => Bindings.StopTracking(); + } +} diff --git a/src/App/Pages/Desktop/ToolboxPage.xaml b/src/App/Pages/Desktop/ToolboxPage.xaml new file mode 100644 index 000000000..2380ff862 --- /dev/null +++ b/src/App/Pages/Desktop/ToolboxPage.xaml @@ -0,0 +1,92 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/App/Pages/Desktop/ToolboxPage.xaml.cs b/src/App/Pages/Desktop/ToolboxPage.xaml.cs new file mode 100644 index 000000000..e8a15e209 --- /dev/null +++ b/src/App/Pages/Desktop/ToolboxPage.xaml.cs @@ -0,0 +1,51 @@ +// Copyright (c) Richasy. All rights reserved. + +using Bili.App.Controls; +using Bili.ViewModels.Interfaces.Home; +using Bili.ViewModels.Interfaces.Toolbox; +using Windows.UI.Xaml; + +namespace Bili.App.Pages.Desktop +{ + /// + /// 工具箱页面. + /// + public sealed partial class ToolboxPage : ToolboxPageBase + { + /// + /// Initializes a new instance of the class. + /// + public ToolboxPage() => InitializeComponent(); + + /// + protected override void OnPageLoaded() + => Bindings.Update(); + + /// + protected override void OnPageUnloaded() + => Bindings.StopTracking(); + + private void OnItemClick(object sender, RoutedEventArgs e) + { + var item = (sender as FrameworkElement).DataContext as IToolboxItemViewModel; + switch (item.Type) + { + case Models.Enums.ToolboxItemType.AvBvConverter: + new AvBvConverterView().Show(); + break; + case Models.Enums.ToolboxItemType.CoverDownloader: + new CoverDownloaderView().Show(); + break; + default: + break; + } + } + } + + /// + /// 的基类. + /// + public class ToolboxPageBase : AppPage + { + } +} diff --git a/src/App/Pages/Desktop/VideoPartitionPage.xaml b/src/App/Pages/Desktop/VideoPartitionPage.xaml new file mode 100644 index 000000000..ea22c3824 --- /dev/null +++ b/src/App/Pages/Desktop/VideoPartitionPage.xaml @@ -0,0 +1,69 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/App/Pages/Desktop/VideoPartitionPage.xaml.cs b/src/App/Pages/Desktop/VideoPartitionPage.xaml.cs new file mode 100644 index 000000000..93cd0c947 --- /dev/null +++ b/src/App/Pages/Desktop/VideoPartitionPage.xaml.cs @@ -0,0 +1,26 @@ +// Copyright (c) Richasy. All rights reserved. + +using Bili.App.Pages.Base; + +namespace Bili.App.Pages.Desktop +{ + /// + /// 可用于自身或导航至 Frame 内部的空白页. + /// + public sealed partial class VideoPartitionPage : VideoPartitionPageBase + { + /// + /// Initializes a new instance of the class. + /// + public VideoPartitionPage() + => InitializeComponent(); + + /// + protected override void OnPageLoaded() + => Bindings.Update(); + + /// + protected override void OnPageUnloaded() + => Bindings.StopTracking(); + } +} diff --git a/src/App/Pages/DocumentaryPage.xaml b/src/App/Pages/DocumentaryPage.xaml deleted file mode 100644 index 3afeae257..000000000 --- a/src/App/Pages/DocumentaryPage.xaml +++ /dev/null @@ -1,12 +0,0 @@ - - - - - - diff --git a/src/App/Pages/DocumentaryPage.xaml.cs b/src/App/Pages/DocumentaryPage.xaml.cs deleted file mode 100644 index f3db757aa..000000000 --- a/src/App/Pages/DocumentaryPage.xaml.cs +++ /dev/null @@ -1,38 +0,0 @@ -// Copyright (c) Richasy. All rights reserved. - -using System.Threading.Tasks; -using Richasy.Bili.App.Controls; -using Richasy.Bili.Models.Enums; -using Windows.UI.Xaml; -using Windows.UI.Xaml.Controls; -using Windows.UI.Xaml.Media.Animation; - -namespace Richasy.Bili.App.Pages -{ - /// - /// 纪录片页面. - /// - public sealed partial class DocumentaryPage : Page, IRefreshPage - { - /// - /// Initializes a new instance of the class. - /// - public DocumentaryPage() - { - this.InitializeComponent(); - this.Loaded += OnLoaded; - } - - /// - public Task RefreshAsync() - => (RootFrame.Content as IRefreshPage).RefreshAsync(); - - private void OnLoaded(object sender, RoutedEventArgs e) - { - if (RootFrame.Content == null) - { - RootFrame.Navigate(typeof(FeedPage), PgcType.Documentary, new SuppressNavigationTransitionInfo()); - } - } - } -} diff --git a/src/App/Pages/DomesticAnimePage.xaml b/src/App/Pages/DomesticAnimePage.xaml deleted file mode 100644 index 745d67927..000000000 --- a/src/App/Pages/DomesticAnimePage.xaml +++ /dev/null @@ -1,13 +0,0 @@ - - - - - - diff --git a/src/App/Pages/DomesticAnimePage.xaml.cs b/src/App/Pages/DomesticAnimePage.xaml.cs deleted file mode 100644 index 99432c864..000000000 --- a/src/App/Pages/DomesticAnimePage.xaml.cs +++ /dev/null @@ -1,38 +0,0 @@ -// Copyright (c) Richasy. All rights reserved. - -using System.Threading.Tasks; -using Richasy.Bili.App.Controls; -using Richasy.Bili.Models.Enums; -using Windows.UI.Xaml; -using Windows.UI.Xaml.Controls; -using Windows.UI.Xaml.Media.Animation; - -namespace Richasy.Bili.App.Pages -{ - /// - /// 国创页面. - /// - public sealed partial class DomesticAnimePage : Page, IRefreshPage - { - /// - /// Initializes a new instance of the class. - /// - public DomesticAnimePage() - { - this.InitializeComponent(); - this.Loaded += OnLoaded; - } - - /// - public Task RefreshAsync() - => (RootFrame.Content as IRefreshPage).RefreshAsync(); - - private void OnLoaded(object sender, RoutedEventArgs e) - { - if (RootFrame.Content == null) - { - RootFrame.Navigate(typeof(AnimePage), PgcType.Domestic, new SuppressNavigationTransitionInfo()); - } - } - } -} diff --git a/src/App/Pages/DynamicFeedPage.xaml b/src/App/Pages/DynamicFeedPage.xaml deleted file mode 100644 index fd076bd5c..000000000 --- a/src/App/Pages/DynamicFeedPage.xaml +++ /dev/null @@ -1,90 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/src/App/Pages/LivePage.xaml.cs b/src/App/Pages/LivePage.xaml.cs deleted file mode 100644 index 0c132d619..000000000 --- a/src/App/Pages/LivePage.xaml.cs +++ /dev/null @@ -1,65 +0,0 @@ -// Copyright (c) Richasy. All rights reserved. - -using System; -using Richasy.Bili.ViewModels.Uwp; -using Windows.UI.Xaml; -using Windows.UI.Xaml.Controls; - -namespace Richasy.Bili.App.Pages -{ - /// - /// 可用于自身或导航至 Frame 内部的空白页. - /// - public sealed partial class LivePage : Page - { - /// - /// 的依赖属性. - /// - public static readonly DependencyProperty ViewModelProperty = - DependencyProperty.Register(nameof(ViewModel), typeof(LiveModuleViewModel), typeof(LivePage), new PropertyMetadata(LiveModuleViewModel.Instance)); - - /// - /// Initializes a new instance of the class. - /// - public LivePage() - { - this.InitializeComponent(); - this.Loaded += OnLoadedAsync; - this.SizeChanged += OnSizeChanged; - } - - /// - /// 直播模块视图模型. - /// - public LiveModuleViewModel ViewModel - { - get { return (LiveModuleViewModel)GetValue(ViewModelProperty); } - set { SetValue(ViewModelProperty, value); } - } - - private async void OnLoadedAsync(object sender, RoutedEventArgs e) - { - if (this.ViewModel.BannerCollection.Count == 0) - { - await ViewModel.RequestDataAsync(); - } - - this.FindName(nameof(FollowLiveView)); - this.FindName(nameof(RootGrid)); - - this.UpdateLayout(); - } - - private void OnSizeChanged(object sender, SizeChangedEventArgs e) => this.UpdateLayout(); - - private async void OnVideoViewRequestLoadMoreAsync(object sender, System.EventArgs e) - { - await ViewModel.RequestDataAsync(); - } - - private async void OnRefreshButtonClickAsync(object sender, RoutedEventArgs e) - { - await ViewModel.InitializeRequestAsync(); - } - } -} diff --git a/src/App/Pages/MoviePage.xaml b/src/App/Pages/MoviePage.xaml deleted file mode 100644 index d4dc9a063..000000000 --- a/src/App/Pages/MoviePage.xaml +++ /dev/null @@ -1,13 +0,0 @@ - - - - - - diff --git a/src/App/Pages/MoviePage.xaml.cs b/src/App/Pages/MoviePage.xaml.cs deleted file mode 100644 index e46ed7199..000000000 --- a/src/App/Pages/MoviePage.xaml.cs +++ /dev/null @@ -1,38 +0,0 @@ -// Copyright (c) Richasy. All rights reserved. - -using System.Threading.Tasks; -using Richasy.Bili.App.Controls; -using Richasy.Bili.Models.Enums; -using Windows.UI.Xaml; -using Windows.UI.Xaml.Controls; -using Windows.UI.Xaml.Media.Animation; - -namespace Richasy.Bili.App.Pages -{ - /// - /// 电影页面. - /// - public sealed partial class MoviePage : Page, IRefreshPage - { - /// - /// Initializes a new instance of the class. - /// - public MoviePage() - { - this.InitializeComponent(); - this.Loaded += OnLoaded; - } - - /// - public Task RefreshAsync() - => (RootFrame.Content as IRefreshPage).RefreshAsync(); - - private void OnLoaded(object sender, RoutedEventArgs e) - { - if (RootFrame.Content == null) - { - RootFrame.Navigate(typeof(FeedPage), PgcType.Movie, new SuppressNavigationTransitionInfo()); - } - } - } -} diff --git a/src/App/Pages/Overlay/FansPage.xaml b/src/App/Pages/Overlay/FansPage.xaml deleted file mode 100644 index 526742474..000000000 --- a/src/App/Pages/Overlay/FansPage.xaml +++ /dev/null @@ -1,82 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/src/App/Pages/Overlay/FansPage.xaml.cs b/src/App/Pages/Overlay/FansPage.xaml.cs deleted file mode 100644 index d99558d7b..000000000 --- a/src/App/Pages/Overlay/FansPage.xaml.cs +++ /dev/null @@ -1,77 +0,0 @@ -// Copyright (c) Richasy. All rights reserved. - -using System; -using Richasy.Bili.App.Controls; -using Richasy.Bili.ViewModels.Uwp; -using Windows.UI.Xaml; -using Windows.UI.Xaml.Controls; -using Windows.UI.Xaml.Navigation; - -namespace Richasy.Bili.App.Pages.Overlay -{ - /// - /// 粉丝详情页面. - /// - public sealed partial class FansPage : Page - { - /// - /// 的依赖属性. - /// - public static readonly DependencyProperty ViewModelProperty = - DependencyProperty.Register(nameof(ViewModel), typeof(FansViewModel), typeof(FansPage), new PropertyMetadata(FansViewModel.Instance)); - - /// - /// Initializes a new instance of the class. - /// - public FansPage() - { - this.InitializeComponent(); - this.Loaded += OnLoadedAsync; - } - - /// - /// 视图模型. - /// - public FansViewModel ViewModel - { - get { return (FansViewModel)GetValue(ViewModelProperty); } - set { SetValue(ViewModelProperty, value); } - } - - /// - protected override async void OnNavigatedTo(NavigationEventArgs e) - { - if (e.Parameter is Tuple userInfo) - { - var canRefresh = ViewModel.SetUser(userInfo.Item1, userInfo.Item2); - if (IsLoaded && canRefresh) - { - await ViewModel.InitializeRequestAsync(); - } - } - } - - private async void OnLoadedAsync(object sender, RoutedEventArgs e) - { - if (!ViewModel.IsRequested) - { - await ViewModel.InitializeRequestAsync(); - } - } - - private async void OnFansRefreshButtonClickAsync(object sender, RoutedEventArgs e) - { - await ViewModel.InitializeRequestAsync(); - } - - private async void OnViewRequestLoadMoreAsync(object sender, EventArgs e) - { - await ViewModel.RequestDataAsync(); - } - - private async void OnUserCardClickAsync(object sender, System.EventArgs e) - { - await UserView.Instance.ShowAsync((sender as UserSlimCard).ViewModel); - } - } -} diff --git a/src/App/Pages/Overlay/FavoritePage.xaml b/src/App/Pages/Overlay/FavoritePage.xaml deleted file mode 100644 index faa15ffa1..000000000 --- a/src/App/Pages/Overlay/FavoritePage.xaml +++ /dev/null @@ -1,61 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/src/App/Pages/Overlay/FavoritePage.xaml.cs b/src/App/Pages/Overlay/FavoritePage.xaml.cs deleted file mode 100644 index f78d9f1b6..000000000 --- a/src/App/Pages/Overlay/FavoritePage.xaml.cs +++ /dev/null @@ -1,163 +0,0 @@ -// Copyright (c) Richasy. All rights reserved. - -using System.ComponentModel; -using System.Threading.Tasks; -using Richasy.Bili.Models.Enums.App; -using Richasy.Bili.ViewModels.Uwp; -using Windows.UI.Xaml; -using Windows.UI.Xaml.Controls; - -namespace Richasy.Bili.App.Pages.Overlay -{ - /// - /// 收藏夹页面. - /// - public sealed partial class FavoritePage : Page - { - /// - /// 的依赖属性. - /// - public static readonly DependencyProperty ViewModelProperty = - DependencyProperty.Register(nameof(ViewModel), typeof(FavoriteViewModel), typeof(FavoritePage), new PropertyMetadata(FavoriteViewModel.Instance)); - - /// - /// Initializes a new instance of the class. - /// - public FavoritePage() - { - this.InitializeComponent(); - this.Loaded += OnLoadedAsync; - this.Unloaded += OnUnloaded; - AnimePanel.ViewModel = FavoriteAnimeViewModel.Instance; - CinemaPanel.ViewModel = FavoriteCinemaViewModel.Instance; - } - - /// - /// 视图模型. - /// - public FavoriteViewModel ViewModel - { - get { return (FavoriteViewModel)GetValue(ViewModelProperty); } - set { SetValue(ViewModelProperty, value); } - } - - private void OnUnloaded(object sender, RoutedEventArgs e) - { - ViewModel.PropertyChanged -= OnViewModelPropertyChangedAsync; - } - - private async void OnLoadedAsync(object sender, RoutedEventArgs e) - { - ViewModel.PropertyChanged -= OnViewModelPropertyChangedAsync; - ViewModel.PropertyChanged += OnViewModelPropertyChangedAsync; - - await CheckFavoriteTypeAsync(); - } - - private async void OnViewModelPropertyChangedAsync(object sender, PropertyChangedEventArgs e) - { - if (e.PropertyName == nameof(ViewModel.CurrentType)) - { - await CheckFavoriteTypeAsync(); - } - } - - private async Task CheckFavoriteTypeAsync() - { - var canInit = false; - VideoPanel.Visibility = ViewModel.CurrentType == FavoriteType.Video ? Visibility.Visible : Visibility.Collapsed; - AnimePanel.Visibility = ViewModel.CurrentType == FavoriteType.Anime ? Visibility.Visible : Visibility.Collapsed; - CinemaPanel.Visibility = ViewModel.CurrentType == FavoriteType.Cinema ? Visibility.Visible : Visibility.Collapsed; - ArticlePanel.Visibility = ViewModel.CurrentType == FavoriteType.Article ? Visibility.Visible : Visibility.Collapsed; - switch (ViewModel.CurrentType) - { - case FavoriteType.Video: - VideoItem.IsSelected = true; - canInit = !ViewModel.IsVideoRequested; - if (canInit) - { - await ViewModel.InitializeRequestAsync(FavoriteType.Video); - } - - break; - case FavoriteType.Anime: - AnimeItem.IsSelected = true; - canInit = !FavoriteAnimeViewModel.Instance.IsRequested; - if (canInit) - { - await FavoriteAnimeViewModel.Instance.InitializeRequestAsync(); - } - - break; - case FavoriteType.Cinema: - CinemaItem.IsSelected = true; - canInit = !FavoriteCinemaViewModel.Instance.IsRequested; - if (canInit) - { - await FavoriteCinemaViewModel.Instance.InitializeRequestAsync(); - } - - break; - case FavoriteType.Article: - ArticleItem.IsSelected = true; - canInit = !FavoriteArticleViewModel.Instance.IsRequested; - if (canInit) - { - await FavoriteArticleViewModel.Instance.InitializeRequestAsync(); - } - - break; - default: - break; - } - } - - private void OnNavSelectionChanged(Microsoft.UI.Xaml.Controls.NavigationView sender, Microsoft.UI.Xaml.Controls.NavigationViewSelectionChangedEventArgs args) - { - if (args.SelectedItem is Microsoft.UI.Xaml.Controls.NavigationViewItem item) - { - switch (item.Name) - { - case nameof(VideoItem): - ViewModel.CurrentType = FavoriteType.Video; - break; - case nameof(AnimeItem): - ViewModel.CurrentType = FavoriteType.Anime; - break; - case nameof(CinemaItem): - ViewModel.CurrentType = FavoriteType.Cinema; - break; - case nameof(ArticleItem): - ViewModel.CurrentType = FavoriteType.Article; - break; - default: - break; - } - } - } - - private async void OnRefreshButtonClickAsync(object sender, RoutedEventArgs e) - { - RefreshButton.IsEnabled = false; - switch (ViewModel.CurrentType) - { - case FavoriteType.Video: - await ViewModel.InitializeRequestAsync(FavoriteType.Video); - break; - case FavoriteType.Anime: - await FavoriteAnimeViewModel.Instance.InitializeRequestAsync(); - break; - case FavoriteType.Cinema: - await FavoriteCinemaViewModel.Instance.InitializeRequestAsync(); - break; - case FavoriteType.Article: - await FavoriteArticleViewModel.Instance.InitializeRequestAsync(); - break; - default: - break; - } - - RefreshButton.IsEnabled = true; - } - } -} diff --git a/src/App/Pages/Overlay/FollowsPage.xaml b/src/App/Pages/Overlay/FollowsPage.xaml deleted file mode 100644 index 6b022d897..000000000 --- a/src/App/Pages/Overlay/FollowsPage.xaml +++ /dev/null @@ -1,81 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/src/App/Pages/Overlay/FollowsPage.xaml.cs b/src/App/Pages/Overlay/FollowsPage.xaml.cs deleted file mode 100644 index d66a60207..000000000 --- a/src/App/Pages/Overlay/FollowsPage.xaml.cs +++ /dev/null @@ -1,77 +0,0 @@ -// Copyright (c) Richasy. All rights reserved. - -using System; -using Richasy.Bili.App.Controls; -using Richasy.Bili.ViewModels.Uwp; -using Windows.UI.Xaml; -using Windows.UI.Xaml.Controls; -using Windows.UI.Xaml.Navigation; - -namespace Richasy.Bili.App.Pages.Overlay -{ - /// - /// 关注用户页面. - /// - public sealed partial class FollowsPage : Page - { - /// - /// 的依赖属性. - /// - public static readonly DependencyProperty ViewModelProperty = - DependencyProperty.Register(nameof(ViewModel), typeof(FollowsViewModel), typeof(FollowsPage), new PropertyMetadata(FollowsViewModel.Instance)); - - /// - /// Initializes a new instance of the class. - /// - public FollowsPage() - { - this.InitializeComponent(); - this.Loaded += OnLoadedAsync; - } - - /// - /// 视图模型. - /// - public FollowsViewModel ViewModel - { - get { return (FollowsViewModel)GetValue(ViewModelProperty); } - set { SetValue(ViewModelProperty, value); } - } - - /// - protected override async void OnNavigatedTo(NavigationEventArgs e) - { - if (e.Parameter is Tuple userInfo) - { - var canRefresh = ViewModel.SetUser(userInfo.Item1, userInfo.Item2); - if (IsLoaded && canRefresh) - { - await ViewModel.InitializeRequestAsync(); - } - } - } - - private async void OnLoadedAsync(object sender, RoutedEventArgs e) - { - if (!ViewModel.IsRequested) - { - await ViewModel.InitializeRequestAsync(); - } - } - - private async void OnFollowsRefreshButtonClickAsync(object sender, RoutedEventArgs e) - { - await ViewModel.InitializeRequestAsync(); - } - - private async void OnViewRequestLoadMoreAsync(object sender, EventArgs e) - { - await ViewModel.RequestDataAsync(); - } - - private async void OnUserCardClickAsync(object sender, System.EventArgs e) - { - await UserView.Instance.ShowAsync((sender as UserSlimCard).ViewModel); - } - } -} diff --git a/src/App/Pages/Overlay/HistoryPage.xaml b/src/App/Pages/Overlay/HistoryPage.xaml deleted file mode 100644 index c01c39380..000000000 --- a/src/App/Pages/Overlay/HistoryPage.xaml +++ /dev/null @@ -1,113 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/src/App/Pages/Overlay/HistoryPage.xaml.cs b/src/App/Pages/Overlay/HistoryPage.xaml.cs deleted file mode 100644 index 9611f8b36..000000000 --- a/src/App/Pages/Overlay/HistoryPage.xaml.cs +++ /dev/null @@ -1,88 +0,0 @@ -// Copyright (c) Richasy. All rights reserved. - -using System; -using Richasy.Bili.App.Controls.Dialogs; -using Richasy.Bili.Locator.Uwp; -using Richasy.Bili.Models.Enums; -using Richasy.Bili.Toolkit.Interfaces; -using Richasy.Bili.ViewModels.Uwp; -using Windows.UI.Xaml; -using Windows.UI.Xaml.Controls; - -namespace Richasy.Bili.App.Pages.Overlay -{ - /// - /// 历史记录页面. - /// - public sealed partial class HistoryPage : Page - { - /// - /// 的依赖属性. - /// - public static readonly DependencyProperty ViewModelProperty = - DependencyProperty.Register(nameof(ViewModel), typeof(HistoryViewModel), typeof(HistoryPage), new PropertyMetadata(HistoryViewModel.Instance)); - - /// - /// Initializes a new instance of the class. - /// - public HistoryPage() - { - this.InitializeComponent(); - this.Loaded += OnLoadedAsync; - } - - /// - /// 视图模型. - /// - public HistoryViewModel ViewModel - { - get { return (HistoryViewModel)GetValue(ViewModelProperty); } - set { SetValue(ViewModelProperty, value); } - } - - private async void OnLoadedAsync(object sender, RoutedEventArgs e) - { - if (!ViewModel.IsRequested) - { - await ViewModel.InitializeRequestAsync(); - } - } - - private async void OnRefreshButtonClickAsync(object sender, RoutedEventArgs e) - { - await ViewModel.InitializeRequestAsync(); - } - - private async void OnVideoViewRequestLoadMoreAsync(object sender, System.EventArgs e) - { - await ViewModel.RequestDataAsync(); - } - - private async void OnDeleteItemClickAsync(object sender, RoutedEventArgs e) - { - var context = (sender as FrameworkElement).DataContext as VideoViewModel; - await ViewModel.DeleteItemAsync(context); - } - - private async void OnClearButtonClickAsync(object sender, RoutedEventArgs e) - { - var isClear = false; - if (ViewModel.VideoCollection.Count > 0) - { - // Show dialog. - var msg = ServiceLocator.Instance.GetService().GetLocaleString(LanguageNames.ClearHistoryWarning); - var dialog = new ConfirmDialog(msg); - var result = await dialog.ShowAsync().AsTask(); - if (result == ContentDialogResult.Primary) - { - isClear = true; - } - } - - if (isClear) - { - await ViewModel.ClearAsync(); - } - } - } -} diff --git a/src/App/Pages/Overlay/MessagePage.xaml b/src/App/Pages/Overlay/MessagePage.xaml deleted file mode 100644 index 77287cbde..000000000 --- a/src/App/Pages/Overlay/MessagePage.xaml +++ /dev/null @@ -1,100 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/src/App/Pages/Overlay/MessagePage.xaml.cs b/src/App/Pages/Overlay/MessagePage.xaml.cs deleted file mode 100644 index da5b36e1b..000000000 --- a/src/App/Pages/Overlay/MessagePage.xaml.cs +++ /dev/null @@ -1,112 +0,0 @@ -// Copyright (c) Richasy. All rights reserved. - -using System.ComponentModel; -using Richasy.Bili.Models.Enums.App; -using Richasy.Bili.ViewModels.Uwp; -using Windows.UI.Xaml; -using Windows.UI.Xaml.Controls; -using Windows.UI.Xaml.Navigation; - -namespace Richasy.Bili.App.Pages.Overlay -{ - /// - /// 消息页面. - /// - public sealed partial class MessagePage : Page - { - /// - /// 的依赖属性. - /// - public static readonly DependencyProperty ViewModelProperty = - DependencyProperty.Register(nameof(ViewModel), typeof(MessageModuleViewModel), typeof(MessagePage), new PropertyMetadata(MessageModuleViewModel.Instance)); - - /// - /// Initializes a new instance of the class. - /// - public MessagePage() - { - this.InitializeComponent(); - NavigationCacheMode = NavigationCacheMode.Enabled; - this.ViewModel.PropertyChanged += OnViewModelPropertyChanged; - this.Loaded += OnLoaded; - } - - /// - /// 视图模型. - /// - public MessageModuleViewModel ViewModel - { - get { return (MessageModuleViewModel)GetValue(ViewModelProperty); } - set { SetValue(ViewModelProperty, value); } - } - - private void OnLoaded(object sender, RoutedEventArgs e) - { - CheckCurrentTypeAsync(); - } - - private void OnNavItemInvokedAsync(Microsoft.UI.Xaml.Controls.NavigationView sender, Microsoft.UI.Xaml.Controls.NavigationViewItemInvokedEventArgs args) - { - var item = args.InvokedItemContainer as Microsoft.UI.Xaml.Controls.NavigationViewItem; - MessageType type = default; - if (item == LikeMeNavItem) - { - type = MessageType.Like; - } - else if (item == AtMeNavItem) - { - type = MessageType.At; - } - else - { - type = MessageType.Reply; - } - - ViewModel.CurrentType = type; - } - - private void OnViewModelPropertyChanged(object sender, PropertyChangedEventArgs e) - { - if (e.PropertyName == nameof(ViewModel.CurrentType)) - { - CheckCurrentTypeAsync(); - } - } - - private async void CheckCurrentTypeAsync() - { - LikeMessageView.Visibility = Visibility.Collapsed; - AtMessageView.Visibility = Visibility.Collapsed; - ReplyMessageView.Visibility = Visibility.Collapsed; - - switch (ViewModel.CurrentType) - { - case MessageType.Like: - LikeMessageView.Visibility = Visibility.Visible; - Nav.SelectedItem = LikeMeNavItem; - break; - case MessageType.At: - AtMessageView.Visibility = Visibility.Visible; - Nav.SelectedItem = AtMeNavItem; - break; - case MessageType.Reply: - ReplyMessageView.Visibility = Visibility.Visible; - Nav.SelectedItem = ReplyMeNavItem; - break; - default: - break; - } - - if (!ViewModel.IsCurrentTypeRequested()) - { - await ViewModel.InitializeRequestAsync(); - await AccountViewModel.Instance.InitUnreadAsync(); - } - } - - private async void OnRefreshButtonClickAsync(object sender, RoutedEventArgs e) - { - await ViewModel.InitializeRequestAsync(); - } - } -} diff --git a/src/App/Pages/Overlay/PartitionDetailPage.xaml b/src/App/Pages/Overlay/PartitionDetailPage.xaml deleted file mode 100644 index 73c643639..000000000 --- a/src/App/Pages/Overlay/PartitionDetailPage.xaml +++ /dev/null @@ -1,155 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/src/App/Pages/Overlay/PartitionDetailPage.xaml.cs b/src/App/Pages/Overlay/PartitionDetailPage.xaml.cs deleted file mode 100644 index 5c6fffd9f..000000000 --- a/src/App/Pages/Overlay/PartitionDetailPage.xaml.cs +++ /dev/null @@ -1,163 +0,0 @@ -// Copyright (c) Richasy. All rights reserved. - -using System.ComponentModel; -using Richasy.Bili.Models.Enums; -using Richasy.Bili.ViewModels.Uwp; -using Windows.UI.Xaml; -using Windows.UI.Xaml.Controls; -using Windows.UI.Xaml.Media.Animation; -using Windows.UI.Xaml.Navigation; - -namespace Richasy.Bili.App.Pages.Overlay -{ - /// - /// 分区详情页面. - /// - public sealed partial class PartitionDetailPage : Page - { - /// - /// Dependency property of . - /// - public static readonly DependencyProperty ViewModelProperty = - DependencyProperty.Register(nameof(ViewModel), typeof(PartitionViewModel), typeof(PartitionDetailPage), new PropertyMetadata(null)); - - /// - /// Initializes a new instance of the class. - /// - public PartitionDetailPage() - { - this.InitializeComponent(); - this.Loaded += this.OnLoaded; - this.Unloaded += this.OnUnloaded; - } - - /// - /// 分区视图模型. - /// - public PartitionViewModel ViewModel - { - get { return (PartitionViewModel)GetValue(ViewModelProperty); } - set { SetValue(ViewModelProperty, value); } - } - - /// - protected override void OnNavigatedTo(NavigationEventArgs e) - { - if (e.Parameter != null && e.Parameter is PartitionViewModel data) - { - this.ViewModel = data; - var animationService = ConnectedAnimationService.GetForCurrentView(); - var animate = animationService.GetAnimation("PartitionAnimate"); - if (animate != null) - { - animate.TryStart(PartitionHeader, new UIElement[] { DetailNavigationView }); - } - } - } - - /// - protected override void OnNavigatingFrom(NavigatingCancelEventArgs e) - { - // 这意味着是退回到主视图,而非切换到其它同级页面. - if (e.SourcePageType.Name == nameof(Page)) - { - var animationService = ConnectedAnimationService.GetForCurrentView(); - var animate = animationService.PrepareToAnimate("PartitionBackAnimate", this.RootContainer); - animate.Configuration = new DirectConnectedAnimationConfiguration(); - } - } - - private void OnUnloaded(object sender, RoutedEventArgs e) - { - ViewModel.PropertyChanged -= OnViewModelPropertyChanged; - } - - private void OnLoaded(object sender, RoutedEventArgs e) - { - ViewModel.PropertyChanged += OnViewModelPropertyChanged; - this.FindName(nameof(ContentGrid)); - CheckCurrentSubPartition(); - } - - private void OnViewModelPropertyChanged(object sender, PropertyChangedEventArgs e) - { - if (e.PropertyName == nameof(ViewModel.CurrentSelectedSubPartition)) - { - CheckCurrentSubPartition(); - } - } - - private async void OnDetailNavigationViewItemInvokedAsync(Microsoft.UI.Xaml.Controls.NavigationView sender, Microsoft.UI.Xaml.Controls.NavigationViewItemInvokedEventArgs args) - { - var vm = args.InvokedItem as SubPartitionViewModel; - await ViewModel.SelectSubPartitionAsync(vm); - CheckError(); - } - - private void CheckCurrentSubPartition() - { - var vm = ViewModel.CurrentSelectedSubPartition; - if (vm != null) - { - var isShowSort = vm.SortTypeCollection != null && vm.SortTypeCollection.Count > 0; - if (isShowSort) - { - VideoSortComboBox.Visibility = Visibility.Visible; - RefreshButton.Visibility = Visibility.Collapsed; - VideoSortComboBox.SelectedItem = vm.CurrentSortType; - } - else - { - RefreshButton.Visibility = Visibility.Visible; - VideoSortComboBox.Visibility = Visibility.Collapsed; - } - - if (!(DetailNavigationView.SelectedItem is SubPartitionViewModel selectedItem) || selectedItem != vm) - { - DetailNavigationView.SelectedItem = vm; - } - } - } - - private void CheckError() - { - if (ViewModel.CurrentSelectedSubPartition.IsError) - { - VideoSortComboBox.Visibility = Visibility.Collapsed; - RefreshButton.Visibility = Visibility.Collapsed; - } - } - - private async void OnVideoViewRequestLoadMoreAsync(object sender, System.EventArgs e) - { - var currentSubPartition = ViewModel.CurrentSelectedSubPartition; - if (currentSubPartition != null && - !currentSubPartition.IsDeltaLoading && - !currentSubPartition.IsInitializeLoading) - { - await currentSubPartition.RequestDataAsync(); - } - } - - private void OnVideoSortComboBoxSlectionChanged(object sender, SelectionChangedEventArgs e) - { - if (VideoSortComboBox.SelectedItem != null) - { - var item = (VideoSortType)VideoSortComboBox.SelectedItem; - ViewModel.CurrentSelectedSubPartition.CurrentSortType = item; - } - } - - private async void OnRefreshButtonClickAsync(object sender, RoutedEventArgs e) - { - if (ViewModel.CurrentSelectedSubPartition != null && - !ViewModel.CurrentSelectedSubPartition.IsInitializeLoading && - !ViewModel.CurrentSelectedSubPartition.IsDeltaLoading) - { - await ViewModel.CurrentSelectedSubPartition.InitializeRequestAsync(); - CheckCurrentSubPartition(); - CheckError(); - } - } - } -} diff --git a/src/App/Pages/Overlay/PgcIndexPage.xaml b/src/App/Pages/Overlay/PgcIndexPage.xaml deleted file mode 100644 index 277b9fe87..000000000 --- a/src/App/Pages/Overlay/PgcIndexPage.xaml +++ /dev/null @@ -1,121 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/src/App/Pages/Overlay/PgcIndexPage.xaml.cs b/src/App/Pages/Overlay/PgcIndexPage.xaml.cs deleted file mode 100644 index 05cb34548..000000000 --- a/src/App/Pages/Overlay/PgcIndexPage.xaml.cs +++ /dev/null @@ -1,75 +0,0 @@ -// Copyright (c) Richasy. All rights reserved. - -using System; -using Richasy.Bili.ViewModels.Uwp; -using Windows.UI.Xaml; -using Windows.UI.Xaml.Controls; -using Windows.UI.Xaml.Navigation; - -namespace Richasy.Bili.App.Pages -{ - /// - /// PGC索引页面. - /// - public sealed partial class PgcIndexPage : Page - { - /// - /// 的依赖属性. - /// - public static readonly DependencyProperty ViewModelProperty = - DependencyProperty.Register(nameof(ViewModel), typeof(PgcViewModelBase), typeof(PgcIndexPage), new PropertyMetadata(null)); - - /// - /// Initializes a new instance of the class. - /// - public PgcIndexPage() - { - this.InitializeComponent(); - this.Loaded += OnLoadedAsync; - } - - /// - /// 视图模型. - /// - public PgcViewModelBase ViewModel - { - get { return (PgcViewModelBase)GetValue(ViewModelProperty); } - set { SetValue(ViewModelProperty, value); } - } - - /// - protected override void OnNavigatedTo(NavigationEventArgs e) - { - if (e.Parameter is PgcViewModelBase vm) - { - ViewModel = vm; - } - } - - private async void OnLoadedAsync(object sender, RoutedEventArgs e) - { - if (ViewModel.FilterCollection.Count == 0) - { - await ViewModel.LoadIndexAsync(); - } - } - - private async void OnViewRequestLoadMoreAsync(object sender, EventArgs e) - { - await ViewModel.DeltaRequestIndexAsync(); - } - - private async void OnIndexRefreshButtonClickAsync(object sender, RoutedEventArgs e) - { - await ViewModel.LoadIndexAsync(); - } - - private async void OnConditionChangedAsync(object sender, SelectionChangedEventArgs e) - { - if (ViewModel.IsIndexRequested) - { - await ViewModel.LoadIndexAsync(); - } - } - } -} diff --git a/src/App/Pages/Overlay/PlayerPage.xaml b/src/App/Pages/Overlay/PlayerPage.xaml deleted file mode 100644 index 23593d76d..000000000 --- a/src/App/Pages/Overlay/PlayerPage.xaml +++ /dev/null @@ -1,164 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/src/App/Pages/Overlay/ViewLaterPage.xaml.cs b/src/App/Pages/Overlay/ViewLaterPage.xaml.cs deleted file mode 100644 index f175c9cdd..000000000 --- a/src/App/Pages/Overlay/ViewLaterPage.xaml.cs +++ /dev/null @@ -1,88 +0,0 @@ -// Copyright (c) Richasy. All rights reserved. - -using System; -using Richasy.Bili.App.Controls.Dialogs; -using Richasy.Bili.Locator.Uwp; -using Richasy.Bili.Models.Enums; -using Richasy.Bili.Toolkit.Interfaces; -using Richasy.Bili.ViewModels.Uwp; -using Windows.UI.Xaml; -using Windows.UI.Xaml.Controls; - -namespace Richasy.Bili.App.Pages.Overlay -{ - /// - /// 可用于自身或导航至 Frame 内部的空白页. - /// - public sealed partial class ViewLaterPage : Page - { - /// - /// 的依赖属性. - /// - public static readonly DependencyProperty ViewModelProperty = - DependencyProperty.Register(nameof(ViewModel), typeof(ViewLaterViewModel), typeof(ViewLaterPage), new PropertyMetadata(ViewLaterViewModel.Instance)); - - /// - /// Initializes a new instance of the class. - /// - public ViewLaterPage() - { - this.InitializeComponent(); - this.Loaded += OnLoadedAsync; - } - - /// - /// 视图模型. - /// - public ViewLaterViewModel ViewModel - { - get { return (ViewLaterViewModel)GetValue(ViewModelProperty); } - set { SetValue(ViewModelProperty, value); } - } - - private async void OnRefreshButtonClickAsync(object sender, RoutedEventArgs e) - { - await ViewModel.InitializeRequestAsync(); - } - - private async void OnClearButtonClickAsync(object sender, RoutedEventArgs e) - { - var isClear = false; - if (ViewModel.VideoCollection.Count > 0) - { - // Show dialog. - var msg = ServiceLocator.Instance.GetService().GetLocaleString(LanguageNames.ClearViewLaterWarning); - var dialog = new ConfirmDialog(msg); - var result = await dialog.ShowAsync().AsTask(); - if (result == ContentDialogResult.Primary) - { - isClear = true; - } - } - - if (isClear) - { - await ViewModel.ClearAsync(); - } - } - - private async void OnLoadedAsync(object sender, RoutedEventArgs e) - { - if (!ViewModel.IsRequested) - { - await ViewModel.InitializeRequestAsync(); - } - } - - private async void OnVideoViewRequestLoadMoreAsync(object sender, System.EventArgs e) - { - await ViewModel.RequestDataAsync(); - } - - private async void OnRemoveItemClickAsync(object sender, RoutedEventArgs e) - { - var vm = (sender as FrameworkElement).DataContext as VideoViewModel; - await ViewModel.RemoveAsync(vm); - } - } -} diff --git a/src/App/Pages/PartitionPage.xaml b/src/App/Pages/PartitionPage.xaml deleted file mode 100644 index 80b5e8c3c..000000000 --- a/src/App/Pages/PartitionPage.xaml +++ /dev/null @@ -1,75 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/src/App/Pages/PartitionPage.xaml.cs b/src/App/Pages/PartitionPage.xaml.cs deleted file mode 100644 index 88be84d49..000000000 --- a/src/App/Pages/PartitionPage.xaml.cs +++ /dev/null @@ -1,73 +0,0 @@ -// Copyright (c) Richasy. All rights reserved. - -using Richasy.Bili.App.Resources.Extension; -using Richasy.Bili.ViewModels.Uwp; -using Windows.UI.Xaml; -using Windows.UI.Xaml.Controls; -using Windows.UI.Xaml.Media.Animation; - -namespace Richasy.Bili.App.Pages -{ - /// - /// 可用于自身或导航至 Frame 内部的空白页. - /// - public sealed partial class PartitionPage : Page, IConnectedAnimationPage - { - /// - /// 的依赖属性. - /// - public static readonly DependencyProperty ViewModelProperty = - DependencyProperty.Register("ViewModel", typeof(PartitionModuleViewModel), typeof(PartitionPage), new PropertyMetadata(PartitionModuleViewModel.Instance)); - - /// - /// Initializes a new instance of the class. - /// - public PartitionPage() - { - this.InitializeComponent(); - this.Loaded += this.OnLoaded; - } - - /// - /// 分区视图模型. - /// - public PartitionModuleViewModel ViewModel - { - get { return (PartitionModuleViewModel)GetValue(ViewModelProperty); } - set { SetValue(ViewModelProperty, value); } - } - - /// - public void TryStartConnectedAnimation() - { - if (this.PartitionView != null && this.ViewModel.CurrentPartition != null) - { - var element = this.PartitionView.GetOrCreateElement(this.ViewModel.PartitionCollection.IndexOf(this.ViewModel.CurrentPartition)); - if (element != null) - { - var animateService = ConnectedAnimationService.GetForCurrentView(); - animateService.TryStartAnimation("PartitionBackAnimate", element); - } - } - } - - private async void OnItemsRepeaterLoadedAsync(object sender, RoutedEventArgs e) - { - if (ViewModel.PartitionCollection.Count == 0) - { - await ViewModel.InitializeAllPartitionAsync(); - } - } - - private void OnLoaded(object sender, RoutedEventArgs e) - { - this.FindName("PartitionView"); - } - - private async void OnPartitionItemClickAsync(object sender, PartitionViewModel e) - { - AppViewModel.Instance.SetOverlayContentId(Models.Enums.PageIds.PartitionDetail, e); - await this.ViewModel.SelectPartitionAsync(e); - } - } -} diff --git a/src/App/Pages/PopularPage.xaml b/src/App/Pages/PopularPage.xaml deleted file mode 100644 index af89d28c5..000000000 --- a/src/App/Pages/PopularPage.xaml +++ /dev/null @@ -1,83 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/src/App/Pages/PopularPage.xaml.cs b/src/App/Pages/PopularPage.xaml.cs deleted file mode 100644 index 1f622b916..000000000 --- a/src/App/Pages/PopularPage.xaml.cs +++ /dev/null @@ -1,59 +0,0 @@ -// Copyright (c) Richasy. All rights reserved. - -using System.Linq; -using System.Threading.Tasks; -using Richasy.Bili.App.Controls; -using Richasy.Bili.ViewModels.Uwp; -using Windows.UI.Xaml; -using Windows.UI.Xaml.Controls; - -namespace Richasy.Bili.App.Pages -{ - /// - /// 热门视频页面. - /// - public sealed partial class PopularPage : Page - { - /// - /// 的依赖属性. - /// - public static readonly DependencyProperty ViewModelProperty = - DependencyProperty.Register(nameof(ViewModel), typeof(PopularViewModel), typeof(RecommendPage), new PropertyMetadata(PopularViewModel.Instance)); - - /// - /// Initializes a new instance of the class. - /// - public PopularPage() - { - this.InitializeComponent(); - this.Loaded += OnLoadedAsync; - } - - /// - /// 热门视频视图模型. - /// - public PopularViewModel ViewModel - { - get { return (PopularViewModel)GetValue(ViewModelProperty); } - set { SetValue(ViewModelProperty, value); } - } - - private async void OnLoadedAsync(object sender, RoutedEventArgs e) - { - if (!this.ViewModel.VideoCollection.Any()) - { - await this.ViewModel.RequestDataAsync(); - } - } - - private async void OnVideoViewRequestLoadMoreAsync(object sender, System.EventArgs e) - { - await this.ViewModel.RequestDataAsync(); - } - - private async void OnRefreshButtonClickAsync(object sender, RoutedEventArgs e) - { - await ViewModel.InitializeRequestAsync(); - } - } -} diff --git a/src/App/Pages/RankPage.xaml b/src/App/Pages/RankPage.xaml deleted file mode 100644 index ea2a413e3..000000000 --- a/src/App/Pages/RankPage.xaml +++ /dev/null @@ -1,110 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/src/App/Pages/RankPage.xaml.cs b/src/App/Pages/RankPage.xaml.cs deleted file mode 100644 index 8a1321d56..000000000 --- a/src/App/Pages/RankPage.xaml.cs +++ /dev/null @@ -1,89 +0,0 @@ -// Copyright (c) Richasy. All rights reserved. - -using System.ComponentModel; -using Richasy.Bili.Models.App.Other; -using Richasy.Bili.ViewModels.Uwp; -using Windows.UI.Xaml; -using Windows.UI.Xaml.Controls; - -namespace Richasy.Bili.App.Pages -{ - /// - /// 可用于自身或导航至 Frame 内部的空白页. - /// - public sealed partial class RankPage : Page - { - /// - /// 的依赖属性. - /// - public static readonly DependencyProperty ViewModelProperty = - DependencyProperty.Register(nameof(ViewModel), typeof(RankViewModel), typeof(RankPage), new PropertyMetadata(RankViewModel.Instance)); - - /// - /// Initializes a new instance of the class. - /// - public RankPage() - { - this.InitializeComponent(); - this.Loaded += OnLoadedAsync; - this.Unloaded += OnUnloaded; - } - - /// - /// 排行榜视图模型. - /// - public RankViewModel ViewModel - { - get { return (RankViewModel)GetValue(ViewModelProperty); } - set { SetValue(ViewModelProperty, value); } - } - - private void OnUnloaded(object sender, RoutedEventArgs e) - { - this.ViewModel.PropertyChanged -= OnViewModelPropertyChanged; - } - - private async void OnLoadedAsync(object sender, RoutedEventArgs e) - { - await ViewModel.InitializeAsync(); - this.ViewModel.PropertyChanged += OnViewModelPropertyChanged; - this.FindName(nameof(VideoView)); - CheckSelectedItem(); - } - - private void OnViewModelPropertyChanged(object sender, PropertyChangedEventArgs e) - { - if (e.PropertyName == nameof(ViewModel.CurrentPartition)) - { - CheckSelectedItem(); - } - } - - private void CheckSelectedItem() - { - if (RankNavigationView.SelectedItem != ViewModel.CurrentPartition) - { - RankNavigationView.SelectedItem = ViewModel.CurrentPartition; - } - } - - private async void OnDetailNavigationViewItemInvokedAsync(Microsoft.UI.Xaml.Controls.NavigationView sender, Microsoft.UI.Xaml.Controls.NavigationViewItemInvokedEventArgs args) - { - var item = args.InvokedItem as RankPartition; - ContentScrollViewer.ChangeView(0, 0, 1); - await ViewModel.SetSelectedRankPartitionAsync(item); - } - - private async void OnRefreshButtonClickAsync(object sender, RoutedEventArgs e) - { - if (ViewModel.PartitionCollection.Count == 0) - { - await ViewModel.InitializeAsync(); - } - else if (ViewModel.CurrentPartition != null) - { - await ViewModel.RefreshCurrentRankPartitionAsync(); - } - } - } -} diff --git a/src/App/Pages/RecommendPage.xaml b/src/App/Pages/RecommendPage.xaml deleted file mode 100644 index c2b41e522..000000000 --- a/src/App/Pages/RecommendPage.xaml +++ /dev/null @@ -1,83 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/src/App/Pages/RecommendPage.xaml.cs b/src/App/Pages/RecommendPage.xaml.cs deleted file mode 100644 index 9fdda41d3..000000000 --- a/src/App/Pages/RecommendPage.xaml.cs +++ /dev/null @@ -1,64 +0,0 @@ -// Copyright (c) Richasy. All rights reserved. - -using System.Linq; -using System.Threading.Tasks; -using Richasy.Bili.App.Controls; -using Richasy.Bili.ViewModels.Uwp; -using Windows.UI.Xaml; -using Windows.UI.Xaml.Controls; - -namespace Richasy.Bili.App.Pages -{ - /// - /// 首页. - /// - public sealed partial class RecommendPage : Page, IRefreshPage - { - /// - /// 的依赖属性. - /// - public static readonly DependencyProperty ViewModelProperty = - DependencyProperty.Register(nameof(ViewModel), typeof(RecommendViewModel), typeof(RecommendPage), new PropertyMetadata(RecommendViewModel.Instance)); - - /// - /// Initializes a new instance of the class. - /// - public RecommendPage() - { - this.InitializeComponent(); - this.Loaded += OnLoadedAsync; - } - - /// - /// 视频推荐视图模型. - /// - public RecommendViewModel ViewModel - { - get { return (RecommendViewModel)GetValue(ViewModelProperty); } - set { SetValue(ViewModelProperty, value); } - } - - /// - public Task RefreshAsync() - => ViewModel.InitializeRequestAsync(); - - private async void OnLoadedAsync(object sender, RoutedEventArgs e) - { - if (!ViewModel.VideoCollection.Any()) - { - await this.ViewModel.RequestDataAsync(); - this.FindName(nameof(VideoView)); - } - } - - private async void OnVideoViewRequestLoadMoreAsync(object sender, System.EventArgs e) - { - await ViewModel.RequestDataAsync(); - } - - private async void OnRefreshButtonClickAsync(object sender, RoutedEventArgs e) - { - await RefreshAsync(); - } - } -} diff --git a/src/App/Pages/Reuse/AnimePage.xaml b/src/App/Pages/Reuse/AnimePage.xaml deleted file mode 100644 index f67207dc0..000000000 --- a/src/App/Pages/Reuse/AnimePage.xaml +++ /dev/null @@ -1,239 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/src/App/Pages/Reuse/AnimePage.xaml.cs b/src/App/Pages/Reuse/AnimePage.xaml.cs deleted file mode 100644 index 3a2353ef2..000000000 --- a/src/App/Pages/Reuse/AnimePage.xaml.cs +++ /dev/null @@ -1,123 +0,0 @@ -// Copyright (c) Richasy. All rights reserved. - -using System.Threading.Tasks; -using Richasy.Bili.App.Controls; -using Richasy.Bili.Models.Enums; -using Richasy.Bili.ViewModels.Uwp; -using Windows.UI.Xaml; -using Windows.UI.Xaml.Controls; -using Windows.UI.Xaml.Navigation; - -namespace Richasy.Bili.App.Pages -{ - /// - /// 动漫页面,属于番剧和国创的共有页面. - /// - public sealed partial class AnimePage : Page, IRefreshPage - { - /// - /// 的依赖属性. - /// - public static readonly DependencyProperty ViewModelProperty = - DependencyProperty.Register(nameof(ViewModel), typeof(AnimeViewModelBase), typeof(AnimePage), new PropertyMetadata(null)); - - /// - /// Initializes a new instance of the class. - /// - public AnimePage() - { - this.InitializeComponent(); - this.Loaded += OnLoadedAsync; - } - - /// - /// 视图模型. - /// - public AnimeViewModelBase ViewModel - { - get { return (AnimeViewModelBase)GetValue(ViewModelProperty); } - set { SetValue(ViewModelProperty, value); } - } - - /// - public Task RefreshAsync() - => ViewModel.CurrentTab.InitializePartitionRequestAsync(); - - /// - protected override void OnNavigatedTo(NavigationEventArgs e) - { - if (e.Parameter is PgcType type) - { - switch (type) - { - case PgcType.Bangumi: - ViewModel = BangumiViewModel.Instance; - break; - case PgcType.Domestic: - ViewModel = DomesticViewModel.Instance; - break; - default: - break; - } - } - } - - private async void OnLoadedAsync(object sender, RoutedEventArgs e) - { - if (!ViewModel.IsRequested) - { - await ViewModel.InitializeRequestAsync(); - } - - CheckCurrentTabAsync(true); - } - - private async void OnRefreshButtonClickAsync(object sender, RoutedEventArgs e) - { - await RefreshAsync(); - } - - private async void CheckCurrentTabAsync(bool needDelay = false) - { - if (!(RootNavView.SelectedItem is PgcTabViewModel selectedItem) || selectedItem != ViewModel.CurrentTab) - { - if (needDelay) - { - await Task.Delay(100); - } - - RootNavView.SelectedItem = ViewModel.CurrentTab; - } - } - - private void OnRootNavViewItemInvoked(Microsoft.UI.Xaml.Controls.NavigationView sender, Microsoft.UI.Xaml.Controls.NavigationViewItemInvokedEventArgs args) - { - ViewModel.CurrentTab = args.InvokedItem as PgcTabViewModel; - CheckCurrentTabAsync(); - } - - private async void OnShowMoreButtonClickAsync(object sender, RoutedEventArgs e) - { - var vm = (sender as Button).Tag as PgcModuleViewModel; - if (vm.Id > 0) - { - await new PgcPlayListView().ShowAsync(vm.Id); - } - } - - private void OnIndexButtonClick(object sender, RoutedEventArgs e) - { - AppViewModel.Instance.SetOverlayContentId(PageIds.PgcIndex, ViewModel); - } - - private void OnTimeChartButtonClick(object sender, RoutedEventArgs e) - { - AppViewModel.Instance.SetOverlayContentId(PageIds.TimeLine, ViewModel); - } - - private async void OnVideoViewRequestLoadMoreAsync(object sender, System.EventArgs e) - { - await ViewModel.CurrentTab.DeltaPartitionRequestAsync(); - } - } -} diff --git a/src/App/Pages/Reuse/FeedPage.xaml b/src/App/Pages/Reuse/FeedPage.xaml deleted file mode 100644 index 0396d2fb7..000000000 --- a/src/App/Pages/Reuse/FeedPage.xaml +++ /dev/null @@ -1,89 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/src/App/Pages/Reuse/FeedPage.xaml.cs b/src/App/Pages/Reuse/FeedPage.xaml.cs deleted file mode 100644 index 89df128e2..000000000 --- a/src/App/Pages/Reuse/FeedPage.xaml.cs +++ /dev/null @@ -1,97 +0,0 @@ -// Copyright (c) Richasy. All rights reserved. - -using System.Threading.Tasks; -using Richasy.Bili.App.Controls; -using Richasy.Bili.Models.Enums; -using Richasy.Bili.ViewModels.Uwp; -using Windows.UI.Xaml; -using Windows.UI.Xaml.Controls; -using Windows.UI.Xaml.Navigation; - -namespace Richasy.Bili.App.Pages -{ - /// - /// 数据源PGC页面. - /// - public sealed partial class FeedPage : Page, IRefreshPage - { - /// - /// 的依赖属性. - /// - public static readonly DependencyProperty ViewModelProperty = - DependencyProperty.Register(nameof(ViewModel), typeof(FeedPgcViewModelBase), typeof(FeedPage), new PropertyMetadata(null)); - - /// - /// Initializes a new instance of the class. - /// - public FeedPage() - { - this.InitializeComponent(); - this.Loaded += OnLoadedAsync; - } - - /// - /// 视图模型. - /// - public FeedPgcViewModelBase ViewModel - { - get { return (FeedPgcViewModelBase)GetValue(ViewModelProperty); } - set { SetValue(ViewModelProperty, value); } - } - - /// - public async Task RefreshAsync() - { - await ViewModel.InitializeRequestAsync(); - ContentScrollViewer.ChangeView(0, 0, 1); - } - - /// - protected override void OnNavigatedTo(NavigationEventArgs e) - { - if (e.Parameter is PgcType type) - { - switch (type) - { - case PgcType.Documentary: - ViewModel = DocumentaryViewModel.Instance; - break; - case PgcType.Movie: - ViewModel = MovieViewModel.Instance; - break; - case PgcType.TV: - ViewModel = TvViewModel.Instance; - break; - default: - break; - } - } - } - - private async void OnLoadedAsync(object sender, RoutedEventArgs e) - { - if (!ViewModel.IsRequested) - { - await ViewModel.InitializeRequestAsync(); - } - } - - private async void OnRefreshButtonClickAsync(object sender, RoutedEventArgs e) - { - await RefreshAsync(); - } - - private async void OnFeedViewRequestLoadMoreAsync(object sender, System.EventArgs e) - { - if (!ViewModel.IsInitializeLoading && !ViewModel.IsDeltaLoading) - { - await ViewModel.DeltaRequestAsync(); - } - } - - private void OnIndexButtonClick(object sender, RoutedEventArgs e) - { - AppViewModel.Instance.SetOverlayContentId(PageIds.PgcIndex, ViewModel); - } - } -} diff --git a/src/App/Pages/RootPage.xaml b/src/App/Pages/RootPage.xaml index d1f58c328..6215ac26d 100644 --- a/src/App/Pages/RootPage.xaml +++ b/src/App/Pages/RootPage.xaml @@ -1,45 +1,50 @@ - - + - - - - - - - - - - - - + Visibility="{x:Bind ViewModel.IsMainViewShown, Mode=OneWay}" /> + + + + Background="{ThemeResource TransparentBackground}" + Visibility="{x:Bind ViewModel.IsPlayViewShown, Mode=OneWay}" /> - + + + + + - + diff --git a/src/App/Pages/RootPage.xaml.cs b/src/App/Pages/RootPage.xaml.cs index 0e1420d0a..c90a51c29 100644 --- a/src/App/Pages/RootPage.xaml.cs +++ b/src/App/Pages/RootPage.xaml.cs @@ -1,51 +1,94 @@ // Copyright (c) Richasy. All rights reserved. -using System.ComponentModel; -using Richasy.Bili.App.Controls; -using Richasy.Bili.Models.App.Args; -using Richasy.Bili.ViewModels.Uwp; +using System; +using System.Linq; +using System.Threading.Tasks; +using Bili.App.Controls; +using Bili.App.Controls.App; +using Bili.App.Controls.Article; +using Bili.App.Controls.Base; +using Bili.App.Controls.Community; +using Bili.App.Controls.Dialogs; +using Bili.App.Pages.Desktop.Overlay; +using Bili.DI.Container; +using Bili.Models.App.Args; +using Bili.Models.Enums; +using Bili.Models.Enums.App; +using Bili.ViewModels.Interfaces.Account; +using Bili.ViewModels.Interfaces.Article; +using Bili.ViewModels.Interfaces.Core; +using Bili.ViewModels.Interfaces.Pgc; +using Windows.ApplicationModel.Activation; +using Windows.UI; +using Windows.UI.Core; using Windows.UI.Xaml; using Windows.UI.Xaml.Controls; +using Windows.UI.Xaml.Media; using Windows.UI.Xaml.Media.Animation; +using Windows.UI.Xaml.Navigation; -namespace Richasy.Bili.App.Pages +namespace Bili.App.Pages.Desktop { /// /// The page is used for default loading. /// - public sealed partial class RootPage : Page + public sealed partial class RootPage : RootPageBase { - /// - /// 的依赖属性. - /// - public static readonly DependencyProperty ViewModelProperty = - DependencyProperty.Register(nameof(ViewModel), typeof(AppViewModel), typeof(RootPage), new PropertyMetadata(AppViewModel.Instance)); + private readonly ICallerViewModel _callerViewModel; + private readonly INavigationViewModel _navigationViewModel; + private readonly IRecordViewModel _recordViewModel; + private string _initialCommandParameters = null; + private Uri _initialUri; /// /// Initializes a new instance of the class. /// public RootPage() { - this.InitializeComponent(); - this.Loaded += OnLoadedAsync; - this.ViewModel.RequestShowTip += OnRequestShowTip; + InitializeComponent(); + Current = this; + _callerViewModel = Locator.Instance.GetService(); + _recordViewModel = Locator.Instance.GetService(); + _navigationViewModel = Locator.Instance.GetService(); + + ViewModel.Navigating += OnNavigating; + ViewModel.ExitPlayer += OnExitPlayer; + Loaded += OnLoaded; + SizeChanged += OnSizeChanged; + + _callerViewModel.RequestShowTip += OnRequestShowTip; + _callerViewModel.RequestShowUpdateDialog += OnRequestShowUpdateDialogAsync; + _callerViewModel.RequestContinuePlay += OnRequestContinuePlayAsync; + _callerViewModel.RequestShowImages += OnRequestShowImagesAsync; + _callerViewModel.RequestShowPgcPlaylist += OnRequestShowPgcPlaylist; + _callerViewModel.RequestShowArticleReader += OnRequestShowArticleReaderAsync; + _callerViewModel.RequestShowReplyDetail += OnRequestShowReplyDetail; + _callerViewModel.RequestShowPgcSeasonDetail += OnRequestShowPgcSeasonDetail; + + SystemNavigationManager.GetForCurrentView().BackRequested += OnBackRequested; + + NavigationViewBase navView = CoreViewModel.IsXbox + ? new XboxNavigationView() + : new DesktopNavigationView(); + navView.FirstLoaded += OnRootNavViewFirstLoadAsync; + NavViewPresenter.Content = navView; + + if (CoreViewModel.IsXbox) + { + Resources.Add("NavigationViewContentGridBorderBrush", new SolidColorBrush(Colors.Transparent)); + } } /// - /// 应用视图模型. + /// 根页面实例. /// - public AppViewModel ViewModel - { - get { return (AppViewModel)GetValue(ViewModelProperty); } - set { SetValue(ViewModelProperty, value); } - } + public static RootPage Current { get; private set; } /// /// 显示顶层视图. /// /// 要显示的元素. - /// 是否需要禁用返回按钮. - public void ShowOnHolder(UIElement element, bool needDisableBackButton = true) + public void ShowOnHolder(UIElement element) { if (!HolderContainer.Children.Contains(element)) { @@ -54,66 +97,256 @@ public void ShowOnHolder(UIElement element, bool needDisableBackButton = true) HolderContainer.Visibility = Visibility.Visible; - if (needDisableBackButton) + ViewModel.AddBackStack( + BackBehavior.ShowHolder, + ele => + { + RemoveFromHolder((UIElement)ele); + }, + element); + + if (element is Control e) { - ViewModel.IsBackButtonEnabled = false; + e.Focus(FocusState.Programmatic); } } /// - /// 清除顶层视图. + /// 显示提示信息,并在指定延时后关闭. /// - public void ClearHolder() + /// 要插入的元素. + /// 延时时间(秒). + /// . + public async Task ShowTipAsync(UIElement element, double delaySeconds) { - HolderContainer.Children.Clear(); - ViewModel.IsBackButtonEnabled = true; + TipContainer.Visibility = Visibility.Visible; + TipContainer.Children.Add(element); + element.Visibility = Visibility.Visible; + await Task.Delay(TimeSpan.FromSeconds(delaySeconds)); + element.Visibility = Visibility.Collapsed; + TipContainer.Children.Remove(element); + if (TipContainer.Children.Count == 0) + { + TipContainer.Visibility = Visibility.Collapsed; + } } + /// + protected override void OnNavigatedTo(NavigationEventArgs e) + { + if (e.Parameter is CommandLineActivatedEventArgs command) + { + _initialCommandParameters = command.Operation.Arguments; + } + else if (e.Parameter is IProtocolActivatedEventArgs protocol) + { + _initialUri = protocol.Uri; + } + } + + private void OnNavigating(object sender, AppNavigationEventArgs e) + { + if (e.Type == NavigationType.Secondary) + { + var type = GetSecondaryViewType(e.PageId); + PlayerFrame.Navigate(typeof(Page)); + SecondaryFrame.Navigate(type, e.Parameter, new DrillInNavigationTransitionInfo()); + } + else if (e.Type == NavigationType.Player) + { + var type = GetPlayerViewType(e.PageId); + PlayerFrame.Navigate(type, e.Parameter); + } + else if (e.Type == NavigationType.Main && SecondaryFrame.Content is AppPage) + { + SecondaryFrame.Navigate(typeof(Page)); + } + } + + private void OnExitPlayer(object sender, EventArgs e) + => PlayerFrame.Navigate(typeof(Page)); + /// /// 从顶层视图中移除元素. /// /// UI元素. - public void RemoveFromHolder(UIElement element) + private void RemoveFromHolder(UIElement element) + => HolderContainer.Children.Remove(element); + + private void OnLoaded(object sender, RoutedEventArgs e) { - HolderContainer.Children.Remove(element); + CoreViewModel.InitializePadding(); + Locator.Instance.GetService().TrySignInCommand.ExecuteAsync(true); +#if !DEBUG + CoreViewModel.CheckUpdateCommand.ExecuteAsync(null); +#endif } - private async void OnLoadedAsync(object sender, RoutedEventArgs e) + private void OnBackRequested(object sender, BackRequestedEventArgs e) { - this.ViewModel.PropertyChanged += OnViewModelPropertyChanged; - this.ViewModel.RequestPlay += OnRequestPlay; - await AccountViewModel.Instance.TrySignInAsync(true); + e.Handled = true; + Back(); } - private void OnRequestPlay(object sender, object e) + private void OnSizeChanged(object sender, SizeChangedEventArgs e) + => CoreViewModel.InitializePadding(); + + private async void OnRequestShowImagesAsync(object sender, ShowImageEventArgs e) { - OverFrame.Navigate(typeof(Overlay.PlayerPage), e, new DrillInNavigationTransitionInfo()); + var viewer = ImageViewer.Instance ?? new ImageViewer(); + if (e != null && e.Images?.Count() != 0) + { + ShowOnHolder(viewer); + await viewer.LoadImagesAsync(e.Images, e.ShowIndex); + } + else + { + _navigationViewModel.BackCommand.Execute(null); + } } - private void OnViewModelPropertyChanged(object sender, PropertyChangedEventArgs e) + private void OnRequestShowTip(object sender, AppTipNotificationEventArgs e) + => new TipPopup(e.Message).ShowAsync(e.Type); + + private async void OnRequestShowUpdateDialogAsync(object sender, UpdateEventArgs e) { - if (e.PropertyName == nameof(ViewModel.IsOpenPlayer)) + try { - if (!ViewModel.IsOpenPlayer) - { - ViewModel.ReleaseDisplayRequest(); - OverFrame.Navigate(typeof(Page)); - } - else - { - ViewModel.ActiveDisplayRequest(); - } + await new UpgradeDialog(e).ShowAsync(); } - else if (e.PropertyName == nameof(ViewModel.IsOverLayerExtendToTitleBar)) + catch (Exception) { - var stateName = ViewModel.IsOverLayerExtendToTitleBar ? nameof(ExtendedOverState) : nameof(DefaultOverState); - VisualStateManager.GoToState(this, stateName, false); } } - private void OnRequestShowTip(object sender, AppTipNotificationEventArgs e) + private async void OnRequestContinuePlayAsync(object sender, EventArgs e) { - new TipPopup(e.Message).ShowAsync(e.Type); + try + { + await new ContinuePlayDialog().ShowAsync(); + } + catch (Exception) + { + } + } + + private void OnRequestShowPgcPlaylist(object sender, IPgcPlaylistViewModel e) + { + var view = new PgcPlayListDetailView + { + ViewModel = e, + }; + + view.Show(); + } + + private async void OnRequestShowArticleReaderAsync(object sender, IArticleItemViewModel e) + { + var view = new ArticleReaderView(); + await view.ShowAsync(e); + } + + private void OnRequestShowReplyDetail(object sender, ShowCommentEventArgs e) + { + var commentPopup = new CommentPopup(); + commentPopup.Show(e); + } + + private void OnRequestShowPgcSeasonDetail(object sender, EventArgs e) + => new PgcSeasonDetailView().Show(); + + private Type GetSecondaryViewType(PageIds pageId) + { + var pageType = pageId switch + { + PageIds.VideoPartitionDetail => CoreViewModel.IsXbox + ? typeof(Xbox.Overlay.VideoPartitionDetailPage) + : typeof(Desktop.Overlay.VideoPartitionDetailPage), + PageIds.Search => CoreViewModel.IsXbox + ? typeof(Xbox.Overlay.SearchPage) + : typeof(Desktop.Overlay.SearchPage), + PageIds.ViewHistory => typeof(HistoryPage), + PageIds.Favorite => CoreViewModel.IsXbox + ? typeof(Xbox.Overlay.FavoritePage) + : typeof(Desktop.Overlay.FavoritePage), + PageIds.ViewLater => typeof(ViewLaterPage), + PageIds.Fans => typeof(FansPage), + PageIds.Follows => typeof(FollowsPage), + PageIds.PgcIndex => typeof(PgcIndexPage), + PageIds.TimeLine => typeof(TimelinePage), + PageIds.Message => typeof(MessagePage), + PageIds.UserSpace => CoreViewModel.IsXbox + ? typeof(Xbox.Overlay.UserSpacePage) + : typeof(Desktop.Overlay.UserSpacePage), + PageIds.VideoFavoriteDetail => CoreViewModel.IsXbox + ? typeof(Xbox.Overlay.VideoFavoriteDetailPage) + : typeof(Desktop.Overlay.VideoFavoriteDetailPage), + PageIds.LivePartition => CoreViewModel.IsXbox + ? typeof(Xbox.Overlay.LivePartitionPage) + : typeof(Desktop.Overlay.LivePartitionPage), + PageIds.LivePartitionDetail => CoreViewModel.IsXbox + ? typeof(Xbox.Overlay.LivePartitionDetailPage) + : typeof(Desktop.Overlay.LivePartitionDetailPage), + PageIds.MyFollows => typeof(MyFollowsPage), + _ => typeof(Page) + }; + + return pageType; + } + + private Type GetPlayerViewType(PageIds pageId) + { + var pageType = pageId switch + { + PageIds.VideoPlayer => CoreViewModel.IsXbox + ? typeof(Xbox.Overlay.VideoPlayerPage) + : typeof(Desktop.Overlay.VideoPlayerPage), + PageIds.PgcPlayer => CoreViewModel.IsXbox + ? typeof(Xbox.Overlay.PgcPlayerPage) + : typeof(Desktop.Overlay.PgcPlayerPage), + PageIds.LivePlayer => CoreViewModel.IsXbox + ? typeof(Xbox.Overlay.LivePlayerPage) + : typeof(Desktop.Overlay.LivePlayerPage), + _ => typeof(Page) + }; + + return pageType; + } + + private void Back() + { + if (ViewModel.CanBack) + { + ViewModel.BackCommand.Execute(null); + } + } + + private async void OnRootNavViewFirstLoadAsync(object sender, EventArgs e) + { + ViewModel.NavigateToMainView(PageIds.Recommend); + if (!string.IsNullOrEmpty(_initialCommandParameters)) + { + await CoreViewModel.InitializeCommandFromArgumentsAsync(_initialCommandParameters); + _initialCommandParameters = null; + } + else if (_initialUri != null) + { + await CoreViewModel.InitializeProtocolFromQueryAsync(_initialUri); + _initialUri = null; + } + else + { + _recordViewModel.CheckContinuePlayCommand.Execute(null); + } + + _ = CoreViewModel.CheckNewDynamicRegistrationCommand.ExecuteAsync(null); } } + + /// + /// 的基类. + /// + public class RootPageBase : AppPage + { + } } diff --git a/src/App/Pages/SettingPage.xaml b/src/App/Pages/SettingPage.xaml deleted file mode 100644 index 7a3748dbd..000000000 --- a/src/App/Pages/SettingPage.xaml +++ /dev/null @@ -1,79 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/src/App/Pages/SettingPage.xaml.cs b/src/App/Pages/SettingPage.xaml.cs deleted file mode 100644 index bf09b3725..000000000 --- a/src/App/Pages/SettingPage.xaml.cs +++ /dev/null @@ -1,34 +0,0 @@ -// Copyright (c) Richasy. All rights reserved. - -using Richasy.Bili.ViewModels.Uwp; -using Windows.UI.Xaml.Controls; -using Windows.UI.Xaml.Navigation; - -namespace Richasy.Bili.App.Pages -{ - /// - /// 可用于自身或导航至 Frame 内部的空白页. - /// - public sealed partial class SettingPage : Page - { - /// - /// Initializes a new instance of the class. - /// - public SettingPage() - { - this.InitializeComponent(); - ViewModel = SettingViewModel.Instance; - } - - /// - /// 视图模型. - /// - public SettingViewModel ViewModel { get; private set; } - - /// - protected override void OnNavigatedTo(NavigationEventArgs e) - { - ViewModel.InitializeSettings(); - } - } -} diff --git a/src/App/Pages/SpecialColumnPage.xaml b/src/App/Pages/SpecialColumnPage.xaml deleted file mode 100644 index addac1983..000000000 --- a/src/App/Pages/SpecialColumnPage.xaml +++ /dev/null @@ -1,161 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/src/App/Pages/SpecialColumnPage.xaml.cs b/src/App/Pages/SpecialColumnPage.xaml.cs deleted file mode 100644 index 53b6e3af5..000000000 --- a/src/App/Pages/SpecialColumnPage.xaml.cs +++ /dev/null @@ -1,105 +0,0 @@ -// Copyright (c) Richasy. All rights reserved. - -using System.Threading.Tasks; -using Richasy.Bili.App.Controls; -using Richasy.Bili.Models.Enums; -using Richasy.Bili.ViewModels.Uwp; -using Windows.UI.Xaml; -using Windows.UI.Xaml.Controls; - -namespace Richasy.Bili.App.Pages -{ - /// - /// 可用于自身或导航至 Frame 内部的空白页. - /// - public sealed partial class SpecialColumnPage : Page, IRefreshPage - { - /// - /// 的依赖属性. - /// - public static readonly DependencyProperty ViewModelProperty = - DependencyProperty.Register(nameof(ViewModel), typeof(SpecialColumnModuleViewModel), typeof(SpecialColumnPage), new PropertyMetadata(SpecialColumnModuleViewModel.Instance)); - - /// - /// Initializes a new instance of the class. - /// - public SpecialColumnPage() - { - this.InitializeComponent(); - NavigationCacheMode = Windows.UI.Xaml.Navigation.NavigationCacheMode.Enabled; - this.Loaded += OnLoadedAsync; - } - - /// - /// 视图模型. - /// - public SpecialColumnModuleViewModel ViewModel - { - get { return (SpecialColumnModuleViewModel)GetValue(ViewModelProperty); } - set { SetValue(ViewModelProperty, value); } - } - - /// - public Task RefreshAsync() - => ViewModel.CurrentCategory.InitializeRequestAsync(); - - private async void OnLoadedAsync(object sender, RoutedEventArgs e) - { - if (this.ViewModel.CategoryCollection.Count == 0) - { - await this.ViewModel.RequestCategoriesAsync(); - } - - CheckCurrentTabAsync(true); - } - - private async void OnArticleViewRequestLoadMoreAsync(object sender, System.EventArgs e) - { - if (!this.ViewModel.CurrentCategory.IsInitializeLoading && !this.ViewModel.CurrentCategory.IsDeltaLoading) - { - await this.ViewModel.CurrentCategory.RequestDataAsync(); - } - } - - private async void OnRefreshButtonClickAsync(object sender, RoutedEventArgs e) - { - await RefreshAsync(); - } - - private void OnArticleSortComboBoxSlectionChanged(object sender, SelectionChangedEventArgs e) - { - if (ArticleSortComboBox.SelectedItem != null) - { - var item = (ArticleSortType)ArticleSortComboBox.SelectedItem; - ViewModel.CurrentCategory.CurrentSortType = item; - } - } - - private void OnSpecialColumnNavigationViewItemInvoked(Microsoft.UI.Xaml.Controls.NavigationView sender, Microsoft.UI.Xaml.Controls.NavigationViewItemInvokedEventArgs args) - { - if (args.InvokedItem is SpecialColumnCategoryViewModel vm) - { - ViewModel.CurrentCategory = vm; - } - else if (args.InvokedItemContainer is Microsoft.UI.Xaml.Controls.NavigationViewItem item) - { - ViewModel.CurrentCategory = item.Tag as SpecialColumnCategoryViewModel; - } - - CheckCurrentTabAsync(); - } - - private async void CheckCurrentTabAsync(bool needDelay = false) - { - if (!(SpecialColumnNavigationView.SelectedItem is SpecialColumnCategoryViewModel selectedItem) || selectedItem != ViewModel.CurrentCategory) - { - if (needDelay) - { - await Task.Delay(100); - } - - SpecialColumnNavigationView.SelectedItem = ViewModel.CurrentCategory; - } - } - } -} diff --git a/src/App/Pages/TVPage.xaml b/src/App/Pages/TVPage.xaml deleted file mode 100644 index e129f110f..000000000 --- a/src/App/Pages/TVPage.xaml +++ /dev/null @@ -1,13 +0,0 @@ - - - - - - diff --git a/src/App/Pages/TVPage.xaml.cs b/src/App/Pages/TVPage.xaml.cs deleted file mode 100644 index 194bd0ddd..000000000 --- a/src/App/Pages/TVPage.xaml.cs +++ /dev/null @@ -1,38 +0,0 @@ -// Copyright (c) Richasy. All rights reserved. - -using System.Threading.Tasks; -using Richasy.Bili.App.Controls; -using Richasy.Bili.Models.Enums; -using Windows.UI.Xaml; -using Windows.UI.Xaml.Controls; -using Windows.UI.Xaml.Media.Animation; - -namespace Richasy.Bili.App.Pages -{ - /// - /// 电视剧页面. - /// - public sealed partial class TVPage : Page, IRefreshPage - { - /// - /// Initializes a new instance of the class. - /// - public TVPage() - { - this.InitializeComponent(); - this.Loaded += OnLoaded; - } - - /// - public Task RefreshAsync() - => (RootFrame.Content as IRefreshPage).RefreshAsync(); - - private void OnLoaded(object sender, RoutedEventArgs e) - { - if (RootFrame.Content == null) - { - RootFrame.Navigate(typeof(FeedPage), PgcType.TV, new SuppressNavigationTransitionInfo()); - } - } - } -} diff --git a/src/App/Pages/Xbox/BangumiPage.xaml b/src/App/Pages/Xbox/BangumiPage.xaml new file mode 100644 index 000000000..11be9a168 --- /dev/null +++ b/src/App/Pages/Xbox/BangumiPage.xaml @@ -0,0 +1,19 @@ + + + + + + + + diff --git a/src/App/Pages/Xbox/BangumiPage.xaml.cs b/src/App/Pages/Xbox/BangumiPage.xaml.cs new file mode 100644 index 000000000..ef17b8164 --- /dev/null +++ b/src/App/Pages/Xbox/BangumiPage.xaml.cs @@ -0,0 +1,25 @@ +// Copyright (c) Richasy. All rights reserved. + +using Bili.App.Pages.Base; + +namespace Bili.App.Pages.Xbox +{ + /// + /// 番剧页面. + /// + public sealed partial class BangumiPage : BangumiPageBase + { + /// + /// Initializes a new instance of the class. + /// + public BangumiPage() => InitializeComponent(); + + /// + protected override void OnPageLoaded() + => Bindings.Update(); + + /// + protected override void OnPageUnloaded() + => Bindings.StopTracking(); + } +} diff --git a/src/App/Pages/Xbox/DocumentaryPage.xaml b/src/App/Pages/Xbox/DocumentaryPage.xaml new file mode 100644 index 000000000..8945b825c --- /dev/null +++ b/src/App/Pages/Xbox/DocumentaryPage.xaml @@ -0,0 +1,14 @@ + + + + diff --git a/src/App/Pages/Xbox/DocumentaryPage.xaml.cs b/src/App/Pages/Xbox/DocumentaryPage.xaml.cs new file mode 100644 index 000000000..3b7d49b9c --- /dev/null +++ b/src/App/Pages/Xbox/DocumentaryPage.xaml.cs @@ -0,0 +1,25 @@ +// Copyright (c) Richasy. All rights reserved. + +using Bili.App.Pages.Base; + +namespace Bili.App.Pages.Xbox +{ + /// + /// 纪录片页面. + /// + public sealed partial class DocumentaryPage : DocumentaryPageBase + { + /// + /// Initializes a new instance of the class. + /// + public DocumentaryPage() => InitializeComponent(); + + /// + protected override void OnPageLoaded() + => Bindings.Update(); + + /// + protected override void OnPageUnloaded() + => Bindings.StopTracking(); + } +} diff --git a/src/App/Pages/Xbox/DomesticPage.xaml b/src/App/Pages/Xbox/DomesticPage.xaml new file mode 100644 index 000000000..954be23e7 --- /dev/null +++ b/src/App/Pages/Xbox/DomesticPage.xaml @@ -0,0 +1,19 @@ + + + + + + + + diff --git a/src/App/Pages/Xbox/DomesticPage.xaml.cs b/src/App/Pages/Xbox/DomesticPage.xaml.cs new file mode 100644 index 000000000..2ae50475e --- /dev/null +++ b/src/App/Pages/Xbox/DomesticPage.xaml.cs @@ -0,0 +1,25 @@ +// Copyright (c) Richasy. All rights reserved. + +using Bili.App.Pages.Base; + +namespace Bili.App.Pages.Xbox +{ + /// + /// 国创页面. + /// + public sealed partial class DomesticPage : DomesticPageBase + { + /// + /// Initializes a new instance of the class. + /// + public DomesticPage() => InitializeComponent(); + + /// + protected override void OnPageLoaded() + => Bindings.Update(); + + /// + protected override void OnPageUnloaded() + => Bindings.StopTracking(); + } +} diff --git a/src/App/Pages/Xbox/DynamicPage.xaml b/src/App/Pages/Xbox/DynamicPage.xaml new file mode 100644 index 000000000..b1d2d4040 --- /dev/null +++ b/src/App/Pages/Xbox/DynamicPage.xaml @@ -0,0 +1,81 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/App/Pages/Xbox/DynamicPage.xaml.cs b/src/App/Pages/Xbox/DynamicPage.xaml.cs new file mode 100644 index 000000000..54febad72 --- /dev/null +++ b/src/App/Pages/Xbox/DynamicPage.xaml.cs @@ -0,0 +1,28 @@ +// Copyright (c) Richasy. All rights reserved. + +using Bili.App.Pages.Base; + +namespace Bili.App.Pages.Xbox +{ + /// + /// 动态页面. + /// + public sealed partial class DynamicPage : DynamicPageBase + { + /// + /// Initializes a new instance of the class. + /// + public DynamicPage() => InitializeComponent(); + + /// + protected override void OnPageLoaded() + { + Bindings.Update(); + ViewModel.VideoModule.InitializeCommand.ExecuteAsync(null); + } + + /// + protected override void OnPageUnloaded() + => Bindings.StopTracking(); + } +} diff --git a/src/App/Pages/Xbox/LiveFeedPage.xaml b/src/App/Pages/Xbox/LiveFeedPage.xaml new file mode 100644 index 000000000..5bf0074f8 --- /dev/null +++ b/src/App/Pages/Xbox/LiveFeedPage.xaml @@ -0,0 +1,130 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/App/Pages/Xbox/Overlay/FavoritePage.xaml.cs b/src/App/Pages/Xbox/Overlay/FavoritePage.xaml.cs new file mode 100644 index 000000000..ac8e4e14a --- /dev/null +++ b/src/App/Pages/Xbox/Overlay/FavoritePage.xaml.cs @@ -0,0 +1,73 @@ +// Copyright (c) Richasy. All rights reserved. + +using System.Linq; +using Bili.App.Pages.Base; +using Bili.Models.App.Other; +using Bili.Models.Enums.App; +using Windows.UI.Xaml.Navigation; + +namespace Bili.App.Pages.Xbox.Overlay +{ + /// + /// 收藏页面. + /// + public sealed partial class FavoritePage : FavoritePageBase + { + /// + /// Initializes a new instance of the class. + /// + public FavoritePage() + { + InitializeComponent(); + AnimePanel.ViewModel = AnimeFavoriteModule; + AnimePanel.DataContext = AnimeFavoriteModule; + CinemaPanel.ViewModel = CinemaFavoriteModule; + CinemaPanel.DataContext = CinemaFavoriteModule; + } + + /// + protected override void OnNavigatedTo(NavigationEventArgs e) + { + if (e.Parameter is FavoriteType type) + { + var header = ViewModel.TypeCollection.First(p => p.Type == type); + ViewModel.SelectTypeCommand.Execute(header); + } + } + + /// + protected override void OnPageLoaded() + => Bindings.Update(); + + /// + protected override void OnPageUnloaded() + => Bindings.StopTracking(); + + private void OnRefreshButtonClickAsync(object sender, Windows.UI.Xaml.RoutedEventArgs e) + { + switch (ViewModel.CurrentType.Type) + { + case FavoriteType.Video: + VideoPanel.ViewModel.ReloadCommand.ExecuteAsync(null); + break; + case FavoriteType.Anime: + AnimePanel.ViewModel.ReloadCommand.ExecuteAsync(null); + break; + case FavoriteType.Cinema: + CinemaPanel.ViewModel.ReloadCommand.ExecuteAsync(null); + break; + default: + break; + } + } + + private void OnNavItemInvoked(Microsoft.UI.Xaml.Controls.NavigationView sender, Microsoft.UI.Xaml.Controls.NavigationViewItemInvokedEventArgs args) + { + var type = args.InvokedItem as FavoriteHeader; + if (type != ViewModel.CurrentType) + { + ViewModel.SelectTypeCommand.Execute(type); + } + } + } +} diff --git a/src/App/Pages/Xbox/Overlay/LivePartitionDetailPage.xaml b/src/App/Pages/Xbox/Overlay/LivePartitionDetailPage.xaml new file mode 100644 index 000000000..74f72862c --- /dev/null +++ b/src/App/Pages/Xbox/Overlay/LivePartitionDetailPage.xaml @@ -0,0 +1,121 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/App/Pages/Xbox/XboxAccountPage.xaml.cs b/src/App/Pages/Xbox/XboxAccountPage.xaml.cs new file mode 100644 index 000000000..e861e631e --- /dev/null +++ b/src/App/Pages/Xbox/XboxAccountPage.xaml.cs @@ -0,0 +1,32 @@ +// Copyright (c) Richasy. All rights reserved. + +using Bili.ViewModels.Interfaces.Account; + +namespace Bili.App.Pages.Xbox +{ + /// + /// Xbox 账户页面. + /// + public sealed partial class XboxAccountPage : XboxAccountPageBase + { + /// + /// Initializes a new instance of the class. + /// + public XboxAccountPage() => InitializeComponent(); + + /// + protected override void OnPageLoaded() + => Bindings.Update(); + + /// + protected override void OnPageUnloaded() + => Bindings.StopTracking(); + } + + /// + /// 的基类. + /// + public class XboxAccountPageBase : AppPage + { + } +} diff --git a/src/App/README.md b/src/App/README.md new file mode 100644 index 000000000..1d5997424 --- /dev/null +++ b/src/App/README.md @@ -0,0 +1,14 @@ +# 主项目介绍 + +这是哔哩应用的主项目,对于 UWP 应用来说,不需要额外的打包项目。 + +该项目主要用于处理 UI,是应用架构中的最上层,和用户直接交互。 + +项目中包含的内容主要分为: + +- 页面 +- 控件 +- 资源,包括样式资源和文本资源 +- 辅助工具,包括各种数据转换器、UI 扩展、行为(Behavior) 等 + +一般来说,对于需要复用且样式可能更改的控件,推荐使用 `TemplateControl` 模板,而简单的控件集合体则可以使用 `UserControl` 模板。 \ No newline at end of file diff --git a/src/App/Resources/Converter/BoolToVisibilityConverter.cs b/src/App/Resources/Converter/BoolToVisibilityConverter.cs index d9809788e..f2495bcbf 100644 --- a/src/App/Resources/Converter/BoolToVisibilityConverter.cs +++ b/src/App/Resources/Converter/BoolToVisibilityConverter.cs @@ -4,7 +4,7 @@ using Windows.UI.Xaml; using Windows.UI.Xaml.Data; -namespace Richasy.Bili.App.Resources.Converter +namespace Bili.App.Resources.Converter { /// /// 的转换. diff --git a/src/App/Resources/Converter/ColorConverter.cs b/src/App/Resources/Converter/ColorConverter.cs index 4a852712b..ee9388492 100644 --- a/src/App/Resources/Converter/ColorConverter.cs +++ b/src/App/Resources/Converter/ColorConverter.cs @@ -1,12 +1,12 @@ // Copyright (c) Richasy. All rights reserved. using System; -using Richasy.Bili.App.Resources.Extension; +using Bili.App.Resources.Extension; using Windows.UI; using Windows.UI.Xaml.Data; using Windows.UI.Xaml.Media; -namespace Richasy.Bili.App.Resources.Converter +namespace Bili.App.Resources.Converter { /// /// 文本哈希到颜色的转换. diff --git a/src/App/Resources/Converter/DanmakuFontSizeConverter.cs b/src/App/Resources/Converter/DanmakuFontSizeConverter.cs new file mode 100644 index 000000000..f43b404a1 --- /dev/null +++ b/src/App/Resources/Converter/DanmakuFontSizeConverter.cs @@ -0,0 +1,48 @@ +// Copyright (c) Richasy. All rights reserved. + +using System; +using Bili.DI.Container; +using Bili.Models.Enums; +using Bili.Toolkit.Interfaces; +using Windows.UI.Xaml.Data; + +namespace Bili.App.Resources.Converter +{ + /// + /// 弹幕大小转换器. + /// + internal sealed class DanmakuFontSizeConverter : IValueConverter + { + /// + public object Convert(object value, Type targetType, object parameter, string language) + { + var size = (double)value; + var name = LanguageNames.Standard; + switch (size) + { + case 0.5: + name = LanguageNames.Minimum; + break; + case 1: + name = LanguageNames.Small; + break; + case 1.5: + name = LanguageNames.Standard; + break; + case 2: + name = LanguageNames.Large; + break; + case 2.5: + name = LanguageNames.Maximum; + break; + default: + break; + } + + return Locator.Instance.GetService().GetLocaleString(name); + } + + /// + public object ConvertBack(object value, Type targetType, object parameter, string language) => throw new NotImplementedException(); + } +} diff --git a/src/App/Resources/Converter/DanmakuLocationConverter.cs b/src/App/Resources/Converter/DanmakuLocationConverter.cs index f9ca5bb20..95cf186a4 100644 --- a/src/App/Resources/Converter/DanmakuLocationConverter.cs +++ b/src/App/Resources/Converter/DanmakuLocationConverter.cs @@ -1,12 +1,12 @@ // Copyright (c) Richasy. All rights reserved. using System; -using Richasy.Bili.Locator.Uwp; -using Richasy.Bili.Models.Enums.App; -using Richasy.Bili.Toolkit.Interfaces; +using Bili.DI.Container; +using Bili.Models.Enums.App; +using Bili.Toolkit.Interfaces; using Windows.UI.Xaml.Data; -namespace Richasy.Bili.App.Resources.Converter +namespace Bili.App.Resources.Converter { /// /// 弹幕位置文本转换. @@ -17,7 +17,7 @@ public class DanmakuLocationConverter : IValueConverter public object Convert(object value, Type targetType, object parameter, string language) { var result = string.Empty; - var resToolkit = ServiceLocator.Instance.GetService(); + var resToolkit = Locator.Instance.GetService(); switch ((DanmakuLocation)value) { case DanmakuLocation.Scroll: diff --git a/src/App/Resources/Converter/DanmakuStyleConverter.cs b/src/App/Resources/Converter/DanmakuStyleConverter.cs deleted file mode 100644 index 84dc2b5d1..000000000 --- a/src/App/Resources/Converter/DanmakuStyleConverter.cs +++ /dev/null @@ -1,40 +0,0 @@ -// Copyright (c) Richasy. All rights reserved. - -using System; -using Richasy.Bili.Locator.Uwp; -using Richasy.Bili.Models.Enums.App; -using Richasy.Bili.Toolkit.Interfaces; -using Windows.UI.Xaml.Data; - -namespace Richasy.Bili.App.Resources.Converter -{ - /// - /// 弹幕样式转换器. - /// - public class DanmakuStyleConverter : IValueConverter - { - /// - public object Convert(object value, Type targetType, object parameter, string language) - { - var result = string.Empty; - var resToolkit = ServiceLocator.Instance.GetService(); - switch ((DanmakuStyle)value) - { - case DanmakuStyle.Stroke: - result = resToolkit.GetLocaleString(Models.Enums.LanguageNames.Stroke); - break; - case DanmakuStyle.NoStroke: - result = resToolkit.GetLocaleString(Models.Enums.LanguageNames.NoStroke); - break; - case DanmakuStyle.Shadow: - result = resToolkit.GetLocaleString(Models.Enums.LanguageNames.Shadow); - break; - } - - return result; - } - - /// - public object ConvertBack(object value, Type targetType, object parameter, string language) => throw new NotImplementedException(); - } -} diff --git a/src/App/Resources/Converter/DecodeTypeConverter.cs b/src/App/Resources/Converter/DecodeTypeConverter.cs new file mode 100644 index 000000000..b601fb1cd --- /dev/null +++ b/src/App/Resources/Converter/DecodeTypeConverter.cs @@ -0,0 +1,43 @@ +// Copyright (c) Richasy. All rights reserved. + +using System; +using Bili.DI.Container; +using Bili.Models.Enums; +using Bili.Models.Enums.App; +using Bili.Toolkit.Interfaces; +using Windows.UI.Xaml.Data; + +namespace Bili.App.Resources.Converter +{ + internal sealed class DecodeTypeConverter : IValueConverter + { + /// + public object Convert(object value, Type targetType, object parameter, string language) + { + var result = string.Empty; + var resourceToolkit = Locator.Instance.GetService(); + if (value is DecodeType decode) + { + switch (decode) + { + case DecodeType.Automatic: + result = resourceToolkit.GetLocaleString(LanguageNames.Automatic); + break; + case DecodeType.HardwareDecode: + result = resourceToolkit.GetLocaleString(LanguageNames.HardwareDecode); + break; + case DecodeType.SoftwareDecode: + result = resourceToolkit.GetLocaleString(LanguageNames.SoftwareDecode); + break; + default: + break; + } + } + + return result; + } + + /// + public object ConvertBack(object value, Type targetType, object parameter, string language) => throw new NotImplementedException(); + } +} diff --git a/src/App/Resources/Converter/DurationConverter.cs b/src/App/Resources/Converter/DurationConverter.cs index 958047982..6fbf65724 100644 --- a/src/App/Resources/Converter/DurationConverter.cs +++ b/src/App/Resources/Converter/DurationConverter.cs @@ -1,11 +1,11 @@ // Copyright (c) Richasy. All rights reserved. using System; -using Richasy.Bili.Locator.Uwp; -using Richasy.Bili.Toolkit.Interfaces; +using Bili.DI.Container; +using Bili.Toolkit.Interfaces; using Windows.UI.Xaml.Data; -namespace Richasy.Bili.App.Resources.Converter +namespace Bili.App.Resources.Converter { /// /// 时长转换为可读文本. @@ -20,7 +20,7 @@ public class DurationConverter : IValueConverter /// public object Convert(object value, Type targetType, object parameter, string language) { - var numToolkit = ServiceLocator.Instance.GetService(); + var numToolkit = Locator.Instance.GetService(); if (value is int time) { if (IsMilliseconds) diff --git a/src/App/Resources/Converter/EpisodeCoverConverter.cs b/src/App/Resources/Converter/EpisodeCoverConverter.cs index ead4d05eb..b0502ba8b 100644 --- a/src/App/Resources/Converter/EpisodeCoverConverter.cs +++ b/src/App/Resources/Converter/EpisodeCoverConverter.cs @@ -4,7 +4,7 @@ using Windows.UI.Xaml.Data; using Windows.UI.Xaml.Media.Imaging; -namespace Richasy.Bili.App.Resources.Converter +namespace Bili.App.Resources.Converter { /// /// 剧集封面转换器(降低分辨率). diff --git a/src/App/Resources/Converter/EpisodeTitleConverter.cs b/src/App/Resources/Converter/EpisodeTitleConverter.cs deleted file mode 100644 index 0ec24884e..000000000 --- a/src/App/Resources/Converter/EpisodeTitleConverter.cs +++ /dev/null @@ -1,41 +0,0 @@ -// Copyright (c) Richasy. All rights reserved. - -using System; -using Richasy.Bili.Models.BiliBili; -using Windows.UI.Xaml.Data; - -namespace Richasy.Bili.App.Resources.Converter -{ - /// - /// 剧集标题转换器. - /// - public class EpisodeTitleConverter : IValueConverter - { - /// - public object Convert(object value, Type targetType, object parameter, string language) - { - var result = "--"; - - if (value is PgcEpisodeDetail episode) - { - if (!string.IsNullOrEmpty(episode.LongTitle)) - { - result = episode.LongTitle; - } - else if (!string.IsNullOrEmpty(episode.Title)) - { - result = episode.Title; - } - else - { - result = episode.Index.ToString(); - } - } - - return result; - } - - /// - public object ConvertBack(object value, Type targetType, object parameter, string language) => throw new NotImplementedException(); - } -} diff --git a/src/App/Resources/Converter/ErrorOpacityConverter.cs b/src/App/Resources/Converter/ErrorOpacityConverter.cs new file mode 100644 index 000000000..0c5b1954f --- /dev/null +++ b/src/App/Resources/Converter/ErrorOpacityConverter.cs @@ -0,0 +1,17 @@ +// Copyright (c) Richasy. All rights reserved. + +using System; +using Windows.UI.Xaml.Data; + +namespace Bili.App.Resources.Converter +{ + internal sealed class ErrorOpacityConverter : IValueConverter + { + public object Convert(object value, Type targetType, object parameter, string language) + => value is bool isError + ? isError ? 0d : 1d + : 1d; + + public object ConvertBack(object value, Type targetType, object parameter, string language) => throw new NotImplementedException(); + } +} diff --git a/src/App/Resources/Converter/FavoriteTypeConverter.cs b/src/App/Resources/Converter/FavoriteTypeConverter.cs index 6d7ddda08..398c351d7 100644 --- a/src/App/Resources/Converter/FavoriteTypeConverter.cs +++ b/src/App/Resources/Converter/FavoriteTypeConverter.cs @@ -1,13 +1,13 @@ // Copyright (c) Richasy. All rights reserved. using System; -using Richasy.Bili.Locator.Uwp; -using Richasy.Bili.Models.Enums; -using Richasy.Bili.Models.Enums.App; -using Richasy.Bili.Toolkit.Interfaces; +using Bili.DI.Container; +using Bili.Models.Enums; +using Bili.Models.Enums.App; +using Bili.Toolkit.Interfaces; using Windows.UI.Xaml.Data; -namespace Richasy.Bili.App.Resources.Converter +namespace Bili.App.Resources.Converter { /// /// 收藏夹类型转换器. @@ -19,7 +19,7 @@ public object Convert(object value, Type targetType, object parameter, string la { var type = (FavoriteType)value; var result = string.Empty; - var resourceToolkit = ServiceLocator.Instance.GetService(); + var resourceToolkit = Locator.Instance.GetService(); switch (type) { case FavoriteType.Video: diff --git a/src/App/Resources/Converter/FixedContentConverter.cs b/src/App/Resources/Converter/FixedContentConverter.cs new file mode 100644 index 000000000..72d682ae9 --- /dev/null +++ b/src/App/Resources/Converter/FixedContentConverter.cs @@ -0,0 +1,28 @@ +// Copyright (c) Richasy. All rights reserved. + +using System; +using Bili.DI.Container; +using Bili.Toolkit.Interfaces; +using Windows.UI.Xaml.Data; + +namespace Bili.App.Resources.Converter +{ + /// + /// 固定内容转换器. + /// + public sealed class FixedContentConverter : IValueConverter + { + /// + public object Convert(object value, Type targetType, object parameter, string language) + { + var isFixed = System.Convert.ToBoolean(value); + var resourceToolkit = Locator.Instance.GetService(); + return isFixed + ? resourceToolkit.GetLocaleString(Models.Enums.LanguageNames.UnfixContent) + : resourceToolkit.GetLocaleString(Models.Enums.LanguageNames.FixContent); + } + + /// + public object ConvertBack(object value, Type targetType, object parameter, string language) => throw new NotImplementedException(); + } +} diff --git a/src/App/Resources/Converter/HorizontalThicknessConverter.cs b/src/App/Resources/Converter/HorizontalThicknessConverter.cs new file mode 100644 index 000000000..44e244ea1 --- /dev/null +++ b/src/App/Resources/Converter/HorizontalThicknessConverter.cs @@ -0,0 +1,19 @@ +// Copyright (c) Richasy. All rights reserved. + +using System; +using Windows.UI.Xaml; +using Windows.UI.Xaml.Data; + +namespace Bili.App.Resources.Converter +{ + internal sealed class HorizontalThicknessConverter : IValueConverter + { + public object Convert(object value, Type targetType, object parameter, string language) + { + var v = (double)value; + return new Thickness(v, 0, v, 0); + } + + public object ConvertBack(object value, Type targetType, object parameter, string language) => throw new NotImplementedException(); + } +} diff --git a/src/App/Resources/Converter/ImageConverter.cs b/src/App/Resources/Converter/ImageConverter.cs new file mode 100644 index 000000000..eeb5d6945 --- /dev/null +++ b/src/App/Resources/Converter/ImageConverter.cs @@ -0,0 +1,16 @@ +// Copyright (c) Richasy. All rights reserved. + +using System; +using Windows.UI.Xaml.Data; +using Windows.UI.Xaml.Media.Imaging; + +namespace Bili.App.Resources.Converter +{ + internal sealed class ImageConverter : IValueConverter + { + public object Convert(object value, Type targetType, object parameter, string language) + => value is string uri ? new BitmapImage(new Uri(uri)) : (object)null; + + public object ConvertBack(object value, Type targetType, object parameter, string language) => throw new NotImplementedException(); + } +} diff --git a/src/App/Resources/Converter/MTCControlModeConverter.cs b/src/App/Resources/Converter/MTCControlModeConverter.cs deleted file mode 100644 index 183e23dc4..000000000 --- a/src/App/Resources/Converter/MTCControlModeConverter.cs +++ /dev/null @@ -1,42 +0,0 @@ -// Copyright (c) Richasy. All rights reserved. - -using System; -using Richasy.Bili.Locator.Uwp; -using Richasy.Bili.Models.Enums; -using Richasy.Bili.Toolkit.Interfaces; -using Windows.UI.Xaml.Data; - -namespace Richasy.Bili.App.Resources.Converter -{ - /// - /// 媒体传输控件控制模式转换器. - /// - public class MTCControlModeConverter : IValueConverter - { - /// - public object Convert(object value, Type targetType, object parameter, string language) - { - var result = string.Empty; - var resourceToolkit = ServiceLocator.Instance.GetService(); - if (value is MTCControlMode mode) - { - switch (mode) - { - case MTCControlMode.Automatic: - result = resourceToolkit.GetLocaleString(LanguageNames.Automatic); - break; - case MTCControlMode.Manual: - result = resourceToolkit.GetLocaleString(LanguageNames.Manual); - break; - default: - break; - } - } - - return result; - } - - /// - public object ConvertBack(object value, Type targetType, object parameter, string language) => throw new NotImplementedException(); - } -} diff --git a/src/App/Resources/Converter/NumberToVisibilityConverter.cs b/src/App/Resources/Converter/NumberToVisibilityConverter.cs index 76502ee1c..817170a5c 100644 --- a/src/App/Resources/Converter/NumberToVisibilityConverter.cs +++ b/src/App/Resources/Converter/NumberToVisibilityConverter.cs @@ -4,10 +4,10 @@ using Windows.UI.Xaml; using Windows.UI.Xaml.Data; -namespace Richasy.Bili.App.Resources.Converter +namespace Bili.App.Resources.Converter { /// - /// 数字转可见性,等于0则不显示. + /// 数字转可见性,小于等于0则不显示. /// public class NumberToVisibilityConverter : IValueConverter { @@ -15,7 +15,7 @@ public class NumberToVisibilityConverter : IValueConverter public object Convert(object value, Type targetType, object parameter, string language) { var number = System.Convert.ToDouble(value); - return number == 0 ? Visibility.Collapsed : Visibility.Visible; + return number <= 0 ? Visibility.Collapsed : Visibility.Visible; } /// diff --git a/src/App/Resources/Converter/ObjectToBoolConverter.cs b/src/App/Resources/Converter/ObjectToBoolConverter.cs index 1d345b788..f69f38275 100644 --- a/src/App/Resources/Converter/ObjectToBoolConverter.cs +++ b/src/App/Resources/Converter/ObjectToBoolConverter.cs @@ -3,7 +3,7 @@ using System; using Windows.UI.Xaml.Data; -namespace Richasy.Bili.App.Resources.Converter +namespace Bili.App.Resources.Converter { /// /// 对象到Boolean的转换器. @@ -31,6 +31,10 @@ public object Convert(object value, Type targetType, object parameter, string la { result = !string.IsNullOrEmpty(str); } + else if (value is bool b) + { + result = b; + } else { result = true; diff --git a/src/App/Resources/Converter/ObjectToVisibilityConverter.cs b/src/App/Resources/Converter/ObjectToVisibilityConverter.cs index 89e533c99..a6966bba5 100644 --- a/src/App/Resources/Converter/ObjectToVisibilityConverter.cs +++ b/src/App/Resources/Converter/ObjectToVisibilityConverter.cs @@ -4,26 +4,37 @@ using Windows.UI.Xaml; using Windows.UI.Xaml.Data; -namespace Richasy.Bili.App.Resources.Converter +namespace Bili.App.Resources.Converter { /// /// 对象到可见性的转换器. 当对象为空时,返回Collapsed. /// public class ObjectToVisibilityConverter : IValueConverter { + /// + /// 是否反转结果. + /// + public bool IsReverse { get; set; } + /// public object Convert(object value, Type targetType, object parameter, string language) { + var isShow = true; if (value == null) { - return Visibility.Collapsed; + isShow = false; } else if (value is string str) { - return string.IsNullOrEmpty(str) ? Visibility.Collapsed : Visibility.Visible; + isShow = !string.IsNullOrEmpty(str); + } + + if (IsReverse) + { + isShow = !isShow; } - return Visibility.Visible; + return isShow ? Visibility.Visible : Visibility.Collapsed; } /// diff --git a/src/App/Resources/Converter/PgcFavoriteStatusConverter.cs b/src/App/Resources/Converter/PgcFavoriteStatusConverter.cs new file mode 100644 index 000000000..4ebc3597f --- /dev/null +++ b/src/App/Resources/Converter/PgcFavoriteStatusConverter.cs @@ -0,0 +1,38 @@ +// Copyright (c) Richasy. All rights reserved. + +using System; +using Bili.DI.Container; +using Bili.Toolkit.Interfaces; +using Windows.UI.Xaml.Data; + +namespace Bili.App.Resources.Converter +{ + /// + /// PGC收藏状态转换器. + /// + internal sealed class PgcFavoriteStatusConverter : IValueConverter + { + public object Convert(object value, Type targetType, object parameter, string language) + { + if (value is int status) + { + var resourceToolkit = Locator.Instance.GetService(); + switch (status) + { + case 1: + return resourceToolkit.GetLocaleString(Models.Enums.LanguageNames.WantWatch); + case 2: + return resourceToolkit.GetLocaleString(Models.Enums.LanguageNames.Watching); + case 3: + return resourceToolkit.GetLocaleString(Models.Enums.LanguageNames.Watched); + default: + break; + } + } + + return string.Empty; + } + + public object ConvertBack(object value, Type targetType, object parameter, string language) => throw new NotImplementedException(); + } +} diff --git a/src/App/Resources/Converter/PgcFollowTextConverter.cs b/src/App/Resources/Converter/PgcFollowTextConverter.cs index 2d2f6af45..2518c34ce 100644 --- a/src/App/Resources/Converter/PgcFollowTextConverter.cs +++ b/src/App/Resources/Converter/PgcFollowTextConverter.cs @@ -1,11 +1,11 @@ // Copyright (c) Richasy. All rights reserved. using System; -using Richasy.Bili.Locator.Uwp; -using Richasy.Bili.Toolkit.Interfaces; +using Bili.DI.Container; +using Bili.Toolkit.Interfaces; using Windows.UI.Xaml.Data; -namespace Richasy.Bili.App.Resources.Converter +namespace Bili.App.Resources.Converter { /// /// 追番/追剧文本转换. @@ -18,7 +18,7 @@ public object Convert(object value, Type targetType, object parameter, string la var result = string.Empty; if (value is bool isFollow) { - var resourceToolkit = ServiceLocator.Instance.GetService(); + var resourceToolkit = Locator.Instance.GetService(); result = isFollow ? resourceToolkit.GetLocaleString(Models.Enums.LanguageNames.PgcFollowing) : resourceToolkit.GetLocaleString(Models.Enums.LanguageNames.PgcNotFollow); diff --git a/src/App/Resources/Converter/PlayerDisplayModeConverter.cs b/src/App/Resources/Converter/PlayerDisplayModeConverter.cs index 940cd2fae..eb24c4525 100644 --- a/src/App/Resources/Converter/PlayerDisplayModeConverter.cs +++ b/src/App/Resources/Converter/PlayerDisplayModeConverter.cs @@ -1,12 +1,12 @@ // Copyright (c) Richasy. All rights reserved. using System; -using Richasy.Bili.Locator.Uwp; -using Richasy.Bili.Models.Enums; -using Richasy.Bili.Toolkit.Interfaces; +using Bili.DI.Container; +using Bili.Models.Enums; +using Bili.Toolkit.Interfaces; using Windows.UI.Xaml.Data; -namespace Richasy.Bili.App.Resources.Converter +namespace Bili.App.Resources.Converter { /// /// 播放器显示模式到可读文本的转换器. @@ -17,7 +17,7 @@ public class PlayerDisplayModeConverter : IValueConverter public object Convert(object value, Type targetType, object parameter, string language) { var result = string.Empty; - var resourceToolkit = ServiceLocator.Instance.GetService(); + var resourceToolkit = Locator.Instance.GetService(); if (value is PlayerDisplayMode mode) { switch (mode) diff --git a/src/App/Resources/Converter/PlayerTypeConverter.cs b/src/App/Resources/Converter/PlayerTypeConverter.cs new file mode 100644 index 000000000..7c6de8e73 --- /dev/null +++ b/src/App/Resources/Converter/PlayerTypeConverter.cs @@ -0,0 +1,40 @@ +// Copyright (c) Richasy. All rights reserved. + +using System; +using Bili.DI.Container; +using Bili.Models.Enums; +using Bili.Models.Enums.Player; +using Bili.Toolkit.Interfaces; +using Windows.UI.Xaml.Data; + +namespace Bili.App.Resources.Converter +{ + internal sealed class PlayerTypeConverter : IValueConverter + { + /// + public object Convert(object value, Type targetType, object parameter, string language) + { + var result = string.Empty; + var resourceToolkit = Locator.Instance.GetService(); + if (value is PlayerType decode) + { + switch (decode) + { + case PlayerType.Native: + result = resourceToolkit.GetLocaleString(LanguageNames.NativePlayer); + break; + case PlayerType.FFmpeg: + result = resourceToolkit.GetLocaleString(LanguageNames.FFmpegPlayer); + break; + default: + break; + } + } + + return result; + } + + /// + public object ConvertBack(object value, Type targetType, object parameter, string language) => throw new NotImplementedException(); + } +} diff --git a/src/App/Resources/Converter/PreferCodecConverter.cs b/src/App/Resources/Converter/PreferCodecConverter.cs index c51e7768d..78324adeb 100644 --- a/src/App/Resources/Converter/PreferCodecConverter.cs +++ b/src/App/Resources/Converter/PreferCodecConverter.cs @@ -1,12 +1,12 @@ // Copyright (c) Richasy. All rights reserved. using System; -using Richasy.Bili.Locator.Uwp; -using Richasy.Bili.Models.Enums; -using Richasy.Bili.Toolkit.Interfaces; +using Bili.DI.Container; +using Bili.Models.Enums; +using Bili.Toolkit.Interfaces; using Windows.UI.Xaml.Data; -namespace Richasy.Bili.App.Resources.Converter +namespace Bili.App.Resources.Converter { /// /// 偏好解码模式到可读文本转换器. @@ -17,7 +17,7 @@ public class PreferCodecConverter : IValueConverter public object Convert(object value, Type targetType, object parameter, string language) { var result = string.Empty; - var resourceToolkit = ServiceLocator.Instance.GetService(); + var resourceToolkit = Locator.Instance.GetService(); if (value is PreferCodec codec) { switch (codec) @@ -28,6 +28,9 @@ public object Convert(object value, Type targetType, object parameter, string la case PreferCodec.H264: result = resourceToolkit.GetLocaleString(LanguageNames.H264); break; + case PreferCodec.Av1: + result = resourceToolkit.GetLocaleString(LanguageNames.Av1); + break; default: break; } diff --git a/src/App/Resources/Converter/PreferQualityConverter.cs b/src/App/Resources/Converter/PreferQualityConverter.cs new file mode 100644 index 000000000..ab1d3fa05 --- /dev/null +++ b/src/App/Resources/Converter/PreferQualityConverter.cs @@ -0,0 +1,34 @@ +// Copyright (c) Richasy. All rights reserved. + +using System; +using Bili.DI.Container; +using Bili.Models.Enums.Player; +using Bili.Toolkit.Interfaces; +using Windows.UI.Xaml.Data; + +namespace Bili.App.Resources.Converter +{ + /// + /// 偏好画质的可读文本转换器. + /// + internal sealed class PreferQualityConverter : IValueConverter + { + public object Convert(object value, Type targetType, object parameter, string language) + { + if (value is PreferQuality quality) + { + var resToolkit = Locator.Instance.GetService(); + return quality switch + { + PreferQuality.HDFirst => resToolkit.GetLocaleString(Models.Enums.LanguageNames.HDFirst), + PreferQuality.HighQuality => resToolkit.GetLocaleString(Models.Enums.LanguageNames.PreferHighQuality), + _ => resToolkit.GetLocaleString(Models.Enums.LanguageNames.Automatic), + }; + } + + return string.Empty; + } + + public object ConvertBack(object value, Type targetType, object parameter, string language) => throw new NotImplementedException(); + } +} diff --git a/src/App/Resources/Converter/RelationButtonStyleConverter.cs b/src/App/Resources/Converter/RelationButtonStyleConverter.cs new file mode 100644 index 000000000..8b87b1d2a --- /dev/null +++ b/src/App/Resources/Converter/RelationButtonStyleConverter.cs @@ -0,0 +1,34 @@ +// Copyright (c) Richasy. All rights reserved. + +using System; +using Bili.DI.Container; +using Bili.Models.Enums.Community; +using Bili.Toolkit.Interfaces; +using Windows.UI.Xaml; +using Windows.UI.Xaml.Data; + +namespace Bili.App.Resources.Converter +{ + /// + /// 关系按钮样式转换器. + /// + public sealed class RelationButtonStyleConverter : IValueConverter + { + /// + public object Convert(object value, Type targetType, object parameter, string language) + { + if (value is UserRelationStatus status) + { + var resourceToolkit = Locator.Instance.GetService(); + return status == UserRelationStatus.Unfollow || status == UserRelationStatus.BeFollowed + ? resourceToolkit.GetResource - - - - + + + diff --git a/src/Workspace/Controls/App/CardPanel/CardPanelAutomationPeer.cs b/src/Workspace/Controls/App/CardPanel/CardPanelAutomationPeer.cs new file mode 100644 index 000000000..76247f048 --- /dev/null +++ b/src/Workspace/Controls/App/CardPanel/CardPanelAutomationPeer.cs @@ -0,0 +1,57 @@ +// Copyright (c) Richasy. All rights reserved. + +using Microsoft.UI.Xaml; +using Microsoft.UI.Xaml.Automation.Peers; +using Microsoft.UI.Xaml.Controls; + +namespace Bili.Workspace.Controls +{ + /// + /// 的自动化公开属性. + /// + public class CardPanelAutomationPeer : ToggleButtonAutomationPeer + { + private readonly CardPanel _owner; + + /// + /// Initializes a new instance of the class. + /// + /// 对象. + public CardPanelAutomationPeer(CardPanel owner) + : base(owner) => _owner = owner; + + /// + protected override AutomationControlType GetAutomationControlTypeCore() + { + return AutomationControlType.ListItem; + } + + /// + protected override int GetPositionInSetCore() + { + var element = _owner as FrameworkElement; + var parent = _owner.Parent; + if (!(parent is ItemsRepeater) && parent != null) + { + parent = (parent as FrameworkElement).Parent; + element = _owner.Parent as FrameworkElement; + } + + return (parent as ItemsRepeater)?.GetElementIndex(element) + 1 + ?? base.GetPositionInSetCore(); + } + + /// + protected override int GetSizeOfSetCore() + { + var parent = _owner.Parent; + if (!(parent is ItemsRepeater) && parent != null) + { + parent = (parent as FrameworkElement).Parent; + } + + var count = (parent as ItemsRepeater)?.ItemsSourceView?.Count; + return count ?? base.GetSizeOfSetCore(); + } + } +} diff --git a/src/Workspace/Controls/App/CardPanel/CardPanelStateChangedEventArgs.cs b/src/Workspace/Controls/App/CardPanel/CardPanelStateChangedEventArgs.cs new file mode 100644 index 000000000..d81b6c50c --- /dev/null +++ b/src/Workspace/Controls/App/CardPanel/CardPanelStateChangedEventArgs.cs @@ -0,0 +1,33 @@ +// Copyright (c) Richasy. All rights reserved. + +using System; + +namespace Bili.Workspace.Controls +{ + /// + /// 卡片面板状态更改事件参数. + /// + public class CardPanelStateChangedEventArgs : EventArgs + { + /// + /// Initializes a new instance of the class. + /// + /// 是否处于PointerOver状态. + /// 是否处于按压状态. + public CardPanelStateChangedEventArgs(bool isPointerOver, bool isPressed) + { + IsPointerOver = isPointerOver; + IsPressed = isPressed; + } + + /// + /// 是否处于PointerOver状态. + /// + public bool IsPointerOver { get; } + + /// + /// 是否处于按压状态. + /// + public bool IsPressed { get; } + } +} diff --git a/src/Workspace/Controls/App/CommonImageEx/CommonImageEx.cs b/src/Workspace/Controls/App/CommonImageEx/CommonImageEx.cs new file mode 100644 index 000000000..494429dac --- /dev/null +++ b/src/Workspace/Controls/App/CommonImageEx/CommonImageEx.cs @@ -0,0 +1,82 @@ +// Copyright (c) Richasy. All rights reserved. + +using Microsoft.UI.Xaml; +using Microsoft.UI.Xaml.Controls; +using Microsoft.UI.Xaml.Media; + +namespace Bili.Workspace.Controls.App +{ + /// + /// 通用图片扩展. + /// + public sealed class CommonImageEx : Control + { + /// + /// 的依赖属性. + /// + public static readonly DependencyProperty ImageUrlProperty = + DependencyProperty.Register(nameof(ImageUrl), typeof(string), typeof(CommonImageEx), new PropertyMetadata(null)); + + /// + /// 的依赖属性. + /// + public static readonly DependencyProperty StretchProperty = + DependencyProperty.Register(nameof(Stretch), typeof(Stretch), typeof(CommonImageEx), new PropertyMetadata(Stretch.UniformToFill)); + + /// + /// 的依赖属性. + /// + public static readonly DependencyProperty DecodePixelWidthProperty = + DependencyProperty.Register(nameof(DecodePixelWidth), typeof(double), typeof(CommonImageEx), new PropertyMetadata(default)); + + /// + /// 的依赖属性. + /// + public static readonly DependencyProperty PlaceholderSourceProperty = + DependencyProperty.Register(nameof(PlaceholderSource), typeof(ImageSource), typeof(CommonImageEx), new PropertyMetadata(default)); + + /// + /// Initializes a new instance of the class. + /// + public CommonImageEx() + { + DefaultStyleKey = typeof(CommonImageEx); + } + + /// + /// 图片地址. + /// + public string ImageUrl + { + get { return (string)GetValue(ImageUrlProperty); } + set { SetValue(ImageUrlProperty, value); } + } + + /// + /// 图片拉伸. + /// + public Stretch Stretch + { + get { return (Stretch)GetValue(StretchProperty); } + set { SetValue(StretchProperty, value); } + } + + /// + /// 横向解码宽度. + /// + public int DecodePixelWidth + { + get { return (int)GetValue(DecodePixelWidthProperty); } + set { SetValue(DecodePixelWidthProperty, value); } + } + + /// + /// 占位符图片. + /// + public ImageSource PlaceholderSource + { + get { return (ImageSource)GetValue(PlaceholderSourceProperty); } + set { SetValue(PlaceholderSourceProperty, value); } + } + } +} diff --git a/src/Workspace/Controls/App/CommonImageEx/CommonImageEx.xaml b/src/Workspace/Controls/App/CommonImageEx/CommonImageEx.xaml new file mode 100644 index 000000000..8c16e17dd --- /dev/null +++ b/src/Workspace/Controls/App/CommonImageEx/CommonImageEx.xaml @@ -0,0 +1,24 @@ + + + diff --git a/src/Workspace/Controls/App/EmoteTextBlock/EmoteTextBlock.cs b/src/Workspace/Controls/App/EmoteTextBlock/EmoteTextBlock.cs new file mode 100644 index 000000000..28f5d948d --- /dev/null +++ b/src/Workspace/Controls/App/EmoteTextBlock/EmoteTextBlock.cs @@ -0,0 +1,182 @@ +// Copyright (c) Richasy. All rights reserved. + +using System; +using System.Linq; +using System.Text.RegularExpressions; +using Bili.Models.Data.Appearance; +using Microsoft.UI.Xaml; +using Microsoft.UI.Xaml.Controls; +using Microsoft.UI.Xaml.Documents; +using Microsoft.UI.Xaml.Media.Imaging; + +namespace Bili.Workspace.Controls.App +{ + /// + /// 带表情的文本. + /// + public sealed class EmoteTextBlock : Control + { + /// + /// 的依赖属性. + /// + public static readonly DependencyProperty MaxLinesProperty = + DependencyProperty.Register(nameof(MaxLines), typeof(int), typeof(EmoteTextBlock), new PropertyMetadata(4)); + + /// + /// 的依赖属性. + /// + public static readonly DependencyProperty TextProperty = + DependencyProperty.Register(nameof(Text), typeof(EmoteText), typeof(EmoteTextBlock), new PropertyMetadata(default, new PropertyChangedCallback(OnTextChanged))); + + private RichTextBlock _richBlock; + private RichTextBlock _flyoutRichBlock; + private Button _overflowButton; + private bool _isOverflowInitialized; + + /// + /// Initializes a new instance of the class. + /// + public EmoteTextBlock() => DefaultStyleKey = typeof(EmoteTextBlock); + + /// + /// 最大行数. + /// + public int MaxLines + { + get { return (int)GetValue(MaxLinesProperty); } + set { SetValue(MaxLinesProperty, value); } + } + + /// + /// 输入的文本. + /// + public EmoteText Text + { + get { return (EmoteText)GetValue(TextProperty); } + set { SetValue(TextProperty, value); } + } + + /// + /// 重置状态. + /// + public void Reset() => _richBlock?.Blocks?.Clear(); + + /// + protected override void OnApplyTemplate() + { + _richBlock = GetTemplateChild("RichBlock") as RichTextBlock; + _flyoutRichBlock = GetTemplateChild("FlyoutRichBlock") as RichTextBlock; + _overflowButton = GetTemplateChild("OverflowButton") as Button; + + _richBlock.IsTextTrimmedChanged += OnIsTextTrimmedChanged; + _overflowButton.Click += OnOverflowButtonClick; + + InitializeContent(); + base.OnApplyTemplate(); + } + + private static void OnReplyInfoChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) + { + var instance = d as EmoteTextBlock; + instance.Text = null; + if (e.NewValue != null) + { + instance.InitializeContent(); + } + } + + private static void OnTextChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) + { + var instance = d as EmoteTextBlock; + if (e.NewValue != null) + { + instance.InitializeContent(); + } + } + + private void OnIsTextTrimmedChanged(RichTextBlock sender, IsTextTrimmedChangedEventArgs args) + => _overflowButton.Visibility = sender.IsTextTrimmed ? Visibility.Visible : Visibility.Collapsed; + + private void InitializeContent() + { + _isOverflowInitialized = false; + if (_richBlock != null) + { + _richBlock.Blocks.Clear(); + Paragraph para = null; + if (Text != null) + { + para = ParseText(); + } + + if (para != null) + { + _richBlock.Blocks.Add(para); + } + } + + if (_overflowButton != null && _richBlock != null) + { + _overflowButton.Visibility = _richBlock.IsTextTrimmed ? Visibility.Visible : Visibility.Collapsed; + } + } + + private void OnOverflowButtonClick(object sender, RoutedEventArgs e) + { + if (!_isOverflowInitialized) + { + _flyoutRichBlock.Blocks.Clear(); + + if (Text != null) + { + var para = ParseText(); + _flyoutRichBlock.Blocks.Add(para); + } + } + } + + private Paragraph ParseText() + { + var text = Text.Text; + var emotes = Text.Emotes; + var para = new Paragraph(); + + if (emotes != null && emotes.Count > 0) + { + // 有表情存在,进行处理. + var emotiRegex = new Regex(@"(\[.*?\])"); + var splitCotents = emotiRegex.Split(text).Where(p => p.Length > 0).ToArray(); + foreach (var content in splitCotents) + { + if (emotiRegex.IsMatch(content)) + { + emotes.TryGetValue(content, out var emoji); + if (emoji != null) + { + var inlineCon = new InlineUIContainer(); + var img = new Microsoft.UI.Xaml.Controls.Image() { Width = 20, VerticalAlignment = VerticalAlignment.Center, Margin = new Thickness(2, 0, 2, -4) }; + var bitmap = new BitmapImage(new Uri(emoji.Uri)) { DecodePixelWidth = 40 }; + img.Source = bitmap; + inlineCon.Child = img; + para.Inlines.Add(inlineCon); + } + else + { + para.Inlines.Add(new Run { Text = content }); + } + } + else + { + para.Inlines.Add(new Run { Text = content }); + } + } + } + else + { + para.Inlines.Add(new Run { Text = text }); + } + + return para; + } + } +} diff --git a/src/Workspace/Controls/App/EmoteTextBlock/EmoteTextBlock.xaml b/src/Workspace/Controls/App/EmoteTextBlock/EmoteTextBlock.xaml new file mode 100644 index 000000000..4f6dd167c --- /dev/null +++ b/src/Workspace/Controls/App/EmoteTextBlock/EmoteTextBlock.xaml @@ -0,0 +1,49 @@ + + + diff --git a/src/Workspace/Controls/App/EpisodeItem/EpisodeItem.cs b/src/Workspace/Controls/App/EpisodeItem/EpisodeItem.cs new file mode 100644 index 000000000..58d2eee20 --- /dev/null +++ b/src/Workspace/Controls/App/EpisodeItem/EpisodeItem.cs @@ -0,0 +1,18 @@ +// Copyright (c) Richasy. All rights reserved. + +using Bili.ViewModels.Interfaces.Pgc; + +namespace Bili.Workspace.Controls.App +{ + /// + /// 剧集单集条目视图. + /// + public sealed class EpisodeItem : ReactiveControl + { + /// + /// Initializes a new instance of the class. + /// + public EpisodeItem() + => DefaultStyleKey = typeof(EpisodeItem); + } +} diff --git a/src/Workspace/Controls/App/EpisodeItem/EpisodeItem.xaml b/src/Workspace/Controls/App/EpisodeItem/EpisodeItem.xaml new file mode 100644 index 000000000..a8a210742 --- /dev/null +++ b/src/Workspace/Controls/App/EpisodeItem/EpisodeItem.xaml @@ -0,0 +1,205 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/Workspace/Controls/App/LandingPane.xaml b/src/Workspace/Controls/App/LandingPane.xaml new file mode 100644 index 000000000..d6857c9d1 --- /dev/null +++ b/src/Workspace/Controls/App/LandingPane.xaml @@ -0,0 +1,46 @@ + + + + + + + + + + + + diff --git a/src/Workspace/Controls/App/PageHeader.xaml.cs b/src/Workspace/Controls/App/PageHeader.xaml.cs new file mode 100644 index 000000000..1b105edab --- /dev/null +++ b/src/Workspace/Controls/App/PageHeader.xaml.cs @@ -0,0 +1,84 @@ +// Copyright (c) Richasy. All rights reserved. + +using System.Windows.Input; +using Bili.DI.Container; +using Bili.ViewModels.Interfaces.Core; +using Microsoft.UI.Xaml; +using Microsoft.UI.Xaml.Controls; + +namespace Bili.Workspace.Controls.App +{ + /// + /// 页面头部. + /// + public sealed partial class PageHeader : UserControl + { + /// + /// 的依赖属性. + /// + public static readonly DependencyProperty ElementProperty = + DependencyProperty.Register(nameof(Element), typeof(object), typeof(PageHeader), new PropertyMetadata(default)); + + /// + /// 的依赖属性. + /// + public static readonly DependencyProperty TitleProperty = + DependencyProperty.Register(nameof(Title), typeof(object), typeof(PageHeader), new PropertyMetadata(default)); + + /// + /// 的依赖属性. + /// + public static readonly DependencyProperty RefreshCommandProperty = + DependencyProperty.Register(nameof(RefreshCommand), typeof(ICommand), typeof(PageHeader), new PropertyMetadata(default)); + + /// + /// 的依赖属性. + /// + public static readonly DependencyProperty IsShowLogoProperty = + DependencyProperty.Register(nameof(IsShowLogo), typeof(bool), typeof(PageHeader), new PropertyMetadata(default)); + + /// + /// Initializes a new instance of the class. + /// + public PageHeader() + { + InitializeComponent(); + } + + /// + /// 标题. + /// + public object Title + { + get { return (object)GetValue(TitleProperty); } + set { SetValue(TitleProperty, value); } + } + + /// + /// 右侧的控制元素. + /// + public object Element + { + get { return (object)GetValue(ElementProperty); } + set { SetValue(ElementProperty, value); } + } + + /// + /// 刷新命令. + /// + public ICommand RefreshCommand + { + get { return (ICommand)GetValue(RefreshCommandProperty); } + set { SetValue(RefreshCommandProperty, value); } + } + + /// + /// 是否显示应用图标. + /// + public bool IsShowLogo + { + get { return (bool)GetValue(IsShowLogoProperty); } + set { SetValue(IsShowLogoProperty, value); } + } + } +} diff --git a/src/Workspace/Controls/App/TipPopup.xaml b/src/Workspace/Controls/App/TipPopup.xaml new file mode 100644 index 000000000..13705e010 --- /dev/null +++ b/src/Workspace/Controls/App/TipPopup.xaml @@ -0,0 +1,84 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/Workspace/Controls/App/TipPopup.xaml.cs b/src/Workspace/Controls/App/TipPopup.xaml.cs new file mode 100644 index 000000000..b33021fd3 --- /dev/null +++ b/src/Workspace/Controls/App/TipPopup.xaml.cs @@ -0,0 +1,69 @@ +// Copyright (c) Richasy. All rights reserved. + +using Bili.Models.Enums.App; +using Microsoft.UI.Xaml; +using Microsoft.UI.Xaml.Controls; + +namespace Bili.Workspace.Controls.App +{ + /// + /// 消息提醒. + /// + public sealed partial class TipPopup : UserControl + { + /// + /// 的依赖属性. + /// + public static readonly DependencyProperty TextProperty = + DependencyProperty.Register(nameof(Text), typeof(string), typeof(TipPopup), new PropertyMetadata(string.Empty)); + + /// + /// Initializes a new instance of the class. + /// + public TipPopup() => InitializeComponent(); + + /// + /// Initializes a new instance of the class. + /// + /// 要显示的文本. + public TipPopup(string text) + : this() => Text = text; + + /// + /// 显示文本. + /// + public string Text + { + get => (string)GetValue(TextProperty); + set => SetValue(TextProperty, value); + } + + /// + /// 显示内容. + /// + /// 信息级别. + /// 显示的时间. + public async void ShowAsync(InfoType type = InfoType.Information, double displaySeconds = 2) + { + switch (type) + { + case InfoType.Information: + InformationIcon.Visibility = Visibility.Visible; + break; + case InfoType.Success: + SuccessIcon.Visibility = Visibility.Visible; + break; + case InfoType.Warning: + WarningIcon.Visibility = Visibility.Visible; + break; + case InfoType.Error: + ErrorIcon.Visibility = Visibility.Visible; + break; + default: + break; + } + + await MainWindow.Instance.ShowTipAsync(this, displaySeconds); + } + } +} diff --git a/src/Workspace/Controls/App/TwoLineButton/TwoLineButton.cs b/src/Workspace/Controls/App/TwoLineButton/TwoLineButton.cs new file mode 100644 index 000000000..d42243e35 --- /dev/null +++ b/src/Workspace/Controls/App/TwoLineButton/TwoLineButton.cs @@ -0,0 +1,51 @@ +// Copyright (c) Richasy. All rights reserved. + +using Microsoft.UI.Xaml; +using Microsoft.UI.Xaml.Controls; + +namespace Bili.Workspace.Controls.App +{ + /// + /// 包含双行文本的按钮. + /// + public sealed class TwoLineButton : Button + { + /// + /// 的依赖属性. + /// + public static readonly DependencyProperty FirstLineTextProperty = + DependencyProperty.Register(nameof(FirstLineText), typeof(string), typeof(TwoLineButton), new PropertyMetadata(string.Empty)); + + /// + /// 的依赖属性. + /// + public static readonly DependencyProperty SecondLineTextProperty = + DependencyProperty.Register(nameof(SecondLineText), typeof(string), typeof(TwoLineButton), new PropertyMetadata(string.Empty)); + + /// + /// Initializes a new instance of the class. + /// + public TwoLineButton() + { + DefaultStyleKey = typeof(TwoLineButton); + } + + /// + /// 首行文本. + /// + public string FirstLineText + { + get { return (string)GetValue(FirstLineTextProperty); } + set { SetValue(FirstLineTextProperty, value); } + } + + /// + /// 次行文本. + /// + public string SecondLineText + { + get { return (string)GetValue(SecondLineTextProperty); } + set { SetValue(SecondLineTextProperty, value); } + } + } +} diff --git a/src/Workspace/Controls/App/TwoLineButton/TwoLineButton.xaml b/src/Workspace/Controls/App/TwoLineButton/TwoLineButton.xaml new file mode 100644 index 000000000..94418aebc --- /dev/null +++ b/src/Workspace/Controls/App/TwoLineButton/TwoLineButton.xaml @@ -0,0 +1,126 @@ + + + + diff --git a/src/Workspace/Controls/App/VerticalRepeaterView/VerticalRepeaterView.Properties.cs b/src/Workspace/Controls/App/VerticalRepeaterView/VerticalRepeaterView.Properties.cs new file mode 100644 index 000000000..d4e6fd73c --- /dev/null +++ b/src/Workspace/Controls/App/VerticalRepeaterView/VerticalRepeaterView.Properties.cs @@ -0,0 +1,168 @@ +// Copyright (c) Richasy. All rights reserved. + +using System; +using Microsoft.UI.Xaml; + +namespace Bili.Workspace.Controls.App +{ + /// + /// 视频视图. + /// + public sealed partial class VerticalRepeaterView + { + /// + /// 的依赖属性. + /// + public static readonly DependencyProperty ItemsSourceProperty = + DependencyProperty.Register(nameof(ItemsSource), typeof(object), typeof(VerticalRepeaterView), new PropertyMetadata(null)); + + /// + /// 的依赖属性. + /// + public static readonly DependencyProperty HeaderTextProperty = + DependencyProperty.Register(nameof(HeaderText), typeof(string), typeof(VerticalRepeaterView), new PropertyMetadata(string.Empty)); + + /// + /// 的依赖属性. + /// + public static readonly DependencyProperty ItemTemplateProperty = + DependencyProperty.Register(nameof(ItemTemplate), typeof(object), typeof(VerticalRepeaterView), new PropertyMetadata(null)); + + /// + /// 的依赖属性. + /// + public static readonly DependencyProperty AdditionalContentProperty = + DependencyProperty.Register(nameof(AdditionalContent), typeof(object), typeof(VerticalRepeaterView), new PropertyMetadata(null)); + + /// + /// 的依赖属性. + /// + public static readonly DependencyProperty IsAutoFillEnableProperty = + DependencyProperty.Register(nameof(IsAutoFillEnable), typeof(bool), typeof(VerticalRepeaterView), new PropertyMetadata(true)); + + /// + /// 的依赖属性. + /// + public static readonly DependencyProperty HeaderVisibilityProperty = + DependencyProperty.Register(nameof(HeaderVisibility), typeof(Visibility), typeof(VerticalRepeaterView), new PropertyMetadata(Visibility.Visible)); + + /// + /// 的依赖属性. + /// + public static readonly DependencyProperty MinWideItemHeightProperty = + DependencyProperty.Register(nameof(MinWideItemHeight), typeof(double), typeof(VerticalRepeaterView), new PropertyMetadata(232d)); + + /// + /// 的依赖属性. + /// + public static readonly DependencyProperty MinWideItemWidthProperty = + DependencyProperty.Register(nameof(MinWideItemWidth), typeof(double), typeof(VerticalRepeaterView), new PropertyMetadata(222d)); + + /// + /// 的依赖属性. + /// + public static readonly DependencyProperty EnableDetectParentScrollViewerProperty = + DependencyProperty.Register(nameof(EnableDetectParentScrollViewer), typeof(bool), typeof(VerticalRepeaterView), new PropertyMetadata(true)); + + /// + /// 的依赖属性. + /// + public static readonly DependencyProperty VerticalCacheSizeProperty = + DependencyProperty.Register(nameof(VerticalCacheSize), typeof(int), typeof(VerticalRepeaterView), new PropertyMetadata(10)); + + /// + /// 在外部的ScrollViewer滚动到接近底部时发生. + /// + public event EventHandler RequestLoadMore; + + /// + /// 条目模板. + /// + public object ItemTemplate + { + get { return (DataTemplate)GetValue(ItemTemplateProperty); } + set { SetValue(ItemTemplateProperty, value); } + } + + /// + /// 数据源. + /// + public object ItemsSource + { + get { return (object)GetValue(ItemsSourceProperty); } + set { SetValue(ItemsSourceProperty, value); } + } + + /// + /// 标题文本. + /// + public string HeaderText + { + get { return (string)GetValue(HeaderTextProperty); } + set { SetValue(HeaderTextProperty, value); } + } + + /// + /// 附加视觉元素,在顶部右上角. + /// + public object AdditionalContent + { + get { return (object)GetValue(AdditionalContentProperty); } + set { SetValue(AdditionalContentProperty, value); } + } + + /// + /// 是否允许根据容器剩余空间自行计算视频条目容量,并主动发起请求填满整个容器. + /// + public bool IsAutoFillEnable + { + get { return (bool)GetValue(IsAutoFillEnableProperty); } + set { SetValue(IsAutoFillEnableProperty, value); } + } + + /// + /// 标题的可见性. + /// + public Visibility HeaderVisibility + { + get { return (Visibility)GetValue(HeaderVisibilityProperty); } + set { SetValue(HeaderVisibilityProperty, value); } + } + + /// + /// 在网格布局下,单个条目的最小宽度. + /// + public double MinWideItemWidth + { + get { return (double)GetValue(MinWideItemWidthProperty); } + set { SetValue(MinWideItemWidthProperty, value); } + } + + /// + /// 在网格布局下,单个条目的最小高度. + /// + public double MinWideItemHeight + { + get { return (double)GetValue(MinWideItemHeightProperty); } + set { SetValue(MinWideItemHeightProperty, value); } + } + + /// + /// 是否启用自动检测父滚动视图. + /// + public bool EnableDetectParentScrollViewer + { + get { return (bool)GetValue(EnableDetectParentScrollViewerProperty); } + set { SetValue(EnableDetectParentScrollViewerProperty, value); } + } + + /// + /// 垂直方向缓存大小. + /// + public int VerticalCacheSize + { + get { return (int)GetValue(VerticalCacheSizeProperty); } + set { SetValue(VerticalCacheSizeProperty, value); } + } + } +} diff --git a/src/Workspace/Controls/App/VerticalRepeaterView/VerticalRepeaterView.cs b/src/Workspace/Controls/App/VerticalRepeaterView/VerticalRepeaterView.cs new file mode 100644 index 000000000..dbd1c5bc7 --- /dev/null +++ b/src/Workspace/Controls/App/VerticalRepeaterView/VerticalRepeaterView.cs @@ -0,0 +1,62 @@ +// Copyright (c) Richasy. All rights reserved. + +using System; +using Bili.Workspace.Resources.Extension; +using Microsoft.UI.Xaml; +using Microsoft.UI.Xaml.Controls; + +namespace Bili.Workspace.Controls.App +{ + /// + /// 视频视图. + /// + public sealed partial class VerticalRepeaterView : Control + { + private readonly double _itemHolderHeight = 24d; + private ScrollViewer _parentScrollViewer; + + /// + /// Initializes a new instance of the class. + /// + public VerticalRepeaterView() + { + DefaultStyleKey = typeof(VerticalRepeaterView); + Loaded += OnLoaded; + Unloaded += OnUnloaded; + } + + private void OnLoaded(object sender, RoutedEventArgs e) + { + if (EnableDetectParentScrollViewer) + { + _parentScrollViewer = this.FindAscendantElementByType(); + if (_parentScrollViewer != null) + { + _parentScrollViewer.ViewChanged += OnParentScrollViewerViewChanged; + } + } + } + + private void OnUnloaded(object sender, RoutedEventArgs e) + { + if (_parentScrollViewer != null) + { + _parentScrollViewer.ViewChanged -= OnParentScrollViewerViewChanged; + _parentScrollViewer = null; + } + } + + private void OnParentScrollViewerViewChanged(object sender, ScrollViewerViewChangedEventArgs e) + { + if (!e.IsIntermediate && _parentScrollViewer != null) + { + var currentPosition = _parentScrollViewer.VerticalOffset; + if (_parentScrollViewer.ScrollableHeight - currentPosition <= _itemHolderHeight && + Visibility == Visibility.Visible) + { + RequestLoadMore?.Invoke(this, EventArgs.Empty); + } + } + } + } +} diff --git a/src/Workspace/Controls/App/VerticalRepeaterView/VerticalRepeaterView.xaml b/src/Workspace/Controls/App/VerticalRepeaterView/VerticalRepeaterView.xaml new file mode 100644 index 000000000..66f8d9cb8 --- /dev/null +++ b/src/Workspace/Controls/App/VerticalRepeaterView/VerticalRepeaterView.xaml @@ -0,0 +1,55 @@ + + + + diff --git a/src/Workspace/Controls/App/VideoItem/VideoItem.cs b/src/Workspace/Controls/App/VideoItem/VideoItem.cs new file mode 100644 index 000000000..f8ad8ad6e --- /dev/null +++ b/src/Workspace/Controls/App/VideoItem/VideoItem.cs @@ -0,0 +1,17 @@ +// Copyright (c) Richasy. All rights reserved. + +using Bili.ViewModels.Interfaces.Video; + +namespace Bili.Workspace.Controls.App +{ + /// + /// 用来显示视频条目的 UI 单元,可以通过样式展现不同的布局. + /// + public sealed partial class VideoItem : ReactiveControl + { + /// + /// Initializes a new instance of the class. + /// + public VideoItem() => DefaultStyleKey = typeof(VideoItem); + } +} diff --git a/src/Workspace/Controls/App/VideoItem/VideoItem.xaml b/src/Workspace/Controls/App/VideoItem/VideoItem.xaml new file mode 100644 index 000000000..b27c6f855 --- /dev/null +++ b/src/Workspace/Controls/App/VideoItem/VideoItem.xaml @@ -0,0 +1,282 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/Workspace/Controls/Community/SearchSuggestBox.xaml b/src/Workspace/Controls/Community/SearchSuggestBox.xaml new file mode 100644 index 000000000..e9841078d --- /dev/null +++ b/src/Workspace/Controls/Community/SearchSuggestBox.xaml @@ -0,0 +1,35 @@ + + + + + + + + + + + + diff --git a/src/Workspace/Controls/Community/SearchSuggestBox.xaml.cs b/src/Workspace/Controls/Community/SearchSuggestBox.xaml.cs new file mode 100644 index 000000000..bbe68985e --- /dev/null +++ b/src/Workspace/Controls/Community/SearchSuggestBox.xaml.cs @@ -0,0 +1,48 @@ +// Copyright (c) Richasy. All rights reserved. + +using Bili.Models.Data.Search; +using Bili.ViewModels.Interfaces.Search; +using Microsoft.UI.Xaml.Controls; + +namespace Bili.Workspace.Controls.Community +{ + /// + /// 搜索框. + /// + public sealed partial class SearchSuggestBox : SearchSuggestBoxBase + { + /// + /// Initializes a new instance of the class. + /// + public SearchSuggestBox() + { + InitializeComponent(); + } + + private void OnSearchBoxSubmitted(AutoSuggestBox sender, AutoSuggestBoxQuerySubmittedEventArgs args) + { + if (args.ChosenSuggestion is SearchSuggest) + { + SelectSuggestItem(args.ChosenSuggestion); + } + + if (!string.IsNullOrEmpty(sender.Text)) + { + ViewModel.SearchCommand.Execute(sender.Text); + } + } + + private void SelectSuggestItem(object suggestObj) + { + var data = suggestObj as SearchSuggest; + ViewModel.SelectSuggestCommand.Execute(data); + } + } + + /// + /// 的基类. + /// + public class SearchSuggestBoxBase : ReactiveUserControl + { + } +} diff --git a/src/Workspace/Controls/Community/UserAvatar.xaml b/src/Workspace/Controls/Community/UserAvatar.xaml new file mode 100644 index 000000000..bd4770080 --- /dev/null +++ b/src/Workspace/Controls/Community/UserAvatar.xaml @@ -0,0 +1,39 @@ + + + + diff --git a/src/Workspace/Controls/Community/UserAvatar.xaml.cs b/src/Workspace/Controls/Community/UserAvatar.xaml.cs new file mode 100644 index 000000000..e6505ff83 --- /dev/null +++ b/src/Workspace/Controls/Community/UserAvatar.xaml.cs @@ -0,0 +1,106 @@ +// Copyright (c) Richasy. All rights reserved. + +using System; +using System.Windows.Input; +using Microsoft.UI.Xaml; +using Microsoft.UI.Xaml.Controls; + +namespace Bili.Workspace.Controls.Community +{ + /// + /// 用户头像. + /// + public sealed partial class UserAvatar : UserControl + { + /// + /// 的依赖属性. + /// + public static readonly DependencyProperty CommandProperty = + DependencyProperty.Register(nameof(Command), typeof(ICommand), typeof(UserAvatar), new PropertyMetadata(default)); + + /// + /// 的依赖属性. + /// + public static readonly DependencyProperty UserNameProperty = + DependencyProperty.Register(nameof(UserName), typeof(string), typeof(UserAvatar), new PropertyMetadata(string.Empty)); + + /// + /// 的依赖属性. + /// + public static readonly DependencyProperty AvatarProperty = + DependencyProperty.Register(nameof(Avatar), typeof(string), typeof(UserAvatar), new PropertyMetadata(string.Empty)); + + /// + /// 的依赖属性. + /// + public static readonly DependencyProperty DecodeSizeProperty = + DependencyProperty.Register(nameof(DecodeSize), typeof(int), typeof(UserAvatar), new PropertyMetadata(50)); + + /// + /// 的依赖属性. + /// + public static readonly DependencyProperty AvatarRadiusProperty = + DependencyProperty.Register(nameof(AvatarRadius), typeof(CornerRadius), typeof(UserAvatar), new PropertyMetadata(new CornerRadius(25))); + + /// + /// Initializes a new instance of the class. + /// + public UserAvatar() + { + InitializeComponent(); + } + + /// + /// 点击时触发. + /// + public event EventHandler Click; + + /// + /// 用户名. + /// + public string UserName + { + get { return (string)GetValue(UserNameProperty); } + set { SetValue(UserNameProperty, value); } + } + + /// + /// 头像. + /// + public string Avatar + { + get { return (string)GetValue(AvatarProperty); } + set { SetValue(AvatarProperty, value); } + } + + /// + /// 解析图像的大小. + /// + public int DecodeSize + { + get { return (int)GetValue(DecodeSizeProperty); } + set { SetValue(DecodeSizeProperty, value); } + } + + /// + /// 命令. + /// + public ICommand Command + { + get { return (ICommand)GetValue(CommandProperty); } + set { SetValue(CommandProperty, value); } + } + + /// + /// 头像的圆角弧度. + /// + public CornerRadius AvatarRadius + { + get { return (CornerRadius)GetValue(AvatarRadiusProperty); } + set { SetValue(AvatarRadiusProperty, value); } + } + + private void OnClick(object sender, RoutedEventArgs e) + => Click?.Invoke(this, EventArgs.Empty); + } +} diff --git a/src/Workspace/Controls/Dynamic/DynamicItem/DynamicItem.cs b/src/Workspace/Controls/Dynamic/DynamicItem/DynamicItem.cs new file mode 100644 index 000000000..0a927cccc --- /dev/null +++ b/src/Workspace/Controls/Dynamic/DynamicItem/DynamicItem.cs @@ -0,0 +1,17 @@ +// Copyright (c) Richasy. All rights reserved. + +using Bili.ViewModels.Interfaces.Community; + +namespace Bili.Workspace.Controls.Dynamic +{ + /// + /// 动态条目. + /// + public sealed class DynamicItem : ReactiveControl + { + /// + /// Initializes a new instance of the class. + /// + public DynamicItem() => DefaultStyleKey = typeof(DynamicItem); + } +} diff --git a/src/Workspace/Controls/Dynamic/DynamicItem/DynamicItem.xaml b/src/Workspace/Controls/Dynamic/DynamicItem/DynamicItem.xaml new file mode 100644 index 000000000..6911dc548 --- /dev/null +++ b/src/Workspace/Controls/Dynamic/DynamicItem/DynamicItem.xaml @@ -0,0 +1,149 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/Workspace/Pages/HomePage.xaml.cs b/src/Workspace/Pages/HomePage.xaml.cs new file mode 100644 index 000000000..60925388e --- /dev/null +++ b/src/Workspace/Pages/HomePage.xaml.cs @@ -0,0 +1,87 @@ +// Copyright (c) Richasy. All rights reserved. + +using System; +using System.Diagnostics; +using Bili.DI.Container; +using Bili.Models.Data.Community; +using Bili.Models.Data.Search; +using Bili.Models.Enums.Workspace; +using Bili.Toolkit.Interfaces; +using Bili.ViewModels.Interfaces.Account; +using Bili.ViewModels.Interfaces.Search; +using Bili.ViewModels.Interfaces.Workspace; +using Microsoft.UI.Xaml; +using Models.Workspace; +using Windows.System; + +namespace Bili.Workspace.Pages +{ + /// + /// 首页. + /// + public sealed partial class HomePage : HomePageBase + { + private readonly IAccountViewModel _accountViewModel; + private readonly ISearchBoxViewModel _searchBoxViewModel; + + /// + /// Initializes a new instance of the class. + /// + public HomePage() + { + InitializeComponent(); + _accountViewModel = Locator.Instance.GetService(); + _searchBoxViewModel = Locator.Instance.GetService(); + } + + /// + protected override void OnPageLoaded() + { + if (ViewModel.VideoPartitions.Count == 0) + { + ViewModel.InitializeVideoPartitionsCommand.Execute(default); + } + + if (_searchBoxViewModel.HotSearchCollection.Count == 0) + { + _searchBoxViewModel.InitializeCommand.Execute(default); + } + } + + private void OnVideoPartitionClick(object sender, RoutedEventArgs e) + { + if (PartitionFlyout.IsOpen) + { + PartitionFlyout.Hide(); + } + + var ele = sender as FrameworkElement; + var data = ele.DataContext is IVideoPartitionViewModel vpvm + ? vpvm.Data + : ele.DataContext as Partition; + CoreViewModel.NavigateToPartition(data); + } + + private async void OnQuickTopicClickAsync(object sender, RoutedEventArgs e) + { + var settingsToolkit = Locator.Instance.GetService(); + var perferLaunch = settingsToolkit.ReadLocalSetting(Models.Enums.SettingNames.LaunchType, LaunchType.Web); + var data = (sender as FrameworkElement).DataContext as QuickTopic; + var uri = new Uri(perferLaunch == LaunchType.Bili ? data.BiliUrl : data.WebUrl); + await Launcher.LaunchUriAsync(uri); + } + + private void OnHotSearchClick(object sender, RoutedEventArgs e) + { + var data = (sender as FrameworkElement).DataContext as SearchSuggest; + _searchBoxViewModel.SelectSuggestCommand.Execute(data); + } + } + + /// + /// 的基类. + /// + public class HomePageBase : PageBase + { + } +} diff --git a/src/Workspace/Pages/PageBase.cs b/src/Workspace/Pages/PageBase.cs new file mode 100644 index 000000000..5c38f5621 --- /dev/null +++ b/src/Workspace/Pages/PageBase.cs @@ -0,0 +1,107 @@ +// Copyright (c) Richasy. All rights reserved. + +using System; +using Bili.DI.Container; +using Bili.ViewModels.Interfaces.Workspace; +using Microsoft.UI.Xaml; +using Microsoft.UI.Xaml.Controls; +using Microsoft.UI.Xaml.Navigation; + +namespace Bili.Workspace.Pages +{ + /// + /// 应用页面基类. + /// + public class PageBase : Page + { + /// + /// 的依赖属性. + /// + public static readonly DependencyProperty CoreViewModelProperty = + DependencyProperty.Register(nameof(CoreViewModel), typeof(IWorkspaceViewModel), typeof(PageBase), new PropertyMetadata(default)); + + /// + /// Initializes a new instance of the class. + /// + public PageBase() => CoreViewModel = Locator.Instance.GetService(); + + /// + /// 应用核心视图模型. + /// + public IWorkspaceViewModel CoreViewModel + { + get => (IWorkspaceViewModel)GetValue(CoreViewModelProperty); + set => SetValue(CoreViewModelProperty, value); + } + + /// + /// 获取页面注册的视图模型. + /// + /// 视图模型,如果没有则返回null. + public virtual object GetViewModel() => null; + + /// + protected override void OnNavigatedFrom(NavigationEventArgs e) + { + GC.Collect(); + GC.SuppressFinalize(this); + } + } + + /// + /// 带视图模型的应用页面基类. + /// + /// 视图模型. + public class PageBase : PageBase + where TViewModel : class + { + /// + /// 的依赖属性. + /// + public static readonly DependencyProperty ViewModelProperty = + DependencyProperty.Register(nameof(ViewModel), typeof(TViewModel), typeof(PageBase), new PropertyMetadata(default)); + + /// + /// Initializes a new instance of the class. + /// + public PageBase() + { + ViewModel = Locator.Instance.GetService(); + DataContext = ViewModel; + Loaded += OnLoaded; + Unloaded += OnUnloaded; + } + + /// + /// 页面的视图模型. + /// + public TViewModel ViewModel + { + get { return (TViewModel)GetValue(ViewModelProperty); } + set { SetValue(ViewModelProperty, value); } + } + + /// + public override object GetViewModel() => ViewModel; + + /// + /// 在页面加载完成后调用. + /// + protected virtual void OnPageLoaded() + { + } + + /// + /// 在页面卸载完成后调用. + /// + protected virtual void OnPageUnloaded() + { + } + + private void OnLoaded(object sender, RoutedEventArgs e) + => OnPageLoaded(); + + private void OnUnloaded(object sender, RoutedEventArgs e) + => OnPageUnloaded(); + } +} diff --git a/src/Workspace/Pages/PartitionPage.xaml b/src/Workspace/Pages/PartitionPage.xaml new file mode 100644 index 000000000..d80fbe40d --- /dev/null +++ b/src/Workspace/Pages/PartitionPage.xaml @@ -0,0 +1,51 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/Workspace/Pages/PartitionPage.xaml.cs b/src/Workspace/Pages/PartitionPage.xaml.cs new file mode 100644 index 000000000..a8bf923c4 --- /dev/null +++ b/src/Workspace/Pages/PartitionPage.xaml.cs @@ -0,0 +1,59 @@ +// Copyright (c) Richasy. All rights reserved. + +using System.ComponentModel; +using Bili.Models.Data.Community; +using Bili.ViewModels.Interfaces.Video; +using Microsoft.UI.Xaml.Navigation; + +namespace Bili.Workspace.Pages +{ + /// + /// 分区页面. + /// + public sealed partial class PartitionPage : PartitionPageBase + { + /// + /// Initializes a new instance of the class. + /// + public PartitionPage() + { + InitializeComponent(); + ViewModel.PropertyChanged += OnViewModelPropertyChanged; + } + + /// + protected override void OnNavigatedTo(NavigationEventArgs e) + { + if (e.Parameter is Partition partition) + { + Header.Title = partition.Name; + ViewModel.SetPartition(partition); + } + } + + /// + protected override void OnPageLoaded() + => ViewModel.InitializeCommand.Execute(default); + + private void OnViewModelPropertyChanged(object sender, PropertyChangedEventArgs e) + { + if (e.PropertyName == nameof(ViewModel.IsReloading)) + { + ContentScrollViewer.ChangeView(0, 0, 1); + } + } + + private void OnErrorPanelButtonClick(object sender, Microsoft.UI.Xaml.RoutedEventArgs e) + => ViewModel.ReloadCommand.Execute(default); + + private void OnVideoViewRequestLoadMore(object sender, System.EventArgs e) + => ViewModel.IncrementalCommand.Execute(default); + } + + /// + /// 的基类. + /// + public class PartitionPageBase : PageBase + { + } +} diff --git a/src/Workspace/Pages/PopularPage.xaml b/src/Workspace/Pages/PopularPage.xaml new file mode 100644 index 000000000..fe9e60a29 --- /dev/null +++ b/src/Workspace/Pages/PopularPage.xaml @@ -0,0 +1,51 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/Workspace/Pages/PopularPage.xaml.cs b/src/Workspace/Pages/PopularPage.xaml.cs new file mode 100644 index 000000000..12b92f75b --- /dev/null +++ b/src/Workspace/Pages/PopularPage.xaml.cs @@ -0,0 +1,47 @@ +// Copyright (c) Richasy. All rights reserved. + +using System.ComponentModel; +using Bili.ViewModels.Interfaces.Home; + +namespace Bili.Workspace.Pages +{ + /// + /// 热门视频页面. + /// + public sealed partial class PopularPage : PopularPageBase + { + /// + /// Initializes a new instance of the class. + /// + public PopularPage() + { + InitializeComponent(); + ViewModel.PropertyChanged += OnViewModelPropertyChanged; + } + + /// + protected override void OnPageLoaded() + => ViewModel.InitializeCommand.Execute(default); + + private void OnViewModelPropertyChanged(object sender, PropertyChangedEventArgs e) + { + if (e.PropertyName == nameof(ViewModel.IsReloading)) + { + ContentScrollViewer.ChangeView(0, 0, 1); + } + } + + private void OnErrorPanelButtonClick(object sender, Microsoft.UI.Xaml.RoutedEventArgs e) + => ViewModel.ReloadCommand.Execute(default); + + private void OnVideoViewRequestLoadMore(object sender, System.EventArgs e) + => ViewModel.IncrementalCommand.Execute(default); + } + + /// + /// 的基类. + /// + public class PopularPageBase : PageBase + { + } +} diff --git a/src/Workspace/Pages/RankPage.xaml b/src/Workspace/Pages/RankPage.xaml new file mode 100644 index 000000000..e001eebb1 --- /dev/null +++ b/src/Workspace/Pages/RankPage.xaml @@ -0,0 +1,50 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/Workspace/Pages/RankPage.xaml.cs b/src/Workspace/Pages/RankPage.xaml.cs new file mode 100644 index 000000000..29e28ba86 --- /dev/null +++ b/src/Workspace/Pages/RankPage.xaml.cs @@ -0,0 +1,44 @@ +// Copyright (c) Richasy. All rights reserved. + +using System.ComponentModel; +using Bili.ViewModels.Interfaces.Home; + +namespace Bili.Workspace.Pages +{ + /// + /// An empty page that can be used on its own or navigated to within a Frame. + /// + public sealed partial class RankPage : RankPageBase + { + /// + /// Initializes a new instance of the class. + /// + public RankPage() + { + InitializeComponent(); + ViewModel.PropertyChanged += OnViewModelPropertyChanged; + } + + /// + protected override void OnPageLoaded() + => ViewModel.InitializeCommand.Execute(default); + + private void OnViewModelPropertyChanged(object sender, PropertyChangedEventArgs e) + { + if (e.PropertyName == nameof(ViewModel.IsReloading)) + { + ContentScrollViewer.ChangeView(0, 0, 1); + } + } + + private void OnErrorPanelButtonClick(object sender, Microsoft.UI.Xaml.RoutedEventArgs e) + => ViewModel.ReloadCommand.Execute(default); + } + + /// + /// 的基类. + /// + public class RankPageBase : PageBase + { + } +} diff --git a/src/Workspace/Pages/RecommendPage.xaml b/src/Workspace/Pages/RecommendPage.xaml new file mode 100644 index 000000000..9db00f45d --- /dev/null +++ b/src/Workspace/Pages/RecommendPage.xaml @@ -0,0 +1,51 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/Workspace/Pages/RecommendPage.xaml.cs b/src/Workspace/Pages/RecommendPage.xaml.cs new file mode 100644 index 000000000..6a84c25d0 --- /dev/null +++ b/src/Workspace/Pages/RecommendPage.xaml.cs @@ -0,0 +1,47 @@ +// Copyright (c) Richasy. All rights reserved. + +using System.ComponentModel; +using Bili.ViewModels.Interfaces.Home; + +namespace Bili.Workspace.Pages +{ + /// + /// 推荐视频页面. + /// + public sealed partial class RecommendPage : RecommendPageBase + { + /// + /// Initializes a new instance of the class. + /// + public RecommendPage() + { + InitializeComponent(); + ViewModel.PropertyChanged += OnViewModelPropertyChanged; + } + + /// + protected override void OnPageLoaded() + => ViewModel.InitializeCommand.Execute(default); + + private void OnViewModelPropertyChanged(object sender, PropertyChangedEventArgs e) + { + if (e.PropertyName == nameof(ViewModel.IsReloading)) + { + ContentScrollViewer.ChangeView(0, 0, 1); + } + } + + private void OnErrorPanelButtonClick(object sender, Microsoft.UI.Xaml.RoutedEventArgs e) + => ViewModel.ReloadCommand.Execute(default); + + private void OnVideoViewRequestLoadMore(object sender, System.EventArgs e) + => ViewModel.IncrementalCommand.Execute(default); + } + + /// + /// 的基类. + /// + public class RecommendPageBase : PageBase + { + } +} diff --git a/src/Workspace/Pages/SettingsPage.xaml b/src/Workspace/Pages/SettingsPage.xaml new file mode 100644 index 000000000..6914aa644 --- /dev/null +++ b/src/Workspace/Pages/SettingsPage.xaml @@ -0,0 +1,44 @@ + + + + + + + 260 + 120 + + + + + + + + + + + + + + + + diff --git a/src/Workspace/Pages/SettingsPage.xaml.cs b/src/Workspace/Pages/SettingsPage.xaml.cs new file mode 100644 index 000000000..3da8cfc0d --- /dev/null +++ b/src/Workspace/Pages/SettingsPage.xaml.cs @@ -0,0 +1,25 @@ +// Copyright (c) Richasy. All rights reserved. + +using Bili.ViewModels.Interfaces.Workspace; + +namespace Bili.Workspace.Pages +{ + /// + /// 设置页面. + /// + public sealed partial class SettingsPage : SettingsPageBase + { + /// + /// Initializes a new instance of the class. + /// + public SettingsPage() + => InitializeComponent(); + } + + /// + /// 的基类. + /// + public class SettingsPageBase : PageBase + { + } +} diff --git a/src/Workspace/Program.cs b/src/Workspace/Program.cs new file mode 100644 index 000000000..a78690fe1 --- /dev/null +++ b/src/Workspace/Program.cs @@ -0,0 +1,66 @@ +// Copyright (c) Richasy. All rights reserved. + +using System; +using System.Threading; +using System.Threading.Tasks; +using DI.Workspace; +using Microsoft.UI.Dispatching; +using Microsoft.UI.Xaml; +using Microsoft.Windows.AppLifecycle; + +namespace Bili.Workspace +{ + /// + /// Single instance mode: + /// https://blogs.windows.com/windowsdeveloper/2022/01/28/making-the-app-single-instanced-part-3/. + /// + public static class Program + { + private static App _app; + + // Note that [STAThread] doesn't work with "async Task Main(string[] args)" + // https://github.com/dotnet/roslyn/issues/22112 + [STAThread] + private static void Main(string[] args) + { + var mainAppInstance = AppInstance.FindOrRegisterForKey(App.Guid); + if (!mainAppInstance.IsCurrent) + { + var current = AppInstance.GetCurrent(); + var actArgs = current.GetActivatedEventArgs(); + RedirectActivationTo(actArgs, mainAppInstance); + return; + } + else + { + mainAppInstance.Activated += OnAppActivated; + } + + WinRT.ComWrappersSupport.InitializeComWrappers(); + + Application.Start(p => + { + DIFactory.RegisterAppRequiredServices(); + var context = new DispatcherQueueSynchronizationContext(DispatcherQueue.GetForCurrentThread()); + SynchronizationContext.SetSynchronizationContext(context); + + _app = new App(); + }); + } + + private static void OnAppActivated(object sender, AppActivationArguments e) + => _app.ActivateWindow(); + + private static void RedirectActivationTo( + AppActivationArguments args, AppInstance keyInstance) + { + var redirectSemaphore = new Semaphore(0, 1); + Task.Run(() => + { + keyInstance.RedirectActivationToAsync(args).AsTask().Wait(); + redirectSemaphore.Release(); + }); + redirectSemaphore.WaitOne(); + } + } +} diff --git a/src/Workspace/Properties/PublishProfiles/win10-arm64.pubxml b/src/Workspace/Properties/PublishProfiles/win10-arm64.pubxml new file mode 100644 index 000000000..a7fdd16b6 --- /dev/null +++ b/src/Workspace/Properties/PublishProfiles/win10-arm64.pubxml @@ -0,0 +1,20 @@ + + + + + FileSystem + ARM64 + win10-arm64 + bin\$(Configuration)\$(TargetFramework)\$(RuntimeIdentifier)\publish\ + true + False + False + True + + + \ No newline at end of file diff --git a/src/Workspace/Properties/PublishProfiles/win10-x64.pubxml b/src/Workspace/Properties/PublishProfiles/win10-x64.pubxml new file mode 100644 index 000000000..26ea7e55c --- /dev/null +++ b/src/Workspace/Properties/PublishProfiles/win10-x64.pubxml @@ -0,0 +1,20 @@ + + + + + FileSystem + x64 + win10-x64 + bin\$(Configuration)\$(TargetFramework)\$(RuntimeIdentifier)\publish\ + true + False + False + True + + + \ No newline at end of file diff --git a/src/Workspace/Properties/PublishProfiles/win10-x86.pubxml b/src/Workspace/Properties/PublishProfiles/win10-x86.pubxml new file mode 100644 index 000000000..34d14d4d4 --- /dev/null +++ b/src/Workspace/Properties/PublishProfiles/win10-x86.pubxml @@ -0,0 +1,20 @@ + + + + + FileSystem + x86 + win10-x86 + bin\$(Configuration)\$(TargetFramework)\$(RuntimeIdentifier)\publish\ + true + False + False + True + + + \ No newline at end of file diff --git a/src/Workspace/Properties/launchSettings.json b/src/Workspace/Properties/launchSettings.json new file mode 100644 index 000000000..b4e860ab9 --- /dev/null +++ b/src/Workspace/Properties/launchSettings.json @@ -0,0 +1,10 @@ +{ + "profiles": { + "Workspace (Package)": { + "commandName": "MsixPackage" + }, + "Workspace (Unpackaged)": { + "commandName": "Project" + } + } +} \ No newline at end of file diff --git a/src/Workspace/Resources/Converter/BoolToVisibilityConverter.cs b/src/Workspace/Resources/Converter/BoolToVisibilityConverter.cs new file mode 100644 index 000000000..40427a252 --- /dev/null +++ b/src/Workspace/Resources/Converter/BoolToVisibilityConverter.cs @@ -0,0 +1,41 @@ +// Copyright (c) Richasy. All rights reserved. + +using System; +using Microsoft.UI.Xaml; +using Microsoft.UI.Xaml.Data; + +namespace Bili.Workspace.Resources.Converter +{ + /// + /// 的转换. + /// + public class BoolToVisibilityConverter : IValueConverter + { + /// + /// 是否反转值. + /// + public bool IsReverse { get; set; } + + /// + public object Convert(object value, Type targetType, object parameter, string language) + { + var vis = Visibility.Visible; + if (value is bool v) + { + if (IsReverse) + { + vis = v ? Visibility.Collapsed : Visibility.Visible; + } + else + { + vis = v ? Visibility.Visible : Visibility.Collapsed; + } + } + + return vis; + } + + /// + public object ConvertBack(object value, Type targetType, object parameter, string language) => throw new NotImplementedException(); + } +} diff --git a/src/Workspace/Resources/Converter/LaunchTypeTextConverter.cs b/src/Workspace/Resources/Converter/LaunchTypeTextConverter.cs new file mode 100644 index 000000000..2605045d5 --- /dev/null +++ b/src/Workspace/Resources/Converter/LaunchTypeTextConverter.cs @@ -0,0 +1,37 @@ +// Copyright (c) Richasy. All rights reserved. + +using System; +using Bili.DI.Container; +using Bili.Models.Enums.Workspace; +using Bili.Toolkit.Interfaces; +using Microsoft.UI.Xaml.Data; + +namespace Bili.Workspace.Resources.Converter +{ + /// + /// 启动类型文本转换器. + /// + public sealed class LaunchTypeTextConverter : IValueConverter + { + /// + public object Convert(object value, Type targetType, object parameter, string language) + { + var result = string.Empty; + var resToolkit = Locator.Instance.GetService(); + switch ((LaunchType)value) + { + case LaunchType.Bili: + result = resToolkit.GetLocaleString(Models.Enums.LanguageNames.Bili); + break; + case LaunchType.Web: + result = resToolkit.GetLocaleString(Models.Enums.LanguageNames.Web); + break; + } + + return result; + } + + /// + public object ConvertBack(object value, Type targetType, object parameter, string language) => throw new NotImplementedException(); + } +} diff --git a/src/Workspace/Resources/Converter/ObjectToBoolConverter.cs b/src/Workspace/Resources/Converter/ObjectToBoolConverter.cs new file mode 100644 index 000000000..8a1a57dca --- /dev/null +++ b/src/Workspace/Resources/Converter/ObjectToBoolConverter.cs @@ -0,0 +1,55 @@ +// Copyright (c) Richasy. All rights reserved. + +using System; +using Microsoft.UI.Xaml.Data; + +namespace Bili.Workspace.Resources.Converter +{ + /// + /// 对象到Boolean的转换器. + /// + /// + /// 当对象不为空时,返回True. + /// + public class ObjectToBoolConverter : IValueConverter + { + /// + /// 是否反转结果. + /// + /// + /// 反转后,当字符串为空时返回True,反之返回False. + /// + public bool IsReverse { get; set; } + + /// + public object Convert(object value, Type targetType, object parameter, string language) + { + var result = false; + if (value != null) + { + if (value is string str) + { + result = !string.IsNullOrEmpty(str); + } + else if (value is bool b) + { + result = b; + } + else + { + result = true; + } + } + + if (IsReverse) + { + result = !result; + } + + return result; + } + + /// + public object ConvertBack(object value, Type targetType, object parameter, string language) => throw new NotImplementedException(); + } +} diff --git a/src/Workspace/Resources/Converter/ObjectToVisibilityConverter.cs b/src/Workspace/Resources/Converter/ObjectToVisibilityConverter.cs new file mode 100644 index 000000000..807ad04d1 --- /dev/null +++ b/src/Workspace/Resources/Converter/ObjectToVisibilityConverter.cs @@ -0,0 +1,43 @@ +// Copyright (c) Richasy. All rights reserved. + +using System; +using Microsoft.UI.Xaml; +using Microsoft.UI.Xaml.Data; + +namespace Bili.Workspace.Resources.Converter +{ + /// + /// 对象到可见性的转换器. 当对象为空时,返回Collapsed. + /// + public class ObjectToVisibilityConverter : IValueConverter + { + /// + /// 是否反转结果. + /// + public bool IsReverse { get; set; } + + /// + public object Convert(object value, Type targetType, object parameter, string language) + { + var isShow = true; + if (value == null) + { + isShow = false; + } + else if (value is string str) + { + isShow = !string.IsNullOrEmpty(str); + } + + if (IsReverse) + { + isShow = !isShow; + } + + return isShow ? Visibility.Visible : Visibility.Collapsed; + } + + /// + public object ConvertBack(object value, Type targetType, object parameter, string language) => throw new NotImplementedException(); + } +} diff --git a/src/Workspace/Resources/Converter/StartupPositionTextConverter.cs b/src/Workspace/Resources/Converter/StartupPositionTextConverter.cs new file mode 100644 index 000000000..5b0b4939e --- /dev/null +++ b/src/Workspace/Resources/Converter/StartupPositionTextConverter.cs @@ -0,0 +1,52 @@ +// Copyright (c) Richasy. All rights reserved. + +using System; +using Bili.DI.Container; +using Bili.Models.Enums.Workspace; +using Bili.Toolkit.Interfaces; +using Microsoft.UI.Xaml.Data; + +namespace Bili.Workspace.Resources.Converter +{ + /// + /// 启动位置文本转换器. + /// + public sealed class StartupPositionTextConverter : IValueConverter + { + /// + public object Convert(object value, Type targetType, object parameter, string language) + { + var result = string.Empty; + var resToolkit = Locator.Instance.GetService(); + switch ((StartupPosition)value) + { + case StartupPosition.TopLeft: + result = resToolkit.GetLocaleString(Models.Enums.LanguageNames.TopLeft); + break; + case StartupPosition.TopCenter: + result = resToolkit.GetLocaleString(Models.Enums.LanguageNames.TopCenter); + break; + case StartupPosition.TopRight: + result = resToolkit.GetLocaleString(Models.Enums.LanguageNames.TopRight); + break; + case StartupPosition.Center: + result = resToolkit.GetLocaleString(Models.Enums.LanguageNames.Center); + break; + case StartupPosition.BottomLeft: + result = resToolkit.GetLocaleString(Models.Enums.LanguageNames.BottomLeft); + break; + case StartupPosition.BottomCenter: + result = resToolkit.GetLocaleString(Models.Enums.LanguageNames.BottomCenter); + break; + case StartupPosition.BottomRight: + result = resToolkit.GetLocaleString(Models.Enums.LanguageNames.BottomRight); + break; + } + + return result; + } + + /// + public object ConvertBack(object value, Type targetType, object parameter, string language) => throw new NotImplementedException(); + } +} diff --git a/src/Workspace/Resources/Converter/UserLevelConverter.cs b/src/Workspace/Resources/Converter/UserLevelConverter.cs new file mode 100644 index 000000000..be2d838b1 --- /dev/null +++ b/src/Workspace/Resources/Converter/UserLevelConverter.cs @@ -0,0 +1,21 @@ +// Copyright (c) Richasy. All rights reserved. + +using System; +using Microsoft.UI.Xaml.Data; +using Microsoft.UI.Xaml.Media.Imaging; + +namespace Bili.Workspace.Resources.Converter +{ + /// + /// 用户等级转换器,将等级转化为对应的图片. + /// + public class UserLevelConverter : IValueConverter + { + /// + public object Convert(object value, Type targetType, object parameter, string language) + => new BitmapImage(new Uri($"ms-appx:///Assets/Level/level_{value}.png")); + + /// + public object ConvertBack(object value, Type targetType, object parameter, string language) => throw new NotImplementedException(); + } +} diff --git a/src/Workspace/Resources/Extension/LocaleExtension.cs b/src/Workspace/Resources/Extension/LocaleExtension.cs new file mode 100644 index 000000000..08287175d --- /dev/null +++ b/src/Workspace/Resources/Extension/LocaleExtension.cs @@ -0,0 +1,28 @@ +// Copyright (c) Richasy. All rights reserved. + +using Bili.DI.Container; +using Bili.Models.Enums; +using Bili.Toolkit.Interfaces; +using Microsoft.UI.Xaml.Markup; + +namespace Bili.Workspace.Resources.Extension +{ + /// + /// Localized text extension. + /// + [MarkupExtensionReturnType(ReturnType = typeof(string))] + public sealed class LocaleExtension : MarkupExtension + { + /// + /// Language name. + /// + public LanguageNames Name { get; set; } + + /// + protected override object ProvideValue() + { + return Locator.Instance.GetService() + .GetLocaleString(Name); + } + } +} diff --git a/src/Workspace/Resources/Extension/VisualTreeExtension.cs b/src/Workspace/Resources/Extension/VisualTreeExtension.cs new file mode 100644 index 000000000..a842e57f1 --- /dev/null +++ b/src/Workspace/Resources/Extension/VisualTreeExtension.cs @@ -0,0 +1,133 @@ +// Copyright (c) Richasy. All rights reserved. + +using System; +using Microsoft.UI.Xaml; +using Microsoft.UI.Xaml.Media; + +namespace Bili.Workspace.Resources.Extension +{ + /// + /// 视觉树扩展. + /// + public static class VisualTreeExtension + { + /// + /// 通过名称查找指定的后裔元素. + /// + /// 元素类型. + /// 从这个元素开始往下查找. + /// 指定元素的名称. + /// 指定元素,如果没找到则返回null. + public static T FindDescendantElementByName(this DependencyObject element, string name) + where T : FrameworkElement + { + if (element == null || string.IsNullOrWhiteSpace(name)) + { + return default; + } + + if (name.Equals((element as FrameworkElement)?.Name, StringComparison.OrdinalIgnoreCase)) + { + return element as T; + } + + var childCount = VisualTreeHelper.GetChildrenCount(element); + for (var i = 0; i < childCount; i++) + { + var result = VisualTreeHelper.GetChild(element, i).FindDescendantElementByName(name); + if (result != null) + { + return result; + } + } + + return default; + } + + /// + /// 通过类型查找指定的后裔元素. + /// + /// 查找元素的类型. + /// 从这个元素开始往下查找. + /// 指定元素,如果没找到则返回null. + public static T FindDescendantElementByType(this DependencyObject element) + where T : DependencyObject + { + T retValue = null; + var childrenCount = VisualTreeHelper.GetChildrenCount(element); + + for (var i = 0; i < childrenCount; i++) + { + var child = VisualTreeHelper.GetChild(element, i); + if (child is T type) + { + retValue = type; + break; + } + + retValue = FindDescendantElementByType(child); + + if (retValue != null) + { + break; + } + } + + return retValue; + } + + /// + /// 通过名称查找指定的祖先元素. + /// + /// 元素类型. + /// 从这个元素开始往上查找. + /// 指定元素的名称. + /// 指定元素,如果没找到则返回null. + public static T FindAscendantElementByName(this DependencyObject element, string name) + where T : FrameworkElement + { + if (element == null || string.IsNullOrWhiteSpace(name)) + { + return null; + } + + var parent = VisualTreeHelper.GetParent(element); + + if (parent == null) + { + return null; + } + + if (name.Equals((parent as FrameworkElement)?.Name, StringComparison.OrdinalIgnoreCase)) + { + return parent as T; + } + + return parent.FindAscendantElementByName(name); + } + + /// + /// 通过类型查找指定的祖先元素. + /// + /// 查找元素的类型. + /// 从这个元素开始往上查找. + /// 指定元素,如果没找到则返回null. + public static T FindAscendantElementByType(this DependencyObject element) + where T : DependencyObject + { + var parent = VisualTreeHelper.GetParent(element); + + if (parent == null) + { + return null; + } + + if (parent.GetType() == typeof(T)) + { + return (T)parent; + } + + return parent.FindAscendantElementByType(); + } + } +} diff --git a/src/Workspace/Resources/Strings/zh-Hans/Resources.resw b/src/Workspace/Resources/Strings/zh-Hans/Resources.resw new file mode 100644 index 000000000..0068a9d28 --- /dev/null +++ b/src/Workspace/Resources/Strings/zh-Hans/Resources.resw @@ -0,0 +1,381 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + 关于 + + + 添加到稍后再看 + + + 添加稍后再看失败 + + + 已添加到稍后再看列表 + + + 番剧 + + + 一款BiliBili的第三方桌面工具 + + + 哔哩空间 + + + 点赞数 + + + 哔哩 + + + 开发者B站主页 + + + 亿 + + + 底部居中 + + + 左下角 + + + 右下角 + + + 取消 + + + 中央 + + + 弹幕数 + + + 删除 + + + 纪录片 + + + 国创 + + + 动态数 + + + 动态 + + + 二维码加载失败,请刷新 + + + 移除视频收藏失败 + + + 删除观看历史失败 + + + 粉丝数 + + + 关注数 + + + 通用 + + + 首页 + + + 热搜 + + + 小时 + + + 启动方式 + + + 选择打开专题/视频的方式 + + + 直播 + + + 登录失败,请稍后重试 + + + 消息 + + + 分钟 + + + 电影 + + + 我的收藏 + + + 你需要先登录 + + + 未找到指定的数据 + + + 打开哔哩 + + + 分区 + + + 个人主页 + + + 播放数 + + + 热门 + + + 项目主页 + + + 二维码已过期,请刷新 + + + 请使用移动客户端扫描下方的二维码登录 + + + 退出应用 + + + 全区排行榜 + + + 排行榜请求失败 + + + 推荐视频 + + + 刷新 + + + 移除稍后再看失败 + + + 请求历史记录失败 + + + 请求热门视频失败 + + + 请求推荐视频失败 + + + 请求分区视频失败 + + + 评分 + + + 搜索视频、番剧或UP主 + + + + + + 查看全部 + + + 设置失败 + + + 设置 + + + 显示更多 + + + 登录 + + + 用户登录 + + + 退出账户 + + + 专栏 + + + 自启动 + + + 将应用添加到开机启动项,应用将在系统启动后最小化在托盘 + + + 启动项由您的组织或组策略管理,应用启动项已被禁用 + + + 启动项由用户手动禁用,您需要手动启用才能生效 + + + 启动位置 + + + 设置窗口显示的位置 + + + + + + 顶部居中 + + + 专题 + + + 左上角 + + + 右上角 + + + 电视剧 + + + 观看历史 + + + 稍后再看 + + + 大会员 + + + 网页 + + + 这不是啥高大上的应用,就是一个集成了哔哩的一些快捷操作的小工具。当然了,在使用它之前你要先登录才行 + + \ No newline at end of file diff --git a/src/Workspace/Styles/Style.Overwrite.xaml b/src/Workspace/Styles/Style.Overwrite.xaml new file mode 100644 index 000000000..459444ecc --- /dev/null +++ b/src/Workspace/Styles/Style.Overwrite.xaml @@ -0,0 +1,278 @@ + + + + + + + + + + + + 20,20,20,12 + + + diff --git a/src/Workspace/Styles/Theme.Dark.xaml b/src/Workspace/Styles/Theme.Dark.xaml new file mode 100644 index 000000000..38cf15601 --- /dev/null +++ b/src/Workspace/Styles/Theme.Dark.xaml @@ -0,0 +1,32 @@ + + + + + + + + + + + + + + + + + + diff --git a/src/Workspace/Styles/Theme.HighContrast.xaml b/src/Workspace/Styles/Theme.HighContrast.xaml new file mode 100644 index 000000000..057f3cae6 --- /dev/null +++ b/src/Workspace/Styles/Theme.HighContrast.xaml @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/src/Workspace/Styles/Theme.Light.xaml b/src/Workspace/Styles/Theme.Light.xaml new file mode 100644 index 000000000..f7fb31014 --- /dev/null +++ b/src/Workspace/Styles/Theme.Light.xaml @@ -0,0 +1,32 @@ + + + + + + + + + + + + + + + + + + diff --git a/src/Workspace/Styles/TrayResources.xaml b/src/Workspace/Styles/TrayResources.xaml new file mode 100644 index 000000000..9c27c2cce --- /dev/null +++ b/src/Workspace/Styles/TrayResources.xaml @@ -0,0 +1,40 @@ + + + Assets/FluentIcon.ttf#FluentSystemIcons-Resizable + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/Workspace/Workspace.csproj b/src/Workspace/Workspace.csproj new file mode 100644 index 000000000..e05b84fbc --- /dev/null +++ b/src/Workspace/Workspace.csproj @@ -0,0 +1,283 @@ + + + WinExe + net6.0-windows10.0.22000.0 + 10.0.17763.0 + Bili.Workspace + app.manifest + x86;x64;ARM64 + win10-x86;win10-x64;win10-arm64 + win10-$(Platform).pubxml + true + true + 10.0.22000.0 + zh-Hans + True + Workspace_TemporaryKey.pfx + True + SHA256 + False + x64 + False + D:\Package\Bili\ + False + True + Never + 0 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Designer + + + Designer + + + Designer + + + Designer + + + Designer + + + Designer + + + Designer + + + Designer + + + Designer + + + Designer + + + Designer + + + Designer + + + Designer + + + Designer + + + Designer + + + Designer + + + Designer + + + Designer + + + Designer + + + MSBuild:Compile + + + + + MSBuild:Compile + + + + + MSBuild:Compile + + + + + MSBuild:Compile + + + + + MSBuild:Compile + + + + + MSBuild:Compile + + + + + MSBuild:Compile + + + + + MSBuild:Compile + + + + + MSBuild:Compile + + + + + MSBuild:Compile + + + + + + + + MSBuild:Compile + + + + + MSBuild:Compile + + + + + MSBuild:Compile + + + + + MSBuild:Compile + + + + + MSBuild:Compile + + + + + MSBuild:Compile + + + + + MSBuild:Compile + + + + + MSBuild:Compile + + + + + + true + + + + + DISABLE_XAML_GENERATED_MAIN + + + DISABLE_XAML_GENERATED_MAIN + + + DISABLE_XAML_GENERATED_MAIN + + + DISABLE_XAML_GENERATED_MAIN + + + DISABLE_XAML_GENERATED_MAIN + + + DISABLE_XAML_GENERATED_MAIN + + diff --git a/src/Workspace/app.manifest b/src/Workspace/app.manifest new file mode 100644 index 000000000..307522ac4 --- /dev/null +++ b/src/Workspace/app.manifest @@ -0,0 +1,20 @@ + + + + + + + + + + + + + + PerMonitorV2 + + + \ No newline at end of file