Summary views implementation.
authorPiotr Czaja/Advanced Frameworks (PLT) /SRPOL/Engineer/Samsung Electronics <p.czaja@samsung.com>
Thu, 10 Jun 2021 11:48:33 +0000 (13:48 +0200)
committerPiotr Czaja <p.czaja@samsung.com>
Tue, 14 Sep 2021 11:01:34 +0000 (13:01 +0200)
13 files changed:
Fitness/Controls/Camera.cs
Fitness/Services/NavigationService.cs
Fitness/ViewModels/ChangeWorkoutViewModel.cs [new file with mode: 0644]
Fitness/ViewModels/ExercisePreviewViewModel.cs
Fitness/ViewModels/ExercisingViewModel.cs
Fitness/ViewModels/SummaryType.cs [new file with mode: 0644]
Fitness/ViewModels/SummaryViewModel.cs
Fitness/Views/BarView.xaml.cs
Fitness/Views/ExercisingView.xaml.cs
Fitness/res/layout/BarView.xaml
Fitness/res/layout/ExercisingView.xaml
Fitness/res/layout/PauseView.xaml
Fitness/res/layout/SummaryView.xaml

index ab1f66b319605a6e88079d2b00b4d30a74eeb94c..36f0bf299f691a5b73ceea20947e5d68083bed67 100644 (file)
@@ -95,6 +95,11 @@ namespace Fitness.Controls
         /// </summary>
         public void StopPreview() => camera.StopPreview();
 
+        /// <summary>
+        /// Starts capturing and drawing preview frames on the screen.
+        /// </summary>
+        public void StartPreview() => camera.StartPreview();
+
         /// <summary>
         /// Dispose.
         /// </summary>
index 3f430956635d5f9ed115611c26582cc9fb80a429..3e6806d85da530271d6ab1262f466f9f89a1d784 100644 (file)
@@ -1,6 +1,8 @@
 using System;
 using System.Threading.Tasks;
 using Fitness.Controls;
+using Fitness.Models;
+using Fitness.ViewModels;
 using Fitness.Views;
 using Tizen.NUI;
 
@@ -35,9 +37,17 @@ namespace Fitness.Services
             navigation.Push(new MainView());
         }
 
-        public void NavigateToExercisingView()
+        public async Task NavigateToExercisingView(WorkoutViewModel workoutViewModel)
         {
-            navigation.Push(new ExercisingView());
+            var result = await PrivilegeService.Instance.CheckCameraPrivilege();
+            if (result == Tizen.Security.RequestResult.AllowForever)
+            {
+                var view = new ExercisingView()
+                {
+                    BindingContext = new ExercisingViewModel(workoutViewModel),
+                };
+                navigation.Push(view);
+            }
         }
 
         public void NavigateToExercisePreviewView(string workoutId)
@@ -54,20 +64,13 @@ namespace Fitness.Services
             }
         }
 
-        public async Task NavigateToLoadingView()
+        public void NavigateToSummaryView(SummaryType summaryType, WorkoutViewModel workoutViewModel)
         {
-            navigation.HideLast();
-            var result = await PrivilegeService.Instance.CheckCameraPrivilege();
-            navigation.ShowLast();
-            if (result == Tizen.Security.RequestResult.AllowForever)
+            var view = new SummaryView()
             {
-                navigation.Push(new LoadingView());
-            }
-        }
-
-        public void NavigateToSummaryView()
-        {
-            navigation.Push(new SummaryView());
+                BindingContext = new SummaryViewModel(summaryType, workoutViewModel),
+            };
+            navigation.Push(view);
         }
 
         public void Pop()
diff --git a/Fitness/ViewModels/ChangeWorkoutViewModel.cs b/Fitness/ViewModels/ChangeWorkoutViewModel.cs
new file mode 100644 (file)
index 0000000..18c9e16
--- /dev/null
@@ -0,0 +1,82 @@
+using System.Collections.Generic;
+using System.Windows.Input;
+using Fitness.Services;
+using Tizen.NUI.Binding;
+
+namespace Fitness.ViewModels
+{
+    /// <summary>
+    /// Class handling the workout change.
+    /// </summary>
+    public abstract class ChangeWorkoutViewModel : BaseViewModel
+    {
+        private WorkoutViewModel currentWorkout;
+
+        /// <summary>
+        /// Initializes a new instance of the <see cref="ChangeWorkoutViewModel"/> class.
+        /// </summary>
+        public ChangeWorkoutViewModel()
+        {
+            PreviousWorkout = new Command(GoPrevious);
+            NextWorkout = new Command(GoNext);
+
+            Workouts = WorkoutRepository.Instance.GetAll();
+        }
+
+        /// <summary>
+        /// Gets or sets review movement.
+        /// </summary>
+        public WorkoutViewModel CurrentWorkout
+        {
+            get => currentWorkout;
+            set
+            {
+                if (value != currentWorkout)
+                {
+                    currentWorkout = value;
+                    RaisePropertyChanged();
+                }
+            }
+        }
+
+        /// <summary>
+        /// Gets previous workout.
+        /// </summary>
+        public ICommand PreviousWorkout { get; private set; }
+
+        /// <summary>
+        /// Gets next workout.
+        /// </summary>
+        public ICommand NextWorkout { get; private set; }
+
+        /// <summary>
+        /// Gets list of all available workouts.
+        /// </summary>
+        public IList<WorkoutViewModel> Workouts { get; private set; }
+
+        /// <summary>
+        /// Go to next workout.
+        /// </summary>
+        protected virtual void GoNext()
+        {
+            MoveBy(1);
+        }
+
+        /// <summary>
+        /// Go to previous workout.
+        /// </summary>
+        protected virtual void GoPrevious()
+        {
+            MoveBy(-1);
+        }
+
+        private void MoveBy(int offset)
+        {
+            int idx = Workouts.IndexOf(CurrentWorkout) + offset;
+            if (idx >= 0 && idx < Workouts.Count)
+            {
+                CurrentWorkout = Workouts[idx];
+            }
+        }
+    }
+}
index 17097ca79c271b4e8207743aed131fbcd0fab0a5..d721659760e982b93aba6cfe9ddc36ddcae9ce21 100644 (file)
@@ -10,34 +10,12 @@ using Tizen.NUI.Binding;
 
 namespace Fitness.ViewModels
 {
-    public class ExercisePreviewViewModel : BaseViewModel
+    public class ExercisePreviewViewModel : ChangeWorkoutViewModel
     {
-        private WorkoutViewModel currentWorkout;
-
         public ExercisePreviewViewModel()
         {
-            Skip = new Command(NavigationService.Instance.NavigateToExercisingView);
+            Skip = new Command(() => { _ = NavigationService.Instance.NavigateToExercisingView(CurrentWorkout); });
             Back = new Command(NavigationService.Instance.Pop);
-            PreviousWorkout = new Command(GoPrevious);
-            NextWorkout = new Command(GoNext);
-
-            Workouts = WorkoutRepository.Instance.GetAll();
-        }
-
-        /// <summary>
-        /// Review movement
-        /// </summary>
-        public WorkoutViewModel CurrentWorkout
-        {
-            get => currentWorkout;
-            set
-            {
-                if (value != currentWorkout)
-                {
-                    currentWorkout = value;
-                    RaisePropertyChanged();
-                }
-            }
         }
 
         /// <summary>
@@ -49,39 +27,5 @@ namespace Fitness.ViewModels
         /// Skip review
         /// </summary>
         public ICommand Skip { get; private set; }
-
-        /// <summary>
-        /// previous workout
-        /// </summary>
-        public ICommand PreviousWorkout { get; private set; }
-
-        /// <summary>
-        /// next workout
-        /// </summary>
-        public ICommand NextWorkout { get; private set; }
-
-        /// <summary>
-        /// List of all available workouts
-        /// </summary>
-        public IList<WorkoutViewModel> Workouts { get; private set; }
-
-        private void GoNext()
-        {
-            MoveBy(1);
-        }
-
-        private void GoPrevious()
-        {
-            MoveBy(-1);
-        }
-
-        private void MoveBy(int offset)
-        {
-            int idx = Workouts.IndexOf(CurrentWorkout) + offset;
-            if (idx >= 0 && idx < Workouts.Count)
-            {
-                CurrentWorkout = Workouts[idx];
-            }
-        }
     }
 }
index b151a4777d63db7f82750ec1f2a8e21a0e2815ec..7597720e3aa7b4410c40e3a649072f5859a1086c 100644 (file)
@@ -5,24 +5,19 @@ using Tizen.NUI.Binding;
 
 namespace Fitness.ViewModels
 {
-    public class ExercisingViewModel : BaseViewModel
+    public class ExercisingViewModel : ChangeWorkoutViewModel
     {
         private WorkoutState state;
 
-        public ExercisingViewModel()
+        public ExercisingViewModel(WorkoutViewModel workoutViewModel)
         {
             PauseResumeWorkout = new Command(TriggerPauseResumeWorkout);
+            TryAgain = new Command(ExecuteTryAgain);
             EndWorkout = new Command(ExecuteEndWorkout);
-            Prev = new Command(ExecutePrev);
-            Next = new Command(ExecuteNext);
             State = WorkoutState.Playing;
+            CurrentWorkout = workoutViewModel;
         }
 
-        /// <summary>
-        /// Gets title.
-        /// </summary>
-        public string Title { get; private set; } = "Sit-up";
-
         /// <summary>
         /// Current score
         /// </summary>
@@ -90,9 +85,9 @@ namespace Fitness.ViewModels
         public ICommand PauseResumeWorkout { get; private set; }
 
         /// <summary>
-        /// Gets ReviewMovement Command.
+        /// Gets TryAgain Command.
         /// </summary>
-        public ICommand ReviewMovement { get; private set; }
+        public ICommand TryAgain { get; private set; }
 
         /// <summary>
         /// Gets EndWorkout Command.
@@ -101,6 +96,16 @@ namespace Fitness.ViewModels
 
         public string PauseResumeLabel { get; private set; } = "Pause";
 
+        protected override void GoPrevious()
+        {
+            Services.NavigationService.Instance.NavigateToSummaryView(SummaryType.ChangeToPreviousWorkout, CurrentWorkout);
+        }
+
+        protected override void GoNext()
+        {
+            Services.NavigationService.Instance.NavigateToSummaryView(SummaryType.ChangeToNextWorkout, CurrentWorkout);
+        }
+
         private void TriggerPauseResumeWorkout()
         {
             if (State == WorkoutState.Paused)
@@ -113,17 +118,14 @@ namespace Fitness.ViewModels
             }
         }
 
-        private void ExecuteEndWorkout()
+        private void ExecuteTryAgain()
         {
-            Services.NavigationService.Instance.NavigateToSummaryView();
+            Services.NavigationService.Instance.NavigateToSummaryView(SummaryType.TryAgain, CurrentWorkout);
         }
 
-        private void ExecutePrev()
-        {
-        }
-
-        private void ExecuteNext()
+        private void ExecuteEndWorkout()
         {
+            Services.NavigationService.Instance.NavigateToSummaryView(SummaryType.EndWorkout, CurrentWorkout);
         }
     }
 }
diff --git a/Fitness/ViewModels/SummaryType.cs b/Fitness/ViewModels/SummaryType.cs
new file mode 100644 (file)
index 0000000..dde1ebc
--- /dev/null
@@ -0,0 +1,33 @@
+namespace Fitness.Models
+{
+    /// <summary>
+    /// Type of summary view.
+    /// </summary>
+    public enum SummaryType
+    {
+        /// <summary>
+        /// End workout.
+        /// </summary>
+        EndWorkout,
+
+        /// <summary>
+        /// Change to the next workout.
+        /// </summary>
+        ChangeToNextWorkout,
+
+        /// <summary>
+        /// Change to the previous workout.
+        /// </summary>
+        ChangeToPreviousWorkout,
+
+        /// <summary>
+        /// Try again.
+        /// </summary>
+        TryAgain,
+
+        /// <summary>
+        /// Time's up.
+        /// </summary>
+        TimeIsUp,
+    }
+}
index 72280a9cad114c157e5d277d72906e1356d366e4..41b6434896d19c3b949129893e35e7b685fa12a3 100644 (file)
@@ -1,5 +1,7 @@
 using System;
+using System.Collections.Generic;
 using System.Windows.Input;
+using Fitness.Models;
 using Fitness.Services;
 using Tizen.NUI.Binding;
 
@@ -10,13 +12,28 @@ namespace Fitness.ViewModels
     /// </summary>
     public class SummaryViewModel : BaseViewModel
     {
+        private const string EndWorkoutTitle = "The current session will be stopped.";
+        private const string ChangeWorkoutTitle = "Do you want to change workout?";
+        private const string TryAgainTitle = "Do you want to try again?";
+        private const string TimeIsUpTitle = "Great job!";
+
+        private SummaryType currentSummaryType;
+        private string title;
+        private Dictionary<SummaryType, string> summaryTitleMap;
+        private ICommand ok;
+
         /// <summary>
         /// Initializes a new instance of the <see cref="SummaryViewModel"/> class.
         /// </summary>
-        public SummaryViewModel()
+        public SummaryViewModel(SummaryType summaryType, WorkoutViewModel workoutViewModel)
         {
             Back = new Command(() => { NavigationService.Instance.Pop(); });
-            Ok = new Command(() => { NavigationService.Instance.NavigateToMainView(); });
+            InitializeTitleMap();
+            UpdateTitle();
+            SetOkCommand();
+            CurrentSummaryType = summaryType;
+            CurrentWorkout = workoutViewModel;
+            Workouts = WorkoutRepository.Instance.GetAll();
         }
 
         /// <summary>
@@ -27,12 +44,58 @@ namespace Fitness.ViewModels
         /// <summary>
         /// Gets Ok command.
         /// </summary>
-        public ICommand Ok { get; private set; }
+        public ICommand Ok
+        {
+            get => ok;
+            private set
+            {
+                if (ok != value)
+                {
+                    ok = value;
+                    RaisePropertyChanged();
+                }
+            }
+        }
+
+        /// <summary>
+        /// Gets or sets the current summary type.
+        /// </summary>
+        public SummaryType CurrentSummaryType
+        {
+            get => currentSummaryType;
+            set
+            {
+                if (value != currentSummaryType)
+                {
+                    currentSummaryType = value;
+                    RaisePropertyChanged();
+                }
+
+                UpdateTitle();
+                SetOkCommand();
+            }
+        }
+
+        /// <summary>
+        /// Gets list of all available workouts.
+        /// </summary>
+        public IList<WorkoutViewModel> Workouts { get; private set; }
 
         /// <summary>
         /// Gets title.
         /// </summary>
-        public string Title { get; private set; } = "Great job!";
+        public string Title
+        {
+            get => title;
+            private set
+            {
+                if (title != value)
+                {
+                    title = value;
+                    RaisePropertyChanged();
+                }
+            }
+        }
 
         /// <summary>
         /// Gets TotalTime.
@@ -48,5 +111,74 @@ namespace Fitness.ViewModels
         /// Gets AverageScore.
         /// </summary>
         public int AverageScore { get; private set; } = 98;
+
+        /// <summary>
+        /// Gets current workout.
+        /// </summary>
+        public WorkoutViewModel CurrentWorkout { get; internal set; }
+
+        private void InitializeTitleMap()
+        {
+            summaryTitleMap = new Dictionary<SummaryType, string>
+            {
+                { SummaryType.EndWorkout, EndWorkoutTitle },
+                { SummaryType.ChangeToPreviousWorkout, ChangeWorkoutTitle },
+                { SummaryType.ChangeToNextWorkout, ChangeWorkoutTitle },
+                { SummaryType.TryAgain, TryAgainTitle },
+                { SummaryType.TimeIsUp, TimeIsUpTitle },
+            };
+        }
+
+        private void UpdateTitle()
+        {
+            if (summaryTitleMap.TryGetValue(currentSummaryType, out string title))
+            {
+                Title = title;
+            }
+        }
+
+        private void SetOkCommand()
+        {
+            switch (currentSummaryType)
+            {
+                case SummaryType.ChangeToPreviousWorkout:
+                    Ok = new Command(() => { ChangeWorkout(-1); });
+                    break;
+                case SummaryType.ChangeToNextWorkout:
+                    Ok = new Command(() => { ChangeWorkout(1); });
+                    break;
+                case SummaryType.TryAgain:
+                    Ok = new Command(() => { ChangeWorkout(); });
+                    break;
+                case SummaryType.EndWorkout:
+                case SummaryType.TimeIsUp:
+                default:
+                    Ok = new Command(() =>
+                    {
+                        NavigationService.Instance.Pop();
+                        NavigationService.Instance.NavigateToMainView();
+                    });
+                    break;
+            }
+        }
+
+        private void ChangeWorkout(int offset = 0)
+        {
+            WorkoutViewModel nextWorkout;
+
+            int idx = Workouts.IndexOf(CurrentWorkout) + offset;
+            if (idx >= 0 && idx < Workouts.Count)
+            {
+                nextWorkout = Workouts[idx];
+            }
+            else
+            {
+                nextWorkout = CurrentWorkout;
+            }
+
+            NavigationService.Instance.Pop();
+            NavigationService.Instance.Pop();
+            _ = NavigationService.Instance.NavigateToExercisingView(nextWorkout);
+        }
     }
 }
index 91b52af6e548312fbb7d1a240bb66c7f8a113387..ffe7a3e20161ce22f0bfd4c8d19b742f80da7429 100644 (file)
@@ -18,6 +18,16 @@ namespace Fitness.Views
             null,
             propertyChanged: OnNextCommandChanged);
 
+        /// <summary>
+        /// Title bindable property.
+        /// </summary>
+        public static readonly BindableProperty TitleProperty = BindableProperty.Create(
+            "Title",
+            typeof(string),
+            typeof(BarView),
+            null,
+            propertyChanged: OnTitleChanged);
+
         public BarView()
         {
             InitializeComponent();
@@ -38,5 +48,13 @@ namespace Fitness.Views
                 view.next.Command = command;
             }
         }
+
+        private static void OnTitleChanged(BindableObject bindable, object oldValue, object newValue)
+        {
+            if (bindable is BarView view && newValue is string title)
+            {
+                view.title.Text = title;
+            }
+        }
     }
 }
index 4cd154f9df08c326a32a59a6527d73cefedec351..78a18eb544080e268d645d7edc2f17b9db408d69 100644 (file)
@@ -1,5 +1,8 @@
+using System.Linq;
 using System.Threading;
 using System.Threading.Tasks;
+using Fitness.Models;
+using Fitness.ViewModels;
 using Tizen.NUI;
 using Tizen.NUI.BaseComponents;
 using Tizen.NUI.Binding;
@@ -32,11 +35,12 @@ namespace Fitness.Views
                 Size = new Size(500, 292),
             });
 
+        /// <summary>
+        /// Initializes a new instance of the <see cref="ExercisingView"/> class.
+        /// </summary>
         public ExercisingView()
         {
-            InitializeComponent();
-
-            PlayingView.PreviewStub.Relayout += OnPlayingViewRelayout;
+            Initialize();
         }
 
         /// <summary>
@@ -50,6 +54,17 @@ namespace Fitness.Views
             }
         }
 
+        /// <summary>
+        /// OnAppearing.
+        /// </summary>
+        protected override void OnAppearing()
+        {
+            if (cameraView.PreviewState == Tizen.Multimedia.CameraState.Created || cameraView.PreviewState == Tizen.Multimedia.CameraState.Captured)
+            {
+                cameraView.StartPreview();
+            }
+        }
+
         private static void OnIsPlayingChanged(BindableObject bindable, object oldValue, object newValue)
         {
             if (newValue is bool isPlaying && bindable is ExercisingView view)
@@ -95,6 +110,13 @@ namespace Fitness.Views
             return position;
         }
 
+        private void Initialize()
+        {
+            InitializeComponent();
+
+            PlayingView.PreviewStub.Relayout += OnPlayingViewRelayout;
+        }
+
         private async Task TriggerStates(bool isPlaying)
         {
             if (isInitialized)
index 80d7694250d4576e3ad0bd1b16a3df32ec6805e7..1e0098d336662e758e4944da4e5be217104153c1 100644 (file)
                           Position="{views:PositionInUnits X=17, Y=10}"
                           Size="{views:SizeInUnits Width=64, Height=20}"
                           behaviors:StyleSetter.Style="{x:Static styles:Buttons.Previous}"/>
-    <TextLabel BindingContext="{Binding Source={x:Reference Root}, Path=BindingContext}"
-                Text="{Binding Title}"
+    <TextLabel x:Name="title"
                 TextColor="#000C2B"
                 PixelSize="32"
                 HorizontalAlignment="Center"
-                Position="{views:PositionInUnits X=220, Y=15}"
-                Size="{views:SizeInUnits Width=40, Height=10}"/>
+                Position="{views:PositionInUnits X=190, Y=15}"
+                Size="{views:SizeInUnits Width=100, Height=10}"/>
     <ctrl:NinePatchButton Text="next"
                           x:Name="next"
                           Position="{views:PositionInUnits X=400, Y=10}"
index 67086acff90d8cd635ce2f470ed9f756ac3e7399..8eead37cd0867710b053883b1a6f3faeafe1e228 100644 (file)
@@ -9,9 +9,6 @@
   x:Name="Root"
   BackgroundColor="#EEEFF1"
   IsPlaying="{Binding State, Converter={x:Static converters:WorkoutStateToBoolConverter.Converter}}">
-    <View.BindingContext>
-        <vm:ExercisingViewModel/>
-    </View.BindingContext>
     <views:PlayingView x:Name="PlayingView"
                        BindingContext="{Binding Source={x:Reference Root}, Path=BindingContext}"/>
     <!--Layer-->
@@ -26,7 +23,8 @@
                      BindingContext="{Binding Source={x:Reference Root}, Path=BindingContext}"/>
     <!--Layer-->
     <views:BarView BindingContext="{Binding Source={x:Reference Root}, Path=BindingContext}"
-                PrevCommand="{Binding Prev}"
-                NextCommand="{Binding Next}"/>
+                PrevCommand="{Binding PreviousWorkout}"
+                NextCommand="{Binding NextWorkout}"
+                Title="{Binding CurrentWorkout.Title}"/>
     <views:LoadingView x:Name="LoadingView"/>
 </ctrl:Page>
index 6b470bc44ec45453327bf21fbb7ff75c780ba76a..4b17dd774fe46bcc446fcbe2eceb1416f0a89e7d 100644 (file)
@@ -38,9 +38,9 @@
                 <ctrl:NinePatchButton BindingContext="{Binding Source={x:Reference Root}, Path=BindingContext}"
                                       HeightSpecification="-1"
                                       WidthSpecification="-1"
-                                      Text="Review movement"
+                                      Text="Try again"
                                       Weight="1"
-                                      Command="{Binding ReviewMovement}"
+                                      Command="{Binding TryAgain}"
                                       behaviors:StyleSetter.Style="{Binding Source={x:Static styles:Buttons.ReviewMovement}}"/>
                 <View HeightSpecification="-1"
                       WidthSpecification="-1"
index e86f42cd90d56ee497c0b35c8e278a0f8616f7e4..3b04eada8952f7c906f97b392efc70d543682f8c 100644 (file)
@@ -9,9 +9,6 @@
   xmlns:behaviors="clr-namespace:Fitness.Views.Behaviors"
   xmlns:styles="clr-namespace:Fitness.Views.Styles"
   x:Name="Root">
-    <View.BindingContext>
-        <vm:SummaryViewModel/>
-    </View.BindingContext>
     <ImageView HeightSpecification="-1"
                WidthSpecification="-1"
                ResourceUrl="*Resource*/layout/images/0_BG_dim.png">
             <View.Layout>
                 <LinearLayout LinearOrientation="Vertical"/>
             </View.Layout>
-            <View Size="{views:SizeInUnits Height=10}"/>
+            <View Size="{views:SizeInUnits Height=10}" HeightSpecification="-1"/>
             <TextLabel BindingContext="{Binding Source={x:Reference Root}, Path=BindingContext}"
                        Text="{Binding Title}"
                        PixelSize="40"
                        WidthSpecification="-1"
+                       HeightSpecification="-1"
                        Size="{views:SizeInUnits Height=14}"
                        HorizontalAlignment="Center"
                        TextColor="#000C2B"/>
-            <View WidthSpecification="-1"
+            <View WidthSpecification="-1" HeightSpecification="-1"
                   Weight="1"/>
             <TextLabel Text="Your session summary:"
                        PixelSize="32"
                        WidthSpecification="-1"
+                       HeightSpecification="-1"
                        Margin="{views:ExtentsInUnits Start=27}"
                        Size="{views:SizeInUnits Height=11}"
                        HorizontalAlignment="Begin"
                        TextColor="#000C2B"/>
             <View Size="{views:SizeInUnits Height=7}"/>
-            <View WidthSpecification="-1">
+            <View WidthSpecification="-1" >
                 <View.Layout>
                     <LinearLayout LinearOrientation="Horizontal"/>
                 </View.Layout>
-                <TextLabel Text="Total time"
+                <View WidthSpecification="-1">
+                    <TextLabel Text="Total time"
                            HorizontalAlignment="End"
                            Weight="1"
                            PixelSize="32"
                            Size="{views:SizeInUnits Height=11}"/>
+                </View>
                 <View Size="{views:SizeInUnits Width=11}"/>
                 <TextLabel BindingContext="{Binding Source={x:Reference Root}, Path=BindingContext}"
                            Text="{Binding TotalTime, StringFormat='{0:m\\:ss}'}"
                 <View.Layout>
                     <LinearLayout LinearOrientation="Horizontal"/>
                 </View.Layout>
-                <TextLabel Text="Total count"
+                <View WidthSpecification="-1">
+                    <TextLabel Text="Total count"
                            HorizontalAlignment="End"
                            Weight="1"
                            PixelSize="32"
                            Size="{views:SizeInUnits Height=11}"/>
+                </View>
                 <View Size="{views:SizeInUnits Width=11}"/>
                 <TextLabel BindingContext="{Binding Source={x:Reference Root}, Path=BindingContext}"
                            Text="{Binding TotalCount}"
                 <View.Layout>
                     <LinearLayout LinearOrientation="Horizontal"/>
                 </View.Layout>
-                <TextLabel Text="Average score"
+                <View WidthSpecification="-1">
+                    <TextLabel Text="Average score"
                            HorizontalAlignment="End"
                            Weight="1"
                            PixelSize="32"
                            Size="{views:SizeInUnits Height=11}"/>
+                </View>
                 <View Size="{views:SizeInUnits Width=11}"/>
                 <TextLabel BindingContext="{Binding Source={x:Reference Root}, Path=BindingContext}"
                            Text="{Binding AverageScore}"
                            PixelSize="32"
                            Size="{views:SizeInUnits Height=11}"/>
             </View>
-            <View WidthSpecification="-1"
+            <View WidthSpecification="-1" HeightSpecification="-1"
                   Weight="1"/>
             <View WidthSpecification="-1">
                 <View.Layout>