added support for overriding locales

This commit is contained in:
Fabian Freund
2025-10-20 16:21:10 +02:00
parent aca0767d9a
commit 1c7e9ec741
66 changed files with 1679 additions and 7 deletions
+1
View File
@@ -45,6 +45,7 @@ import 'package:weblibre/features/settings/presentation/screens/developer_settin
import 'package:weblibre/features/settings/presentation/screens/doh_settings.dart';
import 'package:weblibre/features/settings/presentation/screens/fingerprint_settings.dart';
import 'package:weblibre/features/settings/presentation/screens/general_settings.dart';
import 'package:weblibre/features/settings/presentation/screens/locale_settings.dart';
import 'package:weblibre/features/settings/presentation/screens/settings.dart';
import 'package:weblibre/features/settings/presentation/screens/web_engine_hardening.dart';
import 'package:weblibre/features/settings/presentation/screens/web_engine_hardening_group.dart';
+26
View File
@@ -116,6 +116,11 @@ RouteBase get $settingsRoute => GoRouteData.$route(
name: 'FingerprintSettingsRoute',
factory: $FingerprintSettingsRoute._fromState,
),
GoRouteData.$route(
path: 'locales',
name: 'LocaleSettingsRoute',
factory: $LocaleSettingsRoute._fromState,
),
],
),
GoRouteData.$route(
@@ -306,6 +311,27 @@ mixin $FingerprintSettingsRoute on GoRouteData {
void replace(BuildContext context) => context.replace(location);
}
mixin $LocaleSettingsRoute on GoRouteData {
static LocaleSettingsRoute _fromState(GoRouterState state) =>
LocaleSettingsRoute();
@override
String get location => GoRouteData.$location('/settings/web_engine/locales');
@override
void go(BuildContext context) => context.go(location);
@override
Future<T?> push<T>(BuildContext context) => context.push<T>(location);
@override
void pushReplacement(BuildContext context) =>
context.pushReplacement(location);
@override
void replace(BuildContext context) => context.replace(location);
}
mixin $DeveloperSettingsRoute on GoRouteData {
static DeveloperSettingsRoute _fromState(GoRouterState state) =>
DeveloperSettingsRoute();
+11
View File
@@ -47,6 +47,10 @@ part of 'routes.dart';
name: 'FingerprintSettingsRoute',
path: 'fingerprint',
),
TypedGoRoute<LocaleSettingsRoute>(
name: 'LocaleSettingsRoute',
path: 'locales',
),
],
),
TypedGoRoute<DeveloperSettingsRoute>(
@@ -97,6 +101,13 @@ class FingerprintSettingsRoute extends GoRouteData
}
}
class LocaleSettingsRoute extends GoRouteData with $LocaleSettingsRoute {
@override
Widget build(BuildContext context, GoRouterState state) {
return const LocaleSettingsScreen();
}
}
class WebEngineSettingsRoute extends GoRouteData with $WebEngineSettingsRoute {
@override
Widget build(BuildContext context, GoRouterState state) {
@@ -0,0 +1,39 @@
import 'package:flutter/widgets.dart' hide Locale;
import 'package:intl/locale.dart' as intl;
import 'package:locale_resolver/locale_resolver.dart';
import 'package:riverpod_annotation/riverpod_annotation.dart';
import 'package:weblibre/extensions/locale.dart';
part 'locale_resolver.g.dart';
@Riverpod(keepAlive: true)
class LocaleResolverRepository extends _$LocaleResolverRepository {
final _service = LocaleResolver();
final _cache = <intl.Locale, LocalizedResult>{};
Future<LocalizedResult> resolve(intl.Locale locale) async {
final cached = _cache[locale];
if (cached != null) {
return cached;
}
return _cache[locale] = await _service.resolve(
locale.toLanguageTag(),
targetLocale.toLanguageTag(),
);
}
@override
void build(intl.Locale targetLocale) {}
}
@Riverpod()
Future<LocalizedResult> resolveLocale(Ref ref, intl.Locale locale) {
return ref
.read(
localeResolverRepositoryProvider(
WidgetsBinding.instance.platformDispatcher.locale.toIntlLocale(),
).notifier,
)
.resolve(locale);
}
@@ -0,0 +1,185 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'locale_resolver.dart';
// **************************************************************************
// RiverpodGenerator
// **************************************************************************
// GENERATED CODE - DO NOT MODIFY BY HAND
// ignore_for_file: type=lint, type=warning
@ProviderFor(LocaleResolverRepository)
const localeResolverRepositoryProvider = LocaleResolverRepositoryFamily._();
final class LocaleResolverRepositoryProvider
extends $NotifierProvider<LocaleResolverRepository, void> {
const LocaleResolverRepositoryProvider._({
required LocaleResolverRepositoryFamily super.from,
required intl.Locale super.argument,
}) : super(
retry: null,
name: r'localeResolverRepositoryProvider',
isAutoDispose: false,
dependencies: null,
$allTransitiveDependencies: null,
);
@override
String debugGetCreateSourceHash() => _$localeResolverRepositoryHash();
@override
String toString() {
return r'localeResolverRepositoryProvider'
''
'($argument)';
}
@$internal
@override
LocaleResolverRepository create() => LocaleResolverRepository();
/// {@macro riverpod.override_with_value}
Override overrideWithValue(void value) {
return $ProviderOverride(
origin: this,
providerOverride: $SyncValueProvider<void>(value),
);
}
@override
bool operator ==(Object other) {
return other is LocaleResolverRepositoryProvider &&
other.argument == argument;
}
@override
int get hashCode {
return argument.hashCode;
}
}
String _$localeResolverRepositoryHash() =>
r'dc74908d6ac1f5cc851e2a10dcf33b0dc68b5b15';
final class LocaleResolverRepositoryFamily extends $Family
with
$ClassFamilyOverride<
LocaleResolverRepository,
void,
void,
void,
intl.Locale
> {
const LocaleResolverRepositoryFamily._()
: super(
retry: null,
name: r'localeResolverRepositoryProvider',
dependencies: null,
$allTransitiveDependencies: null,
isAutoDispose: false,
);
LocaleResolverRepositoryProvider call(intl.Locale targetLocale) =>
LocaleResolverRepositoryProvider._(argument: targetLocale, from: this);
@override
String toString() => r'localeResolverRepositoryProvider';
}
abstract class _$LocaleResolverRepository extends $Notifier<void> {
late final _$args = ref.$arg as intl.Locale;
intl.Locale get targetLocale => _$args;
void build(intl.Locale targetLocale);
@$mustCallSuper
@override
void runBuild() {
build(_$args);
final ref = this.ref as $Ref<void, void>;
final element =
ref.element
as $ClassProviderElement<
AnyNotifier<void, void>,
void,
Object?,
Object?
>;
element.handleValue(ref, null);
}
}
@ProviderFor(resolveLocale)
const resolveLocaleProvider = ResolveLocaleFamily._();
final class ResolveLocaleProvider
extends
$FunctionalProvider<
AsyncValue<LocalizedResult>,
LocalizedResult,
FutureOr<LocalizedResult>
>
with $FutureModifier<LocalizedResult>, $FutureProvider<LocalizedResult> {
const ResolveLocaleProvider._({
required ResolveLocaleFamily super.from,
required intl.Locale super.argument,
}) : super(
retry: null,
name: r'resolveLocaleProvider',
isAutoDispose: true,
dependencies: null,
$allTransitiveDependencies: null,
);
@override
String debugGetCreateSourceHash() => _$resolveLocaleHash();
@override
String toString() {
return r'resolveLocaleProvider'
''
'($argument)';
}
@$internal
@override
$FutureProviderElement<LocalizedResult> $createElement(
$ProviderPointer pointer,
) => $FutureProviderElement(pointer);
@override
FutureOr<LocalizedResult> create(Ref ref) {
final argument = this.argument as intl.Locale;
return resolveLocale(ref, argument);
}
@override
bool operator ==(Object other) {
return other is ResolveLocaleProvider && other.argument == argument;
}
@override
int get hashCode {
return argument.hashCode;
}
}
String _$resolveLocaleHash() => r'94d7a9b307a81de372b8b40b06f046acc76e01c8';
final class ResolveLocaleFamily extends $Family
with $FunctionalFamilyOverride<FutureOr<LocalizedResult>, intl.Locale> {
const ResolveLocaleFamily._()
: super(
retry: null,
name: r'resolveLocaleProvider',
dependencies: null,
$allTransitiveDependencies: null,
isAutoDispose: true,
);
ResolveLocaleProvider call(intl.Locale locale) =>
ResolveLocaleProvider._(argument: locale, from: this);
@override
String toString() => r'resolveLocaleProvider';
}
+27
View File
@@ -0,0 +1,27 @@
import 'dart:ui' as ui;
import 'package:intl/locale.dart' as intl;
extension LocaleFormat on intl.Locale {
String rawToString(String separator) {
final StringBuffer out = StringBuffer(languageCode);
if (scriptCode != null && scriptCode!.isNotEmpty) {
out.write('$separator$scriptCode');
}
final String? countryCode = this.countryCode;
if (countryCode != null && countryCode.isNotEmpty) {
out.write('$separator${this.countryCode}');
}
return out.toString();
}
}
extension LocaleConverter on ui.Locale {
intl.Locale toIntlLocale() {
return intl.Locale.fromSubtags(
languageCode: languageCode,
countryCode: countryCode,
scriptCode: scriptCode,
);
}
}
@@ -17,6 +17,7 @@
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import 'package:collection/collection.dart';
import 'package:flutter/material.dart' show ThemeMode;
import 'package:flutter_mozilla_components/flutter_mozilla_components.dart';
import 'package:riverpod/riverpod.dart';
@@ -141,6 +142,18 @@ class EngineSettingsReplicationService
.read(preferenceFixatorProvider.notifier)
.register('pdfjs.disabled', !settings.enablePdfJs);
}
if (!const DeepCollectionEquality.unordered().equals(
previous.value?.locales,
settings.locales,
)) {
// ignore: only_use_keep_alive_inside_keep_alive
await ref
.read(preferenceFixatorProvider.notifier)
.register(
'intl.accept_languages',
settings.locales.join(','),
);
}
} else {
await _service.setDefaultSettings(settings);
@@ -149,6 +162,11 @@ class EngineSettingsReplicationService
.read(preferenceFixatorProvider.notifier)
.register('pdfjs.disabled', !settings.enablePdfJs);
// ignore: only_use_keep_alive_inside_keep_alive
await ref
.read(preferenceFixatorProvider.notifier)
.register('intl.accept_languages', settings.locales.join(','));
initialSettingsSent = true;
}
}
@@ -44,7 +44,7 @@ final class EngineSettingsReplicationServiceProvider
}
String _$engineSettingsReplicationServiceHash() =>
r'd28cc58a94f45008a478bf52a9c40d61811b591d';
r'7583ebc8ca9c11377486ff5a3e763493dcdc903a';
abstract class _$EngineSettingsReplicationService extends $Notifier<void> {
void build();
@@ -25,8 +25,6 @@ class PreferenceChangeListener extends _$PreferenceChangeListener {
@Riverpod()
class PreferenceFixator extends _$PreferenceFixator {
Future<void> register(String name, Object value) async {
await GeckoPrefService().applyPrefs({name: value});
state = {...state}
..update(
name,
@@ -36,6 +34,8 @@ class PreferenceFixator extends _$PreferenceFixator {
return value;
},
);
await GeckoPrefService().applyPrefs({name: value});
}
Future<void> unregister(String name) async {
@@ -87,7 +87,7 @@ final class PreferenceFixatorProvider
}
}
String _$preferenceFixatorHash() => r'dfa323859c660d3db74dba7297e2a33b758ed78a';
String _$preferenceFixatorHash() => r'c76245d5c420e2e3f6c026f23c017c939b69ef59';
abstract class _$PreferenceFixator extends $Notifier<Map<String, Object>> {
Map<String, Object> build();
@@ -0,0 +1,148 @@
import 'package:country_flags/country_flags.dart';
import 'package:fast_equatable/fast_equatable.dart';
import 'package:flutter/material.dart';
import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:intl/locale.dart';
import 'package:nullability/nullability.dart';
import 'package:weblibre/domain/repositories/locale_resolver.dart';
import 'package:weblibre/extensions/locale.dart';
import 'package:weblibre/features/settings/presentation/controllers/save_settings.dart';
import 'package:weblibre/features/user/data/models/engine_settings.dart';
import 'package:weblibre/features/user/domain/repositories/engine_settings.dart';
class LocaleSettingsScreen extends HookConsumerWidget {
const LocaleSettingsScreen();
@override
Widget build(BuildContext context, WidgetRef ref) {
final formKey = useMemoized(() => GlobalKey<FormState>());
final systemLocales = useMemoized(
() => WidgetsBinding.instance.platformDispatcher.locales
.map((locale) => locale.toIntlLocale())
.toList(),
);
final userLocales = ref.watch(
engineSettingsWithDefaultsProvider.select(
(settings) => EquatableValue(
settings.locales.map(Locale.tryParse).nonNulls.toSet(),
),
),
);
final availableLocales = {
...systemLocales,
...userLocales.value,
Locale.fromSubtags(languageCode: 'en', countryCode: 'US'),
};
final customLocaleController = useTextEditingController();
return Scaffold(
appBar: AppBar(title: const Text('Browser Languages')),
body: SafeArea(
child: ListView(
children: [
...availableLocales.map((locale) {
return CheckboxListTile.adaptive(
value: userLocales.value.contains(locale),
onChanged: (value) async {
if (value != null) {
await ref
.read(saveEngineSettingsControllerProvider.notifier)
.save(
(currentSettings) => currentSettings.copyWith.locales(
value
? {
...currentSettings.locales,
locale.toLanguageTag(),
}.toList()
: ([
...currentSettings.locales,
]..remove(locale.toLanguageTag())).toList(),
),
);
}
},
title: Consumer(
builder: (context, ref, child) {
final resolvedAsync = ref.watch(
resolveLocaleProvider(locale),
);
return Text(
resolvedAsync.maybeWhen(
data: (data) =>
data.mapNotNull(
(data) =>
'${data.languageName} ${data.countryName.mapNotNull((country) => '($country)') ?? ''}'
.trim(),
) ??
locale.toLanguageTag(),
orElse: () => locale.toLanguageTag(),
),
);
},
),
subtitle: Text(locale.toLanguageTag()),
secondary: CountryFlag.fromLanguageCode(
locale.languageCode,
theme: const ImageTheme(shape: RoundedRectangle(8.0)),
),
);
}),
const Divider(),
Padding(
padding: const EdgeInsets.only(left: 16.0, right: 20),
child: Form(
key: formKey,
child: TextFormField(
controller: customLocaleController,
decoration: InputDecoration(
label: const Text('Custom Locale'),
hint: const Text('en-US'),
floatingLabelBehavior: FloatingLabelBehavior.always,
suffixIcon: IconButton(
onPressed: () {
if (formKey.currentState?.validate() == true) {
formKey.currentState?.save();
}
},
icon: const Icon(Icons.add),
),
),
validator: (value) {
if (value != null && Locale.tryParse(value) == null) {
return 'Invalid locale identifier';
}
return null;
},
onSaved: (newValue) async {
if (newValue != null) {
await ref
.read(saveEngineSettingsControllerProvider.notifier)
.save(
(currentSettings) =>
currentSettings.copyWith.locales(
{
...currentSettings.locales,
Locale.parse(newValue).toLanguageTag(),
}.toList(),
),
);
customLocaleController.clear();
}
},
),
),
),
],
),
),
);
}
}
@@ -203,6 +203,18 @@ class WebEngineSettingsScreen extends HookConsumerWidget {
],
),
),
ListTile(
title: const Text('Browser Languages'),
contentPadding: const EdgeInsets.symmetric(
vertical: 8.0,
horizontal: 16.0,
),
leading: const Icon(MdiIcons.translate),
trailing: const Icon(Icons.chevron_right),
onTap: () async {
await LocaleSettingsRoute().push(context);
},
),
SwitchListTile.adaptive(
title: const Text('Global Privacy Control (GPC)'),
secondary: const Icon(MdiIcons.incognitoCircleOff),
@@ -21,6 +21,7 @@ import 'dart:convert';
import 'package:copy_with_extension/copy_with_extension.dart';
import 'package:fast_equatable/fast_equatable.dart';
import 'package:flutter/widgets.dart';
import 'package:flutter_mozilla_components/flutter_mozilla_components.dart';
import 'package:json_annotation/json_annotation.dart';
import 'package:nullability/nullability.dart';
@@ -75,6 +76,9 @@ class EngineSettings extends GeckoEngineSettings with FastEquatable {
@override
bool get enterpriseRootsEnabled => super.enterpriseRootsEnabled!;
@override
List<String> get locales => super.locales!;
final QueryParameterStripping queryParameterStripping;
final BounceTrackingProtectionMode bounceTrackingProtectionMode;
@@ -130,6 +134,7 @@ class EngineSettings extends GeckoEngineSettings with FastEquatable {
required this.dohExceptionsList,
required super.fingerprintingProtectionOverrides,
required this.enablePdfJs,
required super.locales,
});
EngineSettings.withDefaults({
@@ -154,6 +159,7 @@ class EngineSettings extends GeckoEngineSettings with FastEquatable {
List<String>? dohExceptionsList,
String? fingerprintingProtectionOverrides,
bool? enablePdfJs,
List<String>? locales,
}) : queryParameterStripping =
queryParameterStripping ?? QueryParameterStripping.disabled,
bounceTrackingProtectionMode =
@@ -188,6 +194,11 @@ class EngineSettings extends GeckoEngineSettings with FastEquatable {
fingerprintingProtectionOverrides:
fingerprintingProtectionOverrides ??
FingerprintOverrides.defaults().toString(),
locales:
locales ??
WidgetsBinding.instance.platformDispatcher.locales
.map((x) => x.toLanguageTag())
.toList(),
);
static AddonCollection? _addonCollectionFromJson(String? json) =>
@@ -226,5 +237,6 @@ class EngineSettings extends GeckoEngineSettings with FastEquatable {
dohExceptionsList,
fingerprintingProtectionOverrides,
enablePdfJs,
locales,
];
}
@@ -67,6 +67,8 @@ abstract class _$EngineSettingsCWProxy {
EngineSettings enablePdfJs(bool enablePdfJs);
EngineSettings locales(List<String>? locales);
/// Creates a new instance with the provided field values.
/// Passing `null` to a nullable field nullifies it, while `null` for a non-nullable field is ignored. To update a single field use `EngineSettings(...).copyWith.fieldName(value)`.
///
@@ -96,6 +98,7 @@ abstract class _$EngineSettingsCWProxy {
List<String> dohExceptionsList,
String? fingerprintingProtectionOverrides,
bool enablePdfJs,
List<String>? locales,
});
}
@@ -207,6 +210,9 @@ class _$EngineSettingsCWProxyImpl implements _$EngineSettingsCWProxy {
EngineSettings enablePdfJs(bool enablePdfJs) =>
call(enablePdfJs: enablePdfJs);
@override
EngineSettings locales(List<String>? locales) => call(locales: locales);
@override
/// Creates a new instance with the provided field values.
/// Passing `null` to a nullable field nullifies it, while `null` for a non-nullable field is ignored. To update a single field use `EngineSettings(...).copyWith.fieldName(value)`.
@@ -239,6 +245,7 @@ class _$EngineSettingsCWProxyImpl implements _$EngineSettingsCWProxy {
Object? dohExceptionsList = const $CopyWithPlaceholder(),
Object? fingerprintingProtectionOverrides = const $CopyWithPlaceholder(),
Object? enablePdfJs = const $CopyWithPlaceholder(),
Object? locales = const $CopyWithPlaceholder(),
}) {
return EngineSettings(
javascriptEnabled: javascriptEnabled == const $CopyWithPlaceholder()
@@ -350,6 +357,10 @@ class _$EngineSettingsCWProxyImpl implements _$EngineSettingsCWProxy {
? _value.enablePdfJs
// ignore: cast_nullable_to_non_nullable
: enablePdfJs as bool,
locales: locales == const $CopyWithPlaceholder()
? _value.locales
// ignore: cast_nullable_to_non_nullable
: locales as List<String>?,
);
}
}
@@ -422,6 +433,9 @@ EngineSettings _$EngineSettingsFromJson(Map<String, dynamic> json) =>
fingerprintingProtectionOverrides:
json['fingerprintingProtectionOverrides'] as String?,
enablePdfJs: json['enablePdfJs'] as bool?,
locales: (json['locales'] as List<dynamic>?)
?.map((e) => e as String)
.toList(),
);
Map<String, dynamic> _$EngineSettingsToJson(
@@ -448,6 +462,7 @@ Map<String, dynamic> _$EngineSettingsToJson(
_$WebContentIsolationStrategyEnumMap[instance
.webContentIsolationStrategy]!,
'enterpriseRootsEnabled': instance.enterpriseRootsEnabled,
'locales': instance.locales,
'queryParameterStripping':
_$QueryParameterStrippingEnumMap[instance.queryParameterStripping]!,
'bounceTrackingProtectionMode':
@@ -124,6 +124,9 @@ class EngineSettingsRepository extends _$EngineSettingsRepository {
DriftSqlType.bool,
db.typeMapping,
),
'locales': settings['locales']
?.readAs(DriftSqlType.string, db.typeMapping)
.mapNotNull(jsonDecode),
});
}
@@ -34,7 +34,7 @@ final class EngineSettingsRepositoryProvider
}
String _$engineSettingsRepositoryHash() =>
r'cd087c5631faaddf1fced31569b0fd577d6030a7';
r'916abcd932fc3b3b1e060bc93a46c1a3a38bb984';
abstract class _$EngineSettingsRepository
extends $StreamNotifier<EngineSettings> {
+3
View File
@@ -11,6 +11,7 @@ dependencies:
background_fetch: ^1.4.0
collection: ^1.19.1
copy_with_extension: ^9.1.1
country_flags: ^4.1.0
drift: ^2.29.0
dynamic_color: ^1.8.1
exceptions: ^0.6.1
@@ -42,6 +43,8 @@ dependencies:
lexo_rank:
git:
url: https://github.com/FaFre/lexo_rank.git
locale_resolver:
path: ../packages/locale_resolver
local_auth: ^3.0.0
logger: ^2.6.2
markdown: ^7.3.0
@@ -73,6 +73,7 @@ object EngineProvider {
//builder.debugLogging(components.logLevel == Log.Priority.DEBUG)
builder.consoleOutput(components.logLevel == Log.Priority.DEBUG)
builder.contentBlocking(contentBlocking.build())
builder.locales(arrayOf("en-US", "en")) // Will be overridden later
runtime = GeckoRuntime.create(context, builder.build())
}
@@ -6,6 +6,7 @@
package eu.weblibre.flutter_mozilla_components.api
import eu.weblibre.flutter_mozilla_components.EngineProvider
import eu.weblibre.flutter_mozilla_components.GlobalComponents
import eu.weblibre.flutter_mozilla_components.pigeons.ColorScheme
import eu.weblibre.flutter_mozilla_components.pigeons.CookieBannerHandlingMode
@@ -147,6 +148,10 @@ class GeckoEngineSettingsApiImpl : GeckoEngineSettingsApi {
if(settings.fingerprintingProtectionOverrides != null) {
components.core.engineSettings.fingerprintingProtectionOverrides = settings.fingerprintingProtectionOverrides
}
if(settings.locales != null) {
// components.core.engineSettings.automaticLanguageAdjustment = false
components.core.runtime.settings.locales = settings.locales.toTypedArray()
}
}
override fun updateRuntimeSettings(settings: GeckoEngineSettings) {
@@ -14,6 +14,7 @@ import eu.weblibre.flutter_mozilla_components.Components
import eu.weblibre.flutter_mozilla_components.interceptor.AppRequestInterceptor
import eu.weblibre.flutter_mozilla_components.services.DownloadService
import eu.weblibre.flutter_mozilla_components.EngineProvider
import eu.weblibre.flutter_mozilla_components.EngineProvider.getOrCreateRuntime
import eu.weblibre.flutter_mozilla_components.PermissionStorage
import eu.weblibre.flutter_mozilla_components.services.MediaSessionService
import eu.weblibre.flutter_mozilla_components.activities.NotificationActivity
@@ -58,6 +59,7 @@ import mozilla.components.feature.session.middleware.undo.UndoMiddleware
import mozilla.components.feature.sitepermissions.OnDiskSitePermissionsStorage
import mozilla.components.feature.webnotifications.WebNotificationFeature
import mozilla.components.support.base.worker.Frequency
import org.mozilla.geckoview.GeckoRuntime
import java.util.concurrent.TimeUnit
private const val AMO_COLLECTION_MAX_CACHE_AGE = 24 * 60L
@@ -107,6 +109,10 @@ class Core(
)
}
val runtime: GeckoRuntime by lazy {
getOrCreateRuntime(context)
}
val engine: Engine by lazy {
EngineProvider.createEngine(context, engineSettings, extensionEvents)
}
@@ -1678,7 +1678,8 @@ data class GeckoEngineSettings (
val contentBlocking: ContentBlocking? = null,
val enterpriseRootsEnabled: Boolean? = null,
val dohSettings: DohSettings? = null,
val fingerprintingProtectionOverrides: String? = null
val fingerprintingProtectionOverrides: String? = null,
val locales: List<String>? = null
)
{
companion object {
@@ -1698,7 +1699,8 @@ data class GeckoEngineSettings (
val enterpriseRootsEnabled = pigeonVar_list[12] as Boolean?
val dohSettings = pigeonVar_list[13] as DohSettings?
val fingerprintingProtectionOverrides = pigeonVar_list[14] as String?
return GeckoEngineSettings(javascriptEnabled, trackingProtectionPolicy, httpsOnlyMode, globalPrivacyControlEnabled, preferredColorScheme, cookieBannerHandlingMode, cookieBannerHandlingModePrivateBrowsing, cookieBannerHandlingGlobalRules, cookieBannerHandlingGlobalRulesSubFrames, webContentIsolationStrategy, userAgent, contentBlocking, enterpriseRootsEnabled, dohSettings, fingerprintingProtectionOverrides)
val locales = pigeonVar_list[15] as List<String>?
return GeckoEngineSettings(javascriptEnabled, trackingProtectionPolicy, httpsOnlyMode, globalPrivacyControlEnabled, preferredColorScheme, cookieBannerHandlingMode, cookieBannerHandlingModePrivateBrowsing, cookieBannerHandlingGlobalRules, cookieBannerHandlingGlobalRulesSubFrames, webContentIsolationStrategy, userAgent, contentBlocking, enterpriseRootsEnabled, dohSettings, fingerprintingProtectionOverrides, locales)
}
}
fun toList(): List<Any?> {
@@ -1718,6 +1720,7 @@ data class GeckoEngineSettings (
enterpriseRootsEnabled,
dohSettings,
fingerprintingProtectionOverrides,
locales,
)
}
override fun equals(other: Any?): Boolean {
@@ -2044,6 +2044,7 @@ class GeckoEngineSettings {
this.enterpriseRootsEnabled,
this.dohSettings,
this.fingerprintingProtectionOverrides,
this.locales,
});
bool? javascriptEnabled;
@@ -2076,6 +2077,8 @@ class GeckoEngineSettings {
String? fingerprintingProtectionOverrides;
List<String>? locales;
List<Object?> _toList() {
return <Object?>[
javascriptEnabled,
@@ -2093,6 +2096,7 @@ class GeckoEngineSettings {
enterpriseRootsEnabled,
dohSettings,
fingerprintingProtectionOverrides,
locales,
];
}
@@ -2117,6 +2121,7 @@ class GeckoEngineSettings {
enterpriseRootsEnabled: result[12] as bool?,
dohSettings: result[13] as DohSettings?,
fingerprintingProtectionOverrides: result[14] as String?,
locales: (result[15] as List<Object?>?)?.cast<String>(),
);
}
@@ -667,6 +667,7 @@ class GeckoEngineSettings {
final bool? enterpriseRootsEnabled;
final DohSettings? dohSettings;
final String? fingerprintingProtectionOverrides;
final List<String>? locales;
GeckoEngineSettings(
this.javascriptEnabled,
@@ -684,6 +685,7 @@ class GeckoEngineSettings {
this.enterpriseRootsEnabled,
this.dohSettings,
this.fingerprintingProtectionOverrides,
this.locales,
);
}
+33
View File
@@ -0,0 +1,33 @@
# Miscellaneous
*.class
*.log
*.pyc
*.swp
.DS_Store
.atom/
.build/
.buildlog/
.history
.svn/
.swiftpm/
migrate_working_dir/
# IntelliJ related
*.iml
*.ipr
*.iws
.idea/
# The .vscode folder contains launch configuration and tasks you configure in
# VS Code which you may wish to be included in version control, so this line
# is commented out by default.
#.vscode/
# Flutter/Dart/Pub related
# Libraries should not include pubspec.lock, per https://dart.dev/guides/libraries/private-files#pubspeclock.
/pubspec.lock
**/doc/api/
.dart_tool/
.flutter-plugins-dependencies
/build/
/coverage/
+30
View File
@@ -0,0 +1,30 @@
# This file tracks properties of this Flutter project.
# Used by Flutter tool to assess capabilities and perform upgrades etc.
#
# This file should be version controlled and should not be manually edited.
version:
revision: "9f455d2486bcb28cad87b062475f42edc959f636"
channel: "stable"
project_type: plugin
# Tracks metadata for the flutter migrate command
migration:
platforms:
- platform: root
create_revision: 9f455d2486bcb28cad87b062475f42edc959f636
base_revision: 9f455d2486bcb28cad87b062475f42edc959f636
- platform: android
create_revision: 9f455d2486bcb28cad87b062475f42edc959f636
base_revision: 9f455d2486bcb28cad87b062475f42edc959f636
# User provided section
# List of Local paths (relative to this file) that should be
# ignored by the migrate tool.
#
# Files that are not part of the templates will be ignored by default.
unmanaged_files:
- 'lib/main.dart'
- 'ios/Runner.xcodeproj/project.pbxproj'
+3
View File
@@ -0,0 +1,3 @@
## 0.0.1
* TODO: Describe initial release.
+1
View File
@@ -0,0 +1 @@
TODO: Add your license here.
+15
View File
@@ -0,0 +1,15 @@
# locale_resolver
A new Flutter plugin project.
## Getting Started
This project is a starting point for a Flutter
[plug-in package](https://flutter.dev/to/develop-plugins),
a specialized package that includes platform-specific implementation code for
Android and/or iOS.
For help getting started with Flutter development, view the
[online documentation](https://docs.flutter.dev), which offers tutorials,
samples, guidance on mobile development, and a full API reference.
@@ -0,0 +1,35 @@
# This file configures the static analysis results for your project (errors,
# warnings, and lints).
#
# This enables the 'recommended' set of lints from `package:lints`.
# This set helps identify many issues that may lead to problems when running
# or consuming Dart code, and enforces writing Dart using a single, idiomatic
# style and format.
#
# If you want a smaller set of lints you can change this to specify
# 'package:lints/core.yaml'. These are just the most critical lints
# (the recommended set includes the core lints).
# The core lints are also what is used by pub.dev for scoring packages.
include: package:lint/package.yaml
# Uncomment the following section to specify additional rules.
linter:
rules:
unawaited_futures: true
discarded_futures: true
collection_methods_unrelated_type: true
analyzer:
plugins:
- custom_lint
exclude:
- "**.g.dart"
- "**.swagger.dart"
- "**.freezed.dart"
- "**.chopper.dart"
# For more information about the core and recommended set of lints, see
# https://dart.dev/go/core-lints
# For additional information about configuring this file, see
# https://dart.dev/guides/language/analysis-options
@@ -0,0 +1,9 @@
*.iml
.gradle
/local.properties
/.idea/workspace.xml
/.idea/libraries
.DS_Store
/build
/captures
.cxx
@@ -0,0 +1,66 @@
group = "eu.weblibre.locale_resolver"
version = "1.0-SNAPSHOT"
buildscript {
ext.kotlin_version = "2.1.20"
repositories {
google()
mavenCentral()
}
dependencies {
classpath("com.android.tools.build:gradle:8.11.1")
classpath("org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version")
}
}
allprojects {
repositories {
google()
mavenCentral()
}
}
apply plugin: "com.android.library"
apply plugin: "kotlin-android"
android {
namespace = "eu.weblibre.locale_resolver"
compileSdk = 36
compileOptions {
sourceCompatibility = JavaVersion.VERSION_11
targetCompatibility = JavaVersion.VERSION_11
}
kotlinOptions {
jvmTarget = JavaVersion.VERSION_11
}
sourceSets {
main.java.srcDirs += "src/main/kotlin"
test.java.srcDirs += "src/test/kotlin"
}
defaultConfig {
minSdk = 24
}
dependencies {
testImplementation("org.jetbrains.kotlin:kotlin-test")
testImplementation("org.mockito:mockito-core:5.0.0")
}
testOptions {
unitTests.all {
useJUnitPlatform()
testLogging {
events "passed", "skipped", "failed", "standardOut", "standardError"
outputs.upToDateWhen {false}
showStandardStreams = true
}
}
}
}
@@ -0,0 +1 @@
rootProject.name = 'locale_resolver'
@@ -0,0 +1,3 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="eu.weblibre.locale_resolver">
</manifest>
@@ -0,0 +1,20 @@
package eu.weblibre.locale_resolver
import eu.weblibre.locale_resolver.pigeons.LocaleResolver
import eu.weblibre.locale_resolver.pigeons.LocalizedResult
import java.util.Locale
class LocaleResolverImpl : LocaleResolver {
override fun resolve(languageTag: String, targetLangouageTag: String): LocalizedResult {
val deviceLocale = Locale.forLanguageTag(targetLangouageTag)
val targetLocale = Locale.forLanguageTag(languageTag)
val languageName = targetLocale.getDisplayLanguage(deviceLocale)
val countryName = targetLocale.getDisplayCountry(deviceLocale)
return LocalizedResult(
languageName,
countryName.ifBlank { null }
)
}
}
@@ -0,0 +1,19 @@
package eu.weblibre.locale_resolver
import eu.weblibre.locale_resolver.pigeons.LocaleResolver
import io.flutter.embedding.engine.plugins.FlutterPlugin
import io.flutter.plugin.common.MethodCall
import io.flutter.plugin.common.MethodChannel
import io.flutter.plugin.common.MethodChannel.MethodCallHandler
import io.flutter.plugin.common.MethodChannel.Result
/** LocaleResolverPlugin */
class LocaleResolverPlugin : FlutterPlugin {
override fun onAttachedToEngine(flutterPluginBinding: FlutterPlugin.FlutterPluginBinding) {
LocaleResolver.setUp(flutterPluginBinding.binaryMessenger, LocaleResolverImpl())
}
override fun onDetachedFromEngine(binding: FlutterPlugin.FlutterPluginBinding) {
}
}
@@ -0,0 +1,166 @@
// Autogenerated from Pigeon (v26.0.1), do not edit directly.
// See also: https://pub.dev/packages/pigeon
@file:Suppress("UNCHECKED_CAST", "ArrayInDataClass")
package eu.weblibre.locale_resolver.pigeons
import android.util.Log
import io.flutter.plugin.common.BasicMessageChannel
import io.flutter.plugin.common.BinaryMessenger
import io.flutter.plugin.common.EventChannel
import io.flutter.plugin.common.MessageCodec
import io.flutter.plugin.common.StandardMethodCodec
import io.flutter.plugin.common.StandardMessageCodec
import java.io.ByteArrayOutputStream
import java.nio.ByteBuffer
private object LocalesPigeonUtils {
fun wrapResult(result: Any?): List<Any?> {
return listOf(result)
}
fun wrapError(exception: Throwable): List<Any?> {
return if (exception is FlutterError) {
listOf(
exception.code,
exception.message,
exception.details
)
} else {
listOf(
exception.javaClass.simpleName,
exception.toString(),
"Cause: " + exception.cause + ", Stacktrace: " + Log.getStackTraceString(exception)
)
}
}
fun deepEquals(a: Any?, b: Any?): Boolean {
if (a is ByteArray && b is ByteArray) {
return a.contentEquals(b)
}
if (a is IntArray && b is IntArray) {
return a.contentEquals(b)
}
if (a is LongArray && b is LongArray) {
return a.contentEquals(b)
}
if (a is DoubleArray && b is DoubleArray) {
return a.contentEquals(b)
}
if (a is Array<*> && b is Array<*>) {
return a.size == b.size &&
a.indices.all{ deepEquals(a[it], b[it]) }
}
if (a is List<*> && b is List<*>) {
return a.size == b.size &&
a.indices.all{ deepEquals(a[it], b[it]) }
}
if (a is Map<*, *> && b is Map<*, *>) {
return a.size == b.size && a.all {
(b as Map<Any?, Any?>).containsKey(it.key) &&
deepEquals(it.value, b[it.key])
}
}
return a == b
}
}
/**
* Error class for passing custom error details to Flutter via a thrown PlatformException.
* @property code The error code.
* @property message The error message.
* @property details The error details. Must be a datatype supported by the api codec.
*/
class FlutterError (
val code: String,
override val message: String? = null,
val details: Any? = null
) : Throwable()
/** Generated class from Pigeon that represents data sent in messages. */
data class LocalizedResult (
val languageName: String,
val countryName: String? = null
)
{
companion object {
fun fromList(pigeonVar_list: List<Any?>): LocalizedResult {
val languageName = pigeonVar_list[0] as String
val countryName = pigeonVar_list[1] as String?
return LocalizedResult(languageName, countryName)
}
}
fun toList(): List<Any?> {
return listOf(
languageName,
countryName,
)
}
override fun equals(other: Any?): Boolean {
if (other !is LocalizedResult) {
return false
}
if (this === other) {
return true
}
return LocalesPigeonUtils.deepEquals(toList(), other.toList()) }
override fun hashCode(): Int = toList().hashCode()
}
private open class LocalesPigeonCodec : StandardMessageCodec() {
override fun readValueOfType(type: Byte, buffer: ByteBuffer): Any? {
return when (type) {
129.toByte() -> {
return (readValue(buffer) as? List<Any?>)?.let {
LocalizedResult.fromList(it)
}
}
else -> super.readValueOfType(type, buffer)
}
}
override fun writeValue(stream: ByteArrayOutputStream, value: Any?) {
when (value) {
is LocalizedResult -> {
stream.write(129)
writeValue(stream, value.toList())
}
else -> super.writeValue(stream, value)
}
}
}
/** Generated interface from Pigeon that represents a handler of messages from Flutter. */
interface LocaleResolver {
fun resolve(languageTag: String, targetLangouageTag: String): LocalizedResult
companion object {
/** The codec used by LocaleResolver. */
val codec: MessageCodec<Any?> by lazy {
LocalesPigeonCodec()
}
/** Sets up an instance of `LocaleResolver` to handle messages through the `binaryMessenger`. */
@JvmOverloads
fun setUp(binaryMessenger: BinaryMessenger, api: LocaleResolver?, messageChannelSuffix: String = "") {
val separatedMessageChannelSuffix = if (messageChannelSuffix.isNotEmpty()) ".$messageChannelSuffix" else ""
run {
val channel = BasicMessageChannel<Any?>(binaryMessenger, "dev.flutter.pigeon.locale_resolver.LocaleResolver.resolve$separatedMessageChannelSuffix", codec)
if (api != null) {
channel.setMessageHandler { message, reply ->
val args = message as List<Any?>
val languageTagArg = args[0] as String
val targetLangouageTagArg = args[1] as String
val wrapped: List<Any?> = try {
listOf(api.resolve(languageTagArg, targetLangouageTagArg))
} catch (exception: Throwable) {
LocalesPigeonUtils.wrapError(exception)
}
reply.reply(wrapped)
}
} else {
channel.setMessageHandler(null)
}
}
}
}
}
@@ -0,0 +1,27 @@
package eu.weblibre.locale_resolver
import io.flutter.plugin.common.MethodCall
import io.flutter.plugin.common.MethodChannel
import org.mockito.Mockito
import kotlin.test.Test
/*
* This demonstrates a simple unit test of the Kotlin portion of this plugin's implementation.
*
* Once you have built the plugin's example app, you can run these tests from the command
* line by running `./gradlew testDebugUnitTest` in the `example/android/` directory, or
* you can run them directly from IDEs that support JUnit such as Android Studio.
*/
internal class LocaleResolverPluginTest {
@Test
fun onMethodCall_getPlatformVersion_returnsExpectedValue() {
val plugin = LocaleResolverPlugin()
val call = MethodCall("getPlatformVersion", null)
val mockResult: MethodChannel.Result = Mockito.mock(MethodChannel.Result::class.java)
plugin.onMethodCall(call, mockResult)
Mockito.verify(mockResult).success("Android " + android.os.Build.VERSION.RELEASE)
}
}
@@ -0,0 +1,45 @@
# Miscellaneous
*.class
*.log
*.pyc
*.swp
.DS_Store
.atom/
.build/
.buildlog/
.history
.svn/
.swiftpm/
migrate_working_dir/
# IntelliJ related
*.iml
*.ipr
*.iws
.idea/
# The .vscode folder contains launch configuration and tasks you configure in
# VS Code which you may wish to be included in version control, so this line
# is commented out by default.
#.vscode/
# Flutter/Dart/Pub related
**/doc/api/
**/ios/Flutter/.last_build_id
.dart_tool/
.flutter-plugins-dependencies
.pub-cache/
.pub/
/build/
/coverage/
# Symbolication related
app.*.symbols
# Obfuscation related
app.*.map.json
# Android Studio will place build artifacts here
/android/app/debug
/android/app/profile
/android/app/release
@@ -0,0 +1,16 @@
# locale_resolver_example
Demonstrates how to use the locale_resolver plugin.
## Getting Started
This project is a starting point for a Flutter application.
A few resources to get you started if this is your first Flutter project:
- [Lab: Write your first Flutter app](https://docs.flutter.dev/get-started/codelab)
- [Cookbook: Useful Flutter samples](https://docs.flutter.dev/cookbook)
For help getting started with Flutter development, view the
[online documentation](https://docs.flutter.dev/), which offers tutorials,
samples, guidance on mobile development, and a full API reference.
@@ -0,0 +1,28 @@
# This file configures the analyzer, which statically analyzes Dart code to
# check for errors, warnings, and lints.
#
# The issues identified by the analyzer are surfaced in the UI of Dart-enabled
# IDEs (https://dart.dev/tools#ides-and-editors). The analyzer can also be
# invoked from the command line by running `flutter analyze`.
# The following line activates a set of recommended lints for Flutter apps,
# packages, and plugins designed to encourage good coding practices.
include: package:flutter_lints/flutter.yaml
linter:
# The lint rules applied to this project can be customized in the
# section below to disable rules from the `package:flutter_lints/flutter.yaml`
# included above or to enable additional rules. A list of all available lints
# and their documentation is published at https://dart.dev/lints.
#
# Instead of disabling a lint rule for the entire project in the
# section below, it can also be suppressed for a single line of code
# or a specific dart file by using the `// ignore: name_of_lint` and
# `// ignore_for_file: name_of_lint` syntax on the line or in the file
# producing the lint.
rules:
# avoid_print: false # Uncomment to disable the `avoid_print` rule
# prefer_single_quotes: true # Uncomment to enable the `prefer_single_quotes` rule
# Additional information about this file can be found at
# https://dart.dev/guides/language/analysis-options
@@ -0,0 +1,14 @@
gradle-wrapper.jar
/.gradle
/captures/
/gradlew
/gradlew.bat
/local.properties
GeneratedPluginRegistrant.java
.cxx/
# Remember to never publicly share your keystore.
# See https://flutter.dev/to/reference-keystore
key.properties
**/*.keystore
**/*.jks
@@ -0,0 +1,44 @@
plugins {
id("com.android.application")
id("kotlin-android")
// The Flutter Gradle Plugin must be applied after the Android and Kotlin Gradle plugins.
id("dev.flutter.flutter-gradle-plugin")
}
android {
namespace = "eu.weblibre.locale_resolver_example"
compileSdk = flutter.compileSdkVersion
ndkVersion = flutter.ndkVersion
compileOptions {
sourceCompatibility = JavaVersion.VERSION_11
targetCompatibility = JavaVersion.VERSION_11
}
kotlinOptions {
jvmTarget = JavaVersion.VERSION_11.toString()
}
defaultConfig {
// TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html).
applicationId = "eu.weblibre.locale_resolver_example"
// You can update the following values to match your application needs.
// For more information, see: https://flutter.dev/to/review-gradle-config.
minSdk = flutter.minSdkVersion
targetSdk = flutter.targetSdkVersion
versionCode = flutter.versionCode
versionName = flutter.versionName
}
buildTypes {
release {
// TODO: Add your own signing config for the release build.
// Signing with the debug keys for now, so `flutter run --release` works.
signingConfig = signingConfigs.getByName("debug")
}
}
}
flutter {
source = "../.."
}
@@ -0,0 +1,7 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<!-- The INTERNET permission is required for development. Specifically,
the Flutter tool needs it to communicate with the running application
to allow setting breakpoints, to provide hot reload, etc.
-->
<uses-permission android:name="android.permission.INTERNET"/>
</manifest>
@@ -0,0 +1,45 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<application
android:label="locale_resolver_example"
android:name="${applicationName}"
android:icon="@mipmap/ic_launcher">
<activity
android:name=".MainActivity"
android:exported="true"
android:launchMode="singleTop"
android:taskAffinity=""
android:theme="@style/LaunchTheme"
android:configChanges="orientation|keyboardHidden|keyboard|screenSize|smallestScreenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode"
android:hardwareAccelerated="true"
android:windowSoftInputMode="adjustResize">
<!-- Specifies an Android theme to apply to this Activity as soon as
the Android process has started. This theme is visible to the user
while the Flutter UI initializes. After that, this theme continues
to determine the Window background behind the Flutter UI. -->
<meta-data
android:name="io.flutter.embedding.android.NormalTheme"
android:resource="@style/NormalTheme"
/>
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
</activity>
<!-- Don't delete the meta-data below.
This is used by the Flutter tool to generate GeneratedPluginRegistrant.java -->
<meta-data
android:name="flutterEmbedding"
android:value="2" />
</application>
<!-- Required to query activities that can process text, see:
https://developer.android.com/training/package-visibility and
https://developer.android.com/reference/android/content/Intent#ACTION_PROCESS_TEXT.
In particular, this is used by the Flutter engine in io.flutter.plugin.text.ProcessTextPlugin. -->
<queries>
<intent>
<action android:name="android.intent.action.PROCESS_TEXT"/>
<data android:mimeType="text/plain"/>
</intent>
</queries>
</manifest>
@@ -0,0 +1,5 @@
package eu.weblibre.locale_resolver_example
import io.flutter.embedding.android.FlutterActivity
class MainActivity : FlutterActivity()
@@ -0,0 +1,12 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Modify this file to customize your launch splash screen -->
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item android:drawable="?android:colorBackground" />
<!-- You can insert your own image assets here -->
<!-- <item>
<bitmap
android:gravity="center"
android:src="@mipmap/launch_image" />
</item> -->
</layer-list>
@@ -0,0 +1,12 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Modify this file to customize your launch splash screen -->
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item android:drawable="@android:color/white" />
<!-- You can insert your own image assets here -->
<!-- <item>
<bitmap
android:gravity="center"
android:src="@mipmap/launch_image" />
</item> -->
</layer-list>
Binary file not shown.

After

Width:  |  Height:  |  Size: 544 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 442 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 721 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

@@ -0,0 +1,18 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<!-- Theme applied to the Android Window while the process is starting when the OS's Dark Mode setting is on -->
<style name="LaunchTheme" parent="@android:style/Theme.Black.NoTitleBar">
<!-- Show a splash screen on the activity. Automatically removed when
the Flutter engine draws its first frame -->
<item name="android:windowBackground">@drawable/launch_background</item>
</style>
<!-- Theme applied to the Android Window as soon as the process has started.
This theme determines the color of the Android Window while your
Flutter UI initializes, as well as behind your Flutter UI while its
running.
This Theme is only used starting with V2 of Flutter's Android embedding. -->
<style name="NormalTheme" parent="@android:style/Theme.Black.NoTitleBar">
<item name="android:windowBackground">?android:colorBackground</item>
</style>
</resources>
@@ -0,0 +1,18 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<!-- Theme applied to the Android Window while the process is starting when the OS's Dark Mode setting is off -->
<style name="LaunchTheme" parent="@android:style/Theme.Light.NoTitleBar">
<!-- Show a splash screen on the activity. Automatically removed when
the Flutter engine draws its first frame -->
<item name="android:windowBackground">@drawable/launch_background</item>
</style>
<!-- Theme applied to the Android Window as soon as the process has started.
This theme determines the color of the Android Window while your
Flutter UI initializes, as well as behind your Flutter UI while its
running.
This Theme is only used starting with V2 of Flutter's Android embedding. -->
<style name="NormalTheme" parent="@android:style/Theme.Light.NoTitleBar">
<item name="android:windowBackground">?android:colorBackground</item>
</style>
</resources>
@@ -0,0 +1,7 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<!-- The INTERNET permission is required for development. Specifically,
the Flutter tool needs it to communicate with the running application
to allow setting breakpoints, to provide hot reload, etc.
-->
<uses-permission android:name="android.permission.INTERNET"/>
</manifest>
@@ -0,0 +1,24 @@
allprojects {
repositories {
google()
mavenCentral()
}
}
val newBuildDir: Directory =
rootProject.layout.buildDirectory
.dir("../../build")
.get()
rootProject.layout.buildDirectory.value(newBuildDir)
subprojects {
val newSubprojectBuildDir: Directory = newBuildDir.dir(project.name)
project.layout.buildDirectory.value(newSubprojectBuildDir)
}
subprojects {
project.evaluationDependsOn(":app")
}
tasks.register<Delete>("clean") {
delete(rootProject.layout.buildDirectory)
}
@@ -0,0 +1,3 @@
org.gradle.jvmargs=-Xmx8G -XX:MaxMetaspaceSize=4G -XX:ReservedCodeCacheSize=512m -XX:+HeapDumpOnOutOfMemoryError
android.useAndroidX=true
android.enableJetifier=true
@@ -0,0 +1,5 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-8.11.1-bin.zip
@@ -0,0 +1,26 @@
pluginManagement {
val flutterSdkPath =
run {
val properties = java.util.Properties()
file("local.properties").inputStream().use { properties.load(it) }
val flutterSdkPath = properties.getProperty("flutter.sdk")
require(flutterSdkPath != null) { "flutter.sdk not set in local.properties" }
flutterSdkPath
}
includeBuild("$flutterSdkPath/packages/flutter_tools/gradle")
repositories {
google()
mavenCentral()
gradlePluginPortal()
}
}
plugins {
id("dev.flutter.flutter-plugin-loader") version "1.0.0"
id("com.android.application") version "8.9.1" apply false
id("org.jetbrains.kotlin.android") version "2.1.0" apply false
}
include(":app")
@@ -0,0 +1,63 @@
// import 'package:flutter/material.dart';
// import 'dart:async';
// import 'package:flutter/services.dart';
// import 'package:locale_resolver/locale_resolver.dart';
// void main() {
// runApp(const MyApp());
// }
// class MyApp extends StatefulWidget {
// const MyApp({super.key});
// @override
// State<MyApp> createState() => _MyAppState();
// }
// class _MyAppState extends State<MyApp> {
// String _platformVersion = 'Unknown';
// final _localeResolverPlugin = LocaleResolver();
// @override
// void initState() {
// super.initState();
// initPlatformState();
// }
// // Platform messages are asynchronous, so we initialize in an async method.
// Future<void> initPlatformState() async {
// String platformVersion;
// // Platform messages may fail, so we use a try/catch PlatformException.
// // We also handle the message potentially returning null.
// try {
// platformVersion =
// await _localeResolverPlugin.getPlatformVersion() ?? 'Unknown platform version';
// } on PlatformException {
// platformVersion = 'Failed to get platform version.';
// }
// // If the widget was removed from the tree while the asynchronous platform
// // message was in flight, we want to discard the reply rather than calling
// // setState to update our non-existent appearance.
// if (!mounted) return;
// setState(() {
// _platformVersion = platformVersion;
// });
// }
// @override
// Widget build(BuildContext context) {
// return MaterialApp(
// home: Scaffold(
// appBar: AppBar(
// title: const Text('Plugin example app'),
// ),
// body: Center(
// child: Text('Running on: $_platformVersion\n'),
// ),
// ),
// );
// }
// }
@@ -0,0 +1,86 @@
name: locale_resolver_example
description: "Demonstrates how to use the locale_resolver plugin."
# The following line prevents the package from being accidentally published to
# pub.dev using `flutter pub publish`. This is preferred for private packages.
publish_to: 'none' # Remove this line if you wish to publish to pub.dev
resolution: workspace
environment:
sdk: ^3.9.2
# Dependencies specify other packages that your package needs in order to work.
# To automatically upgrade your package dependencies to the latest versions
# consider running `flutter pub upgrade --major-versions`. Alternatively,
# dependencies can be manually updated by changing the version numbers below to
# the latest version available on pub.dev. To see which dependencies have newer
# versions available, run `flutter pub outdated`.
dependencies:
flutter:
sdk: flutter
locale_resolver:
# When depending on this package from a real application you should use:
# locale_resolver: ^x.y.z
# See https://dart.dev/tools/pub/dependencies#version-constraints
# The example app is bundled with the plugin so we use a path dependency on
# the parent directory to use the current plugin's version.
path: ../
# The following adds the Cupertino Icons font to your application.
# Use with the CupertinoIcons class for iOS style icons.
cupertino_icons: ^1.0.8
dev_dependencies:
integration_test:
sdk: flutter
flutter_test:
sdk: flutter
# The "flutter_lints" package below contains a set of recommended lints to
# encourage good coding practices. The lint set provided by the package is
# activated in the `analysis_options.yaml` file located at the root of your
# package. See that file for information about deactivating specific lint
# rules and activating additional ones.
flutter_lints: ^6.0.0
# For information on the generic Dart part of this file, see the
# following page: https://dart.dev/tools/pub/pubspec
# The following section is specific to Flutter packages.
flutter:
# The following line ensures that the Material Icons font is
# included with your application, so that you can use the icons in
# the material Icons class.
uses-material-design: true
# To add assets to your application, add an assets section, like this:
# assets:
# - images/a_dot_burr.jpeg
# - images/a_dot_ham.jpeg
# An image asset can refer to one or more resolution-specific "variants", see
# https://flutter.dev/to/resolution-aware-images
# For details regarding adding assets from package dependencies, see
# https://flutter.dev/to/asset-from-package
# To add custom fonts to your application, add a fonts section here,
# in this "flutter" section. Each entry in this list should have a
# "family" key with the font family name, and a "fonts" key with a
# list giving the asset and other descriptors for the font. For
# example:
# fonts:
# - family: Schyler
# fonts:
# - asset: fonts/Schyler-Regular.ttf
# - asset: fonts/Schyler-Italic.ttf
# style: italic
# - family: Trajan Pro
# fonts:
# - asset: fonts/TrajanPro.ttf
# - asset: fonts/TrajanPro_Bold.ttf
# weight: 700
#
# For details regarding fonts from package dependencies,
# see https://flutter.dev/to/font-from-package
@@ -0,0 +1 @@
export 'src/pigeons/locales.g.dart' show LocaleResolver, LocalizedResult;
@@ -0,0 +1,145 @@
// Autogenerated from Pigeon (v26.0.1), do not edit directly.
// See also: https://pub.dev/packages/pigeon
// ignore_for_file: public_member_api_docs, non_constant_identifier_names, avoid_as, unused_import, unnecessary_parenthesis, prefer_null_aware_operators, omit_local_variable_types, unused_shown_name, unnecessary_import, no_leading_underscores_for_local_identifiers
import 'dart:async';
import 'dart:typed_data' show Float64List, Int32List, Int64List, Uint8List;
import 'package:flutter/foundation.dart' show ReadBuffer, WriteBuffer;
import 'package:flutter/services.dart';
PlatformException _createConnectionError(String channelName) {
return PlatformException(
code: 'channel-error',
message: 'Unable to establish connection on channel: "$channelName".',
);
}
bool _deepEquals(Object? a, Object? b) {
if (a is List && b is List) {
return a.length == b.length &&
a.indexed
.every(((int, dynamic) item) => _deepEquals(item.$2, b[item.$1]));
}
if (a is Map && b is Map) {
return a.length == b.length && a.entries.every((MapEntry<Object?, Object?> entry) =>
(b as Map<Object?, Object?>).containsKey(entry.key) &&
_deepEquals(entry.value, b[entry.key]));
}
return a == b;
}
class LocalizedResult {
LocalizedResult({
required this.languageName,
this.countryName,
});
String languageName;
String? countryName;
List<Object?> _toList() {
return <Object?>[
languageName,
countryName,
];
}
Object encode() {
return _toList(); }
static LocalizedResult decode(Object result) {
result as List<Object?>;
return LocalizedResult(
languageName: result[0]! as String,
countryName: result[1] as String?,
);
}
@override
// ignore: avoid_equals_and_hash_code_on_mutable_classes
bool operator ==(Object other) {
if (other is! LocalizedResult || other.runtimeType != runtimeType) {
return false;
}
if (identical(this, other)) {
return true;
}
return _deepEquals(encode(), other.encode());
}
@override
// ignore: avoid_equals_and_hash_code_on_mutable_classes
int get hashCode => Object.hashAll(_toList())
;
}
class _PigeonCodec extends StandardMessageCodec {
const _PigeonCodec();
@override
void writeValue(WriteBuffer buffer, Object? value) {
if (value is int) {
buffer.putUint8(4);
buffer.putInt64(value);
} else if (value is LocalizedResult) {
buffer.putUint8(129);
writeValue(buffer, value.encode());
} else {
super.writeValue(buffer, value);
}
}
@override
Object? readValueOfType(int type, ReadBuffer buffer) {
switch (type) {
case 129:
return LocalizedResult.decode(readValue(buffer)!);
default:
return super.readValueOfType(type, buffer);
}
}
}
class LocaleResolver {
/// Constructor for [LocaleResolver]. The [binaryMessenger] named argument is
/// available for dependency injection. If it is left null, the default
/// BinaryMessenger will be used which routes to the host platform.
LocaleResolver({BinaryMessenger? binaryMessenger, String messageChannelSuffix = ''})
: pigeonVar_binaryMessenger = binaryMessenger,
pigeonVar_messageChannelSuffix = messageChannelSuffix.isNotEmpty ? '.$messageChannelSuffix' : '';
final BinaryMessenger? pigeonVar_binaryMessenger;
static const MessageCodec<Object?> pigeonChannelCodec = _PigeonCodec();
final String pigeonVar_messageChannelSuffix;
Future<LocalizedResult> resolve(String languageTag, String targetLangouageTag) async {
final String pigeonVar_channelName = 'dev.flutter.pigeon.locale_resolver.LocaleResolver.resolve$pigeonVar_messageChannelSuffix';
final BasicMessageChannel<Object?> pigeonVar_channel = BasicMessageChannel<Object?>(
pigeonVar_channelName,
pigeonChannelCodec,
binaryMessenger: pigeonVar_binaryMessenger,
);
final Future<Object?> pigeonVar_sendFuture = pigeonVar_channel.send(<Object?>[languageTag, targetLangouageTag]);
final List<Object?>? pigeonVar_replyList =
await pigeonVar_sendFuture as List<Object?>?;
if (pigeonVar_replyList == null) {
throw _createConnectionError(pigeonVar_channelName);
} else if (pigeonVar_replyList.length > 1) {
throw PlatformException(
code: pigeonVar_replyList[0]! as String,
message: pigeonVar_replyList[1] as String?,
details: pigeonVar_replyList[2],
);
} else if (pigeonVar_replyList[0] == null) {
throw PlatformException(
code: 'null-error',
message: 'Host platform returned null value for non-null return value.',
);
} else {
return (pigeonVar_replyList[0] as LocalizedResult?)!;
}
}
}
@@ -0,0 +1,25 @@
import 'package:pigeon/pigeon.dart';
class LocalizedResult {
final String languageName;
final String? countryName;
LocalizedResult(this.languageName, this.countryName);
}
@ConfigurePigeon(
PigeonOptions(
dartOut: 'lib/src/pigeons/locales.g.dart',
dartOptions: DartOptions(),
kotlinOut:
'android/src/main/kotlin/eu/weblibre/locale_resolver/pigeons/Locales.g.kt',
kotlinOptions: KotlinOptions(
package: 'eu.weblibre.locale_resolver.pigeons',
),
dartPackageName: 'locale_resolver',
),
)
@HostApi()
abstract class LocaleResolver {
LocalizedResult resolve(String languageTag, String targetLangouageTag);
}
+71
View File
@@ -0,0 +1,71 @@
name: locale_resolver
description: "A new Flutter plugin project."
version: 0.0.1
publish_to: 'none'
resolution: workspace
environment:
sdk: ^3.9.2
flutter: '>=3.3.0'
dependencies:
flutter:
sdk: flutter
dev_dependencies:
flutter_test:
sdk: flutter
lint: ^2.8.0
pigeon: ^26.0.2
# For information on the generic Dart part of this file, see the
# following page: https://dart.dev/tools/pub/pubspec
# The following section is specific to Flutter packages.
flutter:
# This section identifies this Flutter project as a plugin project.
# The 'pluginClass' specifies the class (in Java, Kotlin, Swift, Objective-C, etc.)
# which should be registered in the plugin registry. This is required for
# using method channels.
# The Android 'package' specifies package in which the registered class is.
# This is required for using method channels on Android.
# The 'ffiPlugin' specifies that native code should be built and bundled.
# This is required for using `dart:ffi`.
# All these are used by the tooling to maintain consistency when
# adding or updating assets for this project.
plugin:
platforms:
android:
package: eu.weblibre.locale_resolver
pluginClass: LocaleResolverPlugin
# To add assets to your plugin package, add an assets section, like this:
# assets:
# - images/a_dot_burr.jpeg
# - images/a_dot_ham.jpeg
#
# For details regarding assets in packages, see
# https://flutter.dev/to/asset-from-package
#
# An image asset can refer to one or more resolution-specific "variants", see
# https://flutter.dev/to/resolution-aware-images
# To add custom fonts to your plugin package, add a fonts section here,
# in this "flutter" section. Each entry in this list should have a
# "family" key with the font family name, and a "fonts" key with a
# list giving the asset and other descriptors for the font. For
# example:
# fonts:
# - family: Schyler
# fonts:
# - asset: fonts/Schyler-Regular.ttf
# - asset: fonts/Schyler-Italic.ttf
# style: italic
# - family: Trajan Pro
# fonts:
# - asset: fonts/TrajanPro.ttf
# - asset: fonts/TrajanPro_Bold.ttf
# weight: 700
#
# For details regarding fonts in packages, see
# https://flutter.dev/to/font-from-package
+2
View File
@@ -10,6 +10,8 @@ workspace:
- packages/simple_intent_receiver
- packages/simple_intent_receiver/example
- packages/tor
- packages/locale_resolver
- packages/locale_resolver/example
dev_dependencies:
melos: ^7.2.0