diff --git a/Dimmer/AppShellMobile.xaml b/Dimmer/AppShellMobile.xaml index 26628ef1..03e3b072 100644 --- a/Dimmer/AppShellMobile.xaml +++ b/Dimmer/AppShellMobile.xaml @@ -6,7 +6,7 @@ xmlns:m="clr-namespace:UraniumUI.Icons.MaterialIcons;assembly=UraniumUI.Icons.MaterialIcons" Shell.TabBarUnselectedColor="Gray" Shell.TabBarForegroundColor="DarkSlateBlue" - Shell.TabBarBackgroundColor="#0C0E0D" + Shell.TabBarBackgroundColor="Black" Shell.BackgroundColor="Black" Shell.TabBarTitleColor="DarkSlateBlue" Shell.ForegroundColor="Transparent" diff --git a/Dimmer/AppShellMobile.xaml.cs b/Dimmer/AppShellMobile.xaml.cs index d30acc84..b39dd47a 100644 --- a/Dimmer/AppShellMobile.xaml.cs +++ b/Dimmer/AppShellMobile.xaml.cs @@ -17,4 +17,16 @@ public AppShellMobile() Routing.RegisterRoute(nameof(SpecificAlbumPage), typeof(SpecificAlbumPage)); Routing.RegisterRoute(nameof(AlbumPageM), typeof(AlbumPageM)); } + + protected override bool OnBackButtonPressed() + { + var bmtSheet = IPlatformApplication.Current.Services.GetService(); + if (bmtSheet.IsPresented) + { + bmtSheet.IsPresented = false; + return true; + } + return base.OnBackButtonPressed(); + + } } \ No newline at end of file diff --git a/Dimmer/CustomPopUpViews/SortingPopUp.xaml b/Dimmer/CustomPopUpViews/SortingPopUp.xaml index df7b8be1..ae6626c1 100644 --- a/Dimmer/CustomPopUpViews/SortingPopUp.xaml +++ b/Dimmer/CustomPopUpViews/SortingPopUp.xaml @@ -13,6 +13,11 @@ + + diff --git a/Dimmer/Dimmer-MAUI.csproj.user b/Dimmer/Dimmer-MAUI.csproj.user index 12d3194f..dc9d625e 100644 --- a/Dimmer/Dimmer-MAUI.csproj.user +++ b/Dimmer/Dimmer-MAUI.csproj.user @@ -2,9 +2,9 @@ False - net8.0-windows10.0.19041.0 - Windows Machine - PhysicalDevice + net8.0-android34.0 + Pixel 5 - API 34 (Android 14.0 - API 34) + Emulator pixel_5_-_api_34 SideloadOnly False diff --git a/Dimmer/MauiProgram.cs b/Dimmer/MauiProgram.cs index 0f1a0e38..91a00d0f 100644 --- a/Dimmer/MauiProgram.cs +++ b/Dimmer/MauiProgram.cs @@ -92,7 +92,8 @@ public static MauiApp CreateMauiApp() //builder.Services.AddSingleton(FilePicker.Default); builder.Services.AddSingleton(FileSaver.Default); - builder.Services.AddSingleton(); + //builder.Services.AddSingleton(); + builder.Services.AddTransient(); //uranium builder.Services.AddTransient(); /* Registering the DataAccess Services */ diff --git a/Dimmer/Platforms/Android/MAudioLib/MediaPlayerService.cs b/Dimmer/Platforms/Android/MAudioLib/MediaPlayerService.cs index b58ef409..b162dbe6 100644 --- a/Dimmer/Platforms/Android/MAudioLib/MediaPlayerService.cs +++ b/Dimmer/Platforms/Android/MAudioLib/MediaPlayerService.cs @@ -10,6 +10,7 @@ using Activity = Android.App.Activity; using Android.Content.PM; using System.Diagnostics; +using static Android.Icu.Text.Transliterator; namespace Dimmer_MAUI.Platforms.Android.MAudioLib; @@ -174,9 +175,16 @@ private void InitMediaSession() /// /// Intializes the player. /// + public void DoInitialInitialization() + { + InitializePlayer(); + InitMediaSession(); + + } private void InitializePlayer() { mediaPlayer = new MediaPlayer(); + mediaPlayer.SetAudioAttributes(new AudioAttributes.Builder() .SetContentType(AudioContentType.Music) .SetUsage(AudioUsageKind.Media).Build()); @@ -227,10 +235,8 @@ public bool OnError(MediaPlayer mp, MediaError what, int extra) public void OnPrepared(MediaPlayer mp) { - var pos = (int)IPlatformApplication.Current.Services.GetService().CurrentPositionInSeconds * 100; - mp.SeekTo(pos); + mp.SeekTo(positionInMs); mp.Start(); - UpdatePlaybackState(PlaybackStateCode.Playing); Console.WriteLine("Step 9 Prepared"); } @@ -298,13 +304,16 @@ public object Cover /// /// Intializes the player. /// + int positionInMs = 0; public async Task Play(int position = 0) { Console.WriteLine("Step 6 Play method from mediaplayerservice"); + positionInMs = position; if (mediaPlayer != null && MediaPlayerState == PlaybackStateCode.Paused) { //We are simply paused so just start again - Console.WriteLine("Not null"); + + Console.WriteLine("From Play Seeking to " + position); mediaPlayer.SeekTo(position); mediaPlayer.Start(); @@ -338,6 +347,7 @@ public async Task Play(int position = 0) isCurrentEpisode = true; await PrepareAndPlayMediaPlayerAsync(); + } private async Task PrepareAndPlayMediaPlayerAsync() @@ -469,7 +479,8 @@ public async Task Seek(int position = 0, PlaybackStateCode playbackStateCode = P { await Task.Run(() => { - mediaPlayer?.SeekTo(position); + Console.WriteLine("From Seek Seeking to "+position); + mediaPlayer?.SeekTo(position); UpdatePlaybackState(MediaPlayerState, position); }); } @@ -842,6 +853,7 @@ public override async void OnStop() public override async void OnSeekTo(long pos) { await mediaPlayerService.GetMediaPlayerService().Seek((int)pos); + Console.WriteLine("From OnSeek Seeking to " + pos); base.OnSeekTo(pos); } diff --git a/Dimmer/Platforms/Android/MAudioLib/NativeAudioService.android.cs b/Dimmer/Platforms/Android/MAudioLib/NativeAudioService.android.cs index 54131f9d..d16c0b5e 100644 --- a/Dimmer/Platforms/Android/MAudioLib/NativeAudioService.android.cs +++ b/Dimmer/Platforms/Android/MAudioLib/NativeAudioService.android.cs @@ -1,6 +1,7 @@ using Stream = Android.Media.Stream; using Android.Media; using Dimmer_MAUI.Platforms.Android.CurrentActivity; +using System.Diagnostics; namespace Dimmer_MAUI.Platforms.Android.MAudioLib; @@ -15,6 +16,7 @@ public class NativeAudioService : INativeAudioService //private MediaPlayer mediaPlayer => instance != null && // instance.Binder.GetMediaPlayerService() != null ? // instance.Binder.GetMediaPlayerService().mediaPlayer : null; + MediaPlay CurrentMedia { get; set; } private MediaPlayer mediaPlayer { get @@ -46,7 +48,7 @@ public double Duration get { if (mediaPlayer == null) - Console.WriteLine("media player is null"); + Console.WriteLine("media player is null in duration"); return mediaPlayer?.Duration / 1000 ?? 0; } } @@ -96,7 +98,7 @@ public Task PauseAsync() public async Task PlayAsync(double position = 0, bool IsFromUser = false) { - var posInMs = (int)(position * Duration * 1000); + var posInMs = (int)(position * CurrentMedia.DurationInMs); await instance.Binder.GetMediaPlayerService().Play((int)posInMs); } @@ -159,6 +161,7 @@ public Task DisposeAsync() public async Task InitializeAsync(MediaPlay media) { + CurrentMedia = media; if (instance == null) { var activity = CrossCurrentActivity.Current; @@ -169,9 +172,15 @@ public async Task InitializeAsync(MediaPlay media) instance.Binder.GetMediaPlayerService().isCurrentEpisode = false; instance.Binder.GetMediaPlayerService().UpdatePlaybackStateStopped(); } - + instance.Binder.GetMediaPlayerService().mediaPlay = media; + if (instance.Binder.GetMediaPlayerService().mediaPlayer == null) + { + instance.Binder.GetMediaPlayerService().DoInitialInitialization(); + } if (instance.Binder.GetMediaPlayerService().mediaPlayer == null) - Console.WriteLine("MediaPlayer is null"); + { + Debug.WriteLine("Let me know"); + } instance.Binder.GetMediaPlayerService().IsPlayingChanged += IsPlayingChanged; instance.Binder.GetMediaPlayerService().TaskPlayEnded += PlayEnded; instance.Binder.GetMediaPlayerService().TaskPlayNext += PlayNext; @@ -193,7 +202,7 @@ public async Task InitializeAsync(MediaPlay media) }; //if(media.Image!=null) instance.Binder.GetMediaPlayerService().Cover= await GetImageBitmapFromUrl(media.Image); //else instance.Binder.GetMediaPlayerService().Cover = null; - instance.Binder.GetMediaPlayerService().mediaPlay = media; + } } diff --git a/Dimmer/Platforms/Android/MainActivity.cs b/Dimmer/Platforms/Android/MainActivity.cs index 0f4350d6..1a674dd0 100644 --- a/Dimmer/Platforms/Android/MainActivity.cs +++ b/Dimmer/Platforms/Android/MainActivity.cs @@ -32,9 +32,10 @@ protected override void OnCreate(Bundle? savedInstanceState) base.OnCreate(savedInstanceState); //Window.SetStatusBarColor(Android.Graphics.Color.ParseColor("#000000")); Window.SetStatusBarColor(Android.Graphics.Color.Black); - Window.SetNavigationBarColor(Android.Graphics.Color.ParseColor("#0C0E0D")); + Window.SetNavigationBarColor(Android.Graphics.Color.Black); + //Window.SetNavigationBarColor(Android.Graphics.Color.ParseColor("#0C0E0D")); CrossCurrentActivity.Current.Init(this, savedInstanceState); - + NotificationHelper.CreateNotificationChannel(Platform.AppContext); if (mediaPlayerServiceConnection is null) { diff --git a/Dimmer/Properties/PublishProfiles/MSIX-win10-x64.pubxml b/Dimmer/Properties/PublishProfiles/MSIX-win10-x64.pubxml new file mode 100644 index 00000000..37f79a3e --- /dev/null +++ b/Dimmer/Properties/PublishProfiles/MSIX-win10-x64.pubxml @@ -0,0 +1,16 @@ + + + + bin\Debug\net8.0-android34.0\publish\ + FileSystem + win10-x64 + Any CPU + Debug + net8.0-windows10.0.19041.0 + false + false + True + true + bin\Debug\net8.0-windows10.0.19041.0\win10-x64\AppPackages\ + + \ No newline at end of file diff --git a/Dimmer/Utilities/Services/PlaybackUtilsService.cs b/Dimmer/Utilities/Services/PlaybackUtilsService.cs index 3a8c2bde..5c86e91a 100644 --- a/Dimmer/Utilities/Services/PlaybackUtilsService.cs +++ b/Dimmer/Utilities/Services/PlaybackUtilsService.cs @@ -417,8 +417,11 @@ public async Task LoadSongsFromFolder(List folderPaths) // await SongsMgtService.AddSongBatchAsync(songs!); //} - List dbSongs = songs.Select(song => new SongsModel(song)).ToList(); - + List dbSongs = new(); + if (songs != null) + { + dbSongs = songs.Select(song => new SongsModel(song)).ToList(); + } ArtistsMgtService.AddSongToArtistWithArtistIDAndAlbum(allArtists, allAlbums, allLinks, dbSongs); var songss = SongsMgtService.AllSongs.Concat(songs).ToObservableCollection(); @@ -587,6 +590,7 @@ public async Task PlaySelectedSongsOutsideAppAsync(string[] filePaths) return true; } + private readonly SemaphoreSlim _semaphore = new SemaphoreSlim(1, 1); // Initialize with a count of 1 public async Task PlaySongAsync(SongsModelView? song = null, int currentQueue = 0, ObservableCollection? currentList = null, double lastPositionPercentage = 0, int repeatMode = 0, int repeatMaxCount = 0) @@ -656,20 +660,27 @@ public async Task PlaySongAsync(SongsModelView? song = null, int currentQu _playerStateSubject.OnNext(MediaPlayerState.LyricsLoad); //var coverImage = GetCoverImage(ObservableCurrentlyPlayingSong!.FilePath, true); - - await audioService.InitializeAsync(new MediaPlay() + await _semaphore.WaitAsync(); + try { - Name = ObservableCurrentlyPlayingSong.Title, - Author = ObservableCurrentlyPlayingSong!.ArtistName!, - URL = ObservableCurrentlyPlayingSong.FilePath, - ImageBytes = coverImage, - DurationInMs = (long)(ObservableCurrentlyPlayingSong.DurationInSeconds * 1000), - }); - - var positionInSeconds = (double)lastPositionPercentage * ObservableCurrentlyPlayingSong.DurationInSeconds; - - await audioService.PlayAsync(lastPositionPercentage, IsFromUser: true); - bugCount = 0; + // First, initialize the audio service + await audioService.InitializeAsync(new MediaPlay() + { + Name = ObservableCurrentlyPlayingSong.Title, + Author = ObservableCurrentlyPlayingSong!.ArtistName!, + URL = ObservableCurrentlyPlayingSong.FilePath, + ImageBytes = coverImage, + DurationInMs = (long)(ObservableCurrentlyPlayingSong.DurationInSeconds * 1000), + }); + + // Now, play the audio after initialization has completed + await audioService.PlayAsync(lastPositionPercentage, IsFromUser: true); + } + finally + { + // Release the semaphore so other tasks can proceed + _semaphore.Release(); + } _positionTimer.Start(); diff --git a/Dimmer/ViewModels/HomePageVM.cs b/Dimmer/ViewModels/HomePageVM.cs index 46a0278f..42ea5cc9 100644 --- a/Dimmer/ViewModels/HomePageVM.cs +++ b/Dimmer/ViewModels/HomePageVM.cs @@ -65,7 +65,7 @@ public HomePageVM(IPlaybackUtilsService PlaybackManagerService, IFolderPicker fo ArtistMgtService = artistMgtService; CurrentSortingOption = AppSettingsService.SortingModePreference.GetSortingPref(); - //Subscriptions to SongsManagerService + SubscribeToPlayerStateChanges(); SubscribetoDisplayedSongsChanges(); SubscribeToCurrentSongPosition(); @@ -387,56 +387,65 @@ void SubscribeToPlayerStateChanges() .Subscribe(state => { TemporarilyPickedSong = PlayBackService.CurrentlyPlayingSong; - if (DisplayedSongs is not null) + if (TemporarilyPickedSong is not null) { - var songIndex = DisplayedSongs.IndexOf(DisplayedSongs.First(x => x.Id == TemporarilyPickedSong.Id)); - if (songIndex != -1) + if (DisplayedSongs is not null) { - DisplayedSongs[songIndex] = TemporarilyPickedSong; - } - } + if (TemporarilyPickedSong is not null) + { + var songIndex = DisplayedSongs.IndexOf(DisplayedSongs.First(x => x.Id == TemporarilyPickedSong.Id)); - PickedSong = TemporarilyPickedSong; - SelectedSongToOpenBtmSheet = TemporarilyPickedSong; - switch (state) - { - case MediaPlayerState.Playing: + if (songIndex != -1) + { + DisplayedSongs[songIndex] = TemporarilyPickedSong; + } + } - AllSyncLyrics = null; - splittedLyricsLines = null; + } - IsPlaying = true; - CurrentLyricPhrase = new LyricPhraseModel() { Text = "" }; - if (CurrentPage == PageEnum.FullStatsPage) - { - ShowGeneralTopXSongs(); - //ShowSingleSongStats(PickedSong); - } - CurrentRepeatCount = PlayBackService.CurrentRepeatCount; - break; - case MediaPlayerState.Paused: - IsPlaying = false; - break; - case MediaPlayerState.Stopped: - //PickedSong = "Stopped"; - break; - case MediaPlayerState.LoadingSongs: - LoadingSongsProgress = PlayBackService.LoadingSongsProgressPercentage; - break; - case MediaPlayerState.ShowPlayBtn: - IsPlaying = false; - break; - case MediaPlayerState.ShowPauseBtn: - IsPlaying = true; - break; - default: - break; + PickedSong = TemporarilyPickedSong; + SelectedSongToOpenBtmSheet = TemporarilyPickedSong; + switch (state) + { + case MediaPlayerState.Playing: + + AllSyncLyrics = null; + splittedLyricsLines = null; + + IsPlaying = true; + CurrentLyricPhrase = new LyricPhraseModel() { Text = "" }; + if (CurrentPage == PageEnum.FullStatsPage) + { + ShowGeneralTopXSongs(); + //ShowSingleSongStats(PickedSong); + } + CurrentRepeatCount = PlayBackService.CurrentRepeatCount; + break; + case MediaPlayerState.Paused: + IsPlaying = false; + break; + case MediaPlayerState.Stopped: + //PickedSong = "Stopped"; + break; + case MediaPlayerState.LoadingSongs: + LoadingSongsProgress = PlayBackService.LoadingSongsProgressPercentage; + break; + case MediaPlayerState.ShowPlayBtn: + IsPlaying = false; + break; + case MediaPlayerState.ShowPauseBtn: + IsPlaying = true; + break; + default: + break; + } } }); } + public void Dispose() { _playerStateSubscription?.Dispose(); diff --git a/Dimmer/Views/Mobile/CustomViewsM/MediaPlaybackControlsViewM.xaml.cs b/Dimmer/Views/Mobile/CustomViewsM/MediaPlaybackControlsViewM.xaml.cs index eca097f7..094a9c43 100644 --- a/Dimmer/Views/Mobile/CustomViewsM/MediaPlaybackControlsViewM.xaml.cs +++ b/Dimmer/Views/Mobile/CustomViewsM/MediaPlaybackControlsViewM.xaml.cs @@ -3,17 +3,17 @@ namespace Dimmer_MAUI.Views.Mobile.CustomViewsM; public partial class MediaPlaybackControlsViewM : ContentView { public HomePageVM HomePageVM { get; } - public NowPlayingSongPageBtmSheet NowPlayingBtmSheet { get; set; } + public NowPlayingBtmSheetContainer NowPlayingBtmSheet { get; set; } public MediaPlaybackControlsViewM() { InitializeComponent(); HomePageVM = IPlatformApplication.Current.Services.GetService(); - NowPlayingBtmSheet = IPlatformApplication.Current.Services.GetService(); + //NowPlayingBtmSheet = IPlatformApplication.Current.Services.GetService(); BindingContext = HomePageVM; - NowPlayingBtmSheet.Dismissed += NowPlayingBtmSheet_Dismissed; + } private void NowPlayingBtmSheet_Dismissed(object? sender, DismissOrigin e) { @@ -23,14 +23,14 @@ private void NowPlayingBtmSheet_Dismissed(object? sender, DismissOrigin e) private async void MediaControlBtmBar_Tapped(object sender, TappedEventArgs e) { - await NowPlayingBtmSheet.ShowAsync(); + //await NowPlayingBtmSheet.ShowAsync(); DeviceDisplay.Current.KeepScreenOn = true; //await Shell.Current.GoToAsync(nameof(NowPlayingPageM),true); } private async void SwipeGestureRecognizer_Swiped(object sender, SwipedEventArgs e) { - await NowPlayingBtmSheet.ShowAsync(); + //await NowPlayingBtmSheet.ShowAsync(); DeviceDisplay.Current.KeepScreenOn = true; } } \ No newline at end of file diff --git a/Dimmer/Views/Mobile/CustomViewsM/NowPlayingBtmSheet.cs b/Dimmer/Views/Mobile/CustomViewsM/NowPlayingBtmSheet.cs new file mode 100644 index 00000000..8c74a740 --- /dev/null +++ b/Dimmer/Views/Mobile/CustomViewsM/NowPlayingBtmSheet.cs @@ -0,0 +1,252 @@ +using InputKit.Shared.Helpers; +using UraniumUI.Extensions; + +namespace Dimmer_MAUI.Views.Mobile.CustomViewsM; +[ContentProperty(nameof(Body))] +public partial class NowPlayingBtmSheetContainer : Border, IPageAttachment +{ + public UraniumContentPage AttachedPage { get; protected set; } + public AttachmentPosition AttachmentPosition => AttachmentPosition.Front; + + public View Body { get; set; } + + public View? HeaderWhenClosed { get; set; } + + + private TapGestureRecognizer closeGestureRecognizer = new(); + public void OnAttached(UraniumContentPage page) + { + Init(); + + AttachedPage = page; + if (Body != null) + { + Body.SizeChanged += (s, e) => AlignBottomSheet(true); + } + } + protected virtual void Init() + { + HeaderWhenClosed ??= GenerateAnchor(); // Header when sheet is closed + HeaderWhenClosed.IsVisible = true; // Show HeaderWhenClosed + + Padding = 0; + this.StyleClass = new[] { "BottomSheet" }; + this.VerticalOptions = LayoutOptions.End; + this.HorizontalOptions = LayoutOptions.Fill; + this.Body.VerticalOptions = LayoutOptions.End; + + // Initially, show only HeaderWhenClosed and Body + this.Content = new VerticalStackLayout() + { + Children = + { + HeaderWhenClosed, // Show HeaderWhenClosed when closed + Body // The body content + } + }; + + if (DeviceInfo.Idiom != DeviceIdiom.Desktop) + { + var panGestureRecognizer = new PanGestureRecognizer(); + panGestureRecognizer.PanUpdated += PanGestureRecognizer_PanUpdated; + HeaderWhenClosed.GestureRecognizers.Add(panGestureRecognizer); + Body.GestureRecognizers.Add(panGestureRecognizer); // Only attach swipe-to-dismiss to Body + } + var tapGestureRecognizer = new TapGestureRecognizer(); + tapGestureRecognizer.Tapped += HeaderWhenClosedTapGestureRecognizer_Tapped; //(s, e) => IsPresented = !IsPresented; + HeaderWhenClosed.GestureRecognizers.Add(tapGestureRecognizer); + + closeGestureRecognizer.Tapped += (s, e) => IsPresented = false; // Removed tap-to-close on the border + + } + + + // Generate the visual for HeaderWhenClosed (when closed) + protected virtual View GenerateAnchor() + { + var anchor = new ContentView + { + HorizontalOptions = LayoutOptions.Fill, + Padding = 10, + Content = new BoxView + { + HeightRequest = 2, + CornerRadius = 2, + WidthRequest = 50, + Color = this.BackgroundColor?.ToSurfaceColor() ?? Microsoft.Maui.Graphics.Colors.Gray, + HorizontalOptions = LayoutOptions.Center, + } + }; + + return anchor; + } + + protected virtual void OnOpened() + { + if (CloseOnTapOutside) + { + AttachedPage?.ContentFrame?.GestureRecognizers.Add(closeGestureRecognizer); + } + Shell.SetNavBarIsVisible(this.AttachedPage, false); + Shell.SetTabBarIsVisible(this.AttachedPage, false); + DeviceDisplay.Current.KeepScreenOn = true; + } + + protected virtual void OnClosed() + { + if (CloseOnTapOutside) + { + AttachedPage?.ContentFrame?.GestureRecognizers.Remove(closeGestureRecognizer); + } + Shell.SetNavBarIsVisible(this.AttachedPage, false); + Shell.SetTabBarIsVisible(this.AttachedPage, true); + + DeviceDisplay.Current.KeepScreenOn = false; + } + + private bool isVerticalPan = false; + + private async void HeaderWhenClosedTapGestureRecognizer_Tapped(object? sender, TappedEventArgs e) + { + // Set the sheet to open and hide the closed header + IsPresented = true; + AlignBottomSheet(true); + } + private void PanGestureRecognizer_PanUpdated(object sender, PanUpdatedEventArgs e) + { + switch (e.StatusType) + { + case GestureStatus.Started: + isVerticalPan = false; + break; + + case GestureStatus.Running: + if (Math.Abs(e.TotalY) > Math.Abs(e.TotalX) && !isVerticalPan) + { + isVerticalPan = true; // Mark this as a vertical pan + } + + if (isVerticalPan) + { + Body.IsVisible = true; + var isApple = DeviceInfo.Current.Platform == DevicePlatform.iOS || DeviceInfo.Current.Platform == DevicePlatform.MacCatalyst; + + var y = TranslationY + (isApple ? e.TotalY * .05 : e.TotalY); + + this.TranslationY = y; + } + break; + + case GestureStatus.Completed: + case GestureStatus.Canceled: + var openThresholdHeight = this.Height * 0.10; + var closeThresholdHeight = this.Height * 0.10; + + if (isVerticalPan) + { + if (IsPresented) + { + if (this.TranslationY > closeThresholdHeight) + { + IsPresented = false; + } + } + else + { + + IsPresented = true; + + } + } + + AlignBottomSheet(); // Align the sheet after the gesture is completed or canceled + break; + } + } + + // Align and switch headers (HeaderWhenClosed) + private void AlignBottomSheet(bool animate = true) + { + double y; + double heightRequest; + + if (IsPresented) + { + y = 0; // Opened, bring it to full view + HeaderWhenClosed.HeightRequest = 0; + HeaderWhenClosed.Opacity = 0; + OnOpened(); + this.TranslateToSafely(this.X, y, 250, Easing.CubicInOut); + Body.HeightRequest = AttachedPage.Height; + this.HeightRequest = AttachedPage.Height; + } + else + { + HeaderWhenClosed.HeightRequest = 65; + HeaderWhenClosed.Opacity = 1; + y = this.Height - 65; // Align the bottom sheet to be at the bottom + OnClosed(); + this.TranslateToSafely(this.X, y, 250, Easing.CubicInOut); + } + UpdateDisabledStateOfPage(); + } + + // Disabling the rest of the page if needed + protected void UpdateDisabledStateOfPage() + { + if (AttachedPage?.Body != null && DisablePageWhenOpened) + { + AttachedPage.Body.InputTransparent = IsPresented; + + AttachedPage.Body.FadeToSafely(IsPresented ? .5 : 1); + } + } +} + + +public partial class NowPlayingBtmSheetContainer +{ + // Event to notify when IsPresented has changed + public event EventHandler IsPresentedChanged; + public bool IsPresented { + get => (bool)GetValue(IsPresentedProperty); + set { + SetValue(IsPresentedProperty, value); + OnIsPresentedChanged(value); + } + } + + // Method to raise the event when IsPresented changes + protected virtual void OnIsPresentedChanged(bool newValue) + { + IsPresentedChanged?.Invoke(this, newValue); // Fire the event with the new value + } + + + public static readonly BindableProperty IsPresentedProperty = + BindableProperty.Create( + nameof(IsPresented), + typeof(bool), + typeof(NowPlayingBtmSheetContainer), + defaultValue: false, + defaultBindingMode: BindingMode.TwoWay, + propertyChanged: (bo, ov, nv) => + { + (bo as NowPlayingBtmSheetContainer). + AlignBottomSheet(); + }); + + public bool DisablePageWhenOpened { get => (bool)GetValue(DisablePageWhenOpenedProperty); set => SetValue(DisablePageWhenOpenedProperty, value); } + + public static readonly BindableProperty DisablePageWhenOpenedProperty = + BindableProperty.Create( + nameof(DisablePageWhenOpened), + typeof(bool), typeof(NowPlayingBtmSheetContainer), defaultValue: true); + + public bool CloseOnTapOutside { get => (bool)GetValue(CloseOnTapOutsideProperty); set => SetValue(CloseOnTapOutsideProperty, value); } + + public static readonly BindableProperty CloseOnTapOutsideProperty = + BindableProperty.Create( + nameof(CloseOnTapOutside), + typeof(bool), typeof(UraniumUI.Material.Attachments.BackdropView), defaultValue: true); +} \ No newline at end of file diff --git a/Dimmer/Views/Mobile/HomePageM.xaml b/Dimmer/Views/Mobile/HomePageM.xaml index 06f08cea..c7442f0c 100644 --- a/Dimmer/Views/Mobile/HomePageM.xaml +++ b/Dimmer/Views/Mobile/HomePageM.xaml @@ -9,7 +9,7 @@ xmlns:converters="clr-namespace:Dimmer_MAUI.Utilities.TypeConverters" xmlns:vm="clr-namespace:Dimmer_MAUI.ViewModels" xmlns:models="clr-namespace:Dimmer_MAUI.Utilities.Services.Models" - xmlns:thumblessSlider="clr-namespace:Dimmer_MAUI.Views.Mobile" + xmlns:viewsM="clr-namespace:Dimmer_MAUI.Views.Mobile" xmlns:cv="clr-namespace:Dimmer_MAUI.Views.Mobile.CustomViewsM" xmlns:cm="https://github.com/jerry08/Plugin.ContextMenuContainer" x:DataType="vm:HomePageVM" @@ -19,30 +19,48 @@ Shell.TabBarBackgroundColor="Black" Shell.TabBarIsVisible="True" > - - - - + + + + + + + + + + + + + + - + @@ -51,45 +69,30 @@ + Grid.Column="2" IsVisible="False"> - - - - - - - - - - - - - - - - - - - + + + + + + - - @@ -150,7 +153,7 @@ - + @@ -180,18 +183,18 @@ - - - - - + + +