diff --git a/lib/src/about_screen.dart b/lib/src/about_screen.dart index f35fdff..3b5e75c 100644 --- a/lib/src/about_screen.dart +++ b/lib/src/about_screen.dart @@ -98,17 +98,6 @@ class _AboutScreenState extends State { Colors.white.withOpacity(0.15), label: 'DONE', ), - CheckboxListTile( - title: const Text("Is Demo TV"), - value: - RpgLayoutBuilder.forcedLayout == - RpgLayout.demoTv, - onChanged: (value) => setState(() => - RpgLayoutBuilder.forcedLayout = - value - ? RpgLayout.demoTv - : null), - ), ], ), ), diff --git a/lib/src/code_chomper/code_chomper.dart b/lib/src/code_chomper/code_chomper.dart index 2478dd6..270c5fb 100644 --- a/lib/src/code_chomper/code_chomper.dart +++ b/lib/src/code_chomper/code_chomper.dart @@ -1,5 +1,7 @@ library chompy; +import 'dart:math'; + import 'package:flare_flutter/flare_actor.dart'; import 'package:flutter/material.dart'; import 'package:flutter/rendering.dart'; @@ -13,6 +15,7 @@ const TextStyle chompTextStyle = TextStyle(fontFamily: 'SpaceMonoRegular', fontSize: 12, color: chompBlue); const double _keyDefaultWidth = 32; const double _keyPadding = 5; +const double _maxWidth = 1000; /// The main screen for the code chomper mini game. class CodeChomper extends StatefulWidget { @@ -43,106 +46,111 @@ class _CodeChomperState extends State { padding: EdgeInsets.only( top: devicePadding.top + 11, ), - child: Column( - mainAxisSize: MainAxisSize.max, - children: [ - Padding( - padding: const EdgeInsets.symmetric(horizontal: 15), - child: Column( - children: [ - Row( + child: Center( + child: Container( + constraints: const BoxConstraints(maxWidth: _maxWidth), + child: Column( + mainAxisSize: MainAxisSize.max, + children: [ + Padding( + padding: const EdgeInsets.symmetric(horizontal: 15), + child: Column( children: [ - const Text( - 'CODE CHOMPER: ', - style: chompTextStyle, - ), - Text( - 'ACTIVE', - style: chompTextStyle.apply( - fontFamily: 'SpaceMonoBold', - color: const Color.fromRGBO(206, 83, 83, 1), - ), - ), - Expanded(child: Container()), - Container( - width: 120, - height: 10, - decoration: BoxDecoration( - borderRadius: - const BorderRadius.all(Radius.circular(5)), - color: chompBlue.withOpacity(0.24), - ), - child: Row( - children: [ - for (int i = 0; i < 5; i++) - Container( - margin: - const EdgeInsets.only(left: 3, right: 3), - height: 3, - width: 18, - decoration: const BoxDecoration( - borderRadius: BorderRadius.all( - Radius.circular(1.5), + Row( + children: [ + const Text( + 'CODE CHOMPER: ', + style: chompTextStyle, + ), + Text( + 'ACTIVE', + style: chompTextStyle.apply( + fontFamily: 'SpaceMonoBold', + color: const Color.fromRGBO(206, 83, 83, 1), + ), + ), + Expanded(child: Container()), + Container( + width: 120, + height: 10, + decoration: BoxDecoration( + borderRadius: + const BorderRadius.all(Radius.circular(5)), + color: chompBlue.withOpacity(0.24), + ), + child: Row( + children: [ + for (int i = 0; i < 5; i++) + Container( + margin: const EdgeInsets.only( + left: 3, right: 3), + height: 3, + width: 18, + decoration: const BoxDecoration( + borderRadius: BorderRadius.all( + Radius.circular(1.5), + ), + color: chompBlue, + ), ), - color: chompBlue, - ), + ], + ), + ), + ], + ), + const SizedBox(height: 6), + Container(height: 1, color: chompBlue), + const SizedBox(height: 6), + Row( + children: [ + for (int i = 0; i < 3; i++) + Expanded( + child: Container( + margin: EdgeInsets.only(right: i == 2 ? 0 : 5), + height: 1, + color: chompBlue.withOpacity(0.5), ), - ], - ), + ), + ], ), ], ), - const SizedBox(height: 6), - Container(height: 1, color: chompBlue), - const SizedBox(height: 6), - Row( + ), + Expanded( + child: Stack( children: [ - for (int i = 0; i < 3; i++) - Expanded( - child: Container( - margin: EdgeInsets.only(right: i == 2 ? 0 : 5), - height: 1, - color: chompBlue.withOpacity(0.5), - ), - ), - ], - ), - ], - ), - ), - Expanded( - child: Stack( - children: [ - Positioned.fill( - child: Column( - children: [ - // Game renderer goes here - Expanded( - child: CodeChomperScreen(_chomperController), - ), + Positioned.fill( + child: Column( + children: [ + // Game renderer goes here + Expanded( + child: CodeChomperScreen(_chomperController), + ), - GestureDetector( - behavior: HitTestBehavior.opaque, - onTapDown: (details) { - if (_chomperController - .addCode(details.globalPosition)) { - setState(() { - _isGameOver = true; - }); - } - }, - child: _ChompyKeyboard(), + GestureDetector( + behavior: HitTestBehavior.opaque, + onTapDown: (details) { + if (_chomperController + .addCode(details.globalPosition)) { + setState(() { + _isGameOver = true; + }); + } + }, + child: _ChompyKeyboard(), + ), + ], ), - ], - ), - ), - Positioned.fill( - child: _GameOverScreen(isGameOver: _isGameOver), + ), + Positioned.fill( + child: _GameOverScreen(isGameOver: _isGameOver), + ), + ], ), - ], - ), + ), + ], ), - ], + ), ), ), ); @@ -209,8 +217,8 @@ class _ChompyKeyboard extends StatelessWidget { Widget build(BuildContext context) { var media = MediaQuery.of(context); var devicePadding = media.padding; - var defaultKeysPerRow = - (media.size.width / (_keyDefaultWidth + _keyPadding)).floor(); + var width = min(_maxWidth, media.size.width); + var defaultKeysPerRow = (width / (_keyDefaultWidth + _keyPadding)).floor(); return Container( color: Colors.black.withOpacity(0.33), @@ -244,11 +252,11 @@ class _ChompyKeyboard extends StatelessWidget { Row( mainAxisAlignment: MainAxisAlignment.center, children: [ - _ChompyKey(media.size.width / 5), + _ChompyKey(width / 5), const SizedBox(width: 2), - _ChompyKey(media.size.width / 2), + _ChompyKey(width / 2), const SizedBox(width: 2), - _ChompyKey(media.size.width / 5), + _ChompyKey(width / 5), ], ), SizedBox(height: devicePadding.bottom), diff --git a/lib/src/game_screen.dart b/lib/src/game_screen.dart index a4737e3..6059835 100644 --- a/lib/src/game_screen.dart +++ b/lib/src/game_screen.dart @@ -60,8 +60,8 @@ class _GameScreenState extends State { case DemoModeAction.showCharacterModal: // Hire any available character. for (final character in world.characterPool.children) { - if (character.canUpgrade) { - character.upgrade(); + if (character.canUpgradeOrHire) { + character.hire(); break; } } @@ -113,9 +113,8 @@ class _GameScreenState extends State { _scheduleInactivityTimer(context); }, child: RpgLayoutBuilder( - builder: (context, layout) => layout != RpgLayout.slim - ? GameScreenWide(layout == RpgLayout.demoTv) - : GameScreenSlim(), + builder: (context, layout) => + layout == RpgLayout.slim ? GameScreenSlim() : GameScreenWide(), ), ); } diff --git a/lib/src/game_screen/add_task_button.dart b/lib/src/game_screen/add_task_button.dart index 4e49e9d..b5d3717 100644 --- a/lib/src/game_screen/add_task_button.dart +++ b/lib/src/game_screen/add_task_button.dart @@ -8,10 +8,17 @@ class AddTaskButton extends StatefulWidget { final String label; final int count; final VoidCallback onPressed; + final double scale; - const AddTaskButton(this.label, - {Key key, this.count = 0, this.icon, this.color, this.onPressed}) - : super(key: key); + const AddTaskButton( + this.label, { + Key key, + this.count = 0, + this.icon, + this.color, + this.onPressed, + this.scale = 1.0, + }) : super(key: key); @override _AddTaskButtonState createState() => _AddTaskButtonState(); @@ -53,7 +60,7 @@ class _AddTaskButtonState extends State { onTap: isDisabled ? null : onTap, child: AnimatedContainer( duration: const Duration(milliseconds: 100), - height: 40, + height: 40 * widget.scale, padding: const EdgeInsets.symmetric(horizontal: 8), decoration: BoxDecoration( boxShadow: isDisabled @@ -65,7 +72,7 @@ class _AddTaskButtonState extends State { blurRadius: _isPressed ? 10 : 15, spreadRadius: 0), ], - borderRadius: const BorderRadius.all(Radius.circular(20)), + borderRadius: BorderRadius.all(Radius.circular(20 * widget.scale)), color: isDisabled ? disabledTaskColor.withOpacity(0.10) : _isPressed ? widget.color.withOpacity(0.8) : widget.color, @@ -80,16 +87,17 @@ class _AddTaskButtonState extends State { widget.label, style: buttonTextStyle.apply( fontSizeDelta: -2, + fontSizeFactor: widget.scale, color: isDisabled ? disabledTaskColor : Colors.white), ), ), isDisabled ? Container() : Container( - constraints: const BoxConstraints( - minWidth: 26, - minHeight: 26, - maxHeight: 26, + constraints: BoxConstraints( + minWidth: 26 * widget.scale, + minHeight: 26 * widget.scale, + maxHeight: 26 * widget.scale, ), decoration: const BoxDecoration( shape: BoxShape.circle, @@ -98,7 +106,9 @@ class _AddTaskButtonState extends State { child: Center( child: Text(widget.count.toString(), style: buttonTextStyle.apply( - fontSizeDelta: -2, color: widget.color)), + fontSizeDelta: -2, + fontSizeFactor: widget.scale, + color: widget.color)), ), ), ], diff --git a/lib/src/game_screen/character_modal.dart b/lib/src/game_screen/character_modal.dart index f49f8ff..2ab99ae 100644 --- a/lib/src/game_screen/character_modal.dart +++ b/lib/src/game_screen/character_modal.dart @@ -170,13 +170,14 @@ class UpgradeHireButton extends StatelessWidget { Widget build(BuildContext context) { var character = Provider.of(context); return WideButton( + enabled: character.canUpgradeOrHire, onPressed: () { - if (character.canUpgrade && character.upgrade()) { + if (character.upgradeOrHire()) { _controls.play('success'); } }, paddingTweak: const EdgeInsets.only(right: -7), - background: character.canUpgrade + background: character.canUpgradeOrHire ? const Color.fromRGBO(84, 114, 239, 1) : contentColor.withOpacity(0.1), child: Row( @@ -184,7 +185,7 @@ class UpgradeHireButton extends StatelessWidget { Text( character.isHired ? 'UPGRADE' : 'HIRE', style: buttonTextStyle.apply( - color: character.canUpgrade + color: character.canUpgradeOrHire ? Colors.white : contentColor.withOpacity(0.25), ), @@ -203,7 +204,7 @@ class UpgradeHireButton extends StatelessWidget { character.upgradeCost.toString(), style: buttonTextStyle.apply( fontSizeDelta: -2, - color: character.canUpgrade + color: character.canUpgradeOrHire ? const Color.fromRGBO(241, 241, 241, 1) : contentColor.withOpacity(0.25), ), diff --git a/lib/src/game_screen/character_pool_page.dart b/lib/src/game_screen/character_pool_page.dart index 786608e..c179730 100644 --- a/lib/src/game_screen/character_pool_page.dart +++ b/lib/src/game_screen/character_pool_page.dart @@ -1,5 +1,6 @@ import 'package:dev_rpg/src/game_screen/character_modal.dart'; import 'package:dev_rpg/src/game_screen/character_style.dart'; +import 'package:dev_rpg/src/rpg_layout_builder.dart'; import 'package:dev_rpg/src/shared_state/game/character.dart'; import 'package:dev_rpg/src/shared_state/game/character_pool.dart'; import 'package:dev_rpg/src/style.dart'; @@ -120,7 +121,7 @@ class CharacterDisplay extends StatelessWidget { var characterStyle = CharacterStyle.from(character); HiringBustState bustState = character.isHired ? HiringBustState.hired - : character.canUpgrade + : character.canUpgradeOrHire ? HiringBustState.available : HiringBustState.locked; return Material( @@ -173,33 +174,41 @@ class HiringInformation extends StatelessWidget { @override Widget build(BuildContext context) { - var character = Provider.of(context); - return Opacity( - opacity: character.isHired || character.canUpgrade ? 1 : 0.25, - child: Row( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - character.isHired - ? Container() - : Icon( - bustState == HiringBustState.available - ? Icons.add_circle - : Icons.lock, - color: !character.isHired && character.canUpgrade - ? attentionColor - : Colors.white), - const SizedBox(width: 4), - Text( - bustState == HiringBustState.hired - ? 'Hired' - : bustState == HiringBustState.available ? 'Hire!' : 'Locked', - style: contentStyle.apply( - color: bustState == HiringBustState.available - ? attentionColor - : Colors.white), - ) - ], - ), + return RpgLayoutBuilder( + builder: (context, layout) { + double textScale = layout == RpgLayout.ultrawide ? 1.25 : 1; + var character = Provider.of(context); + return Opacity( + opacity: character.isHired || character.canUpgradeOrHire ? 1 : 0.25, + child: Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + character.isHired + ? Container() + : Icon( + bustState == HiringBustState.available + ? Icons.add_circle + : Icons.lock, + color: !character.isHired && character.canUpgradeOrHire + ? attentionColor + : Colors.white), + const SizedBox(width: 4), + Text( + bustState == HiringBustState.hired + ? 'Hired' + : bustState == HiringBustState.available + ? 'Hire!' + : 'Locked', + style: contentStyle.apply( + fontSizeFactor: textScale, + color: bustState == HiringBustState.available + ? attentionColor + : Colors.white), + ) + ], + ), + ); + }, ); } } diff --git a/lib/src/game_screen/task_pool_page.dart b/lib/src/game_screen/task_pool_page.dart index 0c931e3..63f9c2f 100644 --- a/lib/src/game_screen/task_pool_page.dart +++ b/lib/src/game_screen/task_pool_page.dart @@ -1,3 +1,4 @@ +import 'package:dev_rpg/src/rpg_layout_builder.dart'; import 'package:dev_rpg/src/shared_state/game/bug.dart'; import 'package:dev_rpg/src/shared_state/game/task.dart'; import 'package:dev_rpg/src/shared_state/game/task_pool.dart'; @@ -20,12 +21,12 @@ class TaskPoolPage extends StatelessWidget { /// Builds a section of the task list with [title] and a list of [workItems]. /// This returns slivers to be used in a [SliverList]. - void _buildSection( - List slivers, String title, List workItems) { + void _buildSection(List slivers, String title, double scale, + List workItems) { if (workItems.isNotEmpty) { slivers.add(SliverPersistentHeader( pinned: false, - delegate: TasksSectionHeader(title), + delegate: TasksSectionHeader(title, scale), )); slivers.add(SliverList( delegate: SliverChildBuilderDelegate((context, index) { @@ -42,38 +43,42 @@ class TaskPoolPage extends StatelessWidget { @override Widget build(BuildContext context) { - return Container( - color: display == TaskPoolDisplay.completed - ? const Color.fromRGBO(229, 229, 229, 1) - : const Color.fromRGBO(241, 241, 241, 1), - child: Consumer( - builder: (context, taskPool) { - var slivers = []; + return RpgLayoutBuilder(builder: (context, layout) { + double scale = layout == RpgLayout.ultrawide ? 1.25 : 1; + return Container( + color: display == TaskPoolDisplay.completed + ? const Color.fromRGBO(229, 229, 229, 1) + : const Color.fromRGBO(241, 241, 241, 1), + child: Consumer( + builder: (context, taskPool, _) { + var slivers = []; - // Add the header only if we show the in progress tasks. - if (display == TaskPoolDisplay.all || - display == TaskPoolDisplay.inProgress) { - slivers.add( - SliverPersistentHeader( - pinned: false, - delegate: TasksButtonHeader(taskPool: taskPool), - ), - ); - _buildSection(slivers, 'IN PROGRESS', taskPool.workItems); - } + // Add the header only if we show the in progress tasks. + if (display == TaskPoolDisplay.all || + display == TaskPoolDisplay.inProgress) { + slivers.add( + SliverPersistentHeader( + pinned: false, + delegate: TasksButtonHeader(taskPool: taskPool, scale: scale), + ), + ); + _buildSection(slivers, 'IN PROGRESS', scale, taskPool.workItems); + } - if (display == TaskPoolDisplay.all || - display == TaskPoolDisplay.completed) { - _buildSection( - slivers, - 'COMPLETED', - taskPool.completedTasks - .followedBy(taskPool.archivedTasks) - .toList(growable: false)); - } - return CustomScrollView(slivers: slivers); - }, - ), - ); + if (display == TaskPoolDisplay.all || + display == TaskPoolDisplay.completed) { + _buildSection( + slivers, + 'COMPLETED', + scale, + taskPool.completedTasks + .followedBy(taskPool.archivedTasks) + .toList(growable: false)); + } + return CustomScrollView(slivers: slivers); + }, + ), + ); + }); } } diff --git a/lib/src/game_screen/team_picker_modal.dart b/lib/src/game_screen/team_picker_modal.dart index 6092b6b..a52b961 100644 --- a/lib/src/game_screen/team_picker_modal.dart +++ b/lib/src/game_screen/team_picker_modal.dart @@ -51,9 +51,9 @@ class TeamPickerModalState extends State { // If we're showing the wide layout, make sure this modal // isn't too tall by using a factor of the same width // constraint as a constraint for the height. - maxHeight: layout != RpgLayout.slim - ? modalMaxWidth * 1.1 - : double.infinity), + maxHeight: layout == RpgLayout.slim + ? double.infinity + : modalMaxWidth * 1.1), child: ClipRRect( borderRadius: const BorderRadius.only( topLeft: Radius.circular(10), @@ -87,7 +87,7 @@ class TeamPickerModalState extends State { ), Expanded( child: Consumer( - builder: (context, characterPool) { + builder: (context, characterPool, child) { var characters = characterPool.fullTeam; return characters.isEmpty ? Center( diff --git a/lib/src/game_screen_slim.dart b/lib/src/game_screen_slim.dart index 120fe34..aaa3a10 100644 --- a/lib/src/game_screen_slim.dart +++ b/lib/src/game_screen_slim.dart @@ -82,7 +82,7 @@ class GameScreenSlimState extends State { automaticallyImplyLeading: false, titleSpacing: 0, title: Consumer( - builder: (context, company) { + builder: (context, company, child) { // Using RepaintBoundary here because this part of the UI // changes frequently. return RepaintBoundary( @@ -112,7 +112,7 @@ class GameScreenSlimState extends State { bottomNavigationBar: Row( children: [ Consumer( - builder: (context, characterPool) => _BottomNavigationButton( + builder: (context, characterPool, child) => _BottomNavigationButton( 'assets/flare/TeamIcon.flr', label: 'Team', tap: () => _showPageIndex(0), diff --git a/lib/src/game_screen_wide.dart b/lib/src/game_screen_wide.dart index 289891e..d0a1211 100644 --- a/lib/src/game_screen_wide.dart +++ b/lib/src/game_screen_wide.dart @@ -1,6 +1,7 @@ import 'dart:math'; import 'package:dev_rpg/src/game_screen/character_pool_page.dart'; +import 'package:dev_rpg/src/rpg_layout_builder.dart'; import 'package:dev_rpg/src/shared_state/game/company.dart'; import 'package:dev_rpg/src/style.dart'; import 'package:dev_rpg/src/widgets/app_bar/coin_badge.dart'; @@ -13,65 +14,95 @@ import 'package:provider/provider.dart'; import 'game_screen/task_pool_page.dart'; class GameScreenWide extends StatelessWidget { - final bool isDemoTv; - const GameScreenWide(this.isDemoTv); @override Widget build(BuildContext context) { var availableWidth = MediaQuery.of(context).size.width; var taskColumnWidth = min(modalMaxWidth, availableWidth / 3); var charactersWidth = availableWidth - taskColumnWidth * 2; var numCharacterColumns = - (charactersWidth / idealCharacterWidth).round().clamp(2, 4); + (charactersWidth / idealCharacterWidth).round().clamp(2, 4).toInt(); - return Scaffold( - backgroundColor: const Color.fromRGBO(59, 59, 73, 1), - appBar: AppBar( - automaticallyImplyLeading: false, - titleSpacing: 0, - title: Consumer( - builder: (context, company) { - // Using RepaintBoundary here because this part of the UI - // changes frequently. - return RepaintBoundary( - child: Container( - decoration: BoxDecoration( - border: Border( - top: const BorderSide( - color: statsSeparatorColor, - style: BorderStyle.solid, + return RpgLayoutBuilder( + builder: (context, layout) { + double statsScale = layout == RpgLayout.ultrawide ? 1.25 : 1; + double statsWidth = layout == RpgLayout.ultrawide ? 300 : 125; + return Scaffold( + backgroundColor: const Color.fromRGBO(59, 59, 73, 1), + appBar: AppBar( + automaticallyImplyLeading: false, + titleSpacing: 0, + title: Consumer( + builder: (context, company, _) { + // Using RepaintBoundary here because this part of the UI + // changes frequently. + return RepaintBoundary( + child: Container( + decoration: BoxDecoration( + border: Border( + top: const BorderSide( + color: statsSeparatorColor, + style: BorderStyle.solid, + ), + ), + ), + child: Row( + children: [ + Container( + width: statsWidth, + child: UsersBadge( + company.users, + scale: statsScale, + isWide: layout == RpgLayout.ultrawide, + ), + ), + StatSeparator(), + Container( + width: statsWidth, + child: JoyBadge( + company.joy, + scale: statsScale, + isWide: layout == RpgLayout.ultrawide, + ), + ), + StatSeparator(), + layout == RpgLayout.ultrawide + ? Container( + width: statsWidth, + child: CoinBadge( + company.coin, + scale: statsScale, + isWide: layout == RpgLayout.ultrawide, + ), + ) + : Expanded( + child: + CoinBadge(company.coin, scale: statsScale)), + ], ), ), - ), - child: Row( - children: [ - Container(width: 125, child: UsersBadge(company.users)), - StatSeparator(), - Container(width: 125, child: JoyBadge(company.joy)), - StatSeparator(), - Expanded(child: CoinBadge(company.coin)), - ], - ), - ), - ); - }, - ), - ), - body: Row( - children: [ - SizedBox( - width: charactersWidth, - child: CharacterPoolPage(numColumns: numCharacterColumns), - ), - SizedBox( - width: taskColumnWidth, - child: const TaskPoolPage(display: TaskPoolDisplay.inProgress), + ); + }, + ), ), - SizedBox( - width: taskColumnWidth, - child: const TaskPoolPage(display: TaskPoolDisplay.completed), + body: Row( + children: [ + SizedBox( + width: charactersWidth, + child: + CharacterPoolPage(numColumns: numCharacterColumns.toInt()), + ), + SizedBox( + width: taskColumnWidth, + child: const TaskPoolPage(display: TaskPoolDisplay.inProgress), + ), + SizedBox( + width: taskColumnWidth, + child: const TaskPoolPage(display: TaskPoolDisplay.completed), + ), + ], ), - ], - ), + ); + }, ); } } diff --git a/lib/src/rpg_layout_builder.dart b/lib/src/rpg_layout_builder.dart index 27a9a43..82ec830 100644 --- a/lib/src/rpg_layout_builder.dart +++ b/lib/src/rpg_layout_builder.dart @@ -2,7 +2,7 @@ import 'package:dev_rpg/src/style.dart'; import 'package:flutter/material.dart'; /// Layout for the dev_rpg game. -enum RpgLayout { slim, wide, demoTv } +enum RpgLayout { slim, wide, ultrawide } /// Signature for a function that builds a widget given an [RpgLayout]. /// @@ -18,17 +18,14 @@ class RpgLayoutBuilder extends StatelessWidget { }) : assert(builder != null), super(key: key); - // For demo purposes only, a global to force a specific layout. - static RpgLayout forcedLayout; - /// Builds the widgets below this widget given this widget's layout width. final RpgLayoutWidgetBuilder builder; Widget _build(BuildContext context, BoxConstraints constraints) { - final RpgLayout layout = forcedLayout ?? - (MediaQuery.of(context).size.width > wideLayoutThreshold - ? RpgLayout.wide - : RpgLayout.slim); + var mediaWidth = MediaQuery.of(context).size.width; + final RpgLayout layout = mediaWidth >= ultraWideLayoutThreshold + ? RpgLayout.ultrawide + : mediaWidth > wideLayoutThreshold ? RpgLayout.wide : RpgLayout.slim; return builder(context, layout); } diff --git a/lib/src/shared_state/game/character.dart b/lib/src/shared_state/game/character.dart index 96ecd52..78c0a94 100644 --- a/lib/src/shared_state/game/character.dart +++ b/lib/src/shared_state/game/character.dart @@ -58,7 +58,7 @@ class Character extends Aspect with ChildAspect { (_isHired ? 110 : 220) * costMultiplier; - bool get canUpgrade { + bool get canUpgradeOrHire { Company company = get().company; // Make sure there's some skill that's below max (meaning we can bump // it up). @@ -70,6 +70,8 @@ class Character extends Aspect with ChildAspect { return company.coin.number >= upgradeCost; } + bool upgradeOrHire() => _isHired ? upgrade() : hire(); + bool hire() { assert(!_isHired); Company company = get().company; @@ -82,9 +84,7 @@ class Character extends Aspect with ChildAspect { } bool upgrade() { - if (!_isHired) { - return hire(); - } + assert(_isHired); Company company = get().company; if (!company.spend(upgradeCost)) { return false; diff --git a/lib/src/shared_state/game/character_pool.dart b/lib/src/shared_state/game/character_pool.dart index 9952cc7..a649366 100644 --- a/lib/src/shared_state/game/character_pool.dart +++ b/lib/src/shared_state/game/character_pool.dart @@ -14,10 +14,10 @@ class CharacterPool extends AspectContainer with ChildAspect { void initializeCharacters() => setAspects([ Character('jack', {Skill.coding: 1, Skill.coordination: 1, Skill.ux: 1}, - customHiringCost: 220, costMultiplier: 2), + customHiringCost: 220, costMultiplier: 4), Character('sourcerer', {Skill.coding: 1, Skill.coordination: 1, Skill.engineering: 1}, - customHiringCost: 220, costMultiplier: 2), + customHiringCost: 220, costMultiplier: 4), Character('refactorer', {Skill.coding: 1, Skill.engineering: 3}), Character('architect', {Skill.coding: 1, Skill.engineering: 4}), Character('hacker', {Skill.coding: 3, Skill.engineering: 1}), diff --git a/lib/src/shared_state/game/task_tree/animations.dart b/lib/src/shared_state/game/task_tree/animations.dart index e0d9017..372ceb4 100644 --- a/lib/src/shared_state/game/task_tree/animations.dart +++ b/lib/src/shared_state/game/task_tree/animations.dart @@ -3,11 +3,13 @@ part of task_tree; const _basicAnimations = TaskBlueprint( 'Basic Animations', {Skill.ux: 100}, + coinReward: 200, requirements: AllOf([_alpha]), ); const _advancedMotionDesign = TaskBlueprint( 'Advanced Motion Design', {Skill.ux: 200, Skill.coordination: 50}, + coinReward: 400, requirements: AllOf([_basicAnimations, _basicDesign, _uxTesting]), ); diff --git a/lib/src/shared_state/game/task_tree/backend_infrastructure.dart b/lib/src/shared_state/game/task_tree/backend_infrastructure.dart index f489960..0567caa 100644 --- a/lib/src/shared_state/game/task_tree/backend_infrastructure.dart +++ b/lib/src/shared_state/game/task_tree/backend_infrastructure.dart @@ -3,17 +3,20 @@ part of task_tree; const _backendInfrastructure = TaskBlueprint( 'Backend Infrastructure', {Skill.engineering: 100, Skill.coding: 100}, + coinReward: 200, requirements: AllOf([_alpha]), ); const _fastBackend = TaskBlueprint( 'Fast Backend', {Skill.coding: 100}, + coinReward: 250, requirements: AllOf([_backendInfrastructure]), ); const _scalableBackend = TaskBlueprint( 'Scalable backend', {Skill.coding: 100}, + coinReward: 250, requirements: AllOf([_backendInfrastructure]), ); diff --git a/lib/src/shared_state/game/task_tree/beta.dart b/lib/src/shared_state/game/task_tree/beta.dart index e1aceaf..d7becbe 100644 --- a/lib/src/shared_state/game/task_tree/beta.dart +++ b/lib/src/shared_state/game/task_tree/beta.dart @@ -11,4 +11,5 @@ const _beta = TaskBlueprint('Beta', {Skill.coordination: 100}, AnyOf([_redTheme, _greenTheme, _blueTheme]), ]), priority: 100, + coinReward: 800, miniGame: MiniGame.chomp); diff --git a/lib/src/shared_state/game/task_tree/design.dart b/lib/src/shared_state/game/task_tree/design.dart index dcb63e1..2dde9a3 100644 --- a/lib/src/shared_state/game/task_tree/design.dart +++ b/lib/src/shared_state/game/task_tree/design.dart @@ -10,6 +10,7 @@ const _basicDesign = TaskBlueprint( const _dinosaurMascot = TaskBlueprint( 'Dinosaur Mascot & Icon', {Skill.coordination: 100, Skill.ux: 50}, + coinReward: 250, requirements: AllOf([_basicDesign]), mutuallyExclusive: ['Bird Mascot & Icon', 'Cat Mascot & Icon'], priority: 10, @@ -18,6 +19,7 @@ const _dinosaurMascot = TaskBlueprint( const _birdMascot = TaskBlueprint( 'Bird Mascot & Icon', {Skill.coordination: 100, Skill.ux: 50}, + coinReward: 250, requirements: AllOf([_basicDesign]), mutuallyExclusive: ['Cat Mascot & Icon', 'Dinosaur Mascot & Icon'], priority: 10, @@ -26,6 +28,7 @@ const _birdMascot = TaskBlueprint( const _catMascot = TaskBlueprint( 'Cat Mascot & Icon', {Skill.coordination: 100, Skill.ux: 50}, + coinReward: 250, requirements: AllOf([_basicDesign]), mutuallyExclusive: ['Bird Mascot & Icon', 'Dinosaur Mascot & Icon'], priority: 10, @@ -34,6 +37,7 @@ const _catMascot = TaskBlueprint( const _retroDesign = TaskBlueprint( 'Retro Design', {Skill.ux: 100}, + coinReward: 250, requirements: AllOf([_basicDesign]), mutuallyExclusive: ['Sci-Fi Design', 'Mainstream Design'], priority: 10, @@ -42,6 +46,7 @@ const _retroDesign = TaskBlueprint( const _scifiDesign = TaskBlueprint( 'Sci-Fi Design', {Skill.ux: 100}, + coinReward: 250, requirements: AllOf([_basicDesign]), mutuallyExclusive: ['Retro Design', 'Mainstream Design'], priority: 10, @@ -50,6 +55,7 @@ const _scifiDesign = TaskBlueprint( const _mainstreamDesign = TaskBlueprint( 'Mainstream Design', {Skill.ux: 50}, + coinReward: 250, requirements: AllOf([_basicDesign]), mutuallyExclusive: ['Sci-Fi Design', 'Retro Design'], priority: 10, diff --git a/lib/src/shared_state/game/task_tree/geolocation.dart b/lib/src/shared_state/game/task_tree/geolocation.dart index 48ae65a..11baa7a 100644 --- a/lib/src/shared_state/game/task_tree/geolocation.dart +++ b/lib/src/shared_state/game/task_tree/geolocation.dart @@ -3,11 +3,13 @@ part of task_tree; const _geolocation = TaskBlueprint( 'Geolocation', {Skill.engineering: 100, Skill.coding: 50}, + coinReward: 300, requirements: AllOf([_alpha]), ); const _arMessages = TaskBlueprint( 'AR Messages', {Skill.engineering: 50, Skill.coding: 100}, + coinReward: 300, requirements: AllOf([_geolocation]), ); diff --git a/lib/src/shared_state/game/task_tree/image_messaging.dart b/lib/src/shared_state/game/task_tree/image_messaging.dart index 511898c..d6a664f 100644 --- a/lib/src/shared_state/game/task_tree/image_messaging.dart +++ b/lib/src/shared_state/game/task_tree/image_messaging.dart @@ -3,29 +3,34 @@ part of task_tree; const _simpleImageMessaging = TaskBlueprint( 'Simple Image Messaging', {Skill.engineering: 100, Skill.coding: 50, Skill.ux: 50}, + coinReward: 200, requirements: AllOf([_alpha]), ); const _animatedGifSupport = TaskBlueprint( 'Animated GIF Support', {Skill.engineering: 50, Skill.coding: 100}, + coinReward: 200, requirements: AllOf([_simpleImageMessaging]), ); const _backendImageProcessing = TaskBlueprint( 'Backend Image Processing', {Skill.engineering: 50, Skill.coding: 100}, + coinReward: 200, requirements: AllOf([_simpleImageMessaging, _backendInfrastructure]), ); const _memeGenerator = TaskBlueprint( 'Meme Generator', {Skill.coding: 50, Skill.ux: 50, Skill.coordination: 50}, + coinReward: 200, requirements: AllOf([_simpleImageMessaging, _backendImageProcessing]), ); const _imageFilters = TaskBlueprint( 'Image Filters', {Skill.coding: 50, Skill.ux: 50}, + coinReward: 200, requirements: AllOf([_backendImageProcessing, _backendInfrastructure]), ); diff --git a/lib/src/shared_state/game/task_tree/launch.dart b/lib/src/shared_state/game/task_tree/launch.dart index b9269b9..3519f94 100644 --- a/lib/src/shared_state/game/task_tree/launch.dart +++ b/lib/src/shared_state/game/task_tree/launch.dart @@ -14,4 +14,5 @@ const _launch = TaskBlueprint('1.0', {Skill.coordination: 100}, ]), ]), priority: 100, + coinReward: 5000, miniGame: MiniGame.sphinx); diff --git a/lib/src/shared_state/game/task_tree/natural_language.dart b/lib/src/shared_state/game/task_tree/natural_language.dart index 7363358..a345956 100644 --- a/lib/src/shared_state/game/task_tree/natural_language.dart +++ b/lib/src/shared_state/game/task_tree/natural_language.dart @@ -3,24 +3,28 @@ part of task_tree; const _naturalLanguageGeneration = TaskBlueprint( 'Natural Language Generation', {Skill.engineering: 100, Skill.coding: 100}, + coinReward: 350, requirements: AllOf([_alpha]), ); const _naturalLanguageUnderstanding = TaskBlueprint( 'Natural Language Understanding', {Skill.engineering: 200, Skill.coding: 100}, + coinReward: 350, requirements: AllOf([_alpha]), ); const _automatedBots = TaskBlueprint( 'Automated Bots', {Skill.coding: 100, Skill.ux: 50}, + coinReward: 350, requirements: AllOf([_naturalLanguageGeneration]), ); const _conversationalChatbots = TaskBlueprint( 'Conversational Chatbots', {Skill.coding: 200, Skill.ux: 50}, + coinReward: 350, requirements: AllOf([_naturalLanguageGeneration, _naturalLanguageUnderstanding]), ); diff --git a/lib/src/shared_state/game/task_tree/pre_alpha.dart b/lib/src/shared_state/game/task_tree/pre_alpha.dart index ef89dd4..8f19a19 100644 --- a/lib/src/shared_state/game/task_tree/pre_alpha.dart +++ b/lib/src/shared_state/game/task_tree/pre_alpha.dart @@ -9,6 +9,7 @@ const _prototype = TaskBlueprint( const _basicBackend = TaskBlueprint( 'Basic Backend', {Skill.coding: 100}, + coinReward: 100, requirements: AllOf([_prototype]), priority: 10, ); @@ -16,6 +17,7 @@ const _basicBackend = TaskBlueprint( const _basicUI = TaskBlueprint( 'Basic UI', {Skill.ux: 100}, + coinReward: 100, requirements: AllOf([_prototype]), mutuallyExclusive: ['Programmer Art UI'], ); @@ -34,4 +36,5 @@ const _alpha = _basicBackend, ]), priority: 100, + coinReward: 500, miniGame: MiniGame.chomp); diff --git a/lib/src/shared_state/game/task_tree/pre_launch.dart b/lib/src/shared_state/game/task_tree/pre_launch.dart index 8c8a88b..767fabb 100644 --- a/lib/src/shared_state/game/task_tree/pre_launch.dart +++ b/lib/src/shared_state/game/task_tree/pre_launch.dart @@ -4,6 +4,7 @@ const _backendPerformanceOptimization = TaskBlueprint( 'Backend Performance Optimization', {Skill.engineering: 100, Skill.coding: 100}, requirements: AllOf([_beta, _fastBackend]), + coinReward: 350, priority: 50, ); @@ -11,6 +12,7 @@ const _backendScalabilityOptimization = TaskBlueprint( 'Backend Scalability Optimization', {Skill.engineering: 100, Skill.coding: 100}, requirements: AllOf([_beta, _scalableBackend]), + coinReward: 350, priority: 50, ); @@ -18,6 +20,7 @@ const _prelaunchMarketing = TaskBlueprint( 'Pre-launch Marketing', {Skill.coordination: 100}, requirements: AllOf([_beta]), + coinReward: 350, priority: 50, ); @@ -25,6 +28,7 @@ const _backendHardening = TaskBlueprint( 'Backend Hardening', {Skill.engineering: 100, Skill.coding: 100}, requirements: AllOf([_beta]), + coinReward: 350, priority: 50, ); @@ -32,6 +36,7 @@ const _uiPerformanceOptimization = TaskBlueprint( 'UI Performance Optimization', {Skill.coding: 100, Skill.ux: 50}, requirements: AllOf([_beta]), + coinReward: 350, priority: 50, ); @@ -39,5 +44,6 @@ const _uiPolish = TaskBlueprint( 'UI Polish', {Skill.coding: 100, Skill.ux: 100}, requirements: AllOf([_beta]), + coinReward: 350, priority: 50, ); diff --git a/lib/src/shared_state/game/task_tree/responsive_design.dart b/lib/src/shared_state/game/task_tree/responsive_design.dart index dfdd66d..1c5706b 100644 --- a/lib/src/shared_state/game/task_tree/responsive_design.dart +++ b/lib/src/shared_state/game/task_tree/responsive_design.dart @@ -4,34 +4,40 @@ const _responsiveDesign = TaskBlueprint( 'Responsive Design', {Skill.ux: 100, Skill.coordination: 50, Skill.engineering: 50}, requirements: AllOf([_basicDesign]), + coinReward: 150, ); const _tabletUI = TaskBlueprint( 'Tablet UI', {Skill.ux: 100, Skill.coding: 50}, requirements: AllOf([_basicDesign]), + coinReward: 150, ); const _desktopUI = TaskBlueprint( 'Desktop UI', {Skill.ux: 100, Skill.coding: 50}, requirements: AllOf([_basicDesign]), + coinReward: 250, ); const _iOSDesign = TaskBlueprint( 'Custom iOS Design', {Skill.ux: 100, Skill.coding: 50}, requirements: AllOf([_basicDesign]), + coinReward: 150, ); const _webVersion = TaskBlueprint( 'Web Version', {Skill.coding: 100, Skill.ux: 50}, requirements: AllOf([_desktopUI]), + coinReward: 350, ); const _desktopVersion = TaskBlueprint( 'Desktop Version', {Skill.coding: 100, Skill.ux: 50}, requirements: AllOf([_desktopUI]), + coinReward: 350, ); diff --git a/lib/src/shared_state/game/task_tree/task_tree.dart b/lib/src/shared_state/game/task_tree/task_tree.dart index 11e2640..2d73f47 100644 --- a/lib/src/shared_state/game/task_tree/task_tree.dart +++ b/lib/src/shared_state/game/task_tree/task_tree.dart @@ -111,19 +111,29 @@ class TaskNode implements TreeData { children.add(TaskNode(otherBlueprint)); } - // Patch remaining items that are satisfied by this full set. - // We have to do this because some items (like _advancedMotionDesign) - // are only satisfied when multiple non-direct descendent items in the tree - // are satisfied. if (isTop) { - final allPrereqs = allPrerequisites; - for (final otherBlueprint in taskTree) { - if (_processedTaskTree.contains(otherBlueprint) || - otherBlueprint == blueprint || - !otherBlueprint.requirements.isSatisfiedIn(allPrereqs)) { - continue; + // Patch remaining items that are satisfied by this full set. + // We have to do this because some items (like _advancedMotionDesign) + // are only satisfied when multiple non-direct descendent items in the + // tree are satisfied. + while (true) { + var patched = false; + + final allPrereqs = allPrerequisites; + for (final otherBlueprint in taskTree) { + if (_processedTaskTree.contains(otherBlueprint) || + otherBlueprint == blueprint || + !otherBlueprint.requirements.isSatisfiedIn(allPrereqs)) { + continue; + } + // This changes the full list of prereqs, so patch again. + children.add(TaskNode(otherBlueprint)); + patched = true; + break; + } + if (!patched) { + break; } - children.add(TaskNode(otherBlueprint)); } } diff --git a/lib/src/shared_state/game/task_tree/theme.dart b/lib/src/shared_state/game/task_tree/theme.dart index e3ba837..7d56776 100644 --- a/lib/src/shared_state/game/task_tree/theme.dart +++ b/lib/src/shared_state/game/task_tree/theme.dart @@ -4,6 +4,7 @@ const _basicTheme = TaskBlueprint( 'Basic Theme', {Skill.ux: 100, Skill.coordination: 50}, requirements: AllOf([_alpha]), + coinReward: 250, priority: 50, ); @@ -12,6 +13,7 @@ const _greenTheme = TaskBlueprint( {Skill.ux: 50}, requirements: AllOf([_basicTheme]), mutuallyExclusive: ['Red Theme', 'Blue Theme'], + coinReward: 250, priority: 10, ); @@ -20,6 +22,7 @@ const _redTheme = TaskBlueprint( {Skill.ux: 50}, requirements: AllOf([_basicTheme]), mutuallyExclusive: ['Green Theme', 'Blue Theme'], + coinReward: 250, priority: 10, ); @@ -28,5 +31,6 @@ const _blueTheme = TaskBlueprint( {Skill.ux: 50}, requirements: AllOf([_basicTheme]), mutuallyExclusive: ['Green Theme', 'Red Theme'], + coinReward: 250, priority: 10, ); diff --git a/lib/src/shared_state/game/task_tree/ux_testing.dart b/lib/src/shared_state/game/task_tree/ux_testing.dart index c365882..8139a41 100644 --- a/lib/src/shared_state/game/task_tree/ux_testing.dart +++ b/lib/src/shared_state/game/task_tree/ux_testing.dart @@ -3,18 +3,21 @@ part of task_tree; const _uxTesting = TaskBlueprint( 'UX Testing', {Skill.ux: 100}, + coinReward: 120, requirements: AllOf([_alpha]), ); const _foreignLanguageUx = TaskBlueprint( 'Foreign Language UX', {Skill.ux: 100}, + coinReward: 120, requirements: AllOf([_uxTesting]), ); const _accessibilityUx = TaskBlueprint( 'Accessibility UX', {Skill.ux: 100}, + coinReward: 120, requirements: AllOf([_uxTesting]), ); @@ -22,10 +25,12 @@ const _internationalization = TaskBlueprint( 'Internationalization', {Skill.coding: 100, Skill.engineering: 100, Skill.coordination: 50}, requirements: AllOf([_foreignLanguageUx]), + coinReward: 120, ); const _accessibility = TaskBlueprint( 'Accessibility', {Skill.coding: 100, Skill.engineering: 10, Skill.coordination: 50}, requirements: AllOf([_accessibilityUx]), + coinReward: 120, ); diff --git a/lib/src/style.dart b/lib/src/style.dart index bbaf193..f4ee07d 100644 --- a/lib/src/style.dart +++ b/lib/src/style.dart @@ -12,6 +12,7 @@ const Color treeLineColor = Color.fromRGBO(215, 215, 215, 1); const Color bugColor = Color.fromRGBO(236, 41, 117, 1); const Color statsSeparatorColor = Color.fromRGBO(57, 57, 71, 1); const double modalMaxWidth = 400; +const double ultraWideLayoutThreshold = 1920; const double wideLayoutThreshold = 500; const double idealCharacterWidth = 165; const double idealParticleSize = 10; diff --git a/lib/src/welcome_screen.dart b/lib/src/welcome_screen.dart index ad43f42..c556fae 100644 --- a/lib/src/welcome_screen.dart +++ b/lib/src/welcome_screen.dart @@ -90,23 +90,21 @@ class _WelcomeScreenState extends State { @override Widget build(BuildContext context) { return Scaffold( - body: Listener( - child: Container( - alignment: Alignment.center, - color: contentColor, - child: RpgLayoutBuilder( - builder: (context, layout) => layout != RpgLayout.slim - ? _WelcomeScreenWide( - hero, - start: _pressStartGame, - about: _pressAbout, - ) - : _WelcomeScreenSlim( - hero, - start: _pressStartGame, - about: _pressAbout, - ), - ), + body: Container( + alignment: Alignment.center, + color: contentColor, + child: RpgLayoutBuilder( + builder: (context, layout) => layout != RpgLayout.slim + ? _WelcomeScreenWide( + hero, + start: _pressStartGame, + about: _pressAbout, + ) + : _WelcomeScreenSlim( + hero, + start: _pressStartGame, + about: _pressAbout, + ), ), ), ); @@ -169,25 +167,39 @@ class _WelcomeScreenSlim extends StatelessWidget { class _Title extends StatelessWidget { @override Widget build(BuildContext context) { - return Column(crossAxisAlignment: CrossAxisAlignment.start, children: [ - const Text( - 'FLUTTER\nDEVELOPER QUEST', - style: TextStyle( - fontFamily: 'RobotoCondensedBold', fontSize: 30, letterSpacing: 5), - ), - const SizedBox(height: 12), - Container( - height: 2, - color: Colors.white.withOpacity(0.19), - ), - const SizedBox(height: 12), - const Text( - 'Build your team, slay bugs,\ndon\'t get fired.', - style: TextStyle(fontFamily: 'RobotoRegular', fontSize: 20), - ), - const SizedBox(height: 25), - Image.asset('assets/images/2dimensions.png') - ]); + return RpgLayoutBuilder( + builder: (context, layout) => Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + 'FLUTTER\nDEVELOPER QUEST', + style: TextStyle( + fontFamily: 'RobotoCondensedBold', + fontSize: layout == RpgLayout.ultrawide ? 48 : 30, + letterSpacing: 5), + ), + SizedBox(height: layout == RpgLayout.ultrawide ? 24 : 12), + Container( + height: 2, + color: Colors.white.withOpacity(0.19), + ), + SizedBox(height: layout == RpgLayout.ultrawide ? 28 : 12), + Text( + layout == RpgLayout.ultrawide + ? 'Build your team, slay bugs, don\'t get fired.' + : 'Build your team, slay bugs,\ndon\'t get fired.', + style: TextStyle( + fontFamily: 'RobotoRegular', + fontSize: layout == RpgLayout.ultrawide ? 24 : 20), + ), + const SizedBox(height: 25), + layout == RpgLayout.ultrawide + ? Image.asset('assets/images/2.0x/2dimensions.png', + scale: 1.75) + : Image.asset('assets/images/2dimensions.png') + ], + ), + ); } } @@ -204,50 +216,56 @@ class _WelcomeScreenWide extends StatelessWidget { var thirdWidth = size.width / 3; var thirdHeight = size.height / 3; - return Row( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Container( - width: thirdWidth, - height: thirdHeight * 2, - child: StartScreenHero( - filename: hero.flare, - alignment: Alignment.center, - fit: BoxFit.fitHeight, - gradient: contentColor), - ), - const SizedBox(width: 10), - SizedBox( - width: thirdWidth, - child: Column( + return RpgLayoutBuilder( + builder: (context, layout) => Row( mainAxisAlignment: MainAxisAlignment.center, children: [ - _Title(), - const SizedBox(height: 29), - Row( - children: [ - Expanded( - child: WelcomeButton( - key: const Key('start_game'), - onPressed: start, - background: hero.accent, - icon: Icons.chevron_right, - label: 'Start'), - ), - const SizedBox(width: 10), - Expanded( - child: WelcomeButton( - onPressed: about, - background: Colors.white.withOpacity(0.15), - icon: Icons.settings, - label: 'About'), - ), - ], + Container( + width: thirdWidth, + height: thirdHeight * 2, + child: StartScreenHero( + filename: hero.flare, + alignment: Alignment.center, + fit: BoxFit.fitHeight, + gradient: contentColor), + ), + const SizedBox(width: 10), + SizedBox( + width: layout == RpgLayout.ultrawide + ? thirdWidth * 0.702 + : thirdWidth, + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + _Title(), + SizedBox(height: layout == RpgLayout.ultrawide ? 87 : 29), + Row( + children: [ + Expanded( + child: WelcomeButton( + key: const Key('start_game'), + fontSize: layout == RpgLayout.ultrawide ? 20 : 16, + onPressed: start, + background: hero.accent, + icon: Icons.chevron_right, + label: 'Start'), + ), + const SizedBox(width: 10), + Expanded( + child: WelcomeButton( + fontSize: layout == RpgLayout.ultrawide ? 20 : 16, + onPressed: about, + background: Colors.white.withOpacity(0.15), + icon: Icons.settings, + label: 'About'), + ), + ], + ), + ], + ), ), ], ), - ), - ], ); } } diff --git a/lib/src/widgets/app_bar/coin_badge.dart b/lib/src/widgets/app_bar/coin_badge.dart index f8296ca..9fe9e64 100644 --- a/lib/src/widgets/app_bar/coin_badge.dart +++ b/lib/src/widgets/app_bar/coin_badge.dart @@ -4,8 +4,9 @@ import 'package:dev_rpg/src/widgets/app_bar/stat_badge.dart'; /// Visually indicates the amount of capital the [Company] has amassed for this /// game session. class CoinBadge extends StatBadge { - CoinBadge(StatValue listenable) - : super('Capital', listenable, flare: 'assets/flare/Coin.flr'); + CoinBadge(StatValue listenable, {double scale = 1, bool isWide = false}) + : super('Capital', listenable, + flare: 'assets/flare/Coin.flr', scale: scale, isWide: isWide); /// Play the indicator animation after this value changes by at least 5 coins. @override diff --git a/lib/src/widgets/app_bar/joy_badge.dart b/lib/src/widgets/app_bar/joy_badge.dart index eb9fb3b..28261a8 100644 --- a/lib/src/widgets/app_bar/joy_badge.dart +++ b/lib/src/widgets/app_bar/joy_badge.dart @@ -5,8 +5,10 @@ import 'package:dev_rpg/src/widgets/app_bar/stat_badge.dart'; /// sad, happy, neutral states and a call to attention animation whenever /// the numeric value changes. class JoyBadge extends StatBadge { - JoyBadge(StatValue listenable) - : super('Joy', listenable, flare: 'assets/flare/Joy.flr'); + JoyBadge(StatValue listenable, + {double scale = 1, bool isWide = false}) + : super('Joy', listenable, + flare: 'assets/flare/Joy.flr', scale: scale, isWide: isWide); /// Play the celebration animation whenever there's a change /// 0 is a flag for always in this case diff --git a/lib/src/widgets/app_bar/stat_badge.dart b/lib/src/widgets/app_bar/stat_badge.dart index fd0e702..475dd3c 100644 --- a/lib/src/widgets/app_bar/stat_badge.dart +++ b/lib/src/widgets/app_bar/stat_badge.dart @@ -12,6 +12,8 @@ import 'package:flutter/material.dart'; /// the 'points' animation after the state has changed by this amount. abstract class StatBadge extends StatefulWidget { final String stat; + final double scale; + final bool isWide; @required final String flare; @@ -19,7 +21,8 @@ abstract class StatBadge extends StatefulWidget { @required final StatValue listenable; - const StatBadge(this.stat, this.listenable, {this.flare}); + const StatBadge(this.stat, this.listenable, + {this.flare, this.scale = 1, this.isWide}); /// This is intentionally abstract to allow deriving stats to specify /// when they should celebrate. N.B. that a value of 0 means to always @@ -68,8 +71,8 @@ class StatBadgeState extends State> { children: [ const SizedBox(width: 15), Container( - width: 26, - height: 26, + width: 26 * widget.scale, + height: 26 * widget.scale, child: FlareActor( widget.flare, alignment: Alignment.topCenter, @@ -79,26 +82,85 @@ class StatBadgeState extends State> { controller: controls, ), ), - const SizedBox(width: 9), + SizedBox(width: widget.scale * 9), Expanded( - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - ValueListenableBuilder( - valueListenable: widget.listenable, - builder: (context, String value, child) => Text(value, - style: buttonTextStyle.apply( - color: Colors.white, fontSizeDelta: -2)), - ), - Text( - widget.stat.toUpperCase(), + child: widget.isWide + ? _WideStatData( + listenable: widget.listenable, + scale: widget.scale, + stat: widget.stat, + ) + : _SlimStatData( + listenable: widget.listenable, + scale: widget.scale, + stat: widget.stat, + )) + ], + ); + } +} + +class _SlimStatData extends StatelessWidget { + final ValueListenable listenable; + final double scale; + final String stat; + const _SlimStatData({this.listenable, this.scale, this.stat}); + @override + Widget build(BuildContext context) { + return Column( + mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + ValueListenableBuilder( + valueListenable: listenable, + builder: (context, String value, child) => Text(value, + style: buttonTextStyle.apply( + color: Colors.white, + fontSizeDelta: -2, + fontSizeFactor: scale)), + ), + Text( + stat.toUpperCase(), + style: buttonTextStyle.apply( + color: Colors.white.withOpacity(0.5), + fontSizeDelta: -4, + fontSizeFactor: scale), + ), + ], + ); + } +} + +class _WideStatData extends StatelessWidget { + final ValueListenable listenable; + final double scale; + final String stat; + const _WideStatData({this.listenable, this.scale, this.stat}); + @override + Widget build(BuildContext context) { + return Row( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Text( + stat.toUpperCase(), + style: buttonTextStyle.apply( + color: Colors.white.withOpacity(0.5), + fontSizeDelta: -4, + fontSizeFactor: scale), + ), + const SizedBox(width: 15), + Expanded( + child: ValueListenableBuilder( + valueListenable: listenable, + builder: (context, String value, child) => Text(value, style: buttonTextStyle.apply( - color: Colors.white.withOpacity(0.5), fontSizeDelta: -4), - ), - ], + color: Colors.white, + fontSizeDelta: -1, + fontSizeFactor: scale)), ), - ) + ), + const SizedBox(width: 15), ], ); } diff --git a/lib/src/widgets/app_bar/users_badge.dart b/lib/src/widgets/app_bar/users_badge.dart index 60cbfbd..52cec1a 100644 --- a/lib/src/widgets/app_bar/users_badge.dart +++ b/lib/src/widgets/app_bar/users_badge.dart @@ -4,8 +4,10 @@ import 'package:dev_rpg/src/widgets/app_bar/stat_badge.dart'; /// Visually indicates the number of users the [Company] has amassed for this /// game session. class UsersBadge extends StatBadge { - UsersBadge(StatValue listenable) - : super('Users', listenable, flare: 'assets/flare/Users.flr'); + UsersBadge(StatValue listenable, + {double scale = 1, bool isWide = false}) + : super('Users', listenable, + flare: 'assets/flare/Users.flr', scale: scale, isWide: isWide); /// Play a celebration/call to attention animation after the value changes by 100 users. @override diff --git a/lib/src/widgets/buttons/welcome_button.dart b/lib/src/widgets/buttons/welcome_button.dart index 3a9b2bb..fa452ba 100644 --- a/lib/src/widgets/buttons/welcome_button.dart +++ b/lib/src/widgets/buttons/welcome_button.dart @@ -8,16 +8,18 @@ class WelcomeButton extends StatefulWidget { final Color background; final IconData icon; final String label; + final double fontSize; @required final VoidCallback onPressed; - const WelcomeButton( - {Key key, - this.child, - this.onPressed, - this.background, - this.icon, - this.label}) - : super(key: key); + const WelcomeButton({ + Key key, + this.child, + this.onPressed, + this.background, + this.icon, + this.label, + this.fontSize = 16, + }) : super(key: key); @override _WelcomeButtonState createState() => _WelcomeButtonState(); @@ -59,29 +61,33 @@ class _WelcomeButtonState extends State @override Widget build(BuildContext context) { return AnimatedBuilder( - animation: _animationController, - builder: (context, child) => WideButton( - onPressed: widget.onPressed, - background: _colorTween.value, - child: Align( - alignment: Alignment.centerLeft, - child: Row( - children: [ - widget.icon == null - ? Container() - : Padding( - padding: const EdgeInsets.only(right: 13), - child: Icon( - widget.icon, - size: 16, - color: Colors.white.withOpacity(0.5), - ), + animation: _animationController, + builder: (context, child) => WideButton( + onPressed: widget.onPressed, + background: _colorTween.value, + child: Align( + alignment: Alignment.centerLeft, + child: Row( + children: [ + widget.icon == null + ? Container() + : Padding( + padding: const EdgeInsets.only(right: 13), + child: Icon( + widget.icon, + size: widget.fontSize, + color: Colors.white.withOpacity(0.5), ), - Text(widget.label.toUpperCase(), - style: buttonTextStyle.apply(color: Colors.white)) - ], - ), + ), + Text(widget.label.toUpperCase(), + style: buttonTextStyle.apply( + color: Colors.white, + fontSizeDelta: + widget.fontSize - buttonTextStyle.fontSize)) + ], ), - )); + ), + ), + ); } } diff --git a/lib/src/widgets/buttons/wide_button.dart b/lib/src/widgets/buttons/wide_button.dart index 3f888dd..945b249 100644 --- a/lib/src/widgets/buttons/wide_button.dart +++ b/lib/src/widgets/buttons/wide_button.dart @@ -6,6 +6,7 @@ class WideButton extends StatelessWidget { final Widget child; final Color background; final Color shadowColor; + final bool enabled; @required final VoidCallback onPressed; @@ -19,6 +20,7 @@ class WideButton extends StatelessWidget { this.paddingTweak = const EdgeInsets.all(0), this.buttonKey, this.shadowColor, + this.enabled = true, }); @override @@ -45,7 +47,7 @@ class WideButton extends StatelessWidget { shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(9), ), - onPressed: onPressed, + onPressed: enabled ? onPressed : null, color: background, child: child), ); diff --git a/lib/src/widgets/flare/hiring_bust.dart b/lib/src/widgets/flare/hiring_bust.dart index 753d543..1f3e800 100644 --- a/lib/src/widgets/flare/hiring_bust.dart +++ b/lib/src/widgets/flare/hiring_bust.dart @@ -2,16 +2,14 @@ import 'dart:math'; import 'package:dev_rpg/src/widgets/flare/desaturated_actor.dart'; import 'package:dev_rpg/src/widgets/flare/hiring_particles.dart'; +import 'package:dev_rpg/src/style.dart'; import 'package:flare_flutter/flare.dart'; import 'package:flutter/material.dart'; import 'package:flutter/rendering.dart'; import 'package:flare_dart/math/mat2d.dart'; import 'package:flare_dart/math/aabb.dart'; - import 'package:flare_flutter/flare_render_box.dart'; -import '../../style.dart'; - /// The HiringBust displays three different visual states. /// [locked] is for when the character is not available for hire, /// because presumably the user doesn't have the resources to hire them. diff --git a/lib/src/widgets/task_picker/task_picker_item.dart b/lib/src/widgets/task_picker/task_picker_item.dart index ad51eb0..2aa3263 100644 --- a/lib/src/widgets/task_picker/task_picker_item.dart +++ b/lib/src/widgets/task_picker/task_picker_item.dart @@ -1,3 +1,4 @@ +import 'package:dev_rpg/src/rpg_layout_builder.dart'; import 'package:dev_rpg/src/shared_state/game/task_blueprint.dart'; import 'package:dev_rpg/src/style.dart'; import 'package:dev_rpg/src/widgets/work_items/task_header.dart'; @@ -33,89 +34,94 @@ class TaskPickerItem extends StatelessWidget { @override Widget build(BuildContext context) { - // Build up the vertical lines that show the connections in the tree. - List lineWidgets = []; - for (int i = 0; i < lines.length; i++) { - if (!lines[i]) { - continue; - } + return RpgLayoutBuilder( + builder: (context, layout) { + double scale = layout == RpgLayout.ultrawide ? 1.25 : 1; + double height = layout == RpgLayout.ultrawide ? _height * 1.1 : _height; + // Build up the vertical lines that show the connections in the tree. + List lineWidgets = []; + for (int i = 0; i < lines.length; i++) { + if (!lines[i]) { + continue; + } - bool isLast = i == lines.length - 1; - double lineHeight = - i > 0 && isLast && !hasNextSibling ? _halfLineHeight : _height; - lineWidgets.add(Positioned.fromRect( - rect: Rect.fromLTWH( - _leftPadding + i * _lineSpacing, 0, _lineThickness, lineHeight), - child: SizedOverflowBox( - size: const Size.fromHeight(0), - child: Container(color: treeLineColor), - ), - )); - - if (isLast && hasNextChild) { - lineWidgets.add(Positioned.fromRect( - rect: Rect.fromLTWH(_leftPadding + (i + 1) * _lineSpacing, - _height - _bottomPadding, _lineThickness, lineHeight), - child: SizedOverflowBox( - size: const Size.fromHeight(0), - child: Container(color: treeLineColor), - ), - )); - } - } - double left = _leftPadding + (lines.length - 1) * _lineSpacing; - return SizedBox( - height: _height, - child: Stack( - children: [ - Positioned.fromRect( + bool isLast = i == lines.length - 1; + double lineHeight = + i > 0 && isLast && !hasNextSibling ? _halfLineHeight : height; + lineWidgets.add(Positioned.fromRect( rect: Rect.fromLTWH( - left, _halfLineHeight, _dashWidth, _lineThickness), + _leftPadding + i * _lineSpacing, 0, _lineThickness, lineHeight), child: SizedOverflowBox( size: const Size.fromHeight(0), child: Container(color: treeLineColor), ), - ), - Padding( - padding: EdgeInsets.only( - left: left + _dashWidth, bottom: _bottomPadding, right: 16), - child: ConstrainedBox( - constraints: const BoxConstraints( - minWidth: double.infinity, + )); + + if (isLast && hasNextChild) { + lineWidgets.add(Positioned.fromRect( + rect: Rect.fromLTWH(_leftPadding + (i + 1) * _lineSpacing, + height - _bottomPadding, _lineThickness, lineHeight), + child: SizedOverflowBox( + size: const Size.fromHeight(0), + child: Container(color: treeLineColor), ), - child: InkWell( - onTap: display != TaskDisplay.available - ? null - : () => Navigator.pop(context, blueprint), - child: Container( - padding: const EdgeInsets.all(15), - decoration: BoxDecoration( - boxShadow: const [ - BoxShadow( - color: Color.fromRGBO(0, 0, 0, 0.03), - offset: Offset(0, 10), - blurRadius: 10, - spreadRadius: 0), - ], - borderRadius: const BorderRadius.all(Radius.circular(9)), - color: display == TaskDisplay.available - ? Colors.white - : Colors.white.withOpacity(0.5), + )); + } + } + double left = _leftPadding + (lines.length - 1) * _lineSpacing; + return SizedBox( + height: height, + child: Stack( + children: [ + Positioned.fromRect( + rect: Rect.fromLTWH( + left, _halfLineHeight, _dashWidth, _lineThickness), + child: SizedOverflowBox( + size: const Size.fromHeight(0), + child: Container(color: treeLineColor), + ), + ), + Padding( + padding: EdgeInsets.only( + left: left + _dashWidth, bottom: _bottomPadding, right: 16), + child: ConstrainedBox( + constraints: const BoxConstraints( + minWidth: double.infinity, + ), + child: InkWell( + onTap: display != TaskDisplay.available + ? null + : () => Navigator.pop(context, blueprint), + child: Container( + padding: const EdgeInsets.all(15), + decoration: BoxDecoration( + boxShadow: const [ + BoxShadow( + color: Color.fromRGBO(0, 0, 0, 0.03), + offset: Offset(0, 10), + blurRadius: 10, + spreadRadius: 0), + ], + borderRadius: + const BorderRadius.all(Radius.circular(9)), + color: display == TaskDisplay.available + ? Colors.white + : Colors.white.withOpacity(0.5), + ), + child: display == TaskDisplay.available + ? _TaskDisplayAvailable(blueprint, scale: scale) + : display == TaskDisplay.locked + ? _TaskDisplayLocked(blueprint, scale: scale) + : _TaskDisplayCompleted(blueprint, scale: scale), + ), ), - child: display == TaskDisplay.available - ? _TaskDisplayAvailable(blueprint) - : display == TaskDisplay.locked - ? _TaskDisplayLocked( - blueprint, - ) - : _TaskDisplayCompleted(blueprint), ), ), - ), + ...lineWidgets + ], ), - ...lineWidgets - ], - ), + ); + }, ); } } @@ -123,7 +129,8 @@ class TaskPickerItem extends StatelessWidget { /// Widget for available task in task picker tree class _TaskDisplayAvailable extends StatelessWidget { final TaskBlueprint blueprint; - const _TaskDisplayAvailable(this.blueprint); + final double scale; + const _TaskDisplayAvailable(this.blueprint, {this.scale = 1}); @override Widget build(BuildContext context) { @@ -132,7 +139,9 @@ class _TaskDisplayAvailable extends StatelessWidget { children: [ TaskHeader(blueprint), const SizedBox(height: 10), - Text(blueprint.name, style: contentStyle) + Text(blueprint.name, + overflow: TextOverflow.ellipsis, + style: contentStyle.apply(fontSizeFactor: scale)) ], ); } @@ -141,7 +150,8 @@ class _TaskDisplayAvailable extends StatelessWidget { /// Widget for locked task in task picker tree class _TaskDisplayLocked extends StatelessWidget { final TaskBlueprint blueprint; - const _TaskDisplayLocked(this.blueprint); + final double scale; + const _TaskDisplayLocked(this.blueprint, {this.scale = 1}); @override Widget build(BuildContext context) { @@ -149,10 +159,12 @@ class _TaskDisplayLocked extends StatelessWidget { crossAxisAlignment: CrossAxisAlignment.start, children: [ Icon(Icons.lock_outline, - color: disabledColor.withOpacity(0.25), size: 20), + color: disabledColor.withOpacity(0.25), size: 20 * scale), const SizedBox(height: 10), Text(blueprint.name, - style: contentStyle.apply(color: contentColor.withOpacity(0.25))) + overflow: TextOverflow.ellipsis, + style: contentStyle.apply( + color: contentColor.withOpacity(0.25), fontSizeFactor: scale)) ], ); } @@ -161,7 +173,8 @@ class _TaskDisplayLocked extends StatelessWidget { /// Widget for completed task in task picker tree class _TaskDisplayCompleted extends StatelessWidget { final TaskBlueprint blueprint; - const _TaskDisplayCompleted(this.blueprint); + final double scale; + const _TaskDisplayCompleted(this.blueprint, {this.scale = 1}); @override Widget build(BuildContext context) { @@ -169,10 +182,12 @@ class _TaskDisplayCompleted extends StatelessWidget { crossAxisAlignment: CrossAxisAlignment.start, children: [ Icon(Icons.check_circle, - color: disabledColor.withOpacity(0.25), size: 20), + color: disabledColor.withOpacity(0.25), size: 20 * scale), const SizedBox(height: 10), Text(blueprint.name, - style: contentStyle.apply(color: contentColor.withOpacity(0.25))) + overflow: TextOverflow.ellipsis, + style: contentStyle.apply( + color: contentColor.withOpacity(0.25), fontSizeFactor: scale)) ], ); } diff --git a/lib/src/widgets/work_items/task_header.dart b/lib/src/widgets/work_items/task_header.dart index 97ed273..8f0258b 100644 --- a/lib/src/widgets/work_items/task_header.dart +++ b/lib/src/widgets/work_items/task_header.dart @@ -1,3 +1,4 @@ +import 'package:dev_rpg/src/rpg_layout_builder.dart'; import 'package:dev_rpg/src/shared_state/game/skill.dart'; import 'package:dev_rpg/src/shared_state/game/task_blueprint.dart'; import 'package:dev_rpg/src/style.dart'; @@ -13,21 +14,22 @@ class TaskHeader extends StatelessWidget { const TaskHeader(this.blueprint); @override Widget build(BuildContext context) { - return Row( - children: [ - Container( - width: 20, - height: 20, - child: const FlareActor('assets/flare/Coin.flr'), - ), - const SizedBox(width: 4), - Text( - blueprint.coinReward.toString(), - style: contentSmallStyle, - ), - Expanded(child: Container()), - for (Skill skill in blueprint.skillsNeeded) SkillDot(skill) - ], - ); + return RpgLayoutBuilder(builder: (context, layout) { + double scale = layout == RpgLayout.ultrawide ? 1.25 : 1.0; + return Row( + children: [ + Container( + width: 20 * scale, + height: 20 * scale, + child: const FlareActor('assets/flare/Coin.flr'), + ), + const SizedBox(width: 4), + Text(blueprint.coinReward.toString(), + style: contentSmallStyle.apply(fontSizeFactor: scale)), + Expanded(child: Container()), + for (Skill skill in blueprint.skillsNeeded) SkillDot(skill) + ], + ); + }); } } diff --git a/lib/src/widgets/work_items/tasks_button_header.dart b/lib/src/widgets/work_items/tasks_button_header.dart index 155509f..1735ae4 100644 --- a/lib/src/widgets/work_items/tasks_button_header.dart +++ b/lib/src/widgets/work_items/tasks_button_header.dart @@ -11,7 +11,8 @@ import 'package:provider/provider.dart'; /// for selecting active bugs to add to the working list. class TasksButtonHeader extends SliverPersistentHeaderDelegate { final TaskPool taskPool; - const TasksButtonHeader({this.taskPool}); + final double scale; + const TasksButtonHeader({this.taskPool, this.scale}); @override Widget build( @@ -23,6 +24,7 @@ class TasksButtonHeader extends SliverPersistentHeaderDelegate { Expanded( child: AddTaskButton( 'Tasks', + scale: scale, key: const Key('add_task'), count: taskPool.availableTasks.length, icon: Icons.add, @@ -43,6 +45,7 @@ class TasksButtonHeader extends SliverPersistentHeaderDelegate { Expanded( child: AddTaskButton( 'Bugs', + scale: scale, count: taskPool.availableBugs.length, icon: Icons.bug_report, color: const Color(0xffeb2875), @@ -64,10 +67,10 @@ class TasksButtonHeader extends SliverPersistentHeaderDelegate { } @override - double get maxExtent => 55; + double get maxExtent => 55 * scale; @override - double get minExtent => 55; + double get minExtent => 55 * scale; @override bool shouldRebuild(SliverPersistentHeaderDelegate oldDelegate) { diff --git a/lib/src/widgets/work_items/tasks_section_header.dart b/lib/src/widgets/work_items/tasks_section_header.dart index ac45445..5d29555 100644 --- a/lib/src/widgets/work_items/tasks_section_header.dart +++ b/lib/src/widgets/work_items/tasks_section_header.dart @@ -4,7 +4,8 @@ import 'package:flutter/material.dart'; /// This is a simple text header for the tasks list. class TasksSectionHeader extends SliverPersistentHeaderDelegate { final String title; - const TasksSectionHeader(this.title); + final double scale; + const TasksSectionHeader(this.title, this.scale); @override Widget build( @@ -14,7 +15,9 @@ class TasksSectionHeader extends SliverPersistentHeaderDelegate { child: Text( title, style: buttonTextStyle.apply( - fontSizeDelta: -4, color: secondaryContentColor), + fontSizeDelta: -4, + color: secondaryContentColor, + fontSizeFactor: scale), ), ); } @@ -27,6 +30,6 @@ class TasksSectionHeader extends SliverPersistentHeaderDelegate { @override bool shouldRebuild(TasksSectionHeader oldDelegate) { - return title != oldDelegate.title; + return title != oldDelegate.title || scale != oldDelegate.scale; } } diff --git a/lib/src/widgets/work_items/work_list_item.dart b/lib/src/widgets/work_items/work_list_item.dart index 2b25d31..7e76f1a 100644 --- a/lib/src/widgets/work_items/work_list_item.dart +++ b/lib/src/widgets/work_items/work_list_item.dart @@ -1,6 +1,7 @@ import 'dart:async'; import 'package:dev_rpg/src/game_screen/team_picker_modal.dart'; +import 'package:dev_rpg/src/rpg_layout_builder.dart'; import 'package:dev_rpg/src/shared_state/game/character.dart'; import 'package:dev_rpg/src/shared_state/game/work_item.dart'; import 'package:dev_rpg/src/style.dart'; @@ -62,49 +63,55 @@ class WorkListItem extends StatelessWidget { @override Widget build(BuildContext context) { - return Padding( - padding: const EdgeInsets.only(left: 15, right: 15, bottom: 15), - child: Container( - decoration: workListItemDecoration, - child: Material( - type: MaterialType.transparency, - borderRadius: const BorderRadius.all(Radius.circular(9)), - clipBehavior: Clip.antiAlias, - // **Step 5 in emshack/efortuna live-coding: Add InkWell and onTap. - // Also talk about _handleTap above, but have it pre-written. - child: InkWell( - onTap: () => _handleTap(context, workItem), - child: Padding( - padding: const EdgeInsets.all(15), - // **Step 3 in emshack/efortuna live-coding: Add this Column, - // plus the heading and SizedBox children. - child: Column( - // **Step 4 in emshack/efortuna live-coding: Add - // crossAxisAlignment. - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - heading, - const SizedBox(height: 12), - Text( - workItem.name, - // **Step 2 in emshack/efortuna live-coding: Add style. - style: isExpanded - ? contentStyle - : contentStyle.apply(color: disabledColor), + return RpgLayoutBuilder( + builder: (context, layout) { + double scale = layout == RpgLayout.ultrawide ? 1.25 : 1; + return Padding( + padding: const EdgeInsets.only(left: 15, right: 15, bottom: 15), + child: Container( + decoration: workListItemDecoration, + child: Material( + type: MaterialType.transparency, + borderRadius: const BorderRadius.all(Radius.circular(9)), + clipBehavior: Clip.antiAlias, + // **Step 5 in emshack/efortuna live-coding: Add InkWell and onTap. + // Also talk about _handleTap above, but have it pre-written. + child: InkWell( + onTap: () => _handleTap(context, workItem), + child: Padding( + padding: const EdgeInsets.all(15), + // **Step 3 in emshack/efortuna live-coding: Add this Column, + // plus the heading and SizedBox children. + child: Column( + // **Step 4 in emshack/efortuna live-coding: Add + // crossAxisAlignment. + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + heading, + const SizedBox(height: 12), + Text( + workItem.name, + // **Step 2 in emshack/efortuna live-coding: Add style. + style: isExpanded + ? contentStyle.apply(fontSizeFactor: scale) + : contentStyle.apply( + color: disabledColor, fontSizeFactor: scale), + ), + // **Step 6 in emshack/efortuna live-coding: Add this child + // for a final wow moment. + TeamProgressIndicator( + workItem: workItem, + isExpanded: isExpanded, + progressColor: progressColor, + ), + ], ), - // **Step 6 in emshack/efortuna live-coding: Add this child - // for a final wow moment. - TeamProgressIndicator( - workItem: workItem, - isExpanded: isExpanded, - progressColor: progressColor, - ), - ], + ), ), ), ), - ), - ), + ); + }, ); } } diff --git a/pubspec.yaml b/pubspec.yaml index 31bdbd3..2f3c5a3 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -20,7 +20,7 @@ dependencies: flutter: sdk: flutter intl: any - provider: ^2.0.0-dev + provider: ^2.0.0 # The following adds the Cupertino Icons font to your application. # Use with the CupertinoIcons class for iOS style icons. diff --git a/test/character_test.dart b/test/character_test.dart index 819e20b..9e14916 100644 --- a/test/character_test.dart +++ b/test/character_test.dart @@ -20,24 +20,30 @@ void main() { var world = World(); var unhired = world.characterPool.children.firstWhere((ch) => !ch.isHired); var previous = unhired.level; - expect(unhired.upgrade(), false); + expect(() => unhired.upgrade(), throwsA(isA())); expect(unhired.level, previous); }); test('level goes up when upgraded', () { var world = World()..company.coin.number = 1000; - var hired = world.characterPool.children.firstWhere((ch) => ch.isHired); - var previous = hired.level; - expect(hired.upgrade(), true); - expect(hired.level, greaterThan(previous)); + // Find the first available character that we can hire. + var character = world.characterPool.children + .firstWhere((ch) => !ch.isHired && ch.canUpgradeOrHire); + character.hire(); + var previous = character.level; + expect(character.upgrade(), true); + expect(character.level, greaterThan(previous)); }); test('skill goes up when upgraded', () { var world = World()..company.coin.number = 1000; - var hired = world.characterPool.children.firstWhere((ch) => ch.isHired); - var previous = hired.prowess.values.fold(0, (a, b) => a + b); - expect(hired.upgrade(), true); - expect(hired.prowess.values.fold(0, (a, b) => a + b), + // Find the first available character that we can hire. + var character = world.characterPool.children + .firstWhere((ch) => !ch.isHired && ch.canUpgradeOrHire); + character.hire(); + var previous = character.prowess.values.fold(0, (a, b) => a + b); + expect(character.upgrade(), true); + expect(character.prowess.values.fold(0, (a, b) => a + b), greaterThan(previous)); }); } diff --git a/test/world_test.dart b/test/world_test.dart index b237c20..2ab554e 100644 --- a/test/world_test.dart +++ b/test/world_test.dart @@ -12,7 +12,7 @@ void main() { builder: (_) => World(), child: MaterialApp( home: Consumer( - builder: (context, world) => FlatButton( + builder: (context, world, child) => FlatButton( key: buttonKey, onPressed: () => world.start(), child: Text(world.isRunning ? 'Stop' : 'Start'),