Merge branch 'main' into tor_proxy

This commit is contained in:
Fabian Freund
2025-09-15 06:21:24 +02:00
3 changed files with 198 additions and 248 deletions
@@ -57,8 +57,6 @@ class BrowserScreen extends HookConsumerWidget {
@override @override
Widget build(BuildContext context, WidgetRef ref) { Widget build(BuildContext context, WidgetRef ref) {
final scaffoldKey = useMemoized(() => GlobalKey<ScaffoldState>());
final eventService = ref.watch(eventServiceProvider); final eventService = ref.watch(eventServiceProvider);
final sheetDisplayed = ref.watch( final sheetDisplayed = ref.watch(
@@ -106,56 +104,6 @@ class BrowserScreen extends HookConsumerWidget {
); );
final sheetController = useRef<PersistentBottomSheetController?>(null); final sheetController = useRef<PersistentBottomSheetController?>(null);
ref.listen(bottomSheetControllerProvider, (previous, next) {
final state = scaffoldKey.currentState;
if (state != null) {
if (sheetController.value != null) {
sheetController.value!.close();
sheetController.value = null;
}
if (next != null) {
final relativeSafeArea = MediaQuery.of(context).relativeSafeArea();
sheetController.value = state.showBottomSheet((context) {
bool dismissOnThreshold(
DraggableScrollableNotification notification,
) {
if (notification.extent <= 0.1) {
ref.read(bottomSheetControllerProvider.notifier).dismiss();
return true;
} else {
ref
.read(bottomSheetExtendProvider.notifier)
.add(notification.extent);
}
return false;
}
final sheet = switch (next) {
ViewTabsSheet() =>
NotificationListener<DraggableScrollableNotification>(
key: ValueKey(next),
onNotification: dismissOnThreshold,
child: _ViewTabsSheet(maxChildSize: relativeSafeArea),
),
final EditUrlSheet parameter =>
NotificationListener<DraggableScrollableNotification>(
key: ValueKey(parameter),
onNotification: dismissOnThreshold,
child: _ViewUrlSheet(
initialTabState: parameter.tabState,
maxChildSize: relativeSafeArea,
),
),
};
return sheet;
});
}
}
});
return PopScope( return PopScope(
//We need this for BackButtonListener to work downstream //We need this for BackButtonListener to work downstream
@@ -164,7 +112,6 @@ class BrowserScreen extends HookConsumerWidget {
child: Theme( child: Theme(
data: themeData, data: themeData,
child: Scaffold( child: Scaffold(
key: scaffoldKey,
extendBodyBehindAppBar: tabInFullScreen, extendBodyBehindAppBar: tabInFullScreen,
bottomNavigationBar: HookConsumer( bottomNavigationBar: HookConsumer(
builder: (context, ref, child) { builder: (context, ref, child) {
@@ -265,161 +212,221 @@ class BrowserScreen extends HookConsumerWidget {
); );
}, },
), ),
body: DragTarget<TabDragData>( body: Consumer(
onMove: (details) { child: DragTarget<TabDragData>(
ref onMove: (details) {
.read(willAcceptDropProvider.notifier) ref
.setData(DeleteDropData(details.data.tabId)); .read(willAcceptDropProvider.notifier)
}, .setData(DeleteDropData(details.data.tabId));
onLeave: (data) { },
ref.read(willAcceptDropProvider.notifier).clear(); onLeave: (data) {
}, ref.read(willAcceptDropProvider.notifier).clear();
onAcceptWithDetails: (details) async { },
ref.read(willAcceptDropProvider.notifier).clear(); onAcceptWithDetails: (details) async {
await ref ref.read(willAcceptDropProvider.notifier).clear();
.read(tabRepositoryProvider.notifier) await ref
.closeTab(details.data.tabId); .read(tabRepositoryProvider.notifier)
.closeTab(details.data.tabId);
if (context.mounted) { if (context.mounted) {
ui_helper.showTabUndoClose( ui_helper.showTabUndoClose(
context, context,
ref.read(tabRepositoryProvider.notifier).undoClose, ref.read(tabRepositoryProvider.notifier).undoClose,
); );
} }
}, },
builder: (context, _, _) { builder: (context, _, _) {
return OverlayPortal( return OverlayPortal(
controller: overlayController, controller: overlayController,
overlayChildBuilder: (context) { overlayChildBuilder: (context) {
return overlayBuilder!.call(context); return overlayBuilder!.call(context);
}, },
child: Listener( child: Listener(
onPointerDown: sheetDisplayed onPointerDown: sheetDisplayed
? (_) { ? (_) {
ref
.read(bottomSheetControllerProvider.notifier)
.dismiss();
}
: null,
child: BackButtonListener(
onBackButtonPressed: () async {
final tabState = ref.read(selectedTabStateProvider);
final tabCount = ref.read(
tabListProvider.select((tabs) => tabs.value.length),
);
//Don't do anything if a child route is active
if (GoRouterState.of(context).topRoute?.name !=
BrowserRoute.name) {
return false;
}
if (sheetDisplayed) {
ref ref
.read(bottomSheetControllerProvider.notifier) .read(bottomSheetControllerProvider.notifier)
.dismiss(); .dismiss();
return true;
} }
: null,
child: BackButtonListener(
onBackButtonPressed: () async {
final tabState = ref.read(selectedTabStateProvider);
final tabCount = ref.read( if (overlayBuilder != null) {
tabListProvider.select((tabs) => tabs.value.length), ref
); .read(overlayControllerProvider.notifier)
.dismiss();
return true;
}
//Don't do anything if a child route is active if (tabState?.isFullScreen == true) {
if (GoRouterState.of(context).topRoute?.name !=
BrowserRoute.name) {
return false;
}
if (sheetDisplayed) {
ref
.read(bottomSheetControllerProvider.notifier)
.dismiss();
return true;
}
if (overlayBuilder != null) {
ref.read(overlayControllerProvider.notifier).dismiss();
return true;
}
if (tabState?.isFullScreen == true) {
await ref
.read(selectedTabSessionNotifierProvider)
.exitFullscreen();
return true;
}
if (tabState?.isLoading == true) {
lastBackButtonPress.value = null;
final controller = ref.read(
selectedTabSessionNotifierProvider,
);
await controller.stopLoading();
return true;
} else if (tabState?.readerableState.active == true) {
lastBackButtonPress.value = null;
await ref
.read(readerableScreenControllerProvider.notifier)
.toggleReaderView(false);
return true;
} else if (tabState?.historyState.canGoBack == true) {
lastBackButtonPress.value = null;
final controller = ref.read(
selectedTabSessionNotifierProvider,
);
await controller.goBack();
return true;
}
//Go router has routes to go back to
if (context.canPop()) {
return true;
}
if (ref
.read(tabRepositoryProvider.notifier)
.hasLaunchedFromIntent(tabState?.id)) {
//Mark back as unhandled and navigator will pop
await SystemNavigator.pop();
return false;
}
if (lastBackButtonPress.value != null &&
DateTime.now().difference(
lastBackButtonPress.value!,
) <
_backButtonPressTimeout) {
lastBackButtonPress.value = null;
if (tabState != null && tabCount > 1) {
await ref await ref
.read(tabRepositoryProvider.notifier) .read(selectedTabSessionNotifierProvider)
.closeTab(tabState.id); .exitFullscreen();
return true;
}
if (context.mounted) { if (tabState?.isLoading == true) {
ui_helper.showTabUndoClose( lastBackButtonPress.value = null;
context,
ref final controller = ref.read(
.read(tabRepositoryProvider.notifier) selectedTabSessionNotifierProvider,
.undoClose, );
);
} await controller.stopLoading();
return true;
} else if (tabState?.readerableState.active == true) {
lastBackButtonPress.value = null;
await ref
.read(readerableScreenControllerProvider.notifier)
.toggleReaderView(false);
return true; return true;
} else { } else if (tabState?.historyState.canGoBack == true) {
lastBackButtonPress.value = null;
final controller = ref.read(
selectedTabSessionNotifierProvider,
);
await controller.goBack();
return true;
}
//Go router has routes to go back to
if (context.canPop()) {
return true;
}
if (ref
.read(tabRepositoryProvider.notifier)
.hasLaunchedFromIntent(tabState?.id)) {
//Mark back as unhandled and navigator will pop //Mark back as unhandled and navigator will pop
await SystemNavigator.pop(); await SystemNavigator.pop();
return false; return false;
} }
} else {
lastBackButtonPress.value = DateTime.now();
ui_helper.showTabBackButtonMessage(
context,
tabCount,
_backButtonPressTimeout,
);
return true; if (lastBackButtonPress.value != null &&
} DateTime.now().difference(
}, lastBackButtonPress.value!,
child: _BrowserView( ) <
sheetDisplayed: sheetDisplayed, _backButtonPressTimeout) {
isFullscreen: tabInFullScreen, lastBackButtonPress.value = null;
if (tabState != null && tabCount > 1) {
await ref
.read(tabRepositoryProvider.notifier)
.closeTab(tabState.id);
if (context.mounted) {
ui_helper.showTabUndoClose(
context,
ref
.read(tabRepositoryProvider.notifier)
.undoClose,
);
}
return true;
} else {
//Mark back as unhandled and navigator will pop
await SystemNavigator.pop();
return false;
}
} else {
lastBackButtonPress.value = DateTime.now();
ui_helper.showTabBackButtonMessage(
context,
tabCount,
_backButtonPressTimeout,
);
return true;
}
},
child: _BrowserView(
sheetDisplayed: sheetDisplayed,
isFullscreen: tabInFullScreen,
),
), ),
), ),
), );
); },
),
builder: (context, ref, child) {
ref.listen(bottomSheetControllerProvider, (previous, next) {
if (sheetController.value != null) {
sheetController.value!.close();
sheetController.value = null;
}
if (next != null) {
final relativeSafeArea = MediaQuery.of(
context,
).relativeSafeArea();
sheetController.value = Scaffold.of(context).showBottomSheet((
context,
) {
bool dismissOnThreshold(
DraggableScrollableNotification notification,
) {
if (notification.extent <= 0.1) {
ref
.read(bottomSheetControllerProvider.notifier)
.dismiss();
return true;
} else {
ref
.read(bottomSheetExtendProvider.notifier)
.add(notification.extent);
}
return false;
}
final sheet = switch (next) {
ViewTabsSheet() =>
NotificationListener<DraggableScrollableNotification>(
key: ValueKey(next),
onNotification: dismissOnThreshold,
child: _ViewTabsSheet(maxChildSize: relativeSafeArea),
),
final EditUrlSheet parameter =>
NotificationListener<DraggableScrollableNotification>(
key: ValueKey(parameter),
onNotification: dismissOnThreshold,
child: _ViewUrlSheet(
initialTabState: parameter.tabState,
maxChildSize: relativeSafeArea,
),
),
};
return sheet;
});
}
});
return child!;
}, },
), ),
floatingActionButton: ReaderAppearanceButton(), floatingActionButton: ReaderAppearanceButton(),
@@ -37,65 +37,8 @@ class DraggableScrollableHeader extends StatefulWidget {
super.key, super.key,
required this.controller, required this.controller,
required this.child, required this.child,
this.velocitySensitivity = 0.35, // Higher = more sensitive to velocity
this.maxVelocity = 2500.0, // Maximum velocity to consider
this.minVelocity = 150.0, // Minimum velocity to trigger momentum
this.animationDuration = const Duration(milliseconds: 300),
this.animationCurve = Curves.decelerate,
this.dragSensitivity = 1.0, // Controls drag responsiveness
}); });
@override
State<DraggableScrollableHeader> createState() =>
_DraggableScrollableHeaderState();
}
class _DraggableScrollableHeaderState extends State<DraggableScrollableHeader>
with TickerProviderStateMixin {
late AnimationController _animationController;
late Animation<double>? _animation;
@override
void initState() {
super.initState();
_animationController = AnimationController(
vsync: this,
duration: widget.animationDuration,
);
}
@override
void dispose() {
_animationController.dispose();
super.dispose();
}
Future<void> _animateToPosition(
double targetSize, {
Duration? customDuration,
}) async {
if (customDuration != null) {
_animationController.duration = customDuration;
} else {
_animationController.duration = widget.animationDuration;
}
_animation = Tween<double>(begin: widget.controller.size, end: targetSize)
.animate(
CurvedAnimation(
parent: _animationController,
curve: widget.animationCurve,
),
);
_animationController.reset();
await _animationController.forward();
_animation!.addListener(() {
widget.controller.jumpTo(_animation!.value);
});
}
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return GestureDetector( return GestureDetector(
+1 -1
View File
@@ -2,7 +2,7 @@ name: weblibre
description: "The Privacy-Focused & AI-Powered Research Browser" description: "The Privacy-Focused & AI-Powered Research Browser"
publish_to: 'none' publish_to: 'none'
resolution: workspace resolution: workspace
version: 0.9.23-alpha-1+26 version: 0.9.23-alpha-2+26
environment: environment:
sdk: '>=3.8.0 <4.0.0' sdk: '>=3.8.0 <4.0.0'