-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: add view, and widgets for sudoku board, input
- Loading branch information
1 parent
30ac620
commit 8dc76ac
Showing
17 changed files
with
850 additions
and
10 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
export 'sudoku_board_size.dart'; | ||
export 'sudoku_input_size.dart'; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
/// Determines the size of the Sudoku Board depending upon the | ||
/// screen size. | ||
abstract class SudokuBoardSize { | ||
/// Sudoku board size for small layout. | ||
static const double small = 360; | ||
|
||
/// Sudoku board size for medium layout. | ||
static const double medium = 495; | ||
|
||
/// Sudoku board size for large layout. | ||
static const double large = 558; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
/// Determines the size of the Sudoku Input depending upon the | ||
/// screen size. | ||
abstract class SudokuInputSize { | ||
/// Sudoku input size for small layout. | ||
static const double small = 72; | ||
|
||
/// Sudoku input size for medium layout. | ||
static const double medium = 84.6; | ||
|
||
/// Sudoku input size for large layout. | ||
static const double large = 95.4; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1 +1,4 @@ | ||
export 'bloc/sudoku_bloc.dart'; | ||
export 'models/models.dart'; | ||
export 'view/view.dart'; | ||
export 'widgets/widgets.dart'; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,175 @@ | ||
import 'package:flutter/material.dart'; | ||
import 'package:flutter_bloc/flutter_bloc.dart'; | ||
import 'package:sudoku/l10n/l10n.dart'; | ||
import 'package:sudoku/layout/layout.dart'; | ||
import 'package:sudoku/models/models.dart'; | ||
import 'package:sudoku/sudoku/sudoku.dart'; | ||
import 'package:sudoku/typography/typography.dart'; | ||
|
||
class SudokuPage extends StatelessWidget { | ||
const SudokuPage({super.key}); | ||
|
||
static const _generated = [ | ||
[-1, -1, -1, 8, -1, -1, -1, -1, 9], | ||
[-1, 1, 9, -1, -1, 5, 8, 3, -1], | ||
[-1, 4, 3, -1, 1, -1, -1, -1, 7], | ||
[4, -1, -1, 1, 5, -1, -1, -1, 3], | ||
[-1, -1, 2, 7, -1, 4, -1, 1, -1], | ||
[-1, 8, -1, -1, 9, -1, 6, -1, -1], | ||
[-1, 7, -1, -1, -1, 6, 3, -1, -1], | ||
[-1, 3, -1, -1, 7, -1, -1, 8, -1], | ||
[9, -1, 4, 5, -1, -1, -1, -1, 1], | ||
]; | ||
|
||
static const _answer = [ | ||
[2, 5, 6, 8, 3, 7, 1, 4, 9], | ||
[7, 1, 9, 4, 2, 5, 8, 3, 6], | ||
[8, 4, 3, 6, 1, 9, 2, 5, 7], | ||
[4, 6, 7, 1, 5, 8, 9, 2, 3], | ||
[3, 9, 2, 7, 6, 4, 5, 1, 8], | ||
[5, 8, 1, 3, 9, 2, 6, 7, 4], | ||
[1, 7, 8, 2, 4, 6, 3, 9, 5], | ||
[6, 3, 5, 9, 7, 1, 4, 8, 2], | ||
[9, 2, 4, 5, 8, 3, 7, 6, 1], | ||
]; | ||
|
||
@override | ||
Widget build(BuildContext context) { | ||
return BlocProvider<SudokuBloc>( | ||
create: (context) => SudokuBloc( | ||
sudoku: Sudoku.fromRawData(_generated, _answer), | ||
), | ||
child: const SudokuView(), | ||
); | ||
} | ||
} | ||
|
||
class SudokuView extends StatelessWidget { | ||
const SudokuView({super.key}); | ||
|
||
@override | ||
Widget build(BuildContext context) { | ||
final l10n = context.l10n; | ||
final sudoku = context.select( | ||
(SudokuBloc bloc) => bloc.state.sudoku, | ||
); | ||
|
||
return ResponsiveLayoutBuilder( | ||
small: (_, child) => child!, | ||
medium: (_, child) => child!, | ||
large: (_, __) => Scaffold( | ||
body: SingleChildScrollView( | ||
child: Column( | ||
children: [ | ||
const ResponsiveGap( | ||
large: 246, | ||
), | ||
Row( | ||
mainAxisAlignment: MainAxisAlignment.center, | ||
children: [ | ||
SizedBox( | ||
width: 250, | ||
child: Text( | ||
l10n.sudokuAppBarTitle, | ||
style: SudokuTextStyle.headline1.copyWith( | ||
fontWeight: SudokuFontWeight.black, | ||
), | ||
), | ||
), | ||
const SizedBox( | ||
width: 60, | ||
), | ||
const SudokuBoardView( | ||
layoutSize: ResponsiveLayoutSize.large, | ||
), | ||
const SizedBox( | ||
width: 96, | ||
), | ||
SudokuInput( | ||
sudokuDimension: sudoku.getDimesion(), | ||
), | ||
], | ||
), | ||
const ResponsiveGap( | ||
large: 246, | ||
), | ||
], | ||
), | ||
), | ||
), | ||
child: (layoutSize) { | ||
return Scaffold( | ||
appBar: AppBar( | ||
title: Text(l10n.sudokuAppBarTitle), | ||
), | ||
body: SingleChildScrollView( | ||
child: Column( | ||
children: [ | ||
const ResponsiveGap( | ||
small: 24, | ||
medium: 32, | ||
), | ||
Center( | ||
child: SudokuBoardView(layoutSize: layoutSize), | ||
), | ||
const ResponsiveGap( | ||
small: 32, | ||
medium: 56, | ||
), | ||
Center( | ||
child: SudokuInput( | ||
sudokuDimension: sudoku.getDimesion(), | ||
), | ||
), | ||
], | ||
), | ||
), | ||
); | ||
}, | ||
); | ||
} | ||
} | ||
|
||
@visibleForTesting | ||
class SudokuBoardView extends StatelessWidget { | ||
const SudokuBoardView({ | ||
required this.layoutSize, | ||
super.key, | ||
}); | ||
final ResponsiveLayoutSize layoutSize; | ||
|
||
@override | ||
Widget build(BuildContext context) { | ||
final dimension = context.select( | ||
(SudokuBloc bloc) => bloc.state.sudoku.getDimesion(), | ||
); | ||
|
||
final blockSize = switch (layoutSize) { | ||
ResponsiveLayoutSize.small => SudokuBoardSize.small / dimension, | ||
ResponsiveLayoutSize.medium => SudokuBoardSize.medium / dimension, | ||
ResponsiveLayoutSize.large => SudokuBoardSize.large / dimension, | ||
}; | ||
|
||
return BlocBuilder<SudokuBloc, SudokuState>( | ||
buildWhen: (previous, current) => previous.sudoku != current.sudoku, | ||
builder: (context, state) { | ||
return SudokuBoard( | ||
blocks: [ | ||
for (final block in state.sudoku.blocks) | ||
Positioned( | ||
key: Key( | ||
'sudoku_board_view_${block.position.x}_${block.position.y}', | ||
), | ||
top: block.position.x * blockSize, | ||
left: block.position.y * blockSize, | ||
child: SudokuBlock( | ||
block: block, | ||
state: state, | ||
), | ||
), | ||
], | ||
); | ||
}, | ||
); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
export 'sudoku_page.dart'; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,93 @@ | ||
import 'package:flutter/material.dart'; | ||
import 'package:flutter_bloc/flutter_bloc.dart'; | ||
import 'package:sudoku/layout/layout.dart'; | ||
import 'package:sudoku/models/models.dart'; | ||
import 'package:sudoku/sudoku/sudoku.dart'; | ||
import 'package:sudoku/typography/typography.dart'; | ||
|
||
/// {@template sudoku_block} | ||
/// Displays the Sudoku [block] based upon the current [state]. | ||
/// {@endtemplate} | ||
class SudokuBlock extends StatelessWidget { | ||
/// {@macro sudoku_block} | ||
const SudokuBlock({ | ||
required this.block, | ||
required this.state, | ||
super.key, | ||
}); | ||
|
||
/// The [Block] to be displayed. | ||
final Block block; | ||
|
||
/// The state of the sudoku. | ||
final SudokuState state; | ||
|
||
@override | ||
Widget build(BuildContext context) { | ||
final theme = Theme.of(context); | ||
final dimension = state.sudoku.getDimesion(); | ||
|
||
final selectedBlock = context.select( | ||
(SudokuBloc bloc) => bloc.state.currentSelectedBlock, | ||
); | ||
final highlightedBlocks = context.select( | ||
(SudokuBloc bloc) => bloc.state.highlightedBlocks, | ||
); | ||
|
||
// Comparing with the current block's position, otherwise | ||
// this will return false result, when the `currentValue` is updated. | ||
final isBlockSelected = selectedBlock?.position == block.position; | ||
|
||
// Checking with the current block's position, otherwise | ||
// this will return false when the `currentValue` is updated, hence this | ||
// block will no longer remain highlighted. | ||
final isBlockHighlighted = highlightedBlocks | ||
.map((block) => block.position) | ||
.contains(block.position); | ||
|
||
return ResponsiveLayoutBuilder( | ||
small: (_, child) => SizedBox.square( | ||
dimension: SudokuBoardSize.small / dimension, | ||
key: Key('sudoku_block_small_${block.position.x}_${block.position.y}'), | ||
child: child, | ||
), | ||
medium: (_, child) => SizedBox.square( | ||
dimension: SudokuBoardSize.medium / dimension, | ||
key: Key('sudoku_block_medium_${block.position.x}_${block.position.y}'), | ||
child: child, | ||
), | ||
large: (_, child) => SizedBox.square( | ||
dimension: SudokuBoardSize.large / dimension, | ||
key: Key('sudoku_block_large_${block.position.x}_${block.position.y}'), | ||
child: child, | ||
), | ||
child: (_) { | ||
return GestureDetector( | ||
onTap: () { | ||
context.read<SudokuBloc>().add(SudokuBlockSelected(block)); | ||
}, | ||
child: DecoratedBox( | ||
decoration: BoxDecoration( | ||
color: isBlockSelected | ||
? theme.primaryColorLight | ||
: isBlockHighlighted | ||
? theme.splashColor.withOpacity(0.27) | ||
: null, | ||
border: Border.all( | ||
color: theme.highlightColor, | ||
), | ||
), | ||
child: Center( | ||
child: Text( | ||
block.currentValue != -1 ? '${block.currentValue}' : '', | ||
style: SudokuTextStyle.bodyText1.copyWith( | ||
color: block.isGenerated ? null : theme.colorScheme.secondary, | ||
), | ||
), | ||
), | ||
), | ||
); | ||
}, | ||
); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,72 @@ | ||
import 'dart:math'; | ||
|
||
import 'package:flutter/material.dart'; | ||
import 'package:sudoku/layout/layout.dart'; | ||
import 'package:sudoku/sudoku/sudoku.dart'; | ||
|
||
/// {@template sudoku_board} | ||
/// Displays the Sudoku board in a [Stack] containing [blocks]. | ||
/// {@endtemplate} | ||
class SudokuBoard extends StatelessWidget { | ||
/// {@macro sudoku_board} | ||
const SudokuBoard({required this.blocks, super.key}); | ||
|
||
/// The blocks to be displayed on the Sudoku board. | ||
final List<Widget> blocks; | ||
|
||
@override | ||
Widget build(BuildContext context) { | ||
return ResponsiveLayoutBuilder( | ||
small: (_, child) => SizedBox.square( | ||
key: const Key('sudoku_board_small'), | ||
dimension: SudokuBoardSize.small, | ||
child: child, | ||
), | ||
medium: (_, child) => SizedBox.square( | ||
key: const Key('sudoku_board_medium'), | ||
dimension: SudokuBoardSize.medium, | ||
child: child, | ||
), | ||
large: (_, child) => SizedBox.square( | ||
key: const Key('sudoku_board_large'), | ||
dimension: SudokuBoardSize.large, | ||
child: child, | ||
), | ||
child: (currentSize) { | ||
final boardSize = switch (currentSize) { | ||
ResponsiveLayoutSize.small => SudokuBoardSize.small, | ||
ResponsiveLayoutSize.medium => SudokuBoardSize.medium, | ||
ResponsiveLayoutSize.large => SudokuBoardSize.large, | ||
}; | ||
|
||
final boardDimension = sqrt(blocks.length).toInt(); | ||
final subGridDimension = sqrt(boardDimension).toInt(); | ||
|
||
final blockSize = boardSize / boardDimension; | ||
final subGridSize = subGridDimension * blockSize; | ||
return Stack( | ||
children: [ | ||
...blocks, | ||
IgnorePointer( | ||
child: SudokuBoardDivider( | ||
dimension: boardSize, | ||
width: 1.4, | ||
), | ||
), | ||
for (var i = 0; i < boardDimension; i++) | ||
Positioned( | ||
top: (i % subGridDimension) * subGridSize, | ||
left: (i ~/ subGridDimension) * subGridSize, | ||
child: IgnorePointer( | ||
child: SudokuBoardDivider( | ||
dimension: subGridSize, | ||
width: 0.8, | ||
), | ||
), | ||
), | ||
], | ||
); | ||
}, | ||
); | ||
} | ||
} |
Oops, something went wrong.