├── .gitignore ├── LICENSE ├── Masa.Blazor.MauiDemo.Platforms ├── GeoCoordinate.cs ├── IPlatformIntegration.cs └── Masa.Blazor.MauiDemo.Platforms.csproj ├── Masa.Blazor.MauiDemo.Rcl ├── Auth │ └── MauiDemoAuthenticationStateProvider.cs ├── Components │ └── DateDigitalClockSheet.razor ├── Data │ └── ProDatabase.cs ├── Masa.Blazor.MauiDemo.Rcl.csproj ├── Models │ ├── Product.cs │ ├── TodoTag.cs │ └── TodoTask.cs ├── Pages │ ├── Login │ │ ├── Components │ │ │ ├── PhoneNumberConfirm.razor │ │ │ └── PhoneNumberInput.razor │ │ ├── Login.razor │ │ ├── LoginViaSms.razor │ │ ├── LoginViaSmsConfirm.razor │ │ ├── RedirectToLogin.razor │ │ ├── ResetPassword.razor │ │ ├── RetrievePassword.razor │ │ ├── RetrievePasswordConfirm.razor │ │ ├── SignUp.razor │ │ ├── SignUpConfirm.razor │ │ ├── TermsAndConditions.razor │ │ ├── Test1.razor │ │ ├── Test1Abc.razor │ │ └── TestMessage.razor │ ├── Settings │ │ ├── AboutUs.razor │ │ ├── DarkMode.razor │ │ └── Settings.razor │ ├── Shop │ │ ├── ProductDetail.razor │ │ ├── Search.razor │ │ └── Shop.razor │ ├── Todo │ │ ├── EditSheet.razor │ │ ├── Todo.razor │ │ └── TodoNav.razor │ └── User.razor ├── RenderFragments.cs ├── ServiceCollectionExtensions.cs ├── Services │ ├── ProductService.cs │ └── UserService.cs ├── Shared │ ├── LoginLayout.razor │ └── MainLayout.razor ├── _Imports.razor └── wwwroot │ ├── css │ ├── app.css │ └── materialdesign │ │ └── v7.1.96 │ │ ├── css │ │ ├── materialdesignicons.min.css │ │ └── materialdesignicons.min.css.map │ │ └── fonts │ │ ├── materialdesignicons-webfont.eot │ │ ├── materialdesignicons-webfont.ttf │ │ ├── materialdesignicons-webfont.woff │ │ └── materialdesignicons-webfont.woff2 │ └── img │ ├── contact--no-padding.png │ ├── masastack-cn.png │ ├── masastack.png │ └── product │ ├── 1.jpg │ ├── 10.jpg │ ├── 11.jpg │ ├── 12.jpg │ ├── 13.jpg │ ├── 14.jpg │ ├── 15.jpg │ ├── 16.jpg │ ├── 17.jpg │ ├── 18.jpg │ ├── 19.jpg │ ├── 2.jpg │ ├── 20.jpg │ ├── 21.jpg │ ├── 22.jpg │ ├── 23.jpg │ ├── 24.jpg │ ├── 25.jpg │ ├── 26.jpg │ ├── 27.jpg │ ├── 28.jpg │ ├── 29.jpg │ ├── 3.jpg │ ├── 30.jpg │ ├── 31.jpg │ ├── 32.jpg │ ├── 4.jpg │ ├── 5.jpg │ ├── 6.jpg │ ├── 7.jpg │ ├── 8.jpg │ └── 9.jpg ├── Masa.Blazor.MauiDemo.Server ├── App.razor ├── H5PlatformIntegration.cs ├── Masa.Blazor.MauiDemo.Server.csproj ├── Pages │ ├── Error.cshtml │ ├── Error.cshtml.cs │ └── _Host.cshtml ├── Program.cs ├── Properties │ └── launchSettings.json ├── _Imports.razor ├── appsettings.Development.json ├── appsettings.json └── wwwroot │ ├── css │ └── site.css │ └── favicon.png ├── Masa.Blazor.MauiDemo.Web.slnx ├── Masa.Blazor.MauiDemo.slnx ├── Masa.Blazor.MauiDemo ├── App.xaml ├── App.xaml.cs ├── Main.razor ├── MainPage.xaml ├── MainPage.xaml.cs ├── Masa.Blazor.MauiDemo.csproj ├── MauiPlatformIntegration.cs ├── MauiProgram.cs ├── Platforms │ ├── Android │ │ ├── AndroidManifest.xml │ │ ├── MainActivity.cs │ │ ├── MainApplication.cs │ │ └── Resources │ │ │ └── values │ │ │ └── colors.xml │ ├── MacCatalyst │ │ ├── AppDelegate.cs │ │ ├── Info.plist │ │ └── Program.cs │ ├── Tizen │ │ ├── Main.cs │ │ └── tizen-manifest.xml │ ├── Windows │ │ ├── App.xaml │ │ ├── App.xaml.cs │ │ ├── Package.appxmanifest │ │ └── app.manifest │ └── iOS │ │ ├── AppDelegate.cs │ │ ├── Info.plist │ │ └── Program.cs ├── Properties │ └── launchSettings.json ├── Resources │ ├── AppIcon │ │ ├── appicon.svg │ │ └── appiconfg.svg │ ├── Fonts │ │ └── OpenSans-Regular.ttf │ ├── Images │ │ └── dotnet_bot.svg │ ├── Raw │ │ └── AboutAssets.txt │ └── Splash │ │ └── splash.svg ├── _Imports.cs ├── _Imports.razor └── wwwroot │ ├── css │ └── app.css │ ├── favicon.ico │ └── index.html ├── README.md └── imgs ├── login.png ├── shop.png ├── signup.png ├── signup2.png ├── todo.png └── user.png /.gitignore: -------------------------------------------------------------------------------- 1 | ## Ignore Visual Studio temporary files, build results, and 2 | ## files generated by popular Visual Studio add-ons. 3 | ## 4 | ## Get latest from https://github.com/github/gitignore/blob/main/VisualStudio.gitignore 5 | 6 | # User-specific files 7 | *.rsuser 8 | *.suo 9 | *.user 10 | *.userosscache 11 | *.sln.docstates 12 | 13 | # User-specific files (MonoDevelop/Xamarin Studio) 14 | *.userprefs 15 | 16 | # Mono auto generated files 17 | mono_crash.* 18 | 19 | # Build results 20 | [Dd]ebug/ 21 | [Dd]ebugPublic/ 22 | [Rr]elease/ 23 | [Rr]eleases/ 24 | x64/ 25 | x86/ 26 | [Ww][Ii][Nn]32/ 27 | [Aa][Rr][Mm]/ 28 | [Aa][Rr][Mm]64/ 29 | bld/ 30 | [Bb]in/ 31 | [Oo]bj/ 32 | [Ll]og/ 33 | [Ll]ogs/ 34 | 35 | # Visual Studio 2015/2017 cache/options directory 36 | .vs/ 37 | # Uncomment if you have tasks that create the project's static files in wwwroot 38 | #wwwroot/ 39 | 40 | # Visual Studio 2017 auto generated files 41 | Generated\ Files/ 42 | 43 | # MSTest test Results 44 | [Tt]est[Rr]esult*/ 45 | [Bb]uild[Ll]og.* 46 | 47 | # NUnit 48 | *.VisualState.xml 49 | TestResult.xml 50 | nunit-*.xml 51 | 52 | # Build Results of an ATL Project 53 | [Dd]ebugPS/ 54 | [Rr]eleasePS/ 55 | dlldata.c 56 | 57 | # Benchmark Results 58 | BenchmarkDotNet.Artifacts/ 59 | 60 | # .NET Core 61 | project.lock.json 62 | project.fragment.lock.json 63 | artifacts/ 64 | 65 | # ASP.NET Scaffolding 66 | ScaffoldingReadMe.txt 67 | 68 | # StyleCop 69 | StyleCopReport.xml 70 | 71 | # Files built by Visual Studio 72 | *_i.c 73 | *_p.c 74 | *_h.h 75 | *.ilk 76 | *.meta 77 | *.obj 78 | *.iobj 79 | *.pch 80 | *.pdb 81 | *.ipdb 82 | *.pgc 83 | *.pgd 84 | *.rsp 85 | *.sbr 86 | *.tlb 87 | *.tli 88 | *.tlh 89 | *.tmp 90 | *.tmp_proj 91 | *_wpftmp.csproj 92 | *.log 93 | *.tlog 94 | *.vspscc 95 | *.vssscc 96 | .builds 97 | *.pidb 98 | *.svclog 99 | *.scc 100 | 101 | # Chutzpah Test files 102 | _Chutzpah* 103 | 104 | # Visual C++ cache files 105 | ipch/ 106 | *.aps 107 | *.ncb 108 | *.opendb 109 | *.opensdf 110 | *.sdf 111 | *.cachefile 112 | *.VC.db 113 | *.VC.VC.opendb 114 | 115 | # Visual Studio profiler 116 | *.psess 117 | *.vsp 118 | *.vspx 119 | *.sap 120 | 121 | # Visual Studio Trace Files 122 | *.e2e 123 | 124 | # TFS 2012 Local Workspace 125 | $tf/ 126 | 127 | # Guidance Automation Toolkit 128 | *.gpState 129 | 130 | # ReSharper is a .NET coding add-in 131 | _ReSharper*/ 132 | *.[Rr]e[Ss]harper 133 | *.DotSettings.user 134 | 135 | # TeamCity is a build add-in 136 | _TeamCity* 137 | 138 | # DotCover is a Code Coverage Tool 139 | *.dotCover 140 | 141 | # AxoCover is a Code Coverage Tool 142 | .axoCover/* 143 | !.axoCover/settings.json 144 | 145 | # Coverlet is a free, cross platform Code Coverage Tool 146 | coverage*.json 147 | coverage*.xml 148 | coverage*.info 149 | 150 | # Visual Studio code coverage results 151 | *.coverage 152 | *.coveragexml 153 | 154 | # NCrunch 155 | _NCrunch_* 156 | .*crunch*.local.xml 157 | nCrunchTemp_* 158 | 159 | # MightyMoose 160 | *.mm.* 161 | AutoTest.Net/ 162 | 163 | # Web workbench (sass) 164 | .sass-cache/ 165 | 166 | # Installshield output folder 167 | [Ee]xpress/ 168 | 169 | # DocProject is a documentation generator add-in 170 | DocProject/buildhelp/ 171 | DocProject/Help/*.HxT 172 | DocProject/Help/*.HxC 173 | DocProject/Help/*.hhc 174 | DocProject/Help/*.hhk 175 | DocProject/Help/*.hhp 176 | DocProject/Help/Html2 177 | DocProject/Help/html 178 | 179 | # Click-Once directory 180 | publish/ 181 | 182 | # Publish Web Output 183 | *.[Pp]ublish.xml 184 | *.azurePubxml 185 | # Note: Comment the next line if you want to checkin your web deploy settings, 186 | # but database connection strings (with potential passwords) will be unencrypted 187 | *.pubxml 188 | *.publishproj 189 | 190 | # Microsoft Azure Web App publish settings. Comment the next line if you want to 191 | # checkin your Azure Web App publish settings, but sensitive information contained 192 | # in these scripts will be unencrypted 193 | PublishScripts/ 194 | 195 | # NuGet Packages 196 | *.nupkg 197 | # NuGet Symbol Packages 198 | *.snupkg 199 | # The packages folder can be ignored because of Package Restore 200 | **/[Pp]ackages/* 201 | # except build/, which is used as an MSBuild target. 202 | !**/[Pp]ackages/build/ 203 | # Uncomment if necessary however generally it will be regenerated when needed 204 | #!**/[Pp]ackages/repositories.config 205 | # NuGet v3's project.json files produces more ignorable files 206 | *.nuget.props 207 | *.nuget.targets 208 | 209 | # Microsoft Azure Build Output 210 | csx/ 211 | *.build.csdef 212 | 213 | # Microsoft Azure Emulator 214 | ecf/ 215 | rcf/ 216 | 217 | # Windows Store app package directories and files 218 | AppPackages/ 219 | BundleArtifacts/ 220 | Package.StoreAssociation.xml 221 | _pkginfo.txt 222 | *.appx 223 | *.appxbundle 224 | *.appxupload 225 | 226 | # Visual Studio cache files 227 | # files ending in .cache can be ignored 228 | *.[Cc]ache 229 | # but keep track of directories ending in .cache 230 | !?*.[Cc]ache/ 231 | 232 | # Others 233 | ClientBin/ 234 | ~$* 235 | *~ 236 | *.dbmdl 237 | *.dbproj.schemaview 238 | *.jfm 239 | *.pfx 240 | *.publishsettings 241 | orleans.codegen.cs 242 | 243 | # Including strong name files can present a security risk 244 | # (https://github.com/github/gitignore/pull/2483#issue-259490424) 245 | #*.snk 246 | 247 | # Since there are multiple workflows, uncomment next line to ignore bower_components 248 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) 249 | #bower_components/ 250 | 251 | # RIA/Silverlight projects 252 | Generated_Code/ 253 | 254 | # Backup & report files from converting an old project file 255 | # to a newer Visual Studio version. Backup files are not needed, 256 | # because we have git ;-) 257 | _UpgradeReport_Files/ 258 | Backup*/ 259 | UpgradeLog*.XML 260 | UpgradeLog*.htm 261 | ServiceFabricBackup/ 262 | *.rptproj.bak 263 | 264 | # SQL Server files 265 | *.mdf 266 | *.ldf 267 | *.ndf 268 | 269 | # Business Intelligence projects 270 | *.rdl.data 271 | *.bim.layout 272 | *.bim_*.settings 273 | *.rptproj.rsuser 274 | *- [Bb]ackup.rdl 275 | *- [Bb]ackup ([0-9]).rdl 276 | *- [Bb]ackup ([0-9][0-9]).rdl 277 | 278 | # Microsoft Fakes 279 | FakesAssemblies/ 280 | 281 | # GhostDoc plugin setting file 282 | *.GhostDoc.xml 283 | 284 | # Node.js Tools for Visual Studio 285 | .ntvs_analysis.dat 286 | node_modules/ 287 | 288 | # Visual Studio 6 build log 289 | *.plg 290 | 291 | # Visual Studio 6 workspace options file 292 | *.opt 293 | 294 | # Visual Studio 6 auto-generated workspace file (contains which files were open etc.) 295 | *.vbw 296 | 297 | # Visual Studio 6 auto-generated project file (contains which files were open etc.) 298 | *.vbp 299 | 300 | # Visual Studio 6 workspace and project file (working project files containing files to include in project) 301 | *.dsw 302 | *.dsp 303 | 304 | # Visual Studio 6 technical files 305 | *.ncb 306 | *.aps 307 | 308 | # Visual Studio LightSwitch build output 309 | **/*.HTMLClient/GeneratedArtifacts 310 | **/*.DesktopClient/GeneratedArtifacts 311 | **/*.DesktopClient/ModelManifest.xml 312 | **/*.Server/GeneratedArtifacts 313 | **/*.Server/ModelManifest.xml 314 | _Pvt_Extensions 315 | 316 | # Paket dependency manager 317 | .paket/paket.exe 318 | paket-files/ 319 | 320 | # FAKE - F# Make 321 | .fake/ 322 | 323 | # CodeRush personal settings 324 | .cr/personal 325 | 326 | # Python Tools for Visual Studio (PTVS) 327 | __pycache__/ 328 | *.pyc 329 | 330 | # Cake - Uncomment if you are using it 331 | # tools/** 332 | # !tools/packages.config 333 | 334 | # Tabs Studio 335 | *.tss 336 | 337 | # Telerik's JustMock configuration file 338 | *.jmconfig 339 | 340 | # BizTalk build output 341 | *.btp.cs 342 | *.btm.cs 343 | *.odx.cs 344 | *.xsd.cs 345 | 346 | # OpenCover UI analysis results 347 | OpenCover/ 348 | 349 | # Azure Stream Analytics local run output 350 | ASALocalRun/ 351 | 352 | # MSBuild Binary and Structured Log 353 | *.binlog 354 | 355 | # NVidia Nsight GPU debugger configuration file 356 | *.nvuser 357 | 358 | # MFractors (Xamarin productivity tool) working folder 359 | .mfractor/ 360 | 361 | # Local History for Visual Studio 362 | .localhistory/ 363 | 364 | # Visual Studio History (VSHistory) files 365 | .vshistory/ 366 | 367 | # BeatPulse healthcheck temp database 368 | healthchecksdb 369 | 370 | # Backup folder for Package Reference Convert tool in Visual Studio 2017 371 | MigrationBackup/ 372 | 373 | # Ionide (cross platform F# VS Code tools) working folder 374 | .ionide/ 375 | 376 | # Fody - auto-generated XML schema 377 | FodyWeavers.xsd 378 | 379 | # VS Code files for those working on multiple tools 380 | .vscode/* 381 | !.vscode/settings.json 382 | !.vscode/tasks.json 383 | !.vscode/launch.json 384 | !.vscode/extensions.json 385 | *.code-workspace 386 | 387 | # Local History for Visual Studio Code 388 | .history/ 389 | 390 | # Windows Installer files from build outputs 391 | *.cab 392 | *.msi 393 | *.msix 394 | *.msm 395 | *.msp 396 | 397 | # JetBrains Rider 398 | *.sln.iml 399 | 400 | *.db 401 | .idea -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 MASA Stack 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Masa.Blazor.MauiDemo.Platforms/GeoCoordinate.cs: -------------------------------------------------------------------------------- 1 | namespace Masa.Blazor.MauiDemo.Platforms; 2 | 3 | public class GeoCoordinate 4 | { 5 | /// 6 | /// Gets or sets the latitude coordinate of this location. 7 | /// 8 | public double Latitude { get; set; } 9 | 10 | /// 11 | /// Gets or sets the longitude coordinate of this location. 12 | /// 13 | public double Longitude { get; set; } 14 | 15 | /// 16 | /// Gets the altitude in meters (if available) in a reference system which is specified by . 17 | /// 18 | /// Returns 0 or if not available. 19 | public double? Altitude { get; set; } 20 | 21 | public static GeoCoordinate Beijing => new() { Latitude = 39.9042, Longitude = 116.4074 }; 22 | } -------------------------------------------------------------------------------- /Masa.Blazor.MauiDemo.Platforms/IPlatformIntegration.cs: -------------------------------------------------------------------------------- 1 | namespace Masa.Blazor.MauiDemo.Platforms; 2 | 3 | public interface IPlatformIntegration 4 | { 5 | Task GetCachedCoordinateAsync(); 6 | 7 | Task GetCurrentCoordinateAsync(); 8 | 9 | /// 10 | /// Set the status bar color and style 11 | /// 12 | /// The color in ARGB format 13 | /// 0: default, 1: light content, 2: dark content 14 | void SetStatusBar(string argb, int style); 15 | 16 | /// 17 | /// Set app theme 18 | /// 19 | /// 0: auto, 1: light, 2: dark 20 | Task SetThemeAsync(int theme); 21 | 22 | /// 23 | /// Init app theme 24 | /// 25 | Task InitThemeAsync(); 26 | 27 | /// 28 | /// Get app theme 29 | /// 30 | /// 0: auto, 1: light, 2: dark 31 | ValueTask GetThemeAsync(); 32 | 33 | /// 34 | /// Check if the system theme is dark 35 | /// 36 | /// 37 | ValueTask IsDarkThemeOfSystemAsync(); 38 | 39 | Task SetCacheAsync(string key, TValue value); 40 | 41 | Task GetCacheAsync(string key, TValue defaultValue); 42 | 43 | Task RemoveCacheAsync(string key); 44 | } -------------------------------------------------------------------------------- /Masa.Blazor.MauiDemo.Platforms/Masa.Blazor.MauiDemo.Platforms.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | net8.0 5 | enable 6 | enable 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /Masa.Blazor.MauiDemo.Rcl/Auth/MauiDemoAuthenticationStateProvider.cs: -------------------------------------------------------------------------------- 1 | using System.Security.Claims; 2 | using Masa.Blazor.MauiDemo.Rcl.Services; 3 | using Microsoft.AspNetCore.Components.Authorization; 4 | 5 | namespace Masa.Blazor.MauiDemo.Rcl.Auth; 6 | 7 | public class MauiDemoAuthenticationStateProvider(UserService userService) : AuthenticationStateProvider 8 | { 9 | private ClaimsPrincipal _currentUser = new(new ClaimsIdentity()); 10 | 11 | public override async Task GetAuthenticationStateAsync() 12 | { 13 | var user = await userService.GetAuthenticatedUserAsync(); 14 | if (user?.Identity?.IsAuthenticated is true) 15 | { 16 | return new AuthenticationState(user); 17 | } 18 | 19 | // TODO: refresh token here? 20 | 21 | return new AuthenticationState(new ClaimsPrincipal()); 22 | } 23 | 24 | public async Task LoginAsync(string username, string password) 25 | { 26 | var user = await LoginWithExternalProviderAsync(username, password); 27 | _currentUser = user; 28 | var state = new AuthenticationState(_currentUser); 29 | NotifyAuthenticationStateChanged(Task.FromResult(state)); 30 | return state; 31 | } 32 | 33 | public async Task LoginViaSmsAsync(string phoneNumber, string confirmCode) 34 | { 35 | var user = await LoginViaSmsWithExternalProviderAsync(phoneNumber, confirmCode); 36 | _currentUser = user; 37 | var state = new AuthenticationState(_currentUser); 38 | NotifyAuthenticationStateChanged(Task.FromResult(state)); 39 | return state; 40 | } 41 | 42 | private Task LoginWithExternalProviderAsync(string username, string password) 43 | { 44 | /* 45 | Provide OpenID/MSAL code to authenticate the user. See your identity 46 | provider's documentation for details. 47 | 48 | Return a new ClaimsPrincipal based on a new ClaimsIdentity. 49 | */ 50 | 51 | return userService.LoginAsync(username, password); 52 | } 53 | 54 | private Task LoginViaSmsWithExternalProviderAsync(string phoneNumber, string confirmCode) 55 | { 56 | /* 57 | Provide OpenID/MSAL code to authenticate the user. See your identity 58 | provider's documentation for details. 59 | 60 | Return a new ClaimsPrincipal based on a new ClaimsIdentity. 61 | */ 62 | 63 | return userService.LoginBySmsAsync(phoneNumber, confirmCode); 64 | } 65 | 66 | public void Logout() 67 | { 68 | _currentUser = new ClaimsPrincipal(new ClaimsIdentity()); 69 | userService.Logout(); 70 | NotifyAuthenticationStateChanged( 71 | Task.FromResult(new AuthenticationState(_currentUser))); 72 | } 73 | } -------------------------------------------------------------------------------- /Masa.Blazor.MauiDemo.Rcl/Components/DateDigitalClockSheet.razor: -------------------------------------------------------------------------------- 1 |  3 |
4 | 6 | Cancel 7 | 8 | 9 | 11 | Confirm 12 | 13 |
14 | 18 | 19 |
20 | 21 | @code { 22 | 23 | [Parameter] public bool Show { get; set; } 24 | [Parameter] public EventCallback ShowChanged { get; set; } 25 | [Parameter] public DateTime Value { get; set; } 26 | [Parameter] public EventCallback ValueChanged { get; set; } 27 | 28 | private DateTime _internalDate; 29 | private bool _previousShow; 30 | 31 | protected override void OnParametersSet() 32 | { 33 | base.OnParametersSet(); 34 | 35 | if (_previousShow != Show) 36 | { 37 | _previousShow = Show; 38 | 39 | if (Show) 40 | { 41 | _internalDate = Value; 42 | } 43 | } 44 | } 45 | 46 | private void InternalDateChanged(DateTime date) 47 | { 48 | _internalDate = date; 49 | } 50 | 51 | private void HandleOnCancel() 52 | { 53 | ShowChanged.InvokeAsync(false); 54 | } 55 | 56 | private async Task HandleOnConfirm() 57 | { 58 | await ValueChanged.InvokeAsync(_internalDate); 59 | _ = ShowChanged.InvokeAsync(false); 60 | } 61 | 62 | } -------------------------------------------------------------------------------- /Masa.Blazor.MauiDemo.Rcl/Data/ProDatabase.cs: -------------------------------------------------------------------------------- 1 | using System.Diagnostics.CodeAnalysis; 2 | using System.Text; 3 | using Masa.Blazor.MauiDemo.Rcl.Models; 4 | using SQLite; 5 | 6 | namespace Masa.Blazor.MauiDemo.Rcl.Data; 7 | 8 | public class ProDatabase 9 | { 10 | public const string DatabaseFilename = "maui-demo.db"; 11 | 12 | public const SQLiteOpenFlags Flags = 13 | // open the database in read/write mode 14 | SQLiteOpenFlags.ReadWrite | 15 | // create the database if it doesn't exist 16 | SQLiteOpenFlags.Create | 17 | // enable multi-threaded database access 18 | SQLiteOpenFlags.SharedCache; 19 | 20 | public ProDatabase() 21 | { 22 | DatabasePath = DatabaseFilename; 23 | } 24 | 25 | public ProDatabase(string dir) 26 | { 27 | DatabasePath = Path.Combine(dir, DatabaseFilename); 28 | } 29 | 30 | public string DatabasePath { get; private set; } 31 | 32 | private SQLiteAsyncConnection? Database { get; set; } 33 | 34 | [MemberNotNull(nameof(Database))] 35 | async Task InitAsync() 36 | { 37 | if (Database is not null) 38 | { 39 | return; 40 | } 41 | 42 | Database = new SQLiteAsyncConnection(DatabasePath, Flags); 43 | await Database.CreateTableAsync(); 44 | await Database.CreateTableAsync(); 45 | } 46 | 47 | #region Todo task 48 | 49 | public async Task CreateTaskAsync(TodoTask task) 50 | { 51 | await InitAsync(); 52 | return await Database.InsertAsync(task); 53 | } 54 | 55 | public async Task UpdateTaskAsync(TodoTask task) 56 | { 57 | await InitAsync(); 58 | await Database.UpdateAsync(task); 59 | } 60 | 61 | public async Task DeleteTaskAsync(TodoTask task) 62 | { 63 | await InitAsync(); 64 | await Database.DeleteAsync(task); 65 | } 66 | 67 | public async Task> GetTasksAsync( 68 | int page, 69 | int pageSize, 70 | DateTime dateTime = default, 71 | int tag = 0, 72 | TodoTaskPriority? priority = null) 73 | { 74 | await InitAsync(); 75 | 76 | var sqlBuilder = new StringBuilder(); 77 | sqlBuilder.Append("SELECT * FROM [TodoTask]"); 78 | 79 | var hasWhere = false; 80 | 81 | if (dateTime != default) 82 | { 83 | hasWhere = true; 84 | 85 | var tick = dateTime.AddDays(1).Ticks; 86 | sqlBuilder.Append(" WHERE [DueAt] < ").Append(tick); 87 | } 88 | 89 | if (tag != 0) 90 | { 91 | sqlBuilder.Append(hasWhere ? " AND" : " WHERE"); 92 | sqlBuilder.Append(" [Tags] LIKE '%").Append(tag).Append(";%'"); 93 | } 94 | 95 | if (priority is not null) 96 | { 97 | sqlBuilder.Append(hasWhere ? " AND" : " WHERE"); 98 | sqlBuilder.Append(" [Priority] = ").Append((int)priority); 99 | } 100 | 101 | sqlBuilder.Append(" ORDER BY [DueAt] DESC"); 102 | sqlBuilder.Append(" LIMIT ").Append(pageSize).Append(" OFFSET ").Append((page - 1) * pageSize); 103 | 104 | return await Database.QueryAsync(sqlBuilder.ToString()); 105 | } 106 | 107 | #endregion 108 | 109 | #region Todo tag 110 | 111 | public async Task CreateTagAsync(TodoTag tag) 112 | { 113 | await InitAsync(); 114 | 115 | return await this.Database.InsertAsync(tag); 116 | } 117 | 118 | public async Task> GetTagsAsync() 119 | { 120 | await InitAsync(); 121 | 122 | return await Database.Table().ToListAsync(); 123 | } 124 | 125 | public async Task UpdateTagAsync(TodoTag tag) 126 | { 127 | await InitAsync(); 128 | await Database.UpdateAsync(tag); 129 | } 130 | 131 | public async Task DeleteTagAsync(TodoTag tag) 132 | { 133 | await InitAsync(); 134 | await Database.DeleteAsync(tag); 135 | } 136 | 137 | #endregion 138 | } -------------------------------------------------------------------------------- /Masa.Blazor.MauiDemo.Rcl/Masa.Blazor.MauiDemo.Rcl.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | net8.0 5 | enable 6 | enable 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /Masa.Blazor.MauiDemo.Rcl/Models/Product.cs: -------------------------------------------------------------------------------- 1 | namespace Masa.Blazor.MauiDemo.Rcl.Models; 2 | 3 | public class Product 4 | { 5 | public Guid Id { get; init; } 6 | 7 | public string Name { get; init; } 8 | 9 | public string Description { get; init; } 10 | 11 | public double Price { get; init; } 12 | 13 | public string PictureFile { get; init; } 14 | 15 | public string Category { get; set; } 16 | 17 | public int Rating { get; set; } 18 | 19 | public string Brand { get; set; } 20 | 21 | public bool Favorite { get; set; } 22 | 23 | public Product(string name, double price, string pictureFile, string category, int rating, string brand, 24 | string description) 25 | { 26 | Id = Guid.NewGuid(); 27 | Name = name; 28 | Description = description; 29 | Price = price; 30 | PictureFile = pictureFile; 31 | Category = category; 32 | Rating = rating; 33 | Brand = brand; 34 | } 35 | 36 | public Product(Guid id, string name, double price, string pictureFile, string category, int rating, string brand, 37 | string description) 38 | { 39 | Id = id; 40 | Name = name; 41 | Description = description; 42 | Price = price; 43 | PictureFile = pictureFile; 44 | Category = category; 45 | Rating = rating; 46 | Brand = brand; 47 | } 48 | } -------------------------------------------------------------------------------- /Masa.Blazor.MauiDemo.Rcl/Models/TodoTag.cs: -------------------------------------------------------------------------------- 1 | using SQLite; 2 | 3 | namespace Masa.Blazor.MauiDemo.Rcl.Models; 4 | 5 | public class TodoTag 6 | { 7 | [PrimaryKey] [AutoIncrement] public int Id { get; set; } 8 | 9 | public string? Name { get; set; } 10 | 11 | public string? Color { get; set; } 12 | } -------------------------------------------------------------------------------- /Masa.Blazor.MauiDemo.Rcl/Models/TodoTask.cs: -------------------------------------------------------------------------------- 1 | using System.ComponentModel.DataAnnotations; 2 | using SQLite; 3 | 4 | namespace Masa.Blazor.MauiDemo.Rcl.Models; 5 | 6 | public class TodoTask 7 | { 8 | private int[] _tagIds = []; 9 | 10 | public TodoTask() 11 | { 12 | DueAt = DateTime.Today; 13 | } 14 | 15 | [PrimaryKey, AutoIncrement] public int Id { get; set; } 16 | 17 | [Required] [Indexed] public string? Title { get; set; } 18 | 19 | public string? Description { get; set; } 20 | 21 | [Required] public DateTime DueAt { get; set; } 22 | 23 | public TodoTaskPriority Priority { get; set; } 24 | 25 | public bool Important { get; set; } 26 | 27 | public bool Completed { get; set; } 28 | 29 | [Ignore] 30 | public int[] TagIds 31 | { 32 | get => Tags?.Split(';').Where(t => !string.IsNullOrEmpty(t)).Select(int.Parse).ToArray() ?? []; 33 | set 34 | { 35 | Tags = string.Join(';', value); 36 | if (Tags.Length > 0) 37 | { 38 | Tags += ";"; 39 | } 40 | } 41 | } 42 | 43 | public string? Tags { get; set; } 44 | 45 | public object ShallowCopy() 46 | { 47 | return this.MemberwiseClone(); 48 | } 49 | } 50 | 51 | public enum TodoTaskPriority 52 | { 53 | Default, 54 | Low, 55 | Medium, 56 | High 57 | } -------------------------------------------------------------------------------- /Masa.Blazor.MauiDemo.Rcl/Pages/Login/Components/PhoneNumberConfirm.razor: -------------------------------------------------------------------------------- 1 | @using System.Diagnostics.CodeAnalysis 2 | @using System.Timers 3 | @implements IDisposable 4 | 5 | 6 | 7 | 37 | 38 | @code { 39 | 40 | [Parameter] public string PhoneNumber { get; set; } = null!; 41 | 42 | [Parameter] public string? PageTitle { get; set; } 43 | 44 | [Parameter] public EventCallback OnConfirm { get; set; } 45 | 46 | private string? _confirmCode; 47 | private int _seconds = 60; 48 | private Timer? _timer; 49 | 50 | [MemberNotNullWhen(false, nameof(_confirmCode))] 51 | private bool LoginBtnDisabled => string.IsNullOrEmpty(_confirmCode); 52 | 53 | protected override async Task OnAfterRenderAsync(bool firstRender) 54 | { 55 | if (firstRender) 56 | { 57 | await SendSmsAsync(); 58 | StateHasChanged(); 59 | } 60 | } 61 | 62 | private async Task SendSmsAsync() 63 | { 64 | // simulate sending sms 65 | await Task.Delay(100); 66 | 67 | _seconds = 60; 68 | if (_timer is null) 69 | { 70 | _timer = new(1000); 71 | _timer.Elapsed += TimerOnElapsed; 72 | } 73 | 74 | _timer.Start(); 75 | } 76 | 77 | private async Task ValidateCode() 78 | { 79 | if (LoginBtnDisabled) 80 | { 81 | return; 82 | } 83 | 84 | await OnConfirm.InvokeAsync(_confirmCode); 85 | } 86 | 87 | private void TimerOnElapsed(object? sender, ElapsedEventArgs e) 88 | { 89 | InvokeAsync(() => 90 | { 91 | _seconds--; 92 | 93 | if (_seconds == 0) 94 | { 95 | _timer!.Stop(); 96 | } 97 | 98 | StateHasChanged(); 99 | }); 100 | } 101 | 102 | public void Dispose() 103 | { 104 | if (_timer is not null) 105 | { 106 | _timer.Stop(); 107 | _timer.Elapsed -= TimerOnElapsed; 108 | _timer.Dispose(); 109 | } 110 | } 111 | 112 | } -------------------------------------------------------------------------------- /Masa.Blazor.MauiDemo.Rcl/Pages/Login/Components/PhoneNumberInput.razor: -------------------------------------------------------------------------------- 1 | @using System.Text.RegularExpressions 2 | @inject PageStackNavController NavController 3 | 4 | 5 | 6 | 47 | 48 | @code { 49 | 50 | [Parameter] public string? PageTitle { get; set; } 51 | 52 | [Parameter] public bool RequireAgreeCheckbox { get; set; } 53 | 54 | /// 55 | /// The uri prefix to navigate to after the user clicks the login button. 56 | /// template: /{UriPrefixNavigateTo}/{PhoneNumber} 57 | /// 58 | [Parameter] 59 | [EditorRequired] 60 | public string UriPrefixNavigateTo { get; set; } = null!; 61 | 62 | private static readonly Regex PhoneRegex = new(@"^1[3-9]\d{9}$"); 63 | 64 | private static readonly Func PhoneRule = (value) => 65 | { 66 | if (string.IsNullOrWhiteSpace(value) || PhoneRegex.IsMatch(value)) 67 | { 68 | return true; 69 | } 70 | 71 | return "Invalid phone number"; 72 | }; 73 | 74 | private string? _phoneNumber; 75 | private bool _agreed; 76 | private MForm? _form; 77 | 78 | private bool LoginBtnDisabled => string.IsNullOrEmpty(_phoneNumber) || (RequireAgreeCheckbox && !_agreed); 79 | 80 | private Func[] _rules = [PhoneRule]; 81 | 82 | private void NavigateToNext() 83 | { 84 | if (_form?.Validate() is true) 85 | { 86 | var uri = UriPrefixNavigateTo.StartsWith('/') ? UriPrefixNavigateTo : "/" + UriPrefixNavigateTo; 87 | NavController.Push($"{uri}/{_phoneNumber}"); 88 | } 89 | } 90 | 91 | } -------------------------------------------------------------------------------- /Masa.Blazor.MauiDemo.Rcl/Pages/Login/Login.razor: -------------------------------------------------------------------------------- 1 | @page "/login" 2 | @using System.Diagnostics.CodeAnalysis 3 | @layout LoginLayout 4 | @attribute [AllowAnonymous] 5 | @inject AuthenticationStateProvider AuthenticationStateProvider 6 | @inject IPopupService PopupService 7 | @inject NavigationManager NavigationManager 8 | @inject PageStackNavController NavController 9 | 10 |
11 | 82 | 83 |
84 | Or 85 | 86 |
88 | 89 | $wechat 90 | 91 | 92 | $apple 93 | 94 | 95 | $google 96 | 97 |
98 |
99 |
100 | 101 | @code { 102 | 103 | private string? _username; 104 | private string? _password; 105 | private bool _agreed; 106 | private bool _loggingIn; 107 | 108 | [MemberNotNullWhen(false, nameof(_username), nameof(_password))] 109 | private bool LoginBtnDisabled => string.IsNullOrWhiteSpace(_username) || string.IsNullOrWhiteSpace(_password) || !_agreed; 110 | 111 | private async Task LogIn() 112 | { 113 | if (LoginBtnDisabled) 114 | { 115 | return; 116 | } 117 | 118 | _loggingIn = true; 119 | StateHasChanged(); 120 | 121 | var authenticationState = await ((MauiDemoAuthenticationStateProvider)AuthenticationStateProvider).LoginAsync(_username, _password); 122 | if (authenticationState.User.Identity?.IsAuthenticated is true) 123 | { 124 | NavigationManager.NavigateTo("/"); 125 | } 126 | 127 | _loggingIn = false; 128 | } 129 | 130 | private async Task ThirdPartyLogIn(string provider) 131 | { 132 | await PopupService.EnqueueSnackbarAsync("Not implemented.", AlertTypes.Warning); 133 | } 134 | 135 | } -------------------------------------------------------------------------------- /Masa.Blazor.MauiDemo.Rcl/Pages/Login/LoginViaSms.razor: -------------------------------------------------------------------------------- 1 | @page "/login-via-sms" 2 | @attribute [AllowAnonymous] 3 | @layout LoginLayout 4 | 5 | -------------------------------------------------------------------------------- /Masa.Blazor.MauiDemo.Rcl/Pages/Login/LoginViaSmsConfirm.razor: -------------------------------------------------------------------------------- 1 | @page "/login-via-sms-confirm/{phone}" 2 | @attribute [AllowAnonymous] 3 | @layout LoginLayout 4 | @inject NavigationManager NavigationManager 5 | @inject AuthenticationStateProvider AuthenticationStateProvider 6 | @inject IPopupService PopupService 7 | 8 | 12 | 13 | @code { 14 | 15 | [Parameter] public string Phone { get; set; } = null!; 16 | 17 | private async Task HandleOnConfirm(string confirmCode) 18 | { 19 | PopupService.ShowProgressCircular(); 20 | StateHasChanged(); 21 | 22 | var authenticationState = await ((MauiDemoAuthenticationStateProvider)AuthenticationStateProvider).LoginViaSmsAsync(Phone, confirmCode); 23 | PopupService.HideProgressCircular(); 24 | if (authenticationState.User.Identity?.IsAuthenticated is true) 25 | { 26 | NavigationManager.NavigateTo("/"); 27 | } 28 | } 29 | 30 | } -------------------------------------------------------------------------------- /Masa.Blazor.MauiDemo.Rcl/Pages/Login/RedirectToLogin.razor: -------------------------------------------------------------------------------- 1 | @inject NavigationManager Navigation 2 | @layout LoginLayout 3 | 4 | @code { 5 | 6 | protected override void OnInitialized() 7 | { 8 | Navigation.NavigateTo("login"); 9 | } 10 | 11 | } -------------------------------------------------------------------------------- /Masa.Blazor.MauiDemo.Rcl/Pages/Login/ResetPassword.razor: -------------------------------------------------------------------------------- 1 | @page "/reset-password/{phone}/{confirmCode}" 2 | @layout LoginLayout 3 | @attribute [AllowAnonymous] 4 | @inject UserService UserService 5 | @inject PageStackNavController NavController 6 | @inject IPopupService PopupService 7 | 8 | 9 | 10 | 38 | 39 | @code { 40 | 41 | [Parameter] public string Phone { get; set; } = null!; 42 | [Parameter] public string ConfirmCode { get; set; } = null!; 43 | [SupplyParameterFromQuery] public bool SignUp { get; set; } 44 | 45 | private static readonly Func PasswordRule = value => 46 | { 47 | if (value is not null && value.Length >= 6) 48 | { 49 | return true; 50 | } 51 | 52 | return "Password must be at least 6 characters long"; 53 | }; 54 | 55 | private string? _password; 56 | private string? _confirmPassword; 57 | private Func _confirmPasswordRule = _ => true; 58 | private MForm? _form; 59 | private bool _loading; 60 | 61 | private bool LoginBtnDisabled => string.IsNullOrWhiteSpace(_password) || string.IsNullOrWhiteSpace(_confirmPassword); 62 | 63 | protected override void OnInitialized() 64 | { 65 | base.OnInitialized(); 66 | 67 | _confirmPasswordRule = (value) => 68 | { 69 | if (value is not null && value == _password) 70 | { 71 | return true; 72 | } 73 | 74 | return "Passwords do not match"; 75 | }; 76 | } 77 | 78 | private async Task ResetPasswordAndNavigateToLogin() 79 | { 80 | if (_form?.Validate() is true) 81 | { 82 | _loading = true; 83 | StateHasChanged(); 84 | 85 | var success = await UserService.ResetPasswordAsync(Phone, _password, ConfirmCode); 86 | _loading = false; 87 | if (success) 88 | { 89 | await PopupService.EnqueueSnackbarAsync($"Password {(SignUp ? "set" : "reset")} successfully. Please login.", AlertTypes.Success); 90 | NavController.GoBackToTab("/login"); 91 | } 92 | else 93 | { 94 | await PopupService.EnqueueSnackbarAsync("Invalid request", AlertTypes.Error); 95 | } 96 | } 97 | } 98 | 99 | } -------------------------------------------------------------------------------- /Masa.Blazor.MauiDemo.Rcl/Pages/Login/RetrievePassword.razor: -------------------------------------------------------------------------------- 1 | @page "/retrieve-password" 2 | @attribute [AllowAnonymous] 3 | @layout LoginLayout 4 | 5 | -------------------------------------------------------------------------------- /Masa.Blazor.MauiDemo.Rcl/Pages/Login/RetrievePasswordConfirm.razor: -------------------------------------------------------------------------------- 1 | @page "/retrieve-password-confirm/{phone}" 2 | @attribute [AllowAnonymous] 3 | @layout LoginLayout 4 | @inject PageStackNavController NavController 5 | @inject IPopupService PopupService 6 | @inject UserService UserService 7 | 8 | 12 | 13 | @code { 14 | 15 | [Parameter] public string Phone { get; set; } = null!; 16 | 17 | private async Task HandleOnConfirm(string confirmCode) 18 | { 19 | PopupService.ShowProgressCircular(); 20 | var success = await UserService.ValidateConfirmCodeAsync(Phone, confirmCode); 21 | PopupService.HideProgressCircular(); 22 | 23 | if (success) 24 | { 25 | NavController.Push($"/reset-password/{Phone}/{confirmCode}"); 26 | } 27 | else 28 | { 29 | await PopupService.EnqueueSnackbarAsync("Invalid confirm code", AlertTypes.Error); 30 | } 31 | } 32 | 33 | } -------------------------------------------------------------------------------- /Masa.Blazor.MauiDemo.Rcl/Pages/Login/SignUp.razor: -------------------------------------------------------------------------------- 1 | @page "/sign-up" 2 | @attribute [AllowAnonymous] 3 | @layout LoginLayout 4 | 5 | -------------------------------------------------------------------------------- /Masa.Blazor.MauiDemo.Rcl/Pages/Login/SignUpConfirm.razor: -------------------------------------------------------------------------------- 1 | @page "/sign-up-confirm/{phone}" 2 | @attribute [AllowAnonymous] 3 | @layout LoginLayout 4 | @inject PageStackNavController NavController 5 | @inject IPopupService PopupService 6 | @inject UserService UserService 7 | 8 | 12 | 13 | @code { 14 | 15 | [Parameter] public string Phone { get; set; } = null!; 16 | 17 | private async Task HandleOnConfirm(string confirmCode) 18 | { 19 | PopupService.ShowProgressCircular(); 20 | 21 | var success = await UserService.ValidateConfirmCodeAsync(Phone, confirmCode); 22 | PopupService.HideProgressCircular(); 23 | 24 | if (success) 25 | { 26 | NavController.Push($"/reset-password/{Phone}/{confirmCode}?signUp=true"); 27 | } 28 | else 29 | { 30 | await PopupService.EnqueueSnackbarAsync("Invalid confirmation code", AlertTypes.Error); 31 | } 32 | } 33 | 34 | } -------------------------------------------------------------------------------- /Masa.Blazor.MauiDemo.Rcl/Pages/Login/TermsAndConditions.razor: -------------------------------------------------------------------------------- 1 | @page "/terms-and-conditions" 2 | @attribute [AllowAnonymous] 3 | @layout LoginLayout 4 | 5 | 6 | 7 |

8 | This is the terms and conditions page. 9 |

-------------------------------------------------------------------------------- /Masa.Blazor.MauiDemo.Rcl/Pages/Login/Test1.razor: -------------------------------------------------------------------------------- 1 | @page "/test1" 2 | @using System.Web 3 | @layout LoginLayout 4 | @attribute [AllowAnonymous] 5 | @inject PageStackNavController NavController 6 | @inject IPopupService PopupService 7 | 8 | 10 | 11 | ABC 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /Masa.Blazor.MauiDemo.Rcl/Pages/Login/Test1Abc.razor: -------------------------------------------------------------------------------- 1 | @page "/test1/abc" 2 | @using System.Web 3 | @layout LoginLayout 4 | @attribute [AllowAnonymous] 5 | @inject PageStackNavController NavController 6 | 7 |

Test 1 ABC

8 | 9 | -------------------------------------------------------------------------------- /Masa.Blazor.MauiDemo.Rcl/Pages/Login/TestMessage.razor: -------------------------------------------------------------------------------- 1 | @page "/test1/message/{message}" 2 | @using System.Web 3 | @layout LoginLayout 4 | @attribute [AllowAnonymous] 5 | @inject PageStackNavController NavController 6 | 7 |

Test message: @Message

8 | 9 | 20 | 21 | 22 | @code { 23 | 24 | [Parameter] public string Message { get; set; } 25 | 26 | protected override void OnInitialized() 27 | { 28 | base.OnInitialized(); 29 | 30 | Console.Out.WriteLine("[Test1Message] OnInitialized"); 31 | } 32 | 33 | } -------------------------------------------------------------------------------- /Masa.Blazor.MauiDemo.Rcl/Pages/Settings/AboutUs.razor: -------------------------------------------------------------------------------- 1 | @page "/settings/about-us" 2 | 3 | 4 | 5 | 6 |
7 | 8 | 9 | 10 | 11 | 12 | 温州数闪科技有限公司,成立于2021年,是一家开源软件公司。致力于打造开源产品 MASA Stack,提供一站式 .NET 现代化应用开发交付解决方案,赋能560万 .NET 开发者转型为数字化人才,帮助企业降低数字化转型门槛,让变化更简单! 13 | 14 |
-------------------------------------------------------------------------------- /Masa.Blazor.MauiDemo.Rcl/Pages/Settings/DarkMode.razor: -------------------------------------------------------------------------------- 1 | @page "/settings/dark-mode" 2 | @inject IPlatformIntegration PlatformIntegration 3 | 4 | 5 | 6 | 7 |
8 | 9 | 10 | 11 | Automatic 12 | 13 | When enabled, the app will switch dark mode on/off to match your system settings. 14 | 15 | 16 | 17 | 22 | 23 | 24 | 25 | 26 | @if (theme != 0) 27 | { 28 | 29 | 33 | 34 | 35 | 36 | Light Mode 37 | 38 | 39 | 40 | mdi-check 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | Dark Mode 49 | 50 | 51 | 52 | mdi-check 53 | 54 | 55 | 56 | 57 | 58 | 59 | } 60 |
61 | 62 | @code { 63 | 64 | // 0: Auto, 1: Light, 2: Dark 65 | private int theme; 66 | 67 | protected override async Task OnAfterRenderAsync(bool firstRender) 68 | { 69 | if (firstRender) 70 | { 71 | theme = await PlatformIntegration.GetThemeAsync(); 72 | StateHasChanged(); 73 | } 74 | } 75 | 76 | private async Task AutoChanged(bool val) 77 | { 78 | if (val) 79 | { 80 | theme = 0; 81 | } 82 | else 83 | { 84 | var isDark = await PlatformIntegration.IsDarkThemeOfSystemAsync(); 85 | theme = isDark ? 2 : 1; 86 | } 87 | 88 | await PlatformIntegration.SetThemeAsync(theme); 89 | } 90 | 91 | private void ToggleTheme(StringNumber val) 92 | { 93 | var value = val.AsT1; 94 | theme = value; 95 | Console.Out.WriteLine("[DarkMode] Toggle theme: " + value); 96 | PlatformIntegration.SetThemeAsync(value); 97 | } 98 | 99 | } -------------------------------------------------------------------------------- /Masa.Blazor.MauiDemo.Rcl/Pages/Settings/Settings.razor: -------------------------------------------------------------------------------- 1 | @page "/settings" 2 | @inject AuthenticationStateProvider AuthenticationStateProvider 3 | @inject IPopupService PopupService 4 | 5 | Settings 6 | 7 | 8 | 9 | 10 |
11 | 12 | 13 | 14 | Dark mode 15 | 16 | 17 | $next 18 | 19 | 20 | 21 | 22 | 23 | 24 | About us 25 | 26 | 27 | $next 28 | 29 | 30 | 31 | 32 | 33 | 34 | Log Out 35 | 36 | 37 | 38 |
39 | 40 | @code { 41 | 42 | private async Task Logout() 43 | { 44 | var confirmed = await PopupService.ConfirmAsync("Log out", "Are you sure you want to log out?", AlertTypes.Warning); 45 | if (confirmed) 46 | { 47 | ((MauiDemoAuthenticationStateProvider)AuthenticationStateProvider).Logout(); 48 | } 49 | } 50 | 51 | } -------------------------------------------------------------------------------- /Masa.Blazor.MauiDemo.Rcl/Pages/Shop/ProductDetail.razor: -------------------------------------------------------------------------------- 1 | @page "/shop/product/{id:guid}" 2 | @using Microsoft.JSInterop 3 | @inject ProductService ProductService 4 | @inject IPlatformIntegration PlatformIntegration 5 | @inject IJSRuntime JSRuntime 6 | @inject NavigationManager NavigationManager 7 | @inject PageStackNavController NavController 8 | @inject IPopupService PopupService 9 | @inherits PStackPageBase 10 | 11 | @(_product?.Name ?? "Product") 12 | 13 | 14 | 15 | 17 | 18 | 19 | 20 | 21 | mdi-share-outline 22 | 23 | 24 | 25 |
26 |
28 | 29 | $wechat 30 | 31 | WeChat 32 |
33 |
35 | 36 | mdi-link-variant 37 | 38 | Copy link 39 |
40 |
41 | 42 | 45 | Cancel 46 | 47 |
48 |
49 |
50 |
51 | 52 | @if (_product is not null) 53 | { 54 | 58 | 59 | 60 |
61 | 62 |
63 | mdi-currency-usd 64 | 65 | @_product.Price 66 | 67 |
68 |
69 | 71 | NEW 72 | 73 |
74 | @_product.Name 75 |
76 |
77 | 78 | 79 | 80 | 库存 81 | 82 | 83 | 现货 84 | 85 | 86 | 87 | 88 | 89 | 配送 90 | @if (_geoCoordinate != null) 91 | { 92 | 93 | mdi-map-marker 94 | @_geoCoordinate.Latitude, @_geoCoordinate.Longitude 95 | 96 | } 97 | 98 | 99 | 包邮 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 七天无理由退货 108 | 109 | 110 | $next 111 | 112 | 113 | 114 | 115 |
116 |
117 |

七天无理由退货

118 |

满足7天无理由退换货申请的前提下,包邮商品需要买家承担退货邮费,非包邮商品需要买家承担发货和退货邮费。

119 |
120 |
121 | 125 | Got it 126 | 127 |
128 |
129 |
130 |
131 | 132 | 133 | 134 | 135 | 137 | 138 | 139 | 140 | @_product.Brand 141 | 142 | @_availableForSale 件在售商品 143 | 144 | 朗诗德是一家专业致力于健康饮水设备的设计、开发、生产和销售的大型企业。公司通过了ISO9001、ISO14001、OHSAS18001认证;其产品均通过CCC、CE、CB等多项国内外权威认证,并获得国家卫生部门颁发的卫生批件及多项国内外专利,畅销海内外。 145 | 146 | 147 | 148 | 149 | 150 | @_product.Description 151 | 152 | 153 | @if (_relatedProducts.Count > 0) 154 | { 155 |
156 |

相关产品

157 | @Shop.RenderProductList(_relatedProducts) 158 |
159 | } 160 | 161 | 163 | 164 |
165 | } 166 | 167 | 168 | @code { 169 | [Parameter] public Guid Id { get; set; } 170 | 171 | private Product? _product; 172 | private int _page = 1; 173 | private GeoCoordinate? _geoCoordinate; 174 | 175 | private bool _returnGoodsSheet; 176 | 177 | private List _relatedProducts = []; 178 | private int _availableForSale = 0; 179 | 180 | protected override async Task OnAfterRenderAsync(bool firstRender) 181 | { 182 | if (firstRender) 183 | { 184 | _ = Task.Run(async () => 185 | { 186 | await InvokeAsync(async () => 187 | { 188 | _geoCoordinate = await PlatformIntegration.GetCurrentCoordinateAsync(); 189 | StateHasChanged(); 190 | }); 191 | }); 192 | 193 | _product = await ProductService.GetProductAsync(Id); 194 | 195 | _availableForSale = await ProductService.GetAvailableForSaleAsync(_product.Brand); 196 | 197 | StateHasChanged(); 198 | } 199 | } 200 | 201 | private async Task LoadRelatedProducts(InfiniteScrollLoadEventArgs args) 202 | { 203 | if (_product == null) 204 | { 205 | return; 206 | } 207 | 208 | var appendProducts = await ProductService.GetProductsAsync(_page++, 5, category: _product.Category); 209 | _relatedProducts.AddRange(appendProducts); 210 | 211 | args.Status = appendProducts.Count < 5 ? InfiniteScrollLoadStatus.Empty : InfiniteScrollLoadStatus.Ok; 212 | } 213 | 214 | private void CopyLink() 215 | { 216 | _ = JSRuntime.InvokeVoidAsync(JsInteropConstants.CopyText, NavigationManager.Uri); 217 | _ = PopupService.EnqueueSnackbarAsync("Link has been copied to clipboard."); 218 | } 219 | 220 | private void ShareToWeChat() 221 | { 222 | _ = PopupService.EnqueueSnackbarAsync("Share to WeChat is not implemented yet."); 223 | } 224 | 225 | } -------------------------------------------------------------------------------- /Masa.Blazor.MauiDemo.Rcl/Pages/Shop/Search.razor: -------------------------------------------------------------------------------- 1 | @page "/shop/search" 2 | @inject IPopupService PopupService 3 | @inject ProductService ProductService 4 | @inherits PStackPageBase 5 | 6 | 8 | 9 | 10 | $goBack 11 | 12 | 13 | 26 | 27 | 34 | Search 35 | 36 | 37 | 38 | 39 | @if (_products != null) 40 | { 41 |
42 | @Shop.RenderProductList(_products) 43 | 44 | @if (_products.Count >= FirstLoadPageSize) 45 | { 46 | 48 | 49 | } 50 |
51 | } 52 | else if (_searchHistory.Count > 0) 53 | { 54 |
55 |
56 | 57 | Search history 58 | 59 | 60 | mdi-trash-can-outline 61 | 62 |
63 | 64 |
65 | @foreach (var item in _searchHistory) 66 | { 67 | @*Href ?*@ 68 | @item 69 | } 70 |
71 |
72 | } 73 | 74 | @code { 75 | [Inject] private LocalStorage LocalStorage { get; set; } = null!; 76 | 77 | [SupplyParameterFromQuery] private string? Key { get; set; } 78 | 79 | [SupplyParameterFromQuery] private string? Placeholder { get; set; } 80 | 81 | private const int FirstLoadPageSize = 10; 82 | 83 | private string? _searchKey; 84 | private List _searchHistory = []; 85 | private List? _products; 86 | private int _page = 1; 87 | private int PageSize => _page == 1 ? FirstLoadPageSize : 5; 88 | 89 | protected override async Task OnAfterRenderAsync(bool firstRender) 90 | { 91 | if (firstRender) 92 | { 93 | var searchHistory = await LocalStorage.GetItemAsync>("search-history"); 94 | _searchHistory = searchHistory ?? []; 95 | 96 | if (!string.IsNullOrWhiteSpace(Key)) 97 | { 98 | _searchKey = Key; 99 | await HandleOnSearch(); 100 | } 101 | 102 | StateHasChanged(); 103 | } 104 | } 105 | 106 | private async Task HandleOnSearch() 107 | { 108 | if (string.IsNullOrWhiteSpace(_searchKey)) 109 | { 110 | _searchKey = Placeholder; 111 | } 112 | 113 | if (string.IsNullOrWhiteSpace(_searchKey)) 114 | { 115 | return; 116 | } 117 | 118 | _page = 1; 119 | _products = await ProductService.GetProductsAsync(_page, PageSize, _searchKey); 120 | _page++; 121 | 122 | if (_searchHistory.Contains(_searchKey)) 123 | { 124 | _searchHistory.Remove(_searchKey); 125 | } 126 | 127 | _searchHistory.Insert(0, _searchKey); 128 | if (_searchHistory.Count > 10) 129 | { 130 | _searchHistory = _searchHistory.Take(10).ToList(); 131 | } 132 | 133 | _ = LocalStorage.SetItemAsync("search-history", _searchHistory); 134 | } 135 | 136 | private async Task SearchProducts(InfiniteScrollLoadEventArgs args) 137 | { 138 | _page++; 139 | var appendProducts = await ProductService.GetProductsAsync(_page, PageSize, _searchKey); 140 | _products ??= new(); 141 | _products.AddRange(appendProducts); 142 | args.Status = appendProducts.Count < PageSize ? InfiniteScrollLoadStatus.Empty : InfiniteScrollLoadStatus.Ok; 143 | } 144 | 145 | private async Task SearchByTag(string tag) 146 | { 147 | _searchKey = tag; 148 | await HandleOnSearch(); 149 | } 150 | 151 | private async Task ClearSearchHistory() 152 | { 153 | var confirmed = await PopupService.ConfirmAsync("Delete all search history?", AlertTypes.Error); 154 | if (confirmed) 155 | { 156 | _searchHistory.Clear(); 157 | _ = LocalStorage.RemoveItemAsync("search-history"); 158 | } 159 | } 160 | 161 | private void HandleOnInput(string val) 162 | { 163 | if (string.IsNullOrWhiteSpace(val)) 164 | { 165 | _products = null; 166 | } 167 | } 168 | 169 | } -------------------------------------------------------------------------------- /Masa.Blazor.MauiDemo.Rcl/Pages/Shop/Shop.razor: -------------------------------------------------------------------------------- 1 | @page "/" 2 | @inject ProductService ProductService 3 | @inject PageStackNavController NavController 4 | 5 | Shop 6 | 7 |
8 | 11 |
13 | 21 | 22 | 30 | Search 31 | 32 |
33 |
34 | 35 | 37 | @RenderProductList(_products) 38 | 39 | 42 | 43 | 44 |
45 | 46 | @code 47 | { 48 | internal static RenderFragment RenderProductList(List products) => __builder => 49 | { 50 | 51 | @foreach (var item in products) 52 | { 53 | 54 | 58 | 61 | 62 |
63 | 65 | NEW 66 | 67 |
68 | @item.Name 69 |
70 |
71 |
72 | mdi-currency-usd 73 | 74 | @item.Price 75 | 76 |
77 |
78 |
79 | } 80 |
81 | }; 82 | 83 | private int _page = 1; 84 | private string? _pinnedSearch; 85 | private MInfiniteScroll? _infiniteScroll; 86 | 87 | private List _products = []; 88 | 89 | protected override void OnInitialized() 90 | { 91 | base.OnInitialized(); 92 | 93 | _pinnedSearch = new[] { "饮水机", "纯水机", "净化器" }.ElementAt(Random.Shared.Next(0, 2)); 94 | } 95 | 96 | 97 | private async Task HandleOnLoad(InfiniteScrollLoadEventArgs args) 98 | { 99 | var isFirstLoad = _page == 1; 100 | var pageSize = isFirstLoad ? 10 : 5; 101 | var appendProducts = await ProductService.GetProductsAsync(_page++, pageSize); 102 | 103 | if (isFirstLoad) 104 | { 105 | _products = appendProducts; 106 | _page++; 107 | } 108 | else 109 | { 110 | _products.AddRange(appendProducts); 111 | } 112 | 113 | args.Status = appendProducts.Count < pageSize ? InfiniteScrollLoadStatus.Empty : InfiniteScrollLoadStatus.Ok; 114 | } 115 | 116 | private async Task HandleOnRefresh() 117 | { 118 | _page = 1; 119 | 120 | if (_infiniteScroll != null) 121 | { 122 | await _infiniteScroll.ResetAsync(); 123 | } 124 | } 125 | 126 | private void HandleOnTextFieldClick() 127 | { 128 | NavController.Push($"/shop/search?placeholder={_pinnedSearch}"); 129 | } 130 | } 131 | -------------------------------------------------------------------------------- /Masa.Blazor.MauiDemo.Rcl/Pages/Todo/EditSheet.razor: -------------------------------------------------------------------------------- 1 | @inject IPopupService PopupService 2 | 3 | 4 | 10 | 11 | 12 | 17 | 18 | 19 |
20 | 24 | mdi-calendar-today-outline 25 | @GetAliasOfDueAt(_currentTask.DueAt) 26 | 27 | 28 | 29 | 34 | mdi-flag-variant-outline 35 | @_currentTask.Priority 36 | 37 | 38 | 39 | 40 | @foreach (var (priority, color) in Priorities) 41 | { 42 | 43 | mdi-flag-variant-outline 44 | @priority 45 | 46 | } 47 | 48 | 49 | 50 | 51 | 52 | 53 | 56 | mdi-tag-text-outline 57 | 58 | 59 | 60 | 61 | 64 | @foreach (var item in Tags) 65 | { 66 | 67 | @RenderFragments.GenTagItem(item) 68 | 69 | } 70 | 71 | 72 | 73 | 74 |
75 | 76 |
77 | @RenderFragments.GenTagChips(Tags, _currentTask.TagIds, small: true) 78 |
79 | 80 |
81 | @if (_isEdit) 82 | { 83 | 87 | mdi-delete-forever-outline 88 | 89 | } 90 | 91 | 96 | mdi-arrow-up-circle 97 | @(_isEdit ? "Update" : "Add") Task 98 | 99 |
100 |
101 | 102 | 104 | 105 | @code { 106 | 107 | [Parameter] public Func GetAliasOfDueAt { get; set; } 108 | 109 | [Parameter] public List Tags { get; set; } = new(); 110 | 111 | [Parameter] public EventCallback OnAdd { get; set; } 112 | 113 | [Parameter] public EventCallback OnUpdate { get; set; } 114 | 115 | [Parameter] public EventCallback OnDelete { get; set; } 116 | 117 | [Parameter] public string? Attach { get; set; } 118 | 119 | private static List<(TodoTaskPriority priority, string? color)> Priorities = 120 | [ 121 | (TodoTaskPriority.High, "red"), 122 | (TodoTaskPriority.Medium, "orange"), 123 | (TodoTaskPriority.Low, "blue"), 124 | (TodoTaskPriority.Default, null) 125 | ]; 126 | 127 | private bool _show; 128 | private bool _calendarSheet; 129 | private MForm _form = null!; 130 | private bool _isEdit; 131 | 132 | private TodoTask _currentTask = new(); 133 | 134 | private string? PriorityColor => _currentTask.Priority switch 135 | { 136 | TodoTaskPriority.High => "red", 137 | TodoTaskPriority.Medium => "orange", 138 | TodoTaskPriority.Low => "blue", 139 | _ => null 140 | }; 141 | 142 | private async Task HandleOnAddOrUpdate() 143 | { 144 | var action = _isEdit ? OnUpdate : OnAdd; 145 | await action.InvokeAsync(_currentTask); 146 | _show = false; 147 | } 148 | 149 | private async Task DeleteTask() 150 | { 151 | var confirmed = await PopupService.ConfirmAsync("Delete task", $"Are you sure to delete task '{_currentTask.Title}'?"); 152 | if (confirmed) 153 | { 154 | await OnDelete.InvokeAsync(_currentTask); 155 | _show = false; 156 | } 157 | } 158 | 159 | internal void Open() 160 | { 161 | _isEdit = false; 162 | _currentTask = new(); 163 | _show = true; 164 | } 165 | 166 | internal void Open(TodoTask task) 167 | { 168 | _isEdit = true; 169 | _currentTask = (TodoTask)task.ShallowCopy(); 170 | _show = true; 171 | } 172 | 173 | internal void Hide() 174 | { 175 | _isEdit = false; 176 | _show = false; 177 | } 178 | 179 | } -------------------------------------------------------------------------------- /Masa.Blazor.MauiDemo.Rcl/Pages/Todo/Todo.razor: -------------------------------------------------------------------------------- 1 | @page "/todo/{filter?}" 2 | @page "/todo/tag/{tag:int}" 3 | @inject ProDatabase ProDatabase 4 | @inject NavigationManager NavigationManager 5 | @inject PageStackNavController PageStackNavController 6 | @inject IPopupService PopupService 7 | @using Masa.Blazor.Presets.PageStack.NavController 8 | @implements IDisposable 9 | 10 | To do 11 | 12 | 16 | 17 | @Title 18 | 19 | @if (CurrentTag != null) 20 | { 21 | 22 | 23 | 24 | mdi-dots-horizontal 25 | 26 | 27 | 28 | 29 | 30 | 31 | mdi-delete-forever-outline 32 | Delete tag 33 | 34 | 35 | 36 | 37 | 38 | } 39 | 40 | 41 |
42 | 44 | @foreach (var item in _tasks.GroupBy(t => t.Priority).OrderByDescending(t => t.Key)) 45 | { 46 | 47 | @item.Key 48 | 49 | 50 | @foreach (var task in item) 51 | { 52 | 53 | 54 | 59 | 60 | 61 | @task.Title 62 | @if (!string.IsNullOrWhiteSpace(task.Description)) 63 | { 64 | @task.Description 65 | } 66 | @if (task.TagIds.Length > 0) 67 | { 68 |
69 | @RenderFragments.GenTagChips(_tags, task.TagIds, xSmall: true) 70 |
71 | } 72 |
@GetAliasOfDueAt(task.DueAt)
73 |
74 |
75 | } 76 |
77 |
78 |
79 | } 80 | 81 | @if (_completedTasks.Count > 0) 82 | { 83 | 84 | Completed 85 | 86 | 87 | @foreach (var task in _completedTasks) 88 | { 89 | 92 | 93 | 97 | 98 | 99 | @task.Title 100 | @if (!string.IsNullOrWhiteSpace(task.Description)) 101 | { 102 | @task.Description 103 | } 104 | @if (task.TagIds.Length > 0) 105 | { 106 |
107 | @RenderFragments.GenTagChips(_tags, task.TagIds, xSmall: true) 108 |
109 | } 110 |
@GetAliasOfDueAt(task.DueAt)
111 |
112 |
113 | } 114 |
115 |
116 |
117 | } 118 |
119 |
120 | 121 | 124 | mdi-plus 125 | 126 | 127 | 133 | 134 | 135 | 138 | 139 | 140 | 148 | 149 | 154 | @foreach (var color in colors) 155 | { 156 | 157 | 160 | mdi-circle 161 | 162 | 163 | } 164 | 165 | 170 | Add new tag 171 | 172 | 173 | 174 | @code { 175 | 176 | [Parameter] public string? Filter { get; set; } 177 | 178 | [Parameter] public int Tag { get; set; } 179 | 180 | private static string[] colors = ["red", "orange", "blue", "green", "purple", "pink", "yellow"]; 181 | 182 | private bool? _drawer = false; 183 | private string? _prevFilter; 184 | private int _prevTag; 185 | 186 | private EditSheet _taskSheet = default!; 187 | 188 | private bool _tagSheet; 189 | 190 | private TodoTag _newTag = new(); 191 | 192 | private int _page = 1; 193 | private int _pageSize = 10; 194 | private List _tasks = new(); 195 | private List _completedTasks = new(); 196 | private List _tags = new(); 197 | 198 | private List _expandedPriority = [0, 1, 2, 3, -1]; 199 | 200 | private TodoTag? CurrentTag => _tags.FirstOrDefault(t => t.Id == Tag); 201 | 202 | private string Title => CurrentTag?.Name ?? Filter ?? "Today"; 203 | 204 | protected override void OnInitialized() 205 | { 206 | base.OnInitialized(); 207 | 208 | PageStackNavController.TabChanged += PageStackNavControllerOnTabChanged; 209 | } 210 | 211 | private void PageStackNavControllerOnTabChanged(object? sender, PageStackTabChangedEventArgs e) 212 | { 213 | if (e.IsMatch("/todo")) 214 | { 215 | return; 216 | } 217 | 218 | // Hide sheets when navigating to other tabs (for example, go back by the system back button) 219 | _taskSheet.Hide(); 220 | _tagSheet = false; 221 | InvokeAsync(StateHasChanged); 222 | } 223 | 224 | protected override async Task OnAfterRenderAsync(bool firstRender) 225 | { 226 | if (firstRender) 227 | { 228 | _prevFilter = Filter; 229 | _prevTag = Tag; 230 | 231 | await LoadTasks(); 232 | await LoadTags(); 233 | StateHasChanged(); 234 | } 235 | } 236 | 237 | protected override async Task OnParametersSetAsync() 238 | { 239 | if (_prevFilter != Filter || _prevTag != Tag) 240 | { 241 | _prevFilter = Filter; 242 | _prevTag = Tag; 243 | await LoadTasks(); 244 | } 245 | } 246 | 247 | private async Task LoadTasks() 248 | { 249 | List tasks; 250 | 251 | if (Tag != 0) 252 | { 253 | tasks = await ProDatabase.GetTasksAsync(_page, _pageSize, tag: Tag); 254 | } 255 | else 256 | { 257 | tasks = Filter?.ToLowerInvariant() switch 258 | { 259 | "inbox" => await ProDatabase.GetTasksAsync(_page, _pageSize), 260 | _ => await ProDatabase.GetTasksAsync(_page, _pageSize, DateTime.Today) 261 | }; 262 | } 263 | 264 | _tasks = tasks.Where(t => !t.Completed).ToList(); 265 | _completedTasks = tasks.Where(t => t.Completed).ToList(); 266 | } 267 | 268 | private async Task LoadTags() 269 | { 270 | _tags = await ProDatabase.GetTagsAsync(); 271 | } 272 | 273 | private async Task HandleOnSubmit(TodoTask model) 274 | { 275 | await ProDatabase.CreateTaskAsync(model); 276 | await LoadTasks(); 277 | } 278 | 279 | private async Task HandleOnUpdate(TodoTask model) 280 | { 281 | await ProDatabase.UpdateTaskAsync(model); 282 | await LoadTasks(); 283 | } 284 | 285 | private async Task HandleOnDelete(TodoTask model) 286 | { 287 | await ProDatabase.DeleteTaskAsync(model); 288 | await LoadTasks(); 289 | } 290 | 291 | private bool IsExpired(DateTime dueAt) => dueAt < DateTime.Today; 292 | 293 | private string GetAliasOfDueAt(DateTime dueAt) 294 | { 295 | var dateStr = GetDateStr(); 296 | return dueAt.TimeOfDay == TimeSpan.Zero 297 | ? dateStr 298 | : $"{dateStr}, {dueAt.ToShortTimeString()}"; 299 | 300 | string GetDateStr() 301 | { 302 | var today = DateTime.Today; 303 | var dateOfDue = dueAt.Date; 304 | if (dateOfDue == today) 305 | { 306 | return "Today"; 307 | } 308 | 309 | if (dateOfDue == today.AddDays(-1)) 310 | { 311 | return "Yesterday"; 312 | } 313 | 314 | if (dateOfDue == today.AddDays(1)) 315 | { 316 | return "Tomorrow"; 317 | } 318 | 319 | if (dateOfDue.Year == DateTime.Today.Year) 320 | { 321 | return dateOfDue.ToString("M"); 322 | } 323 | 324 | return dateOfDue.ToShortDateString(); 325 | } 326 | } 327 | 328 | private async Task CompleteTask(TodoTask task) 329 | { 330 | if (task.Completed) 331 | { 332 | task.Completed = false; 333 | _tasks.Add(task); 334 | _completedTasks.Remove(task); 335 | } 336 | else 337 | { 338 | task.Completed = true; 339 | _completedTasks.Add(task); 340 | _tasks.Remove(task); 341 | } 342 | 343 | await ProDatabase.UpdateTaskAsync(task); 344 | } 345 | 346 | private void ShowAddTagSheet() 347 | { 348 | _drawer = false; 349 | _tagSheet = true; 350 | } 351 | 352 | private void OnTagNameChanged(string val) 353 | { 354 | _newTag.Name = val.Trim(',').Trim(' ').Trim(';'); 355 | } 356 | 357 | private async Task HandleOnAddTag() 358 | { 359 | await ProDatabase.CreateTagAsync(_newTag); 360 | _tags.Add(_newTag); 361 | 362 | _newTag = new(); 363 | _tagSheet = false; 364 | } 365 | 366 | private void EditTask(TodoTask task) 367 | { 368 | _taskSheet.Open(task); 369 | } 370 | 371 | private async Task DeleteTag(TodoTag model) 372 | { 373 | var confirm = await PopupService.ConfirmAsync("Delete tag", "Are you sure you want to delete this tag?"); 374 | if (!confirm) 375 | { 376 | return; 377 | } 378 | 379 | await ProDatabase.DeleteTagAsync(model); 380 | 381 | _ = PopupService.EnqueueSnackbarAsync("Tag deleted", AlertTypes.Success); 382 | 383 | var tag = _tags.FirstOrDefault(t => t.Id == model.Id); 384 | if (tag != null) 385 | { 386 | _tags.Remove(tag); 387 | } 388 | 389 | NavigationManager.NavigateTo("/todo"); 390 | } 391 | 392 | public void Dispose() 393 | { 394 | PageStackNavController.TabChanged -= PageStackNavControllerOnTabChanged; 395 | } 396 | 397 | } -------------------------------------------------------------------------------- /Masa.Blazor.MauiDemo.Rcl/Pages/Todo/TodoNav.razor: -------------------------------------------------------------------------------- 1 |  6 | @if (ShowAddTask) 7 | { 8 | Add Task 9 | } 10 | 11 | 12 | @foreach (var item in _categories) 13 | { 14 | 17 | 18 | 19 | @item.IconOrColor 20 | 21 | 22 | @item.Title 23 | 24 | 25 | 26 | } 27 | 28 | Tags 29 | 30 | @foreach (var item in Tags) 31 | { 32 | 33 | @RenderFragments.GenTagItem(item) 34 | 35 | } 36 | 37 | 42 | mdi-plus 43 | Add Tag 44 | 45 | 46 | 47 | 48 | 49 | @code { 50 | 51 | [Parameter] public bool? Show { get; set; } 52 | 53 | [Parameter] public EventCallback ShowChanged { get; set; } 54 | 55 | [Parameter] public bool Permanent { get; set; } 56 | 57 | [Parameter] public bool ShowAddTask { get; set; } 58 | 59 | [Parameter] public EventCallback OnAddTagClick { get; set; } 60 | 61 | [Parameter] public List Tags { get; set; } = []; 62 | 63 | private static TodoNavItem[] _categories = 64 | [ 65 | new TodoNavItem("Today", "mdi-calendar-today-outline", "/todo"), 66 | new TodoNavItem("Inbox", "mdi-inbox-outline", "/todo/inbox") 67 | ]; 68 | 69 | record TodoNavItem(string Title, string IconOrColor, string Href); 70 | 71 | } -------------------------------------------------------------------------------- /Masa.Blazor.MauiDemo.Rcl/Pages/User.razor: -------------------------------------------------------------------------------- 1 | @page "/user" 2 | @inject MasaBlazor MasaBlazor 3 | 4 | User 5 | 6 | 9 | 10 | 11 | 15 | 16 | 17 | MASA Team 18 | 19 | 24 | mdi-cog-outline 25 | 26 | 27 | 28 | 29 |
30 | 31 |
32 |
34 |
35 | 36 | 37 | 38 |
39 | mdi-account-multiple-outline 40 | 270 followers 41 |
42 |
43 | mdi-map-marker-outline 44 | Hangzhou, China 45 |
46 |
47 | mdi-link-variant 48 | masastack.com 49 |
50 |
51 | mdi-file-document-outline 52 | docs.masastack.com 53 |
54 |
55 |
56 | @foreach (var repo in Repos) 57 | { 58 | 59 | 60 | 61 | @repo.Title 62 | 63 | 64 | 65 |

66 | @repo.Description 67 |

68 |
69 | 70 | 72 | mdi-circle 73 | @repo.Language 74 | 75 | 76 | mdi-star-outline @repo.Stars 77 | 78 | 79 | mdi-source-fork @repo.Forks 80 | 81 |
82 |
83 |
84 | } 85 |
86 | 87 | @code { 88 | 89 | private static readonly Repo[] Repos = 90 | [ 91 | new("MASA.Framework", "https://github.com/masastack/MASA.Framework", "Provide open, community driven reusable components for building modern applications. These components will be used by the MASA Stack and MASA Labs projects.", "C#", 679, 106), 92 | new("MASA.Blazor", "https://github.com/masastack/MASA.Blazor", "Blazor UI component library based on Material Design. Support Blazor Server, Blazor WebAssembly and MAUI Blazor.", "C#", 1100, 140), 93 | new("MASA.Blazor.Pro", "https://github.com/masastack/MASA.Blazor.Pro", "Material design admin template for blazor.", "HTML", 219, 47), 94 | new("MASA.Auth", "https://github.com/masastack/MASA.Auth", "MASA Auth是MASA Stack中最核心的功能之一,它统一负责了所有产品的权限、菜单、用户等。它包含了单点登录、用户管理、RBAC3、第三方平台接入、Ldap等企业级功能。除了可以用在企业内部管理系统,它还可以帮助管理C端用户", "C#", 70, 14), 95 | new("MASA.MC", "https://github.com/masastack/MASA.MC", "MASA Stack 1.0 系统合集中底层支持消息发送的一款综合性产品,担任了全局消息系统支持多渠道的配置与消息发送规则的配置,并且可以配置多种消息模板以及特定用户组群。可以与关联产品Alert、TSC等对接,一站式解决故障问题触发与处理", "C#", 17, 5), 96 | ]; 97 | 98 | private static readonly Dictionary LanguageColors = new() 99 | { 100 | { "C#", "green" }, 101 | { "HTML", "red" } 102 | }; 103 | 104 | record Repo(string Title, string Href, string Description, string Language, int Stars, int Forks); 105 | 106 | } -------------------------------------------------------------------------------- /Masa.Blazor.MauiDemo.Rcl/RenderFragments.cs: -------------------------------------------------------------------------------- 1 | using Masa.Blazor.MauiDemo.Rcl.Models; 2 | using Microsoft.AspNetCore.Components; 3 | 4 | namespace Masa.Blazor.MauiDemo; 5 | 6 | public static class RenderFragments 7 | { 8 | public static RenderFragment GenTagItem(TodoTag tag) => builder => 9 | { 10 | builder.OpenComponent(0, typeof(MListItemIcon)); 11 | builder.AddAttribute(1, "Class", "mr-4"); 12 | builder.AddAttribute(2, "ChildContent", (RenderFragment)(sb => 13 | { 14 | sb.OpenComponent(0, typeof(MIcon)); 15 | sb.AddAttribute(1, "Icon", (Icon)"mdi-circle"); 16 | sb.AddAttribute(2, "Color", tag.Color); 17 | sb.CloseComponent(); 18 | })); 19 | builder.CloseComponent(); 20 | 21 | builder.OpenComponent(3); 22 | builder.AddAttribute(4, "ChildContent", (RenderFragment)(sb => 23 | { 24 | sb.OpenComponent(0); 25 | sb.AddAttribute(1, "ChildContent", (RenderFragment)(c => c.AddContent(0, tag.Name))); 26 | sb.CloseComponent(); 27 | })); 28 | builder.CloseComponent(); 29 | }; 30 | 31 | public static RenderFragment GenTagChips( 32 | IEnumerable allTags, 33 | int[] shownTagIds, 34 | bool small = false, 35 | bool xSmall = false) => builder => 36 | { 37 | foreach (var tag in allTags) 38 | { 39 | if (!shownTagIds.Contains(tag.Id)) continue; 40 | 41 | builder.OpenRegion(tag.GetHashCode()); 42 | builder.OpenComponent(0, typeof(MChip)); 43 | builder.AddAttribute(1, "Color", tag.Color); 44 | builder.AddAttribute(2, "Dark", true); 45 | builder.AddAttribute(3, "Small", small); 46 | builder.AddAttribute(4, "XSmall", xSmall); 47 | builder.AddAttribute(5, "Label", true); 48 | builder.AddAttribute(6, "Class", "mr-1"); 49 | builder.AddAttribute(7, "ChildContent", (RenderFragment)(sb => { sb.AddContent(0, tag.Name); })); 50 | builder.CloseComponent(); 51 | builder.CloseRegion(); 52 | } 53 | }; 54 | } -------------------------------------------------------------------------------- /Masa.Blazor.MauiDemo.Rcl/ServiceCollectionExtensions.cs: -------------------------------------------------------------------------------- 1 | using Masa.Blazor; 2 | using Masa.Blazor.MauiDemo.Rcl.Auth; 3 | using Masa.Blazor.MauiDemo.Rcl.Services; 4 | using Masa.Blazor.Presets; 5 | using Microsoft.AspNetCore.Components.Authorization; 6 | 7 | namespace Microsoft.Extensions.DependencyInjection; 8 | 9 | public static class ServiceCollectionExtensions 10 | { 11 | public static IServiceCollection AddMasaBlazorMauiDemo(this IServiceCollection services, 12 | ServiceLifetime masaBlazorServiceLifetime = ServiceLifetime.Scoped) 13 | { 14 | services.AddMasaBlazor(options => 15 | { 16 | options.Defaults = new Dictionary?>() 17 | { 18 | ["MBottomSheet"] = new Dictionary() 19 | { 20 | ["ContentClass"] = "pa-4" 21 | }, 22 | ["MButton"] = new Dictionary() 23 | { 24 | [nameof(MButton.Depressed)] = true 25 | }, 26 | ["MImage"] = new Dictionary() 27 | { 28 | // https://github.com/masastack/MASA.Blazor/issues/1624 29 | [nameof(MImage.Eager)] = OperatingSystem.IsAndroid() || OperatingSystem.IsIOS() 30 | }, 31 | ["MSheet"] = new Dictionary() 32 | { 33 | ["Rounded"] = (StringBoolean)true, 34 | }, 35 | ["MTextField"] = new Dictionary() 36 | { 37 | ["Filled"] = true, 38 | ["Rounded"] = true, 39 | ["PersistentPlaceholder"] = true 40 | }, 41 | ["MTextarea"] = new Dictionary() 42 | { 43 | ["Filled"] = true, 44 | ["Rounded"] = true, 45 | ["PersistentPlaceholder"] = true 46 | }, 47 | [PopupComponents.SNACKBAR] = new Dictionary() 48 | { 49 | { nameof(PEnqueuedSnackbars.Closeable), true }, 50 | { nameof(PEnqueuedSnackbars.Text), true } 51 | }, 52 | ["PStackPageBarInit"] = new Dictionary() 53 | { 54 | { nameof(PStackPageBarInit.CenterTitle), true }, 55 | { nameof(PStackPageBarInit.Flat), true } 56 | } 57 | }; 58 | options.ConfigureTheme(theme => 59 | { 60 | theme.Themes.Light.Primary = "#4f33ff"; 61 | theme.Themes.Light.Secondary = "#5e5c71"; 62 | // theme.Themes.Light.Accent = "#006C4F"; 63 | theme.Themes.Light.Error = "#BA1A1A"; 64 | theme.Themes.Light.Surface = "#f0f3fa"; 65 | theme.Themes.Light.OnSurface = "#1C1B1F"; 66 | theme.Themes.Light.InverseSurface = "#131316"; 67 | theme.Themes.Light.InverseOnSurface = "#C9C5CA"; 68 | theme.Themes.Light.InversePrimary = "#C5C0FF"; 69 | 70 | theme.Themes.Dark.Primary = "#C5C0FF"; 71 | // theme.Themes.Dark.OnPrimary = "#090029"; 72 | theme.Themes.Dark.Secondary = "#C7C4DC"; 73 | theme.Themes.Dark.OnSecondary = "#302E42"; 74 | theme.Themes.Dark.Accent = "#67DBAF"; 75 | theme.Themes.Dark.OnAccent = "#003827"; 76 | theme.Themes.Dark.Error = "#FFB4AB"; 77 | theme.Themes.Dark.OnError = "#690005"; 78 | theme.Themes.Dark.Surface = "#131316"; 79 | theme.Themes.Dark.OnSurface = "#C9C5CA"; 80 | theme.Themes.Dark.InverseOnSurface = "#1C1B1F"; 81 | theme.Themes.Dark.InversePrimary = "#4f33ff"; 82 | }); 83 | options.ConfigureIcons(IconSet.MaterialDesignIcons, aliases => 84 | { 85 | aliases.UserDefined["wechat"] = new SvgPath( 86 | "M9.5,4C5.36,4 2,6.69 2,10C2,11.89 3.08,13.56 4.78,14.66L4,17L6.5,15.5C7.39,15.81 8.37,16 9.41,16C9.15,15.37 9,14.7 9,14C9,10.69 12.13,8 16,8C16.19,8 16.38,8 16.56,8.03C15.54,5.69 12.78,4 9.5,4M6.5,6.5A1,1 0 0,1 7.5,7.5A1,1 0 0,1 6.5,8.5A1,1 0 0,1 5.5,7.5A1,1 0 0,1 6.5,6.5M11.5,6.5A1,1 0 0,1 12.5,7.5A1,1 0 0,1 11.5,8.5A1,1 0 0,1 10.5,7.5A1,1 0 0,1 11.5,6.5M16,9C12.69,9 10,11.24 10,14C10,16.76 12.69,19 16,19C16.67,19 17.31,18.92 17.91,18.75L20,20L19.38,18.13C20.95,17.22 22,15.71 22,14C22,11.24 19.31,9 16,9M14,11.5A1,1 0 0,1 15,12.5A1,1 0 0,1 14,13.5A1,1 0 0,1 13,12.5A1,1 0 0,1 14,11.5M18,11.5A1,1 0 0,1 19,12.5A1,1 0 0,1 18,13.5A1,1 0 0,1 17,12.5A1,1 0 0,1 18,11.5Z"); 87 | aliases.UserDefined["apple"] = new SvgPath( 88 | "M18.71,19.5C17.88,20.74 17,21.95 15.66,21.97C14.32,22 13.89,21.18 12.37,21.18C10.84,21.18 10.37,21.95 9.1,22C7.79,22.05 6.8,20.68 5.96,19.47C4.25,17 2.94,12.45 4.7,9.39C5.57,7.87 7.13,6.91 8.82,6.88C10.1,6.86 11.32,7.75 12.11,7.75C12.89,7.75 14.37,6.68 15.92,6.84C16.57,6.87 18.39,7.1 19.56,8.82C19.47,8.88 17.39,10.1 17.41,12.63C17.44,15.65 20.06,16.66 20.09,16.67C20.06,16.74 19.67,18.11 18.71,19.5M13,3.5C13.73,2.67 14.94,2.04 15.94,2C16.07,3.17 15.6,4.35 14.9,5.19C14.21,6.04 13.07,6.7 11.95,6.61C11.8,5.46 12.36,4.26 13,3.5Z"); 89 | aliases.UserDefined["google"] = new SvgPath( 90 | "M21.35,11.1H12.18V13.83H18.69C18.36,17.64 15.19,19.27 12.19,19.27C8.36,19.27 5,16.25 5,12C5,7.9 8.2,4.73 12.2,4.73C15.29,4.73 17.1,6.7 17.1,6.7L19,4.72C19,4.72 16.56,2 12.1,2C6.42,2 2.03,6.8 2.03,12C2.03,17.05 6.16,22 12.25,22C17.6,22 21.5,18.33 21.5,12.91C21.5,11.76 21.35,11.1 21.35,11.1V11.1Z"); 91 | }); 92 | }, masaBlazorServiceLifetime); 93 | 94 | services.AddScoped(); 95 | services.AddScoped(); 96 | services.AddScoped(provider => 97 | provider.GetRequiredService()); 98 | services.AddAuthorizationCore(); 99 | services.AddCascadingAuthenticationState(); 100 | 101 | return services; 102 | } 103 | } -------------------------------------------------------------------------------- /Masa.Blazor.MauiDemo.Rcl/Services/ProductService.cs: -------------------------------------------------------------------------------- 1 | using Masa.Blazor.MauiDemo.Rcl.Models; 2 | 3 | namespace Masa.Blazor.MauiDemo.Rcl.Services; 4 | 5 | public class ProductService 6 | { 7 | // mock data 8 | private static List _data = 9 | [ 10 | new("GA506B 温热管线饮水机", 239.99, "img/product/15.jpg", "饮水机", 5, "LONSID", 11 | "独特内胆 省电节能 智能触控 时尚科技 人性化操作 界面易懂 电子童锁实用安全 水电自动分离 停水贴心保护。不锈钢内胆:永不生锈,加热快,健康安全、彩灯显示:彩色灯条显示,让喝水成为一种享受。自动停水:连续出水超过1分钟会自动停止出水,防止中途离开出现意外。"), 12 | new("朗诗德G3速热管线机", 339.99, "img/product/18.jpg", "饮水机", 5, "LONSID", 13 | "六重智能防护 高原模式 精准控温 硅胶密封设计 休眠模式。适配全通量纯水机:适配全通量纯水机没有频繁起跳的环境噪音,有桶、无桶纯水机都适用。即热即饮:采用速热技术,一次沸腾,避免千滚水。六档精准定制水温:进出水双NTC+可控硅调节精准控温,可满足六种水温需求:25℃常温、55℃泡奶、55℃暖胃温开水、75℃花茶、85℃红茶、95℃咖啡、自动休眠:60s内无任何操作,自动进入休眠模式,节能省电,夜晚不打扰、大小杯设置:小杯 150ml、大杯500ml,走开也能放心接。"), 14 | new("朗诗德牌厨下热饮机", 139.99, "img/product/19.jpg", "饮水机", 5, "LONSID", 15 | "纯净热饮,触手可得 多重用水防护,守护家人安全 智能灯环龙头,水温状态一目了然 厨下安装更省空间,体积小更美观 贴心童锁,防止烫伤 高原模式,轻松设置。净享热饮:告别传统烧水模式,打开龙头即可享受纯净热水,四档温度可供选择,满足家庭日常用水需求。多用重水防护:干烧保护,加热超时保护,缺水保护,守护家人安全。智能灯环龙头:常温水、热水对应不同灯环颜色。厨下安装更省空间:体积小更美观。"), 16 | new("G1管线饮水机", 539.99, "img/product/16.jpg", "饮水机", 5, "LONSID", 17 | "采用热胆加热方式 出水速度更快 选用优质热胆保温棉 PU材质 选用涂三防漆的PCB板 醒目的琴键式按压开关 可靠性高 可拆卸接水盒 清洗更方便。双重防干烧:温度过高、缺水烧水时,自动停止运行。随时掌握工作状态:动态彩灯实时显示,随时了解工作状态。琴键按压:醒目的琴键式按压开关,可靠性高,红色按压出热水,蓝色出温水。"), 18 | new("GT3桌面即热饮水机", 539.99, "img/product/17.jpg", "饮水机", 5, "LONSID", 19 | "童锁 防干烧 多重安全保护 五档温度 选择不同出水温度 3秒即热杜绝反复加热 3.2L大水箱满足全天候饮水需求 旋钮调温 取水操作方便。操作简便:旋钮调温取水,使用只需一旋一按,省去复杂操作步骤,简单易上手。3秒即热 :3秒即热,杜绝反复加热,畅饮鲜活水。多重保护:童锁、三重防干烧、缺水保护和智能缺水提醒,安全使用更安心。"), 20 | new("智能除醛空气净化器", 339.99, "img/product/30.jpg", "空净系列", 5, "LONSID", 21 | "优化进风口设计 配以先进的降噪系统 内置工业级球形轴承新科技操纵 高品质静音轮 有效降低转动噪音 隐藏式收纳线 收纳无忧 每一块滤网均内置RFID芯片。分体监控:分离式设计,一体式体验。语音智控:朗朗——语音控制。专效滤网定制:自由定制属于你的空气。"), 22 | new("智能语音车载净化器", 339.99, "img/product/31.jpg", "空净系列", 5, "LONSID", 23 | "商务典藏 科技创造 澎湃动力 双倍风压。商务典藏:商务典藏,科技创造。3.6分钟净化一遍:澎湃动力。双倍风压:双倍洁净效果。"), 24 | new("空气质量检测仪朗朗", 339.99, "img/product/32.jpg", "空净系列", 5, "LONSID", 25 | "完美视觉交互设计高清LED数字显示 智能语音模块 随时掌握空气状况 甲醛专业级电化学传感器 高精度温度、湿度传感器 内置独立颗粒物激光传感器。精准检测:PM2.5/甲醛/温湿度专业检测。随享智能:空气质量问问朗朗就知道,智能语音播报,不再是冰冷的机器,而是温暖的陪伴。高清LED数显:让每一次呼吸都一目了然。"), 26 | new("RO一体开水机(双龙头)", 339.99, "img/product/24.jpg", "商务机", 5, "LONSID", 27 | "五级净化 层层过滤 热水随取随有 使用便利 一体式设计 节省空间 加大内胆 持续供应热水。五级净化:五级净化,科学组合净化工艺。步进式加热:真正获得随取随有的热水。一体式设计:节省空间。加大内胆:配备50L热罐,可持续供应热水"), 28 | new("悦纯系列商用纯水机", 339.99, "img/product/25.jpg", "商务机", 5, "LONSID", 29 | "超静音制水系统、五级过滤、制水系统高回收率、高效杀菌。自动排空功能:节假日自动排空功能,保证饮水健康卫生。高回收率:制水系统高回收率,回收率大于50%。超静音制水系统:整机噪音小于50dB,强力静音,悦享健康。冷阴极UV杀菌:更安全,更卫生"), 30 | new("智爱系列商用直饮机", 339.99, "img/product/27.jpg", "商务机", 5, "LONSID", 31 | "超静音制水系统、五级过滤、制水系统高回收率、高效杀菌。多重安全保护:防漏电、防缺水、防干烧、防烫伤。智能显示:制水时间监控、滤芯寿命显示,IMD电控显示面板,机器运行状态一目了然。高回收率:制水系统高回收率,回收率大于50%。超静音制水系统:整机噪音小于50dB,强力静音,悦享健康。冷阴极UV杀菌:饮水更安全,更卫生"), 32 | new("净雅系列商用直饮机", 339.99, "img/product/28.jpg", "商务机", 5, "LONSID", 33 | "超静音制水系统、五级过滤、制水系统高回收率、高效杀菌。冷阴极UV杀菌(净雅500):更安全,更卫生。超静音制水系统:整机噪音小于50dB,强力静音,悦享健康。高回收率:制水系统高回收率,回收率大于50%。多方监控:制水时间监控、滤芯寿命显示,IMD电控显示面板,机器运行状态一目了然。多重安全保护:防漏电、防缺水、防干烧、防烫伤。UVC-LED抑菌(净雅100):安全卫生"), 34 | new("豪华商用纯水机", 339.99, "img/product/26.jpg", "商务机", 5, "LONSID", 35 | "五级净化 层层过滤、高效反渗透RO膜技术 纳米级别净化、大流量出水 适合公共场所、奢华品质 精致细节。五级净化:五级净化,科学组合净化工艺。高效反渗透膜:高效反渗透RO膜过滤技术,有效过滤有害物质。大流量出水:大流量出水,满足多人。精致细节:精致仪表盘,及时掌控机器状态。"), 36 | new("智爱100商用直饮机", 339.99, "img/product/29.jpg", "商务机", 5, "LONSID", 37 | "超静音制水系统、五级过滤、制水系统高回收率、高温杀菌。开水热交换系统:饮水更安全,更卫生、多重安全保护:防漏电、防缺水、防干烧、防烫伤。智能显示:制水时间监控滤芯寿命显示,IMD电控显示面板,机器运行状态一目了然。高回收率:制水系统高回收率,回收率大于50%。超静音制水系统:整机噪音小于50dB,强力静音,悦享健康。"), 38 | new("饮立方", 339.99, "img/product/20.jpg", "胶囊机", 5, "LONSID", 39 | "纤薄机身,小巧免安装,通电即用。智能精准,定义不同饮品最佳口感。茶胶囊可多次冲泡,家人朋友聚会齐分享。胶囊结构充氮和隔氧保鲜设计,久置冲泡亦可即刻感受香醇。高原模式,系统自动调节冲泡条件,保障不同地区使用的最佳口感。纤薄小巧:小巧免安装 随心放。智能提醒:冲泡状态一目了然。高原模式:高原模式。一茶多泡:一茶多泡 经济实惠。保持新鲜:告别变质 独立封装"), 40 | new("饮立方PLUS", 339.99, "img/product/21.jpg", "胶囊机", 5, "LONSID", 41 | "复合滤芯防伪设计。三机一体,定义智饮新时代。饮品智能扫码萃取,精准定义最佳口感。胶囊结构充氮和隔氧保鲜设计,即刻感受饮品香醇。茶胶囊可多次冲泡,家人朋友聚会齐分享。 保持新鲜:告别变质 独立封装。一茶多泡:一茶多泡 经济实惠。智能识别:智能识别最佳冲泡条件。"), 42 | new("云湃智能物联纯水机", 339.99, "img/product/8.jpg", "纯水机", 5, "LONSID", 43 | "3合1一体式专利复合滤芯、智能检测 安全防漏、超静音制水系统。深层过滤:专利复合滤芯和RO反渗透膜,持续过滤更彻底。手机监控:手机端远程便捷操作,随时随地查看机器运行状态。防漏水:一体式水路板,智能检测,降低漏水风险,放心使用。低噪音:噪音低至45分贝,家人尽享美好睡眠。自吸设计:适用于进水水压为0的使用场景,解决无压水源问题(自吸款)"), 44 | new("75C-2智能纯水机", 339.99, "img/product/9.jpg", "纯水机", 5, "LONSID", 45 | "进口陶氏RO膜 去除率达90%以上、五层滤芯 科学组合净化工艺、智能显示屏 触摸式按键、阻菌龙头 前置出水口全封闭、半集成水路设计 减少漏水风险。进口RO膜:进口陶氏RO膜,高节水高抗污,放心直饮。五级滤芯:五级过滤,层层过滤,健康饮水。大产水量:75G超大产水量,24小时产水280L,满足全家日常饮用水需求。阻菌龙头:阻菌龙头呵护到口,确保最后一厘米的纯净,保证你入口放心水。智能监控:直观显示滤芯寿命,触摸控制方便快捷。"), 46 | new("L3纯水机", 339.99, "img/product/10.jpg", "纯水机", 5, "LONSID", 47 | "高效RO膜滤芯 去除率达90%以上、五层滤芯及科学组合净化工艺、炫彩触摸式按键、独特磁吸设计机身简洁、滤芯智能冲洗 微电脑智能控制。强滤净化:好“芯”制好水,还原净水本味。反渗透过滤:纳米级精度,RO膜反渗透过滤技术,可过滤大部分有害物质,放心直饮。触摸式按键:相对于传统按钮,操作更便捷,全触摸式按键更科技。磁吸设计:人性化智能设计,自动清洗滤芯,对健康升级自动清洗技术可延长滤芯寿命。"), 48 | new("L2纯水机", 339.99, "img/product/11.jpg", "纯水机", 5, "LONSID", 49 | "高效RO膜滤芯 去除率达90%以上、精确寿命监控 随时掌控滤芯状况、炫彩触摸式按键、自吸式无需水压、智能化空吸保护。五级净化:好“芯”制好水,还原净水本味。反渗透过滤:纳米级精度,RO膜反渗透过滤技术,可过滤大部分有害物质,放心直饮。触摸式按键:相对于传统按钮,操作更便捷,全触摸式按键更科技。空吸保护:能够防止长时间空吸对高压泵造成的危害。自吸功能:无需水压,特殊设计的高压水泵具有自吸功能,可直接将水吸入机器净化,有效解决净水、地下水、水塔等无水压或水压不稳定的净化。"), 50 | new("GXRO80C 杀菌型智能纯水机", 339.99, "img/product/12.jpg", "纯水机", 5, "LONSID", 51 | "高效RO膜滤芯 去除率达90%以上、三级复合滤芯、微电脑控制 智能化保护、触摸式按键 操作更方便、独特杀菌模块 去除二次污染。高精度RO膜:RO膜的孔径非常小,只有水分子及部分有益人体的矿物离子能够通过,放心直饮,净享生活!。精“芯”呵护,体积小巧:三级滤芯,五重净化,精工打造饮水安全保障。"), 52 | new("L6纯水机(标准型)", 339.99, "img/product/13.jpg", "纯水机", 5, "LONSID", 53 | "采用600GRO膜 平均8S一杯水、物理阻菌 保证最后一厘米的纯净、嵌入式漏水保护监控更准确、可选择常规模式或节水模式、过流式紫外线杀菌。四级滤芯,六重过滤:优化设计重重过滤,几乎可拦截水中一切污染物。杀菌阻菌,双管齐下:UV冷阴极杀菌灯,物理阻菌龙头,让你喝上放心水。超大流量,集成水路:8S一杯水节省时间,一体式水路减少漏水风险和噪音。节省空间,维护便捷:无桶设计释放厨房空间,无需工具一提一拉秒速换芯。智能屏显,自由选择:屏幕实时选择工作状态,出现漏水强制停机,常规模式节水模式任你选择。"), 54 | new("S1纯水机", 339.99, "img/product/14.jpg", "纯水机", 5, "LONSID", 55 | "4合1全新高集成滤芯设计 高端智能按键龙头 1:1超低废水比 安全防伪二维码 高性能紫外线杀菌技术 。机身纤巧:安装不受限。智能双屏:龙头、机身双屏幕显示,水质、滤芯寿命一目了然。集成滤芯:四合一滤芯,省心省力省空间,高抗污染RO膜,滤芯性能更卓越。防伪验证:扫一扫可辨真伪,更换滤芯时,只有二维码被识别后方可更换。"), 56 | new("R2中央软水机", 339.99, "img/product/3.jpg", "中央处理设备", 5, "LONSID", 57 | "食品卫生安全材料、核心部件安全保障、智慧流量延滞型再生模式、比同类产品省33%再生剂和65%的水、智能自动循环运行。"), 58 | new("Q3全自动智能冲洗前置过滤器", 339.99, "img/product/2.jpg", "中央处理设备", 5, "LONSID", 59 | "环保无铅 国标62铜材质、食品级高分子防爆瓶体、滤瓶刮洗 免拆滤芯、万向接头 安装不受限、可拆卸透明窗体。"), 60 | new("J2中央净水机", 339.99, "img/product/5.jpg", "中央处理设备", 5, "LONSID", 61 | "高性能高效去除水中余氯、核心部件安全保障、智慧流量延滞型再生模式、比同类产品省33%再生剂和65%的水、智能自动循环运行。"), 62 | new("R3中央软水机", 339.99, "img/product/4.jpg", "中央处理设备", 5, "LONSID", 63 | "大流量匹配大用水量、食品级高容量离子交换树脂、省盐省水、大集成智能控制系统、旁通阀设计。"), 64 | ]; 65 | 66 | public async Task> GetProductsAsync(int page, int pageSize, string? search = null, 67 | string? category = null) 68 | { 69 | await Task.Delay(500); 70 | 71 | var result = _data.AsQueryable(); 72 | if (!string.IsNullOrWhiteSpace(search)) 73 | { 74 | result = result.Where(p => p.Name.Contains(search, StringComparison.OrdinalIgnoreCase)); 75 | } 76 | 77 | if (!string.IsNullOrWhiteSpace(category)) 78 | { 79 | result = result.Where(p => p.Category.Equals(category, StringComparison.OrdinalIgnoreCase)); 80 | } 81 | 82 | return result.Skip((page - 1) * pageSize).Take(pageSize).ToList(); 83 | } 84 | 85 | public async Task GetProductAsync(Guid id) 86 | { 87 | await Task.Delay(500); 88 | 89 | return _data.FirstOrDefault(p => p.Id == id) ?? throw new InvalidOperationException("Product not found"); 90 | } 91 | 92 | public async Task GetAvailableForSaleAsync(string brand) 93 | { 94 | await Task.Delay(500); 95 | 96 | return _data.Count(p => p.Brand.Equals(brand, StringComparison.OrdinalIgnoreCase)); 97 | } 98 | } -------------------------------------------------------------------------------- /Masa.Blazor.MauiDemo.Rcl/Services/UserService.cs: -------------------------------------------------------------------------------- 1 | using System.IdentityModel.Tokens.Jwt; 2 | using System.Security.Claims; 3 | using Masa.Blazor.MauiDemo.Platforms; 4 | 5 | namespace Masa.Blazor.MauiDemo.Rcl.Services; 6 | 7 | public class UserService 8 | { 9 | private readonly IPopupService _popupService; 10 | private readonly IPlatformIntegration _platformIntegration; 11 | 12 | public UserService(IPopupService popupService, IPlatformIntegration platformIntegration) 13 | { 14 | _popupService = popupService; 15 | _platformIntegration = platformIntegration; 16 | } 17 | 18 | public async Task LoginAsync(string username, string password) 19 | { 20 | // Simulate a login request 21 | { 22 | await Task.Delay(2000); 23 | 24 | if (username == "test" && password == "test") 25 | { 26 | // In a real-world scenario, you would call an API to get the token 27 | 28 | // fake token 29 | var accessToken = 30 | "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJodHRwOi8vc2NoZW1hcy54bWxzb2FwLm9yZy93cy8yMDA1LzA1L2lkZW50aXR5L2NsYWltcy9uYW1lIjoidGVzdCIsImlzcyI6Ik1hdWlEZW1vIiwiYXVkIjoiTWF1aURlbW8ifQ.mX1z0dTbK8vLDUxlul11IzyhDEvnunlD-LnMIDqKsG8"; 31 | 32 | // JWT has a set of predefined claims, such as sub, iss, aud, exp, and others. 33 | // exp is the expiration time of the token, and it is a Unix timestamp. 34 | // Because the token is fake, and it doesn't have a real expiration time, 35 | // so we fake it here. 36 | 37 | var expiresOn = TimeSpan.FromMinutes(5); 38 | var expiredAt = DateTime.UtcNow.Add(expiresOn); 39 | 40 | var token = new JwtToken(accessToken, expiredAt); 41 | PersistToken(token); 42 | var claimPrincipal = CreateClaimsPrincipalFromToken(accessToken); 43 | return claimPrincipal; 44 | } 45 | } 46 | 47 | await _popupService.EnqueueSnackbarAsync("Login failed", "Invalid username or password", AlertTypes.Error); 48 | 49 | return new ClaimsPrincipal(); 50 | } 51 | 52 | public async Task LoginBySmsAsync(string phoneNumber, string password) 53 | { 54 | // Simulate a login request 55 | return await LoginAsync("test", "test"); 56 | } 57 | 58 | public async Task ValidateConfirmCodeAsync(string phoneNumber, string confirmCode) 59 | { 60 | // Simulate a validate confirm code request 61 | await Task.Delay(1000); 62 | return await Task.FromResult(true); 63 | } 64 | 65 | public async Task ResetPasswordAsync(string phoneNumber, string password, string confirmCode) 66 | { 67 | // Simulate a reset password request 68 | await Task.Delay(1000); 69 | return await Task.FromResult(true); 70 | } 71 | 72 | public async Task GetAuthenticatedUserAsync() 73 | { 74 | var jwtToken = await _platformIntegration.GetCacheAsync("jwt_token", null); 75 | if (jwtToken is not null) 76 | { 77 | if (jwtToken.ExpiredAt > DateTime.UtcNow) 78 | { 79 | return CreateClaimsPrincipalFromToken(jwtToken.AccessToken); 80 | } 81 | 82 | await _popupService.EnqueueSnackbarAsync("Login expired", "Please login again", AlertTypes.Error); 83 | } 84 | 85 | return null; 86 | } 87 | 88 | public void Logout() 89 | { 90 | _ = _platformIntegration.RemoveCacheAsync("jwt_token"); 91 | } 92 | 93 | private void PersistToken(JwtToken token) 94 | { 95 | _ = _platformIntegration.SetCacheAsync("jwt_token", token); 96 | } 97 | 98 | private ClaimsPrincipal CreateClaimsPrincipalFromToken(string token) 99 | { 100 | var tokenHandler = new JwtSecurityTokenHandler(); 101 | var identity = new ClaimsIdentity(); 102 | 103 | if (tokenHandler.CanReadToken(token)) 104 | { 105 | var jwtSecurityToken = tokenHandler.ReadJwtToken(token); 106 | identity = new ClaimsIdentity(jwtSecurityToken.Claims, "Bearer"); 107 | } 108 | 109 | return new ClaimsPrincipal(identity); 110 | } 111 | 112 | record JwtToken(string AccessToken, DateTime ExpiredAt); 113 | } -------------------------------------------------------------------------------- /Masa.Blazor.MauiDemo.Rcl/Shared/LoginLayout.razor: -------------------------------------------------------------------------------- 1 | @using Masa.Blazor.Presets.PageStack 2 | @inherits LayoutComponentBase 3 | @inject IPlatformIntegration PlatformIntegration 4 | 5 | 6 | 7 | 9 | 10 | @Body 11 | 12 | 13 | 14 | 15 | 16 | @code { 17 | 18 | private static HashSet _tabRules = [new TabRule("/login$")]; 19 | 20 | protected override async Task OnAfterRenderAsync(bool firstRender) 21 | { 22 | if (firstRender) 23 | { 24 | await PlatformIntegration.InitThemeAsync(); 25 | } 26 | } 27 | 28 | } -------------------------------------------------------------------------------- /Masa.Blazor.MauiDemo.Rcl/Shared/MainLayout.razor: -------------------------------------------------------------------------------- 1 | @using Masa.Blazor.Presets.PageStack 2 | @inherits LayoutComponentBase 3 | @inject IPlatformIntegration PlatformIntegration 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | @Body 12 | 13 | 14 | 15 | @Body 16 | 17 | 18 | 19 | 20 | 21 | 27 | 28 | Shop 29 | @ShopIcon 30 | 31 | 32 | To do 33 | @TodoIcon 34 | 35 | 36 | User 37 | @UserIcon 38 | 39 | 40 | 41 | 42 | @code { 43 | 44 | private static HashSet _tabRules = [ 45 | new TabRule("^/$"), 46 | new TabRule("/todo", Self: true), 47 | new TabRule("/user") 48 | ]; 49 | 50 | private StringNumber? _current; 51 | 52 | private string ShopIcon => _current == 0 ? "mdi-shopping" : "mdi-shopping-outline"; 53 | private string TodoIcon => _current == 1 ? "mdi-check-bold" : "mdi-check"; 54 | private string UserIcon => _current == 2 ? "mdi-account" : "mdi-account-outline"; 55 | 56 | protected override async Task OnAfterRenderAsync(bool firstRender) 57 | { 58 | if (firstRender) 59 | { 60 | await PlatformIntegration.InitThemeAsync(); 61 | } 62 | } 63 | 64 | } -------------------------------------------------------------------------------- /Masa.Blazor.MauiDemo.Rcl/_Imports.razor: -------------------------------------------------------------------------------- 1 | @using Microsoft.AspNetCore.Authorization 2 | @using Microsoft.AspNetCore.Components.Web 3 | @using Microsoft.AspNetCore.Components.Routing 4 | @using Masa.Blazor 5 | @using Masa.Blazor.Presets 6 | @using Masa.Blazor.MauiDemo.Rcl.Shared 7 | @using Masa.Blazor.MauiDemo.Platforms 8 | @using Masa.Blazor.MauiDemo.Rcl.Models 9 | @using Masa.Blazor.MauiDemo.Rcl.Data 10 | @using Masa.Blazor.MauiDemo.Rcl.Components 11 | @using Masa.Blazor.MauiDemo.Rcl.Auth 12 | @using Masa.Blazor.MauiDemo.Rcl.Services 13 | @using Microsoft.AspNetCore.Components.Authorization 14 | @attribute [Authorize] -------------------------------------------------------------------------------- /Masa.Blazor.MauiDemo.Rcl/wwwroot/css/app.css: -------------------------------------------------------------------------------- 1 | /* reset */ 2 | 3 | .theme--light.m-app-bar.m-toolbar.m-sheet { 4 | background: #FFFFFF; 5 | } 6 | 7 | .theme--dark.m-app-bar.m-toolbar.m-sheet { 8 | background: #000000; 9 | } 10 | 11 | .m-expansion-panel::before { 12 | box-shadow: none; 13 | } 14 | 15 | .m-expansion-panel:not(:first-child)::after { 16 | border-top: none; 17 | } 18 | 19 | .m-expansion-panel-content__wrap { 20 | padding: 0 8px 8px; 21 | } 22 | 23 | .m-expansion-panel--active>.m-expansion-panel-header { 24 | min-height: inherit; 25 | } 26 | 27 | .m-application--is-ltr .m-list-item__action:first-child, .m-application--is-ltr .m-list-item__icon:first-child { 28 | margin-right: 16px; 29 | } 30 | 31 | .m-sheet > .m-list { 32 | background: inherit; 33 | border-radius: inherit; 34 | } 35 | 36 | /* custom */ 37 | 38 | .priority--default { 39 | } 40 | 41 | .priority--high { 42 | color: #F44336; 43 | } 44 | 45 | .priority--medium { 46 | color: #FF9800; 47 | } 48 | 49 | .priority--low { 50 | color: #2196F3; 51 | } 52 | 53 | .m-simple-checkbox.priority--high .m-icon, 54 | .m-simple-checkbox.priority--medium .m-icon, 55 | .m-simple-checkbox.priority--low .m-icon{ 56 | color: inherit; 57 | } 58 | 59 | .login { 60 | text-align: center; 61 | } 62 | 63 | .login > * { 64 | margin-bottom: 24px !important; 65 | } -------------------------------------------------------------------------------- /Masa.Blazor.MauiDemo.Rcl/wwwroot/css/materialdesign/v7.1.96/fonts/materialdesignicons-webfont.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/masastack/Masa.Blazor.MauiDemo/0f8476be760ee03604444edeafb4d7341e17d7e0/Masa.Blazor.MauiDemo.Rcl/wwwroot/css/materialdesign/v7.1.96/fonts/materialdesignicons-webfont.eot -------------------------------------------------------------------------------- /Masa.Blazor.MauiDemo.Rcl/wwwroot/css/materialdesign/v7.1.96/fonts/materialdesignicons-webfont.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/masastack/Masa.Blazor.MauiDemo/0f8476be760ee03604444edeafb4d7341e17d7e0/Masa.Blazor.MauiDemo.Rcl/wwwroot/css/materialdesign/v7.1.96/fonts/materialdesignicons-webfont.ttf -------------------------------------------------------------------------------- /Masa.Blazor.MauiDemo.Rcl/wwwroot/css/materialdesign/v7.1.96/fonts/materialdesignicons-webfont.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/masastack/Masa.Blazor.MauiDemo/0f8476be760ee03604444edeafb4d7341e17d7e0/Masa.Blazor.MauiDemo.Rcl/wwwroot/css/materialdesign/v7.1.96/fonts/materialdesignicons-webfont.woff -------------------------------------------------------------------------------- /Masa.Blazor.MauiDemo.Rcl/wwwroot/css/materialdesign/v7.1.96/fonts/materialdesignicons-webfont.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/masastack/Masa.Blazor.MauiDemo/0f8476be760ee03604444edeafb4d7341e17d7e0/Masa.Blazor.MauiDemo.Rcl/wwwroot/css/materialdesign/v7.1.96/fonts/materialdesignicons-webfont.woff2 -------------------------------------------------------------------------------- /Masa.Blazor.MauiDemo.Rcl/wwwroot/img/contact--no-padding.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/masastack/Masa.Blazor.MauiDemo/0f8476be760ee03604444edeafb4d7341e17d7e0/Masa.Blazor.MauiDemo.Rcl/wwwroot/img/contact--no-padding.png -------------------------------------------------------------------------------- /Masa.Blazor.MauiDemo.Rcl/wwwroot/img/masastack-cn.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/masastack/Masa.Blazor.MauiDemo/0f8476be760ee03604444edeafb4d7341e17d7e0/Masa.Blazor.MauiDemo.Rcl/wwwroot/img/masastack-cn.png -------------------------------------------------------------------------------- /Masa.Blazor.MauiDemo.Rcl/wwwroot/img/masastack.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/masastack/Masa.Blazor.MauiDemo/0f8476be760ee03604444edeafb4d7341e17d7e0/Masa.Blazor.MauiDemo.Rcl/wwwroot/img/masastack.png -------------------------------------------------------------------------------- /Masa.Blazor.MauiDemo.Rcl/wwwroot/img/product/1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/masastack/Masa.Blazor.MauiDemo/0f8476be760ee03604444edeafb4d7341e17d7e0/Masa.Blazor.MauiDemo.Rcl/wwwroot/img/product/1.jpg -------------------------------------------------------------------------------- /Masa.Blazor.MauiDemo.Rcl/wwwroot/img/product/10.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/masastack/Masa.Blazor.MauiDemo/0f8476be760ee03604444edeafb4d7341e17d7e0/Masa.Blazor.MauiDemo.Rcl/wwwroot/img/product/10.jpg -------------------------------------------------------------------------------- /Masa.Blazor.MauiDemo.Rcl/wwwroot/img/product/11.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/masastack/Masa.Blazor.MauiDemo/0f8476be760ee03604444edeafb4d7341e17d7e0/Masa.Blazor.MauiDemo.Rcl/wwwroot/img/product/11.jpg -------------------------------------------------------------------------------- /Masa.Blazor.MauiDemo.Rcl/wwwroot/img/product/12.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/masastack/Masa.Blazor.MauiDemo/0f8476be760ee03604444edeafb4d7341e17d7e0/Masa.Blazor.MauiDemo.Rcl/wwwroot/img/product/12.jpg -------------------------------------------------------------------------------- /Masa.Blazor.MauiDemo.Rcl/wwwroot/img/product/13.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/masastack/Masa.Blazor.MauiDemo/0f8476be760ee03604444edeafb4d7341e17d7e0/Masa.Blazor.MauiDemo.Rcl/wwwroot/img/product/13.jpg -------------------------------------------------------------------------------- /Masa.Blazor.MauiDemo.Rcl/wwwroot/img/product/14.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/masastack/Masa.Blazor.MauiDemo/0f8476be760ee03604444edeafb4d7341e17d7e0/Masa.Blazor.MauiDemo.Rcl/wwwroot/img/product/14.jpg -------------------------------------------------------------------------------- /Masa.Blazor.MauiDemo.Rcl/wwwroot/img/product/15.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/masastack/Masa.Blazor.MauiDemo/0f8476be760ee03604444edeafb4d7341e17d7e0/Masa.Blazor.MauiDemo.Rcl/wwwroot/img/product/15.jpg -------------------------------------------------------------------------------- /Masa.Blazor.MauiDemo.Rcl/wwwroot/img/product/16.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/masastack/Masa.Blazor.MauiDemo/0f8476be760ee03604444edeafb4d7341e17d7e0/Masa.Blazor.MauiDemo.Rcl/wwwroot/img/product/16.jpg -------------------------------------------------------------------------------- /Masa.Blazor.MauiDemo.Rcl/wwwroot/img/product/17.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/masastack/Masa.Blazor.MauiDemo/0f8476be760ee03604444edeafb4d7341e17d7e0/Masa.Blazor.MauiDemo.Rcl/wwwroot/img/product/17.jpg -------------------------------------------------------------------------------- /Masa.Blazor.MauiDemo.Rcl/wwwroot/img/product/18.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/masastack/Masa.Blazor.MauiDemo/0f8476be760ee03604444edeafb4d7341e17d7e0/Masa.Blazor.MauiDemo.Rcl/wwwroot/img/product/18.jpg -------------------------------------------------------------------------------- /Masa.Blazor.MauiDemo.Rcl/wwwroot/img/product/19.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/masastack/Masa.Blazor.MauiDemo/0f8476be760ee03604444edeafb4d7341e17d7e0/Masa.Blazor.MauiDemo.Rcl/wwwroot/img/product/19.jpg -------------------------------------------------------------------------------- /Masa.Blazor.MauiDemo.Rcl/wwwroot/img/product/2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/masastack/Masa.Blazor.MauiDemo/0f8476be760ee03604444edeafb4d7341e17d7e0/Masa.Blazor.MauiDemo.Rcl/wwwroot/img/product/2.jpg -------------------------------------------------------------------------------- /Masa.Blazor.MauiDemo.Rcl/wwwroot/img/product/20.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/masastack/Masa.Blazor.MauiDemo/0f8476be760ee03604444edeafb4d7341e17d7e0/Masa.Blazor.MauiDemo.Rcl/wwwroot/img/product/20.jpg -------------------------------------------------------------------------------- /Masa.Blazor.MauiDemo.Rcl/wwwroot/img/product/21.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/masastack/Masa.Blazor.MauiDemo/0f8476be760ee03604444edeafb4d7341e17d7e0/Masa.Blazor.MauiDemo.Rcl/wwwroot/img/product/21.jpg -------------------------------------------------------------------------------- /Masa.Blazor.MauiDemo.Rcl/wwwroot/img/product/22.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/masastack/Masa.Blazor.MauiDemo/0f8476be760ee03604444edeafb4d7341e17d7e0/Masa.Blazor.MauiDemo.Rcl/wwwroot/img/product/22.jpg -------------------------------------------------------------------------------- /Masa.Blazor.MauiDemo.Rcl/wwwroot/img/product/23.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/masastack/Masa.Blazor.MauiDemo/0f8476be760ee03604444edeafb4d7341e17d7e0/Masa.Blazor.MauiDemo.Rcl/wwwroot/img/product/23.jpg -------------------------------------------------------------------------------- /Masa.Blazor.MauiDemo.Rcl/wwwroot/img/product/24.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/masastack/Masa.Blazor.MauiDemo/0f8476be760ee03604444edeafb4d7341e17d7e0/Masa.Blazor.MauiDemo.Rcl/wwwroot/img/product/24.jpg -------------------------------------------------------------------------------- /Masa.Blazor.MauiDemo.Rcl/wwwroot/img/product/25.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/masastack/Masa.Blazor.MauiDemo/0f8476be760ee03604444edeafb4d7341e17d7e0/Masa.Blazor.MauiDemo.Rcl/wwwroot/img/product/25.jpg -------------------------------------------------------------------------------- /Masa.Blazor.MauiDemo.Rcl/wwwroot/img/product/26.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/masastack/Masa.Blazor.MauiDemo/0f8476be760ee03604444edeafb4d7341e17d7e0/Masa.Blazor.MauiDemo.Rcl/wwwroot/img/product/26.jpg -------------------------------------------------------------------------------- /Masa.Blazor.MauiDemo.Rcl/wwwroot/img/product/27.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/masastack/Masa.Blazor.MauiDemo/0f8476be760ee03604444edeafb4d7341e17d7e0/Masa.Blazor.MauiDemo.Rcl/wwwroot/img/product/27.jpg -------------------------------------------------------------------------------- /Masa.Blazor.MauiDemo.Rcl/wwwroot/img/product/28.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/masastack/Masa.Blazor.MauiDemo/0f8476be760ee03604444edeafb4d7341e17d7e0/Masa.Blazor.MauiDemo.Rcl/wwwroot/img/product/28.jpg -------------------------------------------------------------------------------- /Masa.Blazor.MauiDemo.Rcl/wwwroot/img/product/29.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/masastack/Masa.Blazor.MauiDemo/0f8476be760ee03604444edeafb4d7341e17d7e0/Masa.Blazor.MauiDemo.Rcl/wwwroot/img/product/29.jpg -------------------------------------------------------------------------------- /Masa.Blazor.MauiDemo.Rcl/wwwroot/img/product/3.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/masastack/Masa.Blazor.MauiDemo/0f8476be760ee03604444edeafb4d7341e17d7e0/Masa.Blazor.MauiDemo.Rcl/wwwroot/img/product/3.jpg -------------------------------------------------------------------------------- /Masa.Blazor.MauiDemo.Rcl/wwwroot/img/product/30.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/masastack/Masa.Blazor.MauiDemo/0f8476be760ee03604444edeafb4d7341e17d7e0/Masa.Blazor.MauiDemo.Rcl/wwwroot/img/product/30.jpg -------------------------------------------------------------------------------- /Masa.Blazor.MauiDemo.Rcl/wwwroot/img/product/31.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/masastack/Masa.Blazor.MauiDemo/0f8476be760ee03604444edeafb4d7341e17d7e0/Masa.Blazor.MauiDemo.Rcl/wwwroot/img/product/31.jpg -------------------------------------------------------------------------------- /Masa.Blazor.MauiDemo.Rcl/wwwroot/img/product/32.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/masastack/Masa.Blazor.MauiDemo/0f8476be760ee03604444edeafb4d7341e17d7e0/Masa.Blazor.MauiDemo.Rcl/wwwroot/img/product/32.jpg -------------------------------------------------------------------------------- /Masa.Blazor.MauiDemo.Rcl/wwwroot/img/product/4.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/masastack/Masa.Blazor.MauiDemo/0f8476be760ee03604444edeafb4d7341e17d7e0/Masa.Blazor.MauiDemo.Rcl/wwwroot/img/product/4.jpg -------------------------------------------------------------------------------- /Masa.Blazor.MauiDemo.Rcl/wwwroot/img/product/5.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/masastack/Masa.Blazor.MauiDemo/0f8476be760ee03604444edeafb4d7341e17d7e0/Masa.Blazor.MauiDemo.Rcl/wwwroot/img/product/5.jpg -------------------------------------------------------------------------------- /Masa.Blazor.MauiDemo.Rcl/wwwroot/img/product/6.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/masastack/Masa.Blazor.MauiDemo/0f8476be760ee03604444edeafb4d7341e17d7e0/Masa.Blazor.MauiDemo.Rcl/wwwroot/img/product/6.jpg -------------------------------------------------------------------------------- /Masa.Blazor.MauiDemo.Rcl/wwwroot/img/product/7.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/masastack/Masa.Blazor.MauiDemo/0f8476be760ee03604444edeafb4d7341e17d7e0/Masa.Blazor.MauiDemo.Rcl/wwwroot/img/product/7.jpg -------------------------------------------------------------------------------- /Masa.Blazor.MauiDemo.Rcl/wwwroot/img/product/8.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/masastack/Masa.Blazor.MauiDemo/0f8476be760ee03604444edeafb4d7341e17d7e0/Masa.Blazor.MauiDemo.Rcl/wwwroot/img/product/8.jpg -------------------------------------------------------------------------------- /Masa.Blazor.MauiDemo.Rcl/wwwroot/img/product/9.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/masastack/Masa.Blazor.MauiDemo/0f8476be760ee03604444edeafb4d7341e17d7e0/Masa.Blazor.MauiDemo.Rcl/wwwroot/img/product/9.jpg -------------------------------------------------------------------------------- /Masa.Blazor.MauiDemo.Server/App.razor: -------------------------------------------------------------------------------- 1 | @using Masa.Blazor.MauiDemo.Rcl.Pages.Login 2 | 3 | 5 | 6 | 7 | 8 | @if (context.User.Identity?.IsAuthenticated is not true) 9 | { 10 | 11 | } 12 | else 13 | { 14 |

You are not authorized to access the resource.

15 | } 16 |
17 |
18 | 19 |
20 | 21 | Not found 22 | 23 |

Sorry, there's nothing at this address.

24 |
25 |
26 |
-------------------------------------------------------------------------------- /Masa.Blazor.MauiDemo.Server/H5PlatformIntegration.cs: -------------------------------------------------------------------------------- 1 | using Masa.Blazor.MauiDemo.Platforms; 2 | using Microsoft.JSInterop; 3 | 4 | namespace Masa.Blazor.MauiDemo.Server; 5 | 6 | public class H5PlatformIntegration : IPlatformIntegration 7 | { 8 | private readonly IJSRuntime _jsRuntime; 9 | private readonly LocalStorage _localStorage; 10 | private readonly MasaBlazor _masaBlazor; 11 | 12 | private GeoCoordinate? _cachedCoordinate; 13 | 14 | public H5PlatformIntegration(IJSRuntime jsRuntime, LocalStorage localStorage, MasaBlazor masaBlazor) 15 | { 16 | _jsRuntime = jsRuntime; 17 | _localStorage = localStorage; 18 | _masaBlazor = masaBlazor; 19 | } 20 | 21 | public Task GetCachedCoordinateAsync() 22 | { 23 | return Task.FromResult(_cachedCoordinate); 24 | } 25 | 26 | public async Task GetCurrentCoordinateAsync() 27 | { 28 | _cachedCoordinate = await _jsRuntime.InvokeAsync("__mauiDemo.getCurrentPosition"); 29 | return _cachedCoordinate; 30 | } 31 | 32 | public void SetStatusBar(string argb, int style) 33 | { 34 | throw new NotImplementedException(); 35 | } 36 | 37 | public async Task SetThemeAsync(int theme) 38 | { 39 | _ = _localStorage.SetItemAsync(AppThemeKey, theme); 40 | 41 | if (theme == 0) 42 | { 43 | var isDarkPreferColor = await IsDarkThemeOfSystemAsync(); 44 | 45 | _masaBlazor.SetTheme(isDarkPreferColor); 46 | } 47 | else 48 | { 49 | _masaBlazor.SetTheme(theme == 2); 50 | } 51 | } 52 | 53 | private const string AppThemeKey = "AppTheme"; 54 | 55 | public async Task InitThemeAsync() 56 | { 57 | var result = await _localStorage.GetItemAsync(AppThemeKey); 58 | var isDark = result is null or 0 ? await IsDarkThemeOfSystemAsync() : result == 2; 59 | _masaBlazor.SetTheme(isDark); 60 | } 61 | 62 | public async ValueTask GetThemeAsync() 63 | { 64 | var result = await _localStorage.GetItemAsync(AppThemeKey); 65 | return result ?? 0; 66 | } 67 | 68 | public async ValueTask IsDarkThemeOfSystemAsync() 69 | { 70 | return await _jsRuntime.InvokeAsync("eval", "window.matchMedia('(prefers-color-scheme: dark)').matches"); 71 | } 72 | 73 | public async Task SetCacheAsync(string key, TValue value) 74 | { 75 | await _localStorage.SetItemAsync(key, value); 76 | } 77 | 78 | public async Task GetCacheAsync(string key, TValue defaultValue) 79 | { 80 | var result = await _localStorage.GetItemAsync(key); 81 | return result ?? defaultValue; 82 | } 83 | 84 | public Task RemoveCacheAsync(string key) 85 | { 86 | return _localStorage.RemoveItemAsync(key); 87 | } 88 | } -------------------------------------------------------------------------------- /Masa.Blazor.MauiDemo.Server/Masa.Blazor.MauiDemo.Server.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | net8.0 5 | enable 6 | enable 7 | 3bcbb53d-cc0b-4e59-bc04-5de8037f7e39 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /Masa.Blazor.MauiDemo.Server/Pages/Error.cshtml: -------------------------------------------------------------------------------- 1 | @page 2 | @model Masa.Blazor.MauiDemo.Server.Pages.ErrorModel 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | Error 11 | 12 | 13 | 22 | 23 | 24 | 25 |
26 |
27 |

Error.

28 |

An error occurred while processing your request.

29 | 30 | @if (Model.ShowRequestId) 31 | { 32 |

33 | Request ID: @Model.RequestId 34 |

35 | } 36 | 37 |

Development Mode

38 |

39 | Swapping to the Development environment displays detailed information about the error 40 | that occurred. 41 |

42 |

43 | The Development environment shouldn't be enabled for deployed applications. 44 | It can result in displaying sensitive information from exceptions to end users. 45 | For local debugging, enable the Development environment by setting the 46 | ASPNETCORE_ENVIRONMENT environment variable to Development 47 | and restarting the app. 48 |

49 |
50 |
51 | 52 | 53 | 54 | -------------------------------------------------------------------------------- /Masa.Blazor.MauiDemo.Server/Pages/Error.cshtml.cs: -------------------------------------------------------------------------------- 1 | using System.Diagnostics; 2 | using Microsoft.AspNetCore.Mvc; 3 | using Microsoft.AspNetCore.Mvc.RazorPages; 4 | 5 | namespace Masa.Blazor.MauiDemo.Server.Pages; 6 | 7 | [ResponseCache(Duration = 0, Location = ResponseCacheLocation.None, NoStore = true)] 8 | [IgnoreAntiforgeryToken] 9 | public class ErrorModel : PageModel 10 | { 11 | public string? RequestId { get; set; } 12 | 13 | public bool ShowRequestId => !string.IsNullOrEmpty(RequestId); 14 | 15 | private readonly ILogger _logger; 16 | 17 | public ErrorModel(ILogger logger) 18 | { 19 | _logger = logger; 20 | } 21 | 22 | public void OnGet() 23 | { 24 | RequestId = Activity.Current?.Id ?? HttpContext.TraceIdentifier; 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /Masa.Blazor.MauiDemo.Server/Pages/_Host.cshtml: -------------------------------------------------------------------------------- 1 | @page "/" 2 | @using Masa.Blazor.MauiDemo.Rcl 3 | @using Microsoft.AspNetCore.Components.Web 4 | @using Microsoft.AspNetCore.Mvc.TagHelpers 5 | @namespace Masa.Blazor.MauiDemo.Server.Pages 6 | @addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 |
27 | 28 | An error has occurred. This application may no longer respond until reloaded. 29 | 30 | 31 | An unhandled exception has occurred. See browser dev tools for details. 32 | 33 | Reload 34 | 🗙 35 |
36 | 37 | 38 | 39 | 55 | 56 | 57 | -------------------------------------------------------------------------------- /Masa.Blazor.MauiDemo.Server/Program.cs: -------------------------------------------------------------------------------- 1 | using Masa.Blazor.MauiDemo.Platforms; 2 | using Masa.Blazor.MauiDemo.Rcl.Data; 3 | using Masa.Blazor.MauiDemo.Rcl.Services; 4 | using Masa.Blazor.MauiDemo.Server; 5 | 6 | var builder = WebApplication.CreateBuilder(args); 7 | 8 | // Add services to the container. 9 | builder.Services.AddRazorPages(); 10 | builder.Services.AddServerSideBlazor(); 11 | builder.Services.AddMasaBlazorMauiDemo(); 12 | builder.Services.AddSingleton(); 13 | builder.Services.AddScoped(); 14 | builder.Services.AddScoped(); 15 | 16 | var app = builder.Build(); 17 | 18 | // Configure the HTTP request pipeline. 19 | if (!app.Environment.IsDevelopment()) 20 | { 21 | app.UseExceptionHandler("/Error"); 22 | // The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts. 23 | app.UseHsts(); 24 | } 25 | 26 | app.UseHttpsRedirection(); 27 | 28 | app.UseStaticFiles(); 29 | 30 | app.UseRouting(); 31 | 32 | app.MapBlazorHub(); 33 | app.MapFallbackToPage("/_Host"); 34 | 35 | app.Run(); 36 | -------------------------------------------------------------------------------- /Masa.Blazor.MauiDemo.Server/Properties/launchSettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "iisSettings": { 3 | "iisExpress": { 4 | "applicationUrl": "http://localhost:11306", 5 | "sslPort": 44300 6 | } 7 | }, 8 | "profiles": { 9 | "http": { 10 | "commandName": "Project", 11 | "dotnetRunMessages": true, 12 | "launchBrowser": true, 13 | "inspectUri": "{wsProtocol}://{url.hostname}:{url.port}/_framework/debug/ws-proxy?browser={browserInspectUri}", 14 | "applicationUrl": "http://localhost:5027", 15 | "environmentVariables": { 16 | "ASPNETCORE_ENVIRONMENT": "Development" 17 | } 18 | }, 19 | "https": { 20 | "commandName": "Project", 21 | "dotnetRunMessages": true, 22 | "launchBrowser": true, 23 | "inspectUri": "{wsProtocol}://{url.hostname}:{url.port}/_framework/debug/ws-proxy?browser={browserInspectUri}", 24 | "applicationUrl": "https://localhost:7115;http://localhost:5027", 25 | "environmentVariables": { 26 | "ASPNETCORE_ENVIRONMENT": "Development" 27 | } 28 | }, 29 | "IIS Express": { 30 | "commandName": "IISExpress", 31 | "launchBrowser": true, 32 | "inspectUri": "{wsProtocol}://{url.hostname}:{url.port}/_framework/debug/ws-proxy?browser={browserInspectUri}", 33 | "environmentVariables": { 34 | "ASPNETCORE_ENVIRONMENT": "Development" 35 | } 36 | } 37 | } 38 | } -------------------------------------------------------------------------------- /Masa.Blazor.MauiDemo.Server/_Imports.razor: -------------------------------------------------------------------------------- 1 | @using System.Net.Http 2 | @using Microsoft.AspNetCore.Authorization 3 | @using Microsoft.AspNetCore.Components.Authorization 4 | @using Microsoft.AspNetCore.Components.Forms 5 | @using Microsoft.AspNetCore.Components.Routing 6 | @using Microsoft.AspNetCore.Components.Web 7 | @using Microsoft.AspNetCore.Components.Web.Virtualization 8 | @using Microsoft.JSInterop 9 | @using Masa.Blazor 10 | @using Masa.Blazor.Presets 11 | @using Masa.Blazor.MauiDemo.Server 12 | @using Masa.Blazor.MauiDemo.Rcl 13 | @using Masa.Blazor.MauiDemo.Rcl.Shared 14 | -------------------------------------------------------------------------------- /Masa.Blazor.MauiDemo.Server/appsettings.Development.json: -------------------------------------------------------------------------------- 1 | { 2 | "DetailedErrors": true, 3 | "Logging": { 4 | "LogLevel": { 5 | "Default": "Information", 6 | "Microsoft.AspNetCore": "Warning" 7 | } 8 | }, 9 | "Authentication": { 10 | "Schemes": { 11 | "Bearer": { 12 | "ValidAudiences": [ 13 | "http://localhost:11306", 14 | "https://localhost:44300", 15 | "http://localhost:5027", 16 | "https://localhost:7115" 17 | ], 18 | "ValidIssuer": "dotnet-user-jwts" 19 | } 20 | } 21 | } 22 | } -------------------------------------------------------------------------------- /Masa.Blazor.MauiDemo.Server/appsettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "Logging": { 3 | "LogLevel": { 4 | "Default": "Information", 5 | "Microsoft.AspNetCore": "Warning" 6 | } 7 | }, 8 | "AllowedHosts": "*" 9 | } 10 | -------------------------------------------------------------------------------- /Masa.Blazor.MauiDemo.Server/wwwroot/css/site.css: -------------------------------------------------------------------------------- 1 | html, body { 2 | font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; 3 | } 4 | 5 | h1:focus { 6 | outline: none; 7 | } 8 | 9 | #blazor-error-ui { 10 | background: lightyellow; 11 | bottom: 0; 12 | box-shadow: 0 -1px 2px rgba(0, 0, 0, 0.2); 13 | display: none; 14 | left: 0; 15 | padding: 0.6rem 1.25rem 0.7rem 1.25rem; 16 | position: fixed; 17 | width: 100%; 18 | z-index: 1000; 19 | } 20 | 21 | #blazor-error-ui .dismiss { 22 | cursor: pointer; 23 | position: absolute; 24 | right: 0.75rem; 25 | top: 0.5rem; 26 | } 27 | 28 | .blazor-error-boundary { 29 | background: url(data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iNTYiIGhlaWdodD0iNDkiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiIG92ZXJmbG93PSJoaWRkZW4iPjxkZWZzPjxjbGlwUGF0aCBpZD0iY2xpcDAiPjxyZWN0IHg9IjIzNSIgeT0iNTEiIHdpZHRoPSI1NiIgaGVpZ2h0PSI0OSIvPjwvY2xpcFBhdGg+PC9kZWZzPjxnIGNsaXAtcGF0aD0idXJsKCNjbGlwMCkiIHRyYW5zZm9ybT0idHJhbnNsYXRlKC0yMzUgLTUxKSI+PHBhdGggZD0iTTI2My41MDYgNTFDMjY0LjcxNyA1MSAyNjUuODEzIDUxLjQ4MzcgMjY2LjYwNiA1Mi4yNjU4TDI2Ny4wNTIgNTIuNzk4NyAyNjcuNTM5IDUzLjYyODMgMjkwLjE4NSA5Mi4xODMxIDI5MC41NDUgOTIuNzk1IDI5MC42NTYgOTIuOTk2QzI5MC44NzcgOTMuNTEzIDI5MSA5NC4wODE1IDI5MSA5NC42NzgyIDI5MSA5Ny4wNjUxIDI4OS4wMzggOTkgMjg2LjYxNyA5OUwyNDAuMzgzIDk5QzIzNy45NjMgOTkgMjM2IDk3LjA2NTEgMjM2IDk0LjY3ODIgMjM2IDk0LjM3OTkgMjM2LjAzMSA5NC4wODg2IDIzNi4wODkgOTMuODA3MkwyMzYuMzM4IDkzLjAxNjIgMjM2Ljg1OCA5Mi4xMzE0IDI1OS40NzMgNTMuNjI5NCAyNTkuOTYxIDUyLjc5ODUgMjYwLjQwNyA1Mi4yNjU4QzI2MS4yIDUxLjQ4MzcgMjYyLjI5NiA1MSAyNjMuNTA2IDUxWk0yNjMuNTg2IDY2LjAxODNDMjYwLjczNyA2Ni4wMTgzIDI1OS4zMTMgNjcuMTI0NSAyNTkuMzEzIDY5LjMzNyAyNTkuMzEzIDY5LjYxMDIgMjU5LjMzMiA2OS44NjA4IDI1OS4zNzEgNzAuMDg4N0wyNjEuNzk1IDg0LjAxNjEgMjY1LjM4IDg0LjAxNjEgMjY3LjgyMSA2OS43NDc1QzI2Ny44NiA2OS43MzA5IDI2Ny44NzkgNjkuNTg3NyAyNjcuODc5IDY5LjMxNzkgMjY3Ljg3OSA2Ny4xMTgyIDI2Ni40NDggNjYuMDE4MyAyNjMuNTg2IDY2LjAxODNaTTI2My41NzYgODYuMDU0N0MyNjEuMDQ5IDg2LjA1NDcgMjU5Ljc4NiA4Ny4zMDA1IDI1OS43ODYgODkuNzkyMSAyNTkuNzg2IDkyLjI4MzcgMjYxLjA0OSA5My41Mjk1IDI2My41NzYgOTMuNTI5NSAyNjYuMTE2IDkzLjUyOTUgMjY3LjM4NyA5Mi4yODM3IDI2Ny4zODcgODkuNzkyMSAyNjcuMzg3IDg3LjMwMDUgMjY2LjExNiA4Ni4wNTQ3IDI2My41NzYgODYuMDU0N1oiIGZpbGw9IiNGRkU1MDAiIGZpbGwtcnVsZT0iZXZlbm9kZCIvPjwvZz48L3N2Zz4=) no-repeat 1rem/1.8rem, #b32121; 30 | padding: 1rem 1rem 1rem 3.7rem; 31 | color: white; 32 | } 33 | 34 | .blazor-error-boundary::after { 35 | content: "An error has occurred." 36 | } 37 | -------------------------------------------------------------------------------- /Masa.Blazor.MauiDemo.Server/wwwroot/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/masastack/Masa.Blazor.MauiDemo/0f8476be760ee03604444edeafb4d7341e17d7e0/Masa.Blazor.MauiDemo.Server/wwwroot/favicon.png -------------------------------------------------------------------------------- /Masa.Blazor.MauiDemo.Web.slnx: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /Masa.Blazor.MauiDemo.slnx: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /Masa.Blazor.MauiDemo/App.xaml: -------------------------------------------------------------------------------- 1 |  2 | 6 | 7 | 8 | #f0f3fa 9 | 10 | 11 | -------------------------------------------------------------------------------- /Masa.Blazor.MauiDemo/App.xaml.cs: -------------------------------------------------------------------------------- 1 | namespace Masa.Blazor.MauiDemo; 2 | 3 | public partial class App : Microsoft.Maui.Controls.Application 4 | { 5 | public App() 6 | { 7 | InitializeComponent(); 8 | 9 | MainPage = new MainPage(); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /Masa.Blazor.MauiDemo/Main.razor: -------------------------------------------------------------------------------- 1 | @using Masa.Blazor 2 | @using Masa.Blazor.MauiDemo.Platforms 3 | @using Microsoft.Extensions.Logging 4 | @using Router = Microsoft.AspNetCore.Components.Routing.Router 5 | @inject IPlatformIntegration PlatformNavigation 6 | @inject MasaBlazor MasaBlazor 7 | @using Masa.Blazor.MauiDemo.Rcl.Pages.Login 8 | 9 | 12 | 13 | 14 | 15 | @if (context.User.Identity?.IsAuthenticated is not true) 16 | { 17 | 18 | } 19 | else 20 | { 21 |

You are not authorized to access the resource.

22 | } 23 |
24 |
25 | 26 |
27 | 28 | 29 |

Sorry, there's nothing at this address.

30 |
31 |
32 |
33 | 34 | @code { 35 | 36 | private const string White = "#FFFFFF"; 37 | private const string Primary = "#4F33FF"; 38 | private const string Surface = "#F0F3FA"; 39 | 40 | private const string Dark = "#000000"; 41 | private const string DarkPrimary = "#C5C0FF"; 42 | private const string DarkSurface = "#131316"; 43 | 44 | private string _path = string.Empty; 45 | private bool _dark; 46 | 47 | private string? _statusBarColor; 48 | 49 | protected override void OnInitialized() 50 | { 51 | base.OnInitialized(); 52 | 53 | _dark = MasaBlazor.Theme.Dark; 54 | MasaBlazor.OnThemeChange += MasaBlazorOnOnThemeChange; 55 | } 56 | 57 | private void MasaBlazorOnOnThemeChange(Theme theme) 58 | { 59 | _dark = theme.Dark; 60 | SetStatusBarColor(); 61 | InvokeAsync(StateHasChanged); 62 | } 63 | 64 | private void HandleOnNavigate(NavigationContext context) 65 | { 66 | _path = context.Path; 67 | SetStatusBarColor(); 68 | } 69 | 70 | private void SetStatusBarColor() 71 | { 72 | _dark = MasaBlazor.Theme.Dark; 73 | int style = 0; 74 | 75 | if (_path.StartsWith("user")) 76 | { 77 | var color = _dark ? DarkPrimary : Primary; 78 | 79 | if (_statusBarColor == color) return; 80 | _statusBarColor = color; 81 | style = _dark ? 2 : 1; 82 | } 83 | else if (_path == "" || _path.StartsWith("shop/search") || _path.StartsWith("todo")) 84 | { 85 | var color = _dark ? DarkSurface : Surface; 86 | if (_statusBarColor == color) return; 87 | _statusBarColor = color; 88 | style = _dark ? 1 : 2; 89 | } 90 | else 91 | { 92 | var color = _dark ? Dark : White; 93 | if (_statusBarColor == color) return; 94 | _statusBarColor = color; 95 | style = _dark ? 1 : 2; 96 | } 97 | 98 | PlatformNavigation.SetStatusBar(_statusBarColor, style); 99 | } 100 | 101 | } -------------------------------------------------------------------------------- /Masa.Blazor.MauiDemo/MainPage.xaml: -------------------------------------------------------------------------------- 1 |  2 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /Masa.Blazor.MauiDemo/MainPage.xaml.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Maui.Controls.PlatformConfiguration.AndroidSpecific; 2 | 3 | namespace Masa.Blazor.MauiDemo; 4 | 5 | public partial class MainPage : ContentPage 6 | { 7 | public MainPage() 8 | { 9 | InitializeComponent(); 10 | 11 | App.Current.On().UseWindowSoftInputModeAdjust(WindowSoftInputModeAdjust.Resize); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /Masa.Blazor.MauiDemo/Masa.Blazor.MauiDemo.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | net8.0-android;net8.0-ios;net8.0-maccatalyst 5 | $(TargetFrameworks);net8.0-windows10.0.19041.0 6 | 7 | 8 | 9 | 14 | 15 | 16 | Exe 17 | Masa.Blazor.MauiDemo 18 | true 19 | true 20 | enable 21 | false 22 | enable 23 | 24 | 25 | Masa.Blazor.MauiDemo 26 | 27 | 28 | com.companyname.masa.blazor.mauidemo 29 | 30 | 31 | 1.0 32 | 1 33 | 34 | 14.2 35 | 14.0 36 | 24.0 37 | 10.0.17763.0 38 | 10.0.17763.0 39 | 6.5 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | -------------------------------------------------------------------------------- /Masa.Blazor.MauiDemo/MauiPlatformIntegration.cs: -------------------------------------------------------------------------------- 1 | using CommunityToolkit.Maui.Core; 2 | using Masa.Blazor.MauiDemo.Platforms; 3 | using System.Text.Json; 4 | 5 | namespace Masa.Blazor.MauiDemo; 6 | 7 | public class MauiPlatformIntegration : IPlatformIntegration 8 | { 9 | private readonly MasaBlazor _masaBlazor; 10 | 11 | public MauiPlatformIntegration(MasaBlazor masaBlazor) 12 | { 13 | _masaBlazor = masaBlazor; 14 | Microsoft.Maui.Controls.Application.Current.RequestedThemeChanged += CurrentOnRequestedThemeChanged; 15 | } 16 | 17 | public event EventHandler? AppThemeChanged; 18 | 19 | private void CurrentOnRequestedThemeChanged(object? sender, AppThemeChangedEventArgs e) 20 | { 21 | Console.Out.WriteLine($"[MauiPlatformIntegration] CurrentOnRequestedThemeChanged {e.RequestedTheme}"); 22 | 23 | var theme = (int)e.RequestedTheme; 24 | 25 | Preferences.Default.Set(AppThemeKey, theme); 26 | 27 | AppThemeChanged?.Invoke(sender, theme); 28 | } 29 | 30 | public async Task GetCachedCoordinateAsync() 31 | { 32 | var location = await GetCachedLocationAsync(); 33 | if (location is null) 34 | { 35 | return null; 36 | } 37 | 38 | return new GeoCoordinate() 39 | { 40 | Latitude = location.Latitude, 41 | Longitude = location.Longitude, 42 | Altitude = location.Altitude 43 | }; 44 | } 45 | 46 | public async Task GetCurrentCoordinateAsync() 47 | { 48 | var location = await GetCurrentLocationAsync(); 49 | if (location is null) 50 | { 51 | return null; 52 | } 53 | 54 | return new GeoCoordinate() 55 | { 56 | Latitude = location.Latitude, 57 | Longitude = location.Longitude, 58 | Altitude = location.Altitude 59 | }; 60 | } 61 | 62 | public void SetStatusBar(System.Drawing.Color color, int style) 63 | { 64 | throw new NotImplementedException(); 65 | } 66 | 67 | // inherit 68 | public void SetStatusBar(string argb, int style) 69 | { 70 | if (OperatingSystem.IsAndroidVersionAtLeast(23) || OperatingSystem.IsIOS()) 71 | { 72 | CommunityToolkit.Maui.Core.Platform.StatusBar.SetColor(Color.FromArgb(argb)); 73 | CommunityToolkit.Maui.Core.Platform.StatusBar.SetStyle((StatusBarStyle)style); 74 | } 75 | } 76 | 77 | private const string AppThemeKey = "AppTheme"; 78 | 79 | public Task SetThemeAsync(int theme) 80 | { 81 | Microsoft.Maui.Controls.Application.Current.UserAppTheme = (AppTheme)theme; 82 | 83 | if (theme == 0) 84 | { 85 | var appTheme = Microsoft.Maui.Controls.Application.Current.RequestedTheme; 86 | _masaBlazor.SetTheme(appTheme == AppTheme.Dark); 87 | } 88 | else 89 | { 90 | _masaBlazor.SetTheme(theme == 2); 91 | } 92 | 93 | Preferences.Default.Set(AppThemeKey, theme); 94 | 95 | return Task.CompletedTask; 96 | } 97 | 98 | public Task InitThemeAsync() 99 | { 100 | var result = Preferences.Default.Get(AppThemeKey, -1); 101 | var isDark = result < 1 102 | ? Microsoft.Maui.Controls.Application.Current.RequestedTheme == AppTheme.Dark 103 | : result == 2; 104 | _masaBlazor.SetTheme(isDark); 105 | return Task.CompletedTask; 106 | } 107 | 108 | public ValueTask GetThemeAsync() 109 | { 110 | var result = Preferences.Default.Get(AppThemeKey, -1); 111 | return new ValueTask(result == -1 112 | ? (int)Microsoft.Maui.Controls.Application.Current.RequestedTheme 113 | : result); 114 | } 115 | 116 | public ValueTask IsDarkThemeOfSystemAsync() 117 | { 118 | return ValueTask.FromResult(Microsoft.Maui.Controls.Application.Current.RequestedTheme == AppTheme.Dark); 119 | } 120 | 121 | public Task SetCacheAsync(string key, TValue value) 122 | { 123 | var type = typeof(TValue); 124 | if (type != typeof(string) && type.IsClass) 125 | { 126 | var jsonValue = JsonSerializer.Serialize(value); 127 | Preferences.Default.Set(key, jsonValue); 128 | } 129 | else 130 | { 131 | Preferences.Default.Set(key, value); 132 | } 133 | 134 | return Task.CompletedTask; 135 | } 136 | 137 | public Task GetCacheAsync(string key, TValue defaultValue) 138 | { 139 | var type = typeof(TValue); 140 | if (type != typeof(string) && type.IsClass) 141 | { 142 | var jsonValue = Preferences.Default.Get(key, string.Empty); 143 | if (string.IsNullOrEmpty(jsonValue)) 144 | { 145 | return Task.FromResult(defaultValue); 146 | } 147 | 148 | return Task.FromResult(JsonSerializer.Deserialize(jsonValue)); 149 | } 150 | 151 | return Task.FromResult(Preferences.Default.Get(key, defaultValue)); 152 | } 153 | 154 | public Task RemoveCacheAsync(string key) 155 | { 156 | Preferences.Default.Remove(key); 157 | return Task.CompletedTask; 158 | } 159 | 160 | private async Task GetCachedLocationAsync() 161 | { 162 | try 163 | { 164 | return await Geolocation.Default.GetLastKnownLocationAsync(); 165 | } 166 | catch (FeatureNotSupportedException fnsEx) 167 | { 168 | // Handle not supported on device exception 169 | } 170 | catch (FeatureNotEnabledException fneEx) 171 | { 172 | // Handle not enabled on device exception 173 | } 174 | catch (PermissionException pEx) 175 | { 176 | // Handle permission exception 177 | } 178 | catch (Exception ex) 179 | { 180 | // Unable to get location 181 | } 182 | 183 | return null; 184 | } 185 | 186 | private CancellationTokenSource? _cancelTokenSource; 187 | private bool _isCheckingLocation; 188 | 189 | private async Task GetCurrentLocationAsync() 190 | { 191 | try 192 | { 193 | _isCheckingLocation = true; 194 | 195 | var request = new GeolocationRequest(GeolocationAccuracy.Medium, TimeSpan.FromSeconds(10)); 196 | _cancelTokenSource = new CancellationTokenSource(); 197 | 198 | return await Geolocation.Default.GetLocationAsync(request, _cancelTokenSource.Token); 199 | } 200 | // Catch one of the following exceptions: 201 | // FeatureNotSupportedException 202 | // FeatureNotEnabledException 203 | // PermissionException 204 | catch (Exception ex) 205 | { 206 | // Unable to get location 207 | } 208 | finally 209 | { 210 | _isCheckingLocation = false; 211 | } 212 | 213 | return null; 214 | } 215 | 216 | private void CancelRequest() 217 | { 218 | if (_isCheckingLocation && _cancelTokenSource != null && _cancelTokenSource.IsCancellationRequested == false) 219 | _cancelTokenSource.Cancel(); 220 | } 221 | } -------------------------------------------------------------------------------- /Masa.Blazor.MauiDemo/MauiProgram.cs: -------------------------------------------------------------------------------- 1 | using Masa.Blazor.MauiDemo.Platforms; 2 | using Masa.Blazor.MauiDemo.Rcl.Data; 3 | using Masa.Blazor.MauiDemo.Rcl.Services; 4 | using Microsoft.Extensions.Logging; 5 | 6 | namespace Masa.Blazor.MauiDemo; 7 | 8 | public static class MauiProgram 9 | { 10 | public static MauiApp CreateMauiApp() 11 | { 12 | var builder = MauiApp.CreateBuilder(); 13 | builder 14 | .UseMauiApp() 15 | .UseMauiCommunityToolkit() 16 | .ConfigureFonts(fonts => 17 | { 18 | fonts.AddFont("OpenSans-Regular.ttf", "OpenSansRegular"); 19 | }); 20 | 21 | builder.Services.AddMauiBlazorWebView(); 22 | builder.Services.AddMasaBlazorMauiDemo(masaBlazorServiceLifetime: ServiceLifetime.Singleton); 23 | builder.Services.AddSingleton(_ = new ProDatabase(FileSystem.AppDataDirectory)); 24 | builder.Services.AddSingleton(); 25 | builder.Services.AddSingleton(); 26 | 27 | builder.Services.AddLogging(logging => 28 | { 29 | logging.AddFilter("Microsoft.AspNetCore.Components.WebView", LogLevel.Trace); 30 | }); 31 | #if DEBUG 32 | builder.Services.AddBlazorWebViewDeveloperTools(); 33 | builder.Logging.AddDebug(); 34 | #endif 35 | 36 | return builder.Build(); 37 | } 38 | } -------------------------------------------------------------------------------- /Masa.Blazor.MauiDemo/Platforms/Android/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /Masa.Blazor.MauiDemo/Platforms/Android/MainActivity.cs: -------------------------------------------------------------------------------- 1 | using Android.App; 2 | using Android.Content.PM; 3 | using Android.OS; 4 | 5 | namespace Masa.Blazor.MauiDemo; 6 | 7 | [Activity(Theme = "@style/Maui.SplashTheme", MainLauncher = true, ConfigurationChanges = ConfigChanges.ScreenSize | ConfigChanges.Orientation | ConfigChanges.UiMode | ConfigChanges.ScreenLayout | ConfigChanges.SmallestScreenSize | ConfigChanges.Density)] 8 | public class MainActivity : MauiAppCompatActivity 9 | { 10 | } 11 | -------------------------------------------------------------------------------- /Masa.Blazor.MauiDemo/Platforms/Android/MainApplication.cs: -------------------------------------------------------------------------------- 1 | using Android.App; 2 | using Android.Runtime; 3 | 4 | [assembly: UsesPermission(Android.Manifest.Permission.AccessCoarseLocation)] 5 | [assembly: UsesPermission(Android.Manifest.Permission.AccessFineLocation)] 6 | [assembly: UsesFeature("android.hardware.location", Required = false)] 7 | [assembly: UsesFeature("android.hardware.location.gps", Required = false)] 8 | [assembly: UsesFeature("android.hardware.location.network", Required = false)] 9 | 10 | namespace Masa.Blazor.MauiDemo; 11 | 12 | [Application] 13 | public class MainApplication : MauiApplication 14 | { 15 | public MainApplication(IntPtr handle, JniHandleOwnership ownership) 16 | : base(handle, ownership) 17 | { 18 | } 19 | 20 | protected override MauiApp CreateMauiApp() => MauiProgram.CreateMauiApp(); 21 | } 22 | -------------------------------------------------------------------------------- /Masa.Blazor.MauiDemo/Platforms/Android/Resources/values/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | #512BD4 4 | #2B0B98 5 | #2B0B98 6 | -------------------------------------------------------------------------------- /Masa.Blazor.MauiDemo/Platforms/MacCatalyst/AppDelegate.cs: -------------------------------------------------------------------------------- 1 | using Foundation; 2 | 3 | namespace Masa.Blazor.MauiDemo; 4 | 5 | [Register("AppDelegate")] 6 | public class AppDelegate : MauiUIApplicationDelegate 7 | { 8 | protected override MauiApp CreateMauiApp() => MauiProgram.CreateMauiApp(); 9 | } 10 | -------------------------------------------------------------------------------- /Masa.Blazor.MauiDemo/Platforms/MacCatalyst/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | UIDeviceFamily 6 | 7 | 1 8 | 2 9 | 10 | UIRequiredDeviceCapabilities 11 | 12 | arm64 13 | 14 | UISupportedInterfaceOrientations 15 | 16 | UIInterfaceOrientationPortrait 17 | UIInterfaceOrientationLandscapeLeft 18 | UIInterfaceOrientationLandscapeRight 19 | 20 | UISupportedInterfaceOrientations~ipad 21 | 22 | UIInterfaceOrientationPortrait 23 | UIInterfaceOrientationPortraitUpsideDown 24 | UIInterfaceOrientationLandscapeLeft 25 | UIInterfaceOrientationLandscapeRight 26 | 27 | XSAppIconAssets 28 | Assets.xcassets/appicon.appiconset 29 | 30 | 31 | -------------------------------------------------------------------------------- /Masa.Blazor.MauiDemo/Platforms/MacCatalyst/Program.cs: -------------------------------------------------------------------------------- 1 | using ObjCRuntime; 2 | using UIKit; 3 | 4 | namespace Masa.Blazor.MauiDemo; 5 | 6 | public class Program 7 | { 8 | // This is the main entry point of the application. 9 | static void Main(string[] args) 10 | { 11 | // if you want to use a different Application Delegate class from "AppDelegate" 12 | // you can specify it here. 13 | UIApplication.Main(args, null, typeof(AppDelegate)); 14 | } 15 | } -------------------------------------------------------------------------------- /Masa.Blazor.MauiDemo/Platforms/Tizen/Main.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Microsoft.Maui; 3 | using Microsoft.Maui.Hosting; 4 | 5 | namespace Masa.Blazor.MauiDemo; 6 | 7 | class Program : MauiApplication 8 | { 9 | protected override MauiApp CreateMauiApp() => MauiProgram.CreateMauiApp(); 10 | 11 | static void Main(string[] args) 12 | { 13 | var app = new Program(); 14 | app.Run(args); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /Masa.Blazor.MauiDemo/Platforms/Tizen/tizen-manifest.xml: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | maui-appicon-placeholder 7 | 8 | 9 | 10 | 11 | http://tizen.org/privilege/internet 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /Masa.Blazor.MauiDemo/Platforms/Windows/App.xaml: -------------------------------------------------------------------------------- 1 |  7 | 8 | 9 | -------------------------------------------------------------------------------- /Masa.Blazor.MauiDemo/Platforms/Windows/App.xaml.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.UI.Xaml; 2 | 3 | // To learn more about WinUI, the WinUI project structure, 4 | // and more about our project templates, see: http://aka.ms/winui-project-info. 5 | 6 | namespace Masa.Blazor.MauiDemo.WinUI; 7 | 8 | /// 9 | /// Provides application-specific behavior to supplement the default Application class. 10 | /// 11 | public partial class App : MauiWinUIApplication 12 | { 13 | /// 14 | /// Initializes the singleton application object. This is the first line of authored code 15 | /// executed, and as such is the logical equivalent of main() or WinMain(). 16 | /// 17 | public App() 18 | { 19 | this.InitializeComponent(); 20 | } 21 | 22 | protected override MauiApp CreateMauiApp() => MauiProgram.CreateMauiApp(); 23 | } 24 | 25 | -------------------------------------------------------------------------------- /Masa.Blazor.MauiDemo/Platforms/Windows/Package.appxmanifest: -------------------------------------------------------------------------------- 1 |  2 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | $placeholder$ 15 | User Name 16 | $placeholder$.png 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | -------------------------------------------------------------------------------- /Masa.Blazor.MauiDemo/Platforms/Windows/app.manifest: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 11 | true/PM 12 | PerMonitorV2, PerMonitor 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /Masa.Blazor.MauiDemo/Platforms/iOS/AppDelegate.cs: -------------------------------------------------------------------------------- 1 | using Foundation; 2 | 3 | namespace Masa.Blazor.MauiDemo; 4 | 5 | [Register("AppDelegate")] 6 | public class AppDelegate : MauiUIApplicationDelegate 7 | { 8 | protected override MauiApp CreateMauiApp() => MauiProgram.CreateMauiApp(); 9 | } 10 | -------------------------------------------------------------------------------- /Masa.Blazor.MauiDemo/Platforms/iOS/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | LSRequiresIPhoneOS 6 | 7 | UIDeviceFamily 8 | 9 | 1 10 | 2 11 | 12 | UIRequiredDeviceCapabilities 13 | 14 | arm64 15 | 16 | UISupportedInterfaceOrientations 17 | 18 | UIInterfaceOrientationPortrait 19 | UIInterfaceOrientationLandscapeLeft 20 | UIInterfaceOrientationLandscapeRight 21 | 22 | UISupportedInterfaceOrientations~ipad 23 | 24 | UIInterfaceOrientationPortrait 25 | UIInterfaceOrientationPortraitUpsideDown 26 | UIInterfaceOrientationLandscapeLeft 27 | UIInterfaceOrientationLandscapeRight 28 | 29 | XSAppIconAssets 30 | Assets.xcassets/appicon.appiconset 31 | UIViewControllerBasedStatusBarAppearance 32 | 33 | 34 | 35 | -------------------------------------------------------------------------------- /Masa.Blazor.MauiDemo/Platforms/iOS/Program.cs: -------------------------------------------------------------------------------- 1 | using ObjCRuntime; 2 | using UIKit; 3 | 4 | namespace Masa.Blazor.MauiDemo; 5 | 6 | public class Program 7 | { 8 | // This is the main entry point of the application. 9 | static void Main(string[] args) 10 | { 11 | // if you want to use a different Application Delegate class from "AppDelegate" 12 | // you can specify it here. 13 | UIApplication.Main(args, null, typeof(AppDelegate)); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /Masa.Blazor.MauiDemo/Properties/launchSettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "profiles": { 3 | "Windows Machine": { 4 | "commandName": "MsixPackage", 5 | "nativeDebugging": false 6 | } 7 | } 8 | } -------------------------------------------------------------------------------- /Masa.Blazor.MauiDemo/Resources/AppIcon/appicon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /Masa.Blazor.MauiDemo/Resources/AppIcon/appiconfg.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /Masa.Blazor.MauiDemo/Resources/Fonts/OpenSans-Regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/masastack/Masa.Blazor.MauiDemo/0f8476be760ee03604444edeafb4d7341e17d7e0/Masa.Blazor.MauiDemo/Resources/Fonts/OpenSans-Regular.ttf -------------------------------------------------------------------------------- /Masa.Blazor.MauiDemo/Resources/Images/dotnet_bot.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | -------------------------------------------------------------------------------- /Masa.Blazor.MauiDemo/Resources/Raw/AboutAssets.txt: -------------------------------------------------------------------------------- 1 | Any raw assets you want to be deployed with your application can be placed in 2 | this directory (and child directories). Deployment of the asset to your application 3 | is automatically handled by the following `MauiAsset` Build Action within your `.csproj`. 4 | 5 | 6 | 7 | These files will be deployed with you package and will be accessible using Essentials: 8 | 9 | async Task LoadMauiAsset() 10 | { 11 | using var stream = await FileSystem.OpenAppPackageFileAsync("AboutAssets.txt"); 12 | using var reader = new StreamReader(stream); 13 | 14 | var contents = reader.ReadToEnd(); 15 | } 16 | -------------------------------------------------------------------------------- /Masa.Blazor.MauiDemo/Resources/Splash/splash.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /Masa.Blazor.MauiDemo/_Imports.cs: -------------------------------------------------------------------------------- 1 | // Global using directives 2 | 3 | global using CommunityToolkit.Maui; -------------------------------------------------------------------------------- /Masa.Blazor.MauiDemo/_Imports.razor: -------------------------------------------------------------------------------- 1 | @using System.Net.Http 2 | @using Microsoft.AspNetCore.Components.Forms 3 | @using Microsoft.AspNetCore.Components.Routing 4 | @using Microsoft.AspNetCore.Components.Web 5 | @using Microsoft.AspNetCore.Components.Web.Virtualization 6 | @using Microsoft.JSInterop 7 | @using Masa.Blazor.MauiDemo 8 | @using Masa.Blazor.MauiDemo.Rcl.Shared 9 | @using Microsoft.AspNetCore.Components.Authorization -------------------------------------------------------------------------------- /Masa.Blazor.MauiDemo/wwwroot/css/app.css: -------------------------------------------------------------------------------- 1 | html, body { 2 | font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; 3 | } 4 | 5 | h1:focus { 6 | outline: none; 7 | } 8 | 9 | #blazor-error-ui { 10 | background: lightyellow; 11 | bottom: 0; 12 | box-shadow: 0 -1px 2px rgba(0, 0, 0, 0.2); 13 | display: none; 14 | left: 0; 15 | padding: 0.6rem 1.25rem 0.7rem 1.25rem; 16 | position: fixed; 17 | width: 100%; 18 | z-index: 1000; 19 | } 20 | 21 | #blazor-error-ui .dismiss { 22 | cursor: pointer; 23 | position: absolute; 24 | right: 0.75rem; 25 | top: 0.5rem; 26 | } 27 | 28 | .blazor-error-boundary { 29 | background: url(data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iNTYiIGhlaWdodD0iNDkiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiIG92ZXJmbG93PSJoaWRkZW4iPjxkZWZzPjxjbGlwUGF0aCBpZD0iY2xpcDAiPjxyZWN0IHg9IjIzNSIgeT0iNTEiIHdpZHRoPSI1NiIgaGVpZ2h0PSI0OSIvPjwvY2xpcFBhdGg+PC9kZWZzPjxnIGNsaXAtcGF0aD0idXJsKCNjbGlwMCkiIHRyYW5zZm9ybT0idHJhbnNsYXRlKC0yMzUgLTUxKSI+PHBhdGggZD0iTTI2My41MDYgNTFDMjY0LjcxNyA1MSAyNjUuODEzIDUxLjQ4MzcgMjY2LjYwNiA1Mi4yNjU4TDI2Ny4wNTIgNTIuNzk4NyAyNjcuNTM5IDUzLjYyODMgMjkwLjE4NSA5Mi4xODMxIDI5MC41NDUgOTIuNzk1IDI5MC42NTYgOTIuOTk2QzI5MC44NzcgOTMuNTEzIDI5MSA5NC4wODE1IDI5MSA5NC42NzgyIDI5MSA5Ny4wNjUxIDI4OS4wMzggOTkgMjg2LjYxNyA5OUwyNDAuMzgzIDk5QzIzNy45NjMgOTkgMjM2IDk3LjA2NTEgMjM2IDk0LjY3ODIgMjM2IDk0LjM3OTkgMjM2LjAzMSA5NC4wODg2IDIzNi4wODkgOTMuODA3MkwyMzYuMzM4IDkzLjAxNjIgMjM2Ljg1OCA5Mi4xMzE0IDI1OS40NzMgNTMuNjI5NCAyNTkuOTYxIDUyLjc5ODUgMjYwLjQwNyA1Mi4yNjU4QzI2MS4yIDUxLjQ4MzcgMjYyLjI5NiA1MSAyNjMuNTA2IDUxWk0yNjMuNTg2IDY2LjAxODNDMjYwLjczNyA2Ni4wMTgzIDI1OS4zMTMgNjcuMTI0NSAyNTkuMzEzIDY5LjMzNyAyNTkuMzEzIDY5LjYxMDIgMjU5LjMzMiA2OS44NjA4IDI1OS4zNzEgNzAuMDg4N0wyNjEuNzk1IDg0LjAxNjEgMjY1LjM4IDg0LjAxNjEgMjY3LjgyMSA2OS43NDc1QzI2Ny44NiA2OS43MzA5IDI2Ny44NzkgNjkuNTg3NyAyNjcuODc5IDY5LjMxNzkgMjY3Ljg3OSA2Ny4xMTgyIDI2Ni40NDggNjYuMDE4MyAyNjMuNTg2IDY2LjAxODNaTTI2My41NzYgODYuMDU0N0MyNjEuMDQ5IDg2LjA1NDcgMjU5Ljc4NiA4Ny4zMDA1IDI1OS43ODYgODkuNzkyMSAyNTkuNzg2IDkyLjI4MzcgMjYxLjA0OSA5My41Mjk1IDI2My41NzYgOTMuNTI5NSAyNjYuMTE2IDkzLjUyOTUgMjY3LjM4NyA5Mi4yODM3IDI2Ny4zODcgODkuNzkyMSAyNjcuMzg3IDg3LjMwMDUgMjY2LjExNiA4Ni4wNTQ3IDI2My41NzYgODYuMDU0N1oiIGZpbGw9IiNGRkU1MDAiIGZpbGwtcnVsZT0iZXZlbm9kZCIvPjwvZz48L3N2Zz4=) no-repeat 1rem/1.8rem, #b32121; 30 | padding: 1rem 1rem 1rem 3.7rem; 31 | color: white; 32 | } 33 | 34 | .blazor-error-boundary::after { 35 | content: "An error has occurred." 36 | } 37 | 38 | .status-bar-safe-area { 39 | display: none; 40 | } 41 | 42 | @supports (-webkit-touch-callout: none) { 43 | .status-bar-safe-area { 44 | display: flex; 45 | position: sticky; 46 | top: 0; 47 | height: env(safe-area-inset-top); 48 | background-color: #f7f7f7; 49 | width: 100%; 50 | z-index: 1; 51 | } 52 | 53 | .flex-column, .navbar-brand { 54 | padding-left: env(safe-area-inset-left); 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /Masa.Blazor.MauiDemo/wwwroot/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/masastack/Masa.Blazor.MauiDemo/0f8476be760ee03604444edeafb4d7341e17d7e0/Masa.Blazor.MauiDemo/wwwroot/favicon.ico -------------------------------------------------------------------------------- /Masa.Blazor.MauiDemo/wwwroot/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Masa.Blazor.MauiDemo 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 |
18 | 19 |
Loading...
20 | 21 |
22 | An unhandled error has occurred. 23 | Reload 24 | 🗙 25 |
26 | 27 | 28 | 29 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # MAUI hybrid with MASA Blazor Sample App 2 | 3 | Provides a best practice example of how to integrate MAUI hybrid with MASA Blazor. 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 |
-------------------------------------------------------------------------------- /imgs/login.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/masastack/Masa.Blazor.MauiDemo/0f8476be760ee03604444edeafb4d7341e17d7e0/imgs/login.png -------------------------------------------------------------------------------- /imgs/shop.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/masastack/Masa.Blazor.MauiDemo/0f8476be760ee03604444edeafb4d7341e17d7e0/imgs/shop.png -------------------------------------------------------------------------------- /imgs/signup.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/masastack/Masa.Blazor.MauiDemo/0f8476be760ee03604444edeafb4d7341e17d7e0/imgs/signup.png -------------------------------------------------------------------------------- /imgs/signup2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/masastack/Masa.Blazor.MauiDemo/0f8476be760ee03604444edeafb4d7341e17d7e0/imgs/signup2.png -------------------------------------------------------------------------------- /imgs/todo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/masastack/Masa.Blazor.MauiDemo/0f8476be760ee03604444edeafb4d7341e17d7e0/imgs/todo.png -------------------------------------------------------------------------------- /imgs/user.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/masastack/Masa.Blazor.MauiDemo/0f8476be760ee03604444edeafb4d7341e17d7e0/imgs/user.png --------------------------------------------------------------------------------