Skip to content

Commit f9a0ee0

Browse files
Merge remote-tracking branch 'origin/master'
2 parents 12073e3 + 23d6cd5 commit f9a0ee0

File tree

3 files changed

+221
-25
lines changed

3 files changed

+221
-25
lines changed

lib/l10n/app_en.arb

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,11 @@
3737
"asc": "Ascending",
3838
"desc": "Descending",
3939
"saveAs": "Save As",
40+
"scrollDirection": "Scroll direction",
41+
"scrollDirectionDefault": "Left to right",
42+
"scrollDirectionRtl": "Right to left",
43+
"scrollDirectionDown": "Top to bottom",
44+
"scrollDirectionUp": "Bottom to top",
4045
"posted": "Posted",
4146
"visible": "Visible",
4247
"yes": "Yes",

lib/l10n/app_zh.arb

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,11 @@
3737
"asc": "升序",
3838
"desc": "降序",
3939
"saveAs": "另存为",
40+
"scrollDirection": "滚动方向",
41+
"scrollDirectionDefault": "从左往右",
42+
"scrollDirectionRtl": "从右往左",
43+
"scrollDirectionDown": "从上往下",
44+
"scrollDirectionUp": "从下往上",
4045
"posted": "上传时间",
4146
"visible": "可见性",
4247
"yes": "是",

lib/viewer/single.dart

Lines changed: 211 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -62,9 +62,158 @@ class _SinglePageViewer extends State<SinglePageViewer>
6262
Object? _error;
6363
bool _inited = false;
6464
bool _showMenu = false;
65+
int _scrollMethod = 0;
66+
double? _sliderValue;
6567
late PhotoViewController _photoViewController;
6668
final LruMap<int, (Uint8List, String?, String)> _imgData =
6769
LruMap(maximumSize: 20);
70+
Axis get _scrollAxis => _scrollMethod >= 2 ? Axis.vertical : Axis.horizontal;
71+
bool get _isReverseScroll => _scrollMethod == 1 || _scrollMethod == 3;
72+
void _showPageSettings(BuildContext context) {
73+
final i18n = AppLocalizations.of(context)!;
74+
final options = [
75+
MapEntry(0, i18n.scrollDirectionDefault),
76+
MapEntry(1, i18n.scrollDirectionRtl),
77+
MapEntry(2, i18n.scrollDirectionDown),
78+
MapEntry(3, i18n.scrollDirectionUp)
79+
];
80+
81+
showModalBottomSheet(
82+
context: context,
83+
builder: (sheetContext) {
84+
var selected = _scrollMethod;
85+
return StatefulBuilder(builder: (context, setSheetState) {
86+
return SafeArea(
87+
child: Column(
88+
mainAxisSize: MainAxisSize.min,
89+
crossAxisAlignment: CrossAxisAlignment.stretch,
90+
children: [
91+
ListTile(
92+
dense: true,
93+
title: Text(i18n.scrollDirection),
94+
),
95+
for (final entry in options)
96+
RadioListTile<int>(
97+
title: Text(entry.value),
98+
value: entry.key,
99+
groupValue: selected,
100+
onChanged: (value) async {
101+
if (value == null) return;
102+
setSheetState(() {
103+
selected = value;
104+
});
105+
Navigator.of(sheetContext).pop();
106+
final saved =
107+
await prefs.setInt("single_viewer_scroll_method", value);
108+
if (!saved) {
109+
_log.warning(
110+
"Failed to save single_viewer_scroll_method.");
111+
return;
112+
}
113+
if (!mounted) return;
114+
setState(() {
115+
_scrollMethod = value;
116+
});
117+
})
118+
]));
119+
});
120+
});
121+
}
122+
123+
Widget _buildBottomBar(BuildContext context) {
124+
if (!_showMenu || _pages == null) return Container();
125+
final theme = Theme.of(context);
126+
final textStyle = theme.textTheme.bodyMedium;
127+
final pagesCount = _pages!.length;
128+
if (pagesCount == 0) return Container();
129+
final double maxPage = (pagesCount - 1).toDouble();
130+
final double currentValue =
131+
(_sliderValue ?? _index.toDouble()).clamp(0.0, maxPage).toDouble();
132+
final displayIndex = currentValue.round().clamp(0, pagesCount - 1);
133+
134+
Slider buildSlider() {
135+
return Slider(
136+
value: currentValue,
137+
min: 0,
138+
max: maxPage,
139+
divisions: pagesCount - 1,
140+
onChanged: (value) {
141+
setState(() {
142+
_sliderValue = value;
143+
});
144+
},
145+
onChangeEnd: (value) {
146+
final target = value.round().clamp(0, pagesCount - 1);
147+
if (target != _index) {
148+
_pageController.animateToPage(target,
149+
duration: const Duration(milliseconds: 200),
150+
curve: Curves.easeInOut);
151+
}
152+
setState(() {
153+
_sliderValue = target.toDouble();
154+
});
155+
},
156+
);
157+
}
158+
159+
return Positioned(
160+
left: 0,
161+
right: 0,
162+
bottom: 0,
163+
child: SafeArea(
164+
minimum: const EdgeInsets.all(16),
165+
child: LayoutBuilder(builder: (context, constraints) {
166+
final isWide = constraints.maxWidth > 480;
167+
return Container(
168+
padding: const EdgeInsets.symmetric(
169+
horizontal: 16, vertical: 12),
170+
decoration: BoxDecoration(
171+
color: theme.colorScheme.surface.withOpacity(0.9),
172+
borderRadius: BorderRadius.circular(16)),
173+
child: isWide
174+
? Row(children: [
175+
Text(
176+
"${displayIndex + 1} / $pagesCount",
177+
style: textStyle,
178+
),
179+
if (pagesCount > 1) ...[
180+
const SizedBox(width: 16),
181+
Expanded(child: buildSlider()),
182+
const SizedBox(width: 16),
183+
] else ...[
184+
const Spacer(),
185+
const SizedBox(width: 16),
186+
],
187+
IconButton(
188+
onPressed: () {
189+
_showPageSettings(context);
190+
},
191+
icon: const Icon(Icons.settings))
192+
])
193+
: Column(
194+
mainAxisSize: MainAxisSize.min,
195+
crossAxisAlignment: CrossAxisAlignment.start,
196+
children: [
197+
if (pagesCount > 1) ...[
198+
buildSlider(),
199+
const SizedBox(height: 12),
200+
],
201+
Row(children: [
202+
Text(
203+
"${displayIndex + 1} / $pagesCount",
204+
style: textStyle,
205+
),
206+
const Spacer(),
207+
IconButton(
208+
onPressed: () {
209+
_showPageSettings(context);
210+
},
211+
icon: const Icon(Icons.settings))
212+
])
213+
],
214+
));
215+
})));
216+
}
68217
void _updatePages() {
69218
if (_data == null) return;
70219
final displayAd = prefs.getBool("displayAd") ?? false;
@@ -76,6 +225,7 @@ class _SinglePageViewer extends State<SinglePageViewer>
76225
_pageController = PageController(initialPage: _index);
77226
_inited = true;
78227
}
228+
_sliderValue = _index.toDouble();
79229
}
80230

81231
@override
@@ -84,6 +234,7 @@ class _SinglePageViewer extends State<SinglePageViewer>
84234
_updatePages();
85235
_files = widget.files;
86236
_back = "/gallery/${widget.gid}";
237+
_scrollMethod = prefs.getInt("single_viewer_scroll_method") ?? 0;
87238
_photoViewController = PhotoViewController();
88239
super.initState();
89240
}
@@ -139,6 +290,8 @@ class _SinglePageViewer extends State<SinglePageViewer>
139290
scrollPhysics: const BouncingScrollPhysics(),
140291
pageController: _pageController,
141292
itemCount: _pages!.length,
293+
scrollDirection: _scrollAxis,
294+
reverse: _isReverseScroll,
142295
builder: (BuildContext context, int index) {
143296
final data = _pages![index];
144297
final f = _files!.files[data.token]!.first;
@@ -160,7 +313,10 @@ class _SinglePageViewer extends State<SinglePageViewer>
160313
);
161314
},
162315
onPageChanged: (index) {
163-
_index = index;
316+
setState(() {
317+
_index = index;
318+
_sliderValue = index.toDouble();
319+
});
164320
SchedulerBinding.instance.addPostFrameCallback((_) {
165321
_onPageChanged(context);
166322
});
@@ -170,26 +326,55 @@ class _SinglePageViewer extends State<SinglePageViewer>
170326

171327
Widget _buildWithKeyboardSupport(BuildContext context,
172328
{required Widget child}) {
329+
void goPrevious() {
330+
if (_index > 0) {
331+
_pageController.previousPage(
332+
duration: const Duration(milliseconds: 200),
333+
curve: Curves.easeInOut);
334+
}
335+
}
336+
337+
void goNext() {
338+
if (_index < _pages!.length - 1) {
339+
_pageController.nextPage(
340+
duration: const Duration(milliseconds: 200),
341+
curve: Curves.easeInOut);
342+
}
343+
}
344+
345+
final bindings = <KeyAction>[];
346+
if (_scrollAxis == Axis.horizontal) {
347+
if (_isReverseScroll) {
348+
bindings.add(KeyAction(
349+
LogicalKeyboardKey.arrowLeft, "next page", () => goNext()));
350+
bindings.add(KeyAction(
351+
LogicalKeyboardKey.arrowRight, "previous page", () => goPrevious()));
352+
} else {
353+
bindings.add(KeyAction(
354+
LogicalKeyboardKey.arrowLeft, "previous page", () => goPrevious()));
355+
bindings.add(KeyAction(
356+
LogicalKeyboardKey.arrowRight, "next page", () => goNext()));
357+
}
358+
} else {
359+
if (_isReverseScroll) {
360+
bindings.add(KeyAction(
361+
LogicalKeyboardKey.arrowUp, "next page", () => goNext()));
362+
bindings.add(KeyAction(
363+
LogicalKeyboardKey.arrowDown, "previous page", () => goPrevious()));
364+
} else {
365+
bindings.add(KeyAction(
366+
LogicalKeyboardKey.arrowUp, "previous page", () => goPrevious()));
367+
bindings.add(KeyAction(
368+
LogicalKeyboardKey.arrowDown, "next page", () => goNext()));
369+
}
370+
}
371+
372+
bindings.add(KeyAction(LogicalKeyboardKey.backspace, "back", () {
373+
context.canPop() ? context.pop() : context.go(_back);
374+
}));
375+
173376
return KeyboardWidget(
174-
bindings: [
175-
KeyAction(LogicalKeyboardKey.arrowLeft, "previous page", () {
176-
if (_index > 0) {
177-
_pageController.previousPage(
178-
duration: const Duration(milliseconds: 200),
179-
curve: Curves.easeInOut);
180-
}
181-
}),
182-
KeyAction(LogicalKeyboardKey.arrowRight, "next page", () {
183-
if (_index < _pages!.length - 1) {
184-
_pageController.nextPage(
185-
duration: const Duration(milliseconds: 200),
186-
curve: Curves.easeInOut);
187-
}
188-
}),
189-
KeyAction(LogicalKeyboardKey.backspace, "back", () {
190-
context.canPop() ? context.pop() : context.go(_back);
191-
}),
192-
],
377+
bindings: bindings,
193378
child: child,
194379
);
195380
}
@@ -254,11 +439,11 @@ class _SinglePageViewer extends State<SinglePageViewer>
254439
title: i18n.saveAs,
255440
callback: () {
256441
try {
257-
platformPath.saveFile(
258-
basenameWithoutExtension(_pages![_index].name),
259-
fmt.toMimeType(),
260-
data,
261-
dir: isAndroid ? widget.gid!.toString() : "");
442+
platformPath.saveFile(
443+
basenameWithoutExtension(_pages![_index].name),
444+
fmt.toMimeType(),
445+
data,
446+
dir: isAndroid ? widget.gid.toString() : "");
262447
} catch (err, stack) {
263448
_log.warning("Failed to save image: $err\n$stack");
264449
}
@@ -360,6 +545,7 @@ class _SinglePageViewer extends State<SinglePageViewer>
360545
body: Stack(children: [
361546
_buildViewer(context),
362547
_buildTopAppBar(context),
548+
_buildBottomBar(context),
363549
]),
364550
);
365551
}

0 commit comments

Comments
 (0)