Add proxy routing and sing-box support

This commit is contained in:
Fabian Freund
2026-05-22 18:16:31 +02:00
parent 51289f1266
commit a5974617aa
262 changed files with 32003 additions and 3962 deletions
@@ -17,14 +17,18 @@
* 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:hooks_riverpod/hooks_riverpod.dart';
import 'package:weblibre/core/logger.dart';
import 'package:weblibre/features/geckoview/domain/providers/selected_tab.dart';
import 'package:weblibre/features/geckoview/domain/providers/tab_state.dart';
import 'package:weblibre/features/small_web/presentation/controllers/small_web_mode_controller.dart';
import 'package:weblibre/features/small_web/presentation/controllers/small_web_session_controller.dart';
import 'package:weblibre/features/small_web/presentation/widgets/small_web_bottom_bar.dart';
import 'package:weblibre/features/small_web/presentation/widgets/small_web_menu_sheet.dart';
import 'package:weblibre/utils/ui_helper.dart';
class SmallWebBrowserOverlay extends HookConsumerWidget {
const SmallWebBrowserOverlay({super.key});
@@ -51,13 +55,17 @@ class SmallWebBrowserOverlay extends HookConsumerWidget {
);
ref.listen(smallWebSessionControllerProvider, (prev, next) {
final error = next.asError?.error;
final asError = next.asError;
final error = asError?.error;
final previousError = prev?.asError?.error;
if (error != null && error != previousError && context.mounted) {
ScaffoldMessenger.of(
context,
).showSnackBar(SnackBar(content: Text(error.toString())));
logger.e(
'Small web session error',
error: error,
stackTrace: asError?.stackTrace,
);
showErrorMessage(context, 'Small web error: $error');
}
});
@@ -71,7 +79,7 @@ class SmallWebBrowserOverlay extends HookConsumerWidget {
currentTabTitle: tabState?.titleOrAuthority,
onDiscover: () =>
ref.read(smallWebSessionControllerProvider.notifier).discover(),
onMenuTap: () => showSmallWebMenuSheet(context),
onMenuTap: () => unawaited(openSmallWebMenuFlow(context)),
onExit: () => ref.read(smallWebModeControllerProvider.notifier).exit(),
),
);
@@ -34,8 +34,29 @@ import 'package:weblibre/features/small_web/presentation/widgets/small_web_mode_
import 'package:weblibre/features/small_web/presentation/widgets/wander_console_sheet.dart';
import 'package:weblibre/presentation/widgets/failure_widget.dart';
Future<void> showSmallWebMenuSheet(BuildContext context) {
return showModalBottomSheet(
/// Hand-off between the small-web menu and the wander-console sheet. Each
/// sheet pops with one of these so [openSmallWebMenuFlow] can re-present the
/// next sheet from a stable parent context instead of the popped sheet's
/// deactivated context.
enum SmallWebSheetRequest { showMenu, showWander }
/// Top-level entry: opens the small-web menu and re-presents whichever sheet
/// the user requested next, looping until a sheet is dismissed without a
/// follow-up request.
Future<void> openSmallWebMenuFlow(BuildContext context) async {
SmallWebSheetRequest? next = SmallWebSheetRequest.showMenu;
while (next != null) {
if (!context.mounted) return;
if (next == SmallWebSheetRequest.showMenu) {
next = await showSmallWebMenuSheet(context);
} else {
next = await showWanderConsoleSheet(context);
}
}
}
Future<SmallWebSheetRequest?> showSmallWebMenuSheet(BuildContext context) {
return showModalBottomSheet<SmallWebSheetRequest>(
context: context,
isScrollControlled: true,
shape: const RoundedRectangleBorder(
@@ -505,10 +526,8 @@ class _DefaultContentPanel extends ConsumerWidget {
_WanderConsoleCard(currentConsoleUrl: session.currentConsoleUrl),
const SizedBox(height: 8),
FilledButton.tonalIcon(
onPressed: () async {
Navigator.of(context).pop();
await showWanderConsoleSheet(context);
},
onPressed: () =>
Navigator.of(context).pop(SmallWebSheetRequest.showWander),
icon: const Icon(Icons.dns, size: 18),
label: const Text('Browse Consoles'),
),
@@ -29,9 +29,10 @@ import 'package:weblibre/presentation/widgets/failure_widget.dart';
import 'package:weblibre/presentation/widgets/sliding_pill_toggle.dart';
import 'package:weblibre/presentation/widgets/url_icon.dart';
import 'package:weblibre/utils/form_validators.dart';
import 'package:weblibre/utils/ui_helper.dart';
Future<void> showWanderConsoleSheet(BuildContext context) {
return showModalBottomSheet(
Future<SmallWebSheetRequest?> showWanderConsoleSheet(BuildContext context) {
return showModalBottomSheet<SmallWebSheetRequest>(
context: context,
isScrollControlled: true,
shape: const RoundedRectangleBorder(
@@ -78,10 +79,9 @@ class _WanderConsoleSheet extends HookConsumerWidget {
child: Row(
children: [
IconButton(
onPressed: () async {
Navigator.of(context).pop();
await showSmallWebMenuSheet(context);
},
onPressed: () => Navigator.of(
context,
).pop(SmallWebSheetRequest.showMenu),
icon: const Icon(Icons.arrow_back),
style: const ButtonStyle(
tapTargetSize: MaterialTapTargetSize.shrinkWrap,
@@ -618,11 +618,10 @@ class _AddConsoleDialog extends HookWidget {
if (!context.mounted) return;
// Show the snackbar first so it's anchored to a still-mounted context;
// popping the dialog afterwards deactivates this BuildContext.
showInfoMessage(context, 'Added console ${consoleUrl.host}');
Navigator.of(context).pop();
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text('Added console ${consoleUrl.host}')),
);
} on Exception catch (e) {
if (!context.mounted) return;
isLoading.value = false;