diff --git a/README.md b/README.md index b063b8e..f035280 100644 --- a/README.md +++ b/README.md @@ -63,12 +63,12 @@ After making changes to any entity you need to add a migration and update the da - Update the Roadmap - Git pull master - Open up *subtrack.maui.csproj* - - Set the value of application Id to `com.companyname.subtrack` + - Set the value of application Id to `com.code2gether.subtrack` - Set the value of application Title `subtrack` - Inside Visual Studio set build mode to **Release** - Build the project - Run the project -- The APK file should have been created at this location: *subtrack.MAUI\bin\Release\net6.0-android\com.companyname.subtrack-Signed.apk* +- The APK file should have been created at this location: *subtrack.MAUI\bin\Release\net6.0-android\com.code2gether.subtrack-Signed.apk* - Remove the changes that were made to the *.csproj* file - Upload the APK release to Github - The version should be prefixed with a **"v"** and suffix of (app stage which is currently alpha 2/9-2023) **"-alpha"** diff --git a/docs/MVP mockups.png b/docs/MVP mockups.png deleted file mode 100644 index fc4f193..0000000 Binary files a/docs/MVP mockups.png and /dev/null differ diff --git a/docs/mockups.drawio b/docs/mockups.drawio index fb0da00..4ce3fcb 100644 --- a/docs/mockups.drawio +++ b/docs/mockups.drawio @@ -1,6 +1,456 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - + @@ -232,7 +682,7 @@ - + @@ -317,7 +767,7 @@ - + @@ -611,7 +1061,7 @@ - + diff --git a/docs/mockups.png b/docs/mockups.png index 4d6806e..62fe794 100644 Binary files a/docs/mockups.png and b/docs/mockups.png differ diff --git a/docs/roadmap.drawio b/docs/roadmap.drawio index e0dc4e7..20eb0a8 100644 --- a/docs/roadmap.drawio +++ b/docs/roadmap.drawio @@ -1,6 +1,6 @@ - + @@ -74,12 +74,12 @@ - + - - + + @@ -122,12 +122,12 @@ - + - - + + @@ -142,9 +142,21 @@ - + + + + + + + + + + + + + diff --git a/docs/roadmap.png b/docs/roadmap.png index 63ffc99..d3e742b 100644 Binary files a/docs/roadmap.png and b/docs/roadmap.png differ diff --git a/subtrack.DAL/Entities/Subscription.cs b/subtrack.DAL/Entities/Subscription.cs index c709733..d455214 100644 --- a/subtrack.DAL/Entities/Subscription.cs +++ b/subtrack.DAL/Entities/Subscription.cs @@ -26,6 +26,8 @@ public class Subscription : ICloneable [Range(1, int.MaxValue, ErrorMessage = "Interval has to be greater than 0")] public int BillingInterval { get; set; } + public string PrimaryColor { get; set; } + public object Clone() { return (Subscription)MemberwiseClone(); diff --git a/subtrack.DAL/Migrations/20230916005301_BackgroundColor.Designer.cs b/subtrack.DAL/Migrations/20230916005301_BackgroundColor.Designer.cs new file mode 100644 index 0000000..1572478 --- /dev/null +++ b/subtrack.DAL/Migrations/20230916005301_BackgroundColor.Designer.cs @@ -0,0 +1,101 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using subtrack.DAL; + +#nullable disable + +namespace subtrack.DAL.Migrations +{ + [DbContext(typeof(SubtrackDbContext))] + [Migration("20230916005301_BackgroundColor")] + partial class BackgroundColor + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder.HasAnnotation("ProductVersion", "7.0.10"); + + modelBuilder.Entity("subtrack.DAL.Entities.SettingsBase", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("settings_type") + .IsRequired() + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.ToTable("Settings"); + + b.HasDiscriminator("settings_type").HasValue("SettingsBase"); + }); + + modelBuilder.Entity("subtrack.DAL.Entities.Subscription", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("BackgroundColor") + .IsRequired() + .HasColumnType("TEXT") + .HasDefaultValue("#282828"); + + b.Property("BillingInterval") + .HasColumnType("INTEGER") + .HasDefaultValue(1); + + b.Property("BillingOccurrence") + .HasColumnType("INTEGER") + .HasDefaultValue(1); + + b.Property("Cost") + .HasColumnType("TEXT"); + + b.Property("Description") + .HasColumnType("TEXT"); + + b.Property("FirstPaymentDay") + .HasColumnType("INTEGER"); + + b.Property("IsAutoPaid") + .HasColumnType("INTEGER"); + + b.Property("LastPayment") + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.ToTable("Subscriptions"); + }); + + modelBuilder.Entity("subtrack.DAL.Entities.DateTimeSetting", b => + { + b.HasBaseType("subtrack.DAL.Entities.SettingsBase"); + + b.Property("Value") + .HasColumnType("TEXT"); + + b.HasDiscriminator().HasValue("DateTimeSetting"); + + b.HasData( + new + { + Id = "LastAutoPaymentTimeStamp" + }); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/subtrack.DAL/Migrations/20230916005301_BackgroundColor.cs b/subtrack.DAL/Migrations/20230916005301_BackgroundColor.cs new file mode 100644 index 0000000..64c57b1 --- /dev/null +++ b/subtrack.DAL/Migrations/20230916005301_BackgroundColor.cs @@ -0,0 +1,29 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace subtrack.DAL.Migrations +{ + /// + public partial class BackgroundColor : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AddColumn( + name: "BackgroundColor", + table: "Subscriptions", + type: "TEXT", + nullable: false, + defaultValue: "#282828"); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropColumn( + name: "BackgroundColor", + table: "Subscriptions"); + } + } +} diff --git a/subtrack.DAL/Migrations/20230917120115_PrimaryColor.Designer.cs b/subtrack.DAL/Migrations/20230917120115_PrimaryColor.Designer.cs new file mode 100644 index 0000000..bc8a0b8 --- /dev/null +++ b/subtrack.DAL/Migrations/20230917120115_PrimaryColor.Designer.cs @@ -0,0 +1,101 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using subtrack.DAL; + +#nullable disable + +namespace subtrack.DAL.Migrations +{ + [DbContext(typeof(SubtrackDbContext))] + [Migration("20230917120115_PrimaryColor")] + partial class PrimaryColor + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder.HasAnnotation("ProductVersion", "7.0.10"); + + modelBuilder.Entity("subtrack.DAL.Entities.SettingsBase", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("settings_type") + .IsRequired() + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.ToTable("Settings"); + + b.HasDiscriminator("settings_type").HasValue("SettingsBase"); + }); + + modelBuilder.Entity("subtrack.DAL.Entities.Subscription", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("BillingInterval") + .HasColumnType("INTEGER") + .HasDefaultValue(1); + + b.Property("BillingOccurrence") + .HasColumnType("INTEGER") + .HasDefaultValue(1); + + b.Property("Cost") + .HasColumnType("TEXT"); + + b.Property("Description") + .HasColumnType("TEXT"); + + b.Property("FirstPaymentDay") + .HasColumnType("INTEGER"); + + b.Property("IsAutoPaid") + .HasColumnType("INTEGER"); + + b.Property("LastPayment") + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property("PrimaryColor") + .IsRequired() + .HasColumnType("TEXT") + .HasDefaultValue("#282828"); + + b.HasKey("Id"); + + b.ToTable("Subscriptions"); + }); + + modelBuilder.Entity("subtrack.DAL.Entities.DateTimeSetting", b => + { + b.HasBaseType("subtrack.DAL.Entities.SettingsBase"); + + b.Property("Value") + .HasColumnType("TEXT"); + + b.HasDiscriminator().HasValue("DateTimeSetting"); + + b.HasData( + new + { + Id = "LastAutoPaymentTimeStamp" + }); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/subtrack.DAL/Migrations/20230917120115_PrimaryColor.cs b/subtrack.DAL/Migrations/20230917120115_PrimaryColor.cs new file mode 100644 index 0000000..1558067 --- /dev/null +++ b/subtrack.DAL/Migrations/20230917120115_PrimaryColor.cs @@ -0,0 +1,28 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace subtrack.DAL.Migrations +{ + /// + public partial class PrimaryColor : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.RenameColumn( + name: "BackgroundColor", + table: "Subscriptions", + newName: "PrimaryColor"); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.RenameColumn( + name: "PrimaryColor", + table: "Subscriptions", + newName: "BackgroundColor"); + } + } +} diff --git a/subtrack.DAL/Migrations/SubtrackDbContextModelSnapshot.cs b/subtrack.DAL/Migrations/SubtrackDbContextModelSnapshot.cs index debab0c..db21001 100644 --- a/subtrack.DAL/Migrations/SubtrackDbContextModelSnapshot.cs +++ b/subtrack.DAL/Migrations/SubtrackDbContextModelSnapshot.cs @@ -40,12 +40,10 @@ protected override void BuildModel(ModelBuilder modelBuilder) .HasColumnType("INTEGER"); b.Property("BillingInterval") - .ValueGeneratedOnAdd() .HasColumnType("INTEGER") .HasDefaultValue(1); b.Property("BillingOccurrence") - .ValueGeneratedOnAdd() .HasColumnType("INTEGER") .HasDefaultValue(1); @@ -66,9 +64,14 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.Property("Name") .IsRequired() - .HasMaxLength(12) + .HasMaxLength(50) .HasColumnType("TEXT"); + b.Property("PrimaryColor") + .IsRequired() + .HasColumnType("TEXT") + .HasDefaultValue("#282828"); + b.HasKey("Id"); b.ToTable("Subscriptions"); diff --git a/subtrack.DAL/SubtrackDbContext.cs b/subtrack.DAL/SubtrackDbContext.cs index 4575fed..3bc79bb 100644 --- a/subtrack.DAL/SubtrackDbContext.cs +++ b/subtrack.DAL/SubtrackDbContext.cs @@ -23,6 +23,7 @@ protected override void OnModelCreating(ModelBuilder modelBuilder) { entity.Property(e => e.BillingOccurrence).HasDefaultValue(BillingOccurrence.Month).ValueGeneratedNever(); entity.Property(e => e.BillingInterval).HasDefaultValue(1).ValueGeneratedNever(); + entity.Property(e => e.PrimaryColor).HasDefaultValue("#282828").ValueGeneratedNever(); }); } } diff --git a/subtrack.MAUI/MauiProgram.cs b/subtrack.MAUI/MauiProgram.cs index 37bc39a..3fab0e9 100644 --- a/subtrack.MAUI/MauiProgram.cs +++ b/subtrack.MAUI/MauiProgram.cs @@ -4,6 +4,7 @@ using subtrack.MAUI.Services; using System.Runtime.CompilerServices; using subtrack.MAUI.Shared.JsInterop; +using subtrack.MAUI.Utilities; [assembly: InternalsVisibleTo("subtrack.Tests")] @@ -57,12 +58,13 @@ private static void SeedDb(SubtrackDbContext dbContext) dbContext.Database.Migrate(); var todayLastMonth = DateTime.Now.AddMonths(-1); + var availableBackgroundColors = CssUtil.AvailableBackgroundColors; dbContext.Subscriptions.AddRange( - new DAL.Entities.Subscription() { Name = "paramount", LastPayment = todayLastMonth.AddDays(-1), FirstPaymentDay = todayLastMonth.AddDays(-1).Day, Cost = 3m, BillingOccurrence = DAL.Entities.BillingOccurrence.Week, BillingInterval = 2 }, - new DAL.Entities.Subscription() { Name = "Disney+", LastPayment = todayLastMonth, FirstPaymentDay = todayLastMonth.Day, Cost = 3m, BillingOccurrence = DAL.Entities.BillingOccurrence.Month, BillingInterval = 1 }, - new DAL.Entities.Subscription() { Name = "Netflix", LastPayment = DateTime.Now.AddDays(-1), FirstPaymentDay = DateTime.Now.AddDays(-1).Day, IsAutoPaid = true, Description = "family plan", Cost = 10, BillingOccurrence = DAL.Entities.BillingOccurrence.Month, BillingInterval = 1 }, - new DAL.Entities.Subscription() { Name = "hbo", LastPayment = DateTime.Now, FirstPaymentDay = DateTime.Now.Day, Cost = 1.5m, BillingOccurrence = DAL.Entities.BillingOccurrence.Year, BillingInterval = 1 }, - new DAL.Entities.Subscription() { Name = "hulu", LastPayment = DateTime.Now.AddMonths(-2), FirstPaymentDay = 1, Cost = 1.5m, IsAutoPaid = true, BillingOccurrence = DAL.Entities.BillingOccurrence.Month, BillingInterval = 3 } + new DAL.Entities.Subscription() { Name = "paramount", LastPayment = todayLastMonth.AddDays(-1), FirstPaymentDay = todayLastMonth.AddDays(-1).Day, Cost = 3m, BillingOccurrence = DAL.Entities.BillingOccurrence.Week, BillingInterval = 2, PrimaryColor = availableBackgroundColors.First() }, + new DAL.Entities.Subscription() { Name = "Disney+", LastPayment = todayLastMonth, FirstPaymentDay = todayLastMonth.Day, Cost = 3m, BillingOccurrence = DAL.Entities.BillingOccurrence.Month, BillingInterval = 1, PrimaryColor = availableBackgroundColors.First() }, + new DAL.Entities.Subscription() { Name = "Netflix", LastPayment = DateTime.Now.AddDays(-1), FirstPaymentDay = DateTime.Now.AddDays(-1).Day, IsAutoPaid = true, Description = "family plan", Cost = 10, BillingOccurrence = DAL.Entities.BillingOccurrence.Month, BillingInterval = 1, PrimaryColor = availableBackgroundColors.First() }, + new DAL.Entities.Subscription() { Name = "hbo", LastPayment = DateTime.Now, FirstPaymentDay = DateTime.Now.Day, Cost = 1.5m, BillingOccurrence = DAL.Entities.BillingOccurrence.Year, BillingInterval = 1, PrimaryColor = availableBackgroundColors.First() }, + new DAL.Entities.Subscription() { Name = "hulu", LastPayment = DateTime.Now.AddMonths(-2), FirstPaymentDay = 1, Cost = 1.5m, IsAutoPaid = true, BillingOccurrence = DAL.Entities.BillingOccurrence.Month, BillingInterval = 3, PrimaryColor = availableBackgroundColors.First() } ); dbContext.SaveChanges(); diff --git a/subtrack.MAUI/Pages/Index.razor b/subtrack.MAUI/Pages/Index.razor index d92fddc..0fa6270 100644 --- a/subtrack.MAUI/Pages/Index.razor +++ b/subtrack.MAUI/Pages/Index.razor @@ -23,7 +23,7 @@
@foreach (var sub in _subscriptionResponses) { -
Navigator.NavigateTo($"/details/{sub.Id}?ReturnUrl={Navigator.Uri}")) id="subscription-card" class="card border-primary mb-3 clickable" style="max-width: 20rem;"> +
Navigator.NavigateTo($"/details/{sub.Id}?ReturnUrl={Navigator.Uri}")) id="subscription-card" class="card border-primary mb-3 clickable" style="max-width: 20rem;background-color:@sub.PrimaryColor">
@sub.Name
@@ -36,7 +36,7 @@ }
-
@GetDueDaysText(sub.DueDays)
+
@GetDueDaysText(sub.DueDays)
diff --git a/subtrack.MAUI/Pages/SubscriptionDetails.razor b/subtrack.MAUI/Pages/SubscriptionDetails.razor index c998255..509360f 100644 --- a/subtrack.MAUI/Pages/SubscriptionDetails.razor +++ b/subtrack.MAUI/Pages/SubscriptionDetails.razor @@ -15,7 +15,7 @@ else {
-
+
@@ -26,8 +26,9 @@ else
-
- +
+
+
@($"{_subscription.Cost:C}")
   @if (_subscription.IsAutoPaid) @@ -36,15 +37,17 @@ else }
+
-
+
- -
@_nextPaymentDate.ToString("MMMM dd, yyyy", _usCulture)
+ +
@_nextPaymentDate.ToString("MMMM dd, yyyy", Constants.UsCulture)
@_dueTimeText
-
+
+
-
- +
+
+
- @_subscription.LastPayment.ToString("MMMM dd, yyyy", _usCulture) + @_subscription.LastPayment.ToString("MMMM dd, yyyy", Constants.UsCulture)
-
- +
+
+
@($"~{Math.Round(_subscriptionsCost, 2):C}")
- @if (!string.IsNullOrWhiteSpace(_subscription.Description)) - { -
- @_subscription.Description -
- } -
+
+
+ @if (!string.IsNullOrWhiteSpace(_subscription.Description)) + { +
+ @_subscription.Description +
+ }
@@ -24,28 +24,39 @@
- - -
-
- - -
-
- -
-
every - +
+
+
+ + +
+
+ + +
+
+ + + + @foreach (var backgroundColor in backgroundColorOptions) + { + + } + +
+
+ +

@@ -69,6 +80,8 @@ BillingOccurrence = BillingOccurrence.Month }; + private IEnumerable backgroundColorOptions = CssUtil.AvailableBackgroundColors; + protected override void OnInitialized() { RegionInfo ri = new RegionInfo(System.Threading.Thread.CurrentThread.CurrentUICulture.LCID); @@ -81,6 +94,9 @@ if (Id != null) { _subscription = await SubscriptionService.GetByIdIfExists(Id.Value); + } else + { + _subscription.PrimaryColor = backgroundColorOptions.First(); } } @@ -111,4 +127,6 @@ { return billingOccurrence.ToQuantity(_subscription.BillingInterval, showQuantityAs: ShowQuantityAs.None); } + + private void UpdateBackgroundColor(ChangeEventArgs e) => _subscription.PrimaryColor = e.Value!.ToString(); } \ No newline at end of file diff --git a/subtrack.MAUI/Responses/SubscriptionResponse.cs b/subtrack.MAUI/Responses/SubscriptionResponse.cs index 0f5c44e..04fd619 100644 --- a/subtrack.MAUI/Responses/SubscriptionResponse.cs +++ b/subtrack.MAUI/Responses/SubscriptionResponse.cs @@ -10,5 +10,6 @@ public class SubscriptionResponse public decimal Cost { get; set; } public DateTime LastPayment { get; set; } public int DueDays { get; set; } + public string PrimaryColor { get; set; } } } diff --git a/subtrack.MAUI/Services/SubscriptionService.cs b/subtrack.MAUI/Services/SubscriptionService.cs index df98aa4..cbca0bf 100644 --- a/subtrack.MAUI/Services/SubscriptionService.cs +++ b/subtrack.MAUI/Services/SubscriptionService.cs @@ -42,6 +42,7 @@ public async Task Update(Subscription subscriptionToUpdate) sub.IsAutoPaid = subscriptionToUpdate.IsAutoPaid; sub.BillingOccurrence = subscriptionToUpdate.BillingOccurrence; sub.BillingInterval = subscriptionToUpdate.BillingInterval; + sub.PrimaryColor = subscriptionToUpdate.PrimaryColor; AutoPay(sub); sub.Cost = subscriptionToUpdate.Cost; diff --git a/subtrack.MAUI/Shared/Components/BackButton.razor b/subtrack.MAUI/Shared/Components/BackButton.razor index 85dc08f..f40a8e4 100644 --- a/subtrack.MAUI/Shared/Components/BackButton.razor +++ b/subtrack.MAUI/Shared/Components/BackButton.razor @@ -1,6 +1,6 @@ @inject NavigationManager NavigationManager - diff --git a/subtrack.MAUI/Shared/Components/EditButton.razor b/subtrack.MAUI/Shared/Components/EditButton.razor index d297978..ba9fe0e 100644 --- a/subtrack.MAUI/Shared/Components/EditButton.razor +++ b/subtrack.MAUI/Shared/Components/EditButton.razor @@ -1,6 +1,6 @@ @inject NavigationManager NavigationManager - diff --git a/subtrack.MAUI/Shared/Components/SubscriptionMonthItem.razor b/subtrack.MAUI/Shared/Components/SubscriptionMonthItem.razor index ae86b12..b8d2f42 100644 --- a/subtrack.MAUI/Shared/Components/SubscriptionMonthItem.razor +++ b/subtrack.MAUI/Shared/Components/SubscriptionMonthItem.razor @@ -11,14 +11,17 @@ @foreach (var sub in SubscriptionMonth.Subscriptions) { -
Navigator.NavigateTo($"/details/{sub.Id}?ReturnUrl={Navigator.Uri}")) class="card mb-2 clickable" style="max-width: 20rem;"> +
Navigator.NavigateTo($"/details/{sub.Id}?ReturnUrl={Navigator.Uri}")) class="card mb-2 clickable" style="max-width: 20rem;background-color:@sub.PrimaryColor">
@sub.Name
@($"{sub.Cost:C}")
+ @{ + int daysUntilDue = (int)(sub.LastPayment - DateTime.Now.Date).TotalDays; + }
-
@($"{sub.LastPayment.Day}{GetDateSuffix(sub.LastPayment.Day)}")
+
@($"{sub.LastPayment.Day}{GetDateSuffix(sub.LastPayment.Day)}")
@if (sub.IsAutoPaid) { diff --git a/subtrack.MAUI/Shared/NavBar.razor b/subtrack.MAUI/Shared/NavBar.razor index 95fe54f..13af47b 100644 --- a/subtrack.MAUI/Shared/NavBar.razor +++ b/subtrack.MAUI/Shared/NavBar.razor @@ -1,24 +1,21 @@  -