原生epub阅读

This commit is contained in:
DelLevin-Home
2026-06-27 14:06:44 +08:00
parent 5acbc3a602
commit d54e33e4cf
177 changed files with 6500 additions and 93933 deletions

View File

@@ -0,0 +1,74 @@
import 'package:flutter/material.dart';
/// A compact card-style stepper for an integer value.
class IntegerStepper extends StatelessWidget {
const IntegerStepper({
super.key,
required this.label,
required this.value,
required this.min,
required this.max,
required this.step,
required this.onChanged,
});
final String label;
final int value;
final int min;
final int max;
final int step;
final ValueChanged<int> onChanged;
@override
Widget build(BuildContext context) {
final colorScheme = Theme.of(context).colorScheme;
return Container(
padding: const EdgeInsets.fromLTRB(12, 8, 8, 8),
decoration: BoxDecoration(
color: colorScheme.surfaceContainerHighest,
borderRadius: BorderRadius.circular(12),
border: Border.all(color: colorScheme.outlineVariant, width: 1.5),
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisSize: MainAxisSize.min,
children: [
Text(
label,
style: TextStyle(fontSize: 12, color: colorScheme.onSurfaceVariant),
),
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
IconButton(
icon: const Icon(Icons.remove_outlined, size: 16),
onPressed: value > min ? () => onChanged(value - step) : null,
padding: EdgeInsets.zero,
constraints: const BoxConstraints(minWidth: 28, minHeight: 28),
visualDensity: VisualDensity.compact,
color: colorScheme.primary,
disabledColor: colorScheme.outline,
),
Text(
'$value',
style: TextStyle(
fontSize: 14,
color: colorScheme.onSurfaceVariant,
),
),
IconButton(
icon: const Icon(Icons.add_outlined, size: 16),
onPressed: value < max ? () => onChanged(value + step) : null,
padding: EdgeInsets.zero,
constraints: const BoxConstraints(minWidth: 28, minHeight: 28),
visualDensity: VisualDensity.compact,
color: colorScheme.primary,
disabledColor: colorScheme.outline,
),
],
),
],
),
);
}
}

View File

@@ -0,0 +1,66 @@
import 'package:flutter/material.dart';
/// A horizontal scale slider with small/large "A" tap targets on either side.
///
/// The slider range is fixed to [0.5, 2.5] with 0.1 increments.
/// Tapping the letter glyphs nudges the value by 0.1 in the respective
/// direction; the glyph is greyed-out when the limit is reached.
class ReaderScaleSlider extends StatelessWidget {
const ReaderScaleSlider({
super.key,
required this.value,
required this.onChanged,
});
final double value;
final ValueChanged<double> onChanged;
static const double _min = 0.5;
static const double _max = 2.5;
static const double _nudge = 0.1;
@override
Widget build(BuildContext context) {
final color = Theme.of(context).colorScheme.onSurfaceVariant;
final disabledColor = Theme.of(context).colorScheme.outline;
return Row(
children: [
GestureDetector(
onTap: value > _min
? () => onChanged((value - _nudge).clamp(_min, _max))
: null,
child: Text(
'A',
style: TextStyle(
fontSize: 12,
color: value > _min ? color : disabledColor,
),
),
),
Expanded(
child: Slider(
value: value,
min: _min,
max: _max,
divisions: 20,
label: value.toStringAsFixed(1),
onChanged: onChanged,
),
),
GestureDetector(
onTap: value < _max
? () => onChanged((value + _nudge).clamp(_min, _max))
: null,
child: Text(
'A',
style: TextStyle(
fontSize: 22,
color: value < _max ? color : disabledColor,
),
),
),
],
);
}
}