mohsen zamani
2 years ago
20 changed files with 470 additions and 1908 deletions
-
27lib/application/notifiers/puzzle_notifier.dart
-
9lib/application/states/puzzle_state.dart
-
160lib/application/states/puzzle_state.freezed.dart
-
9lib/screens/photo/photo_screen.dart
-
232lib/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