diff --git a/MoviesTracker/App.xaml b/MoviesTracker/App.xaml
new file mode 100644
index 0000000..a34988f
--- /dev/null
+++ b/MoviesTracker/App.xaml
@@ -0,0 +1,14 @@
+
+
+
+
+
+
+
+
+
+
+
diff --git a/MoviesTracker/App.xaml.cs b/MoviesTracker/App.xaml.cs
new file mode 100644
index 0000000..3bca980
--- /dev/null
+++ b/MoviesTracker/App.xaml.cs
@@ -0,0 +1,39 @@
+using MoviesTracker.Services;
+using MoviesTracker.ViewModels;
+using MoviesTracker.Models;
+using Microsoft.Extensions.DependencyInjection;
+
+namespace MoviesTracker
+{
+ public partial class App : Application
+ {
+ public static Supabase.Client SupabaseClient { get; private set; }
+ public static IServiceProvider ServiceProvider { get; private set; }
+
+ public App()
+ {
+ InitializeComponent();
+
+ SupabaseClient = new Supabase.Client(AppConfig.SUPABASE_URL, AppConfig.SUPABASE_KEY);
+ SupabaseClient.InitializeAsync().Wait();
+
+ var services = new ServiceCollection();
+ ConfigureServices(services);
+
+ ServiceProvider = services.BuildServiceProvider();
+ var mainPage = ServiceProvider.GetRequiredService();
+
+ MainPage = mainPage;
+ }
+
+ private void ConfigureServices(IServiceCollection services)
+ {
+ services.AddSingleton();
+ services.AddSingleton();
+ services.AddTransient();
+ services.AddTransient();
+ services.AddTransient();
+ services.AddSingleton();
+ }
+ }
+}
diff --git a/MoviesTracker/AppShell.xaml b/MoviesTracker/AppShell.xaml
new file mode 100644
index 0000000..074f21f
--- /dev/null
+++ b/MoviesTracker/AppShell.xaml
@@ -0,0 +1,12 @@
+
+
+
+
+
+
+
diff --git a/MoviesTracker/AppShell.xaml.cs b/MoviesTracker/AppShell.xaml.cs
new file mode 100644
index 0000000..1c01c10
--- /dev/null
+++ b/MoviesTracker/AppShell.xaml.cs
@@ -0,0 +1,20 @@
+using MoviesTracker.Views;
+
+namespace MoviesTracker
+{
+ public partial class AppShell : Shell
+ {
+ public AppShell()
+ {
+ InitializeComponent();
+
+ RegisterForRoute();
+ RegisterForRoute();
+ }
+
+ protected void RegisterForRoute()
+ {
+ Routing.RegisterRoute(typeof(T).Name, typeof(T));
+ }
+ }
+}
diff --git a/MoviesTracker/MainPage.xaml b/MoviesTracker/MainPage.xaml
new file mode 100644
index 0000000..6b94aab
--- /dev/null
+++ b/MoviesTracker/MainPage.xaml
@@ -0,0 +1,36 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/MoviesTracker/MainPage.xaml.cs b/MoviesTracker/MainPage.xaml.cs
new file mode 100644
index 0000000..b55d96d
--- /dev/null
+++ b/MoviesTracker/MainPage.xaml.cs
@@ -0,0 +1,25 @@
+namespace MoviesTracker
+{
+ public partial class MainPage : ContentPage
+ {
+ int count = 0;
+
+ public MainPage()
+ {
+ InitializeComponent();
+ }
+
+ private void OnCounterClicked(object sender, EventArgs e)
+ {
+ count++;
+
+ if (count == 1)
+ CounterBtn.Text = $"Clicked {count} time";
+ else
+ CounterBtn.Text = $"Clicked {count} times";
+
+ SemanticScreenReader.Announce(CounterBtn.Text);
+ }
+ }
+
+}
diff --git a/MoviesTracker/MauiProgram.cs b/MoviesTracker/MauiProgram.cs
new file mode 100644
index 0000000..4f7fb57
--- /dev/null
+++ b/MoviesTracker/MauiProgram.cs
@@ -0,0 +1,54 @@
+using MoviesTracker.Models;
+using MoviesTracker.Services;
+using MoviesTracker.ViewModels;
+using MoviesTracker.Views;
+using CommunityToolkit.Maui;
+using Microsoft.Extensions.Logging;
+using Supabase;
+
+namespace MoviesTracker
+{
+ public static class MauiProgram
+ {
+ public static MauiApp CreateMauiApp()
+ {
+ var builder = MauiApp.CreateBuilder();
+ builder.UseMauiApp()
+ .UseMauiCommunityToolkit()
+ .ConfigureFonts(fonts =>
+ {
+ fonts.AddFont("OpenSans-Regular.ttf", "OpenSansRegular");
+ fonts.AddFont("OpenSans-Semibold.ttf", "OpenSansSemibold");
+ });
+
+ // Configure Supabase
+ var url = AppConfig.SUPABASE_URL;
+ var key = AppConfig.SUPABASE_KEY;
+ builder.Services.AddSingleton(provider => new Supabase.Client(url, key));
+
+ // Add ViewModels
+ builder.Services.AddTransient();
+ builder.Services.AddTransient();
+ builder.Services.AddSingleton();
+ builder.Services.AddTransient();
+ builder.Services.AddTransient();
+
+ // Add Views
+ builder.Services.AddSingleton();
+ builder.Services.AddSingleton();
+ builder.Services.AddSingleton();
+ builder.Services.AddTransient();
+ builder.Services.AddTransient();
+
+ // Add Data Service
+ builder.Services.AddSingleton();
+ builder.Services.AddSingleton();
+
+#if DEBUG
+ builder.Logging.AddDebug();
+#endif
+
+ return builder.Build();
+ }
+ }
+}
diff --git a/MoviesTracker/Models/AppConfig.cs b/MoviesTracker/Models/AppConfig.cs
new file mode 100644
index 0000000..95523be
--- /dev/null
+++ b/MoviesTracker/Models/AppConfig.cs
@@ -0,0 +1,12 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+namespace MoviesTracker.Models;
+
+public static class AppConfig
+{
+ public const string SUPABASE_URL = "https://qdlkufwnccsvaxeqqlnp.supabase.co";
+ public const string SUPABASE_KEY = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6InFkbGt1ZnduY2NzdmF4ZXFxbG5wIiwicm9sZSI6ImFub24iLCJpYXQiOjE3MTk3NDY5OTIsImV4cCI6MjAzNTMyMjk5Mn0.eL1QVu-xZymRRd_pUTFPzsxs0b_Gw8yLCn2ilDsTPxo";
+}
\ No newline at end of file
diff --git a/MoviesTracker/Models/AuthUser.cs b/MoviesTracker/Models/AuthUser.cs
new file mode 100644
index 0000000..4dd6371
--- /dev/null
+++ b/MoviesTracker/Models/AuthUser.cs
@@ -0,0 +1,15 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace MoviesTracker.Models
+{
+ public class AuthUser
+ {
+ public string Email { get; set; }
+ public string Password { get; set; }
+ }
+
+}
diff --git a/MoviesTracker/Models/Movies.cs b/MoviesTracker/Models/Movies.cs
new file mode 100644
index 0000000..27cf048
--- /dev/null
+++ b/MoviesTracker/Models/Movies.cs
@@ -0,0 +1,35 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using Postgrest.Attributes;
+using Postgrest.Models;
+
+namespace MoviesTracker.Models
+{
+ [Postgrest.Attributes.Table("movies")]
+ public class Movie : BaseModel
+ {
+ [Postgrest.Attributes.PrimaryKey("id", false)]
+ public int Id { get; set; }
+
+ [Postgrest.Attributes.Column("title")]
+ public string Title { get; set; }
+
+ [Postgrest.Attributes.Column("director")]
+ public string Director { get; set; }
+
+ [Postgrest.Attributes.Column("image_url")]
+ public string ImageUrl { get; set; }
+
+ [Postgrest.Attributes.Column("created_at", ignoreOnInsert: true)]
+ public DateTime CreatedAt { get; set; }
+
+ [Postgrest.Attributes.Column("is_finished")]
+ public bool IsFinished { get; set; }
+
+ [Postgrest.Attributes.Column("release_date")]
+ public DateTime ReleaseDate { get; set; }
+ }
+}
diff --git a/MoviesTracker/MoviesTracker.csproj b/MoviesTracker/MoviesTracker.csproj
new file mode 100644
index 0000000..d17e2da
--- /dev/null
+++ b/MoviesTracker/MoviesTracker.csproj
@@ -0,0 +1,95 @@
+
+
+
+ net8.0-android;net8.0-ios;net8.0-maccatalyst
+ $(TargetFrameworks);net8.0-windows10.0.19041.0
+
+
+
+
+
+
+ Exe
+ MoviesTracker
+ true
+ true
+ enable
+ enable
+
+
+ MoviesTracker
+
+
+ com.companyname.moviestracker
+
+
+ 1.0
+ 1
+
+ 11.0
+ 13.1
+ 21.0
+ 10.0.17763.0
+ 10.0.17763.0
+ 6.5
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ AddMoviePage.xaml
+
+
+ MoviesListingPage.xaml
+
+
+
+
+
+ MSBuild:Compile
+
+
+ MSBuild:Compile
+
+
+ MSBuild:Compile
+
+
+ MSBuild:Compile
+
+
+ MSBuild:Compile
+
+
+
+
diff --git a/MoviesTracker/Platforms/Android/AndroidManifest.xml b/MoviesTracker/Platforms/Android/AndroidManifest.xml
new file mode 100644
index 0000000..e9937ad
--- /dev/null
+++ b/MoviesTracker/Platforms/Android/AndroidManifest.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/MoviesTracker/Platforms/Android/MainActivity.cs b/MoviesTracker/Platforms/Android/MainActivity.cs
new file mode 100644
index 0000000..03652d7
--- /dev/null
+++ b/MoviesTracker/Platforms/Android/MainActivity.cs
@@ -0,0 +1,11 @@
+using Android.App;
+using Android.Content.PM;
+using Android.OS;
+
+namespace MoviesTracker
+{
+ [Activity(Theme = "@style/Maui.SplashTheme", MainLauncher = true, LaunchMode = LaunchMode.SingleTop, ConfigurationChanges = ConfigChanges.ScreenSize | ConfigChanges.Orientation | ConfigChanges.UiMode | ConfigChanges.ScreenLayout | ConfigChanges.SmallestScreenSize | ConfigChanges.Density)]
+ public class MainActivity : MauiAppCompatActivity
+ {
+ }
+}
diff --git a/MoviesTracker/Platforms/Android/MainApplication.cs b/MoviesTracker/Platforms/Android/MainApplication.cs
new file mode 100644
index 0000000..c0dd59c
--- /dev/null
+++ b/MoviesTracker/Platforms/Android/MainApplication.cs
@@ -0,0 +1,16 @@
+using Android.App;
+using Android.Runtime;
+
+namespace MoviesTracker
+{
+ [Application]
+ public class MainApplication : MauiApplication
+ {
+ public MainApplication(IntPtr handle, JniHandleOwnership ownership)
+ : base(handle, ownership)
+ {
+ }
+
+ protected override MauiApp CreateMauiApp() => MauiProgram.CreateMauiApp();
+ }
+}
diff --git a/MoviesTracker/Platforms/Android/Resources/values/colors.xml b/MoviesTracker/Platforms/Android/Resources/values/colors.xml
new file mode 100644
index 0000000..c04d749
--- /dev/null
+++ b/MoviesTracker/Platforms/Android/Resources/values/colors.xml
@@ -0,0 +1,6 @@
+
+
+ #512BD4
+ #2B0B98
+ #2B0B98
+
\ No newline at end of file
diff --git a/MoviesTracker/Platforms/MacCatalyst/AppDelegate.cs b/MoviesTracker/Platforms/MacCatalyst/AppDelegate.cs
new file mode 100644
index 0000000..144e7eb
--- /dev/null
+++ b/MoviesTracker/Platforms/MacCatalyst/AppDelegate.cs
@@ -0,0 +1,10 @@
+using Foundation;
+
+namespace MoviesTracker
+{
+ [Register("AppDelegate")]
+ public class AppDelegate : MauiUIApplicationDelegate
+ {
+ protected override MauiApp CreateMauiApp() => MauiProgram.CreateMauiApp();
+ }
+}
diff --git a/MoviesTracker/Platforms/MacCatalyst/Entitlements.plist b/MoviesTracker/Platforms/MacCatalyst/Entitlements.plist
new file mode 100644
index 0000000..de4adc9
--- /dev/null
+++ b/MoviesTracker/Platforms/MacCatalyst/Entitlements.plist
@@ -0,0 +1,14 @@
+
+
+
+
+
+
+ com.apple.security.app-sandbox
+
+
+ com.apple.security.network.client
+
+
+
+
diff --git a/MoviesTracker/Platforms/MacCatalyst/Info.plist b/MoviesTracker/Platforms/MacCatalyst/Info.plist
new file mode 100644
index 0000000..7268977
--- /dev/null
+++ b/MoviesTracker/Platforms/MacCatalyst/Info.plist
@@ -0,0 +1,38 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+ UIDeviceFamily
+
+ 2
+
+ UIRequiredDeviceCapabilities
+
+ arm64
+
+ UISupportedInterfaceOrientations
+
+ UIInterfaceOrientationPortrait
+ UIInterfaceOrientationLandscapeLeft
+ UIInterfaceOrientationLandscapeRight
+
+ UISupportedInterfaceOrientations~ipad
+
+ UIInterfaceOrientationPortrait
+ UIInterfaceOrientationPortraitUpsideDown
+ UIInterfaceOrientationLandscapeLeft
+ UIInterfaceOrientationLandscapeRight
+
+ XSAppIconAssets
+ Assets.xcassets/appicon.appiconset
+
+
diff --git a/MoviesTracker/Platforms/MacCatalyst/Program.cs b/MoviesTracker/Platforms/MacCatalyst/Program.cs
new file mode 100644
index 0000000..c6e6207
--- /dev/null
+++ b/MoviesTracker/Platforms/MacCatalyst/Program.cs
@@ -0,0 +1,16 @@
+using ObjCRuntime;
+using UIKit;
+
+namespace MoviesTracker
+{
+ public class Program
+ {
+ // This is the main entry point of the application.
+ static void Main(string[] args)
+ {
+ // if you want to use a different Application Delegate class from "AppDelegate"
+ // you can specify it here.
+ UIApplication.Main(args, null, typeof(AppDelegate));
+ }
+ }
+}
diff --git a/MoviesTracker/Platforms/Tizen/Main.cs b/MoviesTracker/Platforms/Tizen/Main.cs
new file mode 100644
index 0000000..9f7e29c
--- /dev/null
+++ b/MoviesTracker/Platforms/Tizen/Main.cs
@@ -0,0 +1,17 @@
+using Microsoft.Maui;
+using Microsoft.Maui.Hosting;
+using System;
+
+namespace MoviesTracker
+{
+ internal class Program : MauiApplication
+ {
+ protected override MauiApp CreateMauiApp() => MauiProgram.CreateMauiApp();
+
+ static void Main(string[] args)
+ {
+ var app = new Program();
+ app.Run(args);
+ }
+ }
+}
diff --git a/MoviesTracker/Platforms/Tizen/tizen-manifest.xml b/MoviesTracker/Platforms/Tizen/tizen-manifest.xml
new file mode 100644
index 0000000..bb2fa98
--- /dev/null
+++ b/MoviesTracker/Platforms/Tizen/tizen-manifest.xml
@@ -0,0 +1,15 @@
+
+
+
+
+
+ maui-appicon-placeholder
+
+
+
+
+ http://tizen.org/privilege/internet
+
+
+
+
\ No newline at end of file
diff --git a/MoviesTracker/Platforms/Windows/App.xaml b/MoviesTracker/Platforms/Windows/App.xaml
new file mode 100644
index 0000000..7a1a44f
--- /dev/null
+++ b/MoviesTracker/Platforms/Windows/App.xaml
@@ -0,0 +1,8 @@
+
+
+
diff --git a/MoviesTracker/Platforms/Windows/App.xaml.cs b/MoviesTracker/Platforms/Windows/App.xaml.cs
new file mode 100644
index 0000000..348da12
--- /dev/null
+++ b/MoviesTracker/Platforms/Windows/App.xaml.cs
@@ -0,0 +1,25 @@
+using Microsoft.UI.Xaml;
+
+// To learn more about WinUI, the WinUI project structure,
+// and more about our project templates, see: http://aka.ms/winui-project-info.
+
+namespace MoviesTracker.WinUI
+{
+ ///
+ /// Provides application-specific behavior to supplement the default Application class.
+ ///
+ public partial class App : MauiWinUIApplication
+ {
+ ///
+ /// Initializes the singleton application object. This is the first line of authored code
+ /// executed, and as such is the logical equivalent of main() or WinMain().
+ ///
+ public App()
+ {
+ this.InitializeComponent();
+ }
+
+ protected override MauiApp CreateMauiApp() => MauiProgram.CreateMauiApp();
+ }
+
+}
diff --git a/MoviesTracker/Platforms/Windows/Package.appxmanifest b/MoviesTracker/Platforms/Windows/Package.appxmanifest
new file mode 100644
index 0000000..e9a58c4
--- /dev/null
+++ b/MoviesTracker/Platforms/Windows/Package.appxmanifest
@@ -0,0 +1,46 @@
+
+
+
+
+
+
+
+
+ $placeholder$
+ User Name
+ $placeholder$.png
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/MoviesTracker/Platforms/Windows/app.manifest b/MoviesTracker/Platforms/Windows/app.manifest
new file mode 100644
index 0000000..1097931
--- /dev/null
+++ b/MoviesTracker/Platforms/Windows/app.manifest
@@ -0,0 +1,15 @@
+
+
+
+
+
+
+
+ true/PM
+ PerMonitorV2, PerMonitor
+
+
+
diff --git a/MoviesTracker/Platforms/iOS/AppDelegate.cs b/MoviesTracker/Platforms/iOS/AppDelegate.cs
new file mode 100644
index 0000000..144e7eb
--- /dev/null
+++ b/MoviesTracker/Platforms/iOS/AppDelegate.cs
@@ -0,0 +1,10 @@
+using Foundation;
+
+namespace MoviesTracker
+{
+ [Register("AppDelegate")]
+ public class AppDelegate : MauiUIApplicationDelegate
+ {
+ protected override MauiApp CreateMauiApp() => MauiProgram.CreateMauiApp();
+ }
+}
diff --git a/MoviesTracker/Platforms/iOS/Info.plist b/MoviesTracker/Platforms/iOS/Info.plist
new file mode 100644
index 0000000..0004a4f
--- /dev/null
+++ b/MoviesTracker/Platforms/iOS/Info.plist
@@ -0,0 +1,32 @@
+
+
+
+
+ LSRequiresIPhoneOS
+
+ UIDeviceFamily
+
+ 1
+ 2
+
+ UIRequiredDeviceCapabilities
+
+ arm64
+
+ UISupportedInterfaceOrientations
+
+ UIInterfaceOrientationPortrait
+ UIInterfaceOrientationLandscapeLeft
+ UIInterfaceOrientationLandscapeRight
+
+ UISupportedInterfaceOrientations~ipad
+
+ UIInterfaceOrientationPortrait
+ UIInterfaceOrientationPortraitUpsideDown
+ UIInterfaceOrientationLandscapeLeft
+ UIInterfaceOrientationLandscapeRight
+
+ XSAppIconAssets
+ Assets.xcassets/appicon.appiconset
+
+
diff --git a/MoviesTracker/Platforms/iOS/Program.cs b/MoviesTracker/Platforms/iOS/Program.cs
new file mode 100644
index 0000000..c6e6207
--- /dev/null
+++ b/MoviesTracker/Platforms/iOS/Program.cs
@@ -0,0 +1,16 @@
+using ObjCRuntime;
+using UIKit;
+
+namespace MoviesTracker
+{
+ public class Program
+ {
+ // This is the main entry point of the application.
+ static void Main(string[] args)
+ {
+ // if you want to use a different Application Delegate class from "AppDelegate"
+ // you can specify it here.
+ UIApplication.Main(args, null, typeof(AppDelegate));
+ }
+ }
+}
diff --git a/MoviesTracker/Properties/launchSettings.json b/MoviesTracker/Properties/launchSettings.json
new file mode 100644
index 0000000..edf8aad
--- /dev/null
+++ b/MoviesTracker/Properties/launchSettings.json
@@ -0,0 +1,8 @@
+{
+ "profiles": {
+ "Windows Machine": {
+ "commandName": "MsixPackage",
+ "nativeDebugging": false
+ }
+ }
+}
\ No newline at end of file
diff --git a/MoviesTracker/Resources/AppIcon/appicon.svg b/MoviesTracker/Resources/AppIcon/appicon.svg
new file mode 100644
index 0000000..9d63b65
--- /dev/null
+++ b/MoviesTracker/Resources/AppIcon/appicon.svg
@@ -0,0 +1,4 @@
+
+
\ No newline at end of file
diff --git a/MoviesTracker/Resources/AppIcon/appiconfg.svg b/MoviesTracker/Resources/AppIcon/appiconfg.svg
new file mode 100644
index 0000000..21dfb25
--- /dev/null
+++ b/MoviesTracker/Resources/AppIcon/appiconfg.svg
@@ -0,0 +1,8 @@
+
+
+
\ No newline at end of file
diff --git a/MoviesTracker/Resources/Fonts/OpenSans-Regular.ttf b/MoviesTracker/Resources/Fonts/OpenSans-Regular.ttf
new file mode 100644
index 0000000..4e5fbdb
Binary files /dev/null and b/MoviesTracker/Resources/Fonts/OpenSans-Regular.ttf differ
diff --git a/MoviesTracker/Resources/Fonts/OpenSans-Semibold.ttf b/MoviesTracker/Resources/Fonts/OpenSans-Semibold.ttf
new file mode 100644
index 0000000..cfcc6c4
Binary files /dev/null and b/MoviesTracker/Resources/Fonts/OpenSans-Semibold.ttf differ
diff --git a/MoviesTracker/Resources/Images/dotnet_bot.png b/MoviesTracker/Resources/Images/dotnet_bot.png
new file mode 100644
index 0000000..f93ce02
Binary files /dev/null and b/MoviesTracker/Resources/Images/dotnet_bot.png differ
diff --git a/MoviesTracker/Resources/Raw/AboutAssets.txt b/MoviesTracker/Resources/Raw/AboutAssets.txt
new file mode 100644
index 0000000..89dc758
--- /dev/null
+++ b/MoviesTracker/Resources/Raw/AboutAssets.txt
@@ -0,0 +1,15 @@
+Any raw assets you want to be deployed with your application can be placed in
+this directory (and child directories). Deployment of the asset to your application
+is automatically handled by the following `MauiAsset` Build Action within your `.csproj`.
+
+
+
+These files will be deployed with your package and will be accessible using Essentials:
+
+ async Task LoadMauiAsset()
+ {
+ using var stream = await FileSystem.OpenAppPackageFileAsync("AboutAssets.txt");
+ using var reader = new StreamReader(stream);
+
+ var contents = reader.ReadToEnd();
+ }
diff --git a/MoviesTracker/Resources/Splash/splash.svg b/MoviesTracker/Resources/Splash/splash.svg
new file mode 100644
index 0000000..21dfb25
--- /dev/null
+++ b/MoviesTracker/Resources/Splash/splash.svg
@@ -0,0 +1,8 @@
+
+
+
\ No newline at end of file
diff --git a/MoviesTracker/Resources/Styles/Colors.xaml b/MoviesTracker/Resources/Styles/Colors.xaml
new file mode 100644
index 0000000..30307a5
--- /dev/null
+++ b/MoviesTracker/Resources/Styles/Colors.xaml
@@ -0,0 +1,45 @@
+
+
+
+
+
+
+ #512BD4
+ #ac99ea
+ #242424
+ #DFD8F7
+ #9880e5
+ #2B0B98
+
+ White
+ Black
+ #D600AA
+ #190649
+ #1f1f1f
+
+ #E1E1E1
+ #C8C8C8
+ #ACACAC
+ #919191
+ #6E6E6E
+ #404040
+ #212121
+ #141414
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/MoviesTracker/Resources/Styles/Styles.xaml b/MoviesTracker/Resources/Styles/Styles.xaml
new file mode 100644
index 0000000..19ceac5
--- /dev/null
+++ b/MoviesTracker/Resources/Styles/Styles.xaml
@@ -0,0 +1,426 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/MoviesTracker/Services/AuthService.cs b/MoviesTracker/Services/AuthService.cs
new file mode 100644
index 0000000..5624a0e
--- /dev/null
+++ b/MoviesTracker/Services/AuthService.cs
@@ -0,0 +1,48 @@
+using System.Threading.Tasks;
+using Supabase;
+using MoviesTracker.Models;
+using Supabase.Gotrue.Exceptions;
+
+namespace MoviesTracker.Services
+{
+ public class AuthService : IAuthService
+ {
+ private readonly Supabase.Client _supabase;
+
+ public AuthService()
+ {
+ _supabase = new Supabase.Client(AppConfig.SUPABASE_URL, AppConfig.SUPABASE_KEY);
+ _supabase.InitializeAsync().Wait();
+ }
+
+ public async Task SignIn(string email, string password)
+ {
+ var response = await _supabase.Auth.SignIn(email, password);
+ return response.User != null;
+ }
+
+ public async Task SignUp(string email, string password)
+ {
+ try
+ {
+ var response = await _supabase.Auth.SignUp(email, password);
+ return response.User != null;
+ }
+ catch (GotrueException ex) when (ex.Message.Contains("over_email_send_rate_limit"))
+ {
+
+ throw new Exception("Email rate limit exceeded. Please try again later.");
+ }
+ catch (Exception ex)
+ {
+
+ throw new Exception($"Sign up failed: {ex.Message}");
+ }
+ }
+ public async Task SignOut()
+ {
+ await _supabase.Auth.SignOut();
+ }
+ }
+}
+
diff --git a/MoviesTracker/Services/DataService.cs b/MoviesTracker/Services/DataService.cs
new file mode 100644
index 0000000..4070802
--- /dev/null
+++ b/MoviesTracker/Services/DataService.cs
@@ -0,0 +1,47 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+using MoviesTracker.Models;
+using Supabase;
+
+namespace MoviesTracker.Services;
+
+public class DataService : IDataService
+{
+ private readonly Client _supabaseClient;
+
+ public DataService(Client supabaseClient)
+ {
+ _supabaseClient = supabaseClient;
+ }
+ public async Task> GetMovies()
+ {
+ var response = await _supabaseClient.From().Get();
+ return response.Models.OrderByDescending(b => b.CreatedAt);
+ }
+
+ public async Task CreateMovie(Movie movie)
+ {
+ await _supabaseClient.From().Insert(movie);
+ }
+
+ public async Task DeleteMovie(int id)
+ {
+ await _supabaseClient.From()
+ .Where(b => b.Id == id).Delete();
+ }
+
+ public async Task UpdateMovie(Movie movie)
+ {
+ await _supabaseClient.From().Where(b => b.Id == movie.Id)
+ .Set(b => b.Title, movie.Title)
+ .Set(b => b.Director, movie.Director)
+ .Set(b => b.ImageUrl, movie.ImageUrl)
+ .Set(b => b.ReleaseDate, movie.ReleaseDate)
+ .Set(b => b.IsFinished, movie.IsFinished)
+ .Update();
+ }
+}
diff --git a/MoviesTracker/Services/IAuthService.cs b/MoviesTracker/Services/IAuthService.cs
new file mode 100644
index 0000000..a2fd304
--- /dev/null
+++ b/MoviesTracker/Services/IAuthService.cs
@@ -0,0 +1,16 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace MoviesTracker.Services
+{
+ public interface IAuthService
+ {
+ Task SignIn(string email, string password);
+ Task SignUp(string email, string password);
+ Task SignOut();
+ }
+
+}
diff --git a/MoviesTracker/Services/IDataService.cs b/MoviesTracker/Services/IDataService.cs
new file mode 100644
index 0000000..3ea3611
--- /dev/null
+++ b/MoviesTracker/Services/IDataService.cs
@@ -0,0 +1,18 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+
+using MoviesTracker.Models;
+
+namespace MoviesTracker.Services;
+
+public interface IDataService
+{
+ Task> GetMovies();
+ Task CreateMovie(Movie movie);
+ Task DeleteMovie(int id);
+ Task UpdateMovie(Movie movie);
+}
diff --git a/MoviesTracker/ViewModels/AddMovieViewModel.cs b/MoviesTracker/ViewModels/AddMovieViewModel.cs
new file mode 100644
index 0000000..22396f6
--- /dev/null
+++ b/MoviesTracker/ViewModels/AddMovieViewModel.cs
@@ -0,0 +1,58 @@
+using MoviesTracker.Models;
+using MoviesTracker.Services;
+using CommunityToolkit.Mvvm.ComponentModel;
+using CommunityToolkit.Mvvm.Input;
+
+namespace MoviesTracker.ViewModels
+{
+ public partial class AddMovieViewModel : ObservableObject
+ {
+ private readonly IDataService _dataService;
+
+ [ObservableProperty]
+ private string _movieTitle;
+ [ObservableProperty]
+ private string _movieDirector;
+ [ObservableProperty]
+ private string _movieImageUrl;
+ [ObservableProperty]
+ private bool _movieIsFinished;
+ [ObservableProperty]
+ private DateTime _movieReleaseDate;
+ public AddMovieViewModel(IDataService dataService)
+ {
+ _dataService = dataService;
+ _movieReleaseDate = DateTime.Now;
+ }
+
+ [RelayCommand]
+ private async Task AddMovie()
+ {
+ try
+ {
+ if (!string.IsNullOrEmpty(MovieTitle))
+ {
+ Movie movie = new()
+ {
+ Title = MovieTitle,
+ Director = MovieDirector,
+ ImageUrl = MovieImageUrl,
+ IsFinished = MovieIsFinished,
+ ReleaseDate = MovieReleaseDate
+ };
+ await _dataService.CreateMovie(movie);
+
+ await Shell.Current.GoToAsync("..");
+ }
+ else
+ {
+ await Shell.Current.DisplayAlert("Error", "No title!", "OK");
+ }
+ }
+ catch (Exception ex)
+ {
+ await Shell.Current.DisplayAlert("Error", ex.Message, "OK");
+ }
+ }
+ }
+}
diff --git a/MoviesTracker/ViewModels/LoginViewModel.cs b/MoviesTracker/ViewModels/LoginViewModel.cs
new file mode 100644
index 0000000..50c6eb9
--- /dev/null
+++ b/MoviesTracker/ViewModels/LoginViewModel.cs
@@ -0,0 +1,85 @@
+using CommunityToolkit.Mvvm.ComponentModel;
+using CommunityToolkit.Mvvm.Input;
+using MoviesTracker.Services;
+using System.Threading.Tasks;
+using System.Windows.Input;
+using Microsoft.Maui.Controls;
+
+namespace MoviesTracker.ViewModels
+{
+ public class LoginViewModel : ObservableObject
+ {
+ private readonly IAuthService _authService;
+ private string _email;
+ private string _password;
+ private string _errorMessage;
+
+ public string Email
+ {
+ get => _email;
+ set => SetProperty(ref _email, value);
+ }
+
+ public string Password
+ {
+ get => _password;
+ set => SetProperty(ref _password, value);
+ }
+
+ public string ErrorMessage
+ {
+ get => _errorMessage;
+ set => SetProperty(ref _errorMessage, value);
+ }
+
+ public ICommand LoginCommand { get; }
+ public ICommand NavigateToSignUpCommand { get; }
+
+ public LoginViewModel() : this(App.ServiceProvider.GetService()) { }
+
+ public LoginViewModel(IAuthService authService)
+ {
+ _authService = authService;
+ LoginCommand = new AsyncRelayCommand(OnLogin);
+ NavigateToSignUpCommand = new AsyncRelayCommand(OnNavigateToSignUp);
+ }
+
+ private async Task OnLogin()
+ {
+ if (_authService == null)
+ {
+ ErrorMessage = "Authentication service is not available.";
+ return;
+ }
+
+ var success = await _authService.SignIn(Email, Password);
+ if (success)
+ {
+ try
+ {
+ await Shell.Current.GoToAsync("//MoviesListingPage");
+ }
+ catch (Exception ex)
+ {
+ await Shell.Current.DisplayAlert("Error", ex.Message, "OK");
+ }
+ }
+ else
+ {
+ ErrorMessage = "Login failed. Please try again.";
+ }
+ }
+
+ private async Task OnNavigateToSignUp()
+ {
+ try
+ {
+ await Shell.Current.GoToAsync("///SignUpPage");
+ }
+ catch (Exception ex)
+ {
+ await Shell.Current.DisplayAlert("Error", ex.Message, "OK");
+ }
+ }
+ }
+}
diff --git a/MoviesTracker/ViewModels/MoviesListingViewModel.cs b/MoviesTracker/ViewModels/MoviesListingViewModel.cs
new file mode 100644
index 0000000..2c1ec8f
--- /dev/null
+++ b/MoviesTracker/ViewModels/MoviesListingViewModel.cs
@@ -0,0 +1,95 @@
+using MoviesTracker.Models;
+using MoviesTracker.Services;
+using CommunityToolkit.Mvvm.ComponentModel;
+using CommunityToolkit.Mvvm.Input;
+using System.Collections.ObjectModel;
+using System.Threading.Tasks;
+using Microsoft.Maui.Controls;
+
+namespace MoviesTracker.ViewModels
+{
+ public partial class MoviesListingViewModel : ObservableObject
+ {
+ private readonly IDataService _dataService;
+ private readonly IAuthService _authService;
+
+ public ObservableCollection Movies { get; set; } = new();
+
+ public MoviesListingViewModel(IDataService dataService, IAuthService authService)
+ {
+ _dataService = dataService;
+ _authService = authService;
+ }
+
+ [RelayCommand]
+ public async Task GetMovies()
+ {
+ Movies.Clear();
+
+ try
+ {
+ var movies = await _dataService.GetMovies();
+
+ if (movies.Any())
+ {
+ foreach (var movie in movies)
+ {
+ Movies.Add(movie);
+ }
+ }
+ }
+ catch (Exception ex)
+ {
+ await Shell.Current.DisplayAlert("Error", ex.Message, "OK");
+ }
+ }
+
+ [RelayCommand]
+ private async Task AddMovie() => await Shell.Current.GoToAsync("AddMoviePage");
+
+ [RelayCommand]
+ private async Task DeleteMovie(Movie movie)
+ {
+ var result = await Shell.Current.DisplayAlert("Delete", $"Are you sure you want to delete \"{movie.Title}\"?", "Yes", "No");
+
+ if (result)
+ {
+ try
+ {
+ await _dataService.DeleteMovie(movie.Id);
+ await GetMovies();
+ }
+ catch (Exception ex)
+ {
+ await Shell.Current.DisplayAlert("Error", ex.Message, "OK");
+ }
+ }
+ }
+
+ [RelayCommand]
+ private async Task UpdateMovie(Movie movie) => await Shell.Current.GoToAsync("UpdateMoviePage", new Dictionary
+ {
+ {"MovieObject", movie }
+ });
+
+ private async Task OnLogout()
+ {
+ try
+ {
+ await _authService.SignOut();
+ await Shell.Current.GoToAsync("///LoginPage");
+ }
+ catch (Exception ex)
+ {
+ await Shell.Current.DisplayAlert("Error", $"Logout failed: {ex.Message}", "OK");
+ }
+ }
+
+ [RelayCommand]
+ private async Task Logout()
+ {
+ await _authService.SignOut();
+ await Shell.Current.GoToAsync("///LoginPage");
+ }
+ }
+}
diff --git a/MoviesTracker/ViewModels/SignUpViewModel.cs b/MoviesTracker/ViewModels/SignUpViewModel.cs
new file mode 100644
index 0000000..0eadfde
--- /dev/null
+++ b/MoviesTracker/ViewModels/SignUpViewModel.cs
@@ -0,0 +1,109 @@
+using CommunityToolkit.Mvvm.ComponentModel;
+using CommunityToolkit.Mvvm.Input;
+using MoviesTracker.Services;
+using System;
+using System.Threading.Tasks;
+using System.Windows.Input;
+using Microsoft.Maui.Controls;
+
+namespace MoviesTracker.ViewModels
+{
+ public class SignUpViewModel : ObservableObject
+ {
+ private readonly IAuthService _authService;
+ private string _email;
+ private string _password;
+ private string _confirmPassword;
+ private string _errorMessage;
+
+ public string Email
+ {
+ get => _email;
+ set => SetProperty(ref _email, value);
+ }
+
+ public string Password
+ {
+ get => _password;
+ set => SetProperty(ref _password, value);
+ }
+
+ public string ConfirmPassword
+ {
+ get => _confirmPassword;
+ set => SetProperty(ref _confirmPassword, value);
+ }
+
+ public string ErrorMessage
+ {
+ get => _errorMessage;
+ set => SetProperty(ref _errorMessage, value);
+ }
+
+ public ICommand SignUpCommand { get; }
+ public ICommand NavigateToLoginCommand { get; }
+
+ public SignUpViewModel() : this(App.ServiceProvider.GetService()) { }
+
+ public SignUpViewModel(IAuthService authService)
+ {
+ _authService = authService;
+ SignUpCommand = new AsyncRelayCommand(OnSignUp);
+ NavigateToLoginCommand = new AsyncRelayCommand(OnNavigateToLogin);
+ }
+
+ private async Task OnSignUp()
+ {
+ ErrorMessage = string.Empty;
+
+ if (_authService == null)
+ {
+ ErrorMessage = "Authentication service is not available.";
+ return;
+ }
+
+ if (Password != ConfirmPassword)
+ {
+ ErrorMessage = "Passwords do not match.";
+ return;
+ }
+
+ try
+ {
+ var success = await _authService.SignUp(Email, Password);
+ if (success)
+ {
+ try
+ {
+ await Shell.Current.GoToAsync("///LoginPage");
+ }
+ catch (Exception ex)
+ {
+ await Shell.Current.DisplayAlert("Error", ex.Message, "OK");
+ }
+ }
+ else
+ {
+ ErrorMessage = "Sign up failed. Please try again.";
+ }
+ }
+ catch (Exception ex)
+ {
+ ErrorMessage = ex.Message;
+ }
+
+ }
+
+ private async Task OnNavigateToLogin()
+ {
+ try
+ {
+ await Shell.Current.GoToAsync("///LoginPage");
+ }
+ catch (Exception ex)
+ {
+ await Shell.Current.DisplayAlert("Error", ex.Message, "OK");
+ }
+ }
+ }
+}
diff --git a/MoviesTracker/ViewModels/UpdateMovieViewModel.cs b/MoviesTracker/ViewModels/UpdateMovieViewModel.cs
new file mode 100644
index 0000000..50e3048
--- /dev/null
+++ b/MoviesTracker/ViewModels/UpdateMovieViewModel.cs
@@ -0,0 +1,43 @@
+using MoviesTracker.Models;
+using MoviesTracker.Services;
+using CommunityToolkit.Mvvm.ComponentModel;
+using CommunityToolkit.Mvvm.Input;
+using System.Threading.Tasks;
+
+namespace MoviesTracker.ViewModels
+{
+ [QueryProperty(nameof(Movie), "MovieObject")]
+ public partial class UpdateMovieViewModel : ObservableObject
+ {
+ private readonly IDataService _dataService;
+
+ [ObservableProperty]
+ private Movie _movie;
+
+ public UpdateMovieViewModel(IDataService dataService)
+ {
+ _dataService = dataService;
+ }
+
+ [RelayCommand]
+ private async Task UpdateMovie()
+ {
+ if (!string.IsNullOrEmpty(Movie.Title))
+ {
+ try
+ {
+ await _dataService.UpdateMovie(Movie);
+ await Shell.Current.GoToAsync("..");
+ }
+ catch (Exception ex)
+ {
+ await Shell.Current.DisplayAlert("Error", ex.Message, "OK");
+ }
+ }
+ else
+ {
+ await Shell.Current.DisplayAlert("Error", "No title!", "OK");
+ }
+ }
+ }
+}
diff --git a/MoviesTracker/Views/AddMoviePage.xaml b/MoviesTracker/Views/AddMoviePage.xaml
new file mode 100644
index 0000000..8ce6d3f
--- /dev/null
+++ b/MoviesTracker/Views/AddMoviePage.xaml
@@ -0,0 +1,24 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/MoviesTracker/Views/AddMoviePage.xaml.cs b/MoviesTracker/Views/AddMoviePage.xaml.cs
new file mode 100644
index 0000000..4f65e05
--- /dev/null
+++ b/MoviesTracker/Views/AddMoviePage.xaml.cs
@@ -0,0 +1,13 @@
+using MoviesTracker.ViewModels;
+
+namespace MoviesTracker.Views
+{
+ public partial class AddMoviePage : ContentPage
+ {
+ public AddMoviePage(AddMovieViewModel addMovieViewModel)
+ {
+ InitializeComponent();
+ BindingContext = addMovieViewModel;
+ }
+ }
+}
diff --git a/MoviesTracker/Views/LoginPage.xaml b/MoviesTracker/Views/LoginPage.xaml
new file mode 100644
index 0000000..3d6be4f
--- /dev/null
+++ b/MoviesTracker/Views/LoginPage.xaml
@@ -0,0 +1,56 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/MoviesTracker/Views/LoginPage.xaml.cs b/MoviesTracker/Views/LoginPage.xaml.cs
new file mode 100644
index 0000000..373ca49
--- /dev/null
+++ b/MoviesTracker/Views/LoginPage.xaml.cs
@@ -0,0 +1,10 @@
+namespace MoviesTracker.Views
+{
+ public partial class LoginPage : ContentPage
+ {
+ public LoginPage()
+ {
+ InitializeComponent();
+ }
+ }
+}
diff --git a/MoviesTracker/Views/MoviesListingPage.xaml b/MoviesTracker/Views/MoviesListingPage.xaml
new file mode 100644
index 0000000..47a9477
--- /dev/null
+++ b/MoviesTracker/Views/MoviesListingPage.xaml
@@ -0,0 +1,111 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/MoviesTracker/Views/MoviesListingPage.xaml.cs b/MoviesTracker/Views/MoviesListingPage.xaml.cs
new file mode 100644
index 0000000..ac60aef
--- /dev/null
+++ b/MoviesTracker/Views/MoviesListingPage.xaml.cs
@@ -0,0 +1,13 @@
+using MoviesTracker.ViewModels;
+
+namespace MoviesTracker.Views
+{
+ public partial class MoviesListingPage : ContentPage
+ {
+ public MoviesListingPage(MoviesListingViewModel moviesListingViewModel)
+ {
+ InitializeComponent();
+ BindingContext = moviesListingViewModel;
+ }
+ }
+}
diff --git a/MoviesTracker/Views/SignUpPage.xaml b/MoviesTracker/Views/SignUpPage.xaml
new file mode 100644
index 0000000..202ebe0
--- /dev/null
+++ b/MoviesTracker/Views/SignUpPage.xaml
@@ -0,0 +1,16 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/MoviesTracker/Views/SignUpPage.xaml.cs b/MoviesTracker/Views/SignUpPage.xaml.cs
new file mode 100644
index 0000000..e2d8507
--- /dev/null
+++ b/MoviesTracker/Views/SignUpPage.xaml.cs
@@ -0,0 +1,10 @@
+namespace MoviesTracker.Views
+{
+ public partial class SignUpPage : ContentPage
+ {
+ public SignUpPage()
+ {
+ InitializeComponent();
+ }
+ }
+}
diff --git a/MoviesTracker/Views/UpdateMoviePage.xaml b/MoviesTracker/Views/UpdateMoviePage.xaml
new file mode 100644
index 0000000..5eb4950
--- /dev/null
+++ b/MoviesTracker/Views/UpdateMoviePage.xaml
@@ -0,0 +1,24 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/MoviesTracker/Views/UpdateMoviePage.xaml.cs b/MoviesTracker/Views/UpdateMoviePage.xaml.cs
new file mode 100644
index 0000000..b44b7e6
--- /dev/null
+++ b/MoviesTracker/Views/UpdateMoviePage.xaml.cs
@@ -0,0 +1,13 @@
+using MoviesTracker.ViewModels;
+
+namespace MoviesTracker.Views
+{
+ public partial class UpdateMoviePage : ContentPage
+ {
+ public UpdateMoviePage(UpdateMovieViewModel updateMovieViewModel)
+ {
+ InitializeComponent();
+ BindingContext = updateMovieViewModel;
+ }
+ }
+}
diff --git a/MoviesTrackerApp.sln b/MoviesTrackerApp.sln
new file mode 100644
index 0000000..9854815
--- /dev/null
+++ b/MoviesTrackerApp.sln
@@ -0,0 +1,27 @@
+
+Microsoft Visual Studio Solution File, Format Version 12.00
+# Visual Studio Version 17
+VisualStudioVersion = 17.9.34723.18
+MinimumVisualStudioVersion = 10.0.40219.1
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MoviesTracker", "MoviesTracker\MoviesTracker.csproj", "{D2A93F6F-BA89-4955-AA61-D43748D7FDC2}"
+EndProject
+Global
+ GlobalSection(SolutionConfigurationPlatforms) = preSolution
+ Debug|Any CPU = Debug|Any CPU
+ Release|Any CPU = Release|Any CPU
+ EndGlobalSection
+ GlobalSection(ProjectConfigurationPlatforms) = postSolution
+ {D2A93F6F-BA89-4955-AA61-D43748D7FDC2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {D2A93F6F-BA89-4955-AA61-D43748D7FDC2}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {D2A93F6F-BA89-4955-AA61-D43748D7FDC2}.Debug|Any CPU.Deploy.0 = Debug|Any CPU
+ {D2A93F6F-BA89-4955-AA61-D43748D7FDC2}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {D2A93F6F-BA89-4955-AA61-D43748D7FDC2}.Release|Any CPU.Build.0 = Release|Any CPU
+ {D2A93F6F-BA89-4955-AA61-D43748D7FDC2}.Release|Any CPU.Deploy.0 = Release|Any CPU
+ EndGlobalSection
+ GlobalSection(SolutionProperties) = preSolution
+ HideSolutionNode = FALSE
+ EndGlobalSection
+ GlobalSection(ExtensibilityGlobals) = postSolution
+ SolutionGuid = {BC5A079C-DC8F-4218-B383-5063C7362E6A}
+ EndGlobalSection
+EndGlobal