Skip to content

Commit

Permalink
feat: add view, and widgets for sudoku board, input
Browse files Browse the repository at this point in the history
  • Loading branch information
thisissandipp committed Jul 5, 2024
1 parent 30ac620 commit 8dc76ac
Show file tree
Hide file tree
Showing 17 changed files with 850 additions and 10 deletions.
5 changes: 3 additions & 2 deletions lib/app/view/app.dart
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import 'package:flutter/material.dart';
import 'package:sudoku/counter/counter.dart';
import 'package:sudoku/l10n/l10n.dart';
import 'package:sudoku/sudoku/sudoku.dart';
import 'package:sudoku/theme/theme.dart';

class App extends StatelessWidget {
Expand All @@ -11,9 +11,10 @@ class App extends StatelessWidget {
return MaterialApp(
theme: SudokuTheme.light,
darkTheme: SudokuTheme.dark,
themeMode: ThemeMode.light,
localizationsDelegates: AppLocalizations.localizationsDelegates,
supportedLocales: AppLocalizations.supportedLocales,
home: const CounterPage(),
home: const SudokuPage(),
);
}
}
2 changes: 2 additions & 0 deletions lib/sudoku/models/models.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export 'sudoku_board_size.dart';
export 'sudoku_input_size.dart';
12 changes: 12 additions & 0 deletions lib/sudoku/models/sudoku_board_size.dart
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;
}
12 changes: 12 additions & 0 deletions lib/sudoku/models/sudoku_input_size.dart
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;
}
3 changes: 3 additions & 0 deletions lib/sudoku/sudoku.dart
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';
175 changes: 175 additions & 0 deletions lib/sudoku/view/sudoku_page.dart
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,
),
),
],
);
},
);
}
}
1 change: 1 addition & 0 deletions lib/sudoku/view/view.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export 'sudoku_page.dart';
93 changes: 93 additions & 0 deletions lib/sudoku/widgets/sudoku_block.dart
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,
),
),
),
),
);
},
);
}
}
72 changes: 72 additions & 0 deletions lib/sudoku/widgets/sudoku_board.dart
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,
),
),
),
],
);
},
);
}
}
Loading

0 comments on commit 8dc76ac

Please sign in to comment.