From 3a612df3d3d75579b51e445526f8afb9a845485c Mon Sep 17 00:00:00 2001 From: edde746 <86283021+edde746@users.noreply.github.com> Date: Sat, 9 May 2026 11:47:37 +0200 Subject: [PATCH] chore: remove in-app review --- android/fastlane/Fastfile | 2 +- ios/Podfile.lock | 6 - ios/fastlane/Fastfile | 2 +- lib/main.dart | 8 +- lib/services/in_app_review_service.dart | 124 ------------------ macos/Flutter/GeneratedPluginRegistrant.swift | 2 - macos/Podfile.lock | 6 - pubspec.lock | 16 --- pubspec.yaml | 1 - test/services/in_app_review_service_test.dart | 114 ---------------- 10 files changed, 3 insertions(+), 278 deletions(-) delete mode 100644 lib/services/in_app_review_service.dart delete mode 100644 test/services/in_app_review_service_test.dart diff --git a/android/fastlane/Fastfile b/android/fastlane/Fastfile index 5d082be0..f20a6f58 100644 --- a/android/fastlane/Fastfile +++ b/android/fastlane/Fastfile @@ -22,7 +22,7 @@ platform :android do git_commit = `git rev-parse --short HEAD`.strip # Build the Flutter app - sh("cd #{ENV['PWD']}/.. && flutter build appbundle --dart-define=ENABLE_IN_APP_REVIEW=true --dart-define=ENABLE_SENTRY=true --dart-define=GIT_COMMIT=#{git_commit} --dart-define=SENTRY_ENVIRONMENT=play-store --obfuscate --split-debug-info=debug-info/android-aab") + sh("cd #{ENV['PWD']}/.. && flutter build appbundle --dart-define=ENABLE_SENTRY=true --dart-define=GIT_COMMIT=#{git_commit} --dart-define=SENTRY_ENVIRONMENT=play-store --obfuscate --split-debug-info=debug-info/android-aab") sh("cd #{ENV['PWD']}/.. && ./scripts/upload-symbols.sh android-aab") upload_to_play_store( diff --git a/ios/Podfile.lock b/ios/Podfile.lock index 5877e26d..07eef24b 100644 --- a/ios/Podfile.lock +++ b/ios/Podfile.lock @@ -11,8 +11,6 @@ PODS: - file_picker (0.0.1): - Flutter - Flutter (1.0.0) - - in_app_review (2.0.0): - - Flutter - os_media_controls (0.0.1): - Flutter - package_info_plus (0.4.5): @@ -64,7 +62,6 @@ DEPENDENCIES: - device_info_plus (from `.symlinks/plugins/device_info_plus/ios`) - file_picker (from `.symlinks/plugins/file_picker/ios`) - Flutter (from `Flutter`) - - in_app_review (from `.symlinks/plugins/in_app_review/ios`) - os_media_controls (from `.symlinks/plugins/os_media_controls/ios`) - package_info_plus (from `.symlinks/plugins/package_info_plus/ios`) - sentry_flutter (from `.symlinks/plugins/sentry_flutter/ios`) @@ -92,8 +89,6 @@ EXTERNAL SOURCES: :path: ".symlinks/plugins/file_picker/ios" Flutter: :path: Flutter - in_app_review: - :path: ".symlinks/plugins/in_app_review/ios" os_media_controls: :path: ".symlinks/plugins/os_media_controls/ios" package_info_plus: @@ -118,7 +113,6 @@ SPEC CHECKSUMS: device_info_plus: 21fcca2080fbcd348be798aa36c3e5ed849eefbe file_picker: 8fc6fe5e42585a217d44d22f79ec046cb8d81140 Flutter: cabc95a1d2626b1b06e7179b784ebcf0c0cde467 - in_app_review: 7dd1ea365263f834b8464673f9df72c80c17c937 os_media_controls: 94cc278f5802b82b2d6373003aeb511f96718b27 package_info_plus: af8e2ca6888548050f16fa2f1938db7b5a5df499 Sentry: d587a8fe91ca13503ecd69a1905f3e8a0fcf61be diff --git a/ios/fastlane/Fastfile b/ios/fastlane/Fastfile index beaf07f8..e696fe4a 100644 --- a/ios/fastlane/Fastfile +++ b/ios/fastlane/Fastfile @@ -9,7 +9,7 @@ platform :ios do git_commit = `git rev-parse --short HEAD`.strip # Build the Flutter app - sh("cd #{ENV['PWD']}/.. && flutter build ipa --dart-define=ENABLE_IN_APP_REVIEW=true --dart-define=ENABLE_SENTRY=true --dart-define=GIT_COMMIT=#{git_commit} --dart-define=SENTRY_ENVIRONMENT=app-store --split-debug-info=debug-info/ios") + sh("cd #{ENV['PWD']}/.. && flutter build ipa --dart-define=ENABLE_SENTRY=true --dart-define=GIT_COMMIT=#{git_commit} --dart-define=SENTRY_ENVIRONMENT=app-store --split-debug-info=debug-info/ios") # Upload debug symbols sh("cd #{ENV['PWD']}/.. && ./scripts/upload-symbols.sh ios") diff --git a/lib/main.dart b/lib/main.dart index 6b8f37c4..ce9db314 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -56,7 +56,6 @@ import 'watch_together/providers/watch_together_provider.dart'; import 'services/multi_server_manager.dart'; import 'services/offline_watch_sync_service.dart'; import 'services/data_aggregation_service.dart'; -import 'services/in_app_review_service.dart'; import 'services/server_registry.dart'; import 'services/download_manager_service.dart'; import 'services/pip_service.dart'; @@ -469,9 +468,6 @@ class _MainAppState extends State with WidgetsBindingObserver { return AppExitResponse.exit; }, ); - - // Start in-app review session tracking - InAppReviewService.instance.startSession(); } @override @@ -584,10 +580,9 @@ class _MainAppState extends State with WidgetsBindingObserver { void didChangeAppLifecycleState(AppLifecycleState state) { switch (state) { case AppLifecycleState.resumed: - // App came back to foreground - trigger sync check and start new session + // App came back to foreground - trigger sync check _offlineWatchSyncService.onAppResumed(); TraktSyncService.instance.flushQueue(); - InAppReviewService.instance.startSession(); // Re-probe servers — mobile OS may have dropped TCP connections during doze/sleep. // On desktop, resumed fires on every window focus (alt-tab), so apply a cooldown // to avoid piling up network probes from rapid alt-tabbing. @@ -610,7 +605,6 @@ class _MainAppState extends State with WidgetsBindingObserver { // Closing here would kill the Drift isolate channel while services // (sync, downloads, cache) still hold references to the executor. // SQLite WAL mode handles process death; desktop uses onExitRequested. - InAppReviewService.instance.endSession(); if (PlatformDetector.isDesktopOS()) { if (ProcessInfo.currentRss > 1024 * 1024 * 1024) { // 1GB diff --git a/lib/services/in_app_review_service.dart b/lib/services/in_app_review_service.dart deleted file mode 100644 index 5a782264..00000000 --- a/lib/services/in_app_review_service.dart +++ /dev/null @@ -1,124 +0,0 @@ -import 'dart:async'; -import 'dart:io' show Platform; -import 'package:in_app_review/in_app_review.dart'; -import 'package:shared_preferences/shared_preferences.dart'; -import '../utils/app_logger.dart'; -import 'base_shared_preferences_service.dart'; - -/// Service to manage in-app review prompts -/// Only enabled when ENABLE_IN_APP_REVIEW build flag is set -class InAppReviewService { - static final InAppReviewService _instance = InAppReviewService._(); - static InAppReviewService get instance => _instance; - - InAppReviewService._(); - - final InAppReview _inAppReview = InAppReview.instance; - - Future _getPrefs() => BaseSharedPreferencesService.sharedCache(); - - static const String _keyQualifyingSessionsCount = 'review_qualifying_sessions_count'; - static const String _keyLastPromptTime = 'review_last_prompt_time'; - - // Configuration - static const int _requiredSessions = 6; - static const Duration _minimumSessionDuration = Duration(minutes: 5); - static const Duration _promptCooldown = Duration(days: 60); - - DateTime? _sessionStartTime; - - static bool get isEnabled { - if (!Platform.isIOS && !Platform.isAndroid) { - return false; - } - return const bool.fromEnvironment('ENABLE_IN_APP_REVIEW', defaultValue: false); - } - - void startSession() { - if (!isEnabled) return; - _sessionStartTime = DateTime.now(); - appLogger.d('In-app review: Session started'); - // Prompt checks should run while the app is in the foreground. - unawaited(maybeRequestReview()); - } - - /// End the current session and check if it qualifies - /// Call this when app goes to background or is closed - Future endSession() async { - if (!isEnabled || _sessionStartTime == null) return; - - final sessionDuration = DateTime.now().difference(_sessionStartTime!); - _sessionStartTime = null; - - if (sessionDuration >= _minimumSessionDuration) { - await _incrementQualifyingSessions(); - appLogger.d('In-app review: Qualifying session ended (${sessionDuration.inMinutes} minutes)'); - } else { - appLogger.d('In-app review: Session too short (${sessionDuration.inMinutes} minutes)'); - } - } - - Future _incrementQualifyingSessions() async { - final prefs = await _getPrefs(); - final currentCount = prefs.getInt(_keyQualifyingSessionsCount) ?? 0; - await prefs.setInt(_keyQualifyingSessionsCount, currentCount + 1); - } - - Future _getQualifyingSessionsCount() async { - final prefs = await _getPrefs(); - return prefs.getInt(_keyQualifyingSessionsCount) ?? 0; - } - - /// Check if we should request a review based on session count and cooldown - Future _shouldRequestReview() async { - final prefs = await _getPrefs(); - - final sessionCount = await _getQualifyingSessionsCount(); - if (sessionCount < _requiredSessions) { - appLogger.d('In-app review: Not enough sessions ($sessionCount/$_requiredSessions)'); - return false; - } - - final lastPromptString = prefs.getString(_keyLastPromptTime); - if (lastPromptString != null) { - final lastPrompt = DateTime.parse(lastPromptString); - final timeSinceLastPrompt = DateTime.now().difference(lastPrompt); - if (timeSinceLastPrompt < _promptCooldown) { - final daysRemaining = (_promptCooldown - timeSinceLastPrompt).inDays; - appLogger.d('In-app review: Cooldown active ($daysRemaining days remaining)'); - return false; - } - } - - return true; - } - - Future maybeRequestReview() async { - if (!isEnabled) return; - - final shouldRequest = await _shouldRequestReview(); - if (!shouldRequest) return; - - try { - final isAvailable = await _inAppReview.isAvailable(); - if (!isAvailable) { - appLogger.d('In-app review: Not available on this device'); - return; - } - - await _inAppReview.requestReview(); - appLogger.i('In-app review: Review prompt shown'); - - await _recordPromptShown(); - } catch (e) { - appLogger.e('In-app review: Error requesting review', error: e); - } - } - - Future _recordPromptShown() async { - final prefs = await _getPrefs(); - await prefs.setString(_keyLastPromptTime, DateTime.now().toIso8601String()); - // Reset session count so user needs to use app more before next prompt - await prefs.setInt(_keyQualifyingSessionsCount, 0); - } -} diff --git a/macos/Flutter/GeneratedPluginRegistrant.swift b/macos/Flutter/GeneratedPluginRegistrant.swift index 9e7875d2..a020e515 100644 --- a/macos/Flutter/GeneratedPluginRegistrant.swift +++ b/macos/Flutter/GeneratedPluginRegistrant.swift @@ -9,7 +9,6 @@ import auto_updater_macos import connectivity_plus import device_info_plus import file_picker -import in_app_review import os_media_controls import package_info_plus import screen_retriever_macos @@ -26,7 +25,6 @@ func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { ConnectivityPlusPlugin.register(with: registry.registrar(forPlugin: "ConnectivityPlusPlugin")) DeviceInfoPlusMacosPlugin.register(with: registry.registrar(forPlugin: "DeviceInfoPlusMacosPlugin")) FilePickerPlugin.register(with: registry.registrar(forPlugin: "FilePickerPlugin")) - InAppReviewPlugin.register(with: registry.registrar(forPlugin: "InAppReviewPlugin")) OsMediaControlsPlugin.register(with: registry.registrar(forPlugin: "OsMediaControlsPlugin")) FPPPackageInfoPlusPlugin.register(with: registry.registrar(forPlugin: "FPPPackageInfoPlusPlugin")) ScreenRetrieverMacosPlugin.register(with: registry.registrar(forPlugin: "ScreenRetrieverMacosPlugin")) diff --git a/macos/Podfile.lock b/macos/Podfile.lock index f40434a7..6103f3df 100644 --- a/macos/Podfile.lock +++ b/macos/Podfile.lock @@ -12,8 +12,6 @@ PODS: - file_picker (0.0.1): - FlutterMacOS - FlutterMacOS (1.0.0) - - in_app_review (2.0.0): - - FlutterMacOS - os_media_controls (0.0.1): - FlutterMacOS - package_info_plus (0.0.1): @@ -70,7 +68,6 @@ DEPENDENCIES: - device_info_plus (from `Flutter/ephemeral/.symlinks/plugins/device_info_plus/macos`) - file_picker (from `Flutter/ephemeral/.symlinks/plugins/file_picker/macos`) - FlutterMacOS (from `Flutter/ephemeral`) - - in_app_review (from `Flutter/ephemeral/.symlinks/plugins/in_app_review/macos`) - os_media_controls (from `Flutter/ephemeral/.symlinks/plugins/os_media_controls/macos`) - package_info_plus (from `Flutter/ephemeral/.symlinks/plugins/package_info_plus/macos`) - screen_retriever_macos (from `Flutter/ephemeral/.symlinks/plugins/screen_retriever_macos/macos`) @@ -101,8 +98,6 @@ EXTERNAL SOURCES: :path: Flutter/ephemeral/.symlinks/plugins/file_picker/macos FlutterMacOS: :path: Flutter/ephemeral - in_app_review: - :path: Flutter/ephemeral/.symlinks/plugins/in_app_review/macos os_media_controls: :path: Flutter/ephemeral/.symlinks/plugins/os_media_controls/macos package_info_plus: @@ -131,7 +126,6 @@ SPEC CHECKSUMS: device_info_plus: 4fb280989f669696856f8b129e4a5e3cd6c48f76 file_picker: 7584aae6fa07a041af2b36a2655122d42f578c1a FlutterMacOS: d0db08ddef1a9af05a5ec4b724367152bb0500b1 - in_app_review: 66e7680752b632d83f4f0e88b34d52ed303fbff4 os_media_controls: c07c04c4afdf59dda0a3f398457a46823c4ce0ed package_info_plus: f0052d280d17aa382b932f399edf32507174e870 screen_retriever_macos: 452e51764a9e1cdb74b3c541238795849f21557f diff --git a/pubspec.lock b/pubspec.lock index 7da2e12e..8d0c7063 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -554,22 +554,6 @@ packages: url: "https://pub.dev" source: hosted version: "0.1.0" - in_app_review: - dependency: "direct main" - description: - name: in_app_review - sha256: ab26ac54dbd802896af78c670b265eaeab7ecddd6af4d0751e9604b60574817f - url: "https://pub.dev" - source: hosted - version: "2.0.11" - in_app_review_platform_interface: - dependency: transitive - description: - name: in_app_review_platform_interface - sha256: fed2c755f2125caa9ae10495a3c163aa7fab5af3585a9c62ef4a6920c5b45f10 - url: "https://pub.dev" - source: hosted - version: "2.0.5" intl: dependency: "direct main" description: diff --git a/pubspec.yaml b/pubspec.yaml index 753f177b..038d19ca 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -50,7 +50,6 @@ dependencies: saf_stream: ^2.0.0 material_symbols_icons: ^4.2906.0 web_socket_channel: ^3.0.1 - in_app_review: ^2.0.11 dart_discord_presence: ^1.2.0 flutter_svg: ^2.2.3 background_downloader: diff --git a/test/services/in_app_review_service_test.dart b/test/services/in_app_review_service_test.dart deleted file mode 100644 index 1176c2ae..00000000 --- a/test/services/in_app_review_service_test.dart +++ /dev/null @@ -1,114 +0,0 @@ -import 'dart:io' show Platform; - -import 'package:flutter_test/flutter_test.dart'; -import 'package:plezy/services/base_shared_preferences_service.dart'; -import 'package:plezy/services/in_app_review_service.dart'; - -import '../test_helpers/prefs.dart'; - -void main() { - setUp(resetSharedPreferencesForTest); - - // Keys read directly from the underlying SharedPreferences to bypass the - // platform-channel `InAppReview.requestReview()` and assert state. - const keyQualifyingSessionsCount = 'review_qualifying_sessions_count'; - const keyLastPromptTime = 'review_last_prompt_time'; - - // ============================================================ - // Singleton + isEnabled gate - // ============================================================ - - group('singleton & isEnabled', () { - test('instance returns the same singleton', () { - final a = InAppReviewService.instance; - final b = InAppReviewService.instance; - expect(identical(a, b), isTrue); - }); - - test('isEnabled is false on desktop test platforms (no ENABLE_IN_APP_REVIEW)', () { - // Test platform on macOS/Linux/Windows is desktop, so the platform gate - // alone forces isEnabled=false regardless of the build flag. - if (!Platform.isIOS && !Platform.isAndroid) { - expect(InAppReviewService.isEnabled, isFalse); - } - }); - }); - - // ============================================================ - // Session tracking — these methods are no-ops when isEnabled=false - // ============================================================ - - group('session tracking when disabled (test environment)', () { - test('startSession does not write any prefs when isEnabled=false', () async { - InAppReviewService.instance.startSession(); - // No qualifying-session counter set yet. - final prefs = await BaseSharedPreferencesService.sharedCache(); - expect(prefs.getInt(keyQualifyingSessionsCount), isNull); - }); - - test('endSession is a no-op when no session was started AND isEnabled=false', () async { - // Call without preceding startSession — should not throw or mutate prefs. - await InAppReviewService.instance.endSession(); - final prefs = await BaseSharedPreferencesService.sharedCache(); - expect(prefs.getInt(keyQualifyingSessionsCount), isNull); - }); - - test('maybeRequestReview is a no-op when isEnabled=false', () async { - // Pre-set what would normally trigger a prompt. - final prefs = await BaseSharedPreferencesService.sharedCache(); - await prefs.setInt(keyQualifyingSessionsCount, 100); - - await InAppReviewService.instance.maybeRequestReview(); - - // Counter is unchanged because the early-return short-circuits the path - // that would reset it after a successful prompt. - expect(prefs.getInt(keyQualifyingSessionsCount), 100); - expect(prefs.getString(keyLastPromptTime), isNull); - }); - }); - - // ============================================================ - // Pref persistence — ensures the keys/format the service reads/writes - // are the same shape its private logic expects, so we verify the - // gating math separately by writing the prefs directly. - // ============================================================ - - group('pref shape (sanity for gating logic)', () { - test('qualifying sessions counter is int-typed under the documented key', () async { - final prefs = await BaseSharedPreferencesService.sharedCache(); - await prefs.setInt(keyQualifyingSessionsCount, 3); - expect(prefs.getInt(keyQualifyingSessionsCount), 3); - }); - - test('last prompt timestamp is an ISO 8601 string under the documented key', () async { - final prefs = await BaseSharedPreferencesService.sharedCache(); - final now = DateTime.utc(2026, 1, 1, 12, 0, 0).toIso8601String(); - await prefs.setString(keyLastPromptTime, now); - // Must round-trip through DateTime.parse (matches service's internal use). - final parsed = DateTime.parse(prefs.getString(keyLastPromptTime)!); - expect(parsed.toIso8601String(), now); - }); - }); - - // ============================================================ - // What's NOT covered (and why) - // ============================================================ - - // Because [InAppReviewService] is a singleton with no `@visibleForTesting` - // override hooks for either: - // - the `Platform.isIOS / isAndroid` gate, or - // - the `bool.fromEnvironment('ENABLE_IN_APP_REVIEW')` flag, - // we cannot directly exercise the gating math (`_shouldRequestReview`, - // `_incrementQualifyingSessions`) without modifying the service. Per the - // task brief, we do NOT add @visibleForTesting hooks just for tests. - // - // Behavior that requires `isEnabled == true` and is therefore unverified - // here: - // - endSession increments the counter only when sessionDuration ≥ 5 min - // - maybeRequestReview returns false when sessionCount < required (6) - // - maybeRequestReview returns false during the 60-day cooldown window - // - _recordPromptShown writes timestamp + resets the counter - // - // The pref-shape tests above pin the on-disk schema the service depends on, - // so if those keys/types change the production code will fail loudly. -}