added support for translations and printing
This commit is contained in:
@@ -27,6 +27,7 @@ import 'package:weblibre/features/geckoview/domain/entities/states/find_result.d
|
|||||||
import 'package:weblibre/features/geckoview/domain/entities/states/history.dart';
|
import 'package:weblibre/features/geckoview/domain/entities/states/history.dart';
|
||||||
import 'package:weblibre/features/geckoview/domain/entities/states/readerable.dart';
|
import 'package:weblibre/features/geckoview/domain/entities/states/readerable.dart';
|
||||||
import 'package:weblibre/features/geckoview/domain/entities/states/security.dart';
|
import 'package:weblibre/features/geckoview/domain/entities/states/security.dart';
|
||||||
|
import 'package:weblibre/features/geckoview/domain/entities/states/translation.dart';
|
||||||
import 'package:weblibre/features/geckoview/features/tabs/data/entities/tab_mode.dart';
|
import 'package:weblibre/features/geckoview/features/tabs/data/entities/tab_mode.dart';
|
||||||
|
|
||||||
part 'tab.g.dart';
|
part 'tab.g.dart';
|
||||||
@@ -75,6 +76,7 @@ class TabState extends WebPageInfo {
|
|||||||
final HistoryState historyState;
|
final HistoryState historyState;
|
||||||
final ReaderableState readerableState;
|
final ReaderableState readerableState;
|
||||||
final FindResultState findResultState;
|
final FindResultState findResultState;
|
||||||
|
final TranslationState translationState;
|
||||||
|
|
||||||
TabState({
|
TabState({
|
||||||
required this.id,
|
required this.id,
|
||||||
@@ -93,6 +95,7 @@ class TabState extends WebPageInfo {
|
|||||||
required this.historyState,
|
required this.historyState,
|
||||||
required this.readerableState,
|
required this.readerableState,
|
||||||
required this.findResultState,
|
required this.findResultState,
|
||||||
|
required this.translationState,
|
||||||
}) : super(title: title.trim());
|
}) : super(title: title.trim());
|
||||||
|
|
||||||
factory TabState.$default(String tabId) => TabState(
|
factory TabState.$default(String tabId) => TabState(
|
||||||
@@ -111,6 +114,7 @@ class TabState extends WebPageInfo {
|
|||||||
historyState: HistoryState.$default(),
|
historyState: HistoryState.$default(),
|
||||||
readerableState: ReaderableState.$default(),
|
readerableState: ReaderableState.$default(),
|
||||||
findResultState: FindResultState.$default(),
|
findResultState: FindResultState.$default(),
|
||||||
|
translationState: TranslationState.$default(),
|
||||||
);
|
);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@@ -130,5 +134,6 @@ class TabState extends WebPageInfo {
|
|||||||
historyState,
|
historyState,
|
||||||
readerableState,
|
readerableState,
|
||||||
findResultState,
|
findResultState,
|
||||||
|
translationState,
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -37,6 +37,8 @@ abstract class _$TabStateCWProxy {
|
|||||||
|
|
||||||
TabState findResultState(FindResultState findResultState);
|
TabState findResultState(FindResultState findResultState);
|
||||||
|
|
||||||
|
TabState translationState(TranslationState translationState);
|
||||||
|
|
||||||
/// Creates a new instance with the provided field values.
|
/// 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 `TabState(...).copyWith.fieldName(value)`.
|
/// Passing `null` to a nullable field nullifies it, while `null` for a non-nullable field is ignored. To update a single field use `TabState(...).copyWith.fieldName(value)`.
|
||||||
///
|
///
|
||||||
@@ -60,6 +62,7 @@ abstract class _$TabStateCWProxy {
|
|||||||
HistoryState historyState,
|
HistoryState historyState,
|
||||||
ReaderableState readerableState,
|
ReaderableState readerableState,
|
||||||
FindResultState findResultState,
|
FindResultState findResultState,
|
||||||
|
TranslationState translationState,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -120,6 +123,10 @@ class _$TabStateCWProxyImpl implements _$TabStateCWProxy {
|
|||||||
TabState findResultState(FindResultState findResultState) =>
|
TabState findResultState(FindResultState findResultState) =>
|
||||||
call(findResultState: findResultState);
|
call(findResultState: findResultState);
|
||||||
|
|
||||||
|
@override
|
||||||
|
TabState translationState(TranslationState translationState) =>
|
||||||
|
call(translationState: translationState);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
/// Creates a new instance with the provided field values.
|
/// 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 `TabState(...).copyWith.fieldName(value)`.
|
/// Passing `null` to a nullable field nullifies it, while `null` for a non-nullable field is ignored. To update a single field use `TabState(...).copyWith.fieldName(value)`.
|
||||||
@@ -144,6 +151,7 @@ class _$TabStateCWProxyImpl implements _$TabStateCWProxy {
|
|||||||
Object? historyState = const $CopyWithPlaceholder(),
|
Object? historyState = const $CopyWithPlaceholder(),
|
||||||
Object? readerableState = const $CopyWithPlaceholder(),
|
Object? readerableState = const $CopyWithPlaceholder(),
|
||||||
Object? findResultState = const $CopyWithPlaceholder(),
|
Object? findResultState = const $CopyWithPlaceholder(),
|
||||||
|
Object? translationState = const $CopyWithPlaceholder(),
|
||||||
}) {
|
}) {
|
||||||
return TabState(
|
return TabState(
|
||||||
id: _value.id,
|
id: _value.id,
|
||||||
@@ -217,6 +225,12 @@ class _$TabStateCWProxyImpl implements _$TabStateCWProxy {
|
|||||||
? _value.findResultState
|
? _value.findResultState
|
||||||
// ignore: cast_nullable_to_non_nullable
|
// ignore: cast_nullable_to_non_nullable
|
||||||
: findResultState as FindResultState,
|
: findResultState as FindResultState,
|
||||||
|
translationState:
|
||||||
|
translationState == const $CopyWithPlaceholder() ||
|
||||||
|
translationState == null
|
||||||
|
? _value.translationState
|
||||||
|
// ignore: cast_nullable_to_non_nullable
|
||||||
|
: translationState as TranslationState,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,85 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2024-2026 Fabian Freund.
|
||||||
|
*
|
||||||
|
* This file is part of WebLibre
|
||||||
|
* (see https://weblibre.eu).
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU Affero General Public License as
|
||||||
|
* published by the Free Software Foundation, either version 3 of the
|
||||||
|
* License, or (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU Affero General Public License for more details.
|
||||||
|
*
|
||||||
|
* 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:fast_equatable/fast_equatable.dart';
|
||||||
|
import 'package:flutter_mozilla_components/flutter_mozilla_components.dart';
|
||||||
|
|
||||||
|
class TranslationState with FastEquatable {
|
||||||
|
final bool isTranslated;
|
||||||
|
final bool isTranslateProcessing;
|
||||||
|
final bool isOfferTranslate;
|
||||||
|
final bool isExpectedTranslate;
|
||||||
|
final String? detectedLanguageCode;
|
||||||
|
final String? userPreferredLanguageCode;
|
||||||
|
final String? requestedFromLanguage;
|
||||||
|
final String? requestedToLanguage;
|
||||||
|
final String? translationErrorName;
|
||||||
|
final bool? displayError;
|
||||||
|
|
||||||
|
TranslationState({
|
||||||
|
required this.isTranslated,
|
||||||
|
required this.isTranslateProcessing,
|
||||||
|
required this.isOfferTranslate,
|
||||||
|
required this.isExpectedTranslate,
|
||||||
|
this.detectedLanguageCode,
|
||||||
|
this.userPreferredLanguageCode,
|
||||||
|
this.requestedFromLanguage,
|
||||||
|
this.requestedToLanguage,
|
||||||
|
this.translationErrorName,
|
||||||
|
this.displayError,
|
||||||
|
});
|
||||||
|
|
||||||
|
factory TranslationState.$default() => TranslationState(
|
||||||
|
isTranslated: false,
|
||||||
|
isTranslateProcessing: false,
|
||||||
|
isOfferTranslate: false,
|
||||||
|
isExpectedTranslate: false,
|
||||||
|
);
|
||||||
|
|
||||||
|
factory TranslationState.fromData(TabTranslationStateData data) =>
|
||||||
|
TranslationState(
|
||||||
|
isTranslated: data.isTranslated,
|
||||||
|
isTranslateProcessing: data.isTranslateProcessing,
|
||||||
|
isOfferTranslate: data.isOfferTranslate,
|
||||||
|
isExpectedTranslate: data.isExpectedTranslate,
|
||||||
|
detectedLanguageCode: data.detectedLanguageCode,
|
||||||
|
userPreferredLanguageCode: data.userPreferredLanguageCode,
|
||||||
|
requestedFromLanguage: data.requestedFromLanguage,
|
||||||
|
requestedToLanguage: data.requestedToLanguage,
|
||||||
|
translationErrorName: data.translationErrorName,
|
||||||
|
displayError: data.displayError,
|
||||||
|
);
|
||||||
|
|
||||||
|
bool get hasError =>
|
||||||
|
translationErrorName != null && (displayError ?? true);
|
||||||
|
|
||||||
|
@override
|
||||||
|
List<Object?> get hashParameters => [
|
||||||
|
isTranslated,
|
||||||
|
isTranslateProcessing,
|
||||||
|
isOfferTranslate,
|
||||||
|
isExpectedTranslate,
|
||||||
|
detectedLanguageCode,
|
||||||
|
userPreferredLanguageCode,
|
||||||
|
requestedFromLanguage,
|
||||||
|
requestedToLanguage,
|
||||||
|
translationErrorName,
|
||||||
|
displayError,
|
||||||
|
];
|
||||||
|
}
|
||||||
@@ -239,6 +239,27 @@ Stream<MlProgressData> mlProgressEvents(Ref ref) {
|
|||||||
return service.mlProgressEvents;
|
return service.mlProgressEvents;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Translation engine state (browser-level: supported languages, engine availability)
|
||||||
|
@Riverpod(keepAlive: true)
|
||||||
|
class TranslationEngineState extends _$TranslationEngineState {
|
||||||
|
@override
|
||||||
|
TranslationEngineStateData? build() {
|
||||||
|
final eventService = ref.watch(eventServiceProvider);
|
||||||
|
|
||||||
|
final sub = eventService.translationEngineEvents.listen((value) {
|
||||||
|
if (ref.mounted) {
|
||||||
|
state = value;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
ref.onDispose(() async {
|
||||||
|
await sub.cancel();
|
||||||
|
});
|
||||||
|
|
||||||
|
return eventService.translationEngineEvents.valueOrNull;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Tracks active ML model downloads
|
/// Tracks active ML model downloads
|
||||||
@Riverpod()
|
@Riverpod()
|
||||||
class MlDownloadState extends _$MlDownloadState {
|
class MlDownloadState extends _$MlDownloadState {
|
||||||
|
|||||||
@@ -389,6 +389,72 @@ final class MlProgressEventsProvider
|
|||||||
|
|
||||||
String _$mlProgressEventsHash() => r'41c1e6aece7f9ee2bebe5d189c6bec753c2fefc8';
|
String _$mlProgressEventsHash() => r'41c1e6aece7f9ee2bebe5d189c6bec753c2fefc8';
|
||||||
|
|
||||||
|
/// Translation engine state (browser-level: supported languages, engine availability)
|
||||||
|
|
||||||
|
@ProviderFor(TranslationEngineState)
|
||||||
|
final translationEngineStateProvider = TranslationEngineStateProvider._();
|
||||||
|
|
||||||
|
/// Translation engine state (browser-level: supported languages, engine availability)
|
||||||
|
final class TranslationEngineStateProvider
|
||||||
|
extends
|
||||||
|
$NotifierProvider<TranslationEngineState, TranslationEngineStateData?> {
|
||||||
|
/// Translation engine state (browser-level: supported languages, engine availability)
|
||||||
|
TranslationEngineStateProvider._()
|
||||||
|
: super(
|
||||||
|
from: null,
|
||||||
|
argument: null,
|
||||||
|
retry: null,
|
||||||
|
name: r'translationEngineStateProvider',
|
||||||
|
isAutoDispose: false,
|
||||||
|
dependencies: null,
|
||||||
|
$allTransitiveDependencies: null,
|
||||||
|
);
|
||||||
|
|
||||||
|
@override
|
||||||
|
String debugGetCreateSourceHash() => _$translationEngineStateHash();
|
||||||
|
|
||||||
|
@$internal
|
||||||
|
@override
|
||||||
|
TranslationEngineState create() => TranslationEngineState();
|
||||||
|
|
||||||
|
/// {@macro riverpod.override_with_value}
|
||||||
|
Override overrideWithValue(TranslationEngineStateData? value) {
|
||||||
|
return $ProviderOverride(
|
||||||
|
origin: this,
|
||||||
|
providerOverride: $SyncValueProvider<TranslationEngineStateData?>(value),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
String _$translationEngineStateHash() =>
|
||||||
|
r'04592e18715b0e237e9bc027287a880a4bb023da';
|
||||||
|
|
||||||
|
/// Translation engine state (browser-level: supported languages, engine availability)
|
||||||
|
|
||||||
|
abstract class _$TranslationEngineState
|
||||||
|
extends $Notifier<TranslationEngineStateData?> {
|
||||||
|
TranslationEngineStateData? build();
|
||||||
|
@$mustCallSuper
|
||||||
|
@override
|
||||||
|
void runBuild() {
|
||||||
|
final ref =
|
||||||
|
this.ref
|
||||||
|
as $Ref<TranslationEngineStateData?, TranslationEngineStateData?>;
|
||||||
|
final element =
|
||||||
|
ref.element
|
||||||
|
as $ClassProviderElement<
|
||||||
|
AnyNotifier<
|
||||||
|
TranslationEngineStateData?,
|
||||||
|
TranslationEngineStateData?
|
||||||
|
>,
|
||||||
|
TranslationEngineStateData?,
|
||||||
|
Object?,
|
||||||
|
Object?
|
||||||
|
>;
|
||||||
|
element.handleCreate(ref, build);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Tracks active ML model downloads
|
/// Tracks active ML model downloads
|
||||||
|
|
||||||
@ProviderFor(MlDownloadState)
|
@ProviderFor(MlDownloadState)
|
||||||
|
|||||||
@@ -76,6 +76,26 @@ class TabSession extends _$TabSession {
|
|||||||
return _sessionService.saveToPdf();
|
return _sessionService.saveToPdf();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<void> printContent() {
|
||||||
|
return _sessionService.printContent();
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> translate({
|
||||||
|
required String fromLanguage,
|
||||||
|
required String toLanguage,
|
||||||
|
bool? downloadModel,
|
||||||
|
}) {
|
||||||
|
return _sessionService.translate(
|
||||||
|
fromLanguage: fromLanguage,
|
||||||
|
toLanguage: toLanguage,
|
||||||
|
options: TranslationOptions(downloadModel: downloadModel ?? true),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> translateRestore() {
|
||||||
|
return _sessionService.translateRestore();
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void build({required String? tabId}) {
|
void build({required String? tabId}) {
|
||||||
_sessionService = (tabId != null)
|
_sessionService = (tabId != null)
|
||||||
|
|||||||
@@ -57,7 +57,7 @@ final class TabSessionProvider extends $NotifierProvider<TabSession, void> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
String _$tabSessionHash() => r'5be471574bb868889cc1eee37ae7e4570a9e21a4';
|
String _$tabSessionHash() => r'b9381c334672fd3f733a4cc86ce696fb7de6fa5d';
|
||||||
|
|
||||||
final class TabSessionFamily extends $Family
|
final class TabSessionFamily extends $Family
|
||||||
with $ClassFamilyOverride<TabSession, void, void, void, String?> {
|
with $ClassFamilyOverride<TabSession, void, void, void, String?> {
|
||||||
|
|||||||
@@ -31,6 +31,7 @@ import 'package:weblibre/features/geckoview/domain/entities/states/history.dart'
|
|||||||
import 'package:weblibre/features/geckoview/domain/entities/states/readerable.dart';
|
import 'package:weblibre/features/geckoview/domain/entities/states/readerable.dart';
|
||||||
import 'package:weblibre/features/geckoview/domain/entities/states/security.dart';
|
import 'package:weblibre/features/geckoview/domain/entities/states/security.dart';
|
||||||
import 'package:weblibre/features/geckoview/domain/entities/states/tab.dart';
|
import 'package:weblibre/features/geckoview/domain/entities/states/tab.dart';
|
||||||
|
import 'package:weblibre/features/geckoview/domain/entities/states/translation.dart';
|
||||||
import 'package:weblibre/features/geckoview/domain/providers.dart';
|
import 'package:weblibre/features/geckoview/domain/providers.dart';
|
||||||
import 'package:weblibre/features/geckoview/domain/providers/selected_tab.dart';
|
import 'package:weblibre/features/geckoview/domain/providers/selected_tab.dart';
|
||||||
import 'package:weblibre/features/geckoview/features/find_in_page/domain/repositories/find_in_page.dart';
|
import 'package:weblibre/features/geckoview/features/find_in_page/domain/repositories/find_in_page.dart';
|
||||||
@@ -203,6 +204,16 @@ class TabStates extends _$TabStates {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void _onTabTranslationStateChange(TabTranslationEvent event) {
|
||||||
|
final TabTranslationEvent(:tabId, :state) = event;
|
||||||
|
|
||||||
|
final current = this.state[tabId] ?? TabState.$default(tabId);
|
||||||
|
this.state = {...this.state}
|
||||||
|
..[tabId] = current.copyWith.translationState(
|
||||||
|
TranslationState.fromData(state),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
void _onFindResultsChange(FindResultsEvent event) {
|
void _onFindResultsChange(FindResultsEvent event) {
|
||||||
final FindResultsEvent(:tabId, :results) = event;
|
final FindResultsEvent(:tabId, :results) = event;
|
||||||
final current = state[tabId] ?? TabState.$default(tabId);
|
final current = state[tabId] ?? TabState.$default(tabId);
|
||||||
@@ -317,6 +328,18 @@ class TabStates extends _$TabStates {
|
|||||||
);
|
);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
|
eventService.tabTranslationEvents.listen(
|
||||||
|
(event) {
|
||||||
|
_onTabTranslationStateChange(event);
|
||||||
|
},
|
||||||
|
onError: (Object error, StackTrace stackTrace) {
|
||||||
|
logger.e(
|
||||||
|
'Error in tab translation events',
|
||||||
|
error: error,
|
||||||
|
stackTrace: stackTrace,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
];
|
];
|
||||||
|
|
||||||
ref.listen(
|
ref.listen(
|
||||||
@@ -331,6 +354,7 @@ class TabStates extends _$TabStates {
|
|||||||
onSecurityInfoStateChange: true,
|
onSecurityInfoStateChange: true,
|
||||||
onHistoryStateChange: true,
|
onHistoryStateChange: true,
|
||||||
onFindResults: true,
|
onFindResults: true,
|
||||||
|
onTranslationStateChange: true,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -41,7 +41,7 @@ final class TabStatesProvider
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
String _$tabStatesHash() => r'0fd97193ff963006e1943436b25b025dd3885c65';
|
String _$tabStatesHash() => r'6fc9a91b1343f9ae0b442100bed20e42448afa37';
|
||||||
|
|
||||||
abstract class _$TabStates extends $Notifier<Map<String, TabState>> {
|
abstract class _$TabStates extends $Notifier<Map<String, TabState>> {
|
||||||
Map<String, TabState> build();
|
Map<String, TabState> build();
|
||||||
|
|||||||
+64
@@ -49,6 +49,7 @@ import 'package:weblibre/features/geckoview/domain/repositories/tab.dart';
|
|||||||
import 'package:weblibre/features/geckoview/features/browser/presentation/dialogs/content_selection_dialog.dart';
|
import 'package:weblibre/features/geckoview/features/browser/presentation/dialogs/content_selection_dialog.dart';
|
||||||
import 'package:weblibre/features/geckoview/features/browser/presentation/dialogs/qr_code.dart';
|
import 'package:weblibre/features/geckoview/features/browser/presentation/dialogs/qr_code.dart';
|
||||||
import 'package:weblibre/features/geckoview/features/browser/presentation/widgets/extension_badge_icon.dart';
|
import 'package:weblibre/features/geckoview/features/browser/presentation/widgets/extension_badge_icon.dart';
|
||||||
|
import 'package:weblibre/features/geckoview/features/browser/presentation/widgets/translation_bottom_sheet.dart';
|
||||||
import 'package:weblibre/features/geckoview/features/find_in_page/presentation/controllers/find_in_page.dart';
|
import 'package:weblibre/features/geckoview/features/find_in_page/presentation/controllers/find_in_page.dart';
|
||||||
import 'package:weblibre/features/geckoview/features/open_link_tools/domain/services/url_cleaner_catalog_service.dart';
|
import 'package:weblibre/features/geckoview/features/open_link_tools/domain/services/url_cleaner_catalog_service.dart';
|
||||||
import 'package:weblibre/features/geckoview/features/open_link_tools/presentation/hooks/url_cleaner_controller.dart';
|
import 'package:weblibre/features/geckoview/features/open_link_tools/presentation/hooks/url_cleaner_controller.dart';
|
||||||
@@ -395,6 +396,9 @@ class _PageActionsCard extends HookConsumerWidget {
|
|||||||
},
|
},
|
||||||
),
|
),
|
||||||
|
|
||||||
|
// Translate Page
|
||||||
|
_TranslatePageTile(selectedTabId: selectedTabId),
|
||||||
|
|
||||||
// Add to Home Screen (conditional)
|
// Add to Home Screen (conditional)
|
||||||
_AddToHomeScreenTile(selectedTabId: selectedTabId),
|
_AddToHomeScreenTile(selectedTabId: selectedTabId),
|
||||||
],
|
],
|
||||||
@@ -571,6 +575,48 @@ class _AddToHomeScreenTile extends ConsumerWidget {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class _TranslatePageTile extends ConsumerWidget {
|
||||||
|
final String selectedTabId;
|
||||||
|
|
||||||
|
const _TranslatePageTile({required this.selectedTabId});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context, WidgetRef ref) {
|
||||||
|
final engineState = ref.watch(translationEngineStateProvider);
|
||||||
|
final translationState = ref.watch(
|
||||||
|
tabStateProvider(selectedTabId).select((s) => s?.translationState),
|
||||||
|
);
|
||||||
|
final readerActive = ref.watch(
|
||||||
|
tabStateProvider(selectedTabId)
|
||||||
|
.select((s) => s?.readerableState.active ?? false),
|
||||||
|
);
|
||||||
|
|
||||||
|
// Hide when reader mode is active (Fenix-aligned)
|
||||||
|
if (readerActive || engineState?.isEngineSupported != true) {
|
||||||
|
return const SizedBox.shrink();
|
||||||
|
}
|
||||||
|
|
||||||
|
final isTranslated = translationState?.isTranslated ?? false;
|
||||||
|
|
||||||
|
return ListTile(
|
||||||
|
leading: Icon(
|
||||||
|
Icons.translate,
|
||||||
|
color: isTranslated ? Theme.of(context).colorScheme.primary : null,
|
||||||
|
),
|
||||||
|
title: Text(isTranslated ? 'Translated' : 'Translate Page'),
|
||||||
|
onTap: () async {
|
||||||
|
Navigator.pop(context);
|
||||||
|
if (context.mounted) {
|
||||||
|
await showTranslationBottomSheet(
|
||||||
|
context,
|
||||||
|
selectedTabId: selectedTabId,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
class _FetchFeedsTile extends HookConsumerWidget {
|
class _FetchFeedsTile extends HookConsumerWidget {
|
||||||
final String selectedTabId;
|
final String selectedTabId;
|
||||||
|
|
||||||
@@ -1385,6 +1431,24 @@ class _ExportExpansion extends ConsumerWidget {
|
|||||||
if (context.mounted) Navigator.pop(context);
|
if (context.mounted) Navigator.pop(context);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
|
|
||||||
|
// Print
|
||||||
|
_buildSubTile(
|
||||||
|
'Print',
|
||||||
|
icon: MdiIcons.printer,
|
||||||
|
onTap: () async {
|
||||||
|
try {
|
||||||
|
await ref
|
||||||
|
.read(tabSessionProvider(tabId: selectedTabId).notifier)
|
||||||
|
.printContent();
|
||||||
|
} catch (e) {
|
||||||
|
if (context.mounted) {
|
||||||
|
ui_helper.showErrorMessage(context, 'Failed to print page');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (context.mounted) Navigator.pop(context);
|
||||||
|
},
|
||||||
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|||||||
+30
@@ -109,6 +109,36 @@ class SaveToPdfMenuItemButton extends HookConsumerWidget {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class PrintMenuItemButton extends HookConsumerWidget {
|
||||||
|
const PrintMenuItemButton({super.key, required this.selectedTabId});
|
||||||
|
|
||||||
|
final String? selectedTabId;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context, WidgetRef ref) {
|
||||||
|
return MenuItemButton(
|
||||||
|
leadingIcon: const Icon(MdiIcons.printer),
|
||||||
|
closeOnActivate: false,
|
||||||
|
onPressed: () async {
|
||||||
|
try {
|
||||||
|
await ref
|
||||||
|
.read(tabSessionProvider(tabId: selectedTabId).notifier)
|
||||||
|
.printContent();
|
||||||
|
} catch (e) {
|
||||||
|
if (context.mounted) {
|
||||||
|
ui_helper.showErrorMessage(context, 'Failed to print page');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (context.mounted) {
|
||||||
|
MenuController.maybeOf(context)?.close();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
child: const Text('Print'),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
class ShareMarkdownActionMenuItemButton extends HookConsumerWidget {
|
class ShareMarkdownActionMenuItemButton extends HookConsumerWidget {
|
||||||
const ShareMarkdownActionMenuItemButton({
|
const ShareMarkdownActionMenuItemButton({
|
||||||
super.key,
|
super.key,
|
||||||
|
|||||||
@@ -31,12 +31,14 @@ import 'package:weblibre/core/design/app_colors.dart';
|
|||||||
import 'package:weblibre/core/routing/routes.dart';
|
import 'package:weblibre/core/routing/routes.dart';
|
||||||
import 'package:weblibre/features/geckoview/domain/controllers/bottom_sheet.dart';
|
import 'package:weblibre/features/geckoview/domain/controllers/bottom_sheet.dart';
|
||||||
import 'package:weblibre/features/geckoview/domain/entities/tab_container_selection.dart';
|
import 'package:weblibre/features/geckoview/domain/entities/tab_container_selection.dart';
|
||||||
|
import 'package:weblibre/features/geckoview/domain/providers.dart';
|
||||||
import 'package:weblibre/features/geckoview/domain/providers/desktop_mode.dart';
|
import 'package:weblibre/features/geckoview/domain/providers/desktop_mode.dart';
|
||||||
import 'package:weblibre/features/geckoview/domain/providers/tab_session.dart';
|
import 'package:weblibre/features/geckoview/domain/providers/tab_session.dart';
|
||||||
import 'package:weblibre/features/geckoview/domain/providers/tab_state.dart';
|
import 'package:weblibre/features/geckoview/domain/providers/tab_state.dart';
|
||||||
import 'package:weblibre/features/geckoview/domain/repositories/tab.dart';
|
import 'package:weblibre/features/geckoview/domain/repositories/tab.dart';
|
||||||
import 'package:weblibre/features/geckoview/features/browser/presentation/widgets/menu_item_buttons.dart';
|
import 'package:weblibre/features/geckoview/features/browser/presentation/widgets/menu_item_buttons.dart';
|
||||||
import 'package:weblibre/features/geckoview/features/browser/presentation/widgets/navigation_buttons.dart';
|
import 'package:weblibre/features/geckoview/features/browser/presentation/widgets/navigation_buttons.dart';
|
||||||
|
import 'package:weblibre/features/geckoview/features/browser/presentation/widgets/translation_bottom_sheet.dart';
|
||||||
import 'package:weblibre/features/geckoview/features/find_in_page/presentation/controllers/find_in_page.dart';
|
import 'package:weblibre/features/geckoview/features/find_in_page/presentation/controllers/find_in_page.dart';
|
||||||
import 'package:weblibre/features/geckoview/features/pwa/domain/providers.dart';
|
import 'package:weblibre/features/geckoview/features/pwa/domain/providers.dart';
|
||||||
import 'package:weblibre/features/geckoview/features/pwa/presentation/widgets/pwa_install_button.dart';
|
import 'package:weblibre/features/geckoview/features/pwa/presentation/widgets/pwa_install_button.dart';
|
||||||
@@ -577,10 +579,53 @@ class TabMenu extends HookConsumerWidget {
|
|||||||
},
|
},
|
||||||
),
|
),
|
||||||
SaveToPdfMenuItemButton(selectedTabId: selectedTabId),
|
SaveToPdfMenuItemButton(selectedTabId: selectedTabId),
|
||||||
|
PrintMenuItemButton(selectedTabId: selectedTabId),
|
||||||
],
|
],
|
||||||
leadingIcon: const Icon(MdiIcons.fileExport),
|
leadingIcon: const Icon(MdiIcons.fileExport),
|
||||||
child: const Text('Export'),
|
child: const Text('Export'),
|
||||||
),
|
),
|
||||||
|
Consumer(
|
||||||
|
builder: (context, ref, child) {
|
||||||
|
final engineState = ref.watch(translationEngineStateProvider);
|
||||||
|
final translationState = ref.watch(
|
||||||
|
tabStateProvider(selectedTabId)
|
||||||
|
.select((s) => s?.translationState),
|
||||||
|
);
|
||||||
|
final readerActive = ref.watch(
|
||||||
|
tabStateProvider(selectedTabId)
|
||||||
|
.select((s) => s?.readerableState.active ?? false),
|
||||||
|
);
|
||||||
|
|
||||||
|
// Hide when reader mode is active (Fenix-aligned)
|
||||||
|
if (readerActive || engineState?.isEngineSupported != true) {
|
||||||
|
return const SizedBox.shrink();
|
||||||
|
}
|
||||||
|
|
||||||
|
final isTranslated = translationState?.isTranslated ?? false;
|
||||||
|
|
||||||
|
return MenuItemButton(
|
||||||
|
closeOnActivate: false,
|
||||||
|
leadingIcon: Icon(
|
||||||
|
Icons.translate,
|
||||||
|
color: isTranslated
|
||||||
|
? Theme.of(context).colorScheme.primary
|
||||||
|
: null,
|
||||||
|
),
|
||||||
|
onPressed: () async {
|
||||||
|
controller.close();
|
||||||
|
if (context.mounted) {
|
||||||
|
await showTranslationBottomSheet(
|
||||||
|
context,
|
||||||
|
selectedTabId: selectedTabId,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
child: Text(
|
||||||
|
isTranslated ? 'Translated' : 'Translate Page',
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
if (enablePinTab)
|
if (enablePinTab)
|
||||||
Consumer(
|
Consumer(
|
||||||
builder: (context, childRef, child) {
|
builder: (context, childRef, child) {
|
||||||
|
|||||||
+309
@@ -0,0 +1,309 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2024-2026 Fabian Freund.
|
||||||
|
*
|
||||||
|
* This file is part of WebLibre
|
||||||
|
* (see https://weblibre.eu).
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU Affero General Public License as
|
||||||
|
* published by the Free Software Foundation, either version 3 of the
|
||||||
|
* License, or (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU Affero General Public License for more details.
|
||||||
|
*
|
||||||
|
* 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 'dart:async';
|
||||||
|
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_hooks/flutter_hooks.dart';
|
||||||
|
import 'package:flutter_mozilla_components/flutter_mozilla_components.dart';
|
||||||
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
|
import 'package:weblibre/core/logger.dart';
|
||||||
|
import 'package:weblibre/features/geckoview/domain/providers.dart';
|
||||||
|
import 'package:weblibre/features/geckoview/domain/providers/tab_session.dart';
|
||||||
|
import 'package:weblibre/features/geckoview/domain/providers/tab_state.dart';
|
||||||
|
import 'package:weblibre/utils/ui_helper.dart' as ui_helper;
|
||||||
|
|
||||||
|
enum _TranslatePhase { idle, submitting, processing }
|
||||||
|
|
||||||
|
Future<void> showTranslationBottomSheet(
|
||||||
|
BuildContext context, {
|
||||||
|
required String selectedTabId,
|
||||||
|
}) {
|
||||||
|
unawaited(
|
||||||
|
GeckoTabService().syncEvents(onTranslationStateChange: true).catchError((
|
||||||
|
Object e,
|
||||||
|
StackTrace s,
|
||||||
|
) {
|
||||||
|
logger.w(
|
||||||
|
'Failed to refresh translation state before opening sheet',
|
||||||
|
error: e,
|
||||||
|
stackTrace: s,
|
||||||
|
);
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
return showModalBottomSheet(
|
||||||
|
context: context,
|
||||||
|
shape: const RoundedRectangleBorder(
|
||||||
|
borderRadius: BorderRadius.vertical(top: Radius.circular(16)),
|
||||||
|
),
|
||||||
|
builder: (context) => TranslationBottomSheet(selectedTabId: selectedTabId),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
class TranslationBottomSheet extends HookConsumerWidget {
|
||||||
|
final String selectedTabId;
|
||||||
|
|
||||||
|
const TranslationBottomSheet({super.key, required this.selectedTabId});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context, WidgetRef ref) {
|
||||||
|
final colorScheme = Theme.of(context).colorScheme;
|
||||||
|
|
||||||
|
final tabState = ref.watch(tabStateProvider(selectedTabId));
|
||||||
|
final translationState = tabState?.translationState;
|
||||||
|
final engineState = ref.watch(translationEngineStateProvider);
|
||||||
|
|
||||||
|
final fromLanguages = engineState?.fromLanguages?.nonNulls.toList() ?? [];
|
||||||
|
final toLanguages = engineState?.toLanguages?.nonNulls.toList() ?? [];
|
||||||
|
final suggestedFrom =
|
||||||
|
translationState?.detectedLanguageCode ??
|
||||||
|
translationState?.requestedFromLanguage;
|
||||||
|
final suggestedTo =
|
||||||
|
translationState?.userPreferredLanguageCode ??
|
||||||
|
translationState?.requestedToLanguage;
|
||||||
|
|
||||||
|
final selectedFrom = useState<String?>(suggestedFrom);
|
||||||
|
final selectedTo = useState<String?>(suggestedTo);
|
||||||
|
|
||||||
|
useEffect(() {
|
||||||
|
if (selectedFrom.value == null && suggestedFrom != null) {
|
||||||
|
selectedFrom.value = suggestedFrom;
|
||||||
|
}
|
||||||
|
if (selectedTo.value == null && suggestedTo != null) {
|
||||||
|
selectedTo.value = suggestedTo;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}, [suggestedFrom, suggestedTo]);
|
||||||
|
|
||||||
|
final isTranslated = translationState?.isTranslated ?? false;
|
||||||
|
final isProcessing = translationState?.isTranslateProcessing ?? false;
|
||||||
|
final hasError = translationState?.hasError ?? false;
|
||||||
|
final errorName = translationState?.translationErrorName;
|
||||||
|
final translatePhase = useState(_TranslatePhase.idle);
|
||||||
|
|
||||||
|
final effectiveProcessing =
|
||||||
|
isProcessing || translatePhase.value != _TranslatePhase.idle;
|
||||||
|
|
||||||
|
useEffect(() {
|
||||||
|
switch (translatePhase.value) {
|
||||||
|
case _TranslatePhase.idle:
|
||||||
|
return null;
|
||||||
|
case _TranslatePhase.submitting:
|
||||||
|
if (isProcessing) {
|
||||||
|
translatePhase.value = _TranslatePhase.processing;
|
||||||
|
} else if (hasError) {
|
||||||
|
translatePhase.value = _TranslatePhase.idle;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
case _TranslatePhase.processing:
|
||||||
|
if (!isProcessing && (isTranslated || hasError)) {
|
||||||
|
translatePhase.value = _TranslatePhase.idle;
|
||||||
|
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||||
|
if (context.mounted) Navigator.pop(context);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
// Otherwise stay in processing — transient stop during retranslate.
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}, [isProcessing, isTranslated, hasError, translatePhase.value]);
|
||||||
|
|
||||||
|
final canTranslate =
|
||||||
|
selectedFrom.value != null &&
|
||||||
|
selectedTo.value != null &&
|
||||||
|
selectedFrom.value != selectedTo.value &&
|
||||||
|
!effectiveProcessing;
|
||||||
|
|
||||||
|
return SafeArea(
|
||||||
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.all(16),
|
||||||
|
child: Column(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||||
|
children: [
|
||||||
|
// Header
|
||||||
|
Row(
|
||||||
|
children: [
|
||||||
|
Icon(Icons.translate, color: colorScheme.primary),
|
||||||
|
const SizedBox(width: 8),
|
||||||
|
Text(
|
||||||
|
'Translate Page',
|
||||||
|
style: Theme.of(context).textTheme.titleMedium,
|
||||||
|
),
|
||||||
|
const Spacer(),
|
||||||
|
if (effectiveProcessing)
|
||||||
|
const SizedBox(
|
||||||
|
width: 20,
|
||||||
|
height: 20,
|
||||||
|
child: CircularProgressIndicator(strokeWidth: 2),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
const SizedBox(height: 16),
|
||||||
|
|
||||||
|
// From language dropdown
|
||||||
|
_LanguageDropdown(
|
||||||
|
label: 'From',
|
||||||
|
languages: fromLanguages,
|
||||||
|
selectedCode: selectedFrom.value,
|
||||||
|
onChanged: effectiveProcessing
|
||||||
|
? null
|
||||||
|
: (code) => selectedFrom.value = code,
|
||||||
|
),
|
||||||
|
const SizedBox(height: 12),
|
||||||
|
|
||||||
|
// To language dropdown
|
||||||
|
_LanguageDropdown(
|
||||||
|
label: 'To',
|
||||||
|
languages: toLanguages,
|
||||||
|
selectedCode: selectedTo.value,
|
||||||
|
onChanged: effectiveProcessing
|
||||||
|
? null
|
||||||
|
: (code) => selectedTo.value = code,
|
||||||
|
),
|
||||||
|
const SizedBox(height: 16),
|
||||||
|
|
||||||
|
// Error display
|
||||||
|
if (hasError && errorName != null)
|
||||||
|
Padding(
|
||||||
|
padding: const EdgeInsets.only(bottom: 12),
|
||||||
|
child: Container(
|
||||||
|
padding: const EdgeInsets.all(12),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: colorScheme.errorContainer,
|
||||||
|
borderRadius: BorderRadius.circular(8),
|
||||||
|
),
|
||||||
|
child: Text(
|
||||||
|
'Translation error: $errorName',
|
||||||
|
style: TextStyle(color: colorScheme.onErrorContainer),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
|
||||||
|
// Action buttons
|
||||||
|
Row(
|
||||||
|
children: [
|
||||||
|
if (isTranslated)
|
||||||
|
Expanded(
|
||||||
|
child: OutlinedButton(
|
||||||
|
onPressed: effectiveProcessing
|
||||||
|
? null
|
||||||
|
: () async {
|
||||||
|
try {
|
||||||
|
await ref
|
||||||
|
.read(
|
||||||
|
tabSessionProvider(
|
||||||
|
tabId: selectedTabId,
|
||||||
|
).notifier,
|
||||||
|
)
|
||||||
|
.translateRestore();
|
||||||
|
} catch (e) {
|
||||||
|
if (context.mounted) {
|
||||||
|
ui_helper.showErrorMessage(
|
||||||
|
context,
|
||||||
|
'Failed to restore page',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (context.mounted) Navigator.pop(context);
|
||||||
|
},
|
||||||
|
child: const Text('Show Original'),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
if (isTranslated) const SizedBox(width: 12),
|
||||||
|
Expanded(
|
||||||
|
child: FilledButton(
|
||||||
|
onPressed: canTranslate
|
||||||
|
? () async {
|
||||||
|
translatePhase.value = _TranslatePhase.submitting;
|
||||||
|
try {
|
||||||
|
await ref
|
||||||
|
.read(
|
||||||
|
tabSessionProvider(
|
||||||
|
tabId: selectedTabId,
|
||||||
|
).notifier,
|
||||||
|
)
|
||||||
|
.translate(
|
||||||
|
fromLanguage: selectedFrom.value!,
|
||||||
|
toLanguage: selectedTo.value!,
|
||||||
|
);
|
||||||
|
} catch (e) {
|
||||||
|
translatePhase.value = _TranslatePhase.idle;
|
||||||
|
if (context.mounted) {
|
||||||
|
ui_helper.showErrorMessage(
|
||||||
|
context,
|
||||||
|
'Failed to translate page',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
: null,
|
||||||
|
child: Text(isTranslated ? 'Retranslate' : 'Translate'),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class _LanguageDropdown extends StatelessWidget {
|
||||||
|
final String label;
|
||||||
|
final List<TranslationLanguage> languages;
|
||||||
|
final String? selectedCode;
|
||||||
|
final ValueChanged<String?>? onChanged;
|
||||||
|
|
||||||
|
const _LanguageDropdown({
|
||||||
|
required this.label,
|
||||||
|
required this.languages,
|
||||||
|
required this.selectedCode,
|
||||||
|
this.onChanged,
|
||||||
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
// Ensure selectedCode exists in the list, otherwise use null
|
||||||
|
final effectiveSelection = languages.any((l) => l.code == selectedCode)
|
||||||
|
? selectedCode
|
||||||
|
: null;
|
||||||
|
|
||||||
|
return DropdownButtonFormField<String>(
|
||||||
|
decoration: InputDecoration(
|
||||||
|
labelText: label,
|
||||||
|
border: const OutlineInputBorder(),
|
||||||
|
contentPadding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8),
|
||||||
|
),
|
||||||
|
initialValue: effectiveSelection,
|
||||||
|
isExpanded: true,
|
||||||
|
items: languages
|
||||||
|
.map(
|
||||||
|
(lang) => DropdownMenuItem(
|
||||||
|
value: lang.code,
|
||||||
|
child: Text(lang.localizedDisplayName),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.toList(),
|
||||||
|
onChanged: onChanged,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
+2
-2
@@ -227,8 +227,8 @@ abstract class BaseBrowserFragment : Fragment(), UserInteractionHandler, Activit
|
|||||||
|
|
||||||
val engineView = createEngine(components)
|
val engineView = createEngine(components)
|
||||||
fragmentEngineView = engineView // Track for lifecycle management
|
fragmentEngineView = engineView // Track for lifecycle management
|
||||||
val originalContext = ActivityContextWrapper.getOriginalContext(requireActivity())
|
val originalContext =
|
||||||
?.let { ProfileContext(it, components.profileApplicationContext.relativePath) }
|
ActivityContextWrapper.getOriginalContext(requireActivity()) ?: requireActivity()
|
||||||
val engineNativeView = engineView.asView()
|
val engineNativeView = engineView.asView()
|
||||||
engineNativeView.layoutParams = layoutParams
|
engineNativeView.layoutParams = layoutParams
|
||||||
|
|
||||||
|
|||||||
+80
@@ -27,6 +27,9 @@ import eu.weblibre.flutter_mozilla_components.pigeons.ReaderableState
|
|||||||
import eu.weblibre.flutter_mozilla_components.pigeons.RestoreLocation
|
import eu.weblibre.flutter_mozilla_components.pigeons.RestoreLocation
|
||||||
import eu.weblibre.flutter_mozilla_components.pigeons.SecurityInfoState
|
import eu.weblibre.flutter_mozilla_components.pigeons.SecurityInfoState
|
||||||
import eu.weblibre.flutter_mozilla_components.pigeons.TabContentState
|
import eu.weblibre.flutter_mozilla_components.pigeons.TabContentState
|
||||||
|
import eu.weblibre.flutter_mozilla_components.pigeons.TabTranslationStateData
|
||||||
|
import eu.weblibre.flutter_mozilla_components.pigeons.TranslationEngineStateData
|
||||||
|
import eu.weblibre.flutter_mozilla_components.pigeons.TranslationLanguage
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.async
|
import kotlinx.coroutines.async
|
||||||
@@ -39,6 +42,7 @@ import mozilla.components.browser.icons.IconRequest
|
|||||||
import mozilla.components.browser.session.storage.RecoverableBrowserState
|
import mozilla.components.browser.session.storage.RecoverableBrowserState
|
||||||
import mozilla.components.browser.state.action.EngineAction
|
import mozilla.components.browser.state.action.EngineAction
|
||||||
import mozilla.components.browser.state.action.TabListAction
|
import mozilla.components.browser.state.action.TabListAction
|
||||||
|
import mozilla.components.browser.state.action.TranslationsAction
|
||||||
import mozilla.components.browser.state.selector.findTab
|
import mozilla.components.browser.state.selector.findTab
|
||||||
import mozilla.components.browser.state.state.BrowserState
|
import mozilla.components.browser.state.state.BrowserState
|
||||||
import mozilla.components.browser.state.state.LastMediaAccessState
|
import mozilla.components.browser.state.state.LastMediaAccessState
|
||||||
@@ -55,6 +59,7 @@ import org.json.JSONObject
|
|||||||
import org.mozilla.gecko.util.ThreadUtils.runOnUiThread
|
import org.mozilla.gecko.util.ThreadUtils.runOnUiThread
|
||||||
import mozilla.components.feature.addons.logger
|
import mozilla.components.feature.addons.logger
|
||||||
import mozilla.components.concept.engine.EngineSession
|
import mozilla.components.concept.engine.EngineSession
|
||||||
|
import mozilla.components.concept.engine.translate.TranslationOperation
|
||||||
|
|
||||||
class GeckoTabsApiImpl : GeckoTabsApi {
|
class GeckoTabsApiImpl : GeckoTabsApi {
|
||||||
companion object {
|
companion object {
|
||||||
@@ -185,6 +190,7 @@ class GeckoTabsApiImpl : GeckoTabsApi {
|
|||||||
onPageExtensionsChange: Boolean,
|
onPageExtensionsChange: Boolean,
|
||||||
onBrowserExtensionIcons: Boolean,
|
onBrowserExtensionIcons: Boolean,
|
||||||
onPageExtensionIcons: Boolean,
|
onPageExtensionIcons: Boolean,
|
||||||
|
onTranslationStateChange: Boolean,
|
||||||
) {
|
) {
|
||||||
try {
|
try {
|
||||||
val tabs = components.core.store.state.tabs.map { it.copy() }
|
val tabs = components.core.store.state.tabs.map { it.copy() }
|
||||||
@@ -293,11 +299,85 @@ class GeckoTabsApiImpl : GeckoTabsApi {
|
|||||||
onPageExtensionIcons
|
onPageExtensionIcons
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Sync translation events
|
||||||
|
if (onTranslationStateChange) {
|
||||||
|
syncTranslationEvents(tabs)
|
||||||
|
}
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
logger.error("$TAG: Failed to sync events", e)
|
logger.error("$TAG: Failed to sync events", e)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun syncTranslationEvents(tabs: List<mozilla.components.browser.state.state.TabSessionState>) {
|
||||||
|
try {
|
||||||
|
val browserState = components.core.store.state
|
||||||
|
|
||||||
|
// Retry initialization when support status is still unknown.
|
||||||
|
if (browserState.translationEngine.isEngineSupported == null) {
|
||||||
|
components.core.store.dispatch(TranslationsAction.InitTranslationsBrowserState)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fetch supported languages when engine is supported but languages are still missing.
|
||||||
|
if (
|
||||||
|
browserState.translationEngine.isEngineSupported == true &&
|
||||||
|
(
|
||||||
|
browserState.translationEngine.supportedLanguages?.fromLanguages == null ||
|
||||||
|
browserState.translationEngine.supportedLanguages?.toLanguages == null
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
components.core.store.dispatch(
|
||||||
|
TranslationsAction.OperationRequestedAction(
|
||||||
|
tabId = browserState.selectedTabId,
|
||||||
|
operation = TranslationOperation.FETCH_SUPPORTED_LANGUAGES,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
val translationEngine = browserState.translationEngine
|
||||||
|
|
||||||
|
// Emit browser-level engine state
|
||||||
|
val fromLanguages = translationEngine.supportedLanguages?.fromLanguages?.map { lang ->
|
||||||
|
TranslationLanguage(code = lang.code, localizedDisplayName = lang.localizedDisplayName ?: lang.code)
|
||||||
|
}
|
||||||
|
val toLanguages = translationEngine.supportedLanguages?.toLanguages?.map { lang ->
|
||||||
|
TranslationLanguage(code = lang.code, localizedDisplayName = lang.localizedDisplayName ?: lang.code)
|
||||||
|
}
|
||||||
|
|
||||||
|
components.flutterEvents.onTranslationEngineStateChange(
|
||||||
|
EventSequence.next(),
|
||||||
|
TranslationEngineStateData(
|
||||||
|
isEngineSupported = translationEngine.isEngineSupported,
|
||||||
|
fromLanguages = fromLanguages,
|
||||||
|
toLanguages = toLanguages,
|
||||||
|
)
|
||||||
|
) { }
|
||||||
|
|
||||||
|
// Emit per-tab translation state
|
||||||
|
tabs.forEach { tab ->
|
||||||
|
val translationsState = tab.translationsState
|
||||||
|
components.flutterEvents.onTabTranslationStateChange(
|
||||||
|
EventSequence.next(),
|
||||||
|
TabTranslationStateData(
|
||||||
|
tabId = tab.id,
|
||||||
|
isTranslated = translationsState.isTranslated,
|
||||||
|
isTranslateProcessing = translationsState.isTranslateProcessing,
|
||||||
|
isOfferTranslate = translationsState.isOfferTranslate,
|
||||||
|
isExpectedTranslate = translationsState.isExpectedTranslate,
|
||||||
|
detectedLanguageCode = translationsState.translationEngineState?.detectedLanguages?.documentLangTag,
|
||||||
|
userPreferredLanguageCode = translationsState.translationEngineState?.detectedLanguages?.userPreferredLangTag,
|
||||||
|
requestedFromLanguage = translationsState.translationEngineState?.requestedTranslationPair?.fromLanguage,
|
||||||
|
requestedToLanguage = translationsState.translationEngineState?.requestedTranslationPair?.toLanguage,
|
||||||
|
translationErrorName = translationsState.translationError?.errorName,
|
||||||
|
displayError = translationsState.translationError?.displayError,
|
||||||
|
)
|
||||||
|
) { }
|
||||||
|
}
|
||||||
|
} catch (e: Exception) {
|
||||||
|
logger.error("$TAG: Failed to sync translation events", e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private fun syncExtensionEvents(
|
private fun syncExtensionEvents(
|
||||||
onBrowserExtensionsChange: Boolean,
|
onBrowserExtensionsChange: Boolean,
|
||||||
onPageExtensionsChange: Boolean,
|
onPageExtensionsChange: Boolean,
|
||||||
|
|||||||
+5
@@ -30,6 +30,7 @@ import mozilla.components.browser.icons.BrowserIcons
|
|||||||
import mozilla.components.browser.session.storage.SessionStorage
|
import mozilla.components.browser.session.storage.SessionStorage
|
||||||
import mozilla.components.browser.state.engine.EngineMiddleware
|
import mozilla.components.browser.state.engine.EngineMiddleware
|
||||||
import mozilla.components.browser.state.engine.middleware.SessionPrioritizationMiddleware
|
import mozilla.components.browser.state.engine.middleware.SessionPrioritizationMiddleware
|
||||||
|
import mozilla.components.browser.state.engine.middleware.TranslationsMiddleware
|
||||||
import mozilla.components.browser.state.store.BrowserStore
|
import mozilla.components.browser.state.store.BrowserStore
|
||||||
import mozilla.components.browser.storage.sync.PlacesBookmarksStorage
|
import mozilla.components.browser.storage.sync.PlacesBookmarksStorage
|
||||||
import mozilla.components.browser.storage.sync.PlacesHistoryStorage
|
import mozilla.components.browser.storage.sync.PlacesHistoryStorage
|
||||||
@@ -67,6 +68,7 @@ import mozilla.components.concept.base.crash.Breadcrumb
|
|||||||
import mozilla.components.concept.base.crash.CrashReporting
|
import mozilla.components.concept.base.crash.CrashReporting
|
||||||
import mozilla.components.support.base.worker.Frequency
|
import mozilla.components.support.base.worker.Frequency
|
||||||
import org.mozilla.geckoview.GeckoRuntime
|
import org.mozilla.geckoview.GeckoRuntime
|
||||||
|
import kotlinx.coroutines.MainScope
|
||||||
import kotlinx.coroutines.Job
|
import kotlinx.coroutines.Job
|
||||||
import java.util.concurrent.TimeUnit
|
import java.util.concurrent.TimeUnit
|
||||||
|
|
||||||
@@ -207,6 +209,9 @@ class Core(
|
|||||||
SaveToPDFMiddleware(),
|
SaveToPDFMiddleware(),
|
||||||
FileUploadsDirCleanerMiddleware(fileUploadsDirCleaner),
|
FileUploadsDirCleanerMiddleware(fileUploadsDirCleaner),
|
||||||
LastMediaAccessMiddleware(),
|
LastMediaAccessMiddleware(),
|
||||||
|
// Keep parity with Fenix: do not initialize translations on store init.
|
||||||
|
// EngineObserver will dispatch InitTranslationsBrowserState after first completed page load.
|
||||||
|
TranslationsMiddleware(engine, MainScope(), false),
|
||||||
) + EngineMiddleware.create(
|
) + EngineMiddleware.create(
|
||||||
engine,
|
engine,
|
||||||
// We are disabling automatic suspending of engine sessions under memory pressure.
|
// We are disabling automatic suspending of engine sessions under memory pressure.
|
||||||
|
|||||||
+68
@@ -23,6 +23,9 @@ import eu.weblibre.flutter_mozilla_components.pigeons.ShareTarget
|
|||||||
import eu.weblibre.flutter_mozilla_components.pigeons.ShareTargetFiles
|
import eu.weblibre.flutter_mozilla_components.pigeons.ShareTargetFiles
|
||||||
import eu.weblibre.flutter_mozilla_components.pigeons.ShareTargetParams
|
import eu.weblibre.flutter_mozilla_components.pigeons.ShareTargetParams
|
||||||
import eu.weblibre.flutter_mozilla_components.pigeons.TabContentState
|
import eu.weblibre.flutter_mozilla_components.pigeons.TabContentState
|
||||||
|
import eu.weblibre.flutter_mozilla_components.pigeons.TabTranslationStateData
|
||||||
|
import eu.weblibre.flutter_mozilla_components.pigeons.TranslationEngineStateData
|
||||||
|
import eu.weblibre.flutter_mozilla_components.pigeons.TranslationLanguage
|
||||||
import kotlinx.coroutines.FlowPreview
|
import kotlinx.coroutines.FlowPreview
|
||||||
import kotlinx.coroutines.flow.Flow
|
import kotlinx.coroutines.flow.Flow
|
||||||
import kotlinx.coroutines.flow.debounce
|
import kotlinx.coroutines.flow.debounce
|
||||||
@@ -222,6 +225,71 @@ class Events(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Translation engine state (browser-level)
|
||||||
|
stateFlow.flowScoped { flow ->
|
||||||
|
flow.map { state -> state.translationEngine }
|
||||||
|
.distinctUntilChanged { old, new ->
|
||||||
|
old.isEngineSupported == new.isEngineSupported &&
|
||||||
|
old.supportedLanguages == new.supportedLanguages
|
||||||
|
}
|
||||||
|
.debounce(50)
|
||||||
|
.collect { translationEngine ->
|
||||||
|
val fromLanguages = translationEngine.supportedLanguages?.fromLanguages?.map { lang ->
|
||||||
|
TranslationLanguage(code = lang.code, localizedDisplayName = lang.localizedDisplayName ?: lang.code)
|
||||||
|
}
|
||||||
|
val toLanguages = translationEngine.supportedLanguages?.toLanguages?.map { lang ->
|
||||||
|
TranslationLanguage(code = lang.code, localizedDisplayName = lang.localizedDisplayName ?: lang.code)
|
||||||
|
}
|
||||||
|
|
||||||
|
flutterEvents.onTranslationEngineStateChange(
|
||||||
|
EventSequence.next(),
|
||||||
|
TranslationEngineStateData(
|
||||||
|
isEngineSupported = translationEngine.isEngineSupported,
|
||||||
|
fromLanguages = fromLanguages,
|
||||||
|
toLanguages = toLanguages,
|
||||||
|
)
|
||||||
|
) { _ -> }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Per-tab translation state
|
||||||
|
stateFlow.flowScoped { flow ->
|
||||||
|
flow.mapNotNull { state -> state.tabs }
|
||||||
|
.filterChanged {
|
||||||
|
it.translationsState
|
||||||
|
}
|
||||||
|
.ifAnyChanged {
|
||||||
|
arrayOf(
|
||||||
|
it.translationsState.isTranslated,
|
||||||
|
it.translationsState.isTranslateProcessing,
|
||||||
|
it.translationsState.isOfferTranslate,
|
||||||
|
it.translationsState.isExpectedTranslate,
|
||||||
|
it.translationsState.translationEngineState,
|
||||||
|
it.translationsState.translationError,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
.debounce(25)
|
||||||
|
.collect { tab ->
|
||||||
|
val ts = tab.translationsState
|
||||||
|
flutterEvents.onTabTranslationStateChange(
|
||||||
|
EventSequence.next(),
|
||||||
|
TabTranslationStateData(
|
||||||
|
tabId = tab.id,
|
||||||
|
isTranslated = ts.isTranslated,
|
||||||
|
isTranslateProcessing = ts.isTranslateProcessing,
|
||||||
|
isOfferTranslate = ts.isOfferTranslate,
|
||||||
|
isExpectedTranslate = ts.isExpectedTranslate,
|
||||||
|
detectedLanguageCode = ts.translationEngineState?.detectedLanguages?.documentLangTag,
|
||||||
|
userPreferredLanguageCode = ts.translationEngineState?.detectedLanguages?.userPreferredLangTag,
|
||||||
|
requestedFromLanguage = ts.translationEngineState?.requestedTranslationPair?.fromLanguage,
|
||||||
|
requestedToLanguage = ts.translationEngineState?.requestedTranslationPair?.toLanguage,
|
||||||
|
translationErrorName = ts.translationError?.errorName,
|
||||||
|
displayError = ts.translationError?.displayError,
|
||||||
|
)
|
||||||
|
) { _ -> }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// PWA manifest availability events - following Fenix MenuPresenter pattern
|
// PWA manifest availability events - following Fenix MenuPresenter pattern
|
||||||
stateFlow.flowScoped { flow ->
|
stateFlow.flowScoped { flow ->
|
||||||
flow.mapNotNull { state -> state.selectedTab }
|
flow.mapNotNull { state -> state.selectedTab }
|
||||||
|
|||||||
+9
@@ -24,6 +24,15 @@ class SaveToPDFMiddleware : Middleware<BrowserState, BrowserAction> {
|
|||||||
is EngineAction.SaveToPdfExceptionAction -> {
|
is EngineAction.SaveToPdfExceptionAction -> {
|
||||||
logger.error("Unable to save PDF", action.throwable)
|
logger.error("Unable to save PDF", action.throwable)
|
||||||
}
|
}
|
||||||
|
is EngineAction.PrintContentAction -> {
|
||||||
|
next(action)
|
||||||
|
}
|
||||||
|
is EngineAction.PrintContentCompletedAction -> {
|
||||||
|
logger.info("Print completed successfully")
|
||||||
|
}
|
||||||
|
is EngineAction.PrintContentExceptionAction -> {
|
||||||
|
logger.error("Unable to print content", action.throwable)
|
||||||
|
}
|
||||||
else -> {
|
else -> {
|
||||||
next(action)
|
next(action)
|
||||||
}
|
}
|
||||||
|
|||||||
+419
-131
File diff suppressed because it is too large
Load Diff
@@ -94,9 +94,15 @@ export 'src/pigeons/gecko.g.dart'
|
|||||||
SyncRemoteTab,
|
SyncRemoteTab,
|
||||||
TabContent,
|
TabContent,
|
||||||
TabContentState,
|
TabContentState,
|
||||||
|
TabTranslationStateData,
|
||||||
TrackingProtectionException,
|
TrackingProtectionException,
|
||||||
TrackingProtectionPolicy,
|
TrackingProtectionPolicy,
|
||||||
TrackingScope,
|
TrackingScope,
|
||||||
|
TranslationDetectedLanguages,
|
||||||
|
TranslationEngineStateData,
|
||||||
|
TranslationLanguage,
|
||||||
|
TranslationOptions,
|
||||||
|
TranslationPair,
|
||||||
UnknownHitResult,
|
UnknownHitResult,
|
||||||
VideoHitResult,
|
VideoHitResult,
|
||||||
VisitInfo,
|
VisitInfo,
|
||||||
|
|||||||
@@ -21,6 +21,8 @@ typedef FindResultsEvent = ({String tabId, List<FindResultState> results});
|
|||||||
typedef LongPressEvent = ({String tabId, HitResult hitResult});
|
typedef LongPressEvent = ({String tabId, HitResult hitResult});
|
||||||
typedef ScrollEvent = ({String tabId, int scrollY});
|
typedef ScrollEvent = ({String tabId, int scrollY});
|
||||||
typedef ManifestUpdateEvent = ({String tabId, PwaManifest? manifest});
|
typedef ManifestUpdateEvent = ({String tabId, PwaManifest? manifest});
|
||||||
|
typedef TabTranslationEvent = ({String tabId, TabTranslationStateData state});
|
||||||
|
typedef TranslationEngineEvent = TranslationEngineStateData;
|
||||||
|
|
||||||
class GeckoEventService extends GeckoStateEvents {
|
class GeckoEventService extends GeckoStateEvents {
|
||||||
// Stream controllers
|
// Stream controllers
|
||||||
@@ -46,6 +48,9 @@ class GeckoEventService extends GeckoStateEvents {
|
|||||||
final _tabAddedSubject = PublishSubject<String>();
|
final _tabAddedSubject = PublishSubject<String>();
|
||||||
final _mlProgressSubject = PublishSubject<MlProgressData>();
|
final _mlProgressSubject = PublishSubject<MlProgressData>();
|
||||||
final _manifestUpdateSubject = PublishSubject<ManifestUpdateEvent>();
|
final _manifestUpdateSubject = PublishSubject<ManifestUpdateEvent>();
|
||||||
|
final _translationEngineSubject =
|
||||||
|
BehaviorSubject<TranslationEngineEvent>();
|
||||||
|
final _tabTranslationSubject = ReplaySubject<TabTranslationEvent>();
|
||||||
|
|
||||||
// Event streams
|
// Event streams
|
||||||
ValueStream<bool> get viewReadyStateEvents => _viewStateSubject.stream;
|
ValueStream<bool> get viewReadyStateEvents => _viewStateSubject.stream;
|
||||||
@@ -72,6 +77,10 @@ class GeckoEventService extends GeckoStateEvents {
|
|||||||
Stream<MlProgressData> get mlProgressEvents => _mlProgressSubject.stream;
|
Stream<MlProgressData> get mlProgressEvents => _mlProgressSubject.stream;
|
||||||
Stream<ManifestUpdateEvent> get manifestUpdateEvents =>
|
Stream<ManifestUpdateEvent> get manifestUpdateEvents =>
|
||||||
_manifestUpdateSubject.stream;
|
_manifestUpdateSubject.stream;
|
||||||
|
ValueStream<TranslationEngineEvent> get translationEngineEvents =>
|
||||||
|
_translationEngineSubject.stream;
|
||||||
|
Stream<TabTranslationEvent> get tabTranslationEvents =>
|
||||||
|
_tabTranslationSubject.stream;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void onViewReadyStateChange(int sequence, bool state) {
|
void onViewReadyStateChange(int sequence, bool state) {
|
||||||
@@ -210,6 +219,25 @@ class GeckoEventService extends GeckoStateEvents {
|
|||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void onTranslationEngineStateChange(
|
||||||
|
int sequence,
|
||||||
|
TranslationEngineStateData state,
|
||||||
|
) {
|
||||||
|
_translationEngineSubject.addWhenMoreRecent(sequence, null, state);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void onTabTranslationStateChange(
|
||||||
|
int sequence,
|
||||||
|
TabTranslationStateData state,
|
||||||
|
) {
|
||||||
|
_tabTranslationSubject.addWhenMoreRecent(sequence, state.tabId, (
|
||||||
|
tabId: state.tabId,
|
||||||
|
state: state,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
GeckoEventService.setUp({
|
GeckoEventService.setUp({
|
||||||
BinaryMessenger? binaryMessenger,
|
BinaryMessenger? binaryMessenger,
|
||||||
String messageChannelSuffix = '',
|
String messageChannelSuffix = '',
|
||||||
@@ -241,5 +269,7 @@ class GeckoEventService extends GeckoStateEvents {
|
|||||||
await _siteAssignementSubject.close();
|
await _siteAssignementSubject.close();
|
||||||
await _mlProgressSubject.close();
|
await _mlProgressSubject.close();
|
||||||
await _manifestUpdateSubject.close();
|
await _manifestUpdateSubject.close();
|
||||||
|
await _translationEngineSubject.close();
|
||||||
|
await _tabTranslationSubject.close();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -29,6 +29,7 @@ class GeckoTabService {
|
|||||||
bool onPageExtensionsChange = false,
|
bool onPageExtensionsChange = false,
|
||||||
bool onBrowserExtensionIcons = false,
|
bool onBrowserExtensionIcons = false,
|
||||||
bool onPageExtensionIcons = false,
|
bool onPageExtensionIcons = false,
|
||||||
|
bool onTranslationStateChange = false,
|
||||||
}) {
|
}) {
|
||||||
return _api.syncEvents(
|
return _api.syncEvents(
|
||||||
onSelectedTabChange: onSelectedTabChange,
|
onSelectedTabChange: onSelectedTabChange,
|
||||||
@@ -44,6 +45,7 @@ class GeckoTabService {
|
|||||||
onPageExtensionsChange: onPageExtensionsChange,
|
onPageExtensionsChange: onPageExtensionsChange,
|
||||||
onBrowserExtensionIcons: onBrowserExtensionIcons,
|
onBrowserExtensionIcons: onBrowserExtensionIcons,
|
||||||
onPageExtensionIcons: onPageExtensionIcons,
|
onPageExtensionIcons: onPageExtensionIcons,
|
||||||
|
onTranslationStateChange: onTranslationStateChange,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -16,6 +16,77 @@ class TranslationOptions {
|
|||||||
TranslationOptions({this.downloadModel = true});
|
TranslationOptions({this.downloadModel = true});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// A language supported by the translation engine.
|
||||||
|
class TranslationLanguage {
|
||||||
|
final String code;
|
||||||
|
final String localizedDisplayName;
|
||||||
|
|
||||||
|
TranslationLanguage({required this.code, required this.localizedDisplayName});
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Detected languages for a page.
|
||||||
|
class TranslationDetectedLanguages {
|
||||||
|
final String? documentLangTag;
|
||||||
|
final bool? supportedDocumentLang;
|
||||||
|
final String? userPreferredLangTag;
|
||||||
|
|
||||||
|
TranslationDetectedLanguages({
|
||||||
|
this.documentLangTag,
|
||||||
|
this.supportedDocumentLang,
|
||||||
|
this.userPreferredLangTag,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A from/to language pair for translation.
|
||||||
|
class TranslationPair {
|
||||||
|
final String fromLanguage;
|
||||||
|
final String toLanguage;
|
||||||
|
|
||||||
|
TranslationPair({required this.fromLanguage, required this.toLanguage});
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Browser-level translation engine state (global).
|
||||||
|
class TranslationEngineStateData {
|
||||||
|
final bool? isEngineSupported;
|
||||||
|
final List<TranslationLanguage?>? fromLanguages;
|
||||||
|
final List<TranslationLanguage?>? toLanguages;
|
||||||
|
|
||||||
|
TranslationEngineStateData({
|
||||||
|
this.isEngineSupported,
|
||||||
|
this.fromLanguages,
|
||||||
|
this.toLanguages,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Per-tab translation state.
|
||||||
|
class TabTranslationStateData {
|
||||||
|
final String tabId;
|
||||||
|
final bool isTranslated;
|
||||||
|
final bool isTranslateProcessing;
|
||||||
|
final bool isOfferTranslate;
|
||||||
|
final bool isExpectedTranslate;
|
||||||
|
final String? detectedLanguageCode;
|
||||||
|
final String? userPreferredLanguageCode;
|
||||||
|
final String? requestedFromLanguage;
|
||||||
|
final String? requestedToLanguage;
|
||||||
|
final String? translationErrorName;
|
||||||
|
final bool? displayError;
|
||||||
|
|
||||||
|
TabTranslationStateData({
|
||||||
|
required this.tabId,
|
||||||
|
required this.isTranslated,
|
||||||
|
required this.isTranslateProcessing,
|
||||||
|
required this.isOfferTranslate,
|
||||||
|
required this.isExpectedTranslate,
|
||||||
|
this.detectedLanguageCode,
|
||||||
|
this.userPreferredLanguageCode,
|
||||||
|
this.requestedFromLanguage,
|
||||||
|
this.requestedToLanguage,
|
||||||
|
this.translationErrorName,
|
||||||
|
this.displayError,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
/// Value type that represents the state of reader mode/view.
|
/// Value type that represents the state of reader mode/view.
|
||||||
class ReaderState {
|
class ReaderState {
|
||||||
/// Whether or not the current page can be transformed to
|
/// Whether or not the current page can be transformed to
|
||||||
@@ -1256,6 +1327,7 @@ abstract class GeckoTabsApi {
|
|||||||
required bool onPageExtensionsChange,
|
required bool onPageExtensionsChange,
|
||||||
required bool onBrowserExtensionIcons,
|
required bool onBrowserExtensionIcons,
|
||||||
required bool onPageExtensionIcons,
|
required bool onPageExtensionIcons,
|
||||||
|
required bool onTranslationStateChange,
|
||||||
});
|
});
|
||||||
|
|
||||||
void selectTab({required String tabId});
|
void selectTab({required String tabId});
|
||||||
@@ -1562,6 +1634,15 @@ abstract class GeckoStateEvents {
|
|||||||
void onMlProgress(int sequence, MlProgressData progress);
|
void onMlProgress(int sequence, MlProgressData progress);
|
||||||
|
|
||||||
void onManifestUpdate(int sequence, String tabId, PwaManifest? manifest);
|
void onManifestUpdate(int sequence, String tabId, PwaManifest? manifest);
|
||||||
|
|
||||||
|
void onTranslationEngineStateChange(
|
||||||
|
int sequence,
|
||||||
|
TranslationEngineStateData state,
|
||||||
|
);
|
||||||
|
void onTabTranslationStateChange(
|
||||||
|
int sequence,
|
||||||
|
TabTranslationStateData state,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@FlutterApi()
|
@FlutterApi()
|
||||||
|
|||||||
@@ -15,11 +15,7 @@ PlatformException _createConnectionError(String channelName) {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
List<Object?> wrapResponse({
|
List<Object?> wrapResponse({Object? result, PlatformException? error, bool empty = false}) {
|
||||||
Object? result,
|
|
||||||
PlatformException? error,
|
|
||||||
bool empty = false,
|
|
||||||
}) {
|
|
||||||
if (empty) {
|
if (empty) {
|
||||||
return <Object?>[];
|
return <Object?>[];
|
||||||
}
|
}
|
||||||
@@ -28,48 +24,37 @@ List<Object?> wrapResponse({
|
|||||||
}
|
}
|
||||||
return <Object?>[error.code, error.message, error.details];
|
return <Object?>[error.code, error.message, error.details];
|
||||||
}
|
}
|
||||||
|
|
||||||
bool _deepEquals(Object? a, Object? b) {
|
bool _deepEquals(Object? a, Object? b) {
|
||||||
if (a is List && b is List) {
|
if (a is List && b is List) {
|
||||||
return a.length == b.length &&
|
return a.length == b.length &&
|
||||||
a.indexed.every(
|
a.indexed
|
||||||
((int, dynamic) item) => _deepEquals(item.$2, b[item.$1]),
|
.every(((int, dynamic) item) => _deepEquals(item.$2, b[item.$1]));
|
||||||
);
|
|
||||||
}
|
}
|
||||||
if (a is Map && b is Map) {
|
if (a is Map && b is Map) {
|
||||||
return a.length == b.length &&
|
return a.length == b.length && a.entries.every((MapEntry<Object?, Object?> entry) =>
|
||||||
a.entries.every(
|
|
||||||
(MapEntry<Object?, Object?> entry) =>
|
|
||||||
(b as Map<Object?, Object?>).containsKey(entry.key) &&
|
(b as Map<Object?, Object?>).containsKey(entry.key) &&
|
||||||
_deepEquals(entry.value, b[entry.key]),
|
_deepEquals(entry.value, b[entry.key]));
|
||||||
);
|
|
||||||
}
|
}
|
||||||
return a == b;
|
return a == b;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/// Transport types for Tor connections
|
/// Transport types for Tor connections
|
||||||
enum TransportType {
|
enum TransportType {
|
||||||
/// Direct Tor connection (no bridges)
|
/// Direct Tor connection (no bridges)
|
||||||
none,
|
none,
|
||||||
|
|
||||||
/// obfs4 pluggable transport
|
/// obfs4 pluggable transport
|
||||||
obfs4,
|
obfs4,
|
||||||
|
|
||||||
/// Snowflake pluggable transport (default broker)
|
/// Snowflake pluggable transport (default broker)
|
||||||
snowflake,
|
snowflake,
|
||||||
|
|
||||||
/// Snowflake via AMP cache
|
/// Snowflake via AMP cache
|
||||||
snowflakeAmp,
|
snowflakeAmp,
|
||||||
|
|
||||||
/// Meek pluggable transport
|
/// Meek pluggable transport
|
||||||
meek,
|
meek,
|
||||||
|
|
||||||
/// Meek via Azure CDN
|
/// Meek via Azure CDN
|
||||||
meekAzure,
|
meekAzure,
|
||||||
|
|
||||||
/// WebTunnel pluggable transport
|
/// WebTunnel pluggable transport
|
||||||
webtunnel,
|
webtunnel,
|
||||||
|
|
||||||
/// Custom bridge lines (passthrough)
|
/// Custom bridge lines (passthrough)
|
||||||
custom,
|
custom,
|
||||||
}
|
}
|
||||||
@@ -110,8 +95,7 @@ class TorConfiguration {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Object encode() {
|
Object encode() {
|
||||||
return _toList();
|
return _toList(); }
|
||||||
}
|
|
||||||
|
|
||||||
static TorConfiguration decode(Object result) {
|
static TorConfiguration decode(Object result) {
|
||||||
result as List<Object?>;
|
result as List<Object?>;
|
||||||
@@ -138,7 +122,8 @@ class TorConfiguration {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
// ignore: avoid_equals_and_hash_code_on_mutable_classes
|
// ignore: avoid_equals_and_hash_code_on_mutable_classes
|
||||||
int get hashCode => Object.hashAll(_toList());
|
int get hashCode => Object.hashAll(_toList())
|
||||||
|
;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Current Tor status
|
/// Current Tor status
|
||||||
@@ -177,8 +162,7 @@ class TorStatus {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Object encode() {
|
Object encode() {
|
||||||
return _toList();
|
return _toList(); }
|
||||||
}
|
|
||||||
|
|
||||||
static TorStatus decode(Object result) {
|
static TorStatus decode(Object result) {
|
||||||
result as List<Object?>;
|
result as List<Object?>;
|
||||||
@@ -205,7 +189,8 @@ class TorStatus {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
// ignore: avoid_equals_and_hash_code_on_mutable_classes
|
// ignore: avoid_equals_and_hash_code_on_mutable_classes
|
||||||
int get hashCode => Object.hashAll(_toList());
|
int get hashCode => Object.hashAll(_toList())
|
||||||
|
;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Log message from Tor
|
/// Log message from Tor
|
||||||
@@ -226,12 +211,15 @@ class TorLogMessage {
|
|||||||
int timestamp;
|
int timestamp;
|
||||||
|
|
||||||
List<Object?> _toList() {
|
List<Object?> _toList() {
|
||||||
return <Object?>[severity, message, timestamp];
|
return <Object?>[
|
||||||
|
severity,
|
||||||
|
message,
|
||||||
|
timestamp,
|
||||||
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
Object encode() {
|
Object encode() {
|
||||||
return _toList();
|
return _toList(); }
|
||||||
}
|
|
||||||
|
|
||||||
static TorLogMessage decode(Object result) {
|
static TorLogMessage decode(Object result) {
|
||||||
result as List<Object?>;
|
result as List<Object?>;
|
||||||
@@ -256,9 +244,11 @@ class TorLogMessage {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
// ignore: avoid_equals_and_hash_code_on_mutable_classes
|
// ignore: avoid_equals_and_hash_code_on_mutable_classes
|
||||||
int get hashCode => Object.hashAll(_toList());
|
int get hashCode => Object.hashAll(_toList())
|
||||||
|
;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
class _PigeonCodec extends StandardMessageCodec {
|
class _PigeonCodec extends StandardMessageCodec {
|
||||||
const _PigeonCodec();
|
const _PigeonCodec();
|
||||||
@override
|
@override
|
||||||
@@ -308,9 +298,7 @@ class TorApi {
|
|||||||
/// BinaryMessenger will be used which routes to the host platform.
|
/// BinaryMessenger will be used which routes to the host platform.
|
||||||
TorApi({BinaryMessenger? binaryMessenger, String messageChannelSuffix = ''})
|
TorApi({BinaryMessenger? binaryMessenger, String messageChannelSuffix = ''})
|
||||||
: pigeonVar_binaryMessenger = binaryMessenger,
|
: pigeonVar_binaryMessenger = binaryMessenger,
|
||||||
pigeonVar_messageChannelSuffix = messageChannelSuffix.isNotEmpty
|
pigeonVar_messageChannelSuffix = messageChannelSuffix.isNotEmpty ? '.$messageChannelSuffix' : '';
|
||||||
? '.$messageChannelSuffix'
|
|
||||||
: '';
|
|
||||||
final BinaryMessenger? pigeonVar_binaryMessenger;
|
final BinaryMessenger? pigeonVar_binaryMessenger;
|
||||||
|
|
||||||
static const MessageCodec<Object?> pigeonChannelCodec = _PigeonCodec();
|
static const MessageCodec<Object?> pigeonChannelCodec = _PigeonCodec();
|
||||||
@@ -320,16 +308,13 @@ class TorApi {
|
|||||||
/// Start Tor with the given configuration
|
/// Start Tor with the given configuration
|
||||||
/// Returns a Future to avoid blocking the main thread
|
/// Returns a Future to avoid blocking the main thread
|
||||||
Future<int> startTor(TorConfiguration config) async {
|
Future<int> startTor(TorConfiguration config) async {
|
||||||
final pigeonVar_channelName =
|
final pigeonVar_channelName = 'dev.flutter.pigeon.flutter_tor.TorApi.startTor$pigeonVar_messageChannelSuffix';
|
||||||
'dev.flutter.pigeon.flutter_tor.TorApi.startTor$pigeonVar_messageChannelSuffix';
|
|
||||||
final pigeonVar_channel = BasicMessageChannel<Object?>(
|
final pigeonVar_channel = BasicMessageChannel<Object?>(
|
||||||
pigeonVar_channelName,
|
pigeonVar_channelName,
|
||||||
pigeonChannelCodec,
|
pigeonChannelCodec,
|
||||||
binaryMessenger: pigeonVar_binaryMessenger,
|
binaryMessenger: pigeonVar_binaryMessenger,
|
||||||
);
|
);
|
||||||
final Future<Object?> pigeonVar_sendFuture = pigeonVar_channel.send(
|
final Future<Object?> pigeonVar_sendFuture = pigeonVar_channel.send(<Object?>[config]);
|
||||||
<Object?>[config],
|
|
||||||
);
|
|
||||||
final pigeonVar_replyList = await pigeonVar_sendFuture as List<Object?>?;
|
final pigeonVar_replyList = await pigeonVar_sendFuture as List<Object?>?;
|
||||||
if (pigeonVar_replyList == null) {
|
if (pigeonVar_replyList == null) {
|
||||||
throw _createConnectionError(pigeonVar_channelName);
|
throw _createConnectionError(pigeonVar_channelName);
|
||||||
@@ -351,8 +336,7 @@ class TorApi {
|
|||||||
|
|
||||||
/// Stop Tor
|
/// Stop Tor
|
||||||
Future<void> stopTor() async {
|
Future<void> stopTor() async {
|
||||||
final pigeonVar_channelName =
|
final pigeonVar_channelName = 'dev.flutter.pigeon.flutter_tor.TorApi.stopTor$pigeonVar_messageChannelSuffix';
|
||||||
'dev.flutter.pigeon.flutter_tor.TorApi.stopTor$pigeonVar_messageChannelSuffix';
|
|
||||||
final pigeonVar_channel = BasicMessageChannel<Object?>(
|
final pigeonVar_channel = BasicMessageChannel<Object?>(
|
||||||
pigeonVar_channelName,
|
pigeonVar_channelName,
|
||||||
pigeonChannelCodec,
|
pigeonChannelCodec,
|
||||||
@@ -375,8 +359,7 @@ class TorApi {
|
|||||||
|
|
||||||
/// Get current status
|
/// Get current status
|
||||||
Future<TorStatus> getStatus() async {
|
Future<TorStatus> getStatus() async {
|
||||||
final pigeonVar_channelName =
|
final pigeonVar_channelName = 'dev.flutter.pigeon.flutter_tor.TorApi.getStatus$pigeonVar_messageChannelSuffix';
|
||||||
'dev.flutter.pigeon.flutter_tor.TorApi.getStatus$pigeonVar_messageChannelSuffix';
|
|
||||||
final pigeonVar_channel = BasicMessageChannel<Object?>(
|
final pigeonVar_channel = BasicMessageChannel<Object?>(
|
||||||
pigeonVar_channelName,
|
pigeonVar_channelName,
|
||||||
pigeonChannelCodec,
|
pigeonChannelCodec,
|
||||||
@@ -404,8 +387,7 @@ class TorApi {
|
|||||||
|
|
||||||
/// Request a new Tor identity (new circuit)
|
/// Request a new Tor identity (new circuit)
|
||||||
Future<void> requestNewIdentity() async {
|
Future<void> requestNewIdentity() async {
|
||||||
final pigeonVar_channelName =
|
final pigeonVar_channelName = 'dev.flutter.pigeon.flutter_tor.TorApi.requestNewIdentity$pigeonVar_messageChannelSuffix';
|
||||||
'dev.flutter.pigeon.flutter_tor.TorApi.requestNewIdentity$pigeonVar_messageChannelSuffix';
|
|
||||||
final pigeonVar_channel = BasicMessageChannel<Object?>(
|
final pigeonVar_channel = BasicMessageChannel<Object?>(
|
||||||
pigeonVar_channelName,
|
pigeonVar_channelName,
|
||||||
pigeonChannelCodec,
|
pigeonChannelCodec,
|
||||||
@@ -437,76 +419,54 @@ abstract class TorLogApi {
|
|||||||
/// Called when status changes
|
/// Called when status changes
|
||||||
void onStatusChanged(TorStatus status);
|
void onStatusChanged(TorStatus status);
|
||||||
|
|
||||||
static void setUp(
|
static void setUp(TorLogApi? api, {BinaryMessenger? binaryMessenger, String messageChannelSuffix = '',}) {
|
||||||
TorLogApi? api, {
|
messageChannelSuffix = messageChannelSuffix.isNotEmpty ? '.$messageChannelSuffix' : '';
|
||||||
BinaryMessenger? binaryMessenger,
|
|
||||||
String messageChannelSuffix = '',
|
|
||||||
}) {
|
|
||||||
messageChannelSuffix = messageChannelSuffix.isNotEmpty
|
|
||||||
? '.$messageChannelSuffix'
|
|
||||||
: '';
|
|
||||||
{
|
{
|
||||||
final pigeonVar_channel = BasicMessageChannel<Object?>(
|
final pigeonVar_channel = BasicMessageChannel<Object?>(
|
||||||
'dev.flutter.pigeon.flutter_tor.TorLogApi.onLogMessage$messageChannelSuffix',
|
'dev.flutter.pigeon.flutter_tor.TorLogApi.onLogMessage$messageChannelSuffix', pigeonChannelCodec,
|
||||||
pigeonChannelCodec,
|
binaryMessenger: binaryMessenger);
|
||||||
binaryMessenger: binaryMessenger,
|
|
||||||
);
|
|
||||||
if (api == null) {
|
if (api == null) {
|
||||||
pigeonVar_channel.setMessageHandler(null);
|
pigeonVar_channel.setMessageHandler(null);
|
||||||
} else {
|
} else {
|
||||||
pigeonVar_channel.setMessageHandler((Object? message) async {
|
pigeonVar_channel.setMessageHandler((Object? message) async {
|
||||||
assert(
|
assert(message != null,
|
||||||
message != null,
|
'Argument for dev.flutter.pigeon.flutter_tor.TorLogApi.onLogMessage was null.');
|
||||||
'Argument for dev.flutter.pigeon.flutter_tor.TorLogApi.onLogMessage was null.',
|
|
||||||
);
|
|
||||||
final List<Object?> args = (message as List<Object?>?)!;
|
final List<Object?> args = (message as List<Object?>?)!;
|
||||||
final TorLogMessage? arg_log = (args[0] as TorLogMessage?);
|
final TorLogMessage? arg_log = (args[0] as TorLogMessage?);
|
||||||
assert(
|
assert(arg_log != null,
|
||||||
arg_log != null,
|
'Argument for dev.flutter.pigeon.flutter_tor.TorLogApi.onLogMessage was null, expected non-null TorLogMessage.');
|
||||||
'Argument for dev.flutter.pigeon.flutter_tor.TorLogApi.onLogMessage was null, expected non-null TorLogMessage.',
|
|
||||||
);
|
|
||||||
try {
|
try {
|
||||||
api.onLogMessage(arg_log!);
|
api.onLogMessage(arg_log!);
|
||||||
return wrapResponse(empty: true);
|
return wrapResponse(empty: true);
|
||||||
} on PlatformException catch (e) {
|
} on PlatformException catch (e) {
|
||||||
return wrapResponse(error: e);
|
return wrapResponse(error: e);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
return wrapResponse(
|
return wrapResponse(error: PlatformException(code: 'error', message: e.toString()));
|
||||||
error: PlatformException(code: 'error', message: e.toString()),
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
{
|
{
|
||||||
final pigeonVar_channel = BasicMessageChannel<Object?>(
|
final pigeonVar_channel = BasicMessageChannel<Object?>(
|
||||||
'dev.flutter.pigeon.flutter_tor.TorLogApi.onStatusChanged$messageChannelSuffix',
|
'dev.flutter.pigeon.flutter_tor.TorLogApi.onStatusChanged$messageChannelSuffix', pigeonChannelCodec,
|
||||||
pigeonChannelCodec,
|
binaryMessenger: binaryMessenger);
|
||||||
binaryMessenger: binaryMessenger,
|
|
||||||
);
|
|
||||||
if (api == null) {
|
if (api == null) {
|
||||||
pigeonVar_channel.setMessageHandler(null);
|
pigeonVar_channel.setMessageHandler(null);
|
||||||
} else {
|
} else {
|
||||||
pigeonVar_channel.setMessageHandler((Object? message) async {
|
pigeonVar_channel.setMessageHandler((Object? message) async {
|
||||||
assert(
|
assert(message != null,
|
||||||
message != null,
|
'Argument for dev.flutter.pigeon.flutter_tor.TorLogApi.onStatusChanged was null.');
|
||||||
'Argument for dev.flutter.pigeon.flutter_tor.TorLogApi.onStatusChanged was null.',
|
|
||||||
);
|
|
||||||
final List<Object?> args = (message as List<Object?>?)!;
|
final List<Object?> args = (message as List<Object?>?)!;
|
||||||
final TorStatus? arg_status = (args[0] as TorStatus?);
|
final TorStatus? arg_status = (args[0] as TorStatus?);
|
||||||
assert(
|
assert(arg_status != null,
|
||||||
arg_status != null,
|
'Argument for dev.flutter.pigeon.flutter_tor.TorLogApi.onStatusChanged was null, expected non-null TorStatus.');
|
||||||
'Argument for dev.flutter.pigeon.flutter_tor.TorLogApi.onStatusChanged was null, expected non-null TorStatus.',
|
|
||||||
);
|
|
||||||
try {
|
try {
|
||||||
api.onStatusChanged(arg_status!);
|
api.onStatusChanged(arg_status!);
|
||||||
return wrapResponse(empty: true);
|
return wrapResponse(empty: true);
|
||||||
} on PlatformException catch (e) {
|
} on PlatformException catch (e) {
|
||||||
return wrapResponse(error: e);
|
return wrapResponse(error: e);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
return wrapResponse(
|
return wrapResponse(error: PlatformException(code: 'error', message: e.toString()));
|
||||||
error: PlatformException(code: 'error', message: e.toString()),
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -518,13 +478,9 @@ class IPtProxyController {
|
|||||||
/// Constructor for [IPtProxyController]. The [binaryMessenger] named argument is
|
/// Constructor for [IPtProxyController]. The [binaryMessenger] named argument is
|
||||||
/// available for dependency injection. If it is left null, the default
|
/// available for dependency injection. If it is left null, the default
|
||||||
/// BinaryMessenger will be used which routes to the host platform.
|
/// BinaryMessenger will be used which routes to the host platform.
|
||||||
IPtProxyController({
|
IPtProxyController({BinaryMessenger? binaryMessenger, String messageChannelSuffix = ''})
|
||||||
BinaryMessenger? binaryMessenger,
|
: pigeonVar_binaryMessenger = binaryMessenger,
|
||||||
String messageChannelSuffix = '',
|
pigeonVar_messageChannelSuffix = messageChannelSuffix.isNotEmpty ? '.$messageChannelSuffix' : '';
|
||||||
}) : pigeonVar_binaryMessenger = binaryMessenger,
|
|
||||||
pigeonVar_messageChannelSuffix = messageChannelSuffix.isNotEmpty
|
|
||||||
? '.$messageChannelSuffix'
|
|
||||||
: '';
|
|
||||||
final BinaryMessenger? pigeonVar_binaryMessenger;
|
final BinaryMessenger? pigeonVar_binaryMessenger;
|
||||||
|
|
||||||
static const MessageCodec<Object?> pigeonChannelCodec = _PigeonCodec();
|
static const MessageCodec<Object?> pigeonChannelCodec = _PigeonCodec();
|
||||||
@@ -532,16 +488,13 @@ class IPtProxyController {
|
|||||||
final String pigeonVar_messageChannelSuffix;
|
final String pigeonVar_messageChannelSuffix;
|
||||||
|
|
||||||
Future<int> start(TransportType proxyType, String proxy) async {
|
Future<int> start(TransportType proxyType, String proxy) async {
|
||||||
final pigeonVar_channelName =
|
final pigeonVar_channelName = 'dev.flutter.pigeon.flutter_tor.IPtProxyController.start$pigeonVar_messageChannelSuffix';
|
||||||
'dev.flutter.pigeon.flutter_tor.IPtProxyController.start$pigeonVar_messageChannelSuffix';
|
|
||||||
final pigeonVar_channel = BasicMessageChannel<Object?>(
|
final pigeonVar_channel = BasicMessageChannel<Object?>(
|
||||||
pigeonVar_channelName,
|
pigeonVar_channelName,
|
||||||
pigeonChannelCodec,
|
pigeonChannelCodec,
|
||||||
binaryMessenger: pigeonVar_binaryMessenger,
|
binaryMessenger: pigeonVar_binaryMessenger,
|
||||||
);
|
);
|
||||||
final Future<Object?> pigeonVar_sendFuture = pigeonVar_channel.send(
|
final Future<Object?> pigeonVar_sendFuture = pigeonVar_channel.send(<Object?>[proxyType, proxy]);
|
||||||
<Object?>[proxyType, proxy],
|
|
||||||
);
|
|
||||||
final pigeonVar_replyList = await pigeonVar_sendFuture as List<Object?>?;
|
final pigeonVar_replyList = await pigeonVar_sendFuture as List<Object?>?;
|
||||||
if (pigeonVar_replyList == null) {
|
if (pigeonVar_replyList == null) {
|
||||||
throw _createConnectionError(pigeonVar_channelName);
|
throw _createConnectionError(pigeonVar_channelName);
|
||||||
@@ -562,16 +515,13 @@ class IPtProxyController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Future<void> stop(TransportType proxyType) async {
|
Future<void> stop(TransportType proxyType) async {
|
||||||
final pigeonVar_channelName =
|
final pigeonVar_channelName = 'dev.flutter.pigeon.flutter_tor.IPtProxyController.stop$pigeonVar_messageChannelSuffix';
|
||||||
'dev.flutter.pigeon.flutter_tor.IPtProxyController.stop$pigeonVar_messageChannelSuffix';
|
|
||||||
final pigeonVar_channel = BasicMessageChannel<Object?>(
|
final pigeonVar_channel = BasicMessageChannel<Object?>(
|
||||||
pigeonVar_channelName,
|
pigeonVar_channelName,
|
||||||
pigeonChannelCodec,
|
pigeonChannelCodec,
|
||||||
binaryMessenger: pigeonVar_binaryMessenger,
|
binaryMessenger: pigeonVar_binaryMessenger,
|
||||||
);
|
);
|
||||||
final Future<Object?> pigeonVar_sendFuture = pigeonVar_channel.send(
|
final Future<Object?> pigeonVar_sendFuture = pigeonVar_channel.send(<Object?>[proxyType]);
|
||||||
<Object?>[proxyType],
|
|
||||||
);
|
|
||||||
final pigeonVar_replyList = await pigeonVar_sendFuture as List<Object?>?;
|
final pigeonVar_replyList = await pigeonVar_sendFuture as List<Object?>?;
|
||||||
if (pigeonVar_replyList == null) {
|
if (pigeonVar_replyList == null) {
|
||||||
throw _createConnectionError(pigeonVar_channelName);
|
throw _createConnectionError(pigeonVar_channelName);
|
||||||
|
|||||||
@@ -14,39 +14,40 @@ PlatformException _createConnectionError(String channelName) {
|
|||||||
message: 'Unable to establish connection on channel: "$channelName".',
|
message: 'Unable to establish connection on channel: "$channelName".',
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool _deepEquals(Object? a, Object? b) {
|
bool _deepEquals(Object? a, Object? b) {
|
||||||
if (a is List && b is List) {
|
if (a is List && b is List) {
|
||||||
return a.length == b.length &&
|
return a.length == b.length &&
|
||||||
a.indexed.every(
|
a.indexed
|
||||||
((int, dynamic) item) => _deepEquals(item.$2, b[item.$1]),
|
.every(((int, dynamic) item) => _deepEquals(item.$2, b[item.$1]));
|
||||||
);
|
|
||||||
}
|
}
|
||||||
if (a is Map && b is Map) {
|
if (a is Map && b is Map) {
|
||||||
return a.length == b.length &&
|
return a.length == b.length && a.entries.every((MapEntry<Object?, Object?> entry) =>
|
||||||
a.entries.every(
|
|
||||||
(MapEntry<Object?, Object?> entry) =>
|
|
||||||
(b as Map<Object?, Object?>).containsKey(entry.key) &&
|
(b as Map<Object?, Object?>).containsKey(entry.key) &&
|
||||||
_deepEquals(entry.value, b[entry.key]),
|
_deepEquals(entry.value, b[entry.key]));
|
||||||
);
|
|
||||||
}
|
}
|
||||||
return a == b;
|
return a == b;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
class LocalizedResult {
|
class LocalizedResult {
|
||||||
LocalizedResult({required this.languageName, this.countryName});
|
LocalizedResult({
|
||||||
|
required this.languageName,
|
||||||
|
this.countryName,
|
||||||
|
});
|
||||||
|
|
||||||
String languageName;
|
String languageName;
|
||||||
|
|
||||||
String? countryName;
|
String? countryName;
|
||||||
|
|
||||||
List<Object?> _toList() {
|
List<Object?> _toList() {
|
||||||
return <Object?>[languageName, countryName];
|
return <Object?>[
|
||||||
|
languageName,
|
||||||
|
countryName,
|
||||||
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
Object encode() {
|
Object encode() {
|
||||||
return _toList();
|
return _toList(); }
|
||||||
}
|
|
||||||
|
|
||||||
static LocalizedResult decode(Object result) {
|
static LocalizedResult decode(Object result) {
|
||||||
result as List<Object?>;
|
result as List<Object?>;
|
||||||
@@ -70,9 +71,11 @@ class LocalizedResult {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
// ignore: avoid_equals_and_hash_code_on_mutable_classes
|
// ignore: avoid_equals_and_hash_code_on_mutable_classes
|
||||||
int get hashCode => Object.hashAll(_toList());
|
int get hashCode => Object.hashAll(_toList())
|
||||||
|
;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
class _PigeonCodec extends StandardMessageCodec {
|
class _PigeonCodec extends StandardMessageCodec {
|
||||||
const _PigeonCodec();
|
const _PigeonCodec();
|
||||||
@override
|
@override
|
||||||
@@ -103,33 +106,23 @@ class LocaleResolver {
|
|||||||
/// Constructor for [LocaleResolver]. The [binaryMessenger] named argument is
|
/// Constructor for [LocaleResolver]. The [binaryMessenger] named argument is
|
||||||
/// available for dependency injection. If it is left null, the default
|
/// available for dependency injection. If it is left null, the default
|
||||||
/// BinaryMessenger will be used which routes to the host platform.
|
/// BinaryMessenger will be used which routes to the host platform.
|
||||||
LocaleResolver({
|
LocaleResolver({BinaryMessenger? binaryMessenger, String messageChannelSuffix = ''})
|
||||||
BinaryMessenger? binaryMessenger,
|
: pigeonVar_binaryMessenger = binaryMessenger,
|
||||||
String messageChannelSuffix = '',
|
pigeonVar_messageChannelSuffix = messageChannelSuffix.isNotEmpty ? '.$messageChannelSuffix' : '';
|
||||||
}) : pigeonVar_binaryMessenger = binaryMessenger,
|
|
||||||
pigeonVar_messageChannelSuffix = messageChannelSuffix.isNotEmpty
|
|
||||||
? '.$messageChannelSuffix'
|
|
||||||
: '';
|
|
||||||
final BinaryMessenger? pigeonVar_binaryMessenger;
|
final BinaryMessenger? pigeonVar_binaryMessenger;
|
||||||
|
|
||||||
static const MessageCodec<Object?> pigeonChannelCodec = _PigeonCodec();
|
static const MessageCodec<Object?> pigeonChannelCodec = _PigeonCodec();
|
||||||
|
|
||||||
final String pigeonVar_messageChannelSuffix;
|
final String pigeonVar_messageChannelSuffix;
|
||||||
|
|
||||||
Future<LocalizedResult> resolve(
|
Future<LocalizedResult> resolve(String languageTag, String targetLangouageTag) async {
|
||||||
String languageTag,
|
final pigeonVar_channelName = 'dev.flutter.pigeon.locale_resolver.LocaleResolver.resolve$pigeonVar_messageChannelSuffix';
|
||||||
String targetLangouageTag,
|
|
||||||
) async {
|
|
||||||
final pigeonVar_channelName =
|
|
||||||
'dev.flutter.pigeon.locale_resolver.LocaleResolver.resolve$pigeonVar_messageChannelSuffix';
|
|
||||||
final pigeonVar_channel = BasicMessageChannel<Object?>(
|
final pigeonVar_channel = BasicMessageChannel<Object?>(
|
||||||
pigeonVar_channelName,
|
pigeonVar_channelName,
|
||||||
pigeonChannelCodec,
|
pigeonChannelCodec,
|
||||||
binaryMessenger: pigeonVar_binaryMessenger,
|
binaryMessenger: pigeonVar_binaryMessenger,
|
||||||
);
|
);
|
||||||
final Future<Object?> pigeonVar_sendFuture = pigeonVar_channel.send(
|
final Future<Object?> pigeonVar_sendFuture = pigeonVar_channel.send(<Object?>[languageTag, targetLangouageTag]);
|
||||||
<Object?>[languageTag, targetLangouageTag],
|
|
||||||
);
|
|
||||||
final pigeonVar_replyList = await pigeonVar_sendFuture as List<Object?>?;
|
final pigeonVar_replyList = await pigeonVar_sendFuture as List<Object?>?;
|
||||||
if (pigeonVar_replyList == null) {
|
if (pigeonVar_replyList == null) {
|
||||||
throw _createConnectionError(pigeonVar_channelName);
|
throw _createConnectionError(pigeonVar_channelName);
|
||||||
|
|||||||
@@ -8,11 +8,7 @@ import 'dart:typed_data' show Float64List, Int32List, Int64List, Uint8List;
|
|||||||
import 'package:flutter/foundation.dart' show ReadBuffer, WriteBuffer;
|
import 'package:flutter/foundation.dart' show ReadBuffer, WriteBuffer;
|
||||||
import 'package:flutter/services.dart';
|
import 'package:flutter/services.dart';
|
||||||
|
|
||||||
List<Object?> wrapResponse({
|
List<Object?> wrapResponse({Object? result, PlatformException? error, bool empty = false}) {
|
||||||
Object? result,
|
|
||||||
PlatformException? error,
|
|
||||||
bool empty = false,
|
|
||||||
}) {
|
|
||||||
if (empty) {
|
if (empty) {
|
||||||
return <Object?>[];
|
return <Object?>[];
|
||||||
}
|
}
|
||||||
@@ -21,25 +17,21 @@ List<Object?> wrapResponse({
|
|||||||
}
|
}
|
||||||
return <Object?>[error.code, error.message, error.details];
|
return <Object?>[error.code, error.message, error.details];
|
||||||
}
|
}
|
||||||
|
|
||||||
bool _deepEquals(Object? a, Object? b) {
|
bool _deepEquals(Object? a, Object? b) {
|
||||||
if (a is List && b is List) {
|
if (a is List && b is List) {
|
||||||
return a.length == b.length &&
|
return a.length == b.length &&
|
||||||
a.indexed.every(
|
a.indexed
|
||||||
((int, dynamic) item) => _deepEquals(item.$2, b[item.$1]),
|
.every(((int, dynamic) item) => _deepEquals(item.$2, b[item.$1]));
|
||||||
);
|
|
||||||
}
|
}
|
||||||
if (a is Map && b is Map) {
|
if (a is Map && b is Map) {
|
||||||
return a.length == b.length &&
|
return a.length == b.length && a.entries.every((MapEntry<Object?, Object?> entry) =>
|
||||||
a.entries.every(
|
|
||||||
(MapEntry<Object?, Object?> entry) =>
|
|
||||||
(b as Map<Object?, Object?>).containsKey(entry.key) &&
|
(b as Map<Object?, Object?>).containsKey(entry.key) &&
|
||||||
_deepEquals(entry.value, b[entry.key]),
|
_deepEquals(entry.value, b[entry.key]));
|
||||||
);
|
|
||||||
}
|
}
|
||||||
return a == b;
|
return a == b;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
class Intent {
|
class Intent {
|
||||||
Intent({
|
Intent({
|
||||||
this.fromPackageName,
|
this.fromPackageName,
|
||||||
@@ -74,8 +66,7 @@ class Intent {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Object encode() {
|
Object encode() {
|
||||||
return _toList();
|
return _toList(); }
|
||||||
}
|
|
||||||
|
|
||||||
static Intent decode(Object result) {
|
static Intent decode(Object result) {
|
||||||
result as List<Object?>;
|
result as List<Object?>;
|
||||||
@@ -103,9 +94,11 @@ class Intent {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
// ignore: avoid_equals_and_hash_code_on_mutable_classes
|
// ignore: avoid_equals_and_hash_code_on_mutable_classes
|
||||||
int get hashCode => Object.hashAll(_toList());
|
int get hashCode => Object.hashAll(_toList())
|
||||||
|
;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
class _PigeonCodec extends StandardMessageCodec {
|
class _PigeonCodec extends StandardMessageCodec {
|
||||||
const _PigeonCodec();
|
const _PigeonCodec();
|
||||||
@override
|
@override
|
||||||
@@ -137,48 +130,32 @@ abstract class IntentEvents {
|
|||||||
|
|
||||||
void onIntentReceived(int sequence, Intent intent);
|
void onIntentReceived(int sequence, Intent intent);
|
||||||
|
|
||||||
static void setUp(
|
static void setUp(IntentEvents? api, {BinaryMessenger? binaryMessenger, String messageChannelSuffix = '',}) {
|
||||||
IntentEvents? api, {
|
messageChannelSuffix = messageChannelSuffix.isNotEmpty ? '.$messageChannelSuffix' : '';
|
||||||
BinaryMessenger? binaryMessenger,
|
|
||||||
String messageChannelSuffix = '',
|
|
||||||
}) {
|
|
||||||
messageChannelSuffix = messageChannelSuffix.isNotEmpty
|
|
||||||
? '.$messageChannelSuffix'
|
|
||||||
: '';
|
|
||||||
{
|
{
|
||||||
final pigeonVar_channel = BasicMessageChannel<Object?>(
|
final pigeonVar_channel = BasicMessageChannel<Object?>(
|
||||||
'dev.flutter.pigeon.simple_intent_receiver.IntentEvents.onIntentReceived$messageChannelSuffix',
|
'dev.flutter.pigeon.simple_intent_receiver.IntentEvents.onIntentReceived$messageChannelSuffix', pigeonChannelCodec,
|
||||||
pigeonChannelCodec,
|
binaryMessenger: binaryMessenger);
|
||||||
binaryMessenger: binaryMessenger,
|
|
||||||
);
|
|
||||||
if (api == null) {
|
if (api == null) {
|
||||||
pigeonVar_channel.setMessageHandler(null);
|
pigeonVar_channel.setMessageHandler(null);
|
||||||
} else {
|
} else {
|
||||||
pigeonVar_channel.setMessageHandler((Object? message) async {
|
pigeonVar_channel.setMessageHandler((Object? message) async {
|
||||||
assert(
|
assert(message != null,
|
||||||
message != null,
|
'Argument for dev.flutter.pigeon.simple_intent_receiver.IntentEvents.onIntentReceived was null.');
|
||||||
'Argument for dev.flutter.pigeon.simple_intent_receiver.IntentEvents.onIntentReceived was null.',
|
|
||||||
);
|
|
||||||
final List<Object?> args = (message as List<Object?>?)!;
|
final List<Object?> args = (message as List<Object?>?)!;
|
||||||
final int? arg_sequence = (args[0] as int?);
|
final int? arg_sequence = (args[0] as int?);
|
||||||
assert(
|
assert(arg_sequence != null,
|
||||||
arg_sequence != null,
|
'Argument for dev.flutter.pigeon.simple_intent_receiver.IntentEvents.onIntentReceived was null, expected non-null int.');
|
||||||
'Argument for dev.flutter.pigeon.simple_intent_receiver.IntentEvents.onIntentReceived was null, expected non-null int.',
|
|
||||||
);
|
|
||||||
final Intent? arg_intent = (args[1] as Intent?);
|
final Intent? arg_intent = (args[1] as Intent?);
|
||||||
assert(
|
assert(arg_intent != null,
|
||||||
arg_intent != null,
|
'Argument for dev.flutter.pigeon.simple_intent_receiver.IntentEvents.onIntentReceived was null, expected non-null Intent.');
|
||||||
'Argument for dev.flutter.pigeon.simple_intent_receiver.IntentEvents.onIntentReceived was null, expected non-null Intent.',
|
|
||||||
);
|
|
||||||
try {
|
try {
|
||||||
api.onIntentReceived(arg_sequence!, arg_intent!);
|
api.onIntentReceived(arg_sequence!, arg_intent!);
|
||||||
return wrapResponse(empty: true);
|
return wrapResponse(empty: true);
|
||||||
} on PlatformException catch (e) {
|
} on PlatformException catch (e) {
|
||||||
return wrapResponse(error: e);
|
return wrapResponse(error: e);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
return wrapResponse(
|
return wrapResponse(error: PlatformException(code: 'error', message: e.toString()));
|
||||||
error: PlatformException(code: 'error', message: e.toString()),
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,11 +15,7 @@ PlatformException _createConnectionError(String channelName) {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
List<Object?> wrapResponse({
|
List<Object?> wrapResponse({Object? result, PlatformException? error, bool empty = false}) {
|
||||||
Object? result,
|
|
||||||
PlatformException? error,
|
|
||||||
bool empty = false,
|
|
||||||
}) {
|
|
||||||
if (empty) {
|
if (empty) {
|
||||||
return <Object?>[];
|
return <Object?>[];
|
||||||
}
|
}
|
||||||
@@ -29,6 +25,7 @@ List<Object?> wrapResponse({
|
|||||||
return <Object?>[error.code, error.message, error.details];
|
return <Object?>[error.code, error.message, error.details];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
class _PigeonCodec extends StandardMessageCodec {
|
class _PigeonCodec extends StandardMessageCodec {
|
||||||
const _PigeonCodec();
|
const _PigeonCodec();
|
||||||
@override
|
@override
|
||||||
@@ -55,13 +52,9 @@ class SpeechToTextApi {
|
|||||||
/// Constructor for [SpeechToTextApi]. The [binaryMessenger] named argument is
|
/// Constructor for [SpeechToTextApi]. The [binaryMessenger] named argument is
|
||||||
/// available for dependency injection. If it is left null, the default
|
/// available for dependency injection. If it is left null, the default
|
||||||
/// BinaryMessenger will be used which routes to the host platform.
|
/// BinaryMessenger will be used which routes to the host platform.
|
||||||
SpeechToTextApi({
|
SpeechToTextApi({BinaryMessenger? binaryMessenger, String messageChannelSuffix = ''})
|
||||||
BinaryMessenger? binaryMessenger,
|
: pigeonVar_binaryMessenger = binaryMessenger,
|
||||||
String messageChannelSuffix = '',
|
pigeonVar_messageChannelSuffix = messageChannelSuffix.isNotEmpty ? '.$messageChannelSuffix' : '';
|
||||||
}) : pigeonVar_binaryMessenger = binaryMessenger,
|
|
||||||
pigeonVar_messageChannelSuffix = messageChannelSuffix.isNotEmpty
|
|
||||||
? '.$messageChannelSuffix'
|
|
||||||
: '';
|
|
||||||
final BinaryMessenger? pigeonVar_binaryMessenger;
|
final BinaryMessenger? pigeonVar_binaryMessenger;
|
||||||
|
|
||||||
static const MessageCodec<Object?> pigeonChannelCodec = _PigeonCodec();
|
static const MessageCodec<Object?> pigeonChannelCodec = _PigeonCodec();
|
||||||
@@ -76,16 +69,13 @@ class SpeechToTextApi {
|
|||||||
/// The [locale] parameter specifies the language locale for recognition
|
/// The [locale] parameter specifies the language locale for recognition
|
||||||
/// (e.g., 'en-US', 'de-DE'). If null, uses the device default.
|
/// (e.g., 'en-US', 'de-DE'). If null, uses the device default.
|
||||||
Future<bool> showDialog({String? locale}) async {
|
Future<bool> showDialog({String? locale}) async {
|
||||||
final pigeonVar_channelName =
|
final pigeonVar_channelName = 'dev.flutter.pigeon.speech_to_text_dialog.SpeechToTextApi.showDialog$pigeonVar_messageChannelSuffix';
|
||||||
'dev.flutter.pigeon.speech_to_text_dialog.SpeechToTextApi.showDialog$pigeonVar_messageChannelSuffix';
|
|
||||||
final pigeonVar_channel = BasicMessageChannel<Object?>(
|
final pigeonVar_channel = BasicMessageChannel<Object?>(
|
||||||
pigeonVar_channelName,
|
pigeonVar_channelName,
|
||||||
pigeonChannelCodec,
|
pigeonChannelCodec,
|
||||||
binaryMessenger: pigeonVar_binaryMessenger,
|
binaryMessenger: pigeonVar_binaryMessenger,
|
||||||
);
|
);
|
||||||
final Future<Object?> pigeonVar_sendFuture = pigeonVar_channel.send(
|
final Future<Object?> pigeonVar_sendFuture = pigeonVar_channel.send(<Object?>[locale]);
|
||||||
<Object?>[locale],
|
|
||||||
);
|
|
||||||
final pigeonVar_replyList = await pigeonVar_sendFuture as List<Object?>?;
|
final pigeonVar_replyList = await pigeonVar_sendFuture as List<Object?>?;
|
||||||
if (pigeonVar_replyList == null) {
|
if (pigeonVar_replyList == null) {
|
||||||
throw _createConnectionError(pigeonVar_channelName);
|
throw _createConnectionError(pigeonVar_channelName);
|
||||||
@@ -116,43 +106,29 @@ abstract class SpeechToTextEvents {
|
|||||||
/// recognition failed or was cancelled.
|
/// recognition failed or was cancelled.
|
||||||
void onTextReceived(String text);
|
void onTextReceived(String text);
|
||||||
|
|
||||||
static void setUp(
|
static void setUp(SpeechToTextEvents? api, {BinaryMessenger? binaryMessenger, String messageChannelSuffix = '',}) {
|
||||||
SpeechToTextEvents? api, {
|
messageChannelSuffix = messageChannelSuffix.isNotEmpty ? '.$messageChannelSuffix' : '';
|
||||||
BinaryMessenger? binaryMessenger,
|
|
||||||
String messageChannelSuffix = '',
|
|
||||||
}) {
|
|
||||||
messageChannelSuffix = messageChannelSuffix.isNotEmpty
|
|
||||||
? '.$messageChannelSuffix'
|
|
||||||
: '';
|
|
||||||
{
|
{
|
||||||
final pigeonVar_channel = BasicMessageChannel<Object?>(
|
final pigeonVar_channel = BasicMessageChannel<Object?>(
|
||||||
'dev.flutter.pigeon.speech_to_text_dialog.SpeechToTextEvents.onTextReceived$messageChannelSuffix',
|
'dev.flutter.pigeon.speech_to_text_dialog.SpeechToTextEvents.onTextReceived$messageChannelSuffix', pigeonChannelCodec,
|
||||||
pigeonChannelCodec,
|
binaryMessenger: binaryMessenger);
|
||||||
binaryMessenger: binaryMessenger,
|
|
||||||
);
|
|
||||||
if (api == null) {
|
if (api == null) {
|
||||||
pigeonVar_channel.setMessageHandler(null);
|
pigeonVar_channel.setMessageHandler(null);
|
||||||
} else {
|
} else {
|
||||||
pigeonVar_channel.setMessageHandler((Object? message) async {
|
pigeonVar_channel.setMessageHandler((Object? message) async {
|
||||||
assert(
|
assert(message != null,
|
||||||
message != null,
|
'Argument for dev.flutter.pigeon.speech_to_text_dialog.SpeechToTextEvents.onTextReceived was null.');
|
||||||
'Argument for dev.flutter.pigeon.speech_to_text_dialog.SpeechToTextEvents.onTextReceived was null.',
|
|
||||||
);
|
|
||||||
final List<Object?> args = (message as List<Object?>?)!;
|
final List<Object?> args = (message as List<Object?>?)!;
|
||||||
final String? arg_text = (args[0] as String?);
|
final String? arg_text = (args[0] as String?);
|
||||||
assert(
|
assert(arg_text != null,
|
||||||
arg_text != null,
|
'Argument for dev.flutter.pigeon.speech_to_text_dialog.SpeechToTextEvents.onTextReceived was null, expected non-null String.');
|
||||||
'Argument for dev.flutter.pigeon.speech_to_text_dialog.SpeechToTextEvents.onTextReceived was null, expected non-null String.',
|
|
||||||
);
|
|
||||||
try {
|
try {
|
||||||
api.onTextReceived(arg_text!);
|
api.onTextReceived(arg_text!);
|
||||||
return wrapResponse(empty: true);
|
return wrapResponse(empty: true);
|
||||||
} on PlatformException catch (e) {
|
} on PlatformException catch (e) {
|
||||||
return wrapResponse(error: e);
|
return wrapResponse(error: e);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
return wrapResponse(
|
return wrapResponse(error: PlatformException(code: 'error', message: e.toString()));
|
||||||
error: PlatformException(code: 'error', message: e.toString()),
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user