From 1964e901da0657b0583228cef05a4bd6f935d5b7 Mon Sep 17 00:00:00 2001 From: Devasy Patel <110348311+Devasy23@users.noreply.github.com> Date: Thu, 27 Aug 2026 21:46:40 +0530 Subject: [PATCH] fix: remove telemetry/analytics code entirely MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit F-Droid re-review flagged the app as still "defaulting to the Railway endpoint" and persisting a UUID, even though analyticsEnabled already defaults to false and gates every automatic call (PR #74). Rather than keep relitigating a gated-but-present capability, remove it outright: - Delete ApiService (sendHeartbeat, trackEvent, reportUsage, backupData, userAppId, the Railway _baseUrl) entirely. - Delete settings_screen.dart, an orphaned duplicate of profile_screen.dart that was the only other caller of trackEvent/backupData and was never instantiated from any route. - Remove the analyticsEnabled setting and the Privacy toggle from SettingsProvider/profile_sections.dart — nothing left to toggle. - Remove the ApiService provider registration and the gated call block from main.dart's AppInitializer. No network code touching the Railway backend remains anywhere in the reachable app. http and uuid stay in pubspec.yaml — both used elsewhere (gemini_ai_service.dart, workout_provider.dart, etc.). Co-Authored-By: Claude Sonnet 5 --- workout-logger/lib/main.dart | 15 - .../lib/screens/profile_screen.dart | 2 - .../lib/screens/settings_screen.dart | 594 ------------------ .../lib/screens/widgets/profile_sections.dart | 56 -- workout-logger/lib/services/api_service.dart | 173 ----- .../lib/services/settings_provider.dart | 17 - 6 files changed, 857 deletions(-) delete mode 100644 workout-logger/lib/screens/settings_screen.dart delete mode 100644 workout-logger/lib/services/api_service.dart diff --git a/workout-logger/lib/main.dart b/workout-logger/lib/main.dart index 253e741..2460957 100644 --- a/workout-logger/lib/main.dart +++ b/workout-logger/lib/main.dart @@ -18,7 +18,6 @@ import 'services/interfaces/ml_service_interface.dart'; import 'services/interfaces/health_connect_service_interface.dart'; import 'services/workout_provider.dart'; import 'services/settings_provider.dart'; -import 'services/api_service.dart'; import 'services/managers/program_manager.dart'; import 'services/managers/history_manager.dart'; import 'services/managers/health_sync_manager.dart'; @@ -101,8 +100,6 @@ class WorkoutLoggerApp extends StatelessWidget { Provider.value(value: _mlService), // IHealthConnectService stays in tree for ProfileScreen permission flow Provider.value(value: _healthConnectService), - // Provide the ApiService singleton via DI - Provider.value(value: ApiService()), // ProgramManager passed to tree directly ChangeNotifierProvider.value(value: _programManager), // SettingsProvider for user preferences (weight unit, increments) @@ -173,7 +170,6 @@ class _AppInitializerState extends State { final settings = context.read(); final historyManager = context.read(); final prManager = context.read(); - final api = context.read(); final gemini = context.read(); final readiness = context.read(); @@ -200,17 +196,6 @@ class _AppInitializerState extends State { // so the opt-in flag is loaded; never blocks or fails app init. readiness.refresh(); - // Fire-and-forget analytics in background — off by default, only - // fires once the user opts in via the Settings toggle. Must run - // after settings.init() so the flag is loaded. - if (settings.analyticsEnabled) { - api.sendHeartbeat(); - api.trackEvent('app_open'); - provider.getQuickStats().then((stats) => api.reportUsage(stats)).catchError( - (Object e) => debugPrint('Failed to report usage: $e'), - ); - } - if (!mounted) return; setState(() { _initialized = true; diff --git a/workout-logger/lib/screens/profile_screen.dart b/workout-logger/lib/screens/profile_screen.dart index e7432fe..21e2910 100644 --- a/workout-logger/lib/screens/profile_screen.dart +++ b/workout-logger/lib/screens/profile_screen.dart @@ -362,8 +362,6 @@ class _ProfileScreenState extends State const SizedBox(height: AppSpacing.md), const AiSettingsSection(), const SizedBox(height: AppSpacing.md), - PrivacySection(settings: settings), - const SizedBox(height: AppSpacing.md), AboutSection(appVersion: _appVersion), const SizedBox(height: AppSpacing.xxl), ]), diff --git a/workout-logger/lib/screens/settings_screen.dart b/workout-logger/lib/screens/settings_screen.dart deleted file mode 100644 index ca5e33a..0000000 --- a/workout-logger/lib/screens/settings_screen.dart +++ /dev/null @@ -1,594 +0,0 @@ -import 'dart:convert'; -import 'dart:io'; -import 'package:flutter/material.dart'; -import 'package:provider/provider.dart'; -import 'package:file_picker/file_picker.dart'; -import 'package:path_provider/path_provider.dart'; -import 'package:share_plus/share_plus.dart'; -import 'package:intl/intl.dart'; -import '../services/workout_provider.dart'; -import '../services/settings_provider.dart'; -import '../services/api_service.dart'; -import '../theme/app_theme.dart'; - -class SettingsScreen extends StatefulWidget { - const SettingsScreen({super.key}); - - @override - State createState() => _SettingsScreenState(); -} - -class _SettingsScreenState extends State { - bool _isBackingUp = false; - bool _isExporting = false; - bool _isImporting = false; - - // ==================== Feedback helpers ==================== - - void _showErrorSnackBar(String message) { - if (!mounted) return; - ScaffoldMessenger.of(context).showSnackBar( - SnackBar( - content: Text(message), - backgroundColor: AppTheme.error, - ), - ); - } - - void _showSuccessSnackBar(String message) { - if (!mounted) return; - ScaffoldMessenger.of(context).showSnackBar( - SnackBar( - content: Text(message), - backgroundColor: AppTheme.success, - ), - ); - } - - // ==================== Remote Backup ==================== - - Future _performBackup() async { - setState(() => _isBackingUp = true); - - try { - final provider = context.read(); - final jsonString = await provider.exportAllData(); - final data = jsonDecode(jsonString) as Map; - - if (!mounted) return; - final api = context.read(); - await api.trackEvent('backup_triggered').catchError((_) => null); - final success = await api.backupData(data); - - if (success) { - _showSuccessSnackBar('Backup successful!'); - } else { - _showErrorSnackBar('Backup failed. Please try again.'); - } - } catch (e, stackTrace) { - debugPrint('Backup error: $e\n$stackTrace'); - _showErrorSnackBar('Something went wrong. Please try again.'); - } finally { - if (mounted) { - setState(() => _isBackingUp = false); - } - } - } - - // ==================== Local Export ==================== - - Future _exportToFile() async { - setState(() => _isExporting = true); - - try { - final provider = context.read(); - final jsonString = await provider.exportAllData(); - - final tempDir = await getTemporaryDirectory(); - final dateStr = DateFormat('yyyy-MM-dd_HHmmss').format(DateTime.now()); - final fileName = 'repforge_backup_$dateStr.json'; - final file = File('${tempDir.path}/$fileName'); - await file.writeAsString(jsonString); - - final result = await SharePlus.instance.share( - ShareParams( - files: [XFile(file.path)], - subject: 'RepForge Backup', - ), - ); - - if (result.status == ShareResultStatus.success || - result.status == ShareResultStatus.dismissed) { - _showSuccessSnackBar('Backup file exported successfully!'); - } - } catch (e, stackTrace) { - debugPrint('Export error: $e\n$stackTrace'); - _showErrorSnackBar('Export failed. Please try again.'); - } finally { - if (mounted) { - setState(() => _isExporting = false); - } - } - } - - // ==================== Local Import ==================== - - Future _importFromFile() async { - final provider = context.read(); - // Show confirmation dialog - final confirmed = await showDialog( - context: context, - builder: (context) => AlertDialog( - backgroundColor: AppTheme.cardColor, - shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(16)), - title: const Text( - 'Import Backup', - style: TextStyle(color: AppTheme.textPrimary), - ), - content: const Text( - 'This will merge the backup data with your existing data. ' - 'Existing workouts, routines, and targets will be kept. ' - 'New items from the backup will be added.\n\n' - 'Select a .json backup file to continue.', - style: TextStyle(color: AppTheme.textSecondary), - ), - actions: [ - TextButton( - onPressed: () => Navigator.pop(context, false), - child: const Text( - 'Cancel', - style: TextStyle(color: AppTheme.textSecondary), - ), - ), - ElevatedButton( - style: ElevatedButton.styleFrom( - backgroundColor: AppTheme.primaryColor, - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(8), - ), - ), - onPressed: () => Navigator.pop(context, true), - child: const Text('Choose File'), - ), - ], - ), - ); - - if (confirmed != true) return; - - setState(() => _isImporting = true); - - try { - final result = await FilePicker.pickFiles( - type: FileType.custom, - allowedExtensions: ['json'], - ); - - if (result == null || result.files.single.path == null) { - if (mounted) setState(() => _isImporting = false); - return; - } - - final file = File(result.files.single.path!); - final jsonString = await file.readAsString(); - if (!mounted) return; - - // Basic validation: ensure it's valid JSON with expected keys - final data = jsonDecode(jsonString) as Map; - if (!data.containsKey('sessions') && !data.containsKey('routines')) { - _showErrorSnackBar('Invalid backup file. Expected a RepForge backup.'); - return; - } - - await provider.importData(jsonString); - if (!mounted) return; - - final itemCount = (data['sessions'] as List?)?.length ?? 0; - final routineCount = (data['routines'] as List?)?.length ?? 0; - - _showSuccessSnackBar( - 'Import complete! Processed $itemCount sessions, $routineCount routines.', - ); - } catch (e, stackTrace) { - debugPrint('Import error: $e\n$stackTrace'); - _showErrorSnackBar( - 'Import failed. Make sure you selected a valid backup file.', - ); - } finally { - if (mounted) { - setState(() => _isImporting = false); - } - } - } - - // ==================== Build ==================== - - @override - Widget build(BuildContext context) { - return Scaffold( - appBar: AppBar( - title: const Text('Settings'), - backgroundColor: AppTheme.surfaceColor, - elevation: 0, - ), - body: ListView( - padding: const EdgeInsets.all(16), - children: [ - _buildSectionHeader('Preferences'), - const SizedBox(height: 16), - _buildPreferencesCard(), - const SizedBox(height: 24), - _buildSectionHeader('Data Management'), - const SizedBox(height: 16), - _buildLocalBackupCard(), - const SizedBox(height: 16), - _buildBackupCard(), - ], - ), - ); - } - - Widget _buildPreferencesCard() { - final settings = context.watch(); - - return Container( - padding: const EdgeInsets.all(16), - decoration: BoxDecoration( - color: AppTheme.cardColor, - borderRadius: BorderRadius.circular(12), - ), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Row( - children: [ - Container( - padding: const EdgeInsets.all(10), - decoration: BoxDecoration( - color: AppTheme.primaryColor.withValues(alpha: 0.1), - borderRadius: BorderRadius.circular(8), - ), - child: const Icon( - Icons.tune_rounded, - color: AppTheme.primaryColor, - ), - ), - const SizedBox(width: 16), - const Expanded( - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - 'Units & Increments', - style: TextStyle( - fontSize: 16, - fontWeight: FontWeight.bold, - color: AppTheme.textPrimary, - ), - ), - Text( - 'Customize weight display and input steps', - style: TextStyle( - fontSize: 12, - color: AppTheme.textSecondary, - ), - ), - ], - ), - ), - ], - ), - const SizedBox(height: 20), - // Weight unit toggle - const Text( - 'Weight Unit', - style: TextStyle( - color: AppTheme.textSecondary, - fontSize: 13, - fontWeight: FontWeight.w500, - ), - ), - const SizedBox(height: 8), - Row( - children: [ - Expanded( - child: _UnitButton( - label: 'kg', - selected: settings.weightUnit == WeightUnit.kg, - onTap: () => settings.setWeightUnit(WeightUnit.kg), - ), - ), - const SizedBox(width: 8), - Expanded( - child: _UnitButton( - label: 'lbs', - selected: settings.weightUnit == WeightUnit.lbs, - onTap: () => settings.setWeightUnit(WeightUnit.lbs), - ), - ), - ], - ), - const SizedBox(height: 16), - // Weight increment selector - const Text( - 'Weight Increment', - style: TextStyle( - color: AppTheme.textSecondary, - fontSize: 13, - fontWeight: FontWeight.w500, - ), - ), - const SizedBox(height: 8), - Wrap( - spacing: 8, - children: settings.availableIncrements.map((inc) { - final selected = settings.weightIncrement == inc; - return ChoiceChip( - label: Text( - inc == inc.truncateToDouble() - ? '${inc.toStringAsFixed(0)} ${settings.unitLabel}' - : '${inc.toStringAsFixed(2).replaceAll(RegExp(r'0+$'), '')} ${settings.unitLabel}', - ), - selected: selected, - onSelected: (_) => settings.setWeightIncrement(inc), - selectedColor: AppTheme.primaryColor.withValues(alpha: 0.3), - backgroundColor: AppTheme.surfaceColor, - labelStyle: TextStyle( - color: selected ? AppTheme.primaryColor : AppTheme.textSecondary, - fontWeight: selected ? FontWeight.bold : FontWeight.normal, - ), - side: BorderSide( - color: selected - ? AppTheme.primaryColor - : AppTheme.surfaceColor, - ), - ); - }).toList(), - ), - ], - ), - ); - } - - Widget _buildSectionHeader(String title) { - return Text( - title, - style: Theme.of(context).textTheme.titleMedium?.copyWith( - color: AppTheme.primaryColor, - fontWeight: FontWeight.bold, - ), - ); - } - - Widget _buildLocalBackupCard() { - return Container( - padding: const EdgeInsets.all(16), - decoration: BoxDecoration( - color: AppTheme.cardColor, - borderRadius: BorderRadius.circular(12), - ), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Row( - children: [ - Container( - padding: const EdgeInsets.all(10), - decoration: BoxDecoration( - color: AppTheme.secondaryColor.withValues(alpha: 0.1), - borderRadius: BorderRadius.circular(8), - ), - child: const Icon( - Icons.save_alt_rounded, - color: AppTheme.secondaryColor, - ), - ), - const SizedBox(width: 16), - const Expanded( - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - 'Local Backup', - style: TextStyle( - fontSize: 16, - fontWeight: FontWeight.bold, - color: AppTheme.textPrimary, - ), - ), - Text( - 'Export or import a backup file to transfer data between devices', - style: TextStyle( - fontSize: 12, - color: AppTheme.textSecondary, - ), - ), - ], - ), - ), - ], - ), - const SizedBox(height: 16), - Row( - children: [ - Expanded( - child: ElevatedButton.icon( - onPressed: _isExporting ? null : _exportToFile, - icon: _isExporting - ? const SizedBox( - height: 18, - width: 18, - child: CircularProgressIndicator( - strokeWidth: 2, - valueColor: AlwaysStoppedAnimation( - Colors.white, - ), - ), - ) - : const Icon(Icons.upload_file_rounded, size: 20), - label: Text(_isExporting ? 'Exporting...' : 'Export'), - style: ElevatedButton.styleFrom( - backgroundColor: AppTheme.secondaryColor, - foregroundColor: AppTheme.backgroundColor, - padding: const EdgeInsets.symmetric(vertical: 12), - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(8), - ), - ), - ), - ), - const SizedBox(width: 12), - Expanded( - child: OutlinedButton.icon( - onPressed: _isImporting ? null : _importFromFile, - icon: _isImporting - ? const SizedBox( - height: 18, - width: 18, - child: CircularProgressIndicator( - strokeWidth: 2, - valueColor: AlwaysStoppedAnimation( - AppTheme.secondaryColor, - ), - ), - ) - : const Icon(Icons.download_rounded, size: 20), - label: Text(_isImporting ? 'Importing...' : 'Import'), - style: OutlinedButton.styleFrom( - foregroundColor: AppTheme.secondaryColor, - side: const BorderSide(color: AppTheme.secondaryColor), - padding: const EdgeInsets.symmetric(vertical: 12), - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(8), - ), - ), - ), - ), - ], - ), - ], - ), - ); - } - - Widget _buildBackupCard() { - return Container( - padding: const EdgeInsets.all(16), - decoration: BoxDecoration( - color: AppTheme.cardColor, - borderRadius: BorderRadius.circular(12), - ), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Row( - children: [ - Container( - padding: const EdgeInsets.all(10), - decoration: BoxDecoration( - color: AppTheme.primaryColor.withValues(alpha: 0.1), - borderRadius: BorderRadius.circular(8), - ), - child: const Icon( - Icons.cloud_upload_outlined, - color: AppTheme.primaryColor, - ), - ), - const SizedBox(width: 16), - const Expanded( - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - 'Remote Backup', - style: TextStyle( - fontSize: 16, - fontWeight: FontWeight.bold, - color: AppTheme.textPrimary, - ), - ), - Text( - 'Securely backup your workout data to the cloud', - style: TextStyle( - fontSize: 12, - color: AppTheme.textSecondary, - ), - ), - ], - ), - ), - ], - ), - const SizedBox(height: 16), - SizedBox( - width: double.infinity, - child: ElevatedButton( - onPressed: _isBackingUp ? null : _performBackup, - - style: ElevatedButton.styleFrom( - backgroundColor: AppTheme.primaryColor, - padding: const EdgeInsets.symmetric(vertical: 12), - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(8), - ), - ), - child: _isBackingUp - ? const SizedBox( - height: 20, - width: 20, - child: CircularProgressIndicator( - strokeWidth: 2, - valueColor: AlwaysStoppedAnimation(Colors.white), - ), - ) - : const Text('Backup Now'), - ), - ), - ], - ), - ); - } -} - -class _UnitButton extends StatelessWidget { - final String label; - final bool selected; - final VoidCallback onTap; - - const _UnitButton({ - required this.label, - required this.selected, - required this.onTap, - }); - - @override - Widget build(BuildContext context) { - return GestureDetector( - onTap: onTap, - child: AnimatedContainer( - duration: const Duration(milliseconds: 150), - padding: const EdgeInsets.symmetric(vertical: 10), - decoration: BoxDecoration( - color: selected - ? AppTheme.primaryColor.withValues(alpha: 0.2) - : AppTheme.surfaceColor, - borderRadius: BorderRadius.circular(8), - border: Border.all( - color: selected ? AppTheme.primaryColor : AppTheme.surfaceColor, - width: 1.5, - ), - ), - child: Center( - child: Text( - label, - style: TextStyle( - color: selected ? AppTheme.primaryColor : AppTheme.textSecondary, - fontWeight: selected ? FontWeight.bold : FontWeight.normal, - fontSize: 15, - ), - ), - ), - ), - ); - } -} diff --git a/workout-logger/lib/screens/widgets/profile_sections.dart b/workout-logger/lib/screens/widgets/profile_sections.dart index 19b30c3..3abd5f6 100644 --- a/workout-logger/lib/screens/widgets/profile_sections.dart +++ b/workout-logger/lib/screens/widgets/profile_sections.dart @@ -397,62 +397,6 @@ class DataManagementSection extends StatelessWidget { } } -// ── Privacy section ──────────────────────────────────────────────────────────── -class PrivacySection extends StatelessWidget { - const PrivacySection({super.key, required this.settings}); - - final SettingsProvider settings; - - static const _color = AppColors.textSoft; - - @override - Widget build(BuildContext context) { - return _ProfileSection( - icon: Icons.privacy_tip_outlined, - iconColor: _color, - title: 'Privacy', - subtitle: 'Control anonymous usage data', - child: Column( - children: [ - Row( - children: [ - Expanded( - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - 'Share anonymous usage data', - style: TextStyle(fontFamily: 'Geist', - color: AppColors.textPrimary, - fontSize: 14, - fontWeight: FontWeight.w500, - ), - ), - const SizedBox(height: 2), - Text( - 'Off by default. Install ID, platform, and workout counts — no personal data', - style: TextStyle(fontFamily: 'Geist', - color: AppColors.textMuted, - fontSize: 12, - ), - ), - ], - ), - ), - Switch( - value: settings.analyticsEnabled, - onChanged: (v) => settings.setAnalyticsEnabled(v), - activeThumbColor: _color, - activeTrackColor: _color.withValues(alpha: 0.35), - ), - ], - ), - ], - ), - ); - } -} - // ── About section ───────────────────────────────────────────────────────────── class AboutSection extends StatefulWidget { const AboutSection({super.key, required this.appVersion}); diff --git a/workout-logger/lib/services/api_service.dart b/workout-logger/lib/services/api_service.dart deleted file mode 100644 index 4b93765..0000000 --- a/workout-logger/lib/services/api_service.dart +++ /dev/null @@ -1,173 +0,0 @@ -import 'dart:convert'; -import 'dart:async'; -import 'package:http/http.dart' as http; -import 'package:flutter/foundation.dart'; -import 'package:hive/hive.dart'; -import 'package:uuid/uuid.dart'; - -// Conditional import: uses dart:io on native, stub on web. -import 'platform_stub.dart' if (dart.library.io) 'platform_io.dart'; - -/// Singleton service for communicating with the RepForge analytics backend. -class ApiService { - static const String _baseUrl = String.fromEnvironment( - 'API_URL', - defaultValue: 'https://workout-logger-production-1e93.up.railway.app', - ); - - static final ApiService _instance = ApiService._internal(); - - factory ApiService() => _instance; - ApiService._internal(); - - http.Client _client = http.Client(); - - /// Replace the HTTP client for testing only. - @visibleForTesting - static void setTestClient(http.Client client) { - _instance._client = client; - } - - String? _cachedAppId; - - /// Returns a stable installation ID (UUID v4) persisted in Hive. - Future get userAppId async { - if (_cachedAppId != null) return _cachedAppId!; - // Defensively open the box if it's not already open - final Box box; - if (Hive.isBoxOpen('settings')) { - box = Hive.box('settings'); - } else { - box = await Hive.openBox('settings'); - } - var id = box.get('user_app_id'); - if (id == null) { - id = const Uuid().v4(); - await box.put('user_app_id', id); - } - _cachedAppId = id; - return id; - } - - String get _platform { - if (kIsWeb) return 'web'; - return getPlatformName(); - } - - // ───────── ingest helpers ───────── - - Future sendHeartbeat() async { - try { - final id = await userAppId; - final body = { - 'user_app_id': id, - 'platform': _platform, - 'timestamp': DateTime.now().toUtc().toIso8601String(), - }; - final res = await _client - .post( - Uri.parse('$_baseUrl/heartbeat'), - headers: {'Content-Type': 'application/json'}, - body: jsonEncode(body), - ) - .timeout(const Duration(seconds: 10)); - if (res.statusCode != 200) { - debugPrint('Heartbeat failed: ${res.body}'); - } - } on TimeoutException { - debugPrint('Heartbeat timeout after 10 seconds'); - } catch (e) { - debugPrint('Heartbeat error: $e'); - } - } - - Future trackEvent( - String event, { - Map? metadata, - }) async { - try { - final id = await userAppId; - final body = { - 'user_app_id': id, - 'event': event, - 'platform': _platform, - 'timestamp': DateTime.now().toUtc().toIso8601String(), - 'metadata': ?metadata, - }; - final res = await _client - .post( - Uri.parse('$_baseUrl/event'), - headers: {'Content-Type': 'application/json'}, - body: jsonEncode(body), - ) - .timeout(const Duration(seconds: 10)); - if (res.statusCode != 200) { - debugPrint('Event tracking failed: ${res.body}'); - } - } on TimeoutException { - debugPrint('Event tracking timeout after 10 seconds'); - } catch (e) { - debugPrint('Event tracking error: $e'); - } - } - - Future reportUsage(Map stats) async { - try { - final id = await userAppId; - final payload = { - 'user_app_id': id, - 'total_workouts': stats['totalWorkouts'], - 'weekly_workouts': stats['weeklyWorkouts'], - 'weekly_volume': stats['weeklyVolume'], - 'exercises_this_week': stats['exercisesThisWeek'], - 'platform': _platform, - 'report_date': DateTime.now().toUtc().toIso8601String(), - }; - - final response = await _client - .post( - Uri.parse('$_baseUrl/report'), - headers: {'Content-Type': 'application/json'}, - body: jsonEncode(payload), - ) - .timeout(const Duration(seconds: 10)); - - if (response.statusCode != 200) { - debugPrint('Failed to report usage: ${response.body}'); - } - } on TimeoutException { - debugPrint('Usage report timeout after 10 seconds'); - } catch (e) { - debugPrint('Error reporting usage: $e'); - } - } - - Future backupData(Map data) async { - try { - final id = await userAppId; - - final payload = {'user_app_id': id, ...data}; - - final response = await _client - .post( - Uri.parse('$_baseUrl/backup'), - headers: {'Content-Type': 'application/json'}, - body: jsonEncode(payload), - ) - .timeout(const Duration(minutes: 2)); - - if (response.statusCode == 200) { - return true; - } else { - debugPrint('Failed to backup data: ${response.body}'); - return false; - } - } on TimeoutException { - debugPrint('Backup upload timeout after 2 minutes'); - return false; - } catch (e) { - debugPrint('Error backing up data: $e'); - return false; - } - } -} diff --git a/workout-logger/lib/services/settings_provider.dart b/workout-logger/lib/services/settings_provider.dart index f882b7a..164df92 100644 --- a/workout-logger/lib/services/settings_provider.dart +++ b/workout-logger/lib/services/settings_provider.dart @@ -20,7 +20,6 @@ class SettingsProvider extends ChangeNotifier { String _weeklyInsights = ''; DateTime? _weeklyInsightsDate; bool _showAdvancedMetrics = false; - bool _analyticsEnabled = false; WeightUnit get weightUnit => _weightUnit; double get weightIncrement => _weightIncrement; @@ -35,13 +34,6 @@ class SettingsProvider extends ChangeNotifier { DateTime? get weeklyInsightsDate => _weeklyInsightsDate; bool get showAdvancedMetrics => _showAdvancedMetrics; - /// Whether automatic telemetry (heartbeat/app-open/usage-report) may - /// fire. Off by default for every install — installer identity (F-Droid - /// vs. sideload vs. Play) isn't a reliable signal, since there are - /// multiple F-Droid client apps and installer info can be unreadable. - /// Users opt in via the Privacy toggle in Settings. - bool get analyticsEnabled => _analyticsEnabled; - SettingsProvider(this._storage); Future init() async { @@ -68,9 +60,6 @@ class SettingsProvider extends ChangeNotifier { _weeklyInsightsDate = dateStr != null ? DateTime.tryParse(dateStr) : null; final advMetrics = await _storage.getSetting('showAdvancedMetrics'); _showAdvancedMetrics = advMetrics == 'true'; - - final analytics = await _storage.getSetting('analyticsEnabled'); - _analyticsEnabled = analytics == 'true'; } Future setUserName(String name) async { @@ -141,12 +130,6 @@ class SettingsProvider extends ChangeNotifier { notifyListeners(); } - Future setAnalyticsEnabled(bool value) async { - _analyticsEnabled = value; - await _storage.saveSetting('analyticsEnabled', value.toString()); - notifyListeners(); - } - Future saveWeeklyInsights(String insights) async { _weeklyInsights = insights; _weeklyInsightsDate = DateTime.now();