Skip to content

Commit d0b0a6b

Browse files
sharpninjaCopilot
andcommitted
Add Android project, CQRS infrastructure, and three-project architecture
- Create RequestTracker.Core shared library with all Models, Services, ViewModels, Converters, CQRS infrastructure, and Behaviors - Create RequestTracker.Desktop project with zero-code-behind views (using TappedBehavior, DoubleTappedBehavior, KeyDownBehavior) - Create RequestTracker.Android project with Phone (NavigationView) and Tablet (desktop-like) views, device form factor detection - Add CQRS pattern: ICommand/IQuery interfaces, Mediator, 17 command handlers that accept ViewModels to update - Add NavigationViewItemInvokedBehavior for FluentAvalonia NavigationView - Add phone navigation section switching (Sessions/Viewer/History) - Android excludes chat window and AI assistant functionality - All RelayCommands delegate to public *Internal() methods callable by CQRS handlers - Update solution file with all three projects Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
1 parent 8430e47 commit d0b0a6b

70 files changed

Lines changed: 9024 additions & 0 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

RequestTracker.slnx

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
<Solution>
2+
<Folder Name="/src/">
3+
<Project Path="src/RequestTracker/RequestTracker.csproj" />
4+
<Project Path="src/RequestTracker.Core/RequestTracker.Core.csproj" />
5+
<Project Path="src/RequestTracker.Desktop/RequestTracker.Desktop.csproj" />
6+
<Project Path="src/RequestTracker.Android/RequestTracker.Android.csproj" />
7+
</Folder>
8+
</Solution>
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
<Application xmlns="https://github.com/avaloniaui"
2+
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
3+
xmlns:fa="using:FluentAvalonia.Styling"
4+
x:Class="RequestTracker.Android.App"
5+
RequestedThemeVariant="Light">
6+
7+
<Application.Styles>
8+
<StyleInclude Source="avares://Avalonia.Themes.Fluent/FluentTheme.axaml"/>
9+
<fa:FluentAvaloniaTheme />
10+
</Application.Styles>
11+
</Application>
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
using Avalonia;
2+
using Avalonia.Controls.ApplicationLifetimes;
3+
using Avalonia.Data.Core.Plugins;
4+
using Avalonia.Markup.Xaml;
5+
using RequestTracker.Android.Services;
6+
using RequestTracker.Android.Views;
7+
using RequestTracker.Core.ViewModels;
8+
using System;
9+
using System.Linq;
10+
11+
namespace RequestTracker.Android;
12+
13+
public partial class App : Application
14+
{
15+
public override void Initialize()
16+
{
17+
AvaloniaXamlLoader.Load(this);
18+
}
19+
20+
public override void OnFrameworkInitializationCompleted()
21+
{
22+
if (ApplicationLifetime is ISingleViewApplicationLifetime singleView)
23+
{
24+
DisableAvaloniaDataAnnotationValidation();
25+
26+
try
27+
{
28+
var clipboardService = new AndroidClipboardService();
29+
var vm = new MainWindowViewModel(clipboardService);
30+
singleView.MainView = DeviceFormFactor.IsTablet()
31+
? new TabletMainView { DataContext = vm }
32+
: (Avalonia.Controls.Control)new PhoneMainView { DataContext = vm };
33+
vm.InitializeAfterWindowShown();
34+
}
35+
catch (Exception ex)
36+
{
37+
Console.Error.WriteLine($"Android init failed: {ex}");
38+
}
39+
}
40+
41+
base.OnFrameworkInitializationCompleted();
42+
}
43+
44+
private void DisableAvaloniaDataAnnotationValidation()
45+
{
46+
var dataValidationPluginsToRemove =
47+
BindingPlugins.DataValidators.OfType<DataAnnotationsValidationPlugin>().ToArray();
48+
foreach (var plugin in dataValidationPluginsToRemove)
49+
BindingPlugins.DataValidators.Remove(plugin);
50+
}
51+
}
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
using Android.App;
2+
using Android.Content.PM;
3+
using Avalonia;
4+
using Avalonia.Android;
5+
6+
namespace RequestTracker.Android;
7+
8+
[Activity(
9+
Label = "RequestTracker",
10+
Theme = "@style/MyTheme.NoActionBar",
11+
Icon = "@drawable/icon",
12+
MainLauncher = true,
13+
ConfigurationChanges = ConfigChanges.Orientation | ConfigChanges.ScreenSize | ConfigChanges.UiMode)]
14+
public class MainActivity : AvaloniaMainActivity<App>
15+
{
16+
protected override AppBuilder CustomizeAppBuilder(AppBuilder builder)
17+
{
18+
return base.CustomizeAppBuilder(builder)
19+
.WithInterFont();
20+
}
21+
}
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
<Project Sdk="Microsoft.NET.Sdk">
2+
<PropertyGroup>
3+
<TargetFramework>net9.0-android</TargetFramework>
4+
<Nullable>enable</Nullable>
5+
<ApplicationId>com.requesttracker.android</ApplicationId>
6+
<ApplicationVersion>1</ApplicationVersion>
7+
<ApplicationDisplayVersion>1.0</ApplicationDisplayVersion>
8+
<AndroidPackageFormat>apk</AndroidPackageFormat>
9+
<AvaloniaUseCompiledBindingsByDefault>true</AvaloniaUseCompiledBindingsByDefault>
10+
</PropertyGroup>
11+
12+
<ItemGroup>
13+
<PackageReference Include="Avalonia" Version="11.2.6" />
14+
<PackageReference Include="Avalonia.Android" Version="11.2.6" />
15+
<PackageReference Include="Avalonia.Themes.Fluent" Version="11.2.6" />
16+
<PackageReference Include="Avalonia.Fonts.Inter" Version="11.2.6" />
17+
<PackageReference Include="FluentAvaloniaUI" Version="2.3.0" />
18+
</ItemGroup>
19+
20+
<ItemGroup>
21+
<ProjectReference Include="..\RequestTracker.Core\RequestTracker.Core.csproj" />
22+
</ItemGroup>
23+
</Project>
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
<?xml version="1.0" encoding="utf-8"?>
2+
<vector xmlns:android="http://schemas.android.com/apk/res/android"
3+
android:width="108dp"
4+
android:height="108dp"
5+
android:viewportWidth="108"
6+
android:viewportHeight="108">
7+
<path
8+
android:fillColor="#1976d2"
9+
android:pathData="M54,54m-40,0a40,40 0,1 1,80 0a40,40 0,1 1,-80 0"/>
10+
<path
11+
android:fillColor="#FFFFFF"
12+
android:pathData="M40,35h28v5H45v8h18v5H45v12h-5z"/>
13+
</vector>
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
<?xml version="1.0" encoding="utf-8"?>
2+
<resources>
3+
<style name="MyTheme.NoActionBar" parent="@android:style/Theme.Material.Light.NoActionBar">
4+
<item name="android:colorPrimary">#1976d2</item>
5+
<item name="android:colorPrimaryDark">#004ba0</item>
6+
<item name="android:colorAccent">#63a4ff</item>
7+
</style>
8+
</resources>
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
using System.Threading.Tasks;
2+
using RequestTracker.Core.Services;
3+
4+
namespace RequestTracker.Android.Services;
5+
6+
/// <summary>Clipboard service for Android. Uses Android ClipboardManager.</summary>
7+
public class AndroidClipboardService : IClipboardService
8+
{
9+
public Task SetTextAsync(string text)
10+
{
11+
var context = global::Android.App.Application.Context;
12+
var clipboard = (global::Android.Content.ClipboardManager?)
13+
context.GetSystemService(global::Android.Content.Context.ClipboardService);
14+
if (clipboard != null)
15+
{
16+
var clip = global::Android.Content.ClipData.NewPlainText("RequestTracker", text);
17+
clipboard.PrimaryClip = clip;
18+
}
19+
return Task.CompletedTask;
20+
}
21+
}
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
using Android.Content.Res;
2+
using Android.Util;
3+
4+
namespace RequestTracker.Android.Services;
5+
6+
/// <summary>Detects phone vs tablet form factor based on screen size.</summary>
7+
public static class DeviceFormFactor
8+
{
9+
/// <summary>Returns true if the device screen is ≥ 600dp wide (tablet threshold).</summary>
10+
public static bool IsTablet()
11+
{
12+
try
13+
{
14+
var context = global::Android.App.Application.Context;
15+
var metrics = context.Resources?.DisplayMetrics;
16+
if (metrics == null) return false;
17+
18+
float widthDp = metrics.WidthPixels / metrics.Density;
19+
float heightDp = metrics.HeightPixels / metrics.Density;
20+
float smallestWidth = System.Math.Min(widthDp, heightDp);
21+
return smallestWidth >= 600;
22+
}
23+
catch
24+
{
25+
return false;
26+
}
27+
}
28+
}

0 commit comments

Comments
 (0)