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
Widget build(BuildContext context, WidgetRef ref) {
final scaffoldKey = useMemoized(() => GlobalKey<ScaffoldState>());
final eventService = ref.watch(eventServiceProvider);
final sheetDisplayed = ref.watch(
@@ -106,56 +104,6 @@ class BrowserScreen extends HookConsumerWidget {
);
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(
//We need this for BackButtonListener to work downstream
@@ -164,7 +112,6 @@ class BrowserScreen extends HookConsumerWidget {
child: Theme(
data: themeData,
child: Scaffold(
key: scaffoldKey,
extendBodyBehindAppBar: tabInFullScreen,
bottomNavigationBar: HookConsumer(
builder: (context, ref, child) {
@@ -265,161 +212,221 @@ class BrowserScreen extends HookConsumerWidget {
);
},
),
body: DragTarget<TabDragData>(
onMove: (details) {
ref
.read(willAcceptDropProvider.notifier)
.setData(DeleteDropData(details.data.tabId));
},
onLeave: (data) {
ref.read(willAcceptDropProvider.notifier).clear();
},
onAcceptWithDetails: (details) async {
ref.read(willAcceptDropProvider.notifier).clear();
await ref
.read(tabRepositoryProvider.notifier)
.closeTab(details.data.tabId);
body: Consumer(
child: DragTarget<TabDragData>(
onMove: (details) {
ref
.read(willAcceptDropProvider.notifier)
.setData(DeleteDropData(details.data.tabId));
},
onLeave: (data) {
ref.read(willAcceptDropProvider.notifier).clear();
},
onAcceptWithDetails: (details) async {
ref.read(willAcceptDropProvider.notifier).clear();
await ref
.read(tabRepositoryProvider.notifier)
.closeTab(details.data.tabId);
if (context.mounted) {
ui_helper.showTabUndoClose(
context,
ref.read(tabRepositoryProvider.notifier).undoClose,
);
}
},
builder: (context, _, _) {
return OverlayPortal(
controller: overlayController,
overlayChildBuilder: (context) {
return overlayBuilder!.call(context);
},
child: Listener(
onPointerDown: sheetDisplayed
? (_) {
if (context.mounted) {
ui_helper.showTabUndoClose(
context,
ref.read(tabRepositoryProvider.notifier).undoClose,
);
}
},
builder: (context, _, _) {
return OverlayPortal(
controller: overlayController,
overlayChildBuilder: (context) {
return overlayBuilder!.call(context);
},
child: Listener(
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
.read(bottomSheetControllerProvider.notifier)
.dismiss();
return true;
}
: null,
child: BackButtonListener(
onBackButtonPressed: () async {
final tabState = ref.read(selectedTabStateProvider);
final tabCount = ref.read(
tabListProvider.select((tabs) => tabs.value.length),
);
if (overlayBuilder != null) {
ref
.read(overlayControllerProvider.notifier)
.dismiss();
return true;
}
//Don't do anything if a child route is active
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) {
if (tabState?.isFullScreen == true) {
await ref
.read(tabRepositoryProvider.notifier)
.closeTab(tabState.id);
.read(selectedTabSessionNotifierProvider)
.exitFullscreen();
return true;
}
if (context.mounted) {
ui_helper.showTabUndoClose(
context,
ref
.read(tabRepositoryProvider.notifier)
.undoClose,
);
}
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 {
} 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;
}
} else {
lastBackButtonPress.value = DateTime.now();
ui_helper.showTabBackButtonMessage(
context,
tabCount,
_backButtonPressTimeout,
);
return true;
}
},
child: _BrowserView(
sheetDisplayed: sheetDisplayed,
isFullscreen: tabInFullScreen,
if (lastBackButtonPress.value != null &&
DateTime.now().difference(
lastBackButtonPress.value!,
) <
_backButtonPressTimeout) {
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(),
@@ -37,65 +37,8 @@ class DraggableScrollableHeader extends StatefulWidget {
super.key,
required this.controller,
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
Widget build(BuildContext context) {
return GestureDetector(
+1 -1
View File
@@ -2,7 +2,7 @@ name: weblibre
description: "The Privacy-Focused & AI-Powered Research Browser"
publish_to: 'none'
resolution: workspace
version: 0.9.23-alpha-1+26
version: 0.9.23-alpha-2+26
environment:
sdk: '>=3.8.0 <4.0.0'