mohsen zamani
2 years ago
20 changed files with 470 additions and 1908 deletions
-
27lib/application/notifiers/puzzle_notifier.dart
-
7lib/application/states/puzzle_state.dart
-
160lib/application/states/puzzle_state.freezed.dart
-
9lib/screens/photo/photo_screen.dart
-
28lib/screens/photo/photo_screen_large.dart
-
200lib/screens/photo/photo_screen_medium.dart
-
212lib/screens/photo/photo_screen_small.dart
-
59lib/screens/puzzle/puzzle_screen_large.dart
-
8lib/screens/puzzle/puzzle_starter_screen.dart
-
635lib/screens/solo/solo_large_screen_prev.dart
-
63lib/screens/solo/solo_screen.dart
-
164lib/screens/solo/solo_screen_large.dart
-
148lib/screens/solo/solo_screen_medium.dart
-
145lib/screens/solo/solo_screen_small.dart
-
198lib/utils/toast.dart
-
49lib/utils/utils.dart
-
29lib/widgets/puzzle_widgets/timer_test_screen.dart
-
3lib/widgets/solo_screen/count_down_timer_widget.dart
-
12lib/widgets/solo_screen/game_button_widget.dart
-
16lib/widgets/solo_screen/game_button_widget/puzzle_game_button.dart
@ -1,200 +0,0 @@ |
|||||
import 'package:flutter/material.dart'; |
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart'; |
|
||||
import 'package:image_picker/image_picker.dart'; |
|
||||
import 'package:my_flutter_puzzle/application/states/image_splitter_state.dart'; |
|
||||
import 'package:my_flutter_puzzle/application/states/puzzle_state.dart'; |
|
||||
import 'package:my_flutter_puzzle/models/puzzle_data.dart'; |
|
||||
import 'package:my_flutter_puzzle/providers.dart'; |
|
||||
import 'package:my_flutter_puzzle/res/palette.dart'; |
|
||||
import 'package:my_flutter_puzzle/res/puzzle_constants.dart'; |
|
||||
import 'package:my_flutter_puzzle/utils/color_brightness.dart'; |
|
||||
import 'package:my_flutter_puzzle/utils/puzzle_solver.dart'; |
|
||||
import 'package:my_flutter_puzzle/widgets/photo_screen/image_viewer.dart'; |
|
||||
import 'package:my_flutter_puzzle/widgets/photo_screen/pick_image_button.dart'; |
|
||||
import 'package:my_flutter_puzzle/widgets/solo_screen/countdown_overlay.dart'; |
|
||||
import 'package:my_flutter_puzzle/widgets/solo_screen/solo_screen_export.dart'; |
|
||||
import 'package:palette_generator/palette_generator.dart'; |
|
||||
import 'package:rive/rive.dart'; |
|
||||
|
|
||||
class PhotoScreenMedium extends ConsumerStatefulWidget { |
|
||||
const PhotoScreenMedium({ |
|
||||
required this.solverClient, |
|
||||
required this.initialPuzzleData, |
|
||||
required this.puzzleSize, |
|
||||
required this.riveController, |
|
||||
Key? key, |
|
||||
}) : super(key: key); |
|
||||
|
|
||||
final PuzzleSolverClient solverClient; |
|
||||
final PuzzleData initialPuzzleData; |
|
||||
final int puzzleSize; |
|
||||
final RiveAnimationController riveController; |
|
||||
|
|
||||
@override |
|
||||
ConsumerState<ConsumerStatefulWidget> createState() => _PhotoScreenMediumState(); |
|
||||
} |
|
||||
|
|
||||
class _PhotoScreenMediumState extends ConsumerState<PhotoScreenMedium> { |
|
||||
late final PuzzleSolverClient _solverClient; |
|
||||
late final int _puzzleSize; |
|
||||
late final PuzzleData _initialPuzzleData; |
|
||||
late final RiveAnimationController _riveController; |
|
||||
bool _isStartPressed = false; |
|
||||
|
|
||||
final _imagePicker = ImagePicker(); |
|
||||
|
|
||||
List<Image>? _previousImages; |
|
||||
Image? _previousImage; |
|
||||
PaletteGenerator? _previousPalette; |
|
||||
|
|
||||
@override |
|
||||
void initState() { |
|
||||
_solverClient = widget.solverClient; |
|
||||
_puzzleSize = widget.puzzleSize; |
|
||||
_initialPuzzleData = widget.initialPuzzleData; |
|
||||
_riveController = widget.riveController; |
|
||||
super.initState(); |
|
||||
} |
|
||||
|
|
||||
@override |
|
||||
Widget build(BuildContext context) { |
|
||||
ref.listen(puzzleNotifierProvider(_solverClient), (previous, PuzzleState next) { |
|
||||
if (next is PuzzleSolved) {} |
|
||||
if (next is PuzzleInitializing) { |
|
||||
setState(() { |
|
||||
_isStartPressed = true; |
|
||||
}); |
|
||||
} |
|
||||
}); |
|
||||
|
|
||||
ref.listen(imageSplitterNotifierProvider, (previous, next) { |
|
||||
if (next is ImageSplitterComplete) { |
|
||||
setState(() { |
|
||||
_previousImages = next.images; |
|
||||
_previousImage = next.image; |
|
||||
_previousPalette = next.palette; |
|
||||
}); |
|
||||
} |
|
||||
}); |
|
||||
|
|
||||
var fontSize = 64.0; |
|
||||
var boardSize = 400.0; |
|
||||
|
|
||||
var spacing = 5; |
|
||||
var eachBoxSize = (boardSize / _puzzleSize) - (spacing * (_puzzleSize - 1)); |
|
||||
|
|
||||
return Stack( |
|
||||
children: [ |
|
||||
Scaffold( |
|
||||
backgroundColor: Palette.blue.darken(0.3), |
|
||||
body: Stack( |
|
||||
children: [ |
|
||||
Row( |
|
||||
children: [ |
|
||||
const Spacer(), |
|
||||
AnimatedDash( |
|
||||
boardSize: boardSize / 1.5, |
|
||||
riveController: _riveController, |
|
||||
onInit: (_) => setState(() {}), |
|
||||
), |
|
||||
], |
|
||||
), |
|
||||
SingleChildScrollView( |
|
||||
child: Column( |
|
||||
mainAxisAlignment: MainAxisAlignment.center, |
|
||||
// crossAxisAlignment: CrossAxisAlignment.start, |
|
||||
children: [ |
|
||||
Row(), |
|
||||
const SizedBox(height: 16), |
|
||||
MovesTilesWidget(solverClient: _solverClient), |
|
||||
const SizedBox(height: 16), |
|
||||
const TimerWidget(fontSize: 36), |
|
||||
const SizedBox(height: 36), |
|
||||
Consumer( |
|
||||
builder: (context, ref, child) { |
|
||||
final state = ref.watch(imageSplitterNotifierProvider); |
|
||||
return state.maybeWhen( |
|
||||
() => PuzzleWidget( |
|
||||
solverClient: _solverClient, |
|
||||
boardSize: boardSize, |
|
||||
eachBoxSize: eachBoxSize, |
|
||||
initialPuzzleData: _initialPuzzleData, |
|
||||
fontSize: fontSize, |
|
||||
images: _previousImages, |
|
||||
kInitialSpeed: kInitialSpeed, |
|
||||
), |
|
||||
complete: (image, images, palette) { |
|
||||
_previousImages = images; |
|
||||
_previousImage = image; |
|
||||
_previousPalette = palette; |
|
||||
|
|
||||
return PuzzleWidget( |
|
||||
solverClient: _solverClient, |
|
||||
boardSize: boardSize, |
|
||||
eachBoxSize: eachBoxSize, |
|
||||
initialPuzzleData: _initialPuzzleData, |
|
||||
fontSize: fontSize, |
|
||||
images: images, |
|
||||
kInitialSpeed: kInitialSpeed, |
|
||||
); |
|
||||
}, |
|
||||
orElse: () => PuzzleWidget( |
|
||||
solverClient: _solverClient, |
|
||||
boardSize: boardSize, |
|
||||
eachBoxSize: eachBoxSize, |
|
||||
initialPuzzleData: _initialPuzzleData, |
|
||||
fontSize: fontSize, |
|
||||
images: _previousImages, |
|
||||
kInitialSpeed: kInitialSpeed, |
|
||||
), |
|
||||
); |
|
||||
}, |
|
||||
), |
|
||||
const SizedBox(height: 36), |
|
||||
Row( |
|
||||
mainAxisSize: MainAxisSize.min, |
|
||||
children: [ |
|
||||
GameButtonWidget( |
|
||||
solverClient: _solverClient, |
|
||||
initialPuzzleData: _initialPuzzleData, |
|
||||
), |
|
||||
const SizedBox(width: 36), |
|
||||
PickImageButton( |
|
||||
text: 'Pick Image', |
|
||||
onTap: ref.read(imageSplitterNotifierProvider) is ImageSplitterComplete |
|
||||
? () => ref.read(imageSplitterNotifierProvider.notifier).generateImages( |
|
||||
picker: _imagePicker, |
|
||||
puzzleSize: _puzzleSize, |
|
||||
) |
|
||||
: null, |
|
||||
), |
|
||||
], |
|
||||
), |
|
||||
const SizedBox(height: 36), |
|
||||
ImageViewer( |
|
||||
imagePicker: _imagePicker, |
|
||||
puzzleSize: _puzzleSize, |
|
||||
previousImage: _previousImage, |
|
||||
previousPalette: _previousPalette, |
|
||||
imageSize: 150, |
|
||||
), |
|
||||
], |
|
||||
), |
|
||||
), |
|
||||
], |
|
||||
), |
|
||||
), |
|
||||
CountdownOverlay( |
|
||||
isStartPressed: _isStartPressed, |
|
||||
onFinish: () { |
|
||||
ref.read(timerNotifierProvider.notifier).startTimer(); |
|
||||
setState(() { |
|
||||
_isStartPressed = false; |
|
||||
}); |
|
||||
}, |
|
||||
initialSpeed: kInitialSpeed, |
|
||||
), |
|
||||
], |
|
||||
); |
|
||||
} |
|
||||
} |
|
@ -1,212 +0,0 @@ |
|||||
import 'package:flutter/material.dart'; |
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart'; |
|
||||
import 'package:image_picker/image_picker.dart'; |
|
||||
import 'package:my_flutter_puzzle/application/states/image_splitter_state.dart'; |
|
||||
import 'package:my_flutter_puzzle/application/states/puzzle_state.dart'; |
|
||||
import 'package:my_flutter_puzzle/models/puzzle_data.dart'; |
|
||||
import 'package:my_flutter_puzzle/providers.dart'; |
|
||||
import 'package:my_flutter_puzzle/res/puzzle_constants.dart'; |
|
||||
import 'package:my_flutter_puzzle/utils/puzzle_solver.dart'; |
|
||||
import 'package:my_flutter_puzzle/widgets/photo_screen/image_viewer.dart'; |
|
||||
import 'package:my_flutter_puzzle/widgets/photo_screen/pick_image_button.dart'; |
|
||||
import 'package:my_flutter_puzzle/widgets/solo_screen/countdown_overlay.dart'; |
|
||||
import 'package:my_flutter_puzzle/widgets/solo_screen/solo_screen_export.dart'; |
|
||||
import 'package:palette_generator/palette_generator.dart'; |
|
||||
import 'package:rive/rive.dart'; |
|
||||
|
|
||||
class PhotoScreenSmall extends ConsumerStatefulWidget { |
|
||||
const PhotoScreenSmall({ |
|
||||
required this.solverClient, |
|
||||
required this.initialPuzzleData, |
|
||||
required this.puzzleSize, |
|
||||
required this.riveController, |
|
||||
Key? key, |
|
||||
}) : super(key: key); |
|
||||
|
|
||||
final PuzzleSolverClient solverClient; |
|
||||
final PuzzleData initialPuzzleData; |
|
||||
final int puzzleSize; |
|
||||
final RiveAnimationController riveController; |
|
||||
|
|
||||
@override |
|
||||
ConsumerState<ConsumerStatefulWidget> createState() => _SoloScreenLargeState(); |
|
||||
} |
|
||||
|
|
||||
class _SoloScreenLargeState extends ConsumerState<PhotoScreenSmall> { |
|
||||
late final PuzzleSolverClient _solverClient; |
|
||||
late final int _puzzleSize; |
|
||||
late final PuzzleData _initialPuzzleData; |
|
||||
late final RiveAnimationController _riveController; |
|
||||
bool _isStartPressed = false; |
|
||||
|
|
||||
final _imagePicker = ImagePicker(); |
|
||||
|
|
||||
List<Image>? _previousImages; |
|
||||
Image? _previousImage; |
|
||||
PaletteGenerator? _previousPalette; |
|
||||
|
|
||||
@override |
|
||||
void initState() { |
|
||||
_solverClient = widget.solverClient; |
|
||||
_puzzleSize = widget.puzzleSize; |
|
||||
_initialPuzzleData = widget.initialPuzzleData; |
|
||||
_riveController = widget.riveController; |
|
||||
super.initState(); |
|
||||
} |
|
||||
|
|
||||
@override |
|
||||
Widget build(BuildContext context) { |
|
||||
ref.listen(puzzleNotifierProvider(_solverClient), (previous, PuzzleState next) { |
|
||||
if (next is PuzzleInitializing) { |
|
||||
setState(() { |
|
||||
_isStartPressed = true; |
|
||||
}); |
|
||||
} |
|
||||
}); |
|
||||
|
|
||||
ref.listen(imageSplitterNotifierProvider, (previous, next) { |
|
||||
if (next is ImageSplitterComplete) { |
|
||||
setState(() { |
|
||||
_previousImages = next.images; |
|
||||
_previousImage = next.image; |
|
||||
_previousPalette = next.palette; |
|
||||
}); |
|
||||
} |
|
||||
}); |
|
||||
|
|
||||
var fontSize = 48.0; |
|
||||
var boardSize = 300.0; |
|
||||
|
|
||||
var spacing = 2; |
|
||||
var eachBoxSize = (boardSize / _puzzleSize) - (spacing * (_puzzleSize - 1)); |
|
||||
|
|
||||
return Stack( |
|
||||
children: [ |
|
||||
Scaffold( |
|
||||
backgroundColor: Theme.of(context).colorScheme.background, |
|
||||
// appBar: PreferredSize( |
|
||||
// child: Container( |
|
||||
// color: Theme.of(context).colorScheme.background, |
|
||||
// ), |
|
||||
// preferredSize: const Size(double.maxFinite, 30), |
|
||||
// ), |
|
||||
body: Stack( |
|
||||
children: [ |
|
||||
Row( |
|
||||
children: [ |
|
||||
const Spacer(), |
|
||||
AnimatedDash( |
|
||||
boardSize: boardSize / 1.6, |
|
||||
riveController: _riveController, |
|
||||
onInit: (_) => setState(() {}), |
|
||||
padding: const EdgeInsets.only(right: 16.0, bottom: 30), |
|
||||
), |
|
||||
], |
|
||||
), |
|
||||
Column( |
|
||||
crossAxisAlignment: CrossAxisAlignment.center, |
|
||||
children: [ |
|
||||
Row(), |
|
||||
const SizedBox(height: 60), |
|
||||
MovesTilesWidget( |
|
||||
solverClient: _solverClient, |
|
||||
fontSize: 22, |
|
||||
), |
|
||||
const SizedBox(height: 20), |
|
||||
const TimerWidget(fontSize: 24), |
|
||||
const SizedBox(height: 60), |
|
||||
Consumer( |
|
||||
builder: (context, ref, child) { |
|
||||
final state = ref.watch(imageSplitterNotifierProvider); |
|
||||
return state.maybeWhen( |
|
||||
() => PuzzleWidget( |
|
||||
solverClient: _solverClient, |
|
||||
boardSize: boardSize, |
|
||||
eachBoxSize: eachBoxSize, |
|
||||
initialPuzzleData: _initialPuzzleData, |
|
||||
fontSize: fontSize, |
|
||||
images: _previousImages, |
|
||||
kInitialSpeed: kInitialSpeed, |
|
||||
borderRadius: 16, |
|
||||
), |
|
||||
complete: (image, images, palette) { |
|
||||
_previousImages = images; |
|
||||
_previousImage = image; |
|
||||
_previousPalette = palette; |
|
||||
|
|
||||
return PuzzleWidget( |
|
||||
solverClient: _solverClient, |
|
||||
boardSize: boardSize, |
|
||||
eachBoxSize: eachBoxSize, |
|
||||
initialPuzzleData: _initialPuzzleData, |
|
||||
fontSize: fontSize, |
|
||||
images: images, |
|
||||
kInitialSpeed: kInitialSpeed, |
|
||||
borderRadius: 16, |
|
||||
); |
|
||||
}, |
|
||||
orElse: () => PuzzleWidget( |
|
||||
solverClient: _solverClient, |
|
||||
boardSize: boardSize, |
|
||||
eachBoxSize: eachBoxSize, |
|
||||
initialPuzzleData: _initialPuzzleData, |
|
||||
fontSize: fontSize, |
|
||||
images: _previousImages, |
|
||||
kInitialSpeed: kInitialSpeed, |
|
||||
borderRadius: 16, |
|
||||
), |
|
||||
); |
|
||||
}, |
|
||||
), |
|
||||
const SizedBox(height: 24), |
|
||||
Row( |
|
||||
mainAxisSize: MainAxisSize.min, |
|
||||
children: [ |
|
||||
GameButtonWidget( |
|
||||
solverClient: _solverClient, |
|
||||
initialPuzzleData: _initialPuzzleData, |
|
||||
padding: const EdgeInsets.only(top: 10.0, bottom: 9.0), |
|
||||
width: 130, |
|
||||
), |
|
||||
const SizedBox(width: 16), |
|
||||
PickImageButton( |
|
||||
text: 'Pick Image', |
|
||||
onTap: ref.read(imageSplitterNotifierProvider) is ImageSplitterComplete |
|
||||
? () => ref.read(imageSplitterNotifierProvider.notifier).generateImages( |
|
||||
picker: _imagePicker, |
|
||||
puzzleSize: _puzzleSize, |
|
||||
) |
|
||||
: null, |
|
||||
padding: const EdgeInsets.only(top: 10.0, bottom: 9.0), |
|
||||
width: 130, |
|
||||
), |
|
||||
], |
|
||||
), |
|
||||
const SizedBox(height: 16), |
|
||||
ImageViewer( |
|
||||
imagePicker: _imagePicker, |
|
||||
puzzleSize: _puzzleSize, |
|
||||
previousImage: _previousImage, |
|
||||
previousPalette: _previousPalette, |
|
||||
imageSize: 110, |
|
||||
), |
|
||||
const SizedBox(height: 50), |
|
||||
], |
|
||||
), |
|
||||
], |
|
||||
), |
|
||||
), |
|
||||
CountdownOverlay( |
|
||||
isStartPressed: _isStartPressed, |
|
||||
onFinish: () { |
|
||||
ref.read(timerNotifierProvider.notifier).startTimer(); |
|
||||
setState(() { |
|
||||
_isStartPressed = false; |
|
||||
}); |
|
||||
}, |
|
||||
initialSpeed: kInitialSpeed, |
|
||||
), |
|
||||
], |
|
||||
); |
|
||||
} |
|
||||
} |
|
@ -1,59 +0,0 @@ |
|||||
import 'package:flutter/material.dart'; |
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart'; |
|
||||
import 'package:my_flutter_puzzle/application/notifiers/puzzle_type_notifier.dart'; |
|
||||
import 'package:my_flutter_puzzle/models/puzzle_data.dart'; |
|
||||
import 'package:my_flutter_puzzle/screens/photo/photo_screen_large.dart'; |
|
||||
import 'package:my_flutter_puzzle/screens/solo/solo_screen_large.dart'; |
|
||||
import 'package:my_flutter_puzzle/utils/puzzle_solver.dart'; |
|
||||
import 'package:rive/rive.dart'; |
|
||||
|
|
||||
import '../../providers.dart'; |
|
||||
|
|
||||
class PuzzleScreenLarge extends ConsumerWidget { |
|
||||
const PuzzleScreenLarge({ |
|
||||
required this.solverClient, |
|
||||
required this.initialPuzzleData, |
|
||||
required this.puzzleSize, |
|
||||
required this.riveController, |
|
||||
Key? key, |
|
||||
}) : super(key: key); |
|
||||
|
|
||||
final PuzzleSolverClient solverClient; |
|
||||
final PuzzleData initialPuzzleData; |
|
||||
final int puzzleSize; |
|
||||
final RiveAnimationController riveController; |
|
||||
|
|
||||
@override |
|
||||
Widget build(BuildContext context, WidgetRef ref) { |
|
||||
final currentPuzzleType = ref.watch(puzzleTypeNotifierProvider); |
|
||||
|
|
||||
final name = currentPuzzleType.name[0].toUpperCase() + currentPuzzleType.name.substring(1); |
|
||||
|
|
||||
switch (currentPuzzleType) { |
|
||||
case PuzzleType.normal: |
|
||||
return SoloScreenLarge( |
|
||||
solverClient: solverClient, |
|
||||
initialPuzzleData: initialPuzzleData, |
|
||||
puzzleSize: puzzleSize, |
|
||||
puzzleType: name, |
|
||||
riveController: riveController, |
|
||||
); |
|
||||
case PuzzleType.photo: |
|
||||
return PhotoScreenLarge( |
|
||||
solverClient: solverClient, |
|
||||
initialPuzzleData: initialPuzzleData, |
|
||||
puzzleSize: puzzleSize, |
|
||||
riveController: riveController, |
|
||||
); |
|
||||
case PuzzleType.multiplayer: |
|
||||
default: |
|
||||
return SoloScreenLarge( |
|
||||
solverClient: solverClient, |
|
||||
initialPuzzleData: initialPuzzleData, |
|
||||
puzzleSize: puzzleSize, |
|
||||
puzzleType: name, |
|
||||
riveController: riveController, |
|
||||
); |
|
||||
} |
|
||||
} |
|
||||
} |
|
@ -1,635 +0,0 @@ |
|||||
import 'dart:developer'; |
|
||||
|
|
||||
import 'package:flutter/foundation.dart'; |
|
||||
import 'package:flutter/material.dart'; |
|
||||
import 'package:my_flutter_puzzle/res/palette.dart'; |
|
||||
import 'package:my_flutter_puzzle/utils/puzzle_solver.dart'; |
|
||||
import 'package:rive/rive.dart'; |
|
||||
|
|
||||
class SoloLargeScreen extends StatefulWidget { |
|
||||
const SoloLargeScreen({ |
|
||||
Key? key, |
|
||||
}) : super(key: key); |
|
||||
|
|
||||
@override |
|
||||
_SoloLargeScreenState createState() => _SoloLargeScreenState(); |
|
||||
} |
|
||||
|
|
||||
class _SoloLargeScreenState extends State<SoloLargeScreen> { |
|
||||
late PuzzleSolverClient _solverClient; |
|
||||
late RiveAnimationController _riveController; |
|
||||
|
|
||||
List<List<int>>? _board2D; |
|
||||
List<int>? myList; |
|
||||
int _moves = 0; |
|
||||
final _puzzleSize = 3; |
|
||||
final int _animationSpeedInMilliseconds = 300; |
|
||||
bool _isComputing = false; |
|
||||
bool _isAutoSolving = false; |
|
||||
bool _isSolved = false; |
|
||||
|
|
||||
Map<int, FractionalOffset>? _offsetMap; |
|
||||
List<int> _solvedList = []; |
|
||||
|
|
||||
@override |
|
||||
void initState() { |
|
||||
super.initState(); |
|
||||
_solverClient = PuzzleSolverClient(size: _puzzleSize); |
|
||||
_riveController = SimpleAnimation('idle'); |
|
||||
initBoard(); |
|
||||
generateSolvedList(); |
|
||||
} |
|
||||
|
|
||||
generateSolvedList() { |
|
||||
for (int i = 1; i < _puzzleSize * _puzzleSize; i++) { |
|
||||
_solvedList.add(i); |
|
||||
} |
|
||||
_solvedList.add(0); |
|
||||
} |
|
||||
|
|
||||
scrambleBoard() { |
|
||||
final generated2DBoard = _solverClient.createRandomBoard(); |
|
||||
final generated1DBoard = _solverClient.convertTo1D(generated2DBoard); |
|
||||
updateOffset(generated1DBoard); |
|
||||
setState(() { |
|
||||
_board2D = generated2DBoard; |
|
||||
myList = generated1DBoard; |
|
||||
_moves = 0; |
|
||||
_isSolved = false; |
|
||||
}); |
|
||||
} |
|
||||
|
|
||||
initBoard() { |
|
||||
final generated2DBoard = _solverClient.createRandomBoard(); |
|
||||
final generated1DBoard = _solverClient.convertTo1D(generated2DBoard); |
|
||||
|
|
||||
createOffset(generated1DBoard); |
|
||||
|
|
||||
setState(() { |
|
||||
_board2D = generated2DBoard; |
|
||||
myList = generated1DBoard; |
|
||||
_moves = 0; |
|
||||
}); |
|
||||
} |
|
||||
|
|
||||
startAutoSolver() async { |
|
||||
if (_board2D != null) { |
|
||||
setState(() { |
|
||||
_isComputing = true; |
|
||||
}); |
|
||||
|
|
||||
List<List<int>>? boardStates = await compute( |
|
||||
_solverClient.runner, _solverClient.convertTo2D(myList!)); |
|
||||
|
|
||||
setState(() { |
|
||||
_isComputing = false; |
|
||||
_isAutoSolving = true; |
|
||||
}); |
|
||||
|
|
||||
if (boardStates != null) { |
|
||||
for (var board in boardStates) { |
|
||||
await Future.delayed(Duration( |
|
||||
milliseconds: _animationSpeedInMilliseconds, |
|
||||
)); |
|
||||
setState(() { |
|
||||
myList = board; |
|
||||
_moves++; |
|
||||
}); |
|
||||
updateOffset(myList!); |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
setState(() { |
|
||||
_isAutoSolving = false; |
|
||||
_isSolved = true; |
|
||||
}); |
|
||||
showCompleteDialogBox(context); |
|
||||
} |
|
||||
|
|
||||
isSolved(List<int> currentBoard) { |
|
||||
if (listEquals(currentBoard, _solvedList)) { |
|
||||
setState(() { |
|
||||
_isSolved = true; |
|
||||
}); |
|
||||
return true; |
|
||||
} |
|
||||
setState(() { |
|
||||
_isSolved = false; |
|
||||
}); |
|
||||
|
|
||||
return false; |
|
||||
} |
|
||||
|
|
||||
onClick(index) { |
|
||||
log('-----------------------'); |
|
||||
log('Tapped index: $index'); |
|
||||
|
|
||||
if (myList != null) { |
|
||||
int emptyTilePosIndex = myList!.indexOf(0); |
|
||||
int emptyTilePosRow = emptyTilePosIndex ~/ _puzzleSize; |
|
||||
int emptyTilePosCol = emptyTilePosIndex % _puzzleSize; |
|
||||
|
|
||||
int currentTileRow = index ~/ _puzzleSize; |
|
||||
int currentTileCol = index % _puzzleSize; |
|
||||
|
|
||||
//current element moves up |
|
||||
if ((currentTileRow - 1 == emptyTilePosRow) && |
|
||||
(currentTileCol == emptyTilePosCol)) { |
|
||||
myList![emptyTilePosIndex] = myList![index]; |
|
||||
myList![index] = 0; |
|
||||
_moves++; |
|
||||
} |
|
||||
|
|
||||
//current element moves down |
|
||||
else if ((currentTileRow + 1 == emptyTilePosRow) && |
|
||||
(currentTileCol == emptyTilePosCol)) { |
|
||||
myList![emptyTilePosIndex] = myList![index]; |
|
||||
myList![index] = 0; |
|
||||
_moves++; |
|
||||
} |
|
||||
|
|
||||
//current element moves left |
|
||||
else if ((currentTileRow == emptyTilePosRow) && |
|
||||
(currentTileCol + 1 == emptyTilePosCol)) { |
|
||||
myList![emptyTilePosIndex] = myList![index]; |
|
||||
myList![index] = 0; |
|
||||
_moves++; |
|
||||
} |
|
||||
|
|
||||
//current element moves right |
|
||||
else if ((currentTileRow == emptyTilePosRow) && |
|
||||
(currentTileCol - 1 == emptyTilePosCol)) { |
|
||||
myList![emptyTilePosIndex] = myList![index]; |
|
||||
myList![index] = 0; |
|
||||
_moves++; |
|
||||
} else { |
|
||||
if (currentTileCol == emptyTilePosCol) { |
|
||||
int low; |
|
||||
int high; |
|
||||
|
|
||||
// multiple elements move up |
|
||||
if (emptyTilePosRow < currentTileRow) { |
|
||||
low = emptyTilePosRow; |
|
||||
high = currentTileRow; |
|
||||
|
|
||||
int i = low; |
|
||||
while (i < high) { |
|
||||
myList![(i * _puzzleSize) + emptyTilePosCol] = |
|
||||
myList![(((i + 1) * _puzzleSize) + emptyTilePosCol)]; |
|
||||
|
|
||||
i += 1; |
|
||||
} |
|
||||
|
|
||||
myList![(high * _puzzleSize) + emptyTilePosCol] = 0; |
|
||||
_moves++; |
|
||||
} |
|
||||
|
|
||||
//multiple elements move down |
|
||||
else { |
|
||||
low = emptyTilePosRow; |
|
||||
high = currentTileRow; |
|
||||
|
|
||||
int i = low; |
|
||||
while (i > high) { |
|
||||
myList![(i * _puzzleSize) + emptyTilePosCol] = |
|
||||
myList![(((i - 1) * _puzzleSize) + emptyTilePosCol)]; |
|
||||
|
|
||||
i -= 1; |
|
||||
} |
|
||||
|
|
||||
myList![(high * _puzzleSize) + emptyTilePosCol] = 0; |
|
||||
_moves++; |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
// multiple elements move left or right |
|
||||
if (currentTileRow == emptyTilePosRow) { |
|
||||
int low; |
|
||||
int high; |
|
||||
|
|
||||
// multiple elements move left |
|
||||
if (emptyTilePosCol < currentTileCol) { |
|
||||
low = emptyTilePosCol; |
|
||||
high = currentTileCol; |
|
||||
|
|
||||
int i = low; |
|
||||
while (i < high) { |
|
||||
myList![(emptyTilePosRow * _puzzleSize) + i] = |
|
||||
myList![(emptyTilePosRow * _puzzleSize) + (i + 1)]; |
|
||||
|
|
||||
i += 1; |
|
||||
} |
|
||||
|
|
||||
myList![high + (emptyTilePosRow * _puzzleSize)] = 0; |
|
||||
_moves++; |
|
||||
} |
|
||||
|
|
||||
//multiple elements move right |
|
||||
else { |
|
||||
low = emptyTilePosCol; |
|
||||
high = currentTileCol; |
|
||||
|
|
||||
int i = low; |
|
||||
while (i > high) { |
|
||||
myList![(i + (emptyTilePosRow * _puzzleSize))] = |
|
||||
myList![(i - 1) + (emptyTilePosRow * _puzzleSize)]; |
|
||||
|
|
||||
i -= 1; |
|
||||
} |
|
||||
|
|
||||
myList![high + (emptyTilePosRow * _puzzleSize)] = 0; |
|
||||
_moves++; |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
// Update Offset list |
|
||||
// setState(() { |
|
||||
// updateOffset(myList!); |
|
||||
// }); |
|
||||
updateOffset(myList!); |
|
||||
setState(() {}); |
|
||||
|
|
||||
if (isSolved(myList!)) { |
|
||||
showCompleteDialogBox(context); |
|
||||
} |
|
||||
|
|
||||
log('List: $myList'); |
|
||||
log('-----------------------'); |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
createOffset(List<int> board) { |
|
||||
Map<int, FractionalOffset> offsetMap = {}; |
|
||||
int j = 0; |
|
||||
|
|
||||
log('BOARD: $board'); |
|
||||
|
|
||||
for (int i = 0; i < board.length; i++) { |
|
||||
final xMod = i % _puzzleSize; |
|
||||
double x = xMod / (_puzzleSize - 1); |
|
||||
|
|
||||
if (x % i == 0 && i != 0) j++; |
|
||||
int yMod = j % _puzzleSize; |
|
||||
double y = yMod / (_puzzleSize - 1); |
|
||||
|
|
||||
offsetMap.addEntries([ |
|
||||
MapEntry<int, FractionalOffset>( |
|
||||
board[i], |
|
||||
FractionalOffset(x, y), |
|
||||
) |
|
||||
]); |
|
||||
} |
|
||||
|
|
||||
log('INITIAL OFFSET MAP: $offsetMap'); |
|
||||
setState(() { |
|
||||
_offsetMap = offsetMap; |
|
||||
}); |
|
||||
} |
|
||||
|
|
||||
updateOffset(List<int> board) { |
|
||||
int j = 0; |
|
||||
|
|
||||
for (int i = 0; i < board.length; i++) { |
|
||||
final xMod = i % _puzzleSize; |
|
||||
double x = xMod / (_puzzleSize - 1); |
|
||||
|
|
||||
if (x % i == 0 && i != 0) j++; |
|
||||
int yMod = j % _puzzleSize; |
|
||||
double y = yMod / (_puzzleSize - 1); |
|
||||
|
|
||||
_offsetMap![board[i]] = FractionalOffset(x, y); |
|
||||
} |
|
||||
log('OFFSET MAP: $_offsetMap'); |
|
||||
} |
|
||||
|
|
||||
showCompleteDialogBox(BuildContext context) { |
|
||||
showDialog( |
|
||||
context: context, |
|
||||
builder: (context) => Dialog( |
|
||||
child: Padding( |
|
||||
padding: const EdgeInsets.all(24.0), |
|
||||
child: Column( |
|
||||
mainAxisSize: MainAxisSize.min, |
|
||||
children: [ |
|
||||
const Text( |
|
||||
'Solved successfully!', |
|
||||
style: TextStyle(fontSize: 22), |
|
||||
), |
|
||||
const SizedBox(height: 16), |
|
||||
ElevatedButton( |
|
||||
style: ElevatedButton.styleFrom( |
|
||||
primary: Palette.violet, |
|
||||
onSurface: Palette.violet, |
|
||||
shape: RoundedRectangleBorder( |
|
||||
borderRadius: BorderRadius.circular(50), |
|
||||
), |
|
||||
), |
|
||||
onPressed: () { |
|
||||
Navigator.of(context).pop(); |
|
||||
}, |
|
||||
child: const Padding( |
|
||||
padding: EdgeInsets.all(16.0), |
|
||||
child: Text( |
|
||||
'OK', |
|
||||
style: TextStyle(fontSize: 22), |
|
||||
), |
|
||||
), |
|
||||
), |
|
||||
], |
|
||||
), |
|
||||
), |
|
||||
), |
|
||||
); |
|
||||
} |
|
||||
|
|
||||
@override |
|
||||
Widget build(BuildContext context) { |
|
||||
var screenSize = MediaQuery.of(context).size; |
|
||||
|
|
||||
var shortestSide = screenSize.shortestSide; |
|
||||
var fontSize = shortestSide * 0.08; |
|
||||
var boardSize = shortestSide * 0.45; |
|
||||
|
|
||||
var spacing = 5; |
|
||||
var eachBoxSize = (boardSize / _puzzleSize) - (spacing * (_puzzleSize - 1)); |
|
||||
|
|
||||
return Scaffold( |
|
||||
backgroundColor: Colors.black, |
|
||||
appBar: PreferredSize( |
|
||||
child: Container( |
|
||||
color: Colors.black, |
|
||||
), |
|
||||
preferredSize: Size(double.maxFinite, shortestSide * 0.1), |
|
||||
), |
|
||||
body: Row( |
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween, |
|
||||
children: [ |
|
||||
Padding( |
|
||||
padding: const EdgeInsets.only(left: 56.0), |
|
||||
child: Column( |
|
||||
mainAxisAlignment: MainAxisAlignment.center, |
|
||||
crossAxisAlignment: CrossAxisAlignment.start, |
|
||||
children: [ |
|
||||
Row(), |
|
||||
const Text( |
|
||||
'Photo', |
|
||||
style: TextStyle( |
|
||||
fontSize: 18, |
|
||||
fontWeight: FontWeight.w500, |
|
||||
color: Colors.white, |
|
||||
), |
|
||||
), |
|
||||
const SizedBox(height: 8), |
|
||||
const Text( |
|
||||
'Puzzle', |
|
||||
style: TextStyle( |
|
||||
fontSize: 58, |
|
||||
fontWeight: FontWeight.w500, |
|
||||
color: Colors.white, |
|
||||
), |
|
||||
), |
|
||||
const Text( |
|
||||
'Challenge', |
|
||||
style: TextStyle( |
|
||||
fontSize: 58, |
|
||||
fontWeight: FontWeight.w500, |
|
||||
color: Colors.white, |
|
||||
), |
|
||||
), |
|
||||
const SizedBox(height: 32), |
|
||||
RichText( |
|
||||
text: TextSpan( |
|
||||
style: const TextStyle( |
|
||||
fontSize: 24, |
|
||||
// fontWeight: FontWeight.w500, |
|
||||
color: Colors.white, |
|
||||
), |
|
||||
children: [ |
|
||||
TextSpan( |
|
||||
text: _moves.toString(), |
|
||||
style: const TextStyle( |
|
||||
fontWeight: FontWeight.w600, |
|
||||
), |
|
||||
), |
|
||||
const TextSpan(text: ' Moves | '), |
|
||||
const TextSpan( |
|
||||
text: '15', |
|
||||
style: TextStyle( |
|
||||
fontWeight: FontWeight.w600, |
|
||||
), |
|
||||
), |
|
||||
const TextSpan(text: ' Tiles'), |
|
||||
], |
|
||||
), |
|
||||
), |
|
||||
const SizedBox(height: 32), |
|
||||
SizedBox( |
|
||||
width: 145, |
|
||||
child: ElevatedButton( |
|
||||
style: ElevatedButton.styleFrom( |
|
||||
shape: RoundedRectangleBorder( |
|
||||
borderRadius: BorderRadius.circular(30), |
|
||||
), |
|
||||
), |
|
||||
onPressed: () {}, |
|
||||
child: const Padding( |
|
||||
padding: EdgeInsets.only(top: 13.0, bottom: 12.0), |
|
||||
child: Text( |
|
||||
'Start Game', |
|
||||
// 'Restart', |
|
||||
// 'Get ready...', |
|
||||
style: TextStyle( |
|
||||
fontSize: 16, |
|
||||
), |
|
||||
), |
|
||||
), |
|
||||
), |
|
||||
), |
|
||||
], |
|
||||
), |
|
||||
), |
|
||||
myList != null && _offsetMap != null |
|
||||
? Column( |
|
||||
mainAxisAlignment: MainAxisAlignment.spaceEvenly, |
|
||||
children: [ |
|
||||
// Row(), |
|
||||
// MovesText( |
|
||||
// moves: _moves, |
|
||||
// fontSize: fontSize, |
|
||||
// ), |
|
||||
Row( |
|
||||
mainAxisSize: MainAxisSize.min, |
|
||||
children: const [ |
|
||||
Text( |
|
||||
'00:00:00', |
|
||||
style: TextStyle( |
|
||||
fontSize: 40, |
|
||||
fontWeight: FontWeight.bold, |
|
||||
color: Colors.white, |
|
||||
), |
|
||||
), |
|
||||
SizedBox(width: 8), |
|
||||
Icon( |
|
||||
Icons.timer, |
|
||||
color: Colors.white, |
|
||||
size: 40, |
|
||||
) |
|
||||
], |
|
||||
), |
|
||||
SizedBox( |
|
||||
height: boardSize, |
|
||||
width: boardSize, |
|
||||
child: Stack( |
|
||||
children: [ |
|
||||
for (int i = 0; i < _offsetMap!.length; i++) |
|
||||
_offsetMap!.entries.toList()[i].key != 0 |
|
||||
? AnimatedAlign( |
|
||||
alignment: |
|
||||
_offsetMap!.entries.toList()[i].value, |
|
||||
duration: Duration( |
|
||||
milliseconds: |
|
||||
_animationSpeedInMilliseconds, |
|
||||
), |
|
||||
curve: Curves.easeInOut, |
|
||||
child: GestureDetector( |
|
||||
onTap: () => onClick(myList!.indexOf( |
|
||||
_offsetMap!.entries.toList()[i].key)), |
|
||||
child: Card( |
|
||||
elevation: 4, |
|
||||
color: const Color(0xFF2868d7), |
|
||||
shape: RoundedRectangleBorder( |
|
||||
borderRadius: |
|
||||
BorderRadius.circular(20), |
|
||||
), |
|
||||
child: SizedBox( |
|
||||
height: eachBoxSize, |
|
||||
width: eachBoxSize, |
|
||||
child: Center( |
|
||||
child: Text( |
|
||||
_offsetMap!.entries |
|
||||
.toList()[i] |
|
||||
.key |
|
||||
.toString(), |
|
||||
style: TextStyle( |
|
||||
fontSize: fontSize, |
|
||||
fontWeight: FontWeight.bold, |
|
||||
color: Colors.white, |
|
||||
), |
|
||||
), |
|
||||
), |
|
||||
), |
|
||||
), |
|
||||
), |
|
||||
) |
|
||||
: const SizedBox(), |
|
||||
], |
|
||||
), |
|
||||
), |
|
||||
const SizedBox(height: 30), |
|
||||
// Row( |
|
||||
// mainAxisSize: MainAxisSize.min, |
|
||||
// children: [ |
|
||||
// Padding( |
|
||||
// padding: const EdgeInsets.only(bottom: 30.0), |
|
||||
// child: SizedBox( |
|
||||
// width: MediaQuery.of(context).size.width * 0.2, |
|
||||
// child: ElevatedButton( |
|
||||
// style: ElevatedButton.styleFrom( |
|
||||
// primary: Palette.violet, |
|
||||
// onSurface: Palette.violet, |
|
||||
// shape: RoundedRectangleBorder( |
|
||||
// borderRadius: BorderRadius.circular(50), |
|
||||
// ), |
|
||||
// ), |
|
||||
// onPressed: _isComputing || _isAutoSolving || _isSolved |
|
||||
// ? null |
|
||||
// : () { |
|
||||
// startAutoSolver(); |
|
||||
// }, |
|
||||
// child: Padding( |
|
||||
// padding: const EdgeInsets.all(16.0), |
|
||||
// child: _isComputing || _isAutoSolving |
|
||||
// ? Row( |
|
||||
// children: [ |
|
||||
// const SizedBox( |
|
||||
// width: 25, |
|
||||
// height: 25, |
|
||||
// child: CircularProgressIndicator( |
|
||||
// color: Palette.violet, |
|
||||
// strokeWidth: 2, |
|
||||
// ), |
|
||||
// ), |
|
||||
// const SizedBox(width: 16), |
|
||||
// Text( |
|
||||
// _isComputing |
|
||||
// ? 'Computing ...' |
|
||||
// : 'Solving ...', |
|
||||
// style: const TextStyle(fontSize: 20), |
|
||||
// ), |
|
||||
// ], |
|
||||
// ) |
|
||||
// : const Text( |
|
||||
// 'Start Auto Solver', |
|
||||
// style: TextStyle(fontSize: 22), |
|
||||
// ), |
|
||||
// ), |
|
||||
// ), |
|
||||
// ), |
|
||||
// ), |
|
||||
// const SizedBox(width: 16.0), |
|
||||
// Padding( |
|
||||
// padding: const EdgeInsets.only(bottom: 30.0), |
|
||||
// child: SizedBox( |
|
||||
// width: MediaQuery.of(context).size.width * 0.2, |
|
||||
// child: ElevatedButton( |
|
||||
// style: ElevatedButton.styleFrom( |
|
||||
// primary: Palette.crimson, |
|
||||
// onSurface: Palette.crimson, |
|
||||
// shape: RoundedRectangleBorder( |
|
||||
// borderRadius: BorderRadius.circular(50), |
|
||||
// ), |
|
||||
// ), |
|
||||
// onPressed: _isComputing || _isAutoSolving |
|
||||
// ? null |
|
||||
// : () { |
|
||||
// scrambleBoard(); |
|
||||
// }, |
|
||||
// child: const Padding( |
|
||||
// padding: EdgeInsets.all(16.0), |
|
||||
// child: Text( |
|
||||
// 'Scramble', |
|
||||
// style: TextStyle(fontSize: 22), |
|
||||
// ), |
|
||||
// ), |
|
||||
// ), |
|
||||
// ), |
|
||||
// ), |
|
||||
// ], |
|
||||
// ), |
|
||||
], |
|
||||
) |
|
||||
: const SizedBox(), |
|
||||
Align( |
|
||||
alignment: Alignment.bottomCenter, |
|
||||
child: Padding( |
|
||||
padding: const EdgeInsets.only(right: 56.0, bottom: 56), |
|
||||
child: SizedBox( |
|
||||
width: boardSize * 0.75, |
|
||||
height: boardSize * 0.75, |
|
||||
child: RiveAnimation.asset( |
|
||||
'assets/rive/dash.riv', |
|
||||
fit: BoxFit.contain, |
|
||||
antialiasing: true, |
|
||||
controllers: [_riveController], |
|
||||
onInit: (_) => setState(() {}), |
|
||||
), |
|
||||
), |
|
||||
), |
|
||||
), |
|
||||
// SizedBox(), |
|
||||
], |
|
||||
), |
|
||||
); |
|
||||
} |
|
||||
} |
|
@ -1,63 +0,0 @@ |
|||||
import 'package:flutter/material.dart'; |
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart'; |
|
||||
import 'package:my_flutter_puzzle/application/states/puzzle_state.dart'; |
|
||||
import 'package:my_flutter_puzzle/providers.dart'; |
|
||||
import 'package:my_flutter_puzzle/screens/solo/solo_screen_large.dart'; |
|
||||
import 'package:my_flutter_puzzle/screens/solo/solo_screen_small.dart'; |
|
||||
import 'package:my_flutter_puzzle/utils/puzzle_solver.dart'; |
|
||||
import 'package:my_flutter_puzzle/utils/responsive_layout.dart'; |
|
||||
import 'package:rive/rive.dart'; |
|
||||
|
|
||||
import '../../models/puzzle_data.dart'; |
|
||||
import 'solo_screen_medium.dart'; |
|
||||
|
|
||||
class SoloScreen extends ConsumerWidget { |
|
||||
const SoloScreen({ |
|
||||
required this.solverClient, |
|
||||
required this.initialPuzzleData, |
|
||||
required this.puzzleSize, |
|
||||
required this.puzzleType, |
|
||||
required this.riveController, |
|
||||
Key? key, |
|
||||
}) : super(key: key); |
|
||||
|
|
||||
final PuzzleSolverClient solverClient; |
|
||||
final PuzzleData initialPuzzleData; |
|
||||
final int puzzleSize; |
|
||||
final String puzzleType; |
|
||||
final RiveAnimationController riveController; |
|
||||
|
|
||||
@override |
|
||||
Widget build(BuildContext context, WidgetRef ref) { |
|
||||
ref.listen(puzzleNotifierProvider(solverClient), |
|
||||
(previous, PuzzleState next) { |
|
||||
if (next is PuzzleSolved) { |
|
||||
ref.read(timerNotifierProvider.notifier).stopTimer(); |
|
||||
} |
|
||||
}); |
|
||||
|
|
||||
return ResponsiveLayout( |
|
||||
largeChild: SoloScreenLarge( |
|
||||
solverClient: solverClient, |
|
||||
puzzleType: puzzleType, |
|
||||
initialPuzzleData: initialPuzzleData, |
|
||||
puzzleSize: puzzleSize, |
|
||||
riveController: riveController, |
|
||||
), |
|
||||
mediumChild: SoloScreenMedium( |
|
||||
solverClient: solverClient, |
|
||||
initialPuzzleData: initialPuzzleData, |
|
||||
puzzleSize: puzzleSize, |
|
||||
puzzleType: puzzleType, |
|
||||
riveController: riveController, |
|
||||
), |
|
||||
smallChild: SoloScreenSmall( |
|
||||
solverClient: solverClient, |
|
||||
initialPuzzleData: initialPuzzleData, |
|
||||
puzzleSize: puzzleSize, |
|
||||
puzzleType: puzzleType, |
|
||||
riveController: riveController, |
|
||||
), |
|
||||
); |
|
||||
} |
|
||||
} |
|
@ -1,164 +0,0 @@ |
|||||
import 'package:flutter/material.dart'; |
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart'; |
|
||||
import 'package:my_flutter_puzzle/application/states/puzzle_state.dart'; |
|
||||
import 'package:my_flutter_puzzle/providers.dart'; |
|
||||
import 'package:my_flutter_puzzle/res/puzzle_constants.dart'; |
|
||||
import 'package:my_flutter_puzzle/screens/puzzle/top_bar.dart'; |
|
||||
import 'package:my_flutter_puzzle/utils/puzzle_solver.dart'; |
|
||||
import 'package:my_flutter_puzzle/widgets/solo_screen/solo_screen_export.dart'; |
|
||||
import 'package:rive/rive.dart'; |
|
||||
|
|
||||
import '../../models/puzzle_data.dart'; |
|
||||
|
|
||||
class SoloScreenLarge extends ConsumerStatefulWidget { |
|
||||
const SoloScreenLarge({ |
|
||||
required this.solverClient, |
|
||||
required this.initialPuzzleData, |
|
||||
required this.puzzleSize, |
|
||||
required this.puzzleType, |
|
||||
required this.riveController, |
|
||||
Key? key, |
|
||||
}) : super(key: key); |
|
||||
|
|
||||
final PuzzleSolverClient solverClient; |
|
||||
final PuzzleData initialPuzzleData; |
|
||||
final int puzzleSize; |
|
||||
final String puzzleType; |
|
||||
final RiveAnimationController riveController; |
|
||||
|
|
||||
@override |
|
||||
ConsumerState<ConsumerStatefulWidget> createState() => _SoloScreenLargeState(); |
|
||||
} |
|
||||
|
|
||||
class _SoloScreenLargeState extends ConsumerState<SoloScreenLarge> { |
|
||||
late final PuzzleSolverClient _solverClient; |
|
||||
late final int _puzzleSize; |
|
||||
late final PuzzleData _initialPuzzleData; |
|
||||
late final String _puzzleType; |
|
||||
late final RiveAnimationController _riveController; |
|
||||
bool _isStartPressed = false; |
|
||||
|
|
||||
@override |
|
||||
void initState() { |
|
||||
_solverClient = widget.solverClient; |
|
||||
_puzzleSize = widget.puzzleSize; |
|
||||
_initialPuzzleData = widget.initialPuzzleData; |
|
||||
_puzzleType = widget.puzzleType; |
|
||||
_riveController = widget.riveController; |
|
||||
super.initState(); |
|
||||
} |
|
||||
|
|
||||
@override |
|
||||
Widget build(BuildContext context) { |
|
||||
ref.listen(puzzleNotifierProvider(_solverClient), (previous, PuzzleState next) { |
|
||||
if (next is PuzzleSolved) { |
|
||||
// TODO: Add celebration |
|
||||
} |
|
||||
if (next is PuzzleInitializing) { |
|
||||
setState(() { |
|
||||
_isStartPressed = true; |
|
||||
}); |
|
||||
} |
|
||||
}); |
|
||||
|
|
||||
var fontSize = 70.0; |
|
||||
var boardSize = 450.0; |
|
||||
|
|
||||
var spacing = 5; |
|
||||
var eachBoxSize = (boardSize / _puzzleSize) - (spacing * (_puzzleSize - 1)); |
|
||||
|
|
||||
return Scaffold( |
|
||||
backgroundColor: Theme.of(context).backgroundColor, |
|
||||
body: Row( |
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween, |
|
||||
children: [ |
|
||||
Padding( |
|
||||
padding: const EdgeInsets.only(left: 56.0), |
|
||||
child: Column( |
|
||||
mainAxisAlignment: MainAxisAlignment.center, |
|
||||
crossAxisAlignment: CrossAxisAlignment.start, |
|
||||
children: [ |
|
||||
Row(), |
|
||||
Text( |
|
||||
_puzzleType, |
|
||||
style: const TextStyle( |
|
||||
fontSize: 18, |
|
||||
fontWeight: FontWeight.w500, |
|
||||
color: Colors.white, |
|
||||
), |
|
||||
), |
|
||||
const SizedBox(height: 8), |
|
||||
const Text( |
|
||||
'Puzzle', |
|
||||
style: TextStyle( |
|
||||
fontSize: 58, |
|
||||
fontWeight: FontWeight.w500, |
|
||||
color: Colors.white, |
|
||||
), |
|
||||
), |
|
||||
const Text( |
|
||||
'Challenge', |
|
||||
style: TextStyle( |
|
||||
fontSize: 58, |
|
||||
fontWeight: FontWeight.w500, |
|
||||
color: Colors.white, |
|
||||
), |
|
||||
), |
|
||||
const SizedBox(height: 32), |
|
||||
MovesTilesWidget(solverClient: _solverClient), |
|
||||
const SizedBox(height: 32), |
|
||||
GameButtonWidget( |
|
||||
solverClient: _solverClient, |
|
||||
initialPuzzleData: _initialPuzzleData, |
|
||||
), |
|
||||
], |
|
||||
), |
|
||||
), |
|
||||
SingleChildScrollView( |
|
||||
child: Column( |
|
||||
mainAxisAlignment: MainAxisAlignment.spaceEvenly, |
|
||||
children: [ |
|
||||
const TimerWidget( |
|
||||
fontSize: 40, |
|
||||
), |
|
||||
const SizedBox(height: 36), |
|
||||
PuzzleWidget( |
|
||||
solverClient: _solverClient, |
|
||||
boardSize: boardSize, |
|
||||
eachBoxSize: eachBoxSize, |
|
||||
initialPuzzleData: _initialPuzzleData, |
|
||||
fontSize: fontSize, |
|
||||
kInitialSpeed: kInitialSpeed, |
|
||||
), |
|
||||
const SizedBox(height: 30), |
|
||||
], |
|
||||
), |
|
||||
), |
|
||||
Column( |
|
||||
mainAxisSize: MainAxisSize.max, |
|
||||
children: [ |
|
||||
const Spacer(), |
|
||||
CountdownWidget( |
|
||||
isStartPressed: _isStartPressed, |
|
||||
onFinish: () { |
|
||||
ref.read(timerNotifierProvider.notifier).startTimer(); |
|
||||
setState(() { |
|
||||
_isStartPressed = false; |
|
||||
}); |
|
||||
}, |
|
||||
initialSpeed: kInitialSpeed, |
|
||||
), |
|
||||
const Spacer(), |
|
||||
AnimatedDash( |
|
||||
boardSize: boardSize * 0.8, |
|
||||
riveController: _riveController, |
|
||||
onInit: (_) => setState(() {}), |
|
||||
), |
|
||||
], |
|
||||
), |
|
||||
// SizedBox(), |
|
||||
], |
|
||||
), |
|
||||
); |
|
||||
} |
|
||||
} |
|
@ -1,148 +0,0 @@ |
|||||
import 'package:flutter/material.dart'; |
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart'; |
|
||||
import 'package:my_flutter_puzzle/application/states/puzzle_state.dart'; |
|
||||
import 'package:my_flutter_puzzle/providers.dart'; |
|
||||
import 'package:my_flutter_puzzle/res/puzzle_constants.dart'; |
|
||||
import 'package:my_flutter_puzzle/screens/puzzle/top_bar.dart'; |
|
||||
import 'package:my_flutter_puzzle/utils/puzzle_solver.dart'; |
|
||||
import 'package:my_flutter_puzzle/widgets/solo_screen/countdown_overlay.dart'; |
|
||||
import 'package:my_flutter_puzzle/widgets/solo_screen/solo_screen_export.dart'; |
|
||||
import 'package:rive/rive.dart'; |
|
||||
|
|
||||
import '../../models/puzzle_data.dart'; |
|
||||
|
|
||||
class SoloScreenMedium extends ConsumerStatefulWidget { |
|
||||
const SoloScreenMedium({ |
|
||||
required this.solverClient, |
|
||||
required this.initialPuzzleData, |
|
||||
required this.puzzleSize, |
|
||||
required this.puzzleType, |
|
||||
required this.riveController, |
|
||||
Key? key, |
|
||||
}) : super(key: key); |
|
||||
|
|
||||
final PuzzleSolverClient solverClient; |
|
||||
final PuzzleData initialPuzzleData; |
|
||||
final int puzzleSize; |
|
||||
final String puzzleType; |
|
||||
final RiveAnimationController riveController; |
|
||||
|
|
||||
@override |
|
||||
ConsumerState<ConsumerStatefulWidget> createState() => _SoloScreenLargeState(); |
|
||||
} |
|
||||
|
|
||||
class _SoloScreenLargeState extends ConsumerState<SoloScreenMedium> { |
|
||||
late final PuzzleSolverClient _solverClient; |
|
||||
late final int _puzzleSize; |
|
||||
late final PuzzleData _initialPuzzleData; |
|
||||
late final String _puzzleType; |
|
||||
late final RiveAnimationController _riveController; |
|
||||
bool _isStartPressed = false; |
|
||||
|
|
||||
@override |
|
||||
void initState() { |
|
||||
_solverClient = widget.solverClient; |
|
||||
_puzzleSize = widget.puzzleSize; |
|
||||
_initialPuzzleData = widget.initialPuzzleData; |
|
||||
_puzzleType = widget.puzzleType; |
|
||||
_riveController = widget.riveController; |
|
||||
super.initState(); |
|
||||
} |
|
||||
|
|
||||
@override |
|
||||
Widget build(BuildContext context) { |
|
||||
ref.listen(puzzleNotifierProvider(_solverClient), (previous, PuzzleState next) { |
|
||||
if (next is PuzzleSolved) { |
|
||||
// TODO: Add celebration |
|
||||
} |
|
||||
if (next is PuzzleInitializing) { |
|
||||
setState(() { |
|
||||
_isStartPressed = true; |
|
||||
}); |
|
||||
} |
|
||||
}); |
|
||||
|
|
||||
var fontSize = 64.0; |
|
||||
var boardSize = 400.0; |
|
||||
|
|
||||
var spacing = 5; |
|
||||
var eachBoxSize = (boardSize / _puzzleSize) - (spacing * (_puzzleSize - 1)); |
|
||||
|
|
||||
return Stack( |
|
||||
children: [ |
|
||||
Scaffold( |
|
||||
backgroundColor: Theme.of(context).backgroundColor, |
|
||||
body: Stack( |
|
||||
children: [ |
|
||||
Row( |
|
||||
children: [ |
|
||||
const Spacer(), |
|
||||
AnimatedDash( |
|
||||
boardSize: boardSize / 1.5, |
|
||||
riveController: _riveController, |
|
||||
onInit: (_) => setState(() {}), |
|
||||
), |
|
||||
], |
|
||||
), |
|
||||
SingleChildScrollView( |
|
||||
child: Column( |
|
||||
mainAxisAlignment: MainAxisAlignment.center, |
|
||||
// crossAxisAlignment: CrossAxisAlignment.start, |
|
||||
children: [ |
|
||||
Row(), |
|
||||
// Text( |
|
||||
// _puzzleType, |
|
||||
// style: const TextStyle( |
|
||||
// fontSize: 18, |
|
||||
// fontWeight: FontWeight.w500, |
|
||||
// color: Colors.white, |
|
||||
// ), |
|
||||
// ), |
|
||||
const SizedBox(height: 8), |
|
||||
const Text( |
|
||||
'Puzzle Challenge', |
|
||||
style: TextStyle( |
|
||||
fontSize: 36, |
|
||||
fontWeight: FontWeight.w500, |
|
||||
color: Colors.white, |
|
||||
), |
|
||||
), |
|
||||
const SizedBox(height: 16), |
|
||||
MovesTilesWidget(solverClient: _solverClient), |
|
||||
const SizedBox(height: 16), |
|
||||
const TimerWidget(fontSize: 36), |
|
||||
const SizedBox(height: 36), |
|
||||
PuzzleWidget( |
|
||||
solverClient: _solverClient, |
|
||||
boardSize: boardSize, |
|
||||
eachBoxSize: eachBoxSize, |
|
||||
initialPuzzleData: _initialPuzzleData, |
|
||||
fontSize: fontSize, |
|
||||
kInitialSpeed: kInitialSpeed, |
|
||||
), |
|
||||
const SizedBox(height: 36), |
|
||||
GameButtonWidget( |
|
||||
solverClient: _solverClient, |
|
||||
initialPuzzleData: _initialPuzzleData, |
|
||||
), |
|
||||
const SizedBox(height: 100), |
|
||||
], |
|
||||
), |
|
||||
), |
|
||||
], |
|
||||
), |
|
||||
), |
|
||||
CountdownOverlay( |
|
||||
isStartPressed: _isStartPressed, |
|
||||
onFinish: () { |
|
||||
ref.read(timerNotifierProvider.notifier).startTimer(); |
|
||||
setState(() { |
|
||||
_isStartPressed = false; |
|
||||
}); |
|
||||
}, |
|
||||
initialSpeed: kInitialSpeed, |
|
||||
), |
|
||||
], |
|
||||
); |
|
||||
} |
|
||||
} |
|
@ -1,145 +0,0 @@ |
|||||
import 'package:flutter/material.dart'; |
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart'; |
|
||||
import 'package:my_flutter_puzzle/application/states/puzzle_state.dart'; |
|
||||
import 'package:my_flutter_puzzle/providers.dart'; |
|
||||
import 'package:my_flutter_puzzle/res/puzzle_constants.dart'; |
|
||||
import 'package:my_flutter_puzzle/screens/puzzle/top_bar.dart'; |
|
||||
import 'package:my_flutter_puzzle/utils/puzzle_solver.dart'; |
|
||||
import 'package:my_flutter_puzzle/widgets/solo_screen/countdown_overlay.dart'; |
|
||||
import 'package:my_flutter_puzzle/widgets/solo_screen/solo_screen_export.dart'; |
|
||||
import 'package:rive/rive.dart'; |
|
||||
|
|
||||
import '../../models/puzzle_data.dart'; |
|
||||
|
|
||||
class SoloScreenSmall extends ConsumerStatefulWidget { |
|
||||
const SoloScreenSmall({ |
|
||||
required this.solverClient, |
|
||||
required this.initialPuzzleData, |
|
||||
required this.puzzleSize, |
|
||||
required this.puzzleType, |
|
||||
required this.riveController, |
|
||||
Key? key, |
|
||||
}) : super(key: key); |
|
||||
|
|
||||
final PuzzleSolverClient solverClient; |
|
||||
final PuzzleData initialPuzzleData; |
|
||||
final int puzzleSize; |
|
||||
final String puzzleType; |
|
||||
final RiveAnimationController riveController; |
|
||||
|
|
||||
@override |
|
||||
ConsumerState<ConsumerStatefulWidget> createState() => _SoloScreenLargeState(); |
|
||||
} |
|
||||
|
|
||||
class _SoloScreenLargeState extends ConsumerState<SoloScreenSmall> { |
|
||||
late final PuzzleSolverClient _solverClient; |
|
||||
late final int _puzzleSize; |
|
||||
late final PuzzleData _initialPuzzleData; |
|
||||
late final String _puzzleType; |
|
||||
late final RiveAnimationController _riveController; |
|
||||
bool _isStartPressed = false; |
|
||||
|
|
||||
@override |
|
||||
void initState() { |
|
||||
_solverClient = widget.solverClient; |
|
||||
_puzzleSize = widget.puzzleSize; |
|
||||
_initialPuzzleData = widget.initialPuzzleData; |
|
||||
_puzzleType = widget.puzzleType; |
|
||||
_riveController = widget.riveController; |
|
||||
super.initState(); |
|
||||
} |
|
||||
|
|
||||
@override |
|
||||
Widget build(BuildContext context) { |
|
||||
ref.listen(puzzleNotifierProvider(_solverClient), (previous, PuzzleState next) { |
|
||||
if (next is PuzzleSolved) { |
|
||||
// TODO: Add celebration |
|
||||
} |
|
||||
if (next is PuzzleInitializing) { |
|
||||
setState(() { |
|
||||
_isStartPressed = true; |
|
||||
}); |
|
||||
} |
|
||||
}); |
|
||||
|
|
||||
var fontSize = 48.0; |
|
||||
var boardSize = 300.0; |
|
||||
|
|
||||
var spacing = 3; |
|
||||
var eachBoxSize = (boardSize / _puzzleSize) - (spacing * (_puzzleSize - 1)); |
|
||||
|
|
||||
return Stack( |
|
||||
children: [ |
|
||||
Scaffold( |
|
||||
backgroundColor: Theme.of(context).backgroundColor, |
|
||||
body: Stack( |
|
||||
children: [ |
|
||||
Row( |
|
||||
children: [ |
|
||||
const Spacer(), |
|
||||
AnimatedDash( |
|
||||
boardSize: boardSize / 1.5, |
|
||||
riveController: _riveController, |
|
||||
onInit: (_) => setState(() {}), |
|
||||
padding: const EdgeInsets.only(right: 16.0, bottom: 30), |
|
||||
), |
|
||||
], |
|
||||
), |
|
||||
SingleChildScrollView( |
|
||||
child: Column( |
|
||||
mainAxisAlignment: MainAxisAlignment.center, |
|
||||
children: [ |
|
||||
Row(), |
|
||||
const SizedBox(height: 30), |
|
||||
const Text( |
|
||||
'Puzzle Challenge', |
|
||||
style: TextStyle( |
|
||||
fontSize: 32, |
|
||||
fontWeight: FontWeight.w500, |
|
||||
color: Colors.white, |
|
||||
), |
|
||||
), |
|
||||
const SizedBox(height: 8), |
|
||||
MovesTilesWidget( |
|
||||
solverClient: _solverClient, |
|
||||
fontSize: 22, |
|
||||
), |
|
||||
const SizedBox(height: 8), |
|
||||
const TimerWidget(fontSize: 24), |
|
||||
PuzzleWidget( |
|
||||
solverClient: _solverClient, |
|
||||
boardSize: boardSize, |
|
||||
eachBoxSize: eachBoxSize, |
|
||||
initialPuzzleData: _initialPuzzleData, |
|
||||
fontSize: fontSize, |
|
||||
kInitialSpeed: kInitialSpeed, |
|
||||
borderRadius: 16, |
|
||||
), |
|
||||
const SizedBox(height: 24), |
|
||||
GameButtonWidget( |
|
||||
solverClient: _solverClient, |
|
||||
initialPuzzleData: _initialPuzzleData, |
|
||||
padding: const EdgeInsets.only(top: 10.0, bottom: 9.0), |
|
||||
width: 130, |
|
||||
), |
|
||||
const SizedBox(height: 100), |
|
||||
], |
|
||||
), |
|
||||
), |
|
||||
], |
|
||||
), |
|
||||
), |
|
||||
CountdownOverlay( |
|
||||
isStartPressed: _isStartPressed, |
|
||||
onFinish: () { |
|
||||
ref.read(timerNotifierProvider.notifier).startTimer(); |
|
||||
setState(() { |
|
||||
_isStartPressed = false; |
|
||||
}); |
|
||||
}, |
|
||||
initialSpeed: kInitialSpeed, |
|
||||
), |
|
||||
], |
|
||||
); |
|
||||
} |
|
||||
} |
|
@ -0,0 +1,198 @@ |
|||||
|
import 'dart:async'; |
||||
|
|
||||
|
import 'package:flutter/material.dart'; |
||||
|
|
||||
|
enum Toast { |
||||
|
short, |
||||
|
long, |
||||
|
} |
||||
|
|
||||
|
enum ToastGravity { |
||||
|
top, |
||||
|
bottom, |
||||
|
center, |
||||
|
topLeft, |
||||
|
topRight, |
||||
|
bottomLeft, |
||||
|
bottomRight, |
||||
|
centerLEft, |
||||
|
centerRight, |
||||
|
snackBar, |
||||
|
} |
||||
|
|
||||
|
typedef PositionedToastBuilder = Widget Function(BuildContext context, Widget child); |
||||
|
|
||||
|
class FToast { |
||||
|
late BuildContext context; |
||||
|
|
||||
|
static final FToast _instance = FToast._internal(); |
||||
|
|
||||
|
factory FToast() { |
||||
|
return _instance; |
||||
|
} |
||||
|
|
||||
|
FToast init(BuildContext context) { |
||||
|
_instance.context = context; |
||||
|
return _instance; |
||||
|
} |
||||
|
|
||||
|
FToast._internal(); |
||||
|
|
||||
|
OverlayEntry? _entry; |
||||
|
final List<_ToastEntry> _overlayQueue = []; |
||||
|
Timer? _timer; |
||||
|
|
||||
|
void _showOverlay() { |
||||
|
if (_overlayQueue.isEmpty) { |
||||
|
_entry = null; |
||||
|
return; |
||||
|
} |
||||
|
_ToastEntry toastEntry = _overlayQueue.removeAt(0); |
||||
|
_entry = toastEntry.entry; |
||||
|
Overlay.of(context)?.insert(_entry!); |
||||
|
|
||||
|
_timer = Timer(toastEntry.duration, () { |
||||
|
Future.delayed(const Duration(milliseconds: 360), () { |
||||
|
removeCustomToast(); |
||||
|
}); |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
void removeCustomToast() { |
||||
|
_timer!.cancel(); |
||||
|
_timer = null; |
||||
|
if (_entry != null) _entry!.remove(); |
||||
|
_entry = null; |
||||
|
_showOverlay(); |
||||
|
} |
||||
|
|
||||
|
void removeQueuedCustomToasts() { |
||||
|
_timer?.cancel(); |
||||
|
_timer = null; |
||||
|
_overlayQueue.clear(); |
||||
|
if (_entry != null) _entry!.remove(); |
||||
|
_entry = null; |
||||
|
} |
||||
|
|
||||
|
void showToast({ |
||||
|
required Widget child, |
||||
|
PositionedToastBuilder? positionedToastBuilder, |
||||
|
required Duration? toastDuration, |
||||
|
required ToastGravity gravity, |
||||
|
int fadeDuration = 350, |
||||
|
}) { |
||||
|
Widget newChild = _ToastStateFul(child, toastDuration ?? const Duration(seconds: 2), fadeDuration: fadeDuration); |
||||
|
if (gravity == ToastGravity.bottom) { |
||||
|
if (MediaQuery.of(context).viewInsets.bottom != 0) { |
||||
|
gravity = ToastGravity.center; |
||||
|
} |
||||
|
} |
||||
|
OverlayEntry newEntry = OverlayEntry(builder: (context) { |
||||
|
if (positionedToastBuilder != null) return positionedToastBuilder(context, newChild); |
||||
|
return _getPositionWidgetBasedOnGravity(newChild, gravity); |
||||
|
}); |
||||
|
|
||||
|
_overlayQueue.add(_ToastEntry(entry: newEntry, duration: toastDuration ?? const Duration(seconds: 2))); |
||||
|
if (_timer == null) _showOverlay(); |
||||
|
} |
||||
|
|
||||
|
Positioned _getPositionWidgetBasedOnGravity(Widget child, ToastGravity gravity) { |
||||
|
switch (gravity) { |
||||
|
case ToastGravity.top: |
||||
|
return Positioned(top: 100.0, left: 24.0, right: 24.0, child: child); |
||||
|
case ToastGravity.topLeft: |
||||
|
return Positioned(top: 100.0, left: 24.0, child: child); |
||||
|
case ToastGravity.topRight: |
||||
|
return Positioned(top: 100.0, right: 24.0, child: child); |
||||
|
case ToastGravity.center: |
||||
|
return Positioned(top: 50.0, bottom: 50.0, left: 24.0, right: 24.0, child: child); |
||||
|
case ToastGravity.centerLEft: |
||||
|
return Positioned(top: 50.0, bottom: 50.0, left: 24.0, child: child); |
||||
|
case ToastGravity.centerRight: |
||||
|
return Positioned(top: 50.0, bottom: 50.0, right: 24.0, child: child); |
||||
|
case ToastGravity.bottomLeft: |
||||
|
return Positioned(bottom: 50.0, left: 24.0, child: child); |
||||
|
case ToastGravity.bottomRight: |
||||
|
return Positioned(bottom: 50.0, right: 24.0, child: child); |
||||
|
case ToastGravity.snackBar: |
||||
|
return Positioned(bottom: MediaQuery.of(context).viewInsets.bottom, left: 0, right: 0, child: child); |
||||
|
case ToastGravity.bottom: |
||||
|
default: |
||||
|
return Positioned(bottom: 50.0, left: 24.0, right: 24.0, child: child); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
class _ToastEntry { |
||||
|
final OverlayEntry entry; |
||||
|
final Duration duration; |
||||
|
|
||||
|
_ToastEntry({required this.entry, required this.duration}); |
||||
|
} |
||||
|
|
||||
|
class _ToastStateFul extends StatefulWidget { |
||||
|
const _ToastStateFul(this.child, this.duration, {Key? key, this.fadeDuration = 350}) : super(key: key); |
||||
|
|
||||
|
final Widget child; |
||||
|
final Duration duration; |
||||
|
final int fadeDuration; |
||||
|
|
||||
|
@override |
||||
|
ToastStateFulState createState() => ToastStateFulState(); |
||||
|
} |
||||
|
|
||||
|
class ToastStateFulState extends State<_ToastStateFul> with SingleTickerProviderStateMixin { |
||||
|
void showIt() { |
||||
|
_animationController.forward(); |
||||
|
} |
||||
|
|
||||
|
void hideIt() { |
||||
|
_animationController.reverse(); |
||||
|
_timer.cancel(); |
||||
|
} |
||||
|
|
||||
|
late AnimationController _animationController; |
||||
|
late Animation _fadeAnimation; |
||||
|
late Timer _timer; |
||||
|
|
||||
|
@override |
||||
|
void initState() { |
||||
|
_animationController = AnimationController( |
||||
|
vsync: this, |
||||
|
duration: Duration(milliseconds: widget.fadeDuration), |
||||
|
); |
||||
|
_fadeAnimation = CurvedAnimation(parent: _animationController, curve: Curves.easeIn); |
||||
|
super.initState(); |
||||
|
showIt(); |
||||
|
_timer = Timer(widget.duration, () { |
||||
|
hideIt(); |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
@override |
||||
|
void deactivate() { |
||||
|
_timer.cancel(); |
||||
|
_animationController.stop(); |
||||
|
super.deactivate(); |
||||
|
} |
||||
|
|
||||
|
@override |
||||
|
void dispose() { |
||||
|
_timer.cancel(); |
||||
|
_animationController.dispose(); |
||||
|
super.dispose(); |
||||
|
} |
||||
|
|
||||
|
@override |
||||
|
Widget build(BuildContext context) { |
||||
|
return FadeTransition( |
||||
|
opacity: _fadeAnimation as Animation<double>, |
||||
|
child: Center( |
||||
|
child: Material( |
||||
|
color: Colors.transparent, |
||||
|
child: widget.child, |
||||
|
), |
||||
|
), |
||||
|
); |
||||
|
} |
||||
|
} |
@ -0,0 +1,49 @@ |
|||||
|
import 'package:flutter/foundation.dart'; |
||||
|
import 'package:flutter/material.dart'; |
||||
|
import 'package:my_flutter_puzzle/utils/toast.dart'; |
||||
|
|
||||
|
class Utils { |
||||
|
Utils.privateConstructor(); |
||||
|
|
||||
|
static final Utils instance = Utils.privateConstructor(); |
||||
|
|
||||
|
factory Utils() { |
||||
|
return instance; |
||||
|
} |
||||
|
|
||||
|
void showToast(BuildContext? context, String? txt, {bool isError = true, ToastGravity gravity = ToastGravity.top}) { |
||||
|
try { |
||||
|
if (context == null) return; |
||||
|
if (txt == null || txt.isEmpty) { |
||||
|
if (isError == false) { |
||||
|
return; |
||||
|
} |
||||
|
txt = 'Error'; |
||||
|
} |
||||
|
FToast fToast = FToast(); |
||||
|
fToast.init(context); |
||||
|
Widget toast = Container( |
||||
|
padding: const EdgeInsetsDirectional.all(16), |
||||
|
decoration: BoxDecoration( |
||||
|
borderRadius: BorderRadius.circular(8), |
||||
|
color: isError ? Colors.red : Colors.lightBlue, |
||||
|
), |
||||
|
child: Text( |
||||
|
txt, |
||||
|
style: const TextStyle( |
||||
|
color: Colors.white, |
||||
|
fontWeight: FontWeight.bold, |
||||
|
), |
||||
|
), |
||||
|
); |
||||
|
fToast.showToast(child: toast, gravity: gravity, toastDuration: const Duration(milliseconds: 1500)); |
||||
|
} catch (e) { |
||||
|
assert(() { |
||||
|
if (kDebugMode) { |
||||
|
print(e); |
||||
|
} |
||||
|
return true; |
||||
|
}()); |
||||
|
} |
||||
|
} |
||||
|
} |
@ -1,29 +0,0 @@ |
|||||
import 'package:flutter/material.dart'; |
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart'; |
|
||||
import 'package:my_flutter_puzzle/providers.dart'; |
|
||||
|
|
||||
class TimerTestScreen extends ConsumerWidget { |
|
||||
const TimerTestScreen({Key? key}) : super(key: key); |
|
||||
|
|
||||
@override |
|
||||
Widget build(BuildContext context, WidgetRef ref) { |
|
||||
final state = ref.watch(timerNotifierProvider); |
|
||||
|
|
||||
return Scaffold( |
|
||||
body: Column( |
|
||||
mainAxisAlignment: MainAxisAlignment.center, |
|
||||
children: [ |
|
||||
Text(state), |
|
||||
ElevatedButton( |
|
||||
onPressed: () => ref.read(timerNotifierProvider.notifier).startTimer(), |
|
||||
child: const Text('Start timer'), |
|
||||
), |
|
||||
ElevatedButton( |
|
||||
onPressed: () => ref.read(timerNotifierProvider.notifier).stopTimer(), |
|
||||
child: const Text('Stop timer'), |
|
||||
), |
|
||||
], |
|
||||
), |
|
||||
); |
|
||||
} |
|
||||
} |
|
Write
Preview
Loading…
Cancel
Save
Reference in new issue