diff --git a/lib/presentation/views/home/active_game/round_view.dart b/lib/presentation/views/home/active_game/round_view.dart index b20255b..14b1f94 100644 --- a/lib/presentation/views/home/active_game/round_view.dart +++ b/lib/presentation/views/home/active_game/round_view.dart @@ -42,6 +42,8 @@ class _RoundViewState extends State { (index) => FocusNode(), ); + late List _textFieldKeys; + @override void initState() { print('=== Runde ${widget.roundNumber} geƶffnet ==='); @@ -62,6 +64,11 @@ class _RoundViewState extends State { gameSession.roundList[widget.roundNumber - 1].kamikazePlayerIndex; } + _textFieldKeys = List.generate( + widget.gameSession.players.length, + (index) => GlobalKey(), + ); + super.initState(); } @@ -75,7 +82,6 @@ class _RoundViewState extends State { return CupertinoPageScaffold( resizeToAvoidBottomInset: false, navigationBar: CupertinoNavigationBar( - transitionBetweenRoutes: true, leading: CupertinoButton( padding: EdgeInsets.zero, onPressed: () => { @@ -91,11 +97,11 @@ class _RoundViewState extends State { CupertinoIcons.lock, size: 25, ))), - child: Stack( + child: Column( children: [ - Positioned.fill( + Expanded( child: SingleChildScrollView( - padding: EdgeInsets.only(bottom: 100 + bottomInset), + padding: EdgeInsets.only(bottom: 20 + bottomInset), child: SafeArea( child: Column( crossAxisAlignment: CrossAxisAlignment.center, @@ -195,6 +201,7 @@ class _RoundViewState extends State { ' ${AppLocalizations.of(context).points}'), trailing: SizedBox( width: 100, + key: _textFieldKeys[originalIndex], child: CupertinoTextField( maxLength: 3, focusNode: _focusNodeList[originalIndex], @@ -248,11 +255,8 @@ class _RoundViewState extends State { ), ), ), - Positioned( - left: 0, - right: 0, - bottom: bottomInset, - child: KeyboardVisibilityBuilder(builder: (context, visible) { + KeyboardVisibilityBuilder( + builder: (context, visible) { if (!visible) { return Container( height: 80, @@ -284,8 +288,8 @@ class _RoundViewState extends State { } else { return const SizedBox.shrink(); } - }), - ) + }, + ), ], ), ); @@ -371,8 +375,21 @@ class _RoundViewState extends State { final currentPos = originalIndices.indexOf(index); if (currentPos < originalIndices.length - 1) { + final nextIndex = originalIndices[currentPos + 1]; FocusScope.of(context) .requestFocus(_focusNodeList[originalIndices[currentPos + 1]]); + + final scrollContext = _textFieldKeys[nextIndex].currentContext; + if (scrollContext != null) { + WidgetsBinding.instance.addPostFrameCallback((_) { + Scrollable.ensureVisible( + scrollContext, + duration: const Duration(milliseconds: 300), + curve: Curves.easeOut, + alignment: 0.55, + ); + }); + } } else { _focusNodeList[index].unfocus(); } diff --git a/lib/presentation/views/home/create_game_view.dart b/lib/presentation/views/home/create_game_view.dart index 8b18bfc..2a3f3d8 100644 --- a/lib/presentation/views/home/create_game_view.dart +++ b/lib/presentation/views/home/create_game_view.dart @@ -77,249 +77,260 @@ class _CreateGameViewState extends State { @override Widget build(BuildContext context) { - return CupertinoPageScaffold( - resizeToAvoidBottomInset: false, - navigationBar: CupertinoNavigationBar( - previousPageTitle: AppLocalizations.of(context).games, - middle: Text(AppLocalizations.of(context).new_game), - ), - child: SafeArea( - child: SingleChildScrollView( - child: Column( - mainAxisAlignment: MainAxisAlignment.start, - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Padding( - padding: const EdgeInsets.fromLTRB(10, 10, 0, 0), - child: Text( - AppLocalizations.of(context).game, - style: CustomTheme.rowTitle, - ), - ), - Padding( - padding: const EdgeInsets.fromLTRB(15, 10, 10, 0), - child: CupertinoTextField( - decoration: const BoxDecoration(), - maxLength: 20, - prefix: Text(AppLocalizations.of(context).name), - textAlign: TextAlign.right, - placeholder: AppLocalizations.of(context).game_title, - controller: _gameTitleTextController, - onSubmitted: (_) { - _playerNameFocusNodes.isNotEmpty - ? _playerNameFocusNodes[0].requestFocus() - : FocusScope.of(context).unfocus(); - }, - textInputAction: _playerNameFocusNodes.isNotEmpty - ? TextInputAction.next - : TextInputAction.done, - ), - ), - Padding( - padding: const EdgeInsets.fromLTRB(15, 10, 10, 0), - child: CupertinoTextField( - decoration: const BoxDecoration(), - readOnly: true, - prefix: Text(AppLocalizations.of(context).mode), - suffix: Row( - children: [ - _getDisplayedGameMode(), - const SizedBox(width: 3), - const CupertinoListTileChevron(), - ], + return PopScope( + canPop: false, + onPopInvokedWithResult: (bool didPop, dynamic result) async { + if (!didPop) { + await _keyboardDelay(); + if (context.mounted) Navigator.pop(context); + } + }, + child: CupertinoPageScaffold( + resizeToAvoidBottomInset: false, + navigationBar: CupertinoNavigationBar( + previousPageTitle: AppLocalizations.of(context).games, + middle: Text(AppLocalizations.of(context).new_game), + ), + child: SafeArea( + child: SingleChildScrollView( + child: Column( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Padding( + padding: const EdgeInsets.fromLTRB(10, 10, 0, 0), + child: Text( + AppLocalizations.of(context).game, + style: CustomTheme.rowTitle, + ), ), - onTap: () async { - final selectedMode = await Navigator.push( - context, - CupertinoPageRoute( - builder: (context) => ModeSelectionMenu( - pointLimit: ConfigService.getPointLimit(), - showDeselection: false, - ), - ), - ); - - setState(() { - gameMode = selectedMode ?? gameMode; - }); - }, - ), - ), - Padding( - padding: const EdgeInsets.fromLTRB(10, 10, 0, 0), - child: Text( - AppLocalizations.of(context).players, - style: CustomTheme.rowTitle, - ), - ), - ReorderableListView.builder( - shrinkWrap: true, - physics: const BouncingScrollPhysics(), - padding: const EdgeInsets.all(8), - itemCount: _playerNameTextControllers.length, - onReorder: (oldIndex, newIndex) { - setState(() { - if (oldIndex < _playerNameTextControllers.length && - newIndex <= _playerNameTextControllers.length) { - if (newIndex > oldIndex) newIndex--; - final item = - _playerNameTextControllers.removeAt(oldIndex); - _playerNameTextControllers.insert(newIndex, item); - } - }); - }, - itemBuilder: (context, index) { - return Padding( - key: ValueKey(index), - padding: const EdgeInsets.symmetric(vertical: 8.0), - child: Row( + Padding( + padding: const EdgeInsets.fromLTRB(15, 10, 10, 0), + child: CupertinoTextField( + decoration: const BoxDecoration(), + maxLength: 20, + prefix: Text(AppLocalizations.of(context).name), + textAlign: TextAlign.right, + placeholder: AppLocalizations.of(context).game_title, + controller: _gameTitleTextController, + onSubmitted: (_) { + _playerNameFocusNodes.isNotEmpty + ? _playerNameFocusNodes[0].requestFocus() + : FocusScope.of(context).unfocus(); + }, + textInputAction: _playerNameFocusNodes.isNotEmpty + ? TextInputAction.next + : TextInputAction.done, + ), + ), + Padding( + padding: const EdgeInsets.fromLTRB(15, 10, 10, 0), + child: CupertinoTextField( + decoration: const BoxDecoration(), + readOnly: true, + prefix: Text(AppLocalizations.of(context).mode), + suffix: Row( children: [ - CupertinoButton( - padding: EdgeInsets.zero, - child: Icon( - CupertinoIcons.minus_circle_fill, - color: CustomTheme.red, - size: 25, - ), - onPressed: () { - setState(() { - _playerNameTextControllers[index].dispose(); - _playerNameTextControllers.removeAt(index); - }); - }, - ), - Expanded( - child: CupertinoTextField( - controller: _playerNameTextControllers[index], - focusNode: _playerNameFocusNodes[index], - maxLength: 12, - placeholder: - '${AppLocalizations.of(context).player} ${index + 1}', - padding: const EdgeInsets.all(12), - decoration: const BoxDecoration(), - textInputAction: - index + 1 < _playerNameTextControllers.length - ? TextInputAction.next - : TextInputAction.done, - onSubmitted: (_) { - if (index + 1 < _playerNameFocusNodes.length) { - _playerNameFocusNodes[index + 1] - .requestFocus(); - } else { - FocusScope.of(context).unfocus(); - } - }, - ), - ), - AnimatedOpacity( - opacity: _playerNameTextControllers.length > 1 - ? 1.0 - : 0.0, - duration: const Duration( - milliseconds: Constants.kFadeInDuration), - child: Padding( - padding: const EdgeInsets.only(right: 8.0), - child: ReorderableDragStartListener( - index: index, - child: const Icon( - CupertinoIcons.line_horizontal_3, - color: CupertinoColors.systemGrey, - ), - ), - ), - ) + _getDisplayedGameMode(), + const SizedBox(width: 3), + const CupertinoListTileChevron(), ], ), - ); - }), - Padding( - padding: const EdgeInsets.fromLTRB(8, 0, 8, 50), - child: Stack( - children: [ - Row( - mainAxisAlignment: MainAxisAlignment.start, - children: [ - CupertinoButton( - padding: EdgeInsets.zero, - onPressed: null, - child: Icon( - CupertinoIcons.plus_circle_fill, - color: CustomTheme.primaryColor, - size: 25, - ), - ), - ], + onTap: () async { + await _keyboardDelay(); + + if (context.mounted) { + final selectedMode = await Navigator.push( + context, + CupertinoPageRoute( + builder: (context) => ModeSelectionMenu( + pointLimit: ConfigService.getPointLimit(), + showDeselection: false, + ), + ), + ); + + setState(() { + gameMode = selectedMode ?? gameMode; + }); + } + }, ), - Center( - child: CupertinoButton( - padding: const EdgeInsets.symmetric(horizontal: 0), - child: Row( + ), + Padding( + padding: const EdgeInsets.fromLTRB(10, 10, 0, 0), + child: Text( + AppLocalizations.of(context).players, + style: CustomTheme.rowTitle, + ), + ), + ReorderableListView.builder( + shrinkWrap: true, + physics: const BouncingScrollPhysics(), + padding: const EdgeInsets.all(8), + itemCount: _playerNameTextControllers.length, + onReorder: (oldIndex, newIndex) { + setState(() { + if (oldIndex < _playerNameTextControllers.length && + newIndex <= _playerNameTextControllers.length) { + if (newIndex > oldIndex) newIndex--; + final item = + _playerNameTextControllers.removeAt(oldIndex); + _playerNameTextControllers.insert(newIndex, item); + } + }); + }, + itemBuilder: (context, index) { + return Padding( + key: ValueKey(index), + padding: const EdgeInsets.symmetric(vertical: 8.0), + child: Row( + children: [ + CupertinoButton( + padding: EdgeInsets.zero, + child: Icon( + CupertinoIcons.minus_circle_fill, + color: CustomTheme.red, + size: 25, + ), + onPressed: () { + setState(() { + _playerNameTextControllers[index].dispose(); + _playerNameTextControllers.removeAt(index); + }); + }, + ), + Expanded( + child: CupertinoTextField( + controller: _playerNameTextControllers[index], + focusNode: _playerNameFocusNodes[index], + maxLength: 12, + placeholder: + '${AppLocalizations.of(context).player} ${index + 1}', + padding: const EdgeInsets.all(12), + decoration: const BoxDecoration(), + textInputAction: index + 1 < + _playerNameTextControllers.length + ? TextInputAction.next + : TextInputAction.done, + onSubmitted: (_) { + if (index + 1 < + _playerNameFocusNodes.length) { + _playerNameFocusNodes[index + 1] + .requestFocus(); + } else { + FocusScope.of(context).unfocus(); + } + }, + ), + ), + AnimatedOpacity( + opacity: _playerNameTextControllers.length > 1 + ? 1.0 + : 0.0, + duration: const Duration( + milliseconds: Constants.kFadeInDuration), + child: Padding( + padding: const EdgeInsets.only(right: 8.0), + child: ReorderableDragStartListener( + index: index, + child: const Icon( + CupertinoIcons.line_horizontal_3, + color: CupertinoColors.systemGrey, + ), + ), + ), + ) + ], + ), + ); + }), + Padding( + padding: const EdgeInsets.fromLTRB(8, 0, 8, 50), + child: Stack( + children: [ + Row( mainAxisAlignment: MainAxisAlignment.start, children: [ - Expanded( - child: Center( - child: Text( - AppLocalizations.of(context).add_player, - style: TextStyle( - color: CustomTheme.primaryColor), - ), + CupertinoButton( + padding: EdgeInsets.zero, + onPressed: null, + child: Icon( + CupertinoIcons.plus_circle_fill, + color: CustomTheme.primaryColor, + size: 25, ), ), ], ), - onPressed: () { - if (_playerNameTextControllers.length < maxPlayers) { - setState(() { - _playerNameTextControllers - .add(TextEditingController()); - _playerNameFocusNodes.add(FocusNode()); - }); - WidgetsBinding.instance.addPostFrameCallback((_) { - _playerNameFocusNodes.last.requestFocus(); - }); - } else { - _showFeedbackDialog(CreateStatus.maxPlayers); - } + Center( + child: CupertinoButton( + padding: const EdgeInsets.symmetric(horizontal: 0), + child: Row( + mainAxisAlignment: MainAxisAlignment.start, + children: [ + Expanded( + child: Center( + child: Text( + AppLocalizations.of(context).add_player, + style: TextStyle( + color: CustomTheme.primaryColor), + ), + ), + ), + ], + ), + onPressed: () { + if (_playerNameTextControllers.length < + maxPlayers) { + setState(() { + _playerNameTextControllers + .add(TextEditingController()); + _playerNameFocusNodes.add(FocusNode()); + }); + WidgetsBinding.instance + .addPostFrameCallback((_) { + _playerNameFocusNodes.last.requestFocus(); + }); + } else { + _showFeedbackDialog(CreateStatus.maxPlayers); + } + }, + ), + ), + ], + ), + ), + Padding( + padding: const EdgeInsets.fromLTRB(0, 0, 0, 50), + child: Center( + child: CustomButton( + child: Text( + AppLocalizations.of(context).create_game, + style: TextStyle( + color: CustomTheme.primaryColor, + ), + ), + onPressed: () async { + await _keyboardDelay(); + _checkAllGameAttributes(); }, ), ), - ], - ), - ), - Padding( - padding: const EdgeInsets.fromLTRB(0, 0, 0, 50), - child: Center( - child: CustomButton( - child: Text( - AppLocalizations.of(context).create_game, - style: TextStyle( - color: CustomTheme.primaryColor, - ), - ), - onPressed: () { - FocusScope.of(context).unfocus(); - Future.delayed( - const Duration( - milliseconds: Constants.kKeyboardDelay), () { - _checkAllGameAttributes(); - }); - }, ), - ), + KeyboardVisibilityBuilder(builder: (context, visible) { + if (visible) { + return SizedBox( + height: MediaQuery.of(context).viewInsets.bottom * + keyboardHeightAdjustmentFactor, + ); + } else { + return const SizedBox.shrink(); + } + }) + ], ), - KeyboardVisibilityBuilder(builder: (context, visible) { - if (visible) { - return SizedBox( - height: MediaQuery.of(context).viewInsets.bottom * - keyboardHeightAdjustmentFactor, - ); - } else { - return const SizedBox.shrink(); - } - }) - ], - ), - ))); + )))); } /// Returns a widget that displays the currently selected game mode in the View. @@ -460,6 +471,18 @@ class _CreateGameViewState extends State { ); } + /// If the keyboard is visible, this method will unfocus the current text field + /// to prevent the keyboard from interfering with the navigation bar. + Future _keyboardDelay() async { + if (!KeyboardVisibilityController().isVisible) { + return; + } else { + FocusScope.of(context).unfocus(); + await Future.delayed( + const Duration(milliseconds: Constants.kKeyboardDelay)); + } + } + @override void dispose() { _gameTitleTextController.dispose();