feat: Implement Playlists and Programs Management
- Added PlaylistsGroupViewModel to manage playlists and selection. - Introduced ProgramsGroupViewModel for handling program groups and subscriptions. - Created ProgramsListViewModel to manage individual program listings. - Developed SettingsViewModel for user settings including playlist management. - Implemented TVPlayerViewModel as the main view model coordinating screens and data. - Added PlayerView for video playback with LibVLC integration. - Created XAML views for PlaylistsGroup, ProgramsGroup, ProgramsList, and Settings. - Added sample M3U playlist for testing. - Documented WPF build instructions and project structure in WPF-BUILD.md. - Configured global.json for .NET SDK versioning.
This commit is contained in:
@@ -0,0 +1,28 @@
|
||||
<Application xmlns="https://github.com/avaloniaui"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:vm="clr-namespace:TV_Player.AvaloniaApp.ViewModels"
|
||||
xmlns:views="clr-namespace:TV_Player.AvaloniaApp.Views"
|
||||
x:Class="TV_Player.AvaloniaApp.App"
|
||||
RequestedThemeVariant="Dark">
|
||||
<Application.Styles>
|
||||
<FluentTheme />
|
||||
</Application.Styles>
|
||||
|
||||
<Application.DataTemplates>
|
||||
<DataTemplate DataType="vm:PlaylistsGroupViewModel">
|
||||
<views:PlaylistsGroupView />
|
||||
</DataTemplate>
|
||||
<DataTemplate DataType="vm:ProgramsGroupViewModel">
|
||||
<views:ProgramsGroupView />
|
||||
</DataTemplate>
|
||||
<DataTemplate DataType="vm:ProgramsListViewModel">
|
||||
<views:ProgramsListView />
|
||||
</DataTemplate>
|
||||
<DataTemplate DataType="vm:PlayerViewModel">
|
||||
<views:PlayerView />
|
||||
</DataTemplate>
|
||||
<DataTemplate DataType="vm:SettingsViewModel">
|
||||
<views:SettingsView />
|
||||
</DataTemplate>
|
||||
</Application.DataTemplates>
|
||||
</Application>
|
||||
@@ -0,0 +1,29 @@
|
||||
using Avalonia;
|
||||
using Avalonia.Controls.ApplicationLifetimes;
|
||||
using Avalonia.Markup.Xaml;
|
||||
using TV_Player.AvaloniaApp.ViewModels;
|
||||
|
||||
namespace TV_Player.AvaloniaApp;
|
||||
|
||||
public partial class App : Application
|
||||
{
|
||||
public override void Initialize()
|
||||
{
|
||||
AvaloniaXamlLoader.Load(this);
|
||||
}
|
||||
|
||||
public override void OnFrameworkInitializationCompleted()
|
||||
{
|
||||
if (ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop)
|
||||
{
|
||||
desktop.MainWindow = new MainWindow
|
||||
{
|
||||
DataContext = TVPlayerViewModel.Instance.MainWindowViewModel
|
||||
};
|
||||
// Initialize screens after the Lazy singleton is fully constructed
|
||||
TVPlayerViewModel.Instance.Initialize();
|
||||
}
|
||||
|
||||
base.OnFrameworkInitializationCompleted();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
<Window xmlns="https://github.com/avaloniaui"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
x:Class="TV_Player.AvaloniaApp.MainWindow"
|
||||
mc:Ignorable="d"
|
||||
Width="1400"
|
||||
Height="860"
|
||||
MinWidth="980"
|
||||
MinHeight="640"
|
||||
Title="TV Player"
|
||||
WindowState="{Binding CurrentWindowState}"
|
||||
Background="#0B1220">
|
||||
<Grid RowDefinitions="Auto,*">
|
||||
<Border IsVisible="{Binding IsTopPanelVisible}"
|
||||
Background="#AA111827"
|
||||
BorderBrush="#33FFFFFF"
|
||||
BorderThickness="0,0,0,1"
|
||||
Padding="20,14">
|
||||
<Grid ColumnDefinitions="Auto,Auto,*,Auto,Auto">
|
||||
<Button Content="Settings" Command="{Binding SettingsCommand}" MinWidth="92" Margin="0,0,12,0" />
|
||||
<Button Grid.Column="1" Content="Back" Command="{Binding BackCommand}" MinWidth="92" Margin="0,0,12,0" />
|
||||
<TextBlock Grid.Column="2"
|
||||
Text="{Binding TopPanelTitle}"
|
||||
FontSize="26"
|
||||
FontWeight="SemiBold"
|
||||
VerticalAlignment="Center"
|
||||
HorizontalAlignment="Center" />
|
||||
<Button Grid.Column="3" Content="Fullscreen" Command="{Binding FullscreenCommand}" MinWidth="110" Margin="0,0,12,0" />
|
||||
<Button Grid.Column="4" Content="Close" Command="{Binding CloseAppCommand}" MinWidth="92" />
|
||||
</Grid>
|
||||
</Border>
|
||||
|
||||
<Border Grid.Row="1" Padding="20">
|
||||
<ContentControl Content="{Binding CurrentViewModel}" />
|
||||
</Border>
|
||||
</Grid>
|
||||
</Window>
|
||||
@@ -0,0 +1,29 @@
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Input;
|
||||
using TV_Player.AvaloniaApp.ViewModels;
|
||||
|
||||
namespace TV_Player.AvaloniaApp;
|
||||
|
||||
public partial class MainWindow : Window
|
||||
{
|
||||
public MainWindow()
|
||||
{
|
||||
InitializeComponent();
|
||||
KeyDown += OnKeyDown;
|
||||
}
|
||||
|
||||
private void OnKeyDown(object? sender, KeyEventArgs e)
|
||||
{
|
||||
if (DataContext is not MainWindowViewModel viewModel)
|
||||
return;
|
||||
|
||||
if (e.Key == Key.Escape)
|
||||
{
|
||||
viewModel.OnCloseAppButtonClick();
|
||||
}
|
||||
else if (e.Key == Key.Back)
|
||||
{
|
||||
viewModel.TriggerBack();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
using Avalonia;
|
||||
using System;
|
||||
|
||||
namespace TV_Player.AvaloniaApp;
|
||||
|
||||
internal static class Program
|
||||
{
|
||||
[STAThread]
|
||||
public static void Main(string[] args) => BuildAvaloniaApp().StartWithClassicDesktopLifetime(args);
|
||||
|
||||
public static AppBuilder BuildAvaloniaApp()
|
||||
=> AppBuilder.Configure<App>()
|
||||
.UsePlatformDetect()
|
||||
.LogToTrace();
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup>
|
||||
<OutputType>WinExe</OutputType>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<Nullable>enable</Nullable>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<RootNamespace>TV_Player</RootNamespace>
|
||||
<AssemblyName>TV Player Avalonia</AssemblyName>
|
||||
<AvaloniaUseCompiledBindingsByDefault>false</AvaloniaUseCompiledBindingsByDefault>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Avalonia" Version="11.2.5" />
|
||||
<PackageReference Include="Avalonia.Desktop" Version="11.2.5" />
|
||||
<PackageReference Include="Avalonia.Themes.Fluent" Version="11.2.5" />
|
||||
<PackageReference Include="CommunityToolkit.Mvvm" Version="8.4.0" />
|
||||
<PackageReference Include="LibVLCSharp" Version="3.9.6" />
|
||||
<PackageReference Include="LibVLCSharp.Avalonia" Version="3.9.6" />
|
||||
<PackageReference Include="Newtonsoft.Json" Version="13.0.4" />
|
||||
<PackageReference Include="System.Reactive" Version="6.0.2" />
|
||||
<PackageReference Include="VideoLAN.LibVLC.Mac" Version="3.1.3.1" Condition="$([MSBuild]::IsOSPlatform('macos'))" />
|
||||
<PackageReference Include="VideoLAN.LibVLC.Windows" Version="3.0.23" Condition="$([MSBuild]::IsOSPlatform('windows'))" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Compile Include="..\TV Player WPF\PlaylistWorker\GroupInfo.cs" Link="Shared\GroupInfo.cs" />
|
||||
<Compile Include="..\TV Player WPF\PlaylistWorker\M3UInfo.cs" Link="Shared\M3UInfo.cs" />
|
||||
<Compile Include="..\TV Player WPF\PlaylistWorker\M3UParser.cs" Link="Shared\M3UParser.cs" />
|
||||
<Compile Include="..\TV Player WPF\ViewModels\ObservableViewModelBase.cs" Link="Shared\ObservableViewModelBase.cs" />
|
||||
<Compile Include="..\TV Player WPF\ViewModels\ProgramsData.cs" Link="Shared\ProgramsData.cs" />
|
||||
<Compile Include="..\TV Player WPF\ViewModels\SettingsModel.cs" Link="Shared\SettingsModel.cs" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
@@ -0,0 +1,78 @@
|
||||
using Avalonia;
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Controls.ApplicationLifetimes;
|
||||
using CommunityToolkit.Mvvm.Input;
|
||||
using System.Windows.Input;
|
||||
|
||||
namespace TV_Player.AvaloniaApp.ViewModels;
|
||||
|
||||
public class MainWindowViewModel : TV_Player.ObservableViewModelBase
|
||||
{
|
||||
private object? _currentViewModel;
|
||||
public object? CurrentViewModel
|
||||
{
|
||||
get => _currentViewModel;
|
||||
set => SetProperty(ref _currentViewModel, value);
|
||||
}
|
||||
|
||||
private bool _isTopPanelVisible;
|
||||
public bool IsTopPanelVisible
|
||||
{
|
||||
get => _isTopPanelVisible;
|
||||
set => SetProperty(ref _isTopPanelVisible, value);
|
||||
}
|
||||
|
||||
private string _topPanelTitle = string.Empty;
|
||||
public string TopPanelTitle
|
||||
{
|
||||
get => _topPanelTitle;
|
||||
set => SetProperty(ref _topPanelTitle, value);
|
||||
}
|
||||
|
||||
private WindowState _currentWindowState = WindowState.Normal;
|
||||
public WindowState CurrentWindowState
|
||||
{
|
||||
get => _currentWindowState;
|
||||
set => SetProperty(ref _currentWindowState, value);
|
||||
}
|
||||
|
||||
public ICommand FullscreenCommand { get; }
|
||||
public ICommand CloseAppCommand { get; }
|
||||
public ICommand BackCommand { get; }
|
||||
public ICommand SettingsCommand { get; }
|
||||
|
||||
public Action? ButtonBackAction { get; set; }
|
||||
|
||||
public MainWindowViewModel()
|
||||
{
|
||||
BackCommand = new RelayCommand(TriggerBack);
|
||||
FullscreenCommand = new RelayCommand(OnFullScreenButtonClick);
|
||||
SettingsCommand = new RelayCommand(OnSettingsButtonClick);
|
||||
CloseAppCommand = new RelayCommand(OnCloseAppButtonClick);
|
||||
}
|
||||
|
||||
public void OnFullScreenButtonClick()
|
||||
{
|
||||
CurrentWindowState = CurrentWindowState == WindowState.FullScreen
|
||||
? WindowState.Normal
|
||||
: WindowState.FullScreen;
|
||||
}
|
||||
|
||||
public void OnCloseAppButtonClick()
|
||||
{
|
||||
if (Application.Current?.ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop)
|
||||
{
|
||||
desktop.Shutdown();
|
||||
}
|
||||
}
|
||||
|
||||
private void OnSettingsButtonClick()
|
||||
{
|
||||
TVPlayerViewModel.Instance.ShowSettingsScreen();
|
||||
}
|
||||
|
||||
public void TriggerBack()
|
||||
{
|
||||
ButtonBackAction?.Invoke();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,256 @@
|
||||
using CommunityToolkit.Mvvm.Input;
|
||||
using System.Diagnostics;
|
||||
using System.Reactive.Linq;
|
||||
using System.Windows.Input;
|
||||
|
||||
namespace TV_Player.AvaloniaApp.ViewModels;
|
||||
|
||||
public class PlayerViewModel : TV_Player.ObservableViewModelBase, IDisposable
|
||||
{
|
||||
private readonly IDisposable _programSubscription;
|
||||
private IDisposable? _programGuideDisposable;
|
||||
private IDisposable? _timer;
|
||||
private M3UInfo _currentProgram;
|
||||
private List<M3UInfo> _programs = new();
|
||||
private ProgramGuide? _currentGuide;
|
||||
private ProgramInfo? _currentProgramInfo;
|
||||
private int _currentProgramIndex;
|
||||
|
||||
public ICommand BackCommand { get; }
|
||||
public ICommand FullscreenCommand { get; }
|
||||
public ICommand NextCommand { get; }
|
||||
public ICommand PreviousCommand { get; }
|
||||
public ICommand ShowProgramListCommand { get; }
|
||||
public ICommand CloseAppCommand { get; }
|
||||
public ICommand OpenStreamCommand { get; }
|
||||
|
||||
private string _topPanelTitle = string.Empty;
|
||||
public string TopPanelTitle
|
||||
{
|
||||
get => _topPanelTitle;
|
||||
set => SetProperty(ref _topPanelTitle, value);
|
||||
}
|
||||
|
||||
private string _programGuideText = string.Empty;
|
||||
public string ProgramGuideText
|
||||
{
|
||||
get => _programGuideText;
|
||||
set => SetProperty(ref _programGuideText, value);
|
||||
}
|
||||
|
||||
private string _startProgram = string.Empty;
|
||||
public string StartProgram
|
||||
{
|
||||
get => _startProgram;
|
||||
set => SetProperty(ref _startProgram, value);
|
||||
}
|
||||
|
||||
private string _endProgram = string.Empty;
|
||||
public string EndProgram
|
||||
{
|
||||
get => _endProgram;
|
||||
set => SetProperty(ref _endProgram, value);
|
||||
}
|
||||
|
||||
private int _durationValue;
|
||||
public int DurationValue
|
||||
{
|
||||
get => _durationValue;
|
||||
set => SetProperty(ref _durationValue, value);
|
||||
}
|
||||
|
||||
private bool _isProgramInfoVisible;
|
||||
public bool IsProgramInfoVisible
|
||||
{
|
||||
get => _isProgramInfoVisible;
|
||||
set => SetProperty(ref _isProgramInfoVisible, value);
|
||||
}
|
||||
|
||||
private bool _programGuideVisible;
|
||||
public bool ProgramGuideVisible
|
||||
{
|
||||
get => _programGuideVisible;
|
||||
set => SetProperty(ref _programGuideVisible, value);
|
||||
}
|
||||
|
||||
private string _streamUrl = string.Empty;
|
||||
public string StreamUrl
|
||||
{
|
||||
get => _streamUrl;
|
||||
set => SetProperty(ref _streamUrl, value);
|
||||
}
|
||||
|
||||
private string _playbackStatus = "Buffering stream...";
|
||||
public string PlaybackStatus
|
||||
{
|
||||
get => _playbackStatus;
|
||||
set => SetProperty(ref _playbackStatus, value);
|
||||
}
|
||||
|
||||
private bool _hasPlaybackStatus = true;
|
||||
public bool HasPlaybackStatus
|
||||
{
|
||||
get => _hasPlaybackStatus;
|
||||
set => SetProperty(ref _hasPlaybackStatus, value);
|
||||
}
|
||||
|
||||
private List<ProgramInfo> _programItems = new();
|
||||
public List<ProgramInfo> Programs
|
||||
{
|
||||
get => _programItems;
|
||||
set => SetProperty(ref _programItems, value);
|
||||
}
|
||||
|
||||
public PlayerViewModel(M3UInfo selectedProgram)
|
||||
{
|
||||
_currentProgram = selectedProgram;
|
||||
BackCommand = new RelayCommand(OnButtonBackClick);
|
||||
NextCommand = new RelayCommand(NextProgram);
|
||||
PreviousCommand = new RelayCommand(PreviousProgram);
|
||||
FullscreenCommand = new RelayCommand(TVPlayerViewModel.Instance.FullScreenToggle);
|
||||
CloseAppCommand = new RelayCommand(TVPlayerViewModel.Instance.CloseAppCommand);
|
||||
ShowProgramListCommand = new RelayCommand(ShowProgramList);
|
||||
OpenStreamCommand = new RelayCommand(OpenStreamExternally);
|
||||
|
||||
_programSubscription = TVPlayerViewModel.Instance.CurrentProgramsData!
|
||||
.AllPrograms
|
||||
.Subscribe(programs =>
|
||||
{
|
||||
_programs = programs.Where(p => p.GroupTitle == _currentProgram.GroupTitle).ToList();
|
||||
_currentProgramIndex = _programs.FindIndex(p => p.Name == _currentProgram.Name);
|
||||
if (_currentProgramIndex < 0)
|
||||
_currentProgramIndex = 0;
|
||||
});
|
||||
|
||||
UpdateUi();
|
||||
}
|
||||
|
||||
public void SetPlaybackStatus(string status)
|
||||
{
|
||||
PlaybackStatus = status;
|
||||
HasPlaybackStatus = !string.IsNullOrWhiteSpace(status);
|
||||
}
|
||||
|
||||
private void UpdateUi()
|
||||
{
|
||||
TVPlayerViewModel.Instance.TopPanelVisible(false, _currentProgram.Name);
|
||||
TopPanelTitle = _currentProgram.Name;
|
||||
StreamUrl = _currentProgram.Url;
|
||||
SetPlaybackStatus(string.IsNullOrWhiteSpace(StreamUrl)
|
||||
? "No stream URL available for this channel."
|
||||
: "Buffering stream...");
|
||||
ProgramGuideVisible = false;
|
||||
|
||||
_programGuideDisposable?.Dispose();
|
||||
_timer?.Dispose();
|
||||
|
||||
_programGuideDisposable = TVPlayerViewModel.Instance.CurrentProgramsData!
|
||||
.ProgramGuideInfo
|
||||
.Subscribe(async _ =>
|
||||
{
|
||||
try
|
||||
{
|
||||
_currentGuide = await TVPlayerViewModel.Instance.CurrentProgramsData.GetGuideByProgram(_currentProgram.TvgID);
|
||||
UpdateScreenInfo();
|
||||
_timer = Observable.Interval(TimeSpan.FromMinutes(1)).Subscribe(_ => UpdateScreenInfo());
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Debug.WriteLine($"Failed to load program guide: {ex.Message}");
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void PreviousProgram()
|
||||
{
|
||||
if (_programs.Count == 0)
|
||||
return;
|
||||
|
||||
_currentProgramIndex = (_currentProgramIndex - 1 + _programs.Count) % _programs.Count;
|
||||
_currentProgram = _programs[_currentProgramIndex];
|
||||
UpdateUi();
|
||||
}
|
||||
|
||||
private void NextProgram()
|
||||
{
|
||||
if (_programs.Count == 0)
|
||||
return;
|
||||
|
||||
_currentProgramIndex = (_currentProgramIndex + 1) % _programs.Count;
|
||||
_currentProgram = _programs[_currentProgramIndex];
|
||||
UpdateUi();
|
||||
}
|
||||
|
||||
private void ShowProgramList()
|
||||
{
|
||||
ProgramGuideVisible = !ProgramGuideVisible;
|
||||
}
|
||||
|
||||
private void OpenStreamExternally()
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(StreamUrl))
|
||||
return;
|
||||
|
||||
Process.Start(new ProcessStartInfo
|
||||
{
|
||||
FileName = StreamUrl,
|
||||
UseShellExecute = true
|
||||
});
|
||||
}
|
||||
|
||||
private void UpdateScreenInfo()
|
||||
{
|
||||
try
|
||||
{
|
||||
if (_currentGuide?.Programs == null || _currentGuide.Programs.Count == 0)
|
||||
{
|
||||
IsProgramInfoVisible = false;
|
||||
Programs = new List<ProgramInfo>();
|
||||
return;
|
||||
}
|
||||
|
||||
_currentProgramInfo = _currentGuide.Programs
|
||||
.FirstOrDefault(item => item.StartTime <= DateTime.Now && item.EndTime >= DateTime.Now);
|
||||
|
||||
if (_currentProgramInfo == null)
|
||||
{
|
||||
IsProgramInfoVisible = false;
|
||||
Programs = _currentGuide.Programs.Take(7).ToList();
|
||||
return;
|
||||
}
|
||||
|
||||
var currentIndex = _currentGuide.Programs.FindIndex(x => x.Title == _currentProgramInfo.Title);
|
||||
Programs = _currentGuide.Programs.Skip(Math.Max(currentIndex, 0)).Take(7).ToList();
|
||||
|
||||
IsProgramInfoVisible = true;
|
||||
ProgramGuideText = _currentProgramInfo.Title;
|
||||
StartProgram = _currentProgramInfo.StartTime.ToShortTimeString();
|
||||
EndProgram = _currentProgramInfo.EndTime.ToShortTimeString();
|
||||
|
||||
var totalMinutes = (_currentProgramInfo.EndTime - _currentProgramInfo.StartTime).TotalMinutes;
|
||||
DurationValue = totalMinutes <= 0
|
||||
? 0
|
||||
: (int)((DateTime.Now - _currentProgramInfo.StartTime).TotalMinutes / totalMinutes * 100);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Debug.WriteLine($"Failed to update screen info: {ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
private void OnButtonBackClick()
|
||||
{
|
||||
TVPlayerViewModel.Instance.ShowProgramsListScreen(new GroupInfo
|
||||
{
|
||||
Name = _currentProgram.GroupTitle,
|
||||
Count = 0
|
||||
});
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_programSubscription.Dispose();
|
||||
_programGuideDisposable?.Dispose();
|
||||
_timer?.Dispose();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
using CommunityToolkit.Mvvm.Input;
|
||||
using System.Windows.Input;
|
||||
|
||||
namespace TV_Player.AvaloniaApp.ViewModels;
|
||||
|
||||
public class PlaylistsGroupViewModel : TV_Player.ObservableViewModelBase
|
||||
{
|
||||
private List<GroupInfo> _programs = new();
|
||||
public List<GroupInfo> Programs
|
||||
{
|
||||
get => _programs;
|
||||
set => SetProperty(ref _programs, value);
|
||||
}
|
||||
|
||||
public ICommand SelectPlaylistCommand { get; }
|
||||
|
||||
public PlaylistsGroupViewModel()
|
||||
{
|
||||
SelectPlaylistCommand = new RelayCommand<GroupInfo>(OnItemSelected);
|
||||
Programs = TVPlayerViewModel.Instance.PlayListsData
|
||||
.Select(x => new GroupInfo { Name = x.Key, Count = 0 })
|
||||
.ToList();
|
||||
}
|
||||
|
||||
private void OnItemSelected(GroupInfo? group)
|
||||
{
|
||||
if (group == null)
|
||||
return;
|
||||
|
||||
TVPlayerViewModel.Instance.ShowProgramsGroupScreen(group.Name);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
using CommunityToolkit.Mvvm.Input;
|
||||
using System.Windows.Input;
|
||||
using TV_Player.ViewModels;
|
||||
|
||||
namespace TV_Player.AvaloniaApp.ViewModels;
|
||||
|
||||
public class ProgramsGroupViewModel : TV_Player.ObservableViewModelBase, IDisposable
|
||||
{
|
||||
private List<GroupInfo> _programs = new();
|
||||
private readonly IDisposable _groupInformationSubscriber;
|
||||
|
||||
public List<GroupInfo> Programs
|
||||
{
|
||||
get => _programs;
|
||||
set => SetProperty(ref _programs, value);
|
||||
}
|
||||
|
||||
public ICommand SelectGroupCommand { get; }
|
||||
|
||||
public ProgramsGroupViewModel()
|
||||
{
|
||||
System.Diagnostics.Debug.WriteLine("[ProgramsGroupViewModel] Initializing...");
|
||||
SelectGroupCommand = new RelayCommand<GroupInfo>(OnItemSelected);
|
||||
if (TVPlayerViewModel.Instance.CurrentProgramsData == null)
|
||||
{
|
||||
System.Diagnostics.Debug.WriteLine("[ProgramsGroupViewModel] ERROR: CurrentProgramsData is null!");
|
||||
Programs = new List<GroupInfo>();
|
||||
return;
|
||||
}
|
||||
|
||||
_groupInformationSubscriber = TVPlayerViewModel.Instance.CurrentProgramsData
|
||||
.GroupsInformation
|
||||
.Subscribe(groups =>
|
||||
{
|
||||
System.Diagnostics.Debug.WriteLine($"[ProgramsGroupViewModel.Subscribe] Received {groups.Count} groups from observable");
|
||||
// Filter hidden groups but keep all others (including "undefined")
|
||||
var filteredGroups = SettingsModel.HiddenGroups == null
|
||||
? groups
|
||||
: groups.Where(g => !SettingsModel.HiddenGroups.Contains(g.Name.ToLowerInvariant())).ToList();
|
||||
|
||||
System.Diagnostics.Debug.WriteLine($"[ProgramsGroupViewModel] Groups after filter: {string.Join(", ", filteredGroups.Select(g => $"{g.Name}({g.Count})"))}");
|
||||
Programs = filteredGroups;
|
||||
});
|
||||
}
|
||||
|
||||
private void OnItemSelected(GroupInfo? group)
|
||||
{
|
||||
if (group == null)
|
||||
return;
|
||||
|
||||
TVPlayerViewModel.Instance.ShowProgramsListScreen(group);
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_groupInformationSubscriber.Dispose();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Windows.Input;
|
||||
using CommunityToolkit.Mvvm.Input;
|
||||
using Avalonia.Threading;
|
||||
|
||||
namespace TV_Player.AvaloniaApp.ViewModels;
|
||||
|
||||
public class ProgramsListViewModel : TV_Player.ObservableViewModelBase, IDisposable
|
||||
{
|
||||
private readonly IDisposable _programSubscription;
|
||||
private ObservableCollection<M3UInfo> _programs = new();
|
||||
|
||||
public ObservableCollection<M3UInfo> Programs
|
||||
{
|
||||
get => _programs;
|
||||
set => SetProperty(ref _programs, value);
|
||||
}
|
||||
|
||||
public ICommand SelectProgramCommand { get; }
|
||||
|
||||
public ProgramsListViewModel(GroupInfo groupInfo)
|
||||
{
|
||||
SelectProgramCommand = new RelayCommand<M3UInfo>(OnItemSelected);
|
||||
_programSubscription = TVPlayerViewModel.Instance.CurrentProgramsData!
|
||||
.AllPrograms
|
||||
.Subscribe(newPrograms =>
|
||||
{
|
||||
var filteredPrograms = newPrograms
|
||||
.Where(p => p.GroupTitle == groupInfo.Name && !string.IsNullOrWhiteSpace(p.Url))
|
||||
.ToList();
|
||||
|
||||
Dispatcher.UIThread.Post(() =>
|
||||
{
|
||||
Programs = new ObservableCollection<M3UInfo>(filteredPrograms);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
private void OnItemSelected(M3UInfo? program)
|
||||
{
|
||||
if (program == null)
|
||||
return;
|
||||
|
||||
TVPlayerViewModel.Instance.ShowPlayerScreen(program);
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_programSubscription.Dispose();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,101 @@
|
||||
using CommunityToolkit.Mvvm.Input;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Windows.Input;
|
||||
using TV_Player.ViewModels;
|
||||
|
||||
namespace TV_Player.AvaloniaApp.ViewModels;
|
||||
|
||||
public class SettingsViewModel : TV_Player.ObservableViewModelBase
|
||||
{
|
||||
private string _playlistName = string.Empty;
|
||||
public string PlaylistName
|
||||
{
|
||||
get => _playlistName;
|
||||
set => SetProperty(ref _playlistName, value);
|
||||
}
|
||||
|
||||
private string _playlistUrl = string.Empty;
|
||||
public string PlaylistURL
|
||||
{
|
||||
get => _playlistUrl;
|
||||
set => SetProperty(ref _playlistUrl, value);
|
||||
}
|
||||
|
||||
private ObservableCollection<KeyValuePair<string, string>> _playlists = new();
|
||||
public ObservableCollection<KeyValuePair<string, string>> Playlists
|
||||
{
|
||||
get => _playlists;
|
||||
set => SetProperty(ref _playlists, value);
|
||||
}
|
||||
|
||||
private bool _startFullScreen;
|
||||
public bool StartFullScreen
|
||||
{
|
||||
get => _startFullScreen;
|
||||
set => SetProperty(ref _startFullScreen, value);
|
||||
}
|
||||
|
||||
private bool _startLastScreen;
|
||||
public bool StartLastScreen
|
||||
{
|
||||
get => _startLastScreen;
|
||||
set => SetProperty(ref _startLastScreen, value);
|
||||
}
|
||||
|
||||
public ICommand SaveCommand { get; }
|
||||
public ICommand PlaylistDeleteCommand { get; }
|
||||
public ICommand BackCommand { get; }
|
||||
public ICommand AddPlaylistCommand { get; }
|
||||
|
||||
public SettingsViewModel()
|
||||
{
|
||||
SaveCommand = new RelayCommand(OnSaveSettings);
|
||||
BackCommand = new RelayCommand(OnBackCommand);
|
||||
AddPlaylistCommand = new RelayCommand(OnAddPlaylistCommand);
|
||||
PlaylistDeleteCommand = new RelayCommand<KeyValuePair<string, string>>(OnPlaylistDeleteCommand);
|
||||
|
||||
StartFullScreen = SettingsModel.StartFullScreen;
|
||||
StartLastScreen = SettingsModel.StartFromLastScreen;
|
||||
Playlists = SettingsModel.Playlists == null
|
||||
? new ObservableCollection<KeyValuePair<string, string>>()
|
||||
: new ObservableCollection<KeyValuePair<string, string>>(SettingsModel.Playlists);
|
||||
}
|
||||
|
||||
private void OnAddPlaylistCommand()
|
||||
{
|
||||
var url = PlaylistURL?.Trim();
|
||||
var name = PlaylistName?.Trim();
|
||||
if (string.IsNullOrWhiteSpace(name) || string.IsNullOrWhiteSpace(url))
|
||||
return;
|
||||
|
||||
if (!Uri.TryCreate(url, UriKind.Absolute, out var uriResult) ||
|
||||
(uriResult.Scheme != Uri.UriSchemeHttp && uriResult.Scheme != Uri.UriSchemeHttps))
|
||||
return;
|
||||
|
||||
if (Playlists.Any(pair => pair.Key.Equals(name, StringComparison.OrdinalIgnoreCase)))
|
||||
return;
|
||||
|
||||
Playlists.Add(new KeyValuePair<string, string>(name, url));
|
||||
PlaylistName = string.Empty;
|
||||
PlaylistURL = string.Empty;
|
||||
}
|
||||
|
||||
private void OnPlaylistDeleteCommand(KeyValuePair<string, string> pair)
|
||||
{
|
||||
Playlists.Remove(pair);
|
||||
}
|
||||
|
||||
private void OnBackCommand()
|
||||
{
|
||||
TVPlayerViewModel.Instance.SelectScreen();
|
||||
}
|
||||
|
||||
private void OnSaveSettings()
|
||||
{
|
||||
SettingsModel.StartFullScreen = StartFullScreen;
|
||||
SettingsModel.StartFromLastScreen = StartLastScreen;
|
||||
SettingsModel.Playlists = Playlists.ToDictionary(pair => pair.Key, pair => pair.Value);
|
||||
SettingsModel.SaveSetttings();
|
||||
TVPlayerViewModel.Instance.InitializeTVWithData();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,159 @@
|
||||
using TV_Player.ViewModels;
|
||||
|
||||
namespace TV_Player.AvaloniaApp.ViewModels;
|
||||
|
||||
public class TVPlayerViewModel : IDisposable
|
||||
{
|
||||
private static readonly Lazy<TVPlayerViewModel> LazyInstance = new(() => new TVPlayerViewModel());
|
||||
public static TVPlayerViewModel Instance => LazyInstance.Value;
|
||||
|
||||
private readonly MainWindowViewModel _mainWindowViewModel;
|
||||
|
||||
public MainWindowViewModel MainWindowViewModel => _mainWindowViewModel;
|
||||
public ProgramsData? CurrentProgramsData { get; private set; }
|
||||
public Dictionary<string, ProgramsData> PlayListsData { get; } = new();
|
||||
public string? CurrentPlaylistName { get; private set; }
|
||||
|
||||
private TVPlayerViewModel()
|
||||
{
|
||||
_mainWindowViewModel = new MainWindowViewModel();
|
||||
SettingsModel.LoadSettings();
|
||||
}
|
||||
|
||||
public void Initialize()
|
||||
{
|
||||
InitializeTVWithData();
|
||||
}
|
||||
|
||||
public void InitializeTVWithData()
|
||||
{
|
||||
if (SettingsModel.Playlists is { Count: > 0 })
|
||||
{
|
||||
PlayListsData.Clear();
|
||||
foreach (var playlist in SettingsModel.Playlists)
|
||||
{
|
||||
PlayListsData[playlist.Key] = new ProgramsData(playlist.Key, playlist.Value);
|
||||
}
|
||||
|
||||
if (SettingsModel.StartFullScreen)
|
||||
{
|
||||
FullScreenToggle();
|
||||
}
|
||||
|
||||
if (SettingsModel.StartFromLastScreen)
|
||||
{
|
||||
SelectScreen();
|
||||
}
|
||||
else
|
||||
{
|
||||
ShowPlaylistsGroupScreen();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
ShowSettingsScreen();
|
||||
}
|
||||
}
|
||||
|
||||
public void SelectScreen()
|
||||
{
|
||||
switch (SettingsModel.LastScreen)
|
||||
{
|
||||
case nameof(ProgramsListViewModel):
|
||||
if (SettingsModel.Group != null)
|
||||
ShowProgramsListScreen(SettingsModel.Group);
|
||||
else
|
||||
ShowPlaylistsGroupScreen();
|
||||
break;
|
||||
case nameof(PlayerViewModel):
|
||||
if (SettingsModel.Program != null)
|
||||
ShowPlayerScreen(SettingsModel.Program);
|
||||
else
|
||||
ShowPlaylistsGroupScreen();
|
||||
break;
|
||||
default:
|
||||
ShowPlaylistsGroupScreen();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
public void ShowPlaylistsGroupScreen()
|
||||
{
|
||||
SettingsModel.LastScreen = nameof(ProgramsGroupViewModel);
|
||||
TopPanelVisible(true, "Playlists");
|
||||
SetPageContext(new PlaylistsGroupViewModel());
|
||||
}
|
||||
|
||||
public void ShowProgramsGroupScreen(string playlistName)
|
||||
{
|
||||
var selectedData = PlayListsData.First(x => x.Key == playlistName);
|
||||
CurrentPlaylistName = selectedData.Key;
|
||||
CurrentProgramsData = selectedData.Value;
|
||||
|
||||
SettingsModel.LastScreen = nameof(ProgramsGroupViewModel);
|
||||
TopPanelVisible(true, "Groups");
|
||||
SetBackButtonAction(ShowPlaylistsGroupScreen);
|
||||
SetPageContext(new ProgramsGroupViewModel());
|
||||
}
|
||||
|
||||
public void ShowProgramsListScreen(GroupInfo group)
|
||||
{
|
||||
if (CurrentPlaylistName == null)
|
||||
return;
|
||||
|
||||
SettingsModel.Group = group;
|
||||
SettingsModel.LastScreen = nameof(ProgramsListViewModel);
|
||||
TopPanelVisible(true, group.Name);
|
||||
SetBackButtonAction(() => ShowProgramsGroupScreen(CurrentPlaylistName));
|
||||
SetPageContext(new ProgramsListViewModel(group));
|
||||
}
|
||||
|
||||
public void ShowPlayerScreen(M3UInfo program)
|
||||
{
|
||||
SettingsModel.Program = program;
|
||||
SettingsModel.LastScreen = nameof(PlayerViewModel);
|
||||
SetPageContext(new PlayerViewModel(program));
|
||||
}
|
||||
|
||||
public void ShowSettingsScreen()
|
||||
{
|
||||
TopPanelVisible(false, string.Empty);
|
||||
SetPageContext(new SettingsViewModel());
|
||||
}
|
||||
|
||||
public void TopPanelVisible(bool value, string title)
|
||||
{
|
||||
_mainWindowViewModel.IsTopPanelVisible = value;
|
||||
_mainWindowViewModel.TopPanelTitle = title;
|
||||
}
|
||||
|
||||
public void FullScreenToggle()
|
||||
{
|
||||
_mainWindowViewModel.OnFullScreenButtonClick();
|
||||
}
|
||||
|
||||
public void CloseAppCommand()
|
||||
{
|
||||
_mainWindowViewModel.OnCloseAppButtonClick();
|
||||
}
|
||||
|
||||
public void SetBackButtonAction(Action action)
|
||||
{
|
||||
_mainWindowViewModel.ButtonBackAction = action;
|
||||
}
|
||||
|
||||
private void SetPageContext(object viewModel)
|
||||
{
|
||||
if (_mainWindowViewModel.CurrentViewModel is IDisposable disposable)
|
||||
disposable.Dispose();
|
||||
|
||||
_mainWindowViewModel.CurrentViewModel = viewModel;
|
||||
SettingsModel.SaveSetttings();
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
if (_mainWindowViewModel.CurrentViewModel is IDisposable disposable)
|
||||
disposable.Dispose();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
<UserControl xmlns="https://github.com/avaloniaui"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:vlc="clr-namespace:LibVLCSharp.Avalonia;assembly=LibVLCSharp.Avalonia"
|
||||
x:Class="TV_Player.AvaloniaApp.Views.PlayerView">
|
||||
<Grid RowDefinitions="*,Auto" ColumnDefinitions="280,*">
|
||||
<Border Grid.RowSpan="2"
|
||||
Background="#AA111827"
|
||||
Padding="16"
|
||||
CornerRadius="16"
|
||||
IsVisible="{Binding ProgramGuideVisible}">
|
||||
<StackPanel Spacing="10">
|
||||
<TextBlock Text="Upcoming" FontSize="22" FontWeight="SemiBold" />
|
||||
<ItemsControl ItemsSource="{Binding Programs}">
|
||||
<ItemsControl.ItemTemplate>
|
||||
<DataTemplate>
|
||||
<Border Padding="8" Background="#1F2937" CornerRadius="10" Margin="0,0,0,8">
|
||||
<StackPanel Spacing="4">
|
||||
<TextBlock Text="{Binding Title}" TextWrapping="Wrap" />
|
||||
<StackPanel Orientation="Horizontal" Spacing="8">
|
||||
<TextBlock Text="{Binding StartTime, StringFormat='{}{0:HH:mm}'}" />
|
||||
<TextBlock Text="-" />
|
||||
<TextBlock Text="{Binding EndTime, StringFormat='{}{0:HH:mm}'}" />
|
||||
</StackPanel>
|
||||
</StackPanel>
|
||||
</Border>
|
||||
</DataTemplate>
|
||||
</ItemsControl.ItemTemplate>
|
||||
</ItemsControl>
|
||||
</StackPanel>
|
||||
</Border>
|
||||
|
||||
<Border Grid.Column="1" Background="#0F172A" CornerRadius="20" Padding="20" Margin="16,0,0,16">
|
||||
<Grid RowDefinitions="Auto,*,Auto">
|
||||
<TextBlock Text="{Binding TopPanelTitle}" FontSize="30" FontWeight="Bold" TextAlignment="Center" Margin="0,0,0,16" />
|
||||
<Grid Grid.Row="1">
|
||||
<vlc:VideoView x:Name="VideoView" />
|
||||
<Border Background="#66000000" Padding="14" CornerRadius="12"
|
||||
HorizontalAlignment="Center" VerticalAlignment="Bottom" Margin="20"
|
||||
IsVisible="{Binding HasPlaybackStatus}">
|
||||
<StackPanel Spacing="8" MaxWidth="720">
|
||||
<TextBlock Text="{Binding PlaybackStatus}" TextWrapping="Wrap" TextAlignment="Center" />
|
||||
<SelectableTextBlock Text="{Binding StreamUrl}" TextWrapping="Wrap" IsVisible="False" />
|
||||
</StackPanel>
|
||||
</Border>
|
||||
</Grid>
|
||||
<StackPanel Grid.Row="2" Orientation="Horizontal" HorizontalAlignment="Center" Spacing="12">
|
||||
<Button Content="Previous" Command="{Binding PreviousCommand}" />
|
||||
<Button Content="Open Externally" Command="{Binding OpenStreamCommand}" />
|
||||
<Button Content="Next" Command="{Binding NextCommand}" />
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
</Border>
|
||||
|
||||
<Border Grid.Row="1" Grid.Column="1" Background="#AA111827" Padding="16" CornerRadius="16">
|
||||
<Grid ColumnDefinitions="Auto,*,Auto,Auto,Auto,Auto" VerticalAlignment="Center">
|
||||
<Button Content="Back" Command="{Binding BackCommand}" Margin="0,0,12,0" />
|
||||
<StackPanel Grid.Column="1" IsVisible="{Binding IsProgramInfoVisible}" Spacing="6">
|
||||
<TextBlock Text="{Binding ProgramGuideText}" FontSize="18" FontWeight="SemiBold" />
|
||||
<StackPanel Orientation="Horizontal" Spacing="8">
|
||||
<TextBlock Text="{Binding StartProgram}" />
|
||||
<ProgressBar Width="260" Maximum="100" Value="{Binding DurationValue}" />
|
||||
<TextBlock Text="{Binding EndProgram}" />
|
||||
</StackPanel>
|
||||
</StackPanel>
|
||||
<Button Grid.Column="2" Content="Guide" Command="{Binding ShowProgramListCommand}" Margin="0,0,12,0" />
|
||||
<Button Grid.Column="3" Content="Fullscreen" Command="{Binding FullscreenCommand}" Margin="0,0,12,0" />
|
||||
<Button Grid.Column="4" Content="Open" Command="{Binding OpenStreamCommand}" Margin="0,0,12,0" />
|
||||
<Button Grid.Column="5" Content="Close" Command="{Binding CloseAppCommand}" />
|
||||
</Grid>
|
||||
</Border>
|
||||
</Grid>
|
||||
</UserControl>
|
||||
@@ -0,0 +1,109 @@
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Markup.Xaml;
|
||||
using LibVLCSharp.Shared;
|
||||
using System.ComponentModel;
|
||||
using TV_Player.AvaloniaApp.ViewModels;
|
||||
|
||||
namespace TV_Player.AvaloniaApp.Views;
|
||||
|
||||
public partial class PlayerView : UserControl
|
||||
{
|
||||
private LibVLC? _libVlc;
|
||||
private MediaPlayer? _mediaPlayer;
|
||||
private PlayerViewModel? _viewModel;
|
||||
private bool _initialized;
|
||||
|
||||
public PlayerView()
|
||||
{
|
||||
AvaloniaXamlLoader.Load(this);
|
||||
DataContextChanged += OnDataContextChanged;
|
||||
AttachedToVisualTree += OnAttachedToVisualTree;
|
||||
DetachedFromVisualTree += OnDetachedFromVisualTree;
|
||||
}
|
||||
|
||||
private void OnAttachedToVisualTree(object? sender, Avalonia.VisualTreeAttachmentEventArgs e)
|
||||
{
|
||||
if (_initialized)
|
||||
return;
|
||||
|
||||
Core.Initialize();
|
||||
_libVlc = new LibVLC(enableDebugLogs: true);
|
||||
_mediaPlayer = new MediaPlayer(_libVlc)
|
||||
{
|
||||
EnableHardwareDecoding = true
|
||||
};
|
||||
|
||||
VideoView.MediaPlayer = _mediaPlayer;
|
||||
_initialized = true;
|
||||
PlayCurrentStream();
|
||||
}
|
||||
|
||||
private void OnDetachedFromVisualTree(object? sender, Avalonia.VisualTreeAttachmentEventArgs e)
|
||||
{
|
||||
if (_viewModel != null)
|
||||
{
|
||||
_viewModel.PropertyChanged -= OnViewModelPropertyChanged;
|
||||
}
|
||||
|
||||
_mediaPlayer?.Stop();
|
||||
_mediaPlayer?.Dispose();
|
||||
_libVlc?.Dispose();
|
||||
_mediaPlayer = null;
|
||||
_libVlc = null;
|
||||
_initialized = false;
|
||||
}
|
||||
|
||||
private void OnDataContextChanged(object? sender, System.EventArgs e)
|
||||
{
|
||||
if (_viewModel != null)
|
||||
{
|
||||
_viewModel.PropertyChanged -= OnViewModelPropertyChanged;
|
||||
}
|
||||
|
||||
_viewModel = DataContext as PlayerViewModel;
|
||||
if (_viewModel != null)
|
||||
{
|
||||
_viewModel.PropertyChanged += OnViewModelPropertyChanged;
|
||||
}
|
||||
|
||||
PlayCurrentStream();
|
||||
}
|
||||
|
||||
private void OnViewModelPropertyChanged(object? sender, PropertyChangedEventArgs e)
|
||||
{
|
||||
if (e.PropertyName == nameof(PlayerViewModel.StreamUrl))
|
||||
{
|
||||
PlayCurrentStream();
|
||||
}
|
||||
}
|
||||
|
||||
private void PlayCurrentStream()
|
||||
{
|
||||
if (!_initialized || _viewModel == null || _mediaPlayer == null || _libVlc == null)
|
||||
return;
|
||||
|
||||
if (string.IsNullOrWhiteSpace(_viewModel.StreamUrl))
|
||||
{
|
||||
_viewModel.SetPlaybackStatus("No stream URL available for this channel.");
|
||||
return;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
if (_mediaPlayer.Media != null)
|
||||
{
|
||||
_mediaPlayer.Stop();
|
||||
_mediaPlayer.Media.Dispose();
|
||||
}
|
||||
|
||||
var media = new Media(_libVlc, new Uri(_viewModel.StreamUrl));
|
||||
_mediaPlayer.Media = media;
|
||||
_mediaPlayer.Play();
|
||||
_viewModel.SetPlaybackStatus("Playing with embedded VLC.");
|
||||
}
|
||||
catch (System.Exception ex)
|
||||
{
|
||||
_viewModel.SetPlaybackStatus($"Embedded playback failed: {ex.Message}. Use Open Externally as a fallback.");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
<UserControl xmlns="https://github.com/avaloniaui"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
x:Class="TV_Player.AvaloniaApp.Views.PlaylistsGroupView"
|
||||
x:Name="Root">
|
||||
<ScrollViewer>
|
||||
<ItemsControl ItemsSource="{Binding Programs}">
|
||||
<ItemsControl.ItemsPanel>
|
||||
<ItemsPanelTemplate>
|
||||
<WrapPanel Orientation="Horizontal" />
|
||||
</ItemsPanelTemplate>
|
||||
</ItemsControl.ItemsPanel>
|
||||
<ItemsControl.ItemTemplate>
|
||||
<DataTemplate>
|
||||
<Button Width="260"
|
||||
Height="120"
|
||||
Margin="10"
|
||||
Padding="18"
|
||||
CornerRadius="16"
|
||||
Background="#AA111827"
|
||||
BorderBrush="#EAB308"
|
||||
BorderThickness="2"
|
||||
Command="{Binding DataContext.SelectPlaylistCommand, RelativeSource={RelativeSource AncestorType=UserControl}}"
|
||||
CommandParameter="{Binding}">
|
||||
<StackPanel Spacing="8">
|
||||
<TextBlock Text="{Binding Name}" FontSize="24" FontWeight="Bold" TextWrapping="Wrap" />
|
||||
<TextBlock Text="Open playlist" Foreground="#D1D5DB" />
|
||||
</StackPanel>
|
||||
</Button>
|
||||
</DataTemplate>
|
||||
</ItemsControl.ItemTemplate>
|
||||
</ItemsControl>
|
||||
</ScrollViewer>
|
||||
</UserControl>
|
||||
@@ -0,0 +1,12 @@
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Markup.Xaml;
|
||||
|
||||
namespace TV_Player.AvaloniaApp.Views;
|
||||
|
||||
public partial class PlaylistsGroupView : UserControl
|
||||
{
|
||||
public PlaylistsGroupView()
|
||||
{
|
||||
AvaloniaXamlLoader.Load(this);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
<UserControl xmlns="https://github.com/avaloniaui"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
x:Class="TV_Player.AvaloniaApp.Views.ProgramsGroupView">
|
||||
<Grid RowDefinitions="Auto,*">
|
||||
<!-- Debug Info Header -->
|
||||
<Border Grid.Row="0" Background="#1a1a2e" Padding="16" Margin="0,0,0,4">
|
||||
<TextBlock Text="{Binding Programs.Count, StringFormat='Groups Loaded: {0}'}"
|
||||
Foreground="#EAB308"
|
||||
FontSize="14"
|
||||
FontWeight="Bold" />
|
||||
</Border>
|
||||
|
||||
<!-- Groups List -->
|
||||
<ScrollViewer Grid.Row="1">
|
||||
<ItemsControl ItemsSource="{Binding Programs}">
|
||||
<ItemsControl.ItemsPanel>
|
||||
<ItemsPanelTemplate>
|
||||
<WrapPanel Orientation="Horizontal" />
|
||||
</ItemsPanelTemplate>
|
||||
</ItemsControl.ItemsPanel>
|
||||
<ItemsControl.ItemTemplate>
|
||||
<DataTemplate>
|
||||
<Button Width="260"
|
||||
Height="140"
|
||||
Margin="10"
|
||||
Padding="18"
|
||||
CornerRadius="16"
|
||||
Background="#AA111827"
|
||||
BorderBrush="#EAB308"
|
||||
BorderThickness="2"
|
||||
Command="{Binding DataContext.SelectGroupCommand, RelativeSource={RelativeSource AncestorType=UserControl}}"
|
||||
CommandParameter="{Binding}">
|
||||
<StackPanel Spacing="8">
|
||||
<TextBlock Text="{Binding Name}" FontSize="24" FontWeight="Bold" TextWrapping="Wrap" />
|
||||
<TextBlock Text="{Binding Count, StringFormat='Channels: {0}'}" Foreground="#D1D5DB" />
|
||||
</StackPanel>
|
||||
</Button>
|
||||
</DataTemplate>
|
||||
</ItemsControl.ItemTemplate>
|
||||
</ItemsControl>
|
||||
</ScrollViewer>
|
||||
</Grid>
|
||||
</UserControl>
|
||||
@@ -0,0 +1,12 @@
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Markup.Xaml;
|
||||
|
||||
namespace TV_Player.AvaloniaApp.Views;
|
||||
|
||||
public partial class ProgramsGroupView : UserControl
|
||||
{
|
||||
public ProgramsGroupView()
|
||||
{
|
||||
AvaloniaXamlLoader.Load(this);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
<UserControl xmlns="https://github.com/avaloniaui"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
x:Class="TV_Player.AvaloniaApp.Views.ProgramsListView">
|
||||
<ScrollViewer>
|
||||
<ItemsControl ItemsSource="{Binding Programs}">
|
||||
<ItemsControl.ItemsPanel>
|
||||
<ItemsPanelTemplate>
|
||||
<WrapPanel Orientation="Horizontal" />
|
||||
</ItemsPanelTemplate>
|
||||
</ItemsControl.ItemsPanel>
|
||||
<ItemsControl.ItemTemplate>
|
||||
<DataTemplate>
|
||||
<Button Width="160"
|
||||
Height="170"
|
||||
Margin="8"
|
||||
Padding="10"
|
||||
CornerRadius="14"
|
||||
Background="#AA111827"
|
||||
BorderBrush="#EAB308"
|
||||
BorderThickness="2"
|
||||
Command="{Binding DataContext.SelectProgramCommand, RelativeSource={RelativeSource AncestorType=UserControl}}"
|
||||
CommandParameter="{Binding}">
|
||||
<StackPanel Spacing="10">
|
||||
<Border Height="90" Background="#1F2937" CornerRadius="10">
|
||||
<Image Source="{Binding Logo}" Stretch="Uniform" />
|
||||
</Border>
|
||||
<TextBlock Text="{Binding Name}" TextWrapping="Wrap" TextAlignment="Center" />
|
||||
</StackPanel>
|
||||
</Button>
|
||||
</DataTemplate>
|
||||
</ItemsControl.ItemTemplate>
|
||||
</ItemsControl>
|
||||
</ScrollViewer>
|
||||
</UserControl>
|
||||
@@ -0,0 +1,12 @@
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Markup.Xaml;
|
||||
|
||||
namespace TV_Player.AvaloniaApp.Views;
|
||||
|
||||
public partial class ProgramsListView : UserControl
|
||||
{
|
||||
public ProgramsListView()
|
||||
{
|
||||
AvaloniaXamlLoader.Load(this);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
<UserControl xmlns="https://github.com/avaloniaui"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
x:Class="TV_Player.AvaloniaApp.Views.SettingsView">
|
||||
<ScrollViewer>
|
||||
<StackPanel Width="900" HorizontalAlignment="Center" Spacing="18">
|
||||
<TextBlock Text="Settings" FontSize="30" FontWeight="Bold" HorizontalAlignment="Center" />
|
||||
|
||||
<Border Background="#AA111827" Padding="18" CornerRadius="16">
|
||||
<StackPanel Spacing="12">
|
||||
<TextBlock Text="Playlist URL" />
|
||||
<TextBox Text="{Binding PlaylistURL}" Watermark="https://example.com/playlist.m3u" />
|
||||
<TextBlock Text="Playlist name" />
|
||||
<TextBox Text="{Binding PlaylistName}" Watermark="My IPTV" />
|
||||
<Button Content="Add playlist" Command="{Binding AddPlaylistCommand}" HorizontalAlignment="Left" />
|
||||
</StackPanel>
|
||||
</Border>
|
||||
|
||||
<Border Background="#AA111827" Padding="18" CornerRadius="16">
|
||||
<StackPanel Spacing="12">
|
||||
<TextBlock Text="Playlists" FontSize="22" FontWeight="SemiBold" />
|
||||
<ItemsControl ItemsSource="{Binding Playlists}">
|
||||
<ItemsControl.ItemTemplate>
|
||||
<DataTemplate>
|
||||
<Grid ColumnDefinitions="220,*,Auto" Margin="0,4">
|
||||
<TextBlock Text="{Binding Key}" VerticalAlignment="Center" Margin="0,0,12,0" />
|
||||
<TextBlock Grid.Column="1" Text="{Binding Value}" VerticalAlignment="Center" TextWrapping="Wrap" Margin="0,0,12,0" />
|
||||
<Button Grid.Column="2"
|
||||
Content="Remove"
|
||||
Command="{Binding DataContext.PlaylistDeleteCommand, RelativeSource={RelativeSource AncestorType=UserControl}}"
|
||||
CommandParameter="{Binding}" />
|
||||
</Grid>
|
||||
</DataTemplate>
|
||||
</ItemsControl.ItemTemplate>
|
||||
</ItemsControl>
|
||||
</StackPanel>
|
||||
</Border>
|
||||
|
||||
<CheckBox IsChecked="{Binding StartFullScreen}" Content="Start in fullscreen" />
|
||||
<CheckBox IsChecked="{Binding StartLastScreen}" Content="Remember last screen" />
|
||||
|
||||
<StackPanel Orientation="Horizontal" Spacing="12" HorizontalAlignment="Center">
|
||||
<Button Content="Back" Command="{Binding BackCommand}" MinWidth="120" />
|
||||
<Button Content="Save" Command="{Binding SaveCommand}" MinWidth="120" />
|
||||
</StackPanel>
|
||||
</StackPanel>
|
||||
</ScrollViewer>
|
||||
</UserControl>
|
||||
@@ -0,0 +1,12 @@
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Markup.Xaml;
|
||||
|
||||
namespace TV_Player.AvaloniaApp.Views;
|
||||
|
||||
public partial class SettingsView : UserControl
|
||||
{
|
||||
public SettingsView()
|
||||
{
|
||||
AvaloniaXamlLoader.Load(this);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user