diff --git a/.github/workflows/dotnet.yml b/.github/workflows/dotnet.yml index 4ba49ea..c37f981 100644 --- a/.github/workflows/dotnet.yml +++ b/.github/workflows/dotnet.yml @@ -15,14 +15,25 @@ jobs: runs-on: windows-2022 steps: + - uses: actions/setup-java@v2 + with: + distribution: 'microsoft' + java-version: '11' + - uses: actions/checkout@v3 - name: Setup .NET uses: actions/setup-dotnet@v3 with: - dotnet-version: 6.0.x + dotnet-version: 8.0.x + + - name: Restore workload + run: dotnet workload restore + - name: Restore dependencies run: dotnet restore + - name: Build run: dotnet build --no-restore + - name: Test run: dotnet test --no-build --verbosity normal diff --git a/README.md b/README.md index 90a7fd3..8c1e2bc 100644 --- a/README.md +++ b/README.md @@ -16,12 +16,12 @@ Keep track of what subscriptions you have, when they have to be paid and how muc ## Running the project The project itself currently does not require any configuration at all. On startup data is seeded to a locally stored sqlite database. -Before preceding make sure you have installed ".NET Multi-platform App UI development" in the visual studio installer +Before preceding make sure you have installed ".NET Multi-platform App UI development" in the visual studio installer. Also ensure you have a .NET 8 SDK installed `dotnet --list-sdks`. 1. To begin with we only have a mobile app and we focus on making the design and functionality work for Android. You do not need an android phone to actually run the project but you do need to enable **Hyper-V** on your PC https://learn.microsoft.com/en-us/xamarin/android/get-started/installation/android-emulator/hardware-acceleration?pivots=windows. You also need to enable developer mode on your PC https://www.c-sharpcorner.com/blogs/dep0100-please-ensure-that-target-device-has-developer-mode-enabled. -2. After enabling Hyper-V you can run the project with the **Android emulator** (built in to VS) - I am using a **Pixel 5 - API 31 (Android 12.0)** since that was default. You can add the device by opening the **Android Device Manager** inside VS +2. After enabling Hyper-V you can run the project with the **Android emulator** (built in to VS) - I am using a **Pixel 5 - API 33 (Android 13.0)** since that was default. You can add the device by opening the **Android Device Manager** inside VS *Note that you can run the project without the android emulator as long as you won't be making any UI changes, alternatively tell someone else to verify the UI looks ok* @@ -74,7 +74,6 @@ After making changes to any entity you need to add a migration and update the da - 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"** - Should contain notable changes/features that were added - - Should display the contributors - + # Roadmap 2023 ![Roadmap 2023](docs/roadmap.png?) diff --git a/docs/roadmap.drawio b/docs/roadmap.drawio index e164e43..943783d 100644 --- a/docs/roadmap.drawio +++ b/docs/roadmap.drawio @@ -1,6 +1,6 @@ - + @@ -74,7 +74,7 @@ - + @@ -113,7 +113,7 @@ - + @@ -122,7 +122,7 @@ - + @@ -148,8 +148,8 @@ - - + + @@ -157,9 +157,15 @@ - + + + + + + + diff --git a/docs/roadmap.png b/docs/roadmap.png index d3e742b..8be35d4 100644 Binary files a/docs/roadmap.png and b/docs/roadmap.png differ diff --git a/global.json b/global.json new file mode 100644 index 0000000..f1c455d --- /dev/null +++ b/global.json @@ -0,0 +1,6 @@ +{ + "sdk": { + "version": "7.0.100", + "rollForward": "latestMajor" + } +} \ No newline at end of file diff --git a/subtrack.DAL/Entities/DateTimeSetting.cs b/subtrack.DAL/Entities/DateTimeSetting.cs index e37c793..1ab07d2 100644 --- a/subtrack.DAL/Entities/DateTimeSetting.cs +++ b/subtrack.DAL/Entities/DateTimeSetting.cs @@ -3,6 +3,7 @@ public class DateTimeSetting : SettingsBase { public const string LastAutoPaymentTimeStampKey = "LastAutoPaymentTimeStamp"; + public const string LastSubscriptionExportTimeStampKey = "LastSubscriptionExportTimeStamp"; public DateTime? Value { get; set; } } } diff --git a/subtrack.DAL/Migrations/20231211183205_LastSubscriptionExportTimeStamp.Designer.cs b/subtrack.DAL/Migrations/20231211183205_LastSubscriptionExportTimeStamp.Designer.cs new file mode 100644 index 0000000..02912f7 --- /dev/null +++ b/subtrack.DAL/Migrations/20231211183205_LastSubscriptionExportTimeStamp.Designer.cs @@ -0,0 +1,115 @@ +// +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("20231211183205_LastSubscriptionExportTimeStamp")] + partial class LastSubscriptionExportTimeStamp + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder.HasAnnotation("ProductVersion", "7.0.14"); + + 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"); + + b.UseTphMappingStrategy(); + }); + + 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("Icon") + .HasColumnType("TEXT"); + + 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.Property("SecondaryColor") + .IsRequired() + .HasColumnType("TEXT") + .HasDefaultValue("#2a9fd6"); + + 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" + }, + new + { + Id = "LastSubscriptionExportTimeStamp" + }); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/subtrack.DAL/Migrations/20231211183205_LastSubscriptionExportTimeStamp.cs b/subtrack.DAL/Migrations/20231211183205_LastSubscriptionExportTimeStamp.cs new file mode 100644 index 0000000..e5bb910 --- /dev/null +++ b/subtrack.DAL/Migrations/20231211183205_LastSubscriptionExportTimeStamp.cs @@ -0,0 +1,28 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace subtrack.DAL.Migrations +{ + /// + public partial class LastSubscriptionExportTimeStamp : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.InsertData( + table: "Settings", + columns: new[] { "Id", "Value", "settings_type" }, + values: new object[] { "LastSubscriptionExportTimeStamp", null, "DateTimeSetting" }); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DeleteData( + table: "Settings", + keyColumn: "Id", + keyValue: "LastSubscriptionExportTimeStamp"); + } + } +} diff --git a/subtrack.DAL/Migrations/SubtrackDbContextModelSnapshot.cs b/subtrack.DAL/Migrations/SubtrackDbContextModelSnapshot.cs index 242767c..8e5e2e0 100644 --- a/subtrack.DAL/Migrations/SubtrackDbContextModelSnapshot.cs +++ b/subtrack.DAL/Migrations/SubtrackDbContextModelSnapshot.cs @@ -15,7 +15,7 @@ partial class SubtrackDbContextModelSnapshot : ModelSnapshot protected override void BuildModel(ModelBuilder modelBuilder) { #pragma warning disable 612, 618 - modelBuilder.HasAnnotation("ProductVersion", "6.0.18"); + modelBuilder.HasAnnotation("ProductVersion", "7.0.14"); modelBuilder.Entity("subtrack.DAL.Entities.SettingsBase", b => { @@ -31,6 +31,8 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.ToTable("Settings"); b.HasDiscriminator("settings_type").HasValue("SettingsBase"); + + b.UseTphMappingStrategy(); }); modelBuilder.Entity("subtrack.DAL.Entities.Subscription", b => @@ -98,6 +100,10 @@ protected override void BuildModel(ModelBuilder modelBuilder) new { Id = "LastAutoPaymentTimeStamp" + }, + new + { + Id = "LastSubscriptionExportTimeStamp" }); }); #pragma warning restore 612, 618 diff --git a/subtrack.DAL/SubtrackDbContext.cs b/subtrack.DAL/SubtrackDbContext.cs index 108757f..b7ec703 100644 --- a/subtrack.DAL/SubtrackDbContext.cs +++ b/subtrack.DAL/SubtrackDbContext.cs @@ -18,6 +18,7 @@ protected override void OnModelCreating(ModelBuilder modelBuilder) .HasValue(nameof(DateTimeSetting)); modelBuilder.Entity().HasData(new DateTimeSetting { Id = DateTimeSetting.LastAutoPaymentTimeStampKey, Value = null }); + modelBuilder.Entity().HasData(new DateTimeSetting { Id = DateTimeSetting.LastSubscriptionExportTimeStampKey, Value = null }); modelBuilder.Entity(entity => { diff --git a/subtrack.DAL/subtrack.DAL.csproj b/subtrack.DAL/subtrack.DAL.csproj index a39f6f1..4eba25b 100644 --- a/subtrack.DAL/subtrack.DAL.csproj +++ b/subtrack.DAL/subtrack.DAL.csproj @@ -1,15 +1,15 @@  - net6.0 + net7.0 enable enable - - - + + + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/subtrack.MAUI/MauiProgram.cs b/subtrack.MAUI/MauiProgram.cs index 6a42661..2de976d 100644 --- a/subtrack.MAUI/MauiProgram.cs +++ b/subtrack.MAUI/MauiProgram.cs @@ -54,6 +54,7 @@ public static IServiceCollection AddSubtrackServices(this IServiceCollection ser services .AddDbContext(opt => opt.UseSqlite(dbConnectionString)) .AddScoped() + .AddScoped() .AddScoped() .AddScoped() .AddScoped() diff --git a/subtrack.MAUI/Pages/Settings.razor b/subtrack.MAUI/Pages/Settings.razor new file mode 100644 index 0000000..ff4676c --- /dev/null +++ b/subtrack.MAUI/Pages/Settings.razor @@ -0,0 +1,70 @@ +@page "/settings" + +@inject ISubscriptionsImporter subscriptionImporter +@inject ISubscriptionService subscriptionService +@inject ISettingsService settingsService + +@using CsvHelper + +
+
Import
+
+ +
+
+ +

@_importResult

+ +
+
+ Last Export: @(_lastSubscriptionExportTimeStamp.Value?.Humanize() ?? string.Empty) +
+
+ +
+
+ +@code { + private string _importResult = string.Empty; + private DateTimeSetting _lastSubscriptionExportTimeStamp = new(); + + protected async override Task OnInitializedAsync() + { + _lastSubscriptionExportTimeStamp = await settingsService.GetByIdAsync(DateTimeSetting.LastSubscriptionExportTimeStampKey); + } + + private async Task ExportSubscriptions() + { + using var csv = new StringWriter(); + using var csvWriter = new CsvWriter(csv, CultureInfo.InvariantCulture); + csvWriter.Context.RegisterClassMap(); + + var subscriptions = await subscriptionService.GetAllAsync(); + await csvWriter.WriteRecordsAsync(subscriptions); + + var dateNow = DateTime.Now; + await Share.Default.RequestAsync(new ShareTextRequest + { + Title = "subtrack subs", + Subject = $"subtrack-subs_{dateNow:dd-MM-yyyy}.csv", + Text = csv.ToString() + }); + + _lastSubscriptionExportTimeStamp.Value = DateTime.Now; + await settingsService.UpdateAsync(_lastSubscriptionExportTimeStamp); + } + + private async Task ImportSubscriptions(InputFileChangeEventArgs e) + { + try + { + var maxFileSizeInBytes = (long)Math.Round(1.Megabytes().Bytes); + var importedSubscriptions = await subscriptionImporter.ImportFromCsvAsync(e.File.OpenReadStream(maxAllowedSize: maxFileSizeInBytes)); + _importResult = $"imported {importedSubscriptions.Count} subscriptions"; + } + catch (Exception ex) + { + _importResult = ex.Message; + } + } +} diff --git a/subtrack.MAUI/Pages/SubscriptionDetails.razor b/subtrack.MAUI/Pages/SubscriptionDetails.razor index 19eeb96..16e8c6d 100644 --- a/subtrack.MAUI/Pages/SubscriptionDetails.razor +++ b/subtrack.MAUI/Pages/SubscriptionDetails.razor @@ -1,5 +1,4 @@ @page "/details/{Id:int}" -@using System.Globalization; @using System.Text; @using subtrack.MAUI.Utilities; @using subtrack.MAUI.Shared.Components diff --git a/subtrack.MAUI/Platforms/Android/Resources/values/colors.xml b/subtrack.MAUI/Platforms/Android/Resources/values/colors.xml index c04d749..d7b8bf0 100644 --- a/subtrack.MAUI/Platforms/Android/Resources/values/colors.xml +++ b/subtrack.MAUI/Platforms/Android/Resources/values/colors.xml @@ -1,6 +1,6 @@ #512BD4 - #2B0B98 + #000000 #2B0B98 \ No newline at end of file diff --git a/subtrack.MAUI/Services/Abstractions/ISubscriptionService.cs b/subtrack.MAUI/Services/Abstractions/ISubscriptionService.cs index 58eef58..bba2a4d 100644 --- a/subtrack.MAUI/Services/Abstractions/ISubscriptionService.cs +++ b/subtrack.MAUI/Services/Abstractions/ISubscriptionService.cs @@ -7,6 +7,7 @@ public interface ISubscriptionService { Task> GetAllAsync(GetSubscriptionsFilter? filter = null); Task CreateSubscriptionAsync(Subscription subscriptionToCreate); + Task> CreateSubscriptionsAsync(IEnumerable subscriptionsToCreate); Task GetByIdIfExists(int id); Task Delete(int id); Task Update(Subscription subscriptionToUpdate); diff --git a/subtrack.MAUI/Services/Abstractions/ISubscriptionsImporter.cs b/subtrack.MAUI/Services/Abstractions/ISubscriptionsImporter.cs new file mode 100644 index 0000000..426a7a7 --- /dev/null +++ b/subtrack.MAUI/Services/Abstractions/ISubscriptionsImporter.cs @@ -0,0 +1,8 @@ +using subtrack.DAL.Entities; + +namespace subtrack.MAUI.Services.Abstractions; + +public interface ISubscriptionsImporter +{ + Task> ImportFromCsvAsync(Stream content); +} diff --git a/subtrack.MAUI/Services/SubscriptionService.cs b/subtrack.MAUI/Services/SubscriptionService.cs index 692ca92..bf3dbad 100644 --- a/subtrack.MAUI/Services/SubscriptionService.cs +++ b/subtrack.MAUI/Services/SubscriptionService.cs @@ -66,15 +66,26 @@ public async Task Delete(int id) public async Task CreateSubscriptionAsync(Subscription subscriptionToCreate) { - subscriptionToCreate.LastPayment = subscriptionToCreate.LastPayment.Date; - subscriptionToCreate.FirstPaymentDay = subscriptionToCreate.LastPayment.Day; - AutoPay(subscriptionToCreate); + SetupNewSubscription(subscriptionToCreate); await _context.Subscriptions.AddAsync(subscriptionToCreate); await _context.SaveChangesAsync(); - return subscriptionToCreate; } + public async Task> CreateSubscriptionsAsync(IEnumerable subscriptionsToCreate) + { + var newSubscriptions = subscriptionsToCreate.Select(s => + { + SetupNewSubscription(s); + return s; + }).ToArray(); + + await _context.Subscriptions.AddRangeAsync(newSubscriptions); + await _context.SaveChangesAsync(); + + return newSubscriptions; + } + public async Task MarkNextPaymentAsPaidAsync(int subscriptionId) { var sub = await GetByIdAsync(subscriptionId); @@ -93,6 +104,12 @@ public async Task AutoPayAsync(int subscriptionId) return subscription; } + private void SetupNewSubscription(Subscription subscriptionToCreate) + { + subscriptionToCreate.LastPayment = subscriptionToCreate.LastPayment.Date; + subscriptionToCreate.FirstPaymentDay = subscriptionToCreate.LastPayment.Day; + AutoPay(subscriptionToCreate); + } private void AutoPay(Subscription subscription) { if (!subscription.IsAutoPaid) diff --git a/subtrack.MAUI/Services/SubscriptionsImporter.cs b/subtrack.MAUI/Services/SubscriptionsImporter.cs new file mode 100644 index 0000000..bc5b551 --- /dev/null +++ b/subtrack.MAUI/Services/SubscriptionsImporter.cs @@ -0,0 +1,31 @@ +using CsvHelper; +using subtrack.DAL.Entities; +using subtrack.MAUI.Services.Abstractions; +using subtrack.MAUI.Utilities; +using System.Globalization; + +namespace subtrack.MAUI.Services; +internal class SubscriptionsImporter : ISubscriptionsImporter +{ + private readonly ISubscriptionService _subscriptionService; + + public SubscriptionsImporter(ISubscriptionService subscriptionService) + { + _subscriptionService = subscriptionService; + } + + public async Task> ImportFromCsvAsync(Stream content) + { + using var csv = new StreamReader(content); + using var csvReader = new CsvReader(csv, CultureInfo.InvariantCulture); + csvReader.Context.RegisterClassMap(); + + var subscriptionsToImport = new List(); + await foreach (var subscription in csvReader.GetRecordsAsync()) + { + subscriptionsToImport.Add(subscription); + } + + return await _subscriptionService.CreateSubscriptionsAsync(subscriptionsToImport); + } +} diff --git a/subtrack.MAUI/Shared/NavBar.razor b/subtrack.MAUI/Shared/NavBar.razor index 13af47b..c7e3a13 100644 --- a/subtrack.MAUI/Shared/NavBar.razor +++ b/subtrack.MAUI/Shared/NavBar.razor @@ -17,5 +17,10 @@ + \ No newline at end of file diff --git a/subtrack.MAUI/Utilities/CsvMappings.cs b/subtrack.MAUI/Utilities/CsvMappings.cs new file mode 100644 index 0000000..f93fc68 --- /dev/null +++ b/subtrack.MAUI/Utilities/CsvMappings.cs @@ -0,0 +1,14 @@ +using CsvHelper.Configuration; +using subtrack.DAL.Entities; +using System.Globalization; + +namespace subtrack.MAUI.Utilities; + +public class SubscriptionCsvMapping : ClassMap +{ + public SubscriptionCsvMapping() + { + AutoMap(CultureInfo.InvariantCulture); + Map(m => m.Id).Ignore(); + } +} diff --git a/subtrack.MAUI/_Imports.razor b/subtrack.MAUI/_Imports.razor index ccaad2c..1113622 100644 --- a/subtrack.MAUI/_Imports.razor +++ b/subtrack.MAUI/_Imports.razor @@ -1,4 +1,5 @@ @using System.Net.Http +@using System.Globalization; @using Microsoft.AspNetCore.Components.Forms @using Microsoft.AspNetCore.Components.Routing @using Microsoft.AspNetCore.Components.Web diff --git a/subtrack.MAUI/subtrack.MAUI.csproj b/subtrack.MAUI/subtrack.MAUI.csproj index 164a815..574ba4a 100644 --- a/subtrack.MAUI/subtrack.MAUI.csproj +++ b/subtrack.MAUI/subtrack.MAUI.csproj @@ -1,11 +1,11 @@  - net6.0-android;net6.0-ios;net6.0-maccatalyst;net6.0 - $(TargetFrameworks);net6.0-windows10.0.19041.0 + net7.0-android;net7.0 + $(TargetFrameworks);net7.0-windows10.0.19041.0 - Exe + Exe subtrack.MAUI true enable @@ -56,6 +56,7 @@
+ diff --git a/subtrack.Tests/Integration/SubscriptionImporterTests.cs b/subtrack.Tests/Integration/SubscriptionImporterTests.cs new file mode 100644 index 0000000..ffbab8e --- /dev/null +++ b/subtrack.Tests/Integration/SubscriptionImporterTests.cs @@ -0,0 +1,55 @@ +using CsvHelper.TypeConversion; +using System.Text; + +namespace subtrack.Tests.Integration; + +public class SubscriptionImporterTests : IntegrationTestBase +{ + private ISubscriptionsImporter _sut; + + public SubscriptionImporterTests() + { + _sut = _serviceProvider.GetRequiredService(); + } + + [Fact] + public async Task ImportCsv_InvalidCsv_ShouldThrow() + { + var csvText = @"Name,Description,IsAutoPaid,Cost,FirstPaymentDay,LastPayment,BillingOccurrence,BillingInterval,PrimaryColor,Icon,SecondaryColor +Subscription1,Description1,True,29.99,1,2023-12-01,Monthly,1,,,,,, +"; + var csv = new MemoryStream(Encoding.UTF8.GetBytes(csvText)); + + await Assert.ThrowsAsync(async () => await _sut.ImportFromCsvAsync(csv)); + } + + [Fact] + public async Task ImportCsv_EmptyValuesForOptionalProperties_ShouldReturnImportedItems() + { + var csvText = @"Name,Description,IsAutoPaid,Cost,FirstPaymentDay,LastPayment,BillingOccurrence,BillingInterval,PrimaryColor,Icon,SecondaryColor +Subscription1,Description1,True,29.99,1,2023-12-01,Month,1,,,,,, +Subscription2,,True,29.99,1,2023-12-01,Month,1,,,,,, +"; + var csv = new MemoryStream(Encoding.UTF8.GetBytes(csvText)); + + var importedSubs = await _sut.ImportFromCsvAsync(csv); + + Assert.Equal(2, importedSubs.Count); + } + + [Fact] + public async Task ImportCsv_WithSameDetailsAsExisting_ShouldReturnImportedSubs() + { + var existingSubscription = CreateSubscription(DateTime.Today, description: null); + var csvText = $@"Name,Description,IsAutoPaid,Cost,FirstPaymentDay,LastPayment,BillingOccurrence,BillingInterval,PrimaryColor,Icon,SecondaryColor +{existingSubscription.Name},,{existingSubscription.IsAutoPaid},{existingSubscription.Cost},{existingSubscription.FirstPaymentDay},{existingSubscription.LastPayment},{existingSubscription.BillingOccurrence},{existingSubscription.BillingInterval},,,,,, +Subscription1,,True,29.99,1,2023-12-01,Month,1,,,,,, +Subscription2,,False,29.99,1,2022-12-01,Week,1,,,,,, +"; + var csv = new MemoryStream(Encoding.UTF8.GetBytes(csvText)); + + var importedSubs = await _sut.ImportFromCsvAsync(csv); + + Assert.Equal(3, importedSubs.Count); + } +} diff --git a/subtrack.Tests/subtrack.Tests.csproj b/subtrack.Tests/subtrack.Tests.csproj index 7d7df82..a26818b 100644 --- a/subtrack.Tests/subtrack.Tests.csproj +++ b/subtrack.Tests/subtrack.Tests.csproj @@ -1,7 +1,7 @@  - net6.0 + net7.0 enable enable @@ -10,14 +10,18 @@ - - - - + + + + + + + + runtime; build; native; contentfiles; analyzers; buildtransitive all - + runtime; build; native; contentfiles; analyzers; buildtransitive all diff --git a/subtrack.sln b/subtrack.sln index dac6370..4edf0ac 100644 --- a/subtrack.sln +++ b/subtrack.sln @@ -9,6 +9,7 @@ EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{29BE4662-B884-4DB2-8211-06EBD93EB0CC}" ProjectSection(SolutionItems) = preProject .editorconfig = .editorconfig + global.json = global.json docs\mockups.drawio = docs\mockups.drawio docs\mockups.png = docs\mockups.png README.md = README.md