Skip to content

Commit 1a3e8f5

Browse files
authored
feat: draggable thread navigation buttons (#834)
1 parent bc2f70e commit 1a3e8f5

10 files changed

Lines changed: 272 additions & 150 deletions

File tree

lib/cubits/cubits.dart

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ export 'search/search_cubit.dart';
1616
export 'split_view/split_view_cubit.dart';
1717
export 'submit/submit_cubit.dart';
1818
export 'tab/tab_cubit.dart';
19+
export 'thread_navigation_button/thread_navigation_button_cubit.dart';
1920
export 'time_machine/time_machine_cubit.dart';
2021
export 'tips/tips_cubit.dart';
2122
export 'user/user_cubit.dart';
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
import 'dart:ui';
2+
3+
import 'package:equatable/equatable.dart';
4+
import 'package:hydrated_bloc/hydrated_bloc.dart';
5+
6+
part 'thread_navigation_button_state.dart';
7+
8+
class ThreadNavigationButtonCubit
9+
extends HydratedCubit<ThreadNavigationButtonState> {
10+
ThreadNavigationButtonCubit() : super(ThreadNavigationButtonState.init());
11+
12+
void updateButtonPosition(double dx, double dy) =>
13+
emit(state.copyWith(dx: dx, dy: dy));
14+
15+
static const String _buttonPositionDxKey = 'buttonPositionDx';
16+
static const String _buttonPositionDyKey = 'buttonPositionDy';
17+
18+
@override
19+
ThreadNavigationButtonState? fromJson(Map<String, dynamic> json) {
20+
return state.copyWith(
21+
dx: json[_buttonPositionDxKey] as double? ?? defaultOffset.dx,
22+
dy: json[_buttonPositionDyKey] as double? ?? defaultOffset.dy,
23+
);
24+
}
25+
26+
@override
27+
Map<String, dynamic>? toJson(ThreadNavigationButtonState state) {
28+
return <String, dynamic>{
29+
_buttonPositionDxKey: state.dx,
30+
_buttonPositionDyKey: state.dy,
31+
};
32+
}
33+
}
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
part of 'thread_navigation_button_cubit.dart';
2+
3+
const Offset defaultOffset = Offset(16, 180);
4+
5+
class ThreadNavigationButtonState extends Equatable {
6+
const ThreadNavigationButtonState({required this.dx, required this.dy});
7+
8+
ThreadNavigationButtonState.init()
9+
: dx = defaultOffset.dx,
10+
dy = defaultOffset.dy;
11+
12+
final double dx;
13+
final double dy;
14+
15+
ThreadNavigationButtonState copyWith({
16+
required double dx,
17+
required double dy,
18+
}) {
19+
return ThreadNavigationButtonState(dx: dx, dy: dy);
20+
}
21+
22+
@override
23+
List<Object?> get props => <Object?>[dx, dy];
24+
}

lib/main.dart

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -221,6 +221,10 @@ class HackiApp extends StatelessWidget {
221221
lazy: false,
222222
create: (BuildContext context) => TipsCubit(),
223223
),
224+
BlocProvider<ThreadNavigationButtonCubit>(
225+
lazy: false,
226+
create: (_) => ThreadNavigationButtonCubit(),
227+
),
224228
],
225229
child: BlocConsumer<PreferenceCubit, PreferenceState>(
226230
listenWhen: (PreferenceState previous, PreferenceState current) =>
@@ -344,9 +348,17 @@ class HackiApp extends StatelessWidget {
344348
Positioned.fill(child: child!),
345349
DraggableFloatingButton(
346350
onTap: () {
347-
router.push(
348-
Paths.logs.landing,
349-
);
351+
final bool isOnLogsScreen =
352+
router.state.fullPath
353+
?.contains(
354+
Paths.logs.landing,
355+
) ??
356+
false;
357+
if (!isOnLogsScreen) {
358+
router.push(
359+
Paths.logs.landing,
360+
);
361+
}
350362
},
351363
child: Icon(
352364
Icons.bug_report,

lib/screens/item/item_screen.dart

Lines changed: 32 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -252,6 +252,9 @@ class _ItemScreenState extends State<ItemScreen>
252252
isWebViewBottomSheetEnabled &&
253253
widget.item is Story &&
254254
widget.item.url.isNotEmpty;
255+
256+
/// Read here instead of inside [FloatingSkipButtons] since [Scaffold]
257+
/// strips the bottom view inset from the [MediaQuery] of its body.
255258
return MultiBlocListener(
256259
listeners: <BlocListener<dynamic, dynamic>>[
257260
BlocListener<PostCubit, PostState>(
@@ -367,11 +370,7 @@ class _ItemScreenState extends State<ItemScreen>
367370
.read<PreferenceCubit>()
368371
.state
369372
.areSkipButtonsEnabled)
370-
const Positioned(
371-
right: Dimens.pt12,
372-
bottom: Dimens.pt36,
373-
child: FloatingSkipButtons(),
374-
),
373+
const FloatingSkipButtons(),
375374
if (shouldShowWebViewBottomSheet)
376375
ItemScreenWebView(
377376
url: widget.item.url,
@@ -383,7 +382,6 @@ class _ItemScreenState extends State<ItemScreen>
383382
right: Dimens.zero,
384383
child: Material(
385384
child: ReplyBox(
386-
splitViewEnabled: true,
387385
focusNode: focusNode,
388386
textEditingController: commentEditingController,
389387
onSendTapped: onSendTapped,
@@ -400,7 +398,7 @@ class _ItemScreenState extends State<ItemScreen>
400398
child: ScrollsToTop(
401399
child: Scaffold(
402400
extendBodyBehindAppBar: true,
403-
resizeToAvoidBottomInset: true,
401+
resizeToAvoidBottomInset: false,
404402
appBar: CustomAppBar(
405403
context: context,
406404
backgroundColor: Theme.of(
@@ -453,15 +451,6 @@ class _ItemScreenState extends State<ItemScreen>
453451
shouldMarkNewComment: widget.shouldMarkNewComment,
454452
),
455453
),
456-
if (context
457-
.read<PreferenceCubit>()
458-
.state
459-
.areSkipButtonsEnabled)
460-
const Positioned(
461-
right: Dimens.pt12,
462-
bottom: Dimens.pt48,
463-
child: FloatingSkipButtons(),
464-
),
465454
const Positioned(
466455
left: Dimens.zero,
467456
right: Dimens.zero,
@@ -473,22 +462,43 @@ class _ItemScreenState extends State<ItemScreen>
473462
),
474463
],
475464
),
476-
bottomSheet: ReplyBox(
477-
textEditingController: commentEditingController,
478-
focusNode: focusNode,
479-
onSendTapped: onSendTapped,
480-
onChanged: context.read<EditCubit>().onTextChanged,
481-
),
482465
),
483466
onScrollsToTop: (_) =>
484467
context.read<CommentsCubit>().scrollTo(index: 0),
485468
),
486469
),
470+
if (context.read<PreferenceCubit>().state.areSkipButtonsEnabled)
471+
const FloatingSkipButtons(),
487472
if (shouldShowWebViewBottomSheet)
488473
ItemScreenWebView(
489474
url: widget.item.url,
490475
controller: _webViewController,
491476
),
477+
Positioned(
478+
bottom: Dimens.zero,
479+
left: Dimens.zero,
480+
right: Dimens.zero,
481+
child: DecoratedBox(
482+
decoration: const BoxDecoration(
483+
boxShadow: <BoxShadow>[
484+
BoxShadow(
485+
color: Palette.black12,
486+
blurRadius: Dimens.pt24,
487+
offset: Offset(0, -6), // push the shadow upward
488+
),
489+
],
490+
),
491+
child: Material(
492+
color: Theme.of(context).colorScheme.surfaceContainerLow,
493+
child: ReplyBox(
494+
focusNode: focusNode,
495+
textEditingController: commentEditingController,
496+
onSendTapped: onSendTapped,
497+
onChanged: context.read<EditCubit>().onTextChanged,
498+
),
499+
),
500+
),
501+
),
492502
],
493503
),
494504
);

lib/screens/item/widgets/custom_floating_action_button.dart

Lines changed: 0 additions & 113 deletions
This file was deleted.

0 commit comments

Comments
 (0)