Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions l10n.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
arb-dir: lib/l10n/arb
template-arb-file: app_en.arb
output-dir: lib/l10n/generated
output-localization-file: app_localizations.dart
output-class: AppLocalizations
nullable-getter: false
33 changes: 33 additions & 0 deletions lib/core/providers/locale_provider.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import 'package:flutter/widgets.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:unfilter/core/providers/shared_preferences_provider.dart';

const String localePrefKey = "app_locale_code";

final localeProvider = NotifierProvider<LocaleProvider, Locale?>(
LocaleProvider.new
);

class LocaleProvider extends Notifier<Locale?> {
@override
Locale? build() {
final prefs = ref.read(sharedPreferencesProvider);
final savedCode = prefs.getString(localePrefKey);

if(savedCode == null || savedCode.isEmpty) return null;

return Locale(savedCode);
}

Future<void> setLocale(String languageCode) async {
state = Locale(languageCode);
final prefs = ref.read(sharedPreferencesProvider);
await prefs.setString(localePrefKey, languageCode);
}

Future<void> clearLocale() async {
state = null;
final prefs = ref.read(sharedPreferencesProvider);
prefs.remove(localePrefKey);
}
}
40 changes: 17 additions & 23 deletions lib/core/version/update_ui.dart
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:url_launcher/url_launcher.dart';
import 'package:unfilter/l10n/generated/app_localizations.dart';
import 'version_models.dart';
import 'version_provider.dart';
import 'update_service.dart';
Expand Down Expand Up @@ -49,6 +50,8 @@ class ForceUpdateScreen extends StatelessWidget {

@override
Widget build(BuildContext context) {
final l10n = AppLocalizations.of(context);

return Scaffold(
backgroundColor: Colors.black,
body: SafeArea(
Expand All @@ -63,19 +66,8 @@ class ForceUpdateScreen extends StatelessWidget {
color: Colors.white,
),
const SizedBox(height: 32),
const Text(
'Critical Update Required',
style: TextStyle(
fontSize: 28,
fontWeight: FontWeight.bold,
color: Colors.white,
letterSpacing: -0.5,
),
textAlign: TextAlign.center,
),
const SizedBox(height: 16),
Text(
'A critical native update is available. You must update the app to continue using it securely.',
l10n.criticalUpdateMessage,
style: TextStyle(
fontSize: 16,
color: Colors.grey[400],
Expand All @@ -85,15 +77,15 @@ class ForceUpdateScreen extends StatelessWidget {
),
const SizedBox(height: 48),
_buildVersionInfo(
'Current Version',
l10n.currentVersionLabel,
state.currentVersion.displayString,
Colors.grey,
),
const SizedBox(height: 16),
_buildVersionInfo(
'Required Version',
l10n.requiredVersionLabel,
state.config?.minSupportedNativeVersion.nativeVersion ??
'Unknown',
l10n.commonUnknownLabel,
Colors.redAccent,
),
const Spacer(),
Expand All @@ -116,9 +108,9 @@ class ForceUpdateScreen extends StatelessWidget {
borderRadius: BorderRadius.circular(16),
),
),
child: const Text(
'Download Update',
style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold),
child: Text(
l10n.downloadUpdateAction,
style: const TextStyle(fontSize: 18, fontWeight: FontWeight.bold),
),
),
),
Expand Down Expand Up @@ -163,6 +155,8 @@ class SoftUpdateBanner extends StatelessWidget {

@override
Widget build(BuildContext context) {
final l10n = AppLocalizations.of(context);

return Material(
color: Colors.transparent,
child: Container(
Expand Down Expand Up @@ -192,9 +186,9 @@ class SoftUpdateBanner extends StatelessWidget {
size: 20,
),
const SizedBox(width: 12),
const Text(
'New Update Available',
style: TextStyle(
Text(
l10n.newUpdateAvailable,
style: const TextStyle(
color: Colors.white,
fontWeight: FontWeight.bold,
fontSize: 16,
Expand All @@ -210,7 +204,7 @@ class SoftUpdateBanner extends StatelessWidget {
),
const SizedBox(height: 12),
Text(
'A new native version (${state.config?.latestNativeVersion.nativeVersion}) is available with performance improvements.',
l10n.newNativeVersionAvailable(state.config?.latestNativeVersion.nativeVersion ?? 'Unknown'),
style: TextStyle(color: Colors.grey[400], fontSize: 13),
),
const SizedBox(height: 16),
Expand All @@ -232,7 +226,7 @@ class SoftUpdateBanner extends StatelessWidget {
borderRadius: BorderRadius.circular(12),
),
),
child: const Text('Update Now'),
child: Text(l10n.updateNowAction),
),
),
],
Expand Down
61 changes: 32 additions & 29 deletions lib/features/analytics/presentation/pages/analytics_page.dart
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ import 'package:share_plus/share_plus.dart';
import '../../../apps/domain/entities/device_app.dart';
import '../../../apps/presentation/providers/apps_provider.dart';
import '../providers/usage_stats_providers.dart';

import 'package:unfilter/l10n/generated/app_localizations.dart';

import '../../../home/presentation/widgets/premium_app_bar.dart';
import '../../../../core/widgets/top_shadow_gradient.dart';
Expand Down Expand Up @@ -71,7 +73,7 @@ class _AnalyticsPageState extends ConsumerState<AnalyticsPage>
body: appsAsync.when(
data: (apps) => _buildDataState(apps, theme),
loading: () => const Center(child: CircularProgressIndicator()),
error: (err, _) => Center(child: Text('Error: $err')),
error: (err, _) => Center(child: Text(AppLocalizations.of(context).commonErrorWithDetails(err.toString()))),
),
);
}
Expand Down Expand Up @@ -158,7 +160,7 @@ class _AnalyticsPageState extends ConsumerState<AnalyticsPage>
const Center(child: CircularProgressIndicator()),
const TopShadowGradient(),
PremiumAppBar(
title: 'Usage Statistics',
title: AppLocalizations.of(context).usageStatisticsTitle,
scrollController: _scrollController,
menuActions: _buildUsageRangeMenuActions(selectedRange),
),
Expand All @@ -167,6 +169,8 @@ class _AnalyticsPageState extends ConsumerState<AnalyticsPage>
}

Widget _buildEmptyState(bool hasPermission, UsageTimeRange selectedRange) {
final l10n = AppLocalizations.of(context);

return Stack(
children: [
CustomScrollView(
Expand All @@ -190,13 +194,13 @@ class _AnalyticsPageState extends ConsumerState<AnalyticsPage>
),
const SizedBox(height: 16),
Text(
'No Data Available',
l10n.noDataAvailable,
style: Theme.of(context).textTheme.titleLarge
?.copyWith(fontWeight: FontWeight.bold),
),
const SizedBox(height: 8),
Text(
'Try selecting a different date range',
l10n.tryDifferentDateRange,
style: Theme.of(context).textTheme.bodyMedium
?.copyWith(
color: Theme.of(
Expand All @@ -213,7 +217,7 @@ class _AnalyticsPageState extends ConsumerState<AnalyticsPage>
),
const TopShadowGradient(),
PremiumAppBar(
title: 'Usage Statistics',
title: l10n.usageStatisticsTitle,
scrollController: _scrollController,
menuActions: _buildUsageRangeMenuActions(selectedRange),
),
Expand All @@ -222,6 +226,9 @@ class _AnalyticsPageState extends ConsumerState<AnalyticsPage>
}

Widget _buildSearchEmptyState(ThemeData theme, UsageTimeRange selectedRange) {
final l10n = AppLocalizations.of(context);
String emptyStateMessage = l10n.searchEmptyState;

return Stack(
children: [
CustomScrollView(
Expand All @@ -238,7 +245,7 @@ class _AnalyticsPageState extends ConsumerState<AnalyticsPage>
child: AnalyticsSearchBar(
controller: _searchController,
searchQuery: _searchQuery,
hintText: 'Search usage stats...',
hintText: l10n.searchUsageStatsHint,
onChanged: (val) => setState(() => _searchQuery = val),
onClear: () {
_searchController.clear();
Expand All @@ -247,14 +254,14 @@ class _AnalyticsPageState extends ConsumerState<AnalyticsPage>
),
),
),
const SliverFillRemaining(
child: AnalyticsEmptyState(message: 'No apps match your search'),
SliverFillRemaining(
child: AnalyticsEmptyState(message: emptyStateMessage),
),
],
),
const TopShadowGradient(),
PremiumAppBar(
title: 'Usage Statistics',
title: AppLocalizations.of(context).usageStatisticsTitle,
scrollController: _scrollController,
menuActions: _buildUsageRangeMenuActions(selectedRange),
),
Expand Down Expand Up @@ -311,7 +318,7 @@ class _AnalyticsPageState extends ConsumerState<AnalyticsPage>
),
const TopShadowGradient(),
PremiumAppBar(
title: 'Usage Statistics',
title: AppLocalizations.of(context).usageStatisticsTitle,
scrollController: _scrollController,
menuActions: _buildUsageRangeMenuActions(selectedRange),
),
Expand Down Expand Up @@ -382,7 +389,7 @@ class _AnalyticsPageState extends ConsumerState<AnalyticsPage>
child: AnalyticsSearchBar(
controller: _searchController,
searchQuery: _searchQuery,
hintText: 'Search usage stats...',
hintText: AppLocalizations.of(context).searchUsageStatsHint,
onChanged: (val) => setState(() => _searchQuery = val),
onClear: () {
_searchController.clear();
Expand All @@ -401,6 +408,7 @@ class _AnalyticsPageState extends ConsumerState<AnalyticsPage>
final periodText = aggregationService.formatTrackedPeriod(
persistentStats.trackedPeriod,
);
final l10n = AppLocalizations.of(context);

return SliverToBoxAdapter(
child: Padding(
Expand Down Expand Up @@ -428,14 +436,14 @@ class _AnalyticsPageState extends ConsumerState<AnalyticsPage>
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'Tracking for $periodText',
l10n.trackingForPeriod(periodText),
style: theme.textTheme.bodyMedium?.copyWith(
fontWeight: FontWeight.w600,
),
),
if (persistentStats.storedSnapshotCount > 0)
Text(
'${persistentStats.storedSnapshotCount} days stored locally',
l10n.daysStoredLocally(persistentStats.storedSnapshotCount),
style: theme.textTheme.bodySmall?.copyWith(
color: theme.colorScheme.onSurfaceVariant,
),
Expand All @@ -445,7 +453,7 @@ class _AnalyticsPageState extends ConsumerState<AnalyticsPage>
),
if (persistentStats.hasDataGap)
Tooltip(
message: 'Some historical data was cleared by your device',
message: l10n.historicalDataCleared,
child: Icon(
Icons.info_outline,
size: 18,
Expand Down Expand Up @@ -522,7 +530,7 @@ class _AnalyticsPageState extends ConsumerState<AnalyticsPage>
return Padding(
padding: const EdgeInsets.only(bottom: 16),
child: Text(
_searchQuery.isEmpty ? 'TOP CONTRIBUTORS' : 'SEARCH RESULTS',
_searchQuery.isEmpty ? AppLocalizations.of(context).topContributorsSection : AppLocalizations.of(context).searchResultsSection,
style: theme.textTheme.labelSmall?.copyWith(
fontWeight: FontWeight.bold,
letterSpacing: 2.0,
Expand Down Expand Up @@ -618,7 +626,7 @@ class _AnalyticsPageState extends ConsumerState<AnalyticsPage>
),
const SizedBox(width: 8),
Text(
'Share',
AppLocalizations.of(context).shareLabel,
style: theme.textTheme.labelMedium?.copyWith(
fontWeight: FontWeight.bold,
color: theme.colorScheme.primary,
Expand All @@ -634,6 +642,10 @@ class _AnalyticsPageState extends ConsumerState<AnalyticsPage>
if (_isSharing) return;
setState(() => _isSharing = true);

final l10n = AppLocalizations.of(context);
final scaffoldMessenger = ScaffoldMessenger.of(context);
final errorColor = Theme.of(context).colorScheme.error;

try {
for (int i = 0; i < 3; i++) {
await WidgetsBinding.instance.endOfFrame;
Expand Down Expand Up @@ -679,25 +691,16 @@ class _AnalyticsPageState extends ConsumerState<AnalyticsPage>
await SharePlus.instance.share(
ShareParams(
files: [XFile(file.path)],
text: '''Unfilter exposed my screen addiction 💀

See what apps are really made of. Real usage stats. No sugar coating.

100% open source. No trackers. No BS.

Get it: github.com/r4khul/unfilter/releases/latest

Don't forget to give a star!
''',
text: l10n.shareAnalyticsViralText,
),
);
} catch (e) {
debugPrint('Share error: $e');
if (mounted) {
ScaffoldMessenger.of(context).showSnackBar(
scaffoldMessenger.showSnackBar(
SnackBar(
content: Text('Failed to share: ${e.toString()}'),
backgroundColor: Theme.of(context).colorScheme.error,
content: Text(l10n.shareFailedError(e.toString())),
backgroundColor: errorColor,
),
);
}
Expand Down
Loading
Loading