Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
380 changes: 336 additions & 44 deletions .github/workflows/buildrelease.yml

Large diffs are not rendered by default.

132 changes: 132 additions & 0 deletions TFMAudioApp/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
# Build results
bin/
obj/
[Dd]ebug/
[Rr]elease/
x64/
x86/
[Aa][Rr][Mm]/
[Aa][Rr][Mm]64/
bld/
[Bb]in/
[Oo]bj/
[Ll]og/
[Ll]ogs/

# Visual Studio files
.vs/
*.suo
*.user
*.userosscache
*.sln.docstates
*.userprefs

# Rider files
.idea/

# VS Code files
.vscode/

# macOS
.DS_Store
.AppleDouble
.LSOverride

# Windows
Thumbs.db
ehthumbs.db
Desktop.ini

# MAUI / .NET specific
*.csproj.user
*.rsuser
*.svclog
*.scc
*.aps

# NuGet
*.nupkg
*.snupkg
**/[Pp]ackages/*
!**/[Pp]ackages/build/
*.nuget.props
*.nuget.targets

# Android
*.apk
*.aab
*.keystore
!tfmaudio-release.keystore
*.jks
local.properties
*.dex
*.class
gen/
proguard/

# iOS / macOS
*.ipa
*.dSYM.zip
*.dSYM
*.xcuserstate
*.xcworkspace
!*.xcworkspace/contents.xcworkspacedata
xcuserdata/
*.pbxuser
*.perspective
*.perspectivev3
*.mode1v3
*.mode2v3
Pods/
Podfile.lock

# Windows MSIX
*.msix
*.msixbundle
*.appx
*.appxbundle
*.appxupload

# Certificates and secrets
*.pfx
*.p12
*.cer
*.pem
certificates/
!certificates/.gitkeep

# Database files
*.db
*.db-shm
*.db-wal
*.sqlite
*.sqlite3

# Crash reports
crashlytics-build.properties
fabric.properties

# Test results
TestResults/
*.trx
*.coverage
*.coveragexml

# Publish output
publish/
artifacts/

# Temporary files
*.tmp
*.temp
*.bak
*.swp
*~

# Generated files
Generated/
*.g.cs
*.g.i.cs

# MlaunchLog
MlaunchLog/
14 changes: 14 additions & 0 deletions TFMAudioApp/App.xaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
<?xml version = "1.0" encoding = "UTF-8" ?>
<Application xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:local="clr-namespace:TFMAudioApp"
x:Class="TFMAudioApp.App">
<Application.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="Resources/Styles/Colors.xaml" />
<ResourceDictionary Source="Resources/Styles/Styles.xaml" />
</ResourceDictionary.MergedDictionaries>
</ResourceDictionary>
</Application.Resources>
</Application>
68 changes: 68 additions & 0 deletions TFMAudioApp/App.xaml.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
using TFMAudioApp.Helpers;
using TFMAudioApp.Services.Interfaces;
using TFMAudioApp.Views;

namespace TFMAudioApp;

public partial class App : Application
{
private readonly ISettingsService _settingsService;
private readonly IAudioPlayerService _audioPlayerService;

public App(ISettingsService settingsService, IAudioPlayerService audioPlayerService)
{
InitializeComponent();
_settingsService = settingsService;
_audioPlayerService = audioPlayerService;
}

protected override Window CreateWindow(IActivationState? activationState)
{
var window = new Window(new AppShell());

// Handle window closing (app terminated)
window.Destroying += OnWindowDestroying;

return window;
}

protected override async void OnStart()
{
base.OnStart();
await NavigateToStartPageAsync();
}

private void OnWindowDestroying(object? sender, EventArgs e)
{
// Stop playback when app window is destroyed
System.Diagnostics.Debug.WriteLine("[App] Window destroying - stopping audio player");
try
{
_audioPlayerService.StopAsync().ConfigureAwait(false);

// Dispose the player to clean up resources
if (_audioPlayerService is IDisposable disposable)
{
disposable.Dispose();
}
}
catch (Exception ex)
{
System.Diagnostics.Debug.WriteLine($"[App] Error stopping player on destroy: {ex.Message}");
}
}

private async Task NavigateToStartPageAsync()
{
var config = await _settingsService.GetServerConfigAsync();

if (config == null || !config.IsValid)
{
await Shell.Current.GoToAsync($"//{Constants.SetupRoute}");
}
else
{
await Shell.Current.GoToAsync($"//{Constants.HomeRoute}");
}
}
}
82 changes: 82 additions & 0 deletions TFMAudioApp/AppShell.xaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
<?xml version="1.0" encoding="UTF-8" ?>
<Shell
x:Class="TFMAudioApp.AppShell"
xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:views="clr-namespace:TFMAudioApp.Views"
xmlns:controls="clr-namespace:TFMAudioApp.Controls"
Title="TFM Audio"
FlyoutBehavior="Flyout"
Shell.BackgroundColor="{StaticResource PageBackgroundColor}"
Shell.FlyoutBackgroundColor="{StaticResource CardBackgroundColor}"
Shell.TitleColor="White"
Shell.ForegroundColor="White">

<!-- Mini Player Footer -->
<Shell.FlyoutFooter>
<controls:MiniPlayerControl x:Name="MiniPlayer"/>
</Shell.FlyoutFooter>

<Shell.FlyoutHeader>
<VerticalStackLayout Padding="20" Spacing="10" BackgroundColor="{StaticResource PageBackgroundColor}">
<Label Text="🎵" FontSize="48" HorizontalOptions="Center"/>
<Label Text="TFM Audio"
FontSize="24"
FontAttributes="Bold"
HorizontalOptions="Center"
TextColor="{StaticResource Primary}"/>
<!-- Connection Status Indicator -->
<HorizontalStackLayout HorizontalOptions="Center" Spacing="5" Margin="0,5,0,0">
<Label x:Name="ConnectionIndicator"
Text="●"
FontSize="12"
TextColor="{StaticResource Primary}"
VerticalOptions="Center"/>
<Label x:Name="ConnectionText"
Text="Connected"
FontSize="12"
TextColor="{StaticResource Gray400}"
VerticalOptions="Center"/>
</HorizontalStackLayout>
<BoxView HeightRequest="1" Color="{StaticResource Gray600}" Margin="0,10,0,0"/>
</VerticalStackLayout>
</Shell.FlyoutHeader>

<!-- Setup Page (not in flyout) -->
<ShellContent
Route="setup"
ContentTemplate="{DataTemplate views:SetupPage}"
FlyoutItemIsVisible="False"/>

<!-- Main Navigation -->
<FlyoutItem Title="Home" Icon="{OnPlatform Default='home.png'}">
<ShellContent
Route="home"
ContentTemplate="{DataTemplate views:HomePage}"/>
</FlyoutItem>

<FlyoutItem Title="Channels" Icon="{OnPlatform Default='channels.png'}">
<ShellContent
Route="channels"
ContentTemplate="{DataTemplate views:ChannelsPage}"/>
</FlyoutItem>

<FlyoutItem Title="Playlists" Icon="{OnPlatform Default='playlists.png'}">
<ShellContent
Route="playlists"
ContentTemplate="{DataTemplate views:PlaylistsPage}"/>
</FlyoutItem>

<FlyoutItem Title="Downloads" Icon="{OnPlatform Default='downloads.png'}">
<ShellContent
Route="downloads"
ContentTemplate="{DataTemplate views:DownloadsPage}"/>
</FlyoutItem>

<FlyoutItem Title="Settings" Icon="{OnPlatform Default='settings.png'}">
<ShellContent
Route="settings"
ContentTemplate="{DataTemplate views:SettingsPage}"/>
</FlyoutItem>

</Shell>
66 changes: 66 additions & 0 deletions TFMAudioApp/AppShell.xaml.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
using TFMAudioApp.Services.Interfaces;
using TFMAudioApp.Views;

namespace TFMAudioApp;

public partial class AppShell : Shell
{
private IConnectivityService? _connectivityService;

public AppShell()
{
InitializeComponent();

// Register routes for pages that are navigated to with parameters
Routing.RegisterRoute("playlistdetail", typeof(PlaylistDetailPage));
Routing.RegisterRoute("channeldetail", typeof(ChannelDetailPage));
Routing.RegisterRoute("folderdetail", typeof(FolderDetailPage));
Routing.RegisterRoute("player", typeof(PlayerPage));
}

protected override void OnHandlerChanged()
{
base.OnHandlerChanged();

if (Handler != null)
{
InitializeConnectivityService();
}
}

private void InitializeConnectivityService()
{
_connectivityService = Application.Current?.Handler?.MauiContext?.Services.GetService<IConnectivityService>();

if (_connectivityService != null)
{
_connectivityService.ConnectivityChanged += OnConnectivityChanged;
UpdateConnectionUI(_connectivityService.IsConnected);
}
}

private void OnConnectivityChanged(object? sender, bool isConnected)
{
MainThread.BeginInvokeOnMainThread(() =>
{
UpdateConnectionUI(isConnected);
});
}

private void UpdateConnectionUI(bool isConnected)
{
if (ConnectionIndicator != null && ConnectionText != null)
{
if (isConnected)
{
ConnectionIndicator.TextColor = Color.FromArgb("#22C55E"); // Green
ConnectionText.Text = "Online";
}
else
{
ConnectionIndicator.TextColor = Color.FromArgb("#EF4444"); // Red
ConnectionText.Text = "Offline";
}
}
}
}
Loading