From 6bec019e2c265ed13018b589102f9b8127bd5d85 Mon Sep 17 00:00:00 2001 From: Robin Krom Date: Sun, 14 Jun 2026 21:52:09 +0200 Subject: [PATCH 1/5] First steps making StripWolf available for the google store. --- .github/workflows/release.yml | 44 ++++++++++++++++--- .../Properties/AndroidManifest.xml | 5 ++- .../Resources/xml/backup_rules.xml | 14 ++++++ .../Resources/xml/full_backup_content.xml | 7 +++ .../StripWolf.Core/StripWolf.Core.csproj | 6 ++- .../ViewModels/MainViewModel.cs | 17 +++++++ .../ViewModels/SettingsViewModel.cs | 7 +++ .../StripWolf.Core/Views/SettingsView.axaml | 4 +- 8 files changed, 94 insertions(+), 10 deletions(-) create mode 100644 src/StripWolf/StripWolf.Android/Resources/xml/backup_rules.xml create mode 100644 src/StripWolf/StripWolf.Android/Resources/xml/full_backup_content.xml diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 0ed047f..cca27f9 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -288,7 +288,7 @@ jobs: env: ANDROID_KEYSTORE_BASE64: ${{ secrets.ANDROID_KEYSTORE_BASE64 }} - - name: Build Android ARM64 + - name: Build Android ARM64 APK run: | dotnet publish src/StripWolf/StripWolf.Android/StripWolf.Android.csproj -c Release -r android-arm64 -o publish/android-arm64 \ -p:DebugType=none -p:DebugSymbols=false -p:AllowedReferenceRelatedFileExtensions=none \ @@ -299,7 +299,7 @@ jobs: ANDROID_SIGNING_KEY_PASS: ${{ secrets.ANDROID_SIGNING_KEY_PASS || 'android' }} ANDROID_SIGNING_STORE_PASS: ${{ secrets.ANDROID_SIGNING_STORE_PASS || 'android' }} - - name: Build Android x64 + - name: Build Android x64 APK run: | dotnet publish src/StripWolf/StripWolf.Android/StripWolf.Android.csproj -c Release -r android-x64 -o publish/android-x64 \ -p:DebugType=none -p:DebugSymbols=false -p:AllowedReferenceRelatedFileExtensions=none \ @@ -310,27 +310,59 @@ jobs: ANDROID_SIGNING_KEY_PASS: ${{ secrets.ANDROID_SIGNING_KEY_PASS || 'android' }} ANDROID_SIGNING_STORE_PASS: ${{ secrets.ANDROID_SIGNING_STORE_PASS || 'android' }} - - name: Find and rename APKs + - name: Build Android App Bundle (AAB) + run: | + dotnet publish src/StripWolf/StripWolf.Android/StripWolf.Android.csproj -c Release -o publish/android-aab \ + -p:DebugType=none -p:DebugSymbols=false -p:AllowedReferenceRelatedFileExtensions=none \ + -p:AndroidPackageFormat=aab \ + -p:AndroidCreatePackagePerAbi=false \ + -p:IsPlayStoreBuild=true \ + -p:RuntimeIdentifiers="android-arm64;android-x64" \ + -p:GitVersion_CommitsSinceVersionSource=${{ steps.gitversion.outputs.commitsSinceVersionSource }} \ + -p:GitVersion_SemVer=${{ steps.gitversion.outputs.semVer }} + env: + ANDROID_SIGNING_KEY_PASS: ${{ secrets.ANDROID_SIGNING_KEY_PASS || 'android' }} + ANDROID_SIGNING_STORE_PASS: ${{ secrets.ANDROID_SIGNING_STORE_PASS || 'android' }} + + - name: Find and rename Android Artifacts run: | mkdir -p release-artifacts - # Process ARM64 + # Process ARM64 APK APK_ARM=$(find publish/android-arm64 -name "*-Signed.apk" | head -1) if [ -n "$APK_ARM" ]; then cp "$APK_ARM" "release-artifacts/StripWolf-Android-arm64-${{ needs.prepare.outputs.version }}-g${{ needs.prepare.outputs.hash }}.apk" fi - # Process x64 + # Process x64 APK APK_X64=$(find publish/android-x64 -name "*-Signed.apk" | head -1) if [ -n "$APK_X64" ]; then cp "$APK_X64" "release-artifacts/StripWolf-Android-x86_64-${{ needs.prepare.outputs.version }}-g${{ needs.prepare.outputs.hash }}.apk" fi + # Process AAB + AAB_FILE=$(find publish/android-aab -name "*-Signed.aab" | head -1) + if [ -n "$AAB_FILE" ]; then + cp "$AAB_FILE" "release-artifacts/StripWolf-Android-${{ needs.prepare.outputs.version }}-g${{ needs.prepare.outputs.hash }}.aab" + fi + - name: Upload to Release uses: softprops/action-gh-release@v2 with: tag_name: ${{ needs.prepare.outputs.version }} - files: release-artifacts/*.apk + files: | + release-artifacts/*.apk + release-artifacts/*.aab draft: true env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + - name: Upload to Google Play + uses: r0adkll/upload-google-play@v2 + if: ${{ secrets.PLAY_SERVICE_ACCOUNT_KEY != '' }} + with: + serviceAccountJsonPlainText: ${{ secrets.PLAY_SERVICE_ACCOUNT_KEY }} + packageName: com.dapplo.StripWolf + releaseFiles: release-artifacts/*.aab + track: internal + status: completed diff --git a/src/StripWolf/StripWolf.Android/Properties/AndroidManifest.xml b/src/StripWolf/StripWolf.Android/Properties/AndroidManifest.xml index c99d5fd..b99e9b0 100644 --- a/src/StripWolf/StripWolf.Android/Properties/AndroidManifest.xml +++ b/src/StripWolf/StripWolf.Android/Properties/AndroidManifest.xml @@ -3,5 +3,8 @@ + android:networkSecurityConfig="@xml/network_security_config" + android:allowBackup="true" + android:fullBackupContent="@xml/full_backup_content" + android:dataExtractionRules="@xml/backup_rules" /> diff --git a/src/StripWolf/StripWolf.Android/Resources/xml/backup_rules.xml b/src/StripWolf/StripWolf.Android/Resources/xml/backup_rules.xml new file mode 100644 index 0000000..a236fed --- /dev/null +++ b/src/StripWolf/StripWolf.Android/Resources/xml/backup_rules.xml @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + diff --git a/src/StripWolf/StripWolf.Android/Resources/xml/full_backup_content.xml b/src/StripWolf/StripWolf.Android/Resources/xml/full_backup_content.xml new file mode 100644 index 0000000..46c7bca --- /dev/null +++ b/src/StripWolf/StripWolf.Android/Resources/xml/full_backup_content.xml @@ -0,0 +1,7 @@ + + + + + + + diff --git a/src/StripWolf/StripWolf.Core/StripWolf.Core.csproj b/src/StripWolf/StripWolf.Core/StripWolf.Core.csproj index 97e3b83..07e5144 100644 --- a/src/StripWolf/StripWolf.Core/StripWolf.Core.csproj +++ b/src/StripWolf/StripWolf.Core/StripWolf.Core.csproj @@ -1,4 +1,4 @@ - + net10.0 enable @@ -25,6 +25,10 @@ $(DefineConstants);DISABLE_GUIDED_READING + + $(DefineConstants);PLAY_STORE_BUILD + + diff --git a/src/StripWolf/StripWolf.Core/ViewModels/MainViewModel.cs b/src/StripWolf/StripWolf.Core/ViewModels/MainViewModel.cs index 6280020..41f16ea 100644 --- a/src/StripWolf/StripWolf.Core/ViewModels/MainViewModel.cs +++ b/src/StripWolf/StripWolf.Core/ViewModels/MainViewModel.cs @@ -35,7 +35,11 @@ public record WelcomeThemeOption(AppThemePreference Value, string DisplayName); /// public partial class MainViewModel : ViewModelBase { +#if PLAY_STORE_BUILD + private const int WelcomeExperienceStepCount = 6; +#else private const int WelcomeExperienceStepCount = 7; +#endif private readonly LibraryViewModel _libraryViewModel; private readonly KomgaViewModel _komgaViewModel; private readonly ActivityViewModel _activityViewModel; @@ -344,8 +348,13 @@ private void OnReaderCloseRequested(object? sender, EventArgs e) public bool IsWelcomeLibraryImportStep => ShowWelcomeExperience && WelcomeExperienceStep == 2; public bool IsWelcomeKomgaStep => ShowWelcomeExperience && WelcomeExperienceStep == 3; public bool IsWelcomeActivityStep => ShowWelcomeExperience && WelcomeExperienceStep == 4; +#if PLAY_STORE_BUILD + public bool IsWelcomeSettingsStep => ShowWelcomeExperience && WelcomeExperienceStep == 5; + public bool IsWelcomeSupportStep => false; +#else public bool IsWelcomeSettingsStep => ShowWelcomeExperience && (WelcomeExperienceStep == 5 || WelcomeExperienceStep == 6); public bool IsWelcomeSupportStep => ShowWelcomeExperience && WelcomeExperienceStep == 6; +#endif public bool HasWelcomeExperienceTargetHint => !string.IsNullOrWhiteSpace(WelcomeExperienceTargetHint); public double WelcomeBackgroundContentOpacity => ShowWelcomeExperience && WelcomeExperienceStep == 0 ? 0.08 : 1.0; public string WelcomeExperienceStepTitle => WelcomeExperienceStep switch @@ -356,7 +365,9 @@ private void OnReaderCloseRequested(object? sender, EventArgs e) 3 => Resources.Loc.Instance.WelcomeExperienceKomgaTitle, 4 => Resources.Loc.Instance.WelcomeExperienceActivityTitle, 5 => Resources.Loc.Instance.WelcomeExperienceSettingsTitle, +#if !PLAY_STORE_BUILD 6 => Resources.Loc.Instance.WelcomeExperienceSupportTitle, +#endif _ => Resources.Loc.Instance.WelcomeExperienceIntroTitle }; public string WelcomeExperienceStepDescription => WelcomeExperienceStep switch @@ -367,7 +378,9 @@ private void OnReaderCloseRequested(object? sender, EventArgs e) 3 => Resources.Loc.Instance.WelcomeExperienceKomgaDescription, 4 => Resources.Loc.Instance.WelcomeExperienceActivityDescription, 5 => Resources.Loc.Instance.WelcomeExperienceSettingsDescription, +#if !PLAY_STORE_BUILD 6 => Resources.Loc.Instance.WelcomeExperienceSupportDescription, +#endif _ => Resources.Loc.Instance.WelcomeExperienceIntroDescription }; public string WelcomeExperienceTargetHint => WelcomeExperienceStep switch @@ -377,7 +390,9 @@ private void OnReaderCloseRequested(object? sender, EventArgs e) 3 => Resources.Loc.Instance.WelcomeExperienceKomgaHint, 4 => Resources.Loc.Instance.WelcomeExperienceActivityHint, 5 => Resources.Loc.Instance.WelcomeExperienceSettingsHint, +#if !PLAY_STORE_BUILD 6 => Resources.Loc.Instance.WelcomeExperienceSupportHint, +#endif _ => string.Empty }; @@ -545,7 +560,9 @@ private void SetWelcomeExperienceStep(int step) 3 => 1, 4 => 2, 5 => 3, +#if !PLAY_STORE_BUILD 6 => 3, +#endif _ => (int?)null }; diff --git a/src/StripWolf/StripWolf.Core/ViewModels/SettingsViewModel.cs b/src/StripWolf/StripWolf.Core/ViewModels/SettingsViewModel.cs index d372804..f5bfdb9 100644 --- a/src/StripWolf/StripWolf.Core/ViewModels/SettingsViewModel.cs +++ b/src/StripWolf/StripWolf.Core/ViewModels/SettingsViewModel.cs @@ -108,6 +108,13 @@ public partial class SettingsViewModel : ViewModelBase [ObservableProperty] private bool _isSupportMeVisible; + public bool IsSupportMeAllowed => +#if PLAY_STORE_BUILD + false; +#else + true; +#endif + public string AppVersion { get diff --git a/src/StripWolf/StripWolf.Core/Views/SettingsView.axaml b/src/StripWolf/StripWolf.Core/Views/SettingsView.axaml index 85bcb42..bddbe4c 100644 --- a/src/StripWolf/StripWolf.Core/Views/SettingsView.axaml +++ b/src/StripWolf/StripWolf.Core/Views/SettingsView.axaml @@ -60,9 +60,9 @@ Background="Transparent" Padding="0"/> - + - + From 3cabe7186e39954cddd4f8811ed032106afa1e39 Mon Sep 17 00:00:00 2001 From: Robin Krom Date: Sun, 14 Jun 2026 23:49:48 +0200 Subject: [PATCH 2/5] Added the tracking for the trail period and for the overview of how many comics and pages were read. --- src/StripWolf/StripWolf.Core/App.axaml.cs | 2 + .../StripWolf.Core/Data/DatabaseService.cs | 41 ++++ .../StripWolf.Core/Models/AppSettings.cs | 18 ++ .../StripWolf.Core/Models/UsageStats.cs | 20 ++ .../Services/AppEventsService.cs | 35 +++ .../Services/IAppEventsService.cs | 34 +++ .../Services/IBillingService.cs | 15 ++ .../StripWolf.Core/Services/LibraryService.cs | 26 +- .../StripWolf.Core/Services/TrialService.cs | 232 ++++++++++++++++++ .../ViewModels/MainViewModel.cs | 23 +- .../ViewModels/ReaderViewModel.cs | 38 ++- .../ViewModels/SettingsViewModel.cs | 154 +++++++++++- .../StripWolf.Core/Views/MainView.axaml | 55 +++++ .../StripWolf.Core/Views/SettingsView.axaml | 198 +++++++++++++++ 14 files changed, 887 insertions(+), 4 deletions(-) create mode 100644 src/StripWolf/StripWolf.Core/Models/UsageStats.cs create mode 100644 src/StripWolf/StripWolf.Core/Services/AppEventsService.cs create mode 100644 src/StripWolf/StripWolf.Core/Services/IAppEventsService.cs create mode 100644 src/StripWolf/StripWolf.Core/Services/IBillingService.cs create mode 100644 src/StripWolf/StripWolf.Core/Services/TrialService.cs diff --git a/src/StripWolf/StripWolf.Core/App.axaml.cs b/src/StripWolf/StripWolf.Core/App.axaml.cs index 07c4c86..12d14f2 100644 --- a/src/StripWolf/StripWolf.Core/App.axaml.cs +++ b/src/StripWolf/StripWolf.Core/App.axaml.cs @@ -183,6 +183,8 @@ private static void ConfigureServices(IServiceCollection services) services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); + services.AddSingleton(); + services.AddSingleton(); // Register platform-specific PDF renderer // Use the custom registration action if set (e.g., for Android), otherwise default to nothing diff --git a/src/StripWolf/StripWolf.Core/Data/DatabaseService.cs b/src/StripWolf/StripWolf.Core/Data/DatabaseService.cs index 41a1a96..372a94a 100644 --- a/src/StripWolf/StripWolf.Core/Data/DatabaseService.cs +++ b/src/StripWolf/StripWolf.Core/Data/DatabaseService.cs @@ -62,6 +62,9 @@ private static string GetAppDataDirectory() [DynamicDependency( DynamicallyAccessedMemberTypes.PublicConstructors | DynamicallyAccessedMemberTypes.PublicProperties, typeof(KomgaPendingReadProgress))] + [DynamicDependency( + DynamicallyAccessedMemberTypes.PublicConstructors | DynamicallyAccessedMemberTypes.PublicProperties, + typeof(UsageStats))] private async Task GetDatabaseAsync() { if (_isInitialized && _database is not null) @@ -89,7 +92,9 @@ private async Task GetDatabaseAsync() await _database.CreateTableAsync(); await _database.CreateTableAsync(); await _database.CreateTableAsync(); + await _database.CreateTableAsync(); await _database.ExecuteAsync("CREATE INDEX IF NOT EXISTS IX_Comic_FilePath ON Comic(FilePath)"); + await _database.ExecuteAsync("CREATE INDEX IF NOT EXISTS IX_UsageStats_Metric ON UsageStats(Metric)"); await _database.ExecuteAsync("CREATE INDEX IF NOT EXISTS IX_EpubConversionState_Status ON EpubConversionState(Status)"); _isInitialized = true; @@ -469,6 +474,42 @@ await db.RunInTransactionAsync(connection => #endregion + #region Usage Stats + + public async Task LogUsageAsync(string metric, string? metadata = null) + { + try + { + var db = await GetDatabaseAsync(); + var stat = new UsageStats + { + Metric = metric, + Timestamp = DateTime.UtcNow, + Metadata = metadata + }; + await db.InsertAsync(stat); + } + catch + { + // Ignore stats errors so they don't crash the app + } + } + + public async Task GetUsageCountAsync(string metric) + { + try + { + var db = await GetDatabaseAsync(); + return await db.Table().Where(s => s.Metric == metric).CountAsync(); + } + catch + { + return 0; + } + } + + #endregion + public async ValueTask DisposeAsync() { if (_database is not null) diff --git a/src/StripWolf/StripWolf.Core/Models/AppSettings.cs b/src/StripWolf/StripWolf.Core/Models/AppSettings.cs index 85a08e4..9e5963c 100644 --- a/src/StripWolf/StripWolf.Core/Models/AppSettings.cs +++ b/src/StripWolf/StripWolf.Core/Models/AppSettings.cs @@ -61,6 +61,21 @@ public class AppSettings /// public bool? HasCompletedWelcomeExperience { get; set; } + /// + /// Whether the user has purchased the unlimited premium unlock. + /// + public bool IsUnlimitedUnlocked { get; set; } = false; + + /// + /// Book IDs of Komga comics that have been opened (Max 2 in trial). + /// + public List PermanentViewedKomgaBookIds { get; set; } = []; + + /// + /// Local comic paths/filenames that have been opened (Max 2 per format in trial). + /// + public List PermanentViewedLocalPaths { get; set; } = []; + /// /// Preferred reading mode for the comic reader /// @@ -189,6 +204,9 @@ public AppSettings Clone() WasInReader = WasInReader, LastTabIndex = LastTabIndex, HasCompletedWelcomeExperience = HasCompletedWelcomeExperience, + IsUnlimitedUnlocked = IsUnlimitedUnlocked, + PermanentViewedKomgaBookIds = PermanentViewedKomgaBookIds.ToList(), + PermanentViewedLocalPaths = PermanentViewedLocalPaths.ToList(), AppTheme = AppTheme, PreferredReadingMode = PreferredReadingMode, Handedness = Handedness, diff --git a/src/StripWolf/StripWolf.Core/Models/UsageStats.cs b/src/StripWolf/StripWolf.Core/Models/UsageStats.cs new file mode 100644 index 0000000..5cbf878 --- /dev/null +++ b/src/StripWolf/StripWolf.Core/Models/UsageStats.cs @@ -0,0 +1,20 @@ +using System; +using SQLite; + +namespace StripWolf.Core.Models; + +/// +/// Model for logging usage statistics in the database. +/// +public class UsageStats +{ + [PrimaryKey, AutoIncrement] + public int Id { get; set; } + + [Indexed] + public string Metric { get; set; } = string.Empty; // "KomgaDownload", "LocalImport", "ComicOpen", "PagesRead" + + public DateTime Timestamp { get; set; } = DateTime.UtcNow; + + public string? Metadata { get; set; } +} diff --git a/src/StripWolf/StripWolf.Core/Services/AppEventsService.cs b/src/StripWolf/StripWolf.Core/Services/AppEventsService.cs new file mode 100644 index 0000000..90181c8 --- /dev/null +++ b/src/StripWolf/StripWolf.Core/Services/AppEventsService.cs @@ -0,0 +1,35 @@ +using System; +using StripWolf.Core.Models; + +namespace StripWolf.Core.Services; + +/// +/// Centralized implementation of IAppEventsService to publish application-wide events. +/// +public class AppEventsService : IAppEventsService +{ + public event EventHandler? LocalComicImported; + public event EventHandler? KomgaBookDownloaded; + public event EventHandler? ComicOpened; + public event EventHandler? PageRead; + + public void RaiseLocalComicImported(string filePath) + { + LocalComicImported?.Invoke(this, filePath); + } + + public void RaiseKomgaBookDownloaded(string bookId) + { + KomgaBookDownloaded?.Invoke(this, bookId); + } + + public void RaiseComicOpened(int comicId, ComicSource source, string identifier) + { + ComicOpened?.Invoke(this, new ComicOpenedEventArgs(comicId, source, identifier)); + } + + public void RaisePageRead() + { + PageRead?.Invoke(this, EventArgs.Empty); + } +} diff --git a/src/StripWolf/StripWolf.Core/Services/IAppEventsService.cs b/src/StripWolf/StripWolf.Core/Services/IAppEventsService.cs new file mode 100644 index 0000000..eddd5bb --- /dev/null +++ b/src/StripWolf/StripWolf.Core/Services/IAppEventsService.cs @@ -0,0 +1,34 @@ +using System; +using StripWolf.Core.Models; + +namespace StripWolf.Core.Services; + +public class ComicOpenedEventArgs : EventArgs +{ + public int ComicId { get; } + public ComicSource Source { get; } + public string Identifier { get; } // FilePath for local, BookId for Komga + + public ComicOpenedEventArgs(int comicId, ComicSource source, string identifier) + { + ComicId = comicId; + Source = source; + Identifier = identifier; + } +} + +/// +/// Centralized service to publish and subscribe to application-wide events. +/// +public interface IAppEventsService +{ + event EventHandler? LocalComicImported; + event EventHandler? KomgaBookDownloaded; + event EventHandler? ComicOpened; + event EventHandler? PageRead; + + void RaiseLocalComicImported(string filePath); + void RaiseKomgaBookDownloaded(string bookId); + void RaiseComicOpened(int comicId, ComicSource source, string identifier); + void RaisePageRead(); +} diff --git a/src/StripWolf/StripWolf.Core/Services/IBillingService.cs b/src/StripWolf/StripWolf.Core/Services/IBillingService.cs new file mode 100644 index 0000000..ed2c871 --- /dev/null +++ b/src/StripWolf/StripWolf.Core/Services/IBillingService.cs @@ -0,0 +1,15 @@ +using System.Threading; +using System.Threading.Tasks; + +namespace StripWolf.Core.Services; + +/// +/// Platform-specific billing interface to verify Google Play/iOS purchases. +/// +public interface IBillingService +{ + /// + /// Queries the platform's app store to check if the premium unlock product is owned. + /// + Task QueryPremiumPurchaseAsync(CancellationToken cancellationToken); +} diff --git a/src/StripWolf/StripWolf.Core/Services/LibraryService.cs b/src/StripWolf/StripWolf.Core/Services/LibraryService.cs index 3c7775b..723b4b1 100644 --- a/src/StripWolf/StripWolf.Core/Services/LibraryService.cs +++ b/src/StripWolf/StripWolf.Core/Services/LibraryService.cs @@ -60,6 +60,9 @@ public class LibraryService /// public event EventHandler? LibraryChanged; + private readonly TrialService _trialService; + private readonly IAppEventsService _appEventsService; + public LibraryService( DatabaseService databaseService, ComicReaderService comicReaderService, @@ -69,7 +72,9 @@ public LibraryService( PdfToCbzConverterService pdfConverter, EpubToCbzConverterService epubConverter, EpubShadowConversionService epubShadowConversionService, - ComicConverterService comicConverter) + ComicConverterService comicConverter, + TrialService trialService, + IAppEventsService appEventsService) { _databaseService = databaseService; _comicReaderService = comicReaderService; @@ -80,6 +85,8 @@ public LibraryService( _epubConverter = epubConverter; _epubShadowConversionService = epubShadowConversionService; _comicConverter = comicConverter; + _trialService = trialService; + _appEventsService = appEventsService; _appDataDirectory = GetAppDataDirectory(); _comicsDirectory = Path.Combine(_appDataDirectory, "Comics"); @@ -347,6 +354,13 @@ public async Task ImportLocalComicAsync(string filePath, IProgress ImportLocalComicAsync(string filePath, IProgress ImportLocalComicAsync(string filePath, IProgress ImportLocalComicAsync(string filePath, IProgress +/// Service to handle trial limitations and license unlock logic. +/// +public class TrialService +{ + private readonly SettingsService _settingsService; + private readonly DatabaseService _databaseService; + + public event EventHandler? PremiumUnlockRequested; + + public void RequestPremiumUnlock() + { + PremiumUnlockRequested?.Invoke(this, EventArgs.Empty); + } + + public TrialService( + SettingsService settingsService, + DatabaseService databaseService, + IAppEventsService appEventsService, + IBillingService? billingService = null) + { + _settingsService = settingsService; + _databaseService = databaseService; + + appEventsService.LocalComicImported += OnLocalComicImported; + appEventsService.KomgaBookDownloaded += OnKomgaBookDownloaded; + appEventsService.ComicOpened += OnComicOpened; + appEventsService.PageRead += OnPageRead; + + if (billingService is not null) + { + InitializeBillingBackground(billingService); + } + } + + private void InitializeBillingBackground(IBillingService billingService) + { + _ = Task.Run(async () => + { + try + { + // Wait briefly during startup to avoid competing with UI render thread + await Task.Delay(2000); + + var hasPremium = await billingService.QueryPremiumPurchaseAsync(System.Threading.CancellationToken.None); + + if (hasPremium != IsUnlimitedUnlocked) + { + await _settingsService.UpdateSettingsAsync(s => + { + s.IsUnlimitedUnlocked = hasPremium; + }); + } + } + catch + { + // Fail silently (keep using cached value if offline or Play Store is unreachable) + } + }); + } + + private async void OnLocalComicImported(object? sender, string filePath) + { + await LogUsageAsync("LocalImport", filePath); + } + + private async void OnKomgaBookDownloaded(object? sender, string bookId) + { + await LogUsageAsync("KomgaDownload", bookId); + } + + private async void OnPageRead(object? sender, EventArgs e) + { + await LogUsageAsync("PagesRead"); + } + + private async void OnComicOpened(object? sender, ComicOpenedEventArgs e) + { + await LogUsageAsync("ComicOpen", e.ComicId); + + if (!IsUnlimitedUnlocked) + { + if (e.Source == ComicSource.Komga) + { + if (int.TryParse(e.Identifier, out var bookId)) + { + await _settingsService.UpdateSettingsAsync(s => + { + if (!s.PermanentViewedKomgaBookIds.Contains(bookId)) + { + s.PermanentViewedKomgaBookIds.Add(bookId); + } + }); + } + } + else + { + var filename = Path.GetFileName(e.Identifier); + await _settingsService.UpdateSettingsAsync(s => + { + if (!s.PermanentViewedLocalPaths.Contains(filename)) + { + s.PermanentViewedLocalPaths.Add(filename); + } + }); + } + } + } + + public bool IsUnlimitedUnlocked + { + get + { +#if PLAY_STORE_BUILD + return _settingsService.LoadSettings().IsUnlimitedUnlocked; +#else + return true; +#endif + } + } + + /// + /// Checks if a local import of a specific file type is allowed (capacity limit: max 2). + /// + public async Task CanImportLocalAsync(string filePath) + { + if (IsUnlimitedUnlocked) return true; + + var ext = Path.GetExtension(filePath)?.TrimStart('.')?.ToLowerInvariant(); + if (string.IsNullOrEmpty(ext)) return true; + + var allowedFormats = new[] { "cbz", "cbr", "cbt", "cb7", "epub", "pdf" }; + if (!allowedFormats.Contains(ext)) return true; + + // Count how many local comics of this extension currently exist in the database library + var comics = await _databaseService.GetComicsAsync(); + var currentCount = comics.Count(c => + { + if (c.Source == ComicSource.Komga) return false; + var cExt = Path.GetExtension(c.FilePath)?.TrimStart('.')?.ToLowerInvariant(); + return cExt == ext; + }); + + return currentCount < 2; + } + + /// + /// Checks if a Komga download is allowed (capacity limit: max 2). + /// + public async Task CanDownloadKomgaAsync() + { + if (IsUnlimitedUnlocked) return true; + + // Count how many Komga-sourced comics currently exist in the library + var comics = await _databaseService.GetComicsAsync(); + var currentCount = comics.Count(c => c.Source == ComicSource.Komga); + + return currentCount < 2; + } + + /// + /// Checks if opening a local comic is allowed (permanent view limit: max 2 unique files per format). + /// + public async Task CanOpenLocalAsync(string filePath) + { + if (IsUnlimitedUnlocked) return true; + + var ext = Path.GetExtension(filePath)?.TrimStart('.')?.ToLowerInvariant(); + if (string.IsNullOrEmpty(ext)) return true; + + var filename = Path.GetFileName(filePath); + var settings = _settingsService.LoadSettings(); + + // Already viewed in trial - free to open again + if (settings.PermanentViewedLocalPaths.Contains(filename) || settings.PermanentViewedLocalPaths.Contains(filePath)) + { + return true; + } + + // Count how many unique viewed local files of this extension have been opened permanently + var currentViewedCount = settings.PermanentViewedLocalPaths + .Count(p => Path.GetExtension(p)?.TrimStart('.')?.ToLowerInvariant() == ext); + + return currentViewedCount < 2; + } + + /// + /// Checks if opening a Komga comic is allowed (permanent view limit: max 2 unique files). + /// + public async Task CanOpenKomgaAsync(int bookId) + { + if (IsUnlimitedUnlocked) return true; + + var settings = _settingsService.LoadSettings(); + + // Already viewed in trial - free to open again + if (settings.PermanentViewedKomgaBookIds.Contains(bookId)) + { + return true; + } + + return settings.PermanentViewedKomgaBookIds.Count < 2; + } + + /// + /// Simulates/performs premium unlock. + /// + public async Task UnlockPremiumAsync() + { + await _settingsService.UpdateSettingsAsync(s => + { + s.IsUnlimitedUnlocked = true; + }); + } + + /// + /// Logs usage statistics to the database. + /// + public async Task LogUsageAsync(string metric, object? metadata = null) + { + await _databaseService.LogUsageAsync(metric, metadata?.ToString()); + } +} diff --git a/src/StripWolf/StripWolf.Core/ViewModels/MainViewModel.cs b/src/StripWolf/StripWolf.Core/ViewModels/MainViewModel.cs index 41f16ea..26cd6af 100644 --- a/src/StripWolf/StripWolf.Core/ViewModels/MainViewModel.cs +++ b/src/StripWolf/StripWolf.Core/ViewModels/MainViewModel.cs @@ -50,6 +50,7 @@ public partial class MainViewModel : ViewModelBase private readonly IExternalLinkService _externalLinkService; private readonly KomgaSyncService _komgaSyncService; private readonly UpdateService _updateService; + private readonly TrialService _trialService; private bool _isInitializing = true; private bool _isApplyingWelcomePreferences; private bool _shouldStartWelcomeAfterInitialization; @@ -80,6 +81,9 @@ public partial class MainViewModel : ViewModelBase [ObservableProperty] private bool _showWelcomeExperience; + [ObservableProperty] + private bool _showPremiumUnlockDialog; + [ObservableProperty] private int _welcomeExperienceStep; @@ -105,7 +109,8 @@ public MainViewModel( LocalizationService localizationService, IExternalLinkService externalLinkService, KomgaSyncService komgaSyncService, - UpdateService updateService) + UpdateService updateService, + TrialService trialService) { _libraryViewModel = libraryViewModel; _komgaViewModel = komgaViewModel; @@ -117,6 +122,9 @@ public MainViewModel( _externalLinkService = externalLinkService; _komgaSyncService = komgaSyncService; _updateService = updateService; + _trialService = trialService; + + _trialService.PremiumUnlockRequested += (s, e) => ShowPremiumUnlockDialog = true; Title = "StripWolf"; _currentView = _libraryViewModel; @@ -687,4 +695,17 @@ private static void ApplyTheme(AppThemePreference theme) _ => ThemeVariant.Default }; } + + [RelayCommand] + private void ClosePremiumUnlockDialog() + { + ShowPremiumUnlockDialog = false; + } + + [RelayCommand] + private async Task PurchasePremiumUnlock() + { + await _trialService.UnlockPremiumAsync(); + ShowPremiumUnlockDialog = false; + } } diff --git a/src/StripWolf/StripWolf.Core/ViewModels/ReaderViewModel.cs b/src/StripWolf/StripWolf.Core/ViewModels/ReaderViewModel.cs index 5450480..c0387fe 100644 --- a/src/StripWolf/StripWolf.Core/ViewModels/ReaderViewModel.cs +++ b/src/StripWolf/StripWolf.Core/ViewModels/ReaderViewModel.cs @@ -629,6 +629,9 @@ private async Task ResolveAutomaticReadingDirectionAsync() /// public event EventHandler? CloseRequested; + private readonly TrialService _trialService; + private readonly IAppEventsService _appEventsService; + public ReaderViewModel( LibraryService libraryService, ComicReaderService comicReaderService, @@ -636,7 +639,9 @@ public ReaderViewModel( KomgaApiServiceFactory komgaApiServiceFactory, PanelDetectionService panelDetectionService, SettingsService settingsService, - EpubShadowConversionService epubShadowConversionService) + EpubShadowConversionService epubShadowConversionService, + TrialService trialService, + IAppEventsService appEventsService) { _libraryService = libraryService; _comicReaderService = comicReaderService; @@ -645,6 +650,8 @@ public ReaderViewModel( _panelDetectionService = panelDetectionService; _settingsService = settingsService; _epubShadowConversionService = epubShadowConversionService; + _trialService = trialService; + _appEventsService = appEventsService; _epubShadowConversionService.ConversionStateChanged += OnEpubConversionStateChanged; _periodicSyncTimer = new DispatcherTimer @@ -879,11 +886,39 @@ public async Task LoadComicAsync(int comicId) Comic = await _libraryService.GetComicAsync(ComicId); if (Comic is not null) { + // Enforce permanent view limits in trial + bool allowed = true; + if (Comic.Source == ComicSource.Komga && !string.IsNullOrEmpty(Comic.KomgaId)) + { + if (int.TryParse(Comic.KomgaId, out var komgaBookId)) + { + allowed = await _trialService.CanOpenKomgaAsync(komgaBookId); + } + } + else + { + allowed = await _trialService.CanOpenLocalAsync(Comic.FilePath); + } + + if (!allowed) + { + IsBusy = false; + ErrorMessage = "You have reached some limits of the trial. Please unlock premium to read this comic."; + _trialService.RequestPremiumUnlock(); + // Navigate back or close the reader since opening is blocked + Dispatcher.UIThread.Post(() => _ = GoBackAsync()); + return; + } + // Save last opened comic info in settings settings.LastOpenedComicId = Comic.Id; settings.LastOpenedComicPath = Comic.FilePath; settings.WasInReader = true; _ = _settingsService.SaveSettingsAsync(settings); + _appEventsService.RaiseComicOpened( + Comic.Id, + Comic.Source, + Comic.Source == ComicSource.Komga ? (Comic.KomgaId ?? string.Empty) : Comic.FilePath); Title = Comic.Title; OnPropertyChanged(nameof(MaxSliderValue)); @@ -1158,6 +1193,7 @@ private async Task LoadPageAsync() } _lastLoadedPageIndex = pageIndex; + _appEventsService.RaisePageRead(); // Reset zoom when changing pages ZoomLevel = 1.0; diff --git a/src/StripWolf/StripWolf.Core/ViewModels/SettingsViewModel.cs b/src/StripWolf/StripWolf.Core/ViewModels/SettingsViewModel.cs index f5bfdb9..32db9ce 100644 --- a/src/StripWolf/StripWolf.Core/ViewModels/SettingsViewModel.cs +++ b/src/StripWolf/StripWolf.Core/ViewModels/SettingsViewModel.cs @@ -17,13 +17,17 @@ // You should have received a copy of the GNU General Public License // along with this program. If not, see . +using System.Collections.Generic; using System.Collections.ObjectModel; using System.ComponentModel; +using System.IO; +using System.Linq; using Avalonia; using Avalonia.Threading; using Avalonia.Styling; using CommunityToolkit.Mvvm.ComponentModel; using CommunityToolkit.Mvvm.Input; +using StripWolf.Core.Data; using StripWolf.Core.Models; using StripWolf.Core.Resources; using StripWolf.Core.Services; @@ -49,6 +53,8 @@ public partial class SettingsViewModel : ViewModelBase private readonly LocalizationService _localizationService; private readonly IExternalLinkService _externalLinkService; private readonly UpdateService _updateService; + private readonly TrialService _trialService; + private readonly DatabaseService _databaseService; private AppSettings? _appSettings; private int _nextServerId = 1; @@ -320,13 +326,61 @@ public string AppVersion public UpdateService UpdateService => _updateService; - public SettingsViewModel(SettingsService settingsService, LibraryService libraryService, LocalizationService localizationService, IExternalLinkService externalLinkService, UpdateService updateService) + [ObservableProperty] + [NotifyPropertyChangedFor(nameof(IsPlayStoreBuildAndUnlocked))] + [NotifyPropertyChangedFor(nameof(IsPlayStoreBuildAndLocked))] + private bool _isUnlimitedUnlocked; + + public bool IsPlayStoreBuildAndUnlocked => IsPlayStoreBuild && IsUnlimitedUnlocked; + + public bool IsPlayStoreBuildAndLocked => IsPlayStoreBuild && !IsUnlimitedUnlocked; + + public string LicenceSectionHeader => IsPlayStoreBuild ? "Licence & Statistics" : "Personal Reading Statistics"; + + [ObservableProperty] + private int _komgaDownloadsUsed; + + [ObservableProperty] + private int _komgaViewsUsed; + + [ObservableProperty] + private ObservableCollection _localFormatStatuses = []; + + [ObservableProperty] + private int _totalPagesRead; + + [ObservableProperty] + private int _totalComicsOpened; + + [ObservableProperty] + private int _totalLocalImports; + + [ObservableProperty] + private int _totalKomgaDownloads; + + public bool IsPlayStoreBuild => +#if PLAY_STORE_BUILD + true; +#else + false; +#endif + + public SettingsViewModel( + SettingsService settingsService, + LibraryService libraryService, + LocalizationService localizationService, + IExternalLinkService externalLinkService, + UpdateService updateService, + TrialService trialService, + DatabaseService databaseService) { _settingsService = settingsService; _libraryService = libraryService; _localizationService = localizationService; _externalLinkService = externalLinkService; _updateService = updateService; + _trialService = trialService; + _databaseService = databaseService; Title = "Settings"; _settingsService.SettingsChanged += (_, settings) => { @@ -336,6 +390,7 @@ public SettingsViewModel(SettingsService settingsService, LibraryService library ReplaceSectionCollection(LibrarySections, settings.LibrarySections); ReplaceSectionCollection(KomgaSections, settings.KomgaSections); OnPropertyChanged(nameof(ActiveServerId)); + _ = LoadTrialStatusAndStatsAsync(); }); }; } @@ -548,6 +603,7 @@ private void LoadServers() ReplaceSectionCollection(LibrarySections, _appSettings.LibrarySections); ReplaceSectionCollection(KomgaSections, _appSettings.KomgaSections); + _ = LoadTrialStatusAndStatsAsync(); } partial void OnSyncReadProgressChanged(bool value) @@ -1164,4 +1220,100 @@ targetCollection is null || await PersistSectionLayoutAsync(section); } + + [RelayCommand] + private void RequestUnlock() + { + _trialService.RequestPremiumUnlock(); + } + + public async Task LoadTrialStatusAndStatsAsync() + { + IsUnlimitedUnlocked = _trialService.IsUnlimitedUnlocked; + + // Load stats from database + TotalPagesRead = await _databaseService.GetUsageCountAsync("PagesRead"); + TotalComicsOpened = await _databaseService.GetUsageCountAsync("ComicOpen"); + TotalLocalImports = await _databaseService.GetUsageCountAsync("LocalImport"); + TotalKomgaDownloads = await _databaseService.GetUsageCountAsync("KomgaDownload"); + + if (!IsUnlimitedUnlocked) + { + // Load trial limits + var settings = _settingsService.LoadSettings(); + var comics = await _databaseService.GetComicsAsync(); + + KomgaDownloadsUsed = comics.Count(c => c.Source == ComicSource.Komga); + KomgaViewsUsed = settings.PermanentViewedKomgaBookIds.Count; + + var formats = new[] { "cbz", "cbr", "cbt", "cb7", "epub", "pdf" }; + var formatList = new List(); + + foreach (var fmt in formats) + { + var imports = comics.Count(c => + { + if (c.Source == ComicSource.Komga) return false; + var cExt = Path.GetExtension(c.FilePath)?.TrimStart('.')?.ToLowerInvariant(); + return cExt == fmt; + }); + + var views = settings.PermanentViewedLocalPaths.Count(p => + { + var cExt = Path.GetExtension(p)?.TrimStart('.')?.ToLowerInvariant(); + return cExt == fmt; + }); + + formatList.Add(new FormatTrialStatus(fmt.ToUpperInvariant(), imports, views)); + } + + Dispatcher.UIThread.Post(() => + { + LocalFormatStatuses.Clear(); + foreach (var status in formatList) + { + LocalFormatStatuses.Add(status); + } + }); + } + } + + [RelayCommand] + private void ReportIssue() + { + string platform = "Unknown"; + if (System.OperatingSystem.IsAndroid()) platform = "Android"; + else if (System.OperatingSystem.IsWindows()) platform = "Windows"; + else if (System.OperatingSystem.IsMacOS()) platform = "macOS"; + else if (System.OperatingSystem.IsLinux()) platform = "Linux"; + else if (System.OperatingSystem.IsIOS()) platform = "iOS"; + + var osDesc = System.Runtime.InteropServices.RuntimeInformation.OSDescription; + var version = AppVersion; + + var body = $""" +**Describe the bug** +[A clear and concise description of what the bug is.] + +**To Reproduce** +1. Go to '...' +2. Click on '...' +3. Scroll down to '...' +4. See error + +**Environment Details:** +- App Version: {version} +- Platform: {platform} +- OS Description: {osDesc} +"""; + + var url = $"https://github.com/dapplo/StripWolf/issues/new?body={System.Uri.EscapeDataString(body)}"; + _externalLinkService.OpenUrl(url); + } +} + +public record FormatTrialStatus(string Format, int ImportsUsed, int ViewsUsed) +{ + public int ImportsRemaining => Math.Max(0, 2 - ImportsUsed); + public int ViewsRemaining => Math.Max(0, 2 - ViewsUsed); } diff --git a/src/StripWolf/StripWolf.Core/Views/MainView.axaml b/src/StripWolf/StripWolf.Core/Views/MainView.axaml index 2764978..cccafbf 100644 --- a/src/StripWolf/StripWolf.Core/Views/MainView.axaml +++ b/src/StripWolf/StripWolf.Core/Views/MainView.axaml @@ -252,5 +252,60 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + From c8da2dfc42218848ef2e44b677379eee9a09350f Mon Sep 17 00:00:00 2001 From: Robin Krom Date: Mon, 15 Jun 2026 00:00:00 +0200 Subject: [PATCH 3/5] Added a PRIVACY.md --- PRIVACY.md | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) create mode 100644 PRIVACY.md diff --git a/PRIVACY.md b/PRIVACY.md new file mode 100644 index 0000000..d988b41 --- /dev/null +++ b/PRIVACY.md @@ -0,0 +1,30 @@ +# Privacy Policy for StripWolf + +**Effective Date: June 14, 2026** + +This privacy policy governs your use of the software application **StripWolf** ("Application") for mobile and desktop devices. StripWolf is a comic book reader that supports local files and self-hosted server integrations (such as Komga). + +## 1. Information Collection and Use +StripWolf is designed with a "local-first" privacy model. + +* **No Personal Data Collection**: We do not collect, harvest, or transmit any personally identifiable information (PII) or usage data to our servers. +* **Local Data Storage**: All library data, reading progress, login credentials for self-hosted servers, and personal reading statistics are stored strictly on your local device. +* **Self-Hosted Integrations**: If you connect the Application to a self-hosted server (e.g., Komga), your server address and credentials are encrypted and stored locally. Connections are made directly between your device and your server; we have no access to this traffic. + +## 2. In-App Purchases & Payments +The Application offers an optional premium unlock. +* Payment transactions are processed entirely and securely by Google Play Billing (Google Play Store). +* We do not collect, process, or store your payment details (such as credit card numbers or billing addresses). + +## 3. Data Backups +If you enable Android Auto Backup on your device, a copy of the Application's settings and credentials may be backed up to your personal Google Drive account. This backup data is encrypted and subject to Google's standard backup terms and privacy policies. + +## 4. Crash Logs & Issue Reporting +If you choose to submit a bug report via the "Report an Issue" button, the Application pre-populates a template containing: +* Your Operating System type and version (e.g. Android 13, Windows 10) +* The version of StripWolf you are running +No personal files, browsing history, or private data are attached. You will have full visibility of the report text before submitting it to our public GitHub issue tracker. + +## 5. Contact Us +If you have any questions regarding privacy while using the Application, please contact us via the GitHub project repository: +https://github.com/dapplo/StripWolf From e7c668298d392d7ddcb7c00509d5e77baeec7d8e Mon Sep 17 00:00:00 2001 From: Robin Krom Date: Mon, 15 Jun 2026 22:57:32 +0200 Subject: [PATCH 4/5] Changed the trail to allow 5 instead of 2 comics per possibility. Also translated all the texts. --- src/StripWolf/StripWolf.Core/Resources/Loc.cs | 27 +++++++ .../StripWolf.Core/Resources/Strings.de.resx | 77 ++++++++++++++++++- .../StripWolf.Core/Resources/Strings.es.resx | 77 ++++++++++++++++++- .../StripWolf.Core/Resources/Strings.fr.resx | 77 ++++++++++++++++++- .../StripWolf.Core/Resources/Strings.nl.resx | 77 ++++++++++++++++++- .../StripWolf.Core/Resources/Strings.resx | 77 ++++++++++++++++++- .../StripWolf.Core/Services/TrialService.cs | 18 +++-- .../ViewModels/ReaderViewModel.cs | 2 +- .../ViewModels/SettingsViewModel.cs | 13 +++- .../StripWolf.Core/Views/MainView.axaml | 16 ++-- .../StripWolf.Core/Views/SettingsView.axaml | 70 ++++++++--------- 11 files changed, 471 insertions(+), 60 deletions(-) diff --git a/src/StripWolf/StripWolf.Core/Resources/Loc.cs b/src/StripWolf/StripWolf.Core/Resources/Loc.cs index dff6a58..5ca5a58 100644 --- a/src/StripWolf/StripWolf.Core/Resources/Loc.cs +++ b/src/StripWolf/StripWolf.Core/Resources/Loc.cs @@ -357,4 +357,31 @@ private string GetString([CallerMemberName] string? key = null) public string WelcomeExperienceActivityHint => GetString(); public string WelcomeExperienceSettingsHint => GetString(); public string WelcomeExperienceSupportHint => GetString(); + + // Google Play Store Trial limits and stats translations + public string TrialUnlockPremiumFeatures => GetString(); + public string TrialReachedLimitsDesc => GetString(); + public string TrialUnlimitedLocalImports => GetString(); + public string TrialUnlimitedKomga => GetString(); + public string TrialStatsDashboard => GetString(); + public string TrialSupportsDevelopment => GetString(); + public string TrialUnlockButton => GetString(); + public string TrialKeepTesting => GetString(); + public string TrialLimitReachedError => GetString(); + public string TrialEdition => GetString(); + public string TrialUpgradeDesc => GetString(); + public string TrialLocked => GetString(); + public string TrialKomgaLimitsDesc => GetString(); + public string TrialLocalLimitsTitle => GetString(); + public string TrialPremiumEdition => GetString(); + public string TrialAllFeaturesUnlocked => GetString(); + public string TrialUnlocked => GetString(); + public string TrialPersonalReadingStats => GetString(); + public string TrialLicenceAndStats => GetString(); + public string TrialViews => GetString(); + public string TrialPagesRead => GetString(); + public string TrialComicsOpened => GetString(); + public string TrialLocalImports => GetString(); + public string TrialKomgaDownloads => GetString(); + public string TrialKomgaServerIntegration => GetString(); } diff --git a/src/StripWolf/StripWolf.Core/Resources/Strings.de.resx b/src/StripWolf/StripWolf.Core/Resources/Strings.de.resx index 20ea345..b32d154 100644 --- a/src/StripWolf/StripWolf.Core/Resources/Strings.de.resx +++ b/src/StripWolf/StripWolf.Core/Resources/Strings.de.resx @@ -1,4 +1,4 @@ - + @@ -963,4 +963,79 @@ Fertig + + Sie haben einige Limits der Testversion erreicht. Bitte schalten Sie Premium frei, um diesen Comic zu lesen. + + + Persönliches Lese-Statistik-Dashboard + + + Geöffnete Comics + + + Persönliche Lesestatistik + + + Premium freischalten (Einmaliger Kauf) + + + Unbegrenzte Downloads & Ansichten vom Komga-Server + + + Freigeschaltet + + + Premium-Funktionen freischalten + + + Lizenz & Statistik + + + Gesperrt + + + Gelesene Seiten + + + Testversion der lokalen Bibliothek (Importe & Ansichten pro Format) + + + Premium-Edition + + + Lokale Importe + + + Kostenlose Version weiter testen + + + Führen Sie ein Upgrade auf Premium durch, um unbegrenzte Importe und Ansichten zu erhalten. + + + Sie haben einige Limits der Testversion in der kostenlosen Version erreicht. Um unbegrenzt Comics zu lesen, herunterzuladen und zu importieren, unterstützen Sie die App, indem Sie das Premium-Upgrade erwerben! + + + Testversion + + + Unterstützt die laufende Entwicklung & Updates + + + Ansichten + + + Komga-Downloads + + + Unbegrenzte lokale Bibliotheks-Importe + + + Limit für aktive Downloads & permanente Ansichten + + + Vielen Dank für Ihre Unterstützung von StripWolf! Alle Funktionen freigeschaltet. + + + Komga-Server-Integration + \ No newline at end of file diff --git a/src/StripWolf/StripWolf.Core/Resources/Strings.es.resx b/src/StripWolf/StripWolf.Core/Resources/Strings.es.resx index 6db10d0..d4233e4 100644 --- a/src/StripWolf/StripWolf.Core/Resources/Strings.es.resx +++ b/src/StripWolf/StripWolf.Core/Resources/Strings.es.resx @@ -1,4 +1,4 @@ - + @@ -963,4 +963,79 @@ Finalizar + + Ha alcanzado algunos límites de la versión de prueba. Desbloquee premium para leer este cómic. + + + Panel de estadísticas de lectura personal + + + Cómics abiertos + + + Estadísticas de lectura personal + + + Desbloquear Premium (Compra única) + + + Descargas y visualizaciones ilimitadas del servidor Komga + + + Desbloqueado + + + Desbloquear funciones premium + + + Licencia y estadísticas + + + Bloqueado + + + Páginas leídas + + + Prueba de biblioteca local (Importaciones y vistas por formato) + + + Edición Premium + + + Importaciones locales + + + Seguir probando la versión gratuita + + + Actualice a Premium para obtener importaciones y visualizaciones ilimitadas. + + + Ha alcanzado algunos límites de la versión de prueba en la versión gratuita. Para leer, descargar e importar cómics ilimitados, ¡apoye la aplicación comprando el desbloqueo premium! + + + Edición de prueba + + + Apoya el desarrollo continuo y las actualizaciones + + + Vistas + + + Descargas de Komga + + + Importaciones locales ilimitadas a la biblioteca + + + Límite de descargas activas y vistas permanentes + + + ¡Gracias por apoyar a StripWolf! Todas las funciones desbloqueadas. + + + Integración con el servidor Komga + \ No newline at end of file diff --git a/src/StripWolf/StripWolf.Core/Resources/Strings.fr.resx b/src/StripWolf/StripWolf.Core/Resources/Strings.fr.resx index 92eca3b..ac683fe 100644 --- a/src/StripWolf/StripWolf.Core/Resources/Strings.fr.resx +++ b/src/StripWolf/StripWolf.Core/Resources/Strings.fr.resx @@ -1,4 +1,4 @@ - + @@ -963,4 +963,79 @@ Terminer + + Vous avez atteint certaines limites de la version d'essai. Veuillez débloquer la version premium pour lire cette bande dessinée. + + + Tableau de bord des statistiques de lecture personnelles + + + Bandes dessinées ouvertes + + + Statistiques de lecture personnelles + + + Débloquer Premium (Achat unique) + + + Téléchargements et visionnages illimités sur le serveur Komga + + + Débloqué + + + Débloquer les fonctionnalités premium + + + Licence et statistiques + + + Verrouillé + + + Pages lues + + + Essai de bibliothèque locale (Imports et lectures par format) + + + Édition Premium + + + Importations locales + + + Continuer à tester la version gratuite + + + Passez à la version Premium pour obtenir des imports et des lectures illimités. + + + Vous avez atteint certaines limites de la version d'essai dans la version gratuite. Pour lire, télécharger et importer des bandes dessinées en illimité, soutenez l'application en achetant le déverrouillage premium ! + + + Édition d'essai + + + Soutient le développement et les mises à jour en cours + + + Visionnages + + + Téléchargements Komga + + + Imports de bibliothèque locale illimités + + + Limite des téléchargements actifs et des lectures permanentes + + + Merci de soutenir StripWolf ! Toutes les fonctionnalités sont débloquées. + + + Intégration du serveur Komga + \ No newline at end of file diff --git a/src/StripWolf/StripWolf.Core/Resources/Strings.nl.resx b/src/StripWolf/StripWolf.Core/Resources/Strings.nl.resx index 7923b5d..58b8452 100644 --- a/src/StripWolf/StripWolf.Core/Resources/Strings.nl.resx +++ b/src/StripWolf/StripWolf.Core/Resources/Strings.nl.resx @@ -1,4 +1,4 @@ - + @@ -973,4 +973,79 @@ Voltooien + + Je hebt enkele limieten van de proefversie bereikt. Ontgrendel premium om deze strip te lezen. + + + Persoonlijk leesstatistieken dashboard + + + Geopende strips + + + Persoonlijke leesstatistieken + + + Premium ontgrendelen (Eenmalige aankoop) + + + Onbekt downloaden & bekijken vanaf de Komga-server + + + Ontgrendeld + + + Premium functies ontgrendelen + + + Licentie & statistieken + + + Vergrendeld + + + Gelezen pagina's + + + Lokale bibliotheek proefversie (Importen & weergaven per formaat) + + + Premium-editie + + + Lokale importen + + + De gratis versie blijven proberen + + + Upgrade naar Premium voor onbeperkt importeren en bekijken. + + + Je hebt enkele limieten van de proefversie in de gratis versie bereikt. Om onbeperkt strips te lezen, downloaden en importeren, kun je de app steunen door de premium ontgrendeling te kopen! + + + Proefversie + + + Ondersteunt voortdurende ontwikkeling & updates + + + Weergaven + + + Komga downloads + + + Onbeperkte lokale bibliotheekimporten + + + Limiet voor actieve downloads & permanente weergaven + + + Bedankt voor het steunen van StripWolf! Alle functies ontgrendeld. + + + Komga server integratie + \ No newline at end of file diff --git a/src/StripWolf/StripWolf.Core/Resources/Strings.resx b/src/StripWolf/StripWolf.Core/Resources/Strings.resx index 4a158a2..6692c0e 100644 --- a/src/StripWolf/StripWolf.Core/Resources/Strings.resx +++ b/src/StripWolf/StripWolf.Core/Resources/Strings.resx @@ -1,4 +1,4 @@ - + @@ -1075,4 +1075,79 @@ Finish + + You have reached some limits of the trial. Please unlock premium to read this comic. + + + Personal reading statistics dashboard + + + Comics Opened + + + Personal Reading Statistics + + + Unlock Premium (One-Time Purchase) + + + Unlimited Komga server downloads & viewing + + + Unlocked + + + Unlock Premium Features + + + Licence & Statistics + + + Locked + + + Pages Read + + + Local Library Trial (Imports & Views per format) + + + Premium Edition + + + Local Imports + + + Keep Testing Free Version + + + Upgrade to Premium to get unlimited imports and views. + + + You have reached some limits of the trial in the free version. To read, download, and import unlimited comics, support the app by purchasing the premium unlock! + + + Trial Edition + + + Supports ongoing development & updates + + + Views + + + Komga Downloads + + + Unlimited local library imports + + + Active downloads & permanent views limit + + + Thank you for supporting StripWolf! All features unlocked. + + + Komga Server Integration + \ No newline at end of file diff --git a/src/StripWolf/StripWolf.Core/Services/TrialService.cs b/src/StripWolf/StripWolf.Core/Services/TrialService.cs index 04554cc..1059460 100644 --- a/src/StripWolf/StripWolf.Core/Services/TrialService.cs +++ b/src/StripWolf/StripWolf.Core/Services/TrialService.cs @@ -12,6 +12,8 @@ namespace StripWolf.Core.Services; /// public class TrialService { + public const int MaxTrialLimit = 5; + private readonly SettingsService _settingsService; private readonly DatabaseService _databaseService; @@ -129,7 +131,7 @@ public bool IsUnlimitedUnlocked } /// - /// Checks if a local import of a specific file type is allowed (capacity limit: max 2). + /// Checks if a local import of a specific file type is allowed (capacity limit: max MaxTrialLimit). /// public async Task CanImportLocalAsync(string filePath) { @@ -150,11 +152,11 @@ public async Task CanImportLocalAsync(string filePath) return cExt == ext; }); - return currentCount < 2; + return currentCount < MaxTrialLimit; } /// - /// Checks if a Komga download is allowed (capacity limit: max 2). + /// Checks if a Komga download is allowed (capacity limit: max MaxTrialLimit). /// public async Task CanDownloadKomgaAsync() { @@ -164,11 +166,11 @@ public async Task CanDownloadKomgaAsync() var comics = await _databaseService.GetComicsAsync(); var currentCount = comics.Count(c => c.Source == ComicSource.Komga); - return currentCount < 2; + return currentCount < MaxTrialLimit; } /// - /// Checks if opening a local comic is allowed (permanent view limit: max 2 unique files per format). + /// Checks if opening a local comic is allowed (permanent view limit: max MaxTrialLimit unique files per format). /// public async Task CanOpenLocalAsync(string filePath) { @@ -190,11 +192,11 @@ public async Task CanOpenLocalAsync(string filePath) var currentViewedCount = settings.PermanentViewedLocalPaths .Count(p => Path.GetExtension(p)?.TrimStart('.')?.ToLowerInvariant() == ext); - return currentViewedCount < 2; + return currentViewedCount < MaxTrialLimit; } /// - /// Checks if opening a Komga comic is allowed (permanent view limit: max 2 unique files). + /// Checks if opening a Komga comic is allowed (permanent view limit: max MaxTrialLimit unique files). /// public async Task CanOpenKomgaAsync(int bookId) { @@ -208,7 +210,7 @@ public async Task CanOpenKomgaAsync(int bookId) return true; } - return settings.PermanentViewedKomgaBookIds.Count < 2; + return settings.PermanentViewedKomgaBookIds.Count < MaxTrialLimit; } /// diff --git a/src/StripWolf/StripWolf.Core/ViewModels/ReaderViewModel.cs b/src/StripWolf/StripWolf.Core/ViewModels/ReaderViewModel.cs index c0387fe..099de20 100644 --- a/src/StripWolf/StripWolf.Core/ViewModels/ReaderViewModel.cs +++ b/src/StripWolf/StripWolf.Core/ViewModels/ReaderViewModel.cs @@ -903,7 +903,7 @@ public async Task LoadComicAsync(int comicId) if (!allowed) { IsBusy = false; - ErrorMessage = "You have reached some limits of the trial. Please unlock premium to read this comic."; + ErrorMessage = Resources.Loc.Instance.TrialLimitReachedError; _trialService.RequestPremiumUnlock(); // Navigate back or close the reader since opening is blocked Dispatcher.UIThread.Post(() => _ = GoBackAsync()); diff --git a/src/StripWolf/StripWolf.Core/ViewModels/SettingsViewModel.cs b/src/StripWolf/StripWolf.Core/ViewModels/SettingsViewModel.cs index 32db9ce..23b886f 100644 --- a/src/StripWolf/StripWolf.Core/ViewModels/SettingsViewModel.cs +++ b/src/StripWolf/StripWolf.Core/ViewModels/SettingsViewModel.cs @@ -335,14 +335,19 @@ public string AppVersion public bool IsPlayStoreBuildAndLocked => IsPlayStoreBuild && !IsUnlimitedUnlocked; - public string LicenceSectionHeader => IsPlayStoreBuild ? "Licence & Statistics" : "Personal Reading Statistics"; + public string LicenceSectionHeader => IsPlayStoreBuild ? Resources.Loc.Instance.TrialLicenceAndStats : Resources.Loc.Instance.TrialPersonalReadingStats; [ObservableProperty] + [NotifyPropertyChangedFor(nameof(KomgaDownloadsDisplay))] private int _komgaDownloadsUsed; [ObservableProperty] + [NotifyPropertyChangedFor(nameof(KomgaViewsDisplay))] private int _komgaViewsUsed; + public string KomgaDownloadsDisplay => $"{KomgaDownloadsUsed}/{TrialService.MaxTrialLimit}"; + public string KomgaViewsDisplay => $"{KomgaViewsUsed}/{TrialService.MaxTrialLimit}"; + [ObservableProperty] private ObservableCollection _localFormatStatuses = []; @@ -1314,6 +1319,8 @@ 4. See error public record FormatTrialStatus(string Format, int ImportsUsed, int ViewsUsed) { - public int ImportsRemaining => Math.Max(0, 2 - ImportsUsed); - public int ViewsRemaining => Math.Max(0, 2 - ViewsUsed); + public int ImportsRemaining => Math.Max(0, TrialService.MaxTrialLimit - ImportsUsed); + public int ViewsRemaining => Math.Max(0, TrialService.MaxTrialLimit - ViewsUsed); + public string ImportsDisplay => $"{ImportsUsed}/{TrialService.MaxTrialLimit}"; + public string ViewsDisplay => $"{ViewsUsed}/{TrialService.MaxTrialLimit}"; } diff --git a/src/StripWolf/StripWolf.Core/Views/MainView.axaml b/src/StripWolf/StripWolf.Core/Views/MainView.axaml index cccafbf..283c0a7 100644 --- a/src/StripWolf/StripWolf.Core/Views/MainView.axaml +++ b/src/StripWolf/StripWolf.Core/Views/MainView.axaml @@ -261,34 +261,34 @@ BorderBrush="{DynamicResource SystemControlForegroundBaseMediumLowBrush}" BorderThickness="1"> - + - - + - + - + - + - @@ -182,18 +182,18 @@ - - + + - + - + - - - + + + @@ -201,7 +201,7 @@ - + @@ -209,7 +209,7 @@ - + @@ -217,7 +217,7 @@ - + @@ -225,7 +225,7 @@ - + @@ -244,7 +244,7 @@ - + @@ -252,7 +252,7 @@ - + @@ -260,7 +260,7 @@ - + @@ -268,7 +268,7 @@ - + From 0edf4f9a2002e2f24b4653a5c78fd23b7c529761 Mon Sep 17 00:00:00 2001 From: Robin Krom Date: Mon, 15 Jun 2026 23:14:52 +0200 Subject: [PATCH 5/5] Fixed some encoding / translation issues reduced some array Initialize code, all as remarked by gemini. --- src/StripWolf/StripWolf.Core/App.axaml.cs | 11 ++++ .../StripWolf.Core/Data/DatabaseService.cs | 1 - .../StripWolf.Core/Resources/Strings.de.resx | 16 ++--- .../StripWolf.Core/Resources/Strings.es.resx | 22 +++---- .../StripWolf.Core/Resources/Strings.fr.resx | 38 +++++------ .../StripWolf.Core/Resources/Strings.nl.resx | 2 +- .../StripWolf.Core/Services/TrialService.cs | 63 ++++++++++++------- .../StripWolf.Core/StripWolf.Core.csproj | 1 + .../ViewModels/SettingsViewModel.cs | 2 +- 9 files changed, 94 insertions(+), 62 deletions(-) diff --git a/src/StripWolf/StripWolf.Core/App.axaml.cs b/src/StripWolf/StripWolf.Core/App.axaml.cs index 12d14f2..5708314 100644 --- a/src/StripWolf/StripWolf.Core/App.axaml.cs +++ b/src/StripWolf/StripWolf.Core/App.axaml.cs @@ -63,6 +63,12 @@ public partial class App : Application /// public static Action? RegisterFullScreenService { get; set; } + /// + /// Action to register the platform-specific billing service. + /// Set this before Initialize() is called if you need a custom implementation (e.g., on Android). + /// + public static Action? RegisterBillingService { get; set; } + public override void Initialize() { AvaloniaXamlLoader.Load(this); @@ -186,6 +192,11 @@ private static void ConfigureServices(IServiceCollection services) services.AddSingleton(); services.AddSingleton(); + if (RegisterBillingService != null) + { + RegisterBillingService(services); + } + // Register platform-specific PDF renderer // Use the custom registration action if set (e.g., for Android), otherwise default to nothing if (RegisterPdfRenderer != null) diff --git a/src/StripWolf/StripWolf.Core/Data/DatabaseService.cs b/src/StripWolf/StripWolf.Core/Data/DatabaseService.cs index 372a94a..a2f4ce2 100644 --- a/src/StripWolf/StripWolf.Core/Data/DatabaseService.cs +++ b/src/StripWolf/StripWolf.Core/Data/DatabaseService.cs @@ -94,7 +94,6 @@ private async Task GetDatabaseAsync() await _database.CreateTableAsync(); await _database.CreateTableAsync(); await _database.ExecuteAsync("CREATE INDEX IF NOT EXISTS IX_Comic_FilePath ON Comic(FilePath)"); - await _database.ExecuteAsync("CREATE INDEX IF NOT EXISTS IX_UsageStats_Metric ON UsageStats(Metric)"); await _database.ExecuteAsync("CREATE INDEX IF NOT EXISTS IX_EpubConversionState_Status ON EpubConversionState(Status)"); _isInitialized = true; diff --git a/src/StripWolf/StripWolf.Core/Resources/Strings.de.resx b/src/StripWolf/StripWolf.Core/Resources/Strings.de.resx index b32d154..20d458b 100644 --- a/src/StripWolf/StripWolf.Core/Resources/Strings.de.resx +++ b/src/StripWolf/StripWolf.Core/Resources/Strings.de.resx @@ -967,13 +967,13 @@ Sie haben einige Limits der Testversion erreicht. Bitte schalten Sie Premium frei, um diesen Comic zu lesen. - Persönliches Lese-Statistik-Dashboard + Persönliches Lese-Statistik-Dashboard - Geöffnete Comics + Geöffnete Comics - Persönliche Lesestatistik + Persönliche Lesestatistik Premium freischalten (Einmaliger Kauf) @@ -1009,16 +1009,16 @@ Kostenlose Version weiter testen - Führen Sie ein Upgrade auf Premium durch, um unbegrenzte Importe und Ansichten zu erhalten. + Führen Sie ein Upgrade auf Premium durch, um unbegrenzte Importe und Ansichten zu erhalten. - Sie haben einige Limits der Testversion in der kostenlosen Version erreicht. Um unbegrenzt Comics zu lesen, herunterzuladen und zu importieren, unterstützen Sie die App, indem Sie das Premium-Upgrade erwerben! + Sie haben einige Limits der Testversion in der kostenlosen Version erreicht. Um unbegrenzt Comics zu lesen, herunterzuladen und zu importieren, unterstützen Sie die App, indem Sie das Premium-Upgrade erwerben! Testversion - Unterstützt die laufende Entwicklung & Updates + Unterstützt die laufende Entwicklung & Updates Ansichten @@ -1030,10 +1030,10 @@ Unbegrenzte lokale Bibliotheks-Importe - Limit für aktive Downloads & permanente Ansichten + Limit für aktive Downloads & permanente Ansichten - Vielen Dank für Ihre Unterstützung von StripWolf! Alle Funktionen freigeschaltet. + Vielen Dank für Ihre Unterstützung von StripWolf! Alle Funktionen freigeschaltet. Komga-Server-Integration diff --git a/src/StripWolf/StripWolf.Core/Resources/Strings.es.resx b/src/StripWolf/StripWolf.Core/Resources/Strings.es.resx index d4233e4..f33a963 100644 --- a/src/StripWolf/StripWolf.Core/Resources/Strings.es.resx +++ b/src/StripWolf/StripWolf.Core/Resources/Strings.es.resx @@ -964,19 +964,19 @@ Finalizar - Ha alcanzado algunos límites de la versión de prueba. Desbloquee premium para leer este cómic. + Ha alcanzado algunos límites de la versión de prueba. Desbloquee premium para leer este cómic. - Panel de estadísticas de lectura personal + Panel de estadísticas de lectura personal - Cómics abiertos + Cómics abiertos Estadísticas de lectura personal - Desbloquear Premium (Compra única) + Desbloquear Premium (Compra única) Descargas y visualizaciones ilimitadas del servidor Komga @@ -988,34 +988,34 @@ Desbloquear funciones premium - Licencia y estadísticas + Licencia y estadísticas Bloqueado - Páginas leídas + Páginas leídas Prueba de biblioteca local (Importaciones y vistas por formato) - Edición Premium + Edición Premium Importaciones locales - Seguir probando la versión gratuita + Seguir probando la versión gratuita Actualice a Premium para obtener importaciones y visualizaciones ilimitadas. - Ha alcanzado algunos límites de la versión de prueba en la versión gratuita. Para leer, descargar e importar cómics ilimitados, ¡apoye la aplicación comprando el desbloqueo premium! + Ha alcanzado algunos límites de la versión de prueba en la versión gratuita. Para leer, descargar e importar cómics ilimitados, ¡apoye la aplicación comprando el desbloqueo premium! - Edición de prueba + Edición de prueba Apoya el desarrollo continuo y las actualizaciones @@ -1036,6 +1036,6 @@ ¡Gracias por apoyar a StripWolf! Todas las funciones desbloqueadas. - Integración con el servidor Komga + Integración con el servidor Komga \ No newline at end of file diff --git a/src/StripWolf/StripWolf.Core/Resources/Strings.fr.resx b/src/StripWolf/StripWolf.Core/Resources/Strings.fr.resx index ac683fe..a3b73ab 100644 --- a/src/StripWolf/StripWolf.Core/Resources/Strings.fr.resx +++ b/src/StripWolf/StripWolf.Core/Resources/Strings.fr.resx @@ -964,78 +964,78 @@ Terminer - Vous avez atteint certaines limites de la version d'essai. Veuillez débloquer la version premium pour lire cette bande dessinée. + Vous avez atteint certaines limites de la version d'essai. Veuillez débloquer la version premium pour lire cette bande dessinée. Tableau de bord des statistiques de lecture personnelles - Bandes dessinées ouvertes + Bandes dessinées ouvertes Statistiques de lecture personnelles - Débloquer Premium (Achat unique) + Débloquer Premium (Achat unique) - Téléchargements et visionnages illimités sur le serveur Komga + Téléchargements et visionnages illimités sur le serveur Komga - Débloqué + Débloqué - Débloquer les fonctionnalités premium + Débloquer les fonctionnalités premium Licence et statistiques - Verrouillé + Verrouillé Pages lues - Essai de bibliothèque locale (Imports et lectures par format) + Essai de bibliothèque locale (Imports et lectures par format) - Édition Premium + Édition Premium Importations locales - Continuer à tester la version gratuite + Continuer à tester la version gratuite - Passez à la version Premium pour obtenir des imports et des lectures illimités. + Passez à la version Premium pour obtenir des imports et des lectures illimités. - Vous avez atteint certaines limites de la version d'essai dans la version gratuite. Pour lire, télécharger et importer des bandes dessinées en illimité, soutenez l'application en achetant le déverrouillage premium ! + Vous avez atteint certaines limites de la version d'essai dans la version gratuite. Pour lire, télécharger et importer des bandes dessinées en illimité, soutenez l'application en achetant le déverrouillage premium ! - Édition d'essai + Édition d'essai - Soutient le développement et les mises à jour en cours + Soutient le développement et les mises à jour en cours Visionnages - Téléchargements Komga + Téléchargements Komga - Imports de bibliothèque locale illimités + Imports de bibliothèque locale illimités - Limite des téléchargements actifs et des lectures permanentes + Limite des téléchargements actifs et des lectures permanentes - Merci de soutenir StripWolf ! Toutes les fonctionnalités sont débloquées. + Merci de soutenir StripWolf ! Toutes les fonctionnalités sont débloquées. - Intégration du serveur Komga + Intégration du serveur Komga \ No newline at end of file diff --git a/src/StripWolf/StripWolf.Core/Resources/Strings.nl.resx b/src/StripWolf/StripWolf.Core/Resources/Strings.nl.resx index 58b8452..3d286b4 100644 --- a/src/StripWolf/StripWolf.Core/Resources/Strings.nl.resx +++ b/src/StripWolf/StripWolf.Core/Resources/Strings.nl.resx @@ -989,7 +989,7 @@ Premium ontgrendelen (Eenmalige aankoop) - Onbekt downloaden & bekijken vanaf de Komga-server + Onbeperkt downloaden & bekijken vanaf de Komga-server Ontgrendeld diff --git a/src/StripWolf/StripWolf.Core/Services/TrialService.cs b/src/StripWolf/StripWolf.Core/Services/TrialService.cs index 1059460..dd73467 100644 --- a/src/StripWolf/StripWolf.Core/Services/TrialService.cs +++ b/src/StripWolf/StripWolf.Core/Services/TrialService.cs @@ -14,6 +14,13 @@ public class TrialService { public const int MaxTrialLimit = 5; + public static readonly System.Collections.Generic.HashSet AllowedFormats = new(StringComparer.OrdinalIgnoreCase) + { + "cbz", "cbr", "cbt", "cb7", "epub", "pdf" + }; + + private readonly System.Threading.SemaphoreSlim _settingsSemaphore = new(1, 1); + private readonly SettingsService _settingsService; private readonly DatabaseService _databaseService; @@ -87,35 +94,50 @@ private async void OnPageRead(object? sender, EventArgs e) private async void OnComicOpened(object? sender, ComicOpenedEventArgs e) { - await LogUsageAsync("ComicOpen", e.ComicId); - - if (!IsUnlimitedUnlocked) + try { - if (e.Source == ComicSource.Komga) + await LogUsageAsync("ComicOpen", e.ComicId); + + if (!IsUnlimitedUnlocked) { - if (int.TryParse(e.Identifier, out var bookId)) + await _settingsSemaphore.WaitAsync(); + try { - await _settingsService.UpdateSettingsAsync(s => + if (e.Source == ComicSource.Komga) { - if (!s.PermanentViewedKomgaBookIds.Contains(bookId)) + if (int.TryParse(e.Identifier, out var bookId)) { - s.PermanentViewedKomgaBookIds.Add(bookId); + await _settingsService.UpdateSettingsAsync(s => + { + if (!s.PermanentViewedKomgaBookIds.Contains(bookId)) + { + s.PermanentViewedKomgaBookIds.Add(bookId); + } + }); } - }); - } - } - else - { - var filename = Path.GetFileName(e.Identifier); - await _settingsService.UpdateSettingsAsync(s => - { - if (!s.PermanentViewedLocalPaths.Contains(filename)) + } + else { - s.PermanentViewedLocalPaths.Add(filename); + var filename = Path.GetFileName(e.Identifier); + await _settingsService.UpdateSettingsAsync(s => + { + if (!s.PermanentViewedLocalPaths.Contains(filename)) + { + s.PermanentViewedLocalPaths.Add(filename); + } + }); } - }); + } + finally + { + _settingsSemaphore.Release(); + } } } + catch + { + // Gracefully ignore settings update/I/O exceptions from async void event handler + } } public bool IsUnlimitedUnlocked @@ -140,8 +162,7 @@ public async Task CanImportLocalAsync(string filePath) var ext = Path.GetExtension(filePath)?.TrimStart('.')?.ToLowerInvariant(); if (string.IsNullOrEmpty(ext)) return true; - var allowedFormats = new[] { "cbz", "cbr", "cbt", "cb7", "epub", "pdf" }; - if (!allowedFormats.Contains(ext)) return true; + if (!AllowedFormats.Contains(ext)) return true; // Count how many local comics of this extension currently exist in the database library var comics = await _databaseService.GetComicsAsync(); diff --git a/src/StripWolf/StripWolf.Core/StripWolf.Core.csproj b/src/StripWolf/StripWolf.Core/StripWolf.Core.csproj index 07e5144..78d8baa 100644 --- a/src/StripWolf/StripWolf.Core/StripWolf.Core.csproj +++ b/src/StripWolf/StripWolf.Core/StripWolf.Core.csproj @@ -15,6 +15,7 @@ true true StripWolf.Core + true diff --git a/src/StripWolf/StripWolf.Core/ViewModels/SettingsViewModel.cs b/src/StripWolf/StripWolf.Core/ViewModels/SettingsViewModel.cs index 23b886f..9edcb6f 100644 --- a/src/StripWolf/StripWolf.Core/ViewModels/SettingsViewModel.cs +++ b/src/StripWolf/StripWolf.Core/ViewModels/SettingsViewModel.cs @@ -1251,7 +1251,7 @@ public async Task LoadTrialStatusAndStatsAsync() KomgaDownloadsUsed = comics.Count(c => c.Source == ComicSource.Komga); KomgaViewsUsed = settings.PermanentViewedKomgaBookIds.Count; - var formats = new[] { "cbz", "cbr", "cbt", "cb7", "epub", "pdf" }; + var formats = TrialService.AllowedFormats; var formatList = new List(); foreach (var fmt in formats)