Advertisement
Guest User

Untitled

a guest
Jan 26th, 2024
124
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 7.45 KB | None | 0 0
  1. import 'dart:ui';
  2.  
  3. import 'package:flutter/material.dart';
  4. import 'package:flutter/scheduler.dart';
  5. import 'package:lucide_icons/lucide_icons.dart';
  6. import 'package:flutter_gen/gen_l10n/app_localizations.dart';
  7. import 'package:pomotimer/app_text_style.dart';
  8.  
  9. import '../../generated/l10n.dart';
  10.  
  11. class HomePage extends StatelessWidget {
  12. const HomePage({super.key});
  13.  
  14. @override
  15. Widget build(BuildContext context) {
  16. var theme = Theme.of(context);
  17.  
  18. return Scaffold(
  19. backgroundColor: theme.scaffoldBackgroundColor,
  20. appBar: TopBar(theme),
  21. body: const HomeBody(),
  22. );
  23. }
  24. }
  25.  
  26. class TopBar extends AppBar {
  27. TopBar(ThemeData theme, {super.key})
  28. : super(
  29. backgroundColor: Colors.transparent,
  30. leading: Container(
  31. margin: const EdgeInsets.only(left: 15),
  32. child: IconButton(
  33. iconSize: 25,
  34. tooltip: S.current.settingBtnTooltip,
  35. icon: Icon(LucideIcons.settings,
  36. color: theme.colorScheme.onBackground),
  37. onPressed: () {
  38. // TODO 跳转到设置
  39. },
  40. ),
  41. ));
  42. }
  43.  
  44. class HomeBody extends StatelessWidget {
  45. const HomeBody({super.key});
  46.  
  47. @override
  48. Widget build(BuildContext context) {
  49. return Container(
  50. constraints: const BoxConstraints.expand(),
  51. child: Center(
  52. child: Container(
  53. constraints: const BoxConstraints.tightFor(width: 300, height: 500),
  54. child: const TimerController()),
  55. ),
  56. );
  57. }
  58. }
  59.  
  60. class TimerController extends StatefulWidget {
  61. const TimerController({super.key});
  62.  
  63. @override
  64. State<StatefulWidget> createState() => _TimerControllerState();
  65. }
  66.  
  67. class _TimerControllerState extends State<TimerController> {
  68. var selected = 0;
  69.  
  70. @override
  71. Widget build(BuildContext context) {
  72. var theme = Theme.of(context);
  73.  
  74. return Column(
  75. mainAxisAlignment: MainAxisAlignment.center,
  76. children: [
  77. AttributeSwitcher(
  78. selected: selected,
  79. onSelected: (selected) => debugPrint('selected: $selected'),
  80. ),
  81. ],
  82. );
  83. }
  84. }
  85.  
  86. class AttributeSwitcher extends StatefulWidget {
  87. AttributeSwitcher(
  88. {super.key, required this.selected, required this.onSelected}) {
  89. if (selected < 0 || selected > 2) {
  90. throw Exception('selected must be in [0, 2]');
  91. }
  92. }
  93.  
  94. final int selected;
  95. final void Function(int) onSelected;
  96.  
  97. @override
  98. State<AttributeSwitcher> createState() => _AttributeSwitcherState();
  99. }
  100.  
  101. class _AttributeSwitcherState extends State<AttributeSwitcher> {
  102.  
  103. int _selected = 0;
  104.  
  105. @override
  106. void initState() {
  107. super.initState();
  108. _selected = widget.selected;
  109. }
  110.  
  111. void _updateSelected(int selected) {
  112. setState(() {
  113. _selected = selected;
  114. });
  115. widget.onSelected(selected);
  116. }
  117.  
  118. Widget _createSticky({
  119. required double posX,
  120. required double posY,
  121. required double height,
  122. required double width,
  123. required ColorScheme colorScheme,
  124. }) {
  125. return AnimatedPositioned(
  126. duration: const Duration(milliseconds: 500),
  127. top: posY,
  128. left: posX,
  129. child: UnconstrainedBox(
  130. child: AnimatedContainer(
  131. height: height,
  132. width: width,
  133. decoration: BoxDecoration(
  134. color: colorScheme.primaryContainer,
  135. borderRadius: BorderRadius.circular(80),
  136. ),
  137. duration: const Duration(milliseconds: 500),
  138. ),
  139. ));
  140. }
  141.  
  142. OverlayEntry _createRow({required List<GlobalKey> btnKeys}) {
  143. return OverlayEntry(
  144. builder: (context) => Row(
  145. mainAxisSize: MainAxisSize.min,
  146. mainAxisAlignment: MainAxisAlignment.spaceBetween,
  147. crossAxisAlignment: CrossAxisAlignment.center,
  148. children: [
  149. AttributeBtn(
  150. btnKey: btnKeys[0], text: '专注', onPressed: () => _updateSelected(0)),
  151. const AttributeSplitter(),
  152. AttributeBtn(
  153. btnKey: btnKeys[1],
  154. text: '小休息',
  155. onPressed: () => _updateSelected(1),
  156. ),
  157. const AttributeSplitter(),
  158. AttributeBtn(
  159. btnKey: btnKeys[2],
  160. text: '大休息',
  161. onPressed: () => _updateSelected(2),
  162. ),
  163. ],
  164. ),
  165. );
  166. }
  167.  
  168. @override
  169. Widget build(BuildContext context) {
  170. ThemeData theme = Theme.of(context);
  171. ColorScheme colorScheme = theme.colorScheme;
  172. TextTheme textTheme = theme.textTheme;
  173.  
  174. List<GlobalKey> btnKeys = [GlobalKey(), GlobalKey(), GlobalKey()];
  175. List<Offset> btnPos = [];
  176. List<Size> btnSize = [];
  177.  
  178. GlobalKey overlayKey = GlobalKey();
  179.  
  180. OverlayEntry row = _createRow(btnKeys: btnKeys);
  181.  
  182. SchedulerBinding.instance.addPostFrameCallback((_) {
  183. OverlayState state = overlayKey.currentState as OverlayState;
  184.  
  185. if (overlayKey.currentContext == null) {
  186. throw Exception('overlayKey.currentContext is null');
  187. }
  188.  
  189. var overlayPos =
  190. (overlayKey.currentContext?.findRenderObject() as RenderBox)
  191. .localToGlobal(Offset.zero);
  192.  
  193. for (var element in btnKeys) {
  194. if (element.currentContext == null) {
  195. throw Exception('element.currentContext is null');
  196. }
  197. var readerBox = element.currentContext?.findRenderObject() as RenderBox;
  198. var readerSize = readerBox.size;
  199. var readerPos =
  200. readerBox.localToGlobal(Offset(-overlayPos.dx, -overlayPos.dy));
  201.  
  202. btnPos.add(readerPos);
  203. btnSize.add(readerSize);
  204. }
  205.  
  206. state.insert(OverlayEntry(builder: (context) {
  207. return _createSticky(
  208. posX: btnPos[_selected].dx,
  209. posY: btnPos[_selected].dy,
  210. height: btnSize[_selected].height,
  211. width: btnSize[_selected].width,
  212. colorScheme: colorScheme,
  213. );
  214. }), below: row);
  215. });
  216.  
  217. return SizedBox(
  218. width: 300,
  219. height: 50,
  220. child: Overlay(key: overlayKey, initialEntries: [row]),
  221. );
  222. }
  223. }
  224.  
  225. class AttributeBtn extends StatelessWidget {
  226. const AttributeBtn(
  227. {super.key, this.btnKey, this.onPressed, required this.text});
  228.  
  229. final void Function()? onPressed;
  230. final String text;
  231. final GlobalKey? btnKey;
  232.  
  233. @override
  234. Widget build(BuildContext context) {
  235. ThemeData theme = Theme.of(context);
  236. TextTheme textTheme = theme.textTheme;
  237.  
  238. return Container(
  239. margin: const EdgeInsets.symmetric(horizontal: 5),
  240. child: ConstrainedBox(
  241. constraints: const BoxConstraints(
  242. maxHeight: double.infinity,
  243. minHeight: 0,
  244. maxWidth: double.infinity,
  245. minWidth: 70,
  246. ),
  247. child: TextButton(
  248. key: btnKey,
  249. onPressed: onPressed,
  250. child: Text(
  251. text,
  252. style: AppTextStyle.generateWithTextStyle(textTheme.bodyLarge!),
  253. )),
  254. ),
  255. );
  256. }
  257. }
  258.  
  259. class AttributeSplitter extends StatelessWidget {
  260. const AttributeSplitter({super.key});
  261.  
  262. @override
  263. Widget build(BuildContext context) {
  264. ThemeData theme = Theme.of(context);
  265. ColorScheme colorScheme = theme.colorScheme;
  266.  
  267. return Container(
  268. decoration: BoxDecoration(
  269. color: colorScheme.surface,
  270. shape: BoxShape.circle,
  271. ),
  272. height: 4,
  273. width: 4,
  274. );
  275. }
  276. }
  277.  
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement