genesis/ui/tidbits (#3)

## Contents
* Add bottom bar façade
* Apply good practices re. separation of concerns
* Generally clean things up

Co-authored-by: Alexis DRAI <alexis.drai@etu.uca.fr>
Reviewed-on: #3
Alexis Drai 2 years ago
parent 49b422d3d0
commit 149def6ccb

@ -49,14 +49,31 @@
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<None Remove="Resources\Images\icon_albums.png" /> <None Remove="Resources\Images\album_macroblank_1.png" />
<None Remove="Resources\Images\icon_artists.png" /> <None Remove="Resources\Images\album_macroblank_2.png" />
<None Remove="Resources\Images\icon_genres.png" /> <None Remove="Resources\Images\album_macroblank_3.png" />
<None Remove="Resources\Images\icon_playlists.png" /> <None Remove="Resources\Images\icon_bottom_browse_gray.png" />
<None Remove="Resources\Images\icon_songs.png" /> <None Remove="Resources\Images\icon_bottom_browse_red.png" />
<None Remove="Resources\Images\macroblank_1.png" /> <None Remove="Resources\Images\icon_bottom_library_gray.png" />
<None Remove="Resources\Images\macroblank_2.png" /> <None Remove="Resources\Images\icon_bottom_library_red.png" />
<None Remove="Resources\Images\macroblank_3.png" /> <None Remove="Resources\Images\icon_bottom_play_gray.png" />
<None Remove="Resources\Images\icon_bottom_play_red.png" />
<None Remove="Resources\Images\icon_bottom_radio_gray.png" />
<None Remove="Resources\Images\icon_bottom_radio_red.png" />
<None Remove="Resources\Images\icon_bottom_search_gray.png" />
<None Remove="Resources\Images\icon_bottom_search_red.png" />
<None Remove="Resources\Images\icon_categories_albums.png" />
<None Remove="Resources\Images\icon_categories_artists.png" />
<None Remove="Resources\Images\icon_categories_genres.png" />
<None Remove="Resources\Images\icon_categories_playlists.png" />
<None Remove="Resources\Images\icon_categories_songs.png" />
<None Remove="Resources\Images\icon_default_song.png" />
<None Remove="Resources\Images\icon_next.png" />
<None Remove="Resources\Images\icon_next_dark.png" />
<None Remove="Resources\Images\icon_play.png" />
<None Remove="Resources\Images\icon_play_dark.png" />
<None Remove="Resources\Images\icon_wide_button_play.png" />
<None Remove="Resources\Images\icon_wide_button_shuffle.png" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
@ -68,6 +85,9 @@
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<Compile Update="Controls\BottomBar.xaml.cs">
<DependentUpon>BottomBar.xaml</DependentUpon>
</Compile>
<Compile Update="Resources\Strings\Strings.Designer.cs"> <Compile Update="Resources\Strings\Strings.Designer.cs">
<DesignTime>True</DesignTime> <DesignTime>True</DesignTime>
<AutoGen>True</AutoGen> <AutoGen>True</AutoGen>
@ -86,6 +106,18 @@
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<MauiXaml Update="Controls\BottomBar.xaml">
<Generator>MSBuild:Compile</Generator>
</MauiXaml>
<MauiXaml Update="Controls\IconLabelButton.xaml">
<Generator>MSBuild:Compile</Generator>
</MauiXaml>
<MauiXaml Update="Controls\IconLabelButtonWide.xaml">
<Generator>MSBuild:Compile</Generator>
</MauiXaml>
<MauiXaml Update="Resources\Styles\Values.xaml">
<Generator>MSBuild:Compile</Generator>
</MauiXaml>
<MauiXaml Update="Views\AlbumPage.xaml"> <MauiXaml Update="Views\AlbumPage.xaml">
<Generator>MSBuild:Compile</Generator> <Generator>MSBuild:Compile</Generator>
</MauiXaml> </MauiXaml>
@ -97,8 +129,4 @@
</MauiXaml> </MauiXaml>
</ItemGroup> </ItemGroup>
<ItemGroup>
<Folder Include="Resources\Icons\" />
</ItemGroup>
</Project> </Project>

@ -8,6 +8,7 @@
<ResourceDictionary.MergedDictionaries> <ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="Resources/Styles/Colors.xaml" /> <ResourceDictionary Source="Resources/Styles/Colors.xaml" />
<ResourceDictionary Source="Resources/Styles/Styles.xaml" /> <ResourceDictionary Source="Resources/Styles/Styles.xaml" />
<ResourceDictionary Source="Resources/Styles/Values.xaml" />
</ResourceDictionary.MergedDictionaries> </ResourceDictionary.MergedDictionaries>
</ResourceDictionary> </ResourceDictionary>
</Application.Resources> </Application.Resources>

@ -8,14 +8,4 @@ public partial class App : Application
MainPage = new AppShell(); MainPage = new AppShell();
} }
protected override void OnStart()
{
base.OnStart();
// Uncomment to set the culture to French
// CultureInfo.CurrentCulture = new CultureInfo("fr-FR");
// CultureInfo.CurrentUICulture = new CultureInfo("fr-FR");
}
} }

@ -0,0 +1,75 @@
<?xml version="1.0" encoding="utf-8" ?>
<ContentView xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:strings="clr-namespace:AMC.View.Resources.Strings"
xmlns:ctl="clr-namespace:AMC.View.Controls"
x:Class="AMC.View.Controls.BottomBar">
<StackLayout Orientation="Vertical"
BackgroundColor="{AppThemeBinding Light={StaticResource Gray100}, Dark={StaticResource Gray900}}">
<StackLayout Orientation="Horizontal"
HeightRequest="{StaticResource SpaceXL}">
<Frame Margin="{StaticResource HSpaceLittleVSpaceVeryLittle}"
BackgroundColor="{AppThemeBinding Light={StaticResource Gray200}, Dark={StaticResource Gray750}}"
BorderColor="Transparent"
HorizontalOptions="Start"
VerticalOptions="Center">
<Image Source="icon_default_song.png"
Aspect="AspectFill"/>
</Frame>
<Label Text="{x:Static strings:Strings.DefaultPlayingSongLabel}"
HorizontalOptions="StartAndExpand"
VerticalOptions="Center"
TextColor="{AppThemeBinding Light={StaticResource Primary}, Dark={StaticResource PrimaryDark}}" />
<ImageButton Source="{AppThemeBinding Light='icon_play.png', Dark='icon_play_dark.png'}"
Margin="{StaticResource SpaceS}"
HorizontalOptions="End"
VerticalOptions="Center"
HeightRequest="{StaticResource SpaceML}"
WidthRequest="{StaticResource SpaceML}" />
<ImageButton Source="{AppThemeBinding Light='icon_next.png', Dark='icon_next_dark.png'}"
Margin="{StaticResource SpaceS}"
HorizontalOptions="End"
VerticalOptions="Center"
HeightRequest="{StaticResource SpaceML}"
WidthRequest="{StaticResource SpaceML}" />
</StackLayout>
<BoxView Style="{StaticResource BottomBarGraySeparator}" />
<FlexLayout Direction="Row"
HeightRequest="{StaticResource SpaceXL}"
JustifyContent="SpaceAround"
AlignItems="Center">
<ctl:IconLabelButton ButtonSource="icon_bottom_play_gray.png"
ButtonLabelText="{x:Static strings:Strings.ListenNowTitle}"
LabelTextColor="{StaticResource Gray}"/>
<ctl:IconLabelButton ButtonSource="icon_bottom_browse_gray.png"
ButtonLabelText="{x:Static strings:Strings.BrowseTitle}"
LabelTextColor="{StaticResource Gray}"/>
<ctl:IconLabelButton ButtonSource="icon_bottom_radio_gray.png"
ButtonLabelText="{x:Static strings:Strings.RadioTitle}"
LabelTextColor="{StaticResource Gray}"/>
<ctl:IconLabelButton ButtonSource="icon_bottom_library_red.png"
ButtonLabelText="{x:Static strings:Strings.LibraryTitle}"
LabelTextColor="{StaticResource Secondary}"/>
<ctl:IconLabelButton ButtonSource="icon_bottom_search_gray.png"
ButtonLabelText="{x:Static strings:Strings.SearchTitle}"
LabelTextColor="{StaticResource Gray}"/>
</FlexLayout>
</StackLayout>
</ContentView>

@ -0,0 +1,10 @@
namespace AMC.View.Controls
{
public partial class BottomBar : ContentView
{
public BottomBar()
{
InitializeComponent();
}
}
}

@ -0,0 +1,18 @@
<?xml version="1.0" encoding="utf-8" ?>
<ContentView xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="AMC.View.Controls.IconLabelButton">
<StackLayout Orientation="Vertical"
Margin="{StaticResource SpaceXS}">
<ImageButton x:Name="Button"
HeightRequest="{StaticResource SpaceML}"
WidthRequest="{StaticResource SpaceML}" />
<Label x:Name="ButtonLabel"
FontSize="{StaticResource TinyFontSize}"
Margin="{StaticResource SpaceXXS}"/>
</StackLayout>
</ContentView>

@ -0,0 +1,58 @@
namespace AMC.View.Controls
{
public partial class IconLabelButton : ContentView
{
public static readonly BindableProperty ButtonSourceProperty = BindableProperty.Create(
nameof(ButtonSource),
typeof(string),
typeof(IconLabelButton),
default(string),
propertyChanged: (bindable, oldValue, newValue) =>
{
((IconLabelButton)bindable).Button.Source = (string)newValue;
});
public static readonly BindableProperty ButtonLabelTextProperty = BindableProperty.Create(
nameof(ButtonLabelText),
typeof(string),
typeof(IconLabelButton),
default(string),
propertyChanged: (bindable, oldValue, newValue) =>
{
((IconLabelButton)bindable).ButtonLabel.Text = (string)newValue;
});
public static readonly BindableProperty LabelTextColorProperty = BindableProperty.Create(
nameof(LabelTextColor),
typeof(Color),
typeof(IconLabelButton),
default(Color),
propertyChanged: (bindable, oldValue, newValue) =>
{
((IconLabelButton)bindable).ButtonLabel.TextColor = (Color)newValue;
});
public IconLabelButton()
{
InitializeComponent();
}
public string ButtonSource
{
get => (string)GetValue(ButtonSourceProperty);
set => SetValue(ButtonSourceProperty, value);
}
public string ButtonLabelText
{
get => (string)GetValue(ButtonLabelTextProperty);
set => SetValue(ButtonLabelTextProperty, value);
}
public Color LabelTextColor
{
get => (Color)GetValue(LabelTextColorProperty);
set => SetValue(LabelTextColorProperty, value);
}
}
}

@ -0,0 +1,32 @@
<?xml version="1.0" encoding="utf-8" ?>
<ContentView xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="AMC.View.Controls.IconLabelButtonWide">
<Frame HeightRequest="{StaticResource SpaceLXL}"
WidthRequest="{StaticResource SpaceXXXL}"
HorizontalOptions="Center"
BackgroundColor="{AppThemeBinding Light={StaticResource Gray200}, Dark={StaticResource Gray750}}"
Margin="{StaticResource SpaceXS}"
Padding="{StaticResource SpaceXS}"
CornerRadius="{StaticResource SlightlyRoundedCorners}">
<StackLayout Orientation="Horizontal"
HorizontalOptions="CenterAndExpand"
VerticalOptions="CenterAndExpand"
Spacing="{StaticResource SpaceXXS}">
<ImageButton x:Name="Button"
HeightRequest="{StaticResource SpaceML}"
WidthRequest="{StaticResource SpaceML}" />
<Label x:Name="ButtonLabel"
FontSize="{StaticResource SubSubtitleFontSize}"
HorizontalOptions="Center"
VerticalOptions="Center"
TextColor="{StaticResource Secondary}"/>
</StackLayout>
</Frame>
</ContentView>

@ -0,0 +1,43 @@
namespace AMC.View.Controls
{
public partial class IconLabelButtonWide : ContentView
{
public static readonly BindableProperty ButtonSourceProperty = BindableProperty.Create(
nameof(ButtonSource),
typeof(string),
typeof(IconLabelButtonWide),
default(string),
propertyChanged: (bindable, oldValue, newValue) =>
{
((IconLabelButtonWide)bindable).Button.Source = (string)newValue;
});
public static readonly BindableProperty ButtonLabelTextProperty = BindableProperty.Create(
nameof(ButtonLabelText),
typeof(string),
typeof(IconLabelButtonWide),
default(string),
propertyChanged: (bindable, oldValue, newValue) =>
{
((IconLabelButtonWide)bindable).ButtonLabel.Text = (string)newValue;
});
public IconLabelButtonWide()
{
InitializeComponent();
}
public string ButtonSource
{
get => (string)GetValue(ButtonSourceProperty);
set => SetValue(ButtonSourceProperty, value);
}
public string ButtonLabelText
{
get => (string)GetValue(ButtonLabelTextProperty);
set => SetValue(ButtonLabelTextProperty, value);
}
}
}

@ -4,21 +4,21 @@
x:Class="AMC.View.Controls.LibraryCategoryItem"> x:Class="AMC.View.Controls.LibraryCategoryItem">
<StackLayout Orientation="Horizontal" <StackLayout Orientation="Horizontal"
Padding="8" Padding="{StaticResource SpaceXS}"
BackgroundColor="{AppThemeBinding Light={StaticResource Background}, Dark={StaticResource BackgroundDark}}"> BackgroundColor="{AppThemeBinding Light={StaticResource Background}, Dark={StaticResource BackgroundDark}}">
<Image x:Name="IconImage" <Image x:Name="IconImage"
WidthRequest="24" WidthRequest="{StaticResource SpaceM}"
HeightRequest="24" HeightRequest="{StaticResource SpaceM}"
Margin="0, 0, 8, 0" Margin="{StaticResource RightSpaceLittle}"
HorizontalOptions="Start" /> HorizontalOptions="Start" />
<Label x:Name="CategoryLabel" <Label x:Name="CategoryLabel"
FontSize="16" FontSize="{StaticResource SubSubtitleFontSize}"
TextColor="{AppThemeBinding Light={StaticResource Primary}, Dark={StaticResource PrimaryDark}}" TextColor="{AppThemeBinding Light={StaticResource Primary}, Dark={StaticResource PrimaryDark}}"
HorizontalOptions="StartAndExpand" /> HorizontalOptions="StartAndExpand" />
<Image Source="icon_chevron_right.png" <Image Source="icon_chevron_right.png"
WidthRequest="24" WidthRequest="{StaticResource SpaceM}"
HeightRequest="24" HeightRequest="{StaticResource SpaceM}"
Margin="8, 0, 0, 0" Margin="{StaticResource LeftSpaceLittle}"
HorizontalOptions="End" /> HorizontalOptions="End" />
</StackLayout> </StackLayout>

@ -1,5 +1,4 @@
 namespace AMC.View.Controls
namespace AMC.View.Controls
{ {
public partial class LibraryCategoryItem : ContentView public partial class LibraryCategoryItem : ContentView
{ {
@ -8,14 +7,16 @@ namespace AMC.View.Controls
returnType: typeof(string), returnType: typeof(string),
declaringType: typeof(LibraryCategoryItem), declaringType: typeof(LibraryCategoryItem),
defaultValue: "", defaultValue: "",
propertyChanged: CategoryTextChanged); propertyChanged: CategoryTextChanged
);
public static readonly BindableProperty IconSourceProperty = BindableProperty.Create( public static readonly BindableProperty IconSourceProperty = BindableProperty.Create(
propertyName: nameof(IconSource), propertyName: nameof(IconSource),
returnType: typeof(ImageSource), returnType: typeof(ImageSource),
declaringType: typeof(LibraryCategoryItem), declaringType: typeof(LibraryCategoryItem),
defaultValue: null, defaultValue: null,
propertyChanged: IconSourceChanged); propertyChanged: IconSourceChanged
);
public LibraryCategoryItem() public LibraryCategoryItem()
{ {

@ -1,22 +0,0 @@
using System.Globalization;
namespace AMC.View.Converters
{
public class AlbumDetailsConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
var (genre, year) = (ValueTuple<string, int>)value;
return string.Format(
"{0} · {1}",
genre,
year
);
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
}

@ -1,22 +0,0 @@
using System.Globalization;
namespace AMC.View.Converters
{
public class CopyrightInfoConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
var (copyrightYear, producerBlurb) = (ValueTuple<int, string>)value;
return string.Format(
"℗ {0} {1}",
copyrightYear,
producerBlurb
);
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
}

Before

Width:  |  Height:  |  Size: 414 KiB

After

Width:  |  Height:  |  Size: 414 KiB

Before

Width:  |  Height:  |  Size: 125 KiB

After

Width:  |  Height:  |  Size: 125 KiB

Before

Width:  |  Height:  |  Size: 251 KiB

After

Width:  |  Height:  |  Size: 251 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Before

Width:  |  Height:  |  Size: 6.1 KiB

After

Width:  |  Height:  |  Size: 6.1 KiB

Before

Width:  |  Height:  |  Size: 12 KiB

After

Width:  |  Height:  |  Size: 12 KiB

Before

Width:  |  Height:  |  Size: 19 KiB

After

Width:  |  Height:  |  Size: 19 KiB

Before

Width:  |  Height:  |  Size: 9.9 KiB

After

Width:  |  Height:  |  Size: 9.9 KiB

Before

Width:  |  Height:  |  Size: 8.4 KiB

After

Width:  |  Height:  |  Size: 8.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.7 KiB

@ -78,6 +78,24 @@ namespace AMC.View.Resources.Strings {
} }
} }
/// <summary>
/// Looks up a localized string similar to Browse.
/// </summary>
public static string BrowseTitle {
get {
return ResourceManager.GetString("BrowseTitle", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Not Playing.
/// </summary>
public static string DefaultPlayingSongLabel {
get {
return ResourceManager.GetString("DefaultPlayingSongLabel", resourceCulture);
}
}
/// <summary> /// <summary>
/// Looks up a localized string similar to Genres. /// Looks up a localized string similar to Genres.
/// </summary> /// </summary>
@ -87,6 +105,15 @@ namespace AMC.View.Resources.Strings {
} }
} }
/// <summary>
/// Looks up a localized string similar to ···.
/// </summary>
public static string HThreeDotsMenu {
get {
return ResourceManager.GetString("HThreeDotsMenu", resourceCulture);
}
}
/// <summary> /// <summary>
/// Looks up a localized string similar to Library. /// Looks up a localized string similar to Library.
/// </summary> /// </summary>
@ -96,6 +123,15 @@ namespace AMC.View.Resources.Strings {
} }
} }
/// <summary>
/// Looks up a localized string similar to Listen Now.
/// </summary>
public static string ListenNowTitle {
get {
return ResourceManager.GetString("ListenNowTitle", resourceCulture);
}
}
/// <summary> /// <summary>
/// Looks up a localized string similar to minutes. /// Looks up a localized string similar to minutes.
/// </summary> /// </summary>
@ -132,6 +168,15 @@ namespace AMC.View.Resources.Strings {
} }
} }
/// <summary>
/// Looks up a localized string similar to Radio.
/// </summary>
public static string RadioTitle {
get {
return ResourceManager.GetString("RadioTitle", resourceCulture);
}
}
/// <summary> /// <summary>
/// Looks up a localized string similar to Recently Added. /// Looks up a localized string similar to Recently Added.
/// </summary> /// </summary>
@ -141,6 +186,15 @@ namespace AMC.View.Resources.Strings {
} }
} }
/// <summary>
/// Looks up a localized string similar to Search.
/// </summary>
public static string SearchTitle {
get {
return ResourceManager.GetString("SearchTitle", resourceCulture);
}
}
/// <summary> /// <summary>
/// Looks up a localized string similar to Shuffle. /// Looks up a localized string similar to Shuffle.
/// </summary> /// </summary>

@ -156,4 +156,19 @@
<data name="GenresCategory" xml:space="preserve"> <data name="GenresCategory" xml:space="preserve">
<value>Genres</value> <value>Genres</value>
</data> </data>
<data name="DefaultPlayingSongLabel" xml:space="preserve">
<value>Rien en lecture</value>
</data>
<data name="ListenNowTitle" xml:space="preserve">
<value>Ecouter</value>
</data>
<data name="RadioTitle" xml:space="preserve">
<value>Radio</value>
</data>
<data name="BrowseTitle" xml:space="preserve">
<value>Éxplorer</value>
</data>
<data name="SearchTitle" xml:space="preserve">
<value>Recherche</value>
</data>
</root> </root>

@ -156,4 +156,23 @@
<data name="GenresCategory" xml:space="preserve"> <data name="GenresCategory" xml:space="preserve">
<value>Genres</value> <value>Genres</value>
</data> </data>
<data name="HThreeDotsMenu" xml:space="preserve">
<value>···</value>
<comment>@Invariant</comment>
</data>
<data name="DefaultPlayingSongLabel" xml:space="preserve">
<value>Not Playing</value>
</data>
<data name="ListenNowTitle" xml:space="preserve">
<value>Listen Now</value>
</data>
<data name="RadioTitle" xml:space="preserve">
<value>Radio</value>
</data>
<data name="BrowseTitle" xml:space="preserve">
<value>Browse</value>
</data>
<data name="SearchTitle" xml:space="preserve">
<value>Search</value>
</data>
</root> </root>

@ -27,6 +27,7 @@
<Color x:Key="Gray400">#919191</Color> <Color x:Key="Gray400">#919191</Color>
<Color x:Key="Gray500">#6E6E6E</Color> <Color x:Key="Gray500">#6E6E6E</Color>
<Color x:Key="Gray600">#404040</Color> <Color x:Key="Gray600">#404040</Color>
<Color x:Key="Gray750">#303030</Color>
<Color x:Key="Gray900">#212121</Color> <Color x:Key="Gray900">#212121</Color>
<Color x:Key="Gray950">#141414</Color> <Color x:Key="Gray950">#141414</Color>

@ -6,9 +6,22 @@
<Style TargetType="BoxView" x:Key="GraySeparator"> <Style TargetType="BoxView" x:Key="GraySeparator">
<Setter Property="HeightRequest" Value="1"/> <Setter Property="HeightRequest" Value="1"/>
<Setter Property="Color" Value="{StaticResource Gray200}"/> <Setter Property="Color" Value="{AppThemeBinding Light={StaticResource Gray200}, Dark={StaticResource Gray900}}"/>
<Setter Property="HorizontalOptions" Value="FillAndExpand"/>
<Setter Property="Margin" Value="32, 0, 8, 0"/>
</Style>
<Style TargetType="BoxView" x:Key="HeadGraySeparator">
<Setter Property="HeightRequest" Value="1"/>
<Setter Property="Color" Value="{AppThemeBinding Light={StaticResource Gray200}, Dark={StaticResource Gray900}}"/>
<Setter Property="HorizontalOptions" Value="FillAndExpand"/>
<Setter Property="Margin" Value="8, 8, 8, 8"/>
</Style>
<Style TargetType="BoxView" x:Key="BottomBarGraySeparator">
<Setter Property="HeightRequest" Value="1"/>
<Setter Property="Color" Value="{AppThemeBinding Light={StaticResource Gray400}, Dark={StaticResource Gray600}}"/>
<Setter Property="HorizontalOptions" Value="FillAndExpand"/> <Setter Property="HorizontalOptions" Value="FillAndExpand"/>
<Setter Property="Margin" Value="32,0,8,0"/>
</Style> </Style>
<Style TargetType="Label" x:Key="FooterLabel"> <Style TargetType="Label" x:Key="FooterLabel">
@ -38,7 +51,7 @@
<Style TargetType="Button"> <Style TargetType="Button">
<Setter Property="TextColor" Value="{StaticResource Secondary}" /> <Setter Property="TextColor" Value="{StaticResource Secondary}" />
<Setter Property="BackgroundColor" Value="{AppThemeBinding Light={StaticResource Gray200}, Dark={StaticResource Gray600}}" /> <Setter Property="BackgroundColor" Value="{AppThemeBinding Light={StaticResource Gray200}, Dark={StaticResource Gray750}}" />
<Setter Property="FontFamily" Value="OpenSansRegular"/> <Setter Property="FontFamily" Value="OpenSansRegular"/>
<Setter Property="FontSize" Value="14"/> <Setter Property="FontSize" Value="14"/>
<Setter Property="CornerRadius" Value="8"/> <Setter Property="CornerRadius" Value="8"/>

@ -0,0 +1,38 @@
<?xml version="1.0" encoding="UTF-8" ?>
<?xaml-comp compile="true" ?>
<ResourceDictionary
xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml">
<x:Double x:Key="SpaceXXS">4</x:Double>
<x:Double x:Key="SpaceXS">8</x:Double>
<x:Double x:Key="SpaceS">12</x:Double>
<x:Double x:Key="SpaceM">16</x:Double>
<x:Double x:Key="SpaceML">24</x:Double>
<x:Double x:Key="SpaceL">32</x:Double>
<x:Double x:Key="SpaceLXL">48</x:Double>
<x:Double x:Key="SpaceXL">64</x:Double>
<x:Double x:Key="SpaceXXL">128</x:Double>
<x:Double x:Key="SpaceXXXL">144</x:Double>
<x:Double x:Key="AlbumPageCoverHeight">320</x:Double>
<Thickness x:Key="BottomBarSpace">0, 0, 0, 128</Thickness>
<Thickness x:Key="TopSpaceSome">0, 24, 0, 0</Thickness>
<Thickness x:Key="TopBottomSpaceLittle">0, 8, 0, 8</Thickness>
<Thickness x:Key="RightSpaceLittle">0, 0, 8, 0</Thickness>
<Thickness x:Key="LeftSpaceLittle">8, 0, 0, 0</Thickness>
<Thickness x:Key="BottomSpaceLittle">0, 0, 0, 8</Thickness>
<Thickness x:Key="LeftRightSpaceLarge">32, 0, 32, 0</Thickness>
<Thickness x:Key="WideButtonLeft">16, 16, 8, 16</Thickness>
<Thickness x:Key="WideButtonRight">8, 16, 16, 16</Thickness>
<Thickness x:Key="HSpaceLittleVSpaceVeryLittle">8, 4, 8, 4</Thickness>
<CornerRadius x:Key="SlightlyRoundedCorners">8</CornerRadius>
<x:Double x:Key="TitleFontSize">32</x:Double>
<x:Double x:Key="SubtitleFontSize">24</x:Double>
<x:Double x:Key="SubSubtitleFontSize">16</x:Double>
<x:Double x:Key="DetailsFontSize">12</x:Double>
<x:Double x:Key="TinyFontSize">8</x:Double>
</ResourceDictionary>

@ -4,143 +4,141 @@
xmlns:local="clr-namespace:AMC.View" xmlns:local="clr-namespace:AMC.View"
xmlns:strings="clr-namespace:AMC.View.Resources.Strings" xmlns:strings="clr-namespace:AMC.View.Resources.Strings"
xmlns:conv="clr-namespace:AMC.View.Converters" xmlns:conv="clr-namespace:AMC.View.Converters"
xmlns:ctl="clr-namespace:AMC.View.Controls"
xmlns:vm="clr-namespace:AMC.ViewModel.ViewModels;assembly=AMC.ViewModel" xmlns:vm="clr-namespace:AMC.ViewModel.ViewModels;assembly=AMC.ViewModel"
x:Class="AMC.View.Views.AlbumPage" x:Class="AMC.View.Views.AlbumPage"
x:DataType="vm:AlbumViewModel"> x:DataType="vm:AlbumViewModel">
<ContentPage.Resources> <ContentPage.Resources>
<ResourceDictionary> <ResourceDictionary>
<conv:SongsInfoConverter x:Key="SongsInfo" /> <conv:SongsInfoConverter x:Key="SongsInfo" />
<conv:CopyrightInfoConverter x:Key="CopyrightInfo" />
<conv:AlbumDetailsConverter x:Key="AlbumDetails" />
</ResourceDictionary> </ResourceDictionary>
</ContentPage.Resources> </ContentPage.Resources>
<Grid>
<ScrollView <Grid.RowDefinitions>
Padding="0, 0, 0, 128" <RowDefinition Height="*"/>
BackgroundColor="{AppThemeBinding Light={StaticResource Background}, Dark={StaticResource BackgroundDark}}"> <RowDefinition Height="Auto"/>
<Grid Margin="10"> </Grid.RowDefinitions>
<Grid.RowDefinitions> <ScrollView Grid.Row="0"
<RowDefinition Height="300"/> BackgroundColor="{AppThemeBinding Light={StaticResource Background}, Dark={StaticResource BackgroundDark}}">
<RowDefinition Height="Auto"/> <Grid Margin="{StaticResource SpaceXS}">
<RowDefinition Height="Auto"/> <Grid.RowDefinitions>
<RowDefinition Height="Auto"/> <RowDefinition Height="{StaticResource AlbumPageCoverHeight}"/>
<RowDefinition Height="*"/> <RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/> <RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/> <RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/> <RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/> <RowDefinition Height="*"/>
</Grid.RowDefinitions> <RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<Border
<Border
Grid.Row="0" Grid.Row="0"
Margin="32,0,32,0"> Margin="{StaticResource LeftRightSpaceLarge}">
<Border.StrokeShape> <Border.StrokeShape>
<RoundRectangle CornerRadius="8,8,8,8" /> <RoundRectangle CornerRadius="{StaticResource SlightlyRoundedCorners}" />
</Border.StrokeShape> </Border.StrokeShape>
<Image Source="{Binding CoverImage}" <Image Source="{Binding CoverImage}"
Aspect="AspectFill"/> Aspect="AspectFill"/>
</Border> </Border>
<StackLayout Padding="8" <StackLayout Padding="{StaticResource SpaceXS}"
Grid.Row="1"> Grid.Row="1">
<Label Text="{Binding Title}"
FontSize="24" <Label Text="{Binding Title}"
FontSize="{StaticResource SubtitleFontSize}"
TextColor="{AppThemeBinding Light={StaticResource Primary}, Dark={StaticResource PrimaryDark}}" TextColor="{AppThemeBinding Light={StaticResource Primary}, Dark={StaticResource PrimaryDark}}"
HorizontalTextAlignment="Center"/> HorizontalTextAlignment="Center"/>
<Label Text="{Binding Artist}" <Label Text="{Binding Artist}"
FontSize="16" FontSize="{StaticResource SubSubtitleFontSize}"
TextColor="{StaticResource Secondary}" TextColor="{StaticResource Secondary}"
HorizontalTextAlignment="Center" /> HorizontalTextAlignment="Center" />
</StackLayout>
<StackLayout Grid.Row="2"> </StackLayout>
<Label Text="{Binding Details, Converter={StaticResource AlbumDetails}}"
FontSize="12" <Label Grid.Row="2"
Text="{Binding Details, StringFormat='\{0\} · \{1\}'}"
FontSize="{StaticResource DetailsFontSize}"
TextColor="{StaticResource Gray}" TextColor="{StaticResource Gray}"
HorizontalTextAlignment="Center"/> HorizontalTextAlignment="Center"/>
<Grid Margin="8"> <FlexLayout Grid.Row="3"
<Grid.ColumnDefinitions> Direction="Row"
<ColumnDefinition Width="*"/> Margin="{StaticResource SpaceXS}"
<ColumnDefinition Width="*"/> JustifyContent="SpaceAround"
</Grid.ColumnDefinitions> AlignItems="Center">
<Button Text="{x:Static strings:Strings.PlayButton}" <ctl:IconLabelButtonWide ButtonSource="icon_wide_button_play.png"
Margin="16,16,8,16" ButtonLabelText="{x:Static strings:Strings.PlayButton}"
Grid.Column="0"/> Margin="{StaticResource WideButtonLeft}" />
<Button Text="{x:Static strings:Strings.ShuffleButton}" <ctl:IconLabelButtonWide ButtonSource="icon_wide_button_shuffle.png"
Margin="8,16,16,16" ButtonLabelText="{x:Static strings:Strings.ShuffleButton}"
Grid.Column="1"/> Margin="{StaticResource WideButtonRight}" />
</Grid> </FlexLayout>
</StackLayout>
<BoxView Style="{StaticResource HeadGraySeparator}"
<BoxView HeightRequest="1" Grid.Row="4" />
Color="{StaticResource Gray300}"
HorizontalOptions="FillAndExpand" <CollectionView ItemsSource="{Binding Songs}"
Margin="8" Grid.Row="5">
Grid.Row="3" /> <CollectionView.ItemTemplate>
<DataTemplate x:DataType="vm:SongViewModel">
<CollectionView ItemsSource="{Binding Songs}" <StackLayout>
Margin="0,0,0,24" <Grid Margin="{StaticResource TopBottomSpaceLittle}">
Grid.Row="4"> <Grid.ColumnDefinitions>
<CollectionView.ItemTemplate> <ColumnDefinition Width="{StaticResource SpaceL}"/>
<DataTemplate x:DataType="vm:SongViewModel"> <ColumnDefinition Width="*"/>
<StackLayout> <ColumnDefinition Width="Auto"/>
<Grid Margin="0,8,0,8"> </Grid.ColumnDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="32"/> <Label Text="{Binding Index}"
<ColumnDefinition Width="*"/> FontSize="{StaticResource SubSubtitleFontSize}"
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<Label Text="{Binding Index}"
FontSize="16"
TextColor="{StaticResource Gray}" TextColor="{StaticResource Gray}"
Margin="0,0,8,0" Margin="{StaticResource RightSpaceLittle}"
HorizontalTextAlignment="Center" HorizontalTextAlignment="Center"
HorizontalOptions="Center" HorizontalOptions="Center"
Grid.Column="0"/> Grid.Column="0"/>
<Label Text="{Binding Title}" <Label Text="{Binding Title}"
FontSize="16" FontSize="{StaticResource SubSubtitleFontSize}"
TextColor="{AppThemeBinding Light={StaticResource Primary}, Dark={StaticResource PrimaryDark}}" TextColor="{AppThemeBinding Light={StaticResource Primary}, Dark={StaticResource PrimaryDark}}"
LineBreakMode="TailTruncation" LineBreakMode="TailTruncation"
Grid.Column="1"/> Grid.Column="1"/>
<Label Text="···" <Label Text="{x:Static strings:Strings.HThreeDotsMenu}"
FontSize="16" FontSize="{StaticResource SubSubtitleFontSize}"
TextColor="{AppThemeBinding Light={StaticResource Primary}, Dark={StaticResource PrimaryDark}}" TextColor="{AppThemeBinding Light={StaticResource Primary}, Dark={StaticResource PrimaryDark}}"
FontAttributes="Bold" FontAttributes="Bold"
HorizontalOptions="End" HorizontalOptions="End"
Margin="0,0,8,0" Margin="{StaticResource RightSpaceLittle}"
Grid.Column="2"/> Grid.Column="2"/>
</Grid> </Grid>
<BoxView HeightRequest="1" <BoxView Style="{StaticResource GraySeparator}"/>
Color="{StaticResource Gray300}" </StackLayout>
HorizontalOptions="FillAndExpand" </DataTemplate>
Margin="32,0,8,0"/> </CollectionView.ItemTemplate>
</StackLayout> </CollectionView>
</DataTemplate>
</CollectionView.ItemTemplate> <StackLayout Margin="{StaticResource TopSpaceSome}"
</CollectionView> Grid.Row="6">
<Label Text="{Binding ReleaseDate, StringFormat='{0:d MMMM yyyy}'}"
<Label Text="{Binding ReleaseDate, StringFormat='{0:d MMMM yyyy}'}" Style="{StaticResource FooterLabel}" />
Style="{StaticResource FooterLabel}"
Grid.Row="5" /> <Label Text="{Binding SongsInfo, Converter={StaticResource SongsInfo}}"
Style="{StaticResource FooterLabel}" />
<Label Text="{Binding SongsInfo, Converter={StaticResource SongsInfo}}"
Style="{StaticResource FooterLabel}" <Label Text="{Binding CopyrightInfo, StringFormat='℗ \{0\} \{1\}'}"
Grid.Row="6" /> Style="{StaticResource FooterLabel}" />
</StackLayout>
<Label Text="{Binding CopyrightInfo, Converter={StaticResource CopyrightInfo}}"
Style="{StaticResource FooterLabel}" </Grid>
Grid.Row="7" /> </ScrollView>
</Grid> <!-- TODO Insert this in a main layout of some sort...-->
</ScrollView> <ctl:BottomBar Grid.Row="1" />
</Grid>
</ContentPage> </ContentPage>

@ -9,79 +9,78 @@
x:Class="AMC.View.Views.LibraryPage" x:Class="AMC.View.Views.LibraryPage"
x:DataType="vm:LibraryViewModel"> x:DataType="vm:LibraryViewModel">
<ScrollView <Grid>
Padding="0, 0, 0, 128" <Grid.RowDefinitions>
BackgroundColor="{AppThemeBinding Light={StaticResource Background}, Dark={StaticResource BackgroundDark}}"> <RowDefinition Height="*"/>
<StackLayout Spacing="8"> <RowDefinition Height="Auto"/>
<Label Text="{x:Static strings:Strings.LibraryTitle}" </Grid.RowDefinitions>
FontSize="32" <ScrollView Grid.Row="0"
FontAttributes="Bold" BackgroundColor="{AppThemeBinding Light={StaticResource Background}, Dark={StaticResource BackgroundDark}}">
TextColor="{AppThemeBinding Light={StaticResource Primary}, Dark={StaticResource PrimaryDark}}" <StackLayout Spacing="{StaticResource SpaceXS}"
Margin="8" /> Margin="{StaticResource BottomSpaceLittle}">
<Label Text="{x:Static strings:Strings.LibraryTitle}"
FontSize="{StaticResource TitleFontSize}"
FontAttributes="Bold"
TextColor="{AppThemeBinding Light={StaticResource Primary}, Dark={StaticResource PrimaryDark}}"
Margin="{StaticResource SpaceXS}" />
<StackLayout Orientation="Vertical" Spacing="0"> <StackLayout Orientation="Vertical" Spacing="0">
<ctl:LibraryCategoryItem <ctl:LibraryCategoryItem CategoryText="{x:Static strings:Strings.PlaylistsCategory}"
CategoryText="{x:Static strings:Strings.PlaylistsCategory}" IconSource="icon_categories_playlists.png" />
IconSource="icon_playlists.png" /> <BoxView Style="{StaticResource GraySeparator}"/>
<BoxView Style="{StaticResource GraySeparator}"/>
<ctl:LibraryCategoryItem <ctl:LibraryCategoryItem CategoryText="{x:Static strings:Strings.ArtistsCategory}"
CategoryText="{x:Static strings:Strings.ArtistsCategory}" IconSource="icon_categories_artists.png" />
IconSource="icon_artists.png" /> <BoxView Style="{StaticResource GraySeparator}"/>
<BoxView Style="{StaticResource GraySeparator}"/>
<ctl:LibraryCategoryItem <ctl:LibraryCategoryItem CategoryText="{x:Static strings:Strings.AlbumsCategory}"
CategoryText="{x:Static strings:Strings.AlbumsCategory}" IconSource="icon_categories_albums.png" />
IconSource="icon_albums.png" /> <BoxView Style="{StaticResource GraySeparator}"/>
<BoxView Style="{StaticResource GraySeparator}"/>
<ctl:LibraryCategoryItem <ctl:LibraryCategoryItem CategoryText="{x:Static strings:Strings.SongsCategory}"
CategoryText="{x:Static strings:Strings.SongsCategory}" IconSource="icon_categories_songs.png" />
IconSource="icon_songs.png" /> <BoxView Style="{StaticResource GraySeparator}"/>
<BoxView Style="{StaticResource GraySeparator}"/>
<ctl:LibraryCategoryItem <ctl:LibraryCategoryItem CategoryText="{x:Static strings:Strings.GenresCategory}"
CategoryText="{x:Static strings:Strings.GenresCategory}" IconSource="icon_categories_genres.png" />
IconSource="icon_genres.png" /> <BoxView Style="{StaticResource GraySeparator}"/>
<BoxView Style="{StaticResource GraySeparator}"/>
</StackLayout> </StackLayout>
<Label Text="{x:Static strings:Strings.RecentlyAddedHeader}" <Label Text="{x:Static strings:Strings.RecentlyAddedHeader}"
FontSize="16" FontSize="{StaticResource SubSubtitleFontSize}"
FontAttributes="Bold" FontAttributes="Bold"
TextColor="{AppThemeBinding Light={StaticResource Primary}, Dark={StaticResource PrimaryDark}}" TextColor="{AppThemeBinding Light={StaticResource Primary}, Dark={StaticResource PrimaryDark}}"
Margin="8, 16, 8, 8" /> Margin="{StaticResource SpaceXS}" />
<CollectionView <CollectionView ItemsSource="{Binding Albums}"
ItemsSource="{Binding Albums}" ItemsLayout="VerticalGrid, 2"
ItemsLayout="VerticalGrid, 2" SelectionMode="Single"
SelectionMode="Single" SelectionChanged="OnAlbumSelected">
SelectionChanged="OnAlbumSelected"> <CollectionView.ItemTemplate>
<CollectionView.ItemTemplate> <DataTemplate x:DataType="vm:AlbumViewModel">
<DataTemplate x:DataType="vm:AlbumViewModel"> <StackLayout Margin="{StaticResource SpaceS}">
<StackLayout Margin="16"> <Border>
<Border> <Border.StrokeShape>
<Border.StrokeShape> <RoundRectangle CornerRadius="{StaticResource SlightlyRoundedCorners}" />
<RoundRectangle CornerRadius="8,8,8,8" /> </Border.StrokeShape>
</Border.StrokeShape> <Image Source="{Binding CoverImage}"
<Image Aspect="AspectFill" />
Source="{Binding CoverImage}" </Border>
Aspect="AspectFill" />
</Border>
<Label <Label Text="{Binding Title}"
Text="{Binding Title}" TextColor="{AppThemeBinding Light={StaticResource Primary}, Dark={StaticResource PrimaryDark}}"
TextColor="{AppThemeBinding Light={StaticResource Primary}, Dark={StaticResource PrimaryDark}}" LineBreakMode="TailTruncation" />
LineBreakMode="TailTruncation" /> <Label Text="{Binding Artist}"
<Label TextColor="{StaticResource Gray}"
Text="{Binding Artist}" LineBreakMode="TailTruncation" />
TextColor="{StaticResource Gray}" </StackLayout>
LineBreakMode="TailTruncation" /> </DataTemplate>
</StackLayout> </CollectionView.ItemTemplate>
</DataTemplate> </CollectionView>
</CollectionView.ItemTemplate> </StackLayout>
</CollectionView> </ScrollView>
</StackLayout> <!-- TODO Insert this in a main layout of some sort...-->
</ScrollView> <ctl:BottomBar Grid.Row="1" />
</Grid>
</ContentPage> </ContentPage>

@ -8,6 +8,8 @@ namespace AMC.View.Views
public LibraryPage() : this(null) public LibraryPage() : this(null)
{ } { }
[System.Diagnostics.CodeAnalysis.SuppressMessage("Blocker Code Smell", "S3427:Method overloads with default parameter values should not overlap ", Justification = "The parameterless ctor is needed by MAUI")]
public LibraryPage(LibraryViewModel? libraryViewModel = null) public LibraryPage(LibraryViewModel? libraryViewModel = null)
{ {
InitializeComponent(); InitializeComponent();
@ -19,9 +21,8 @@ namespace AMC.View.Views
private void OnAlbumSelected(object sender, SelectionChangedEventArgs e) private void OnAlbumSelected(object sender, SelectionChangedEventArgs e)
{ {
var collectionView = (CollectionView)sender; var collectionView = (CollectionView)sender;
var selectedAlbum = (AlbumViewModel)e.CurrentSelection.FirstOrDefault();
if (selectedAlbum != null) if (e.CurrentSelection.FirstOrDefault() is AlbumViewModel selectedAlbum)
{ {
Navigation.PushAsync(new AlbumPage(selectedAlbum)); Navigation.PushAsync(new AlbumPage(selectedAlbum));
} }

@ -24,7 +24,7 @@ namespace AMC.ViewModel.ViewModels
Id = 1, Id = 1,
Title = "Test Album 1", Title = "Test Album 1",
Artist = "Test Artist 1", Artist = "Test Artist 1",
CoverImage = "macroblank_1.png", CoverImage = "album_macroblank_1.png",
Genre = "Test genre 1", Genre = "Test genre 1",
Year = 1970, Year = 1970,
ReleaseDate = new DateTime(1970, 01, 01), ReleaseDate = new DateTime(1970, 01, 01),
@ -48,7 +48,7 @@ namespace AMC.ViewModel.ViewModels
Id = 2, Id = 2,
Title = "Test Albuuuuuuuuuuuuuuuuuuum 2", Title = "Test Albuuuuuuuuuuuuuuuuuuum 2",
Artist = "Test Artist 2", Artist = "Test Artist 2",
CoverImage = "macroblank_2.png", CoverImage = "album_macroblank_2.png",
Genre = "Test genre 2", Genre = "Test genre 2",
Year = 1970, Year = 1970,
ReleaseDate = new DateTime(1970, 01, 01), ReleaseDate = new DateTime(1970, 01, 01),
@ -69,7 +69,7 @@ namespace AMC.ViewModel.ViewModels
Id = 3, Id = 3,
Title = "Test Album 3", Title = "Test Album 3",
Artist = "Test Aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaartist 3", Artist = "Test Aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaartist 3",
CoverImage = "macroblank_3.png", CoverImage = "album_macroblank_3.png",
Genre = "Test genre 3", Genre = "Test genre 3",
Year = 1970, Year = 1970,
ReleaseDate = new DateTime(1970, 01, 01), ReleaseDate = new DateTime(1970, 01, 01),

@ -1,7 +1,94 @@
# AD_MAUI # Apple Music Clone
A MAUI Apple Music mini-clone based on [these instructions](https://codefirst.iut.uca.fr/git/mchSamples_.NET/MAUI_TP1_2023) ## Project Overview
## README stuff Apple Music allows users to listen to music, browse playlists, and interact with a
sophisticated UI to control their playback.
It goes here... This clone does not.
Apple Music Clone is an application built on the .NET MAUI (Multi-platform App UI)
framework. It focuses on the `Library` "master-detail". As of the 20th of May 2023,
it is a _façade_: only the View part of the project is serviceable.
## The View
* ### Library ("master")
Users can browse a `Library` of `Album` and select any album to inspect.
* ### Album ("detail")
Users can browse an `Album` of `Songs`.
* ### Dark/Light theme
This clone replicates the original dark/light themes by Apple music.
To test this, you can change your device's (or your emulator's) display
setting to dark/light theme.
* ### `i18n`: `en`, `fr`
This clone supports two locales: English (by default), and French.
To test this, you can change your device's (or your emulator's) primary language
settings to English/French.
* ### Bottom bar
In Apple Music, a consistent, stylish, slightly transparent bottom bar allows
navigation between different views, and paying songs.
In this clone, the bar is just eye-candy, and not even a little bit transparent.
* ### Top bar
In Apple Music, a stylish, slightly transparent top bar contains certain menu
options, and displays the name of the current
section once the user has scrolled past the corresponding header.
In this clone, the top bar is left at the OS default.
## Installation
To run the Apple Music Clone, you must first install .NET MAUI. You can follow
[the official guide](https://learn.microsoft.com/en-us/dotnet/maui/get-started/installation)
for the same.
Once MAUI is installed, clone this repository and open the `AD_MAUI.sln` Solution in
*Visual Studio*.
If you don't have an Android emulator installed for *Visual Studio* already, open the
*Android Device Manager* to take care of that. For reference, this project was tested on a
`Pixel 5 - API 33 (Android 13.0 - API 33)`.
When you're ready to run the project, please make sure you launch the `AMC.View` project as a
`Single startup project`, if *Visual Studio* hasn't configured it that way automatically.
## Some known limitations and shortcomings
Concerning the View part of this project:
* the bottom bar is inserted once in the `Library` view, and once in the `Album` view
* instead, it should be incorporated in a main layout.
* the bottom bar's "top" part, AKA the player, has some repeated code.
```csharp
<ImageButton Source="{AppThemeBinding Light='icon_play.png', Dark='icon_play_dark.png'}"
Margin="{StaticResource SpaceS}"
HorizontalOptions="End"
VerticalOptions="Center"
HeightRequest="{StaticResource SpaceML}"
WidthRequest="{StaticResource SpaceML}" />
<ImageButton Source="{AppThemeBinding Light='icon_next.png', Dark='icon_next_dark.png'}"
Margin="{StaticResource SpaceS}"
HorizontalOptions="End"
VerticalOptions="Center"
HeightRequest="{StaticResource SpaceML}"
WidthRequest="{StaticResource SpaceML}" />
```
* instead, it should have been extracted into another reusable component, but there were
difficulties in doing that -- having to do with the dark/light themes.
* the bottom bar is not as stylish as the original.
* the top bar was left to the OS default.
* and many others will join this list, no doubt.
Loading…
Cancel
Save