-
Notifications
You must be signed in to change notification settings - Fork 4
/
DetailedAlbumsView.xaml.cs
367 lines (305 loc) · 15.9 KB
/
DetailedAlbumsView.xaml.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
using ExpressionBuilder;
using SoftdocMusicPlayer.Core.Models;
using SoftdocMusicPlayer.Helpers;
using SoftdocMusicPlayer.ViewModels;
using System;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Linq;
using System.Numerics;
using Windows.UI.Composition;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
using Windows.UI.Xaml.Controls.Primitives;
using Windows.UI.Xaml.Hosting;
using Windows.UI.Xaml.Media;
using Windows.UI.Xaml.Media.Animation;
using Windows.UI.Xaml.Navigation;
using EF = ExpressionBuilder.ExpressionFunctions;
using ExpressionNode = ExpressionBuilder.ExpressionNode;
namespace SoftdocMusicPlayer.Views
{
public sealed partial class DetailedAlbumPage : Page, INotifyPropertyChanged
{
#region Private fields
private ObservableCollection<SongModel> _collection;
private SongModel _detailedObject;
private MediaViewModel _viewModel;
private CompositionPropertySet _props;
private CompositionPropertySet _scrollerPropertySet;
private Compositor _compositor;
#endregion Private fields
#region Public fields
public ObservableCollection<SongModel> Collection
{
get => _collection;
set
{
if (_collection != value)
{
_collection = value;
OnPropertyChanged("Collection");
}
}
}
#endregion Public fields
#region Default constructor
public DetailedAlbumPage()
{
this.InitializeComponent();
// Register event to handle "on scroll" animation
Loaded += DetailedAlbumPage_Loaded;
_detailedObject = new SongModel();
_viewModel = new MediaViewModel();
Collection = new ObservableCollection<SongModel>();
}
#endregion Default constructor
#region NotifyPropertyChanged Event
// Event fires whenever property is changed.
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged(string propertyName) =>
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
#endregion NotifyPropertyChanged Event
#region Conntected page animation
protected override async void OnNavigatedTo(NavigationEventArgs e)
{
// Forward connected page animation.
base.OnNavigatedTo(e);
// Store the item to be used in binding to UI.
_detailedObject = e.Parameter as SongModel;
// Get animation from the destination page.
var animation = ConnectedAnimationService.GetForCurrentView().GetAnimation("forwardAnimation");
if (animation != null)
{
// new connected animation configuration.
CreateNewConfiguration(animation);
// Create coordinated animation
CreateImplicitAnimations();
// Play connected animation
animation.TryStart(ProfileImage);
}
// Update UI bindings.
await UpdateBindings();
}
protected override void OnNavigatedFrom(NavigationEventArgs e)
{
// Backward connected page animation.
base.OnNavigatedFrom(e);
if (e.NavigationMode == NavigationMode.Back)
{
var animation = ConnectedAnimationService.GetForCurrentView().PrepareToAnimate("backAnimation", ProfileImage);
if (animation != null)
{
// Use the recommended configuration for back animation.
animation.Configuration = new DirectConnectedAnimationConfiguration();
}
}
}
#endregion Conntected page animation
#region Methods
/// <summary>
/// Set compositor value if null
/// </summary>
private void FetchCompositor()
{
if (_compositor == null)
{
_compositor = ElementCompositionPreview.GetElementVisual(this).Compositor;
}
}
/// <summary>
/// Panel to be coordinated with connected animation.
/// </summary>
private void CreateImplicitAnimations()
{
TextContainer.Opacity = 0;
FetchCompositor();
// Animate the "more info" panel opacity when it first shows.
var fadeMoreInfroPanel = _compositor.CreateScalarKeyFrameAnimation();
fadeMoreInfroPanel.InsertKeyFrame(0, 0f);
fadeMoreInfroPanel.InsertKeyFrame(1, 1f);
fadeMoreInfroPanel.Duration = TimeSpan.FromSeconds(1.5f);
fadeMoreInfroPanel.Target = "Opacity";
// Set animation at the right place.
ElementCompositionPreview.SetImplicitShowAnimation(TextContainer, fadeMoreInfroPanel);
}
/// <summary>
/// Create new configuration for connected page animation.
/// </summary>
/// <param name="animation"></param>
private void CreateNewConfiguration(ConnectedAnimation animation)
{
FetchCompositor();
animation.Configuration = new DirectConnectedAnimationConfiguration();
// Configure for x-axis
var offsetXAnimation = _compositor.CreateScalarKeyFrameAnimation();
offsetXAnimation.Duration = System.TimeSpan.FromSeconds(0.75f);
offsetXAnimation.InsertExpressionKeyFrame(0.0f, "StartingValue");
offsetXAnimation.InsertExpressionKeyFrame(0.5f, "FinalValue");
offsetXAnimation.InsertExpressionKeyFrame(1.0f, "FinalValue");
animation.SetAnimationComponent(ConnectedAnimationComponent.OffsetX, offsetXAnimation);
// Configure for y-axis
var offsetYAnimation = _compositor.CreateScalarKeyFrameAnimation();
offsetYAnimation.Duration = System.TimeSpan.FromSeconds(0.75f);
offsetYAnimation.InsertExpressionKeyFrame(0.0f, "StartingValue");
offsetYAnimation.InsertExpressionKeyFrame(0.5f, "FinalValue");
offsetYAnimation.InsertExpressionKeyFrame(1.0f, "FinalValue");
animation.SetAnimationComponent(ConnectedAnimationComponent.OffsetY, offsetYAnimation);
}
/// <summary>
/// Update bindings to the UI elements.
/// </summary>
/// <returns></returns>
private async System.Threading.Tasks.Task UpdateBindings()
{
if (_detailedObject != null)
{
// Update header bindings.
ProfileImageBrush.ImageSource = _detailedObject.Thumbnail;
BackgroundHost.ImageSource = _detailedObject.Thumbnail;
TitleBlock.Text = _detailedObject.Album;
SubtitleBlock.Text = _detailedObject.Artist;
Genre.Text = _detailedObject.Genre;
if (Convert.ToInt32(_detailedObject.Year) > 0)
{
Year.Text = _detailedObject.Year;
Year.Visibility = Visibility.Visible;
Dot.Visibility = Visibility.Visible;
}
else
{
Year.Text = string.Empty;
Year.Visibility = Visibility.Collapsed;
Dot.Visibility = Visibility.Collapsed;
}
// Retreve all tracks that matches to the current album.
var tracks = await DataAccessLibrary.DataAccess.GetAlbumTracksAsync(_detailedObject.Album, _detailedObject.AlbumArtist);
foreach (var track in tracks)
{
Collection.Add(new SongModel()
{
Id = track.Id,
Thumbnail = track.Thumbnail,
Title = track.Title,
Artist = track.Artist,
Album = track.Album,
Duration = await ConvertToNaturalDuration(track.Duration)
});
}
}
}
/// <summary>
/// Returns natural duration of total <paramref name="duration"/>
/// </summary>
/// <param name="duration"></param>
/// <returns></returns>
private async System.Threading.Tasks.Task<string> ConvertToNaturalDuration(string duration)
{
double min = 0, sec = 0, totalSeconds;
await System.Threading.Tasks.Task.Run(() =>
{
// Converts total seconds to Natural Duration.
totalSeconds = Convert.ToDouble(duration);
min = Math.Floor(totalSeconds / 60);
sec = totalSeconds % 60;
});
return ((sec < 10) ? $"{min}:0{sec}" : $"{min}:{sec}");
}
#endregion Methods
#region Events
private void DetailedAlbumPage_Loaded(object sender, RoutedEventArgs e)
{
// Retrieve the ScrollViewer that the ListView is using internally
var scrollViewer = collectionListView.ChildrenBreadthFirst().OfType<ScrollViewer>().First();
var scrollBar = collectionListView.ChildrenBreadthFirst().OfType<ScrollBar>().First();
if (scrollBar != null)
{
scrollBar.Margin = new Thickness(0, 32, 0, 90);
}
// Update the ZIndex of the header container so that the header is above the items when scrolling
var headerPresenter = (UIElement)VisualTreeHelper.GetParent((UIElement)collectionListView.Header);
var headerContainer = (UIElement)VisualTreeHelper.GetParent(headerPresenter);
Canvas.SetZIndex(headerContainer, 1);
// Get the PropertySet that contains the scroll values from the ScrollViewer
_scrollerPropertySet = ElementCompositionPreview.GetScrollViewerManipulationPropertySet(scrollViewer);
_compositor = _scrollerPropertySet.Compositor;
// Create a PropertySet that has values to be referenced in the ExpressionAnimations below
_props = _compositor.CreatePropertySet();
_props.InsertScalar("progress", 0);
_props.InsertScalar("clampSize", 160);
_props.InsertScalar("scaleFactor", 0.5f);
_props.InsertScalar("textScaleFactor", 0.8f);
// Get references to our property sets for use with ExpressionNodes
var scrollingProperties = _scrollerPropertySet.GetSpecializedReference<ExpressionBuilder.ManipulationPropertySetReferenceNode>();
var props = _props.GetReference();
var progressNode = props.GetScalarProperty("progress");
var clampSizeNode = props.GetScalarProperty("clampSize");
var scaleFactorNode = props.GetScalarProperty("scaleFactor");
var textScaleFactorNode = props.GetScalarProperty("textScaleFactor");
// Create and start an ExpressionAnimation to track scroll progress over the desired distance
ExpressionNode progressAnimation = EF.Clamp(-scrollingProperties.Translation.Y / clampSizeNode, 0, 1);
_props.StartAnimation("progress", progressAnimation);
// Get the backing visual for the header so that its properties can be animated
Visual headerVisual = ElementCompositionPreview.GetElementVisual(Header);
// Create and start an ExpressionAnimation to clamp the header's offset to keep it onscreen
ExpressionNode headerTranslationAnimation = EF.Conditional(progressNode < 1, 0, -scrollingProperties.Translation.Y - clampSizeNode);
headerVisual.StartAnimation("Offset.Y", headerTranslationAnimation);
//Set the header's CenterPoint to ensure the overpan scale looks as desired
headerVisual.CenterPoint = new Vector3((float)(Header.ActualWidth / 2), (float)Header.ActualHeight, 0);
// Create and start an ExpressionAnimation to scale the header during overpan
ExpressionNode headerScaleAnimation = EF.Lerp(1, 1.25f, EF.Clamp(scrollingProperties.Translation.Y / 50, 0, 1));
headerVisual.StartAnimation("Scale.X", headerScaleAnimation);
headerVisual.StartAnimation("Scale.Y", headerScaleAnimation);
// Get the backing visual for the profile picture visual so that its properties can be animated
Visual profileVisual = ElementCompositionPreview.GetElementVisual(ProfileImage);
// Create and start an ExpressionAnimation to scale the profile image with scroll position
ExpressionNode scaleAnimation = EF.Lerp(1, scaleFactorNode, progressNode);
profileVisual.StartAnimation("Scale.X", scaleAnimation);
profileVisual.StartAnimation("Scale.Y", scaleAnimation);
// Get the backing visuals for the text and button containers so that their properites can be animated
Visual textVisual = ElementCompositionPreview.GetElementVisual(TextContainer);
Visual buttonVisual = ElementCompositionPreview.GetElementVisual(ButtonPanel);
// When the header stops scrolling it is 160 pixels offscreen. We want the text header to end up with 20 pixels of its content
// offscreen which means it needs to go from offset 0 to 140 as we traverse through the scrollable region
ExpressionNode profileOffsetXAnimation = progressNode * -20;
ExpressionNode profileOffsetYAnimation = progressNode * 140;
profileVisual.StartAnimation("Offset.X", profileOffsetXAnimation);
profileVisual.StartAnimation("Offset.Y", profileOffsetYAnimation);
ExpressionNode contentOffsetXAnimation = progressNode * -160;
ExpressionNode contentOffsetYAnimation = progressNode * 140;
textVisual.StartAnimation("Offset.X", contentOffsetXAnimation);
textVisual.StartAnimation("Offset.Y", contentOffsetYAnimation);
ExpressionNode buttonOffsetXAnimation = progressNode * -160;
ExpressionNode buttonOffsetYAnimation = progressNode * 10;
buttonVisual.StartAnimation("Offset.X", buttonOffsetXAnimation);
buttonVisual.StartAnimation("Offset.Y", buttonOffsetYAnimation);
// Get backing visuals for the text blocks so that their properties can be animated
Visual titleVisual = ElementCompositionPreview.GetElementVisual(TitleBlock);
Visual subtitleVisual = ElementCompositionPreview.GetElementVisual(SubtitleBlock);
Visual moreVisual = ElementCompositionPreview.GetElementVisual(MoreText);
// Create an ExpressionAnimation that moves between 1 and 0 with scroll progress, to be used for text block opacity
ExpressionNode textOpacityAnimation = EF.Clamp(1 - (progressNode * 2), 0, 1);
// Start opacity animation on the more text visual
moreVisual.StartAnimation("Opacity", textOpacityAnimation);
// Create and start an ExpressionAnimation to scale the text block visuals with scroll position
ExpressionNode textScaleAnimation = EF.Lerp(1, textScaleFactorNode, progressNode);
titleVisual.StartAnimation("Scale.X", textScaleAnimation);
titleVisual.StartAnimation("Scale.Y", textScaleAnimation);
subtitleVisual.StartAnimation("Scale.X", textScaleAnimation);
subtitleVisual.StartAnimation("Scale.Y", textScaleAnimation);
}
private async void PlayBtn_Clicked(object sender, RoutedEventArgs e)
{
// Retrieve the "list view item" of clicked item.
var content = ViewHelper.FindParent<ContentPresenter>(sender as DependencyObject);
if (content == null)
return;
if (content.DataContext is SongModel container)
{
_viewModel = new MediaViewModel();
await _viewModel.LoadMediaFromIdAsync(container.Id);
}
}
#endregion Events
}
}