├── .gitignore ├── Routini.MAUI.Test ├── GlobalUsings.cs ├── Mocks │ ├── MockTimer.cs │ └── MockEnvironment.cs ├── Routini.MAUI.Test.csproj ├── ListRoutineTests.cs ├── CreateRoutineTests.cs ├── PlayRoutineTests.cs └── EditRoutineTests.cs ├── Routini.MAUI ├── Resources │ ├── Raw │ │ ├── ping.mp3 │ │ └── AboutAssets.txt │ ├── Fonts │ │ ├── Inter-Bold.ttf │ │ └── Inter-Regular.ttf │ ├── Images │ │ └── dotnet_bot.png │ ├── Splash │ │ └── splash.svg │ └── AppIcon │ │ └── appicon.svg ├── Features │ ├── CreateRoutine │ │ ├── NewRoutineStep.cs │ │ ├── NewRoutine.cs │ │ └── CreateRoutineMutation.cs │ ├── PlayRoutine │ │ ├── PlayRoutineView.xaml.cs │ │ ├── PlayRoutineStepViewModel.cs │ │ ├── GetRoutineByIdQuery.cs │ │ ├── PlayRoutineViewModel.cs │ │ └── PlayRoutineView.xaml │ ├── EditRoutine │ │ ├── EditRoutineFormView.xaml.cs │ │ ├── EditRoutineFormView.xaml │ │ ├── UpdateRoutineMutation.cs │ │ └── EditRoutineFormViewModel.cs │ ├── DeleteRoutine │ │ └── DeleteRoutineMutation.cs │ └── ListRoutines │ │ ├── RoutinePreviewViewModel.cs │ │ └── GetAllRoutinesQuery.cs ├── Properties │ └── launchSettings.json ├── Shared │ ├── Time │ │ ├── IDateTimeProvider.cs │ │ └── DateTimeProvider.cs │ ├── Databases │ │ ├── ISqliteConnectionFactory.cs │ │ ├── SqliteConnectionFactory.cs │ │ └── InMemorySqliteConnectionFactory.cs │ ├── Shell │ │ ├── IShell.cs │ │ └── MauiShell.cs │ ├── Timers │ │ ├── ITimer.cs │ │ └── Timer.cs │ └── Components │ │ └── If.cs ├── Widgets │ ├── PageLayout.xaml.cs │ └── PageLayout.xaml ├── Platforms │ ├── Android │ │ ├── Resources │ │ │ └── values │ │ │ │ └── colors.xml │ │ ├── AndroidManifest.xml │ │ ├── MainApplication.cs │ │ └── MainActivity.cs │ ├── iOS │ │ ├── AppDelegate.cs │ │ ├── Program.cs │ │ └── Info.plist │ ├── MacCatalyst │ │ ├── AppDelegate.cs │ │ ├── Program.cs │ │ ├── Entitlements.plist │ │ └── Info.plist │ ├── Windows │ │ ├── App.xaml │ │ ├── app.manifest │ │ ├── App.xaml.cs │ │ └── Package.appxmanifest │ └── Tizen │ │ ├── Main.cs │ │ └── tizen-manifest.xml ├── Pages │ ├── EditRoutineView.xaml.cs │ ├── EditRoutineView.xaml │ ├── ListRoutinesView.xaml.cs │ ├── CreateRoutineView.xaml │ ├── CreateRoutineView.xaml.cs │ ├── RoutineDetailView.xaml.cs │ ├── RoutineDetailView.xaml │ ├── CreateRoutineViewModel.cs │ ├── EditRoutineViewModel.cs │ ├── ListRoutinesViewModel.cs │ ├── RoutineDetailViewModel.cs │ └── ListRoutinesView.xaml ├── Entities │ └── Routines │ │ ├── RoutineDto.cs │ │ ├── RoutineStep.cs │ │ ├── RoutineStepDto.cs │ │ ├── Routine.cs │ │ ├── RoutineFormView.xaml.cs │ │ ├── RoutineStepFormViewModel.cs │ │ ├── RoutineFormViewModel.cs │ │ ├── PlayableRoutine.cs │ │ └── RoutineFormView.xaml ├── AppShell.xaml ├── AppShell.xaml.cs ├── App.xaml.cs ├── Application │ └── Database │ │ └── RoutiniDatabaseInitializer.cs ├── Routini.MAUI.csproj.user ├── MauiProgram.cs ├── App.xaml └── Routini.MAUI.csproj ├── README.md ├── .github └── workflows │ └── deploy.yml └── Routini.sln /.gitignore: -------------------------------------------------------------------------------- 1 | .vs 2 | bin 3 | obj -------------------------------------------------------------------------------- /Routini.MAUI.Test/GlobalUsings.cs: -------------------------------------------------------------------------------- 1 | global using NUnit.Framework; -------------------------------------------------------------------------------- /Routini.MAUI/Resources/Raw/ping.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SingletonSean/routini/HEAD/Routini.MAUI/Resources/Raw/ping.mp3 -------------------------------------------------------------------------------- /Routini.MAUI/Resources/Fonts/Inter-Bold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SingletonSean/routini/HEAD/Routini.MAUI/Resources/Fonts/Inter-Bold.ttf -------------------------------------------------------------------------------- /Routini.MAUI/Resources/Images/dotnet_bot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SingletonSean/routini/HEAD/Routini.MAUI/Resources/Images/dotnet_bot.png -------------------------------------------------------------------------------- /Routini.MAUI/Resources/Fonts/Inter-Regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SingletonSean/routini/HEAD/Routini.MAUI/Resources/Fonts/Inter-Regular.ttf -------------------------------------------------------------------------------- /Routini.MAUI/Features/CreateRoutine/NewRoutineStep.cs: -------------------------------------------------------------------------------- 1 | namespace Routini.MAUI.Features.CreateRoutine 2 | { 3 | public record NewRoutineStep(string Name, TimeSpan Duration); 4 | } 5 | -------------------------------------------------------------------------------- /Routini.MAUI/Features/CreateRoutine/NewRoutine.cs: -------------------------------------------------------------------------------- 1 | namespace Routini.MAUI.Features.CreateRoutine 2 | { 3 | public record NewRoutine(string Name, IEnumerable Steps); 4 | } 5 | -------------------------------------------------------------------------------- /Routini.MAUI/Properties/launchSettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "profiles": { 3 | "Windows Machine": { 4 | "commandName": "MsixPackage", 5 | "nativeDebugging": false 6 | } 7 | } 8 | } -------------------------------------------------------------------------------- /Routini.MAUI/Shared/Time/IDateTimeProvider.cs: -------------------------------------------------------------------------------- 1 | namespace Routini.MAUI.Shared.Time 2 | { 3 | public interface IDateTimeProvider 4 | { 5 | DateTimeOffset UtcNow { get; } 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /Routini.MAUI/Widgets/PageLayout.xaml.cs: -------------------------------------------------------------------------------- 1 | namespace Routini.MAUI.Widgets; 2 | 3 | public partial class PageLayout : ContentView 4 | { 5 | public PageLayout() 6 | { 7 | InitializeComponent(); 8 | } 9 | } -------------------------------------------------------------------------------- /Routini.MAUI/Shared/Time/DateTimeProvider.cs: -------------------------------------------------------------------------------- 1 | namespace Routini.MAUI.Shared.Time 2 | { 3 | public class DateTimeProvider : IDateTimeProvider 4 | { 5 | public DateTimeOffset UtcNow => DateTimeOffset.UtcNow; 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /Routini.MAUI/Features/PlayRoutine/PlayRoutineView.xaml.cs: -------------------------------------------------------------------------------- 1 | namespace Routini.MAUI.Features.PlayRoutine; 2 | 3 | public partial class PlayRoutineView : ContentView 4 | { 5 | public PlayRoutineView() 6 | { 7 | InitializeComponent(); 8 | } 9 | } -------------------------------------------------------------------------------- /Routini.MAUI/Shared/Databases/ISqliteConnectionFactory.cs: -------------------------------------------------------------------------------- 1 | using SQLite; 2 | 3 | namespace Routini.MAUI.Shared.Databases 4 | { 5 | public interface ISqliteConnectionFactory 6 | { 7 | ISQLiteAsyncConnection Create(); 8 | } 9 | } -------------------------------------------------------------------------------- /Routini.MAUI/Features/EditRoutine/EditRoutineFormView.xaml.cs: -------------------------------------------------------------------------------- 1 | namespace Routini.MAUI.Features.EditRoutine; 2 | 3 | public partial class EditRoutineFormView : ContentView 4 | { 5 | public EditRoutineFormView() 6 | { 7 | InitializeComponent(); 8 | } 9 | } -------------------------------------------------------------------------------- /Routini.MAUI/Platforms/Android/Resources/values/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | #512BD4 4 | #2B0B98 5 | #2B0B98 6 | -------------------------------------------------------------------------------- /Routini.MAUI/Pages/EditRoutineView.xaml.cs: -------------------------------------------------------------------------------- 1 | namespace Routini.MAUI.Pages; 2 | 3 | public partial class EditRoutineView : ContentPage 4 | { 5 | public EditRoutineView(EditRoutineViewModel viewModel) 6 | { 7 | InitializeComponent(); 8 | 9 | BindingContext = viewModel; 10 | } 11 | } -------------------------------------------------------------------------------- /Routini.MAUI/Entities/Routines/RoutineDto.cs: -------------------------------------------------------------------------------- 1 | using SQLite; 2 | 3 | namespace Routini.MAUI.Entities.Routines 4 | { 5 | public class RoutineDto 6 | { 7 | [PrimaryKey] 8 | public Guid Id { get; set; } 9 | public string? Name { get; set; } 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /Routini.MAUI/Platforms/iOS/AppDelegate.cs: -------------------------------------------------------------------------------- 1 | using Foundation; 2 | 3 | namespace Routini.MAUI 4 | { 5 | [Register("AppDelegate")] 6 | public class AppDelegate : MauiUIApplicationDelegate 7 | { 8 | protected override MauiApp CreateMauiApp() => MauiProgram.CreateMauiApp(); 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /Routini.MAUI/Platforms/MacCatalyst/AppDelegate.cs: -------------------------------------------------------------------------------- 1 | using Foundation; 2 | 3 | namespace Routini.MAUI 4 | { 5 | [Register("AppDelegate")] 6 | public class AppDelegate : MauiUIApplicationDelegate 7 | { 8 | protected override MauiApp CreateMauiApp() => MauiProgram.CreateMauiApp(); 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /Routini.MAUI/Shared/Shell/IShell.cs: -------------------------------------------------------------------------------- 1 | namespace Routini.MAUI.Shared.Shells 2 | { 3 | public interface IShell 4 | { 5 | Task DisplayAlert(string title, string message, string cancel); 6 | Task DisplayAlert(string title, string message, string accept, string cancel); 7 | Task GoToAsync(string route); 8 | } 9 | } -------------------------------------------------------------------------------- /Routini.MAUI/Shared/Timers/ITimer.cs: -------------------------------------------------------------------------------- 1 | using System.Timers; 2 | 3 | namespace Routini.MAUI.Shared.Timers 4 | { 5 | public interface ITimer : IDisposable 6 | { 7 | double Interval { get; set; } 8 | 9 | event ElapsedEventHandler? Elapsed; 10 | 11 | void Start(); 12 | void Stop(); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /Routini.MAUI/Platforms/Windows/App.xaml: -------------------------------------------------------------------------------- 1 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # 🔁 Routini 2 | 3 | A cross-platform routine runner to keep you on track. 4 | 5 | ## Features 6 | 7 | - Run routines 8 | - Save repeatable routines 9 | 10 | ## Technology 11 | 12 | - .NET MAUI 13 | - MVVM (Community Toolkit) 14 | - SQLite 15 | 16 | ## Contributing 17 | 18 | Feel free to create issues or open pull requests for any features, bug fixes, or enhancements! 19 | -------------------------------------------------------------------------------- /Routini.MAUI/Entities/Routines/RoutineStep.cs: -------------------------------------------------------------------------------- 1 | namespace Routini.MAUI.Entities.Routines 2 | { 3 | public class RoutineStep 4 | { 5 | public string Name { get; } 6 | public TimeSpan Duration { get; } 7 | 8 | public RoutineStep(string name, TimeSpan duration) 9 | { 10 | Name = name; 11 | Duration = duration; 12 | } 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /Routini.MAUI/Entities/Routines/RoutineStepDto.cs: -------------------------------------------------------------------------------- 1 | using SQLite; 2 | 3 | namespace Routini.MAUI.Entities.Routines 4 | { 5 | public class RoutineStepDto 6 | { 7 | [PrimaryKey] 8 | public Guid Id { get; set; } 9 | public Guid RoutineId { get; set; } 10 | public string? Name { get; set; } 11 | public double? DurationSeconds { get; set; } 12 | public int? Order { get; set; } 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /Routini.MAUI/Platforms/Tizen/Main.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Maui; 2 | using Microsoft.Maui.Hosting; 3 | using System; 4 | 5 | namespace Routini.MAUI 6 | { 7 | internal class Program : MauiApplication 8 | { 9 | protected override MauiApp CreateMauiApp() => MauiProgram.CreateMauiApp(); 10 | 11 | static void Main(string[] args) 12 | { 13 | var app = new Program(); 14 | app.Run(args); 15 | } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /Routini.MAUI/Platforms/Android/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /Routini.MAUI/Platforms/Android/MainApplication.cs: -------------------------------------------------------------------------------- 1 | using Android.App; 2 | using Android.Runtime; 3 | 4 | namespace Routini.MAUI 5 | { 6 | [Application] 7 | public class MainApplication : MauiApplication 8 | { 9 | public MainApplication(IntPtr handle, JniHandleOwnership ownership) 10 | : base(handle, ownership) 11 | { 12 | } 13 | 14 | protected override MauiApp CreateMauiApp() => MauiProgram.CreateMauiApp(); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /Routini.MAUI/Features/EditRoutine/EditRoutineFormView.xaml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /Routini.MAUI/Platforms/Android/MainActivity.cs: -------------------------------------------------------------------------------- 1 | using Android.App; 2 | using Android.Content.PM; 3 | using Android.OS; 4 | 5 | namespace Routini.MAUI 6 | { 7 | [Activity(Theme = "@style/Maui.SplashTheme", MainLauncher = true, ConfigurationChanges = ConfigChanges.ScreenSize | ConfigChanges.Orientation | ConfigChanges.UiMode | ConfigChanges.ScreenLayout | ConfigChanges.SmallestScreenSize | ConfigChanges.Density)] 8 | public class MainActivity : MauiAppCompatActivity 9 | { 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /Routini.MAUI/AppShell.xaml: -------------------------------------------------------------------------------- 1 | 2 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /Routini.MAUI/Entities/Routines/Routine.cs: -------------------------------------------------------------------------------- 1 | using System.Timers; 2 | 3 | namespace Routini.MAUI.Entities.Routines 4 | { 5 | public class Routine 6 | { 7 | public Guid Id { get; } 8 | public string Name { get; } 9 | public IEnumerable Steps { get; } 10 | 11 | public Routine(Guid id, string name, IEnumerable steps) 12 | { 13 | Id = id; 14 | Name = name; 15 | Steps = steps; 16 | } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /Routini.MAUI/Platforms/iOS/Program.cs: -------------------------------------------------------------------------------- 1 | using ObjCRuntime; 2 | using UIKit; 3 | 4 | namespace Routini.MAUI 5 | { 6 | public class Program 7 | { 8 | // This is the main entry point of the application. 9 | static void Main(string[] args) 10 | { 11 | // if you want to use a different Application Delegate class from "AppDelegate" 12 | // you can specify it here. 13 | UIApplication.Main(args, null, typeof(AppDelegate)); 14 | } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /Routini.MAUI/Platforms/MacCatalyst/Program.cs: -------------------------------------------------------------------------------- 1 | using ObjCRuntime; 2 | using UIKit; 3 | 4 | namespace Routini.MAUI 5 | { 6 | public class Program 7 | { 8 | // This is the main entry point of the application. 9 | static void Main(string[] args) 10 | { 11 | // if you want to use a different Application Delegate class from "AppDelegate" 12 | // you can specify it here. 13 | UIApplication.Main(args, null, typeof(AppDelegate)); 14 | } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /Routini.MAUI/AppShell.xaml.cs: -------------------------------------------------------------------------------- 1 | using Routini.MAUI.Pages; 2 | 3 | namespace Routini.MAUI 4 | { 5 | public partial class AppShell : Shell 6 | { 7 | public AppShell() 8 | { 9 | InitializeComponent(); 10 | 11 | Routing.RegisterRoute("Routines/Create", typeof(CreateRoutineView)); 12 | Routing.RegisterRoute("Routines/Detail", typeof(RoutineDetailView)); 13 | Routing.RegisterRoute("Routines/Detail/Edit", typeof(EditRoutineView)); 14 | } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /Routini.MAUI/Shared/Databases/SqliteConnectionFactory.cs: -------------------------------------------------------------------------------- 1 | using SQLite; 2 | 3 | namespace Routini.MAUI.Shared.Databases 4 | { 5 | public class SqliteConnectionFactory : ISqliteConnectionFactory 6 | { 7 | public ISQLiteAsyncConnection Create() 8 | { 9 | return new SQLiteAsyncConnection( 10 | Path.Combine(FileSystem.Current.AppDataDirectory, "routini.db3"), 11 | SQLiteOpenFlags.ReadWrite | SQLiteOpenFlags.Create | SQLiteOpenFlags.SharedCache); 12 | } 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /Routini.MAUI/Shared/Databases/InMemorySqliteConnectionFactory.cs: -------------------------------------------------------------------------------- 1 | using SQLite; 2 | 3 | namespace Routini.MAUI.Shared.Databases 4 | { 5 | public class InMemorySqliteConnectionFactory : ISqliteConnectionFactory 6 | { 7 | private readonly Guid _id; 8 | 9 | public InMemorySqliteConnectionFactory() 10 | { 11 | _id = Guid.NewGuid(); 12 | } 13 | 14 | public ISQLiteAsyncConnection Create() 15 | { 16 | return new SQLiteAsyncConnection($"Data Source={_id};Mode=Memory;Cache=Shared"); 17 | } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /Routini.MAUI/Features/PlayRoutine/PlayRoutineStepViewModel.cs: -------------------------------------------------------------------------------- 1 | using CommunityToolkit.Mvvm.ComponentModel; 2 | 3 | namespace Routini.MAUI.Features.PlayRoutine 4 | { 5 | public class PlayRoutineStepViewModel : ObservableObject 6 | { 7 | public string Name { get; } 8 | public double DurationSeconds { get; } 9 | public int Order { get; } 10 | 11 | public PlayRoutineStepViewModel(string name, double durationSeconds, int order) 12 | { 13 | Name = name; 14 | DurationSeconds = durationSeconds; 15 | Order = order + 1; 16 | } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /Routini.MAUI/Entities/Routines/RoutineFormView.xaml.cs: -------------------------------------------------------------------------------- 1 | using MauiIcons.Core; 2 | 3 | namespace Routini.MAUI.Entities.Routines; 4 | 5 | public partial class RoutineFormView : ContentView 6 | { 7 | public static readonly BindableProperty SubmitButtonTextProperty = 8 | BindableProperty.Create(nameof(SubmitButtonText), typeof(string), typeof(RoutineFormView), "Submit"); 9 | 10 | public string SubmitButtonText 11 | { 12 | get => (string)GetValue(SubmitButtonTextProperty); 13 | set => SetValue(SubmitButtonTextProperty, value); 14 | } 15 | 16 | public RoutineFormView() 17 | { 18 | InitializeComponent(); 19 | 20 | _ = new MauiIcon(); 21 | } 22 | } -------------------------------------------------------------------------------- /Routini.MAUI/Widgets/PageLayout.xaml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /Routini.MAUI/Shared/Shell/MauiShell.cs: -------------------------------------------------------------------------------- 1 | namespace Routini.MAUI.Shared.Shells 2 | { 3 | internal class MauiShell : IShell 4 | { 5 | public async Task DisplayAlert(string title, string message, string cancel) 6 | { 7 | await Shell.Current.DisplayAlert(title, message, cancel); 8 | } 9 | 10 | public async Task DisplayAlert(string title, string message, string accept, string cancel) 11 | { 12 | return await Shell.Current.DisplayAlert(title, message, accept, cancel); 13 | } 14 | 15 | public async Task GoToAsync(string route) 16 | { 17 | await Shell.Current.GoToAsync(route); 18 | } 19 | } 20 | } -------------------------------------------------------------------------------- /Routini.MAUI/App.xaml.cs: -------------------------------------------------------------------------------- 1 | using Routini.MAUI.Application.Database; 2 | 3 | namespace Routini.MAUI 4 | { 5 | public partial class App : Microsoft.Maui.Controls.Application 6 | { 7 | private readonly RoutiniDatabaseInitializer _databaseInitializer; 8 | 9 | public App(RoutiniDatabaseInitializer databaseInitializer) 10 | { 11 | _databaseInitializer = databaseInitializer; 12 | 13 | InitializeComponent(); 14 | 15 | MainPage = new AppShell(); 16 | } 17 | 18 | protected override async void OnStart() 19 | { 20 | await _databaseInitializer.Initialize(); 21 | 22 | base.OnStart(); 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /Routini.MAUI/Resources/Raw/AboutAssets.txt: -------------------------------------------------------------------------------- 1 | Any raw assets you want to be deployed with your application can be placed in 2 | this directory (and child directories). Deployment of the asset to your application 3 | is automatically handled by the following `MauiAsset` Build Action within your `.csproj`. 4 | 5 | 6 | 7 | These files will be deployed with you package and will be accessible using Essentials: 8 | 9 | async Task LoadMauiAsset() 10 | { 11 | using var stream = await FileSystem.OpenAppPackageFileAsync("AboutAssets.txt"); 12 | using var reader = new StreamReader(stream); 13 | 14 | var contents = reader.ReadToEnd(); 15 | } 16 | -------------------------------------------------------------------------------- /Routini.MAUI/Pages/EditRoutineView.xaml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 10 | 13 | 14 | -------------------------------------------------------------------------------- /Routini.MAUI/Platforms/MacCatalyst/Entitlements.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | com.apple.security.app-sandbox 8 | 9 | 10 | com.apple.security.network.client 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /Routini.MAUI/Features/DeleteRoutine/DeleteRoutineMutation.cs: -------------------------------------------------------------------------------- 1 | using Routini.MAUI.Entities.Routines; 2 | using Routini.MAUI.Shared.Databases; 3 | using SQLite; 4 | 5 | namespace Routini.MAUI.Features.DeleteRoutine 6 | { 7 | public class DeleteRoutineMutation 8 | { 9 | private readonly ISqliteConnectionFactory _sqliteConnectionFactory; 10 | 11 | public DeleteRoutineMutation(ISqliteConnectionFactory sqliteConnectionFactory) 12 | { 13 | _sqliteConnectionFactory = sqliteConnectionFactory; 14 | } 15 | 16 | public async Task Execute(Guid id) 17 | { 18 | ISQLiteAsyncConnection database = _sqliteConnectionFactory.Create(); 19 | 20 | await database.DeleteAsync(id); 21 | } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /Routini.MAUI.Test/Mocks/MockTimer.cs: -------------------------------------------------------------------------------- 1 | using System.Timers; 2 | 3 | namespace Routini.MAUI.Test.Mocks 4 | { 5 | public class MockTimer : Shared.Timers.ITimer 6 | { 7 | private bool _running; 8 | 9 | public double Interval { get; set; } 10 | 11 | public event ElapsedEventHandler? Elapsed; 12 | 13 | public void Dispose() { } 14 | 15 | public void Start() 16 | { 17 | _running = true; 18 | } 19 | 20 | public void Stop() 21 | { 22 | _running = false; 23 | } 24 | 25 | public void RaiseElapsed() 26 | { 27 | if (!_running) 28 | { 29 | return; 30 | } 31 | 32 | Elapsed?.Invoke(this, null!); 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /Routini.MAUI/Platforms/Windows/app.manifest: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 11 | true/PM 12 | PerMonitorV2, PerMonitor 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /Routini.MAUI/Platforms/Tizen/tizen-manifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | maui-appicon-placeholder 7 | 8 | 9 | 10 | 11 | http://tizen.org/privilege/internet 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /Routini.MAUI/Pages/ListRoutinesView.xaml.cs: -------------------------------------------------------------------------------- 1 | using System.Windows.Input; 2 | 3 | namespace Routini.MAUI.Pages; 4 | 5 | public partial class ListRoutinesView : ContentPage 6 | { 7 | public static readonly BindableProperty OnAppearingCommandProperty = 8 | BindableProperty.Create(nameof(OnAppearingCommand), typeof(ICommand), typeof(ListRoutinesView), null); 9 | 10 | public ICommand OnAppearingCommand 11 | { 12 | get => (ICommand)GetValue(OnAppearingCommandProperty); 13 | set => SetValue(OnAppearingCommandProperty, value); 14 | } 15 | 16 | public ListRoutinesView(ListRoutinesViewModel viewModel) 17 | { 18 | InitializeComponent(); 19 | 20 | BindingContext = viewModel; 21 | } 22 | 23 | protected override void OnAppearing() 24 | { 25 | OnAppearingCommand?.Execute(null); 26 | 27 | base.OnAppearing(); 28 | } 29 | } -------------------------------------------------------------------------------- /Routini.MAUI/Application/Database/RoutiniDatabaseInitializer.cs: -------------------------------------------------------------------------------- 1 | using Routini.MAUI.Entities.Routines; 2 | using Routini.MAUI.Shared.Databases; 3 | using SQLite; 4 | 5 | namespace Routini.MAUI.Application.Database 6 | { 7 | public class RoutiniDatabaseInitializer 8 | { 9 | private readonly ISqliteConnectionFactory _sqliteConnectionFactory; 10 | 11 | public RoutiniDatabaseInitializer(ISqliteConnectionFactory sqliteConnectionFactory) 12 | { 13 | _sqliteConnectionFactory = sqliteConnectionFactory; 14 | } 15 | 16 | public async Task Initialize() 17 | { 18 | ISQLiteAsyncConnection database = _sqliteConnectionFactory.Create(); 19 | 20 | await database.CreateTableAsync(); 21 | await database.CreateTableAsync(); 22 | } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /Routini.MAUI/Pages/CreateRoutineView.xaml: -------------------------------------------------------------------------------- 1 | 2 | 9 | 10 | 11 | 17 | 18 | -------------------------------------------------------------------------------- /Routini.MAUI/Pages/CreateRoutineView.xaml.cs: -------------------------------------------------------------------------------- 1 | using System.Windows.Input; 2 | 3 | namespace Routini.MAUI.Pages; 4 | 5 | public partial class CreateRoutineView : ContentPage 6 | { 7 | public static readonly BindableProperty OnAppearingCommandProperty = 8 | BindableProperty.Create(nameof(OnAppearingCommand), typeof(ICommand), typeof(ListRoutinesView), null); 9 | 10 | public ICommand OnAppearingCommand 11 | { 12 | get => (ICommand)GetValue(OnAppearingCommandProperty); 13 | set => SetValue(OnAppearingCommandProperty, value); 14 | } 15 | 16 | public CreateRoutineView(CreateRoutineViewModel viewModel) 17 | { 18 | InitializeComponent(); 19 | 20 | BindingContext = viewModel; 21 | } 22 | 23 | protected override void OnAppearing() 24 | { 25 | OnAppearingCommand?.Execute(null); 26 | 27 | base.OnAppearing(); 28 | } 29 | } -------------------------------------------------------------------------------- /Routini.MAUI/Pages/RoutineDetailView.xaml.cs: -------------------------------------------------------------------------------- 1 | using System.Windows.Input; 2 | 3 | namespace Routini.MAUI.Pages; 4 | 5 | public partial class RoutineDetailView : ContentPage 6 | { 7 | public static readonly BindableProperty OnDisappearingCommandProperty = 8 | BindableProperty.Create(nameof(OnDisappearingCommand), typeof(ICommand), typeof(ListRoutinesView), null); 9 | 10 | public ICommand OnDisappearingCommand 11 | { 12 | get => (ICommand)GetValue(OnDisappearingCommandProperty); 13 | set => SetValue(OnDisappearingCommandProperty, value); 14 | } 15 | 16 | public RoutineDetailView(RoutineDetailViewModel viewModel) 17 | { 18 | InitializeComponent(); 19 | 20 | BindingContext = viewModel; 21 | } 22 | 23 | protected override void OnDisappearing() 24 | { 25 | OnDisappearingCommand?.Execute(null); 26 | 27 | base.OnDisappearing(); 28 | } 29 | } -------------------------------------------------------------------------------- /Routini.MAUI/Platforms/Windows/App.xaml.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.UI.Xaml; 2 | 3 | // To learn more about WinUI, the WinUI project structure, 4 | // and more about our project templates, see: http://aka.ms/winui-project-info. 5 | 6 | namespace Routini.MAUI.WinUI 7 | { 8 | /// 9 | /// Provides application-specific behavior to supplement the default Application class. 10 | /// 11 | public partial class App : MauiWinUIApplication 12 | { 13 | /// 14 | /// Initializes the singleton application object. This is the first line of authored code 15 | /// executed, and as such is the logical equivalent of main() or WinMain(). 16 | /// 17 | public App() 18 | { 19 | this.InitializeComponent(); 20 | } 21 | 22 | protected override MauiApp CreateMauiApp() => MauiProgram.CreateMauiApp(); 23 | } 24 | 25 | } 26 | -------------------------------------------------------------------------------- /Routini.MAUI/Features/ListRoutines/RoutinePreviewViewModel.cs: -------------------------------------------------------------------------------- 1 | using CommunityToolkit.Mvvm.ComponentModel; 2 | using CommunityToolkit.Mvvm.Input; 3 | using Routini.MAUI.Shared.Shells; 4 | 5 | namespace Routini.MAUI.Features.ListRoutines 6 | { 7 | public partial class RoutinePreviewViewModel : ObservableObject 8 | { 9 | private readonly IShell _shell; 10 | 11 | public Guid Id { get; } 12 | public string Name { get; } 13 | public int StepsCount { get; } 14 | 15 | public RoutinePreviewViewModel(Guid id, string name, int stepsCount, IShell shell) 16 | { 17 | Id = id; 18 | Name = name; 19 | StepsCount = stepsCount; 20 | _shell = shell; 21 | } 22 | 23 | [RelayCommand] 24 | private async Task NavigateRoutinePlay() 25 | { 26 | await _shell.GoToAsync($"Detail?Id={Id}"); 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /Routini.MAUI.Test/Routini.MAUI.Test.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net8.0 5 | enable 6 | enable 7 | 8 | false 9 | true 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /Routini.MAUI/Shared/Timers/Timer.cs: -------------------------------------------------------------------------------- 1 | using System.Timers; 2 | 3 | namespace Routini.MAUI.Shared.Timers 4 | { 5 | public class Timer : ITimer 6 | { 7 | private readonly System.Timers.Timer _timer; 8 | 9 | public double Interval 10 | { 11 | get => _timer.Interval; 12 | set => _timer.Interval = value; 13 | } 14 | 15 | public event ElapsedEventHandler? Elapsed 16 | { 17 | add => _timer.Elapsed += value; 18 | remove => _timer.Elapsed -= value; 19 | } 20 | 21 | public Timer() 22 | { 23 | _timer = new System.Timers.Timer(); 24 | } 25 | 26 | public void Dispose() 27 | { 28 | _timer.Dispose(); 29 | } 30 | 31 | public void Start() 32 | { 33 | _timer.Start(); 34 | } 35 | 36 | public void Stop() 37 | { 38 | _timer.Stop(); 39 | } 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /.github/workflows/deploy.yml: -------------------------------------------------------------------------------- 1 | name: Deploy 2 | 3 | on: 4 | push: 5 | branches: ["master"] 6 | 7 | jobs: 8 | deploy: 9 | runs-on: macos-14 10 | 11 | steps: 12 | - name: Checkout 13 | uses: actions/checkout@v3 14 | 15 | - name: Setup .NET 16 | uses: actions/setup-dotnet@v3 17 | with: 18 | dotnet-version: 8.0.x 19 | 20 | - name: Install MAUI workload 21 | run: dotnet workload install maui 22 | 23 | - name: Restore 24 | run: dotnet restore 25 | 26 | - name: Build 27 | run: dotnet build -c Release -f:net8.0-maccatalyst 28 | working-directory: ./Routini.MAUI 29 | 30 | - name: Test 31 | run: dotnet test -c Release 32 | working-directory: ./Routini.MAUI.Test 33 | 34 | - name: Publish 35 | run: dotnet publish -c Release -f:net8.0-maccatalyst 36 | working-directory: ./Routini.MAUI 37 | 38 | - name: Upload Artifacts 39 | uses: actions/upload-artifact@v3.1.0 40 | with: 41 | path: Routini.MAUI/bin/Release/net8.0-maccatalyst/publish -------------------------------------------------------------------------------- /Routini.MAUI/Platforms/iOS/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | LSRequiresIPhoneOS 6 | 7 | UIDeviceFamily 8 | 9 | 1 10 | 2 11 | 12 | UIRequiredDeviceCapabilities 13 | 14 | arm64 15 | 16 | UISupportedInterfaceOrientations 17 | 18 | UIInterfaceOrientationPortrait 19 | UIInterfaceOrientationLandscapeLeft 20 | UIInterfaceOrientationLandscapeRight 21 | 22 | UISupportedInterfaceOrientations~ipad 23 | 24 | UIInterfaceOrientationPortrait 25 | UIInterfaceOrientationPortraitUpsideDown 26 | UIInterfaceOrientationLandscapeLeft 27 | UIInterfaceOrientationLandscapeRight 28 | 29 | XSAppIconAssets 30 | Assets.xcassets/appicon.appiconset 31 | 32 | 33 | -------------------------------------------------------------------------------- /Routini.MAUI/Features/CreateRoutine/CreateRoutineMutation.cs: -------------------------------------------------------------------------------- 1 | using Routini.MAUI.Entities.Routines; 2 | using Routini.MAUI.Shared.Databases; 3 | using SQLite; 4 | 5 | namespace Routini.MAUI.Features.CreateRoutine 6 | { 7 | public class CreateRoutineMutation 8 | { 9 | private readonly ISqliteConnectionFactory _sqliteConnectionFactory; 10 | 11 | public CreateRoutineMutation(ISqliteConnectionFactory sqliteConnectionFactory) 12 | { 13 | _sqliteConnectionFactory = sqliteConnectionFactory; 14 | } 15 | 16 | public async Task Execute(NewRoutine routine) 17 | { 18 | ISQLiteAsyncConnection database = _sqliteConnectionFactory.Create(); 19 | 20 | RoutineDto routineDto = new RoutineDto() 21 | { 22 | Id = Guid.NewGuid(), 23 | Name = routine.Name 24 | }; 25 | await database.InsertAsync(routineDto); 26 | 27 | IEnumerable routineStepDtos = routine.Steps.Select((s, i) => new RoutineStepDto() 28 | { 29 | Id = Guid.NewGuid(), 30 | RoutineId = routineDto.Id, 31 | Name = s.Name, 32 | DurationSeconds = s.Duration.TotalSeconds, 33 | Order = i 34 | }); 35 | await database.InsertAllAsync(routineStepDtos); 36 | } 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /Routini.MAUI/Features/PlayRoutine/GetRoutineByIdQuery.cs: -------------------------------------------------------------------------------- 1 | using Routini.MAUI.Entities.Routines; 2 | using Routini.MAUI.Shared.Databases; 3 | using SQLite; 4 | 5 | namespace Routini.MAUI.Features.ListRoutines 6 | { 7 | public class GetRoutineByIdQuery 8 | { 9 | private readonly ISqliteConnectionFactory _sqliteConnectionFactory; 10 | 11 | public GetRoutineByIdQuery(ISqliteConnectionFactory sqliteConnectionFactory) 12 | { 13 | _sqliteConnectionFactory = sqliteConnectionFactory; 14 | } 15 | 16 | public async Task Execute(Guid id) 17 | { 18 | ISQLiteAsyncConnection database = _sqliteConnectionFactory.Create(); 19 | 20 | RoutineDto routineDto = await database 21 | .Table() 22 | .FirstOrDefaultAsync(r => r.Id == id); 23 | 24 | IEnumerable routineStepDtos = await database 25 | .Table() 26 | .Where(s => s.RoutineId == id) 27 | .ToListAsync(); 28 | 29 | return new Routine( 30 | routineDto.Id, 31 | routineDto.Name ?? string.Empty, 32 | routineStepDtos 33 | .OrderBy(s => s.Order) 34 | .Select(s => new RoutineStep( 35 | s.Name ?? string.Empty, 36 | TimeSpan.FromSeconds(s.DurationSeconds ?? 0)))); 37 | } 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /Routini.MAUI.Test/ListRoutineTests.cs: -------------------------------------------------------------------------------- 1 | using Routini.MAUI.Entities.Routines; 2 | using Routini.MAUI.Features.CreateRoutine; 3 | using Routini.MAUI.Pages; 4 | using Routini.MAUI.Test.Mocks; 5 | 6 | namespace Routini.MAUI.Test 7 | { 8 | [TestFixture] 9 | public class ListRoutineTests 10 | { 11 | [Test] 12 | public async Task ListsRoutines_WhenSuccessful() 13 | { 14 | MockEnvironment mockEnvironment = await MockEnvironment.Initialize(); 15 | 16 | await mockEnvironment.AddRoutine(new NewRoutine("Stretch 1", new List 17 | { 18 | new NewRoutineStep("Hamstrings", TimeSpan.FromSeconds(15)), 19 | })); 20 | await mockEnvironment.AddRoutine(new NewRoutine("Stretch 2", new List 21 | { 22 | new NewRoutineStep("Quadriceps", TimeSpan.FromSeconds(15)), 23 | })); 24 | await mockEnvironment.AddRoutine(new NewRoutine("Stretch 3", new List 25 | { 26 | new NewRoutineStep("Hip Flexors", TimeSpan.FromSeconds(15)), 27 | })); 28 | 29 | ListRoutinesViewModel listRoutinesViewModel = mockEnvironment 30 | .ServiceProvider 31 | .GetRequiredService(); 32 | 33 | await listRoutinesViewModel.LoadRoutinesCommand.ExecuteAsync(null); 34 | 35 | Assert.That(listRoutinesViewModel.RoutinePreviews, Has.Count.EqualTo(3)); 36 | } 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /Routini.MAUI/Features/EditRoutine/UpdateRoutineMutation.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Maui.Controls; 2 | using Routini.MAUI.Entities.Routines; 3 | using Routini.MAUI.Shared.Databases; 4 | using SQLite; 5 | 6 | namespace Routini.MAUI.Features.EditRoutine 7 | { 8 | public class UpdateRoutineMutation 9 | { 10 | private readonly ISqliteConnectionFactory _sqliteConnectionFactory; 11 | 12 | public UpdateRoutineMutation(ISqliteConnectionFactory sqliteConnectionFactory) 13 | { 14 | _sqliteConnectionFactory = sqliteConnectionFactory; 15 | } 16 | 17 | public async Task Execute(Routine updatedRoutine) 18 | { 19 | ISQLiteAsyncConnection database = _sqliteConnectionFactory.Create(); 20 | 21 | RoutineDto routineDto = new RoutineDto() 22 | { 23 | Id = updatedRoutine.Id, 24 | Name = updatedRoutine.Name 25 | }; 26 | await database.UpdateAsync(routineDto); 27 | 28 | await database.Table().DeleteAsync(s => s.RoutineId == updatedRoutine.Id); 29 | IEnumerable routineStepDtos = updatedRoutine.Steps.Select((s, i) => new RoutineStepDto() 30 | { 31 | Id = Guid.NewGuid(), 32 | RoutineId = routineDto.Id, 33 | Name = s.Name, 34 | DurationSeconds = s.Duration.TotalSeconds, 35 | Order = i 36 | }); 37 | await database.InsertAllAsync(routineStepDtos); 38 | } 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /Routini.MAUI/Features/ListRoutines/GetAllRoutinesQuery.cs: -------------------------------------------------------------------------------- 1 | using Routini.MAUI.Entities.Routines; 2 | using Routini.MAUI.Shared.Databases; 3 | using SQLite; 4 | 5 | namespace Routini.MAUI.Features.ListRoutines 6 | { 7 | public class GetAllRoutinesQuery 8 | { 9 | private readonly ISqliteConnectionFactory _sqliteConnectionFactory; 10 | 11 | public GetAllRoutinesQuery(ISqliteConnectionFactory sqliteConnectionFactory) 12 | { 13 | _sqliteConnectionFactory = sqliteConnectionFactory; 14 | } 15 | 16 | public async Task> Execute() 17 | { 18 | ISQLiteAsyncConnection database = _sqliteConnectionFactory.Create(); 19 | 20 | IEnumerable routineDtos = await database 21 | .Table() 22 | .ToListAsync(); 23 | IEnumerable routineIds = routineDtos.Select(r => r.Id); 24 | 25 | IEnumerable routineStepDtos = await database 26 | .Table() 27 | .Where(routineStep => routineIds.Contains(routineStep.RoutineId)) 28 | .ToListAsync(); 29 | ILookup routineStepsForRoutine = routineStepDtos 30 | .ToLookup(r => r.RoutineId); 31 | 32 | return routineDtos.Select(d => new Routine( 33 | d.Id, 34 | d.Name ?? string.Empty, 35 | routineStepsForRoutine[d.Id] 36 | .OrderBy(s => s.Order) 37 | .Select(s => new RoutineStep( 38 | s.Name ?? string.Empty, 39 | TimeSpan.FromSeconds(s.DurationSeconds ?? 0))))); 40 | } 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /Routini.MAUI/Platforms/MacCatalyst/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | UIDeviceFamily 15 | 16 | 2 17 | 18 | UIRequiredDeviceCapabilities 19 | 20 | arm64 21 | 22 | UISupportedInterfaceOrientations 23 | 24 | UIInterfaceOrientationPortrait 25 | UIInterfaceOrientationLandscapeLeft 26 | UIInterfaceOrientationLandscapeRight 27 | 28 | UISupportedInterfaceOrientations~ipad 29 | 30 | UIInterfaceOrientationPortrait 31 | UIInterfaceOrientationPortraitUpsideDown 32 | UIInterfaceOrientationLandscapeLeft 33 | UIInterfaceOrientationLandscapeRight 34 | 35 | XSAppIconAssets 36 | Assets.xcassets/appicon.appiconset 37 | 38 | 39 | -------------------------------------------------------------------------------- /Routini.MAUI/Resources/Splash/splash.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /Routini.MAUI/Shared/Components/If.cs: -------------------------------------------------------------------------------- 1 | namespace Routini.MAUI.Shared.Components 2 | { 3 | public class If : ContentView 4 | { 5 | public static readonly BindableProperty ConditionProperty = 6 | BindableProperty.Create(nameof(Condition), typeof(bool), typeof(If), false, propertyChanged: OnContentDependentPropertyChanged); 7 | 8 | public bool Condition 9 | { 10 | get => (bool)GetValue(ConditionProperty); 11 | set => SetValue(ConditionProperty, value); 12 | } 13 | 14 | public static readonly BindableProperty TrueProperty = 15 | BindableProperty.Create(nameof(True), typeof(View), typeof(If), null, propertyChanged: OnContentDependentPropertyChanged); 16 | 17 | public View True 18 | { 19 | get => (View)GetValue(TrueProperty); 20 | set => SetValue(TrueProperty, value); 21 | } 22 | 23 | public static readonly BindableProperty FalseProperty = 24 | BindableProperty.Create(nameof(False), typeof(View), typeof(If), null, propertyChanged: OnContentDependentPropertyChanged); 25 | 26 | public View False 27 | { 28 | get => (View)GetValue(FalseProperty); 29 | set => SetValue(FalseProperty, value); 30 | } 31 | 32 | private static void OnContentDependentPropertyChanged(BindableObject bindable, object oldValue, object newValue) 33 | { 34 | If currentIf = (If)bindable; 35 | 36 | currentIf.UpdateContent(); 37 | } 38 | 39 | private void UpdateContent() 40 | { 41 | if (Condition) 42 | { 43 | Content = True; 44 | } 45 | else 46 | { 47 | Content = False; 48 | } 49 | } 50 | } 51 | } -------------------------------------------------------------------------------- /Routini.MAUI/Pages/RoutineDetailView.xaml: -------------------------------------------------------------------------------- 1 | 2 | 10 | 11 | 12 | 13 |