rewrite drawer fixes #316
rewrites the drawer from scratch add drawer shaddow
This commit is contained in:
parent
b19c6354e3
commit
4525953b33
|
@ -28,7 +28,7 @@ import 'package:hive_flutter/hive_flutter.dart' show Box;
|
|||
import 'package:path_provider/path_provider.dart';
|
||||
import 'package:sanmill/generated/intl/l10n.dart';
|
||||
import 'package:sanmill/models/display.dart';
|
||||
import 'package:sanmill/screens/navigation_home_screen.dart';
|
||||
import 'package:sanmill/screens/home.dart';
|
||||
import 'package:sanmill/services/audios.dart';
|
||||
import 'package:sanmill/services/enviornment_config.dart';
|
||||
import 'package:sanmill/services/language_info.dart';
|
||||
|
@ -103,7 +103,7 @@ class SanmillApp extends StatelessWidget {
|
|||
snackBar: SnackBar(
|
||||
content: Text(S.of(context).tapBackAgainToLeave),
|
||||
),
|
||||
child: const NavigationHomeScreen(),
|
||||
child: const Home(),
|
||||
),
|
||||
);
|
||||
},
|
||||
|
|
|
@ -32,7 +32,9 @@ import 'package:sanmill/shared/theme/app_theme.dart';
|
|||
import 'package:url_launcher/url_launcher.dart';
|
||||
|
||||
class AboutPage extends StatelessWidget {
|
||||
final String tag = "[about] ";
|
||||
const AboutPage({Key? key}) : super(key: key);
|
||||
|
||||
static const String tag = "[about] ";
|
||||
|
||||
String get mode {
|
||||
if (kDebugMode) {
|
||||
|
|
|
@ -240,8 +240,7 @@ class Board extends StatelessWidget {
|
|||
1
|
||||
];
|
||||
|
||||
final bool ltr = Directionality.of(context) == TextDirection.ltr;
|
||||
|
||||
final bool ltr = Directionality.of(context) == TextDirection.ltr;
|
||||
|
||||
if (ltr) {
|
||||
for (final file in ['a', 'b', 'c', 'd', 'e', 'f', 'g']) {
|
||||
|
|
|
@ -53,6 +53,7 @@ double boardWidth = 0.0;
|
|||
class GamePage extends StatefulWidget {
|
||||
final EngineType engineType;
|
||||
|
||||
// TODO: use gameInstamce.enginetype
|
||||
const GamePage(this.engineType, {Key? key}) : super(key: key);
|
||||
|
||||
@override
|
||||
|
@ -886,12 +887,10 @@ class _GamePageState extends State<GamePage>
|
|||
);
|
||||
}
|
||||
|
||||
void onOptionButtonPressed() {
|
||||
Navigator.push(
|
||||
context,
|
||||
MaterialPageRoute(builder: (context) => GameSettingsPage()),
|
||||
);
|
||||
}
|
||||
void onOptionButtonPressed() => Navigator.push(
|
||||
context,
|
||||
MaterialPageRoute(builder: (context) => const GameSettingsPage()),
|
||||
);
|
||||
|
||||
void onMoveButtonPressed() {
|
||||
final List<Widget> _historyNavigation = [
|
||||
|
|
|
@ -38,6 +38,7 @@ part 'package:sanmill/screens/game_settings/skill_level_slider.dart';
|
|||
part 'package:sanmill/screens/game_settings/move_time_slider.dart';
|
||||
|
||||
class GameSettingsPage extends StatelessWidget {
|
||||
const GameSettingsPage({Key? key}) : super(key: key);
|
||||
static const List<String> _algorithmNames = ['Alpha-Beta', 'PVS', 'MTD(f)'];
|
||||
|
||||
static const String _tag = "[game_settings_page]";
|
||||
|
|
|
@ -0,0 +1,235 @@
|
|||
/*
|
||||
This file is part of Sanmill.
|
||||
Copyright (C) 2019-2021 The Sanmill developers (see AUTHORS file)
|
||||
|
||||
Sanmill is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
Sanmill is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import 'dart:io';
|
||||
import 'dart:typed_data';
|
||||
|
||||
import 'package:feedback/feedback.dart';
|
||||
import 'package:fluentui_system_icons/fluentui_system_icons.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_email_sender/flutter_email_sender.dart';
|
||||
import 'package:package_info_plus/package_info_plus.dart';
|
||||
import 'package:path_provider/path_provider.dart';
|
||||
import 'package:sanmill/generated/intl/l10n.dart';
|
||||
import 'package:sanmill/mill/game.dart';
|
||||
import 'package:sanmill/screens/about_page.dart';
|
||||
import 'package:sanmill/screens/game_page/game_page.dart';
|
||||
import 'package:sanmill/screens/game_settings/game_settings_page.dart';
|
||||
import 'package:sanmill/screens/personalization_settings/help_screen.dart';
|
||||
import 'package:sanmill/screens/personalization_settings/personalization_settings_page.dart';
|
||||
import 'package:sanmill/screens/rule_settings/rule_settings_page.dart';
|
||||
import 'package:sanmill/services/engine/engine.dart';
|
||||
import 'package:sanmill/services/storage/storage.dart';
|
||||
import 'package:sanmill/shared/constants.dart';
|
||||
import 'package:sanmill/shared/custom_drawer/custom_drawer.dart';
|
||||
|
||||
enum _DrawerIndex {
|
||||
humanVsAi,
|
||||
humanVsHuman,
|
||||
aiVsAi,
|
||||
preferences,
|
||||
ruleSettings,
|
||||
personalization,
|
||||
feedback,
|
||||
Help,
|
||||
About,
|
||||
}
|
||||
|
||||
/// Home View
|
||||
///
|
||||
/// this widget implements the home view of our app.
|
||||
class Home extends StatefulWidget {
|
||||
const Home({Key? key}) : super(key: key);
|
||||
|
||||
@override
|
||||
_HomeState createState() => _HomeState();
|
||||
}
|
||||
|
||||
class _HomeState extends State<Home> with TickerProviderStateMixin {
|
||||
final _controller = CustomDrawerController();
|
||||
|
||||
Widget _screenView = const GamePage(EngineType.humanVsAi);
|
||||
_DrawerIndex _drawerIndex = _DrawerIndex.humanVsAi;
|
||||
|
||||
static const Map<_DrawerIndex, Widget> _gamePages = {
|
||||
_DrawerIndex.humanVsAi: GamePage(EngineType.humanVsAi),
|
||||
_DrawerIndex.humanVsHuman: GamePage(EngineType.humanVsHuman),
|
||||
_DrawerIndex.aiVsAi: GamePage(EngineType.aiVsAi),
|
||||
};
|
||||
|
||||
/// callback from drawer for replace screen
|
||||
/// as user need with passing DrawerIndex (Enum index)
|
||||
void _changeIndex(_DrawerIndex index) {
|
||||
_controller.hideDrawer();
|
||||
if (_drawerIndex == index && _drawerIndex != _DrawerIndex.feedback) {
|
||||
return;
|
||||
}
|
||||
|
||||
setState(() {
|
||||
_drawerIndex = index;
|
||||
switch (_drawerIndex) {
|
||||
case _DrawerIndex.humanVsAi:
|
||||
gameInstance.setWhoIsAi(EngineType.humanVsAi);
|
||||
_screenView = _gamePages[_DrawerIndex.humanVsAi]!;
|
||||
break;
|
||||
case _DrawerIndex.humanVsHuman:
|
||||
gameInstance.setWhoIsAi(EngineType.humanVsHuman);
|
||||
_screenView = _gamePages[_DrawerIndex.humanVsHuman]!;
|
||||
break;
|
||||
case _DrawerIndex.aiVsAi:
|
||||
gameInstance.setWhoIsAi(EngineType.aiVsAi);
|
||||
_screenView = _gamePages[_DrawerIndex.aiVsAi]!;
|
||||
break;
|
||||
case _DrawerIndex.preferences:
|
||||
_screenView = const GameSettingsPage();
|
||||
break;
|
||||
case _DrawerIndex.ruleSettings:
|
||||
_screenView = const RuleSettingsPage();
|
||||
break;
|
||||
case _DrawerIndex.personalization:
|
||||
_screenView = const PersonalizationSettingsPage();
|
||||
break;
|
||||
case _DrawerIndex.feedback:
|
||||
if (!LocalDatabaseService.preferences.developerMode) {
|
||||
if (Platform.isWindows) {
|
||||
debugPrint("flutter_email_sender does not support Windows.");
|
||||
} else {
|
||||
BetterFeedback.of(context).show(_launchFeedback);
|
||||
}
|
||||
}
|
||||
break;
|
||||
case _DrawerIndex.Help:
|
||||
if (!LocalDatabaseService.preferences.developerMode) {
|
||||
_screenView = const HelpScreen();
|
||||
}
|
||||
break;
|
||||
|
||||
case _DrawerIndex.About:
|
||||
if (!LocalDatabaseService.preferences.developerMode) {
|
||||
_screenView = const AboutPage();
|
||||
}
|
||||
break;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final List<CustomDrawerItem> drawerItems = [
|
||||
CustomDrawerItem<_DrawerIndex>(
|
||||
value: _DrawerIndex.humanVsAi,
|
||||
title: S.of(context).humanVsAi,
|
||||
icon: const Icon(FluentIcons.person_24_regular),
|
||||
groupValue: _drawerIndex,
|
||||
onChanged: _changeIndex,
|
||||
),
|
||||
CustomDrawerItem<_DrawerIndex>(
|
||||
value: _DrawerIndex.humanVsHuman,
|
||||
title: S.of(context).humanVsHuman,
|
||||
icon: const Icon(FluentIcons.people_24_regular),
|
||||
groupValue: _drawerIndex,
|
||||
onChanged: _changeIndex,
|
||||
),
|
||||
CustomDrawerItem<_DrawerIndex>(
|
||||
value: _DrawerIndex.aiVsAi,
|
||||
title: S.of(context).aiVsAi,
|
||||
icon: const Icon(FluentIcons.bot_24_regular),
|
||||
groupValue: _drawerIndex,
|
||||
onChanged: _changeIndex,
|
||||
),
|
||||
CustomDrawerItem<_DrawerIndex>(
|
||||
value: _DrawerIndex.preferences,
|
||||
title: S.of(context).preferences,
|
||||
icon: const Icon(FluentIcons.options_24_regular),
|
||||
groupValue: _drawerIndex,
|
||||
onChanged: _changeIndex,
|
||||
),
|
||||
CustomDrawerItem<_DrawerIndex>(
|
||||
value: _DrawerIndex.ruleSettings,
|
||||
title: S.of(context).ruleSettings,
|
||||
icon: const Icon(FluentIcons.task_list_ltr_24_regular),
|
||||
groupValue: _drawerIndex,
|
||||
onChanged: _changeIndex,
|
||||
),
|
||||
CustomDrawerItem<_DrawerIndex>(
|
||||
value: _DrawerIndex.personalization,
|
||||
title: S.of(context).personalization,
|
||||
icon: const Icon(FluentIcons.design_ideas_24_regular),
|
||||
groupValue: _drawerIndex,
|
||||
onChanged: _changeIndex,
|
||||
),
|
||||
CustomDrawerItem<_DrawerIndex>(
|
||||
value: _DrawerIndex.feedback,
|
||||
title: S.of(context).feedback,
|
||||
icon: const Icon(FluentIcons.chat_warning_24_regular),
|
||||
groupValue: _drawerIndex,
|
||||
onChanged: _changeIndex,
|
||||
),
|
||||
CustomDrawerItem<_DrawerIndex>(
|
||||
value: _DrawerIndex.Help,
|
||||
title: S.of(context).help,
|
||||
icon: const Icon(FluentIcons.question_circle_24_regular),
|
||||
groupValue: _drawerIndex,
|
||||
onChanged: _changeIndex,
|
||||
),
|
||||
CustomDrawerItem<_DrawerIndex>(
|
||||
value: _DrawerIndex.About,
|
||||
title: S.of(context).about,
|
||||
icon: const Icon(FluentIcons.info_24_regular),
|
||||
groupValue: _drawerIndex,
|
||||
onChanged: _changeIndex,
|
||||
),
|
||||
];
|
||||
|
||||
// LocalDatabaseService.colorSettings.drawerColor,
|
||||
|
||||
return CustomDrawer(
|
||||
controller: _controller,
|
||||
header: CustomDrawerHeader(
|
||||
title: S.of(context).appName,
|
||||
),
|
||||
items: drawerItems,
|
||||
child: _screenView,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// drafts an email and sends it to the developer
|
||||
Future<void> _launchFeedback(UserFeedback feedback) async {
|
||||
final screenshotFilePath = await _writeImageToStorage(feedback.screenshot);
|
||||
final packageInfo = await PackageInfo.fromPlatform();
|
||||
final _version = '${packageInfo.version} (${packageInfo.buildNumber})';
|
||||
|
||||
final Email email = Email(
|
||||
body: feedback.text,
|
||||
subject: Constants.feedbackSubjectPrefix +
|
||||
_version +
|
||||
Constants.feedbackSubjectSuffix,
|
||||
recipients: [Constants.recipients],
|
||||
attachmentPaths: [screenshotFilePath],
|
||||
);
|
||||
await FlutterEmailSender.send(email);
|
||||
}
|
||||
|
||||
Future<String> _writeImageToStorage(Uint8List feedbackScreenshot) async {
|
||||
final Directory output = await getTemporaryDirectory();
|
||||
final String screenshotFilePath = '${output.path}/sanmill-feedback.png';
|
||||
final File screenshotFile = File(screenshotFilePath);
|
||||
await screenshotFile.writeAsBytes(feedbackScreenshot);
|
||||
return screenshotFilePath;
|
||||
}
|
|
@ -1,261 +0,0 @@
|
|||
/*
|
||||
This file is part of Sanmill.
|
||||
Copyright (C) 2019-2021 The Sanmill developers (see AUTHORS file)
|
||||
|
||||
Sanmill is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
Sanmill is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
part of 'package:sanmill/screens/navigation_home_screen.dart';
|
||||
|
||||
enum DrawerIndex {
|
||||
humanVsAi,
|
||||
humanVsHuman,
|
||||
aiVsAi,
|
||||
preferences,
|
||||
ruleSettings,
|
||||
personalization,
|
||||
feedback,
|
||||
Help,
|
||||
About
|
||||
}
|
||||
|
||||
class DrawerListItem {
|
||||
const DrawerListItem({
|
||||
required this.index,
|
||||
required this.title,
|
||||
required this.icon,
|
||||
});
|
||||
|
||||
final DrawerIndex index;
|
||||
final String title;
|
||||
final Icon icon;
|
||||
}
|
||||
|
||||
class HomeDrawer extends StatelessWidget {
|
||||
const HomeDrawer({
|
||||
Key? key,
|
||||
required this.screenIndex,
|
||||
required this.iconAnimationController,
|
||||
required this.callBackIndex,
|
||||
required this.items,
|
||||
}) : super(key: key);
|
||||
|
||||
final AnimationController iconAnimationController;
|
||||
final DrawerIndex screenIndex;
|
||||
final Function(DrawerIndex) callBackIndex;
|
||||
final List<DrawerListItem> items;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Material(
|
||||
color: LocalDatabaseService.colorSettings.drawerBackgroundColor,
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: <Widget>[
|
||||
_DrawerHeader(
|
||||
iconAnimationController: iconAnimationController,
|
||||
),
|
||||
Divider(height: 1, color: AppTheme.drawerDividerColor),
|
||||
ListView.builder(
|
||||
padding: const EdgeInsets.only(top: 4.0),
|
||||
physics: const BouncingScrollPhysics(),
|
||||
shrinkWrap: true,
|
||||
itemCount: items.length,
|
||||
itemBuilder: _buildChildren,
|
||||
),
|
||||
//drawFooter,
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> navigationToScreen(DrawerIndex index) async {
|
||||
callBackIndex(index);
|
||||
}
|
||||
|
||||
Widget _buildChildren(BuildContext context, int index) {
|
||||
final listItem = items[index];
|
||||
final bool isSelected = screenIndex == listItem.index;
|
||||
|
||||
final bool ltr = Directionality.of(context) == TextDirection.ltr;
|
||||
const double radius = 28.0;
|
||||
final animatedBuilder = AnimatedBuilder(
|
||||
animation: iconAnimationController,
|
||||
builder: (BuildContext context, Widget? child) {
|
||||
return Transform(
|
||||
transform: Matrix4.translationValues(
|
||||
(MediaQuery.of(context).size.width * 0.75 - 64) *
|
||||
(1.0 - iconAnimationController.value - 1.0),
|
||||
0.0,
|
||||
0.0,
|
||||
),
|
||||
child: child,
|
||||
);
|
||||
},
|
||||
child: Container(
|
||||
width: MediaQuery.of(context).size.width * 0.75 - 64,
|
||||
height: 46,
|
||||
decoration: BoxDecoration(
|
||||
color: LocalDatabaseService.colorSettings.drawerHighlightItemColor,
|
||||
borderRadius: BorderRadius.horizontal(
|
||||
right: ltr ? const Radius.circular(radius) : Radius.zero,
|
||||
left: !ltr ? const Radius.circular(radius) : Radius.zero,
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
final listItemIcon = Icon(
|
||||
listItem.icon.icon,
|
||||
color: isSelected
|
||||
? LocalDatabaseService
|
||||
.colorSettings.drawerTextColor // TODO: drawerHighlightTextColor
|
||||
: LocalDatabaseService.colorSettings.drawerTextColor,
|
||||
);
|
||||
|
||||
final child = Row(
|
||||
children: <Widget>[
|
||||
const SizedBox(height: 46.0, width: 6.0),
|
||||
const Padding(
|
||||
padding: EdgeInsets.all(4.0),
|
||||
),
|
||||
listItemIcon,
|
||||
const Padding(
|
||||
padding: EdgeInsets.all(4.0),
|
||||
),
|
||||
Text(
|
||||
listItem.title,
|
||||
style: TextStyle(
|
||||
fontWeight: isSelected ? FontWeight.w700 : FontWeight.w500,
|
||||
fontSize: LocalDatabaseService.display.fontSize,
|
||||
color: isSelected
|
||||
? LocalDatabaseService.colorSettings.drawerTextColor
|
||||
// TODO: drawerHighlightTextColor
|
||||
: LocalDatabaseService.colorSettings.drawerTextColor,
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
|
||||
return InkWell(
|
||||
splashColor: AppTheme.drawerSplashColor,
|
||||
highlightColor: AppTheme.drawerHighlightColor,
|
||||
onTap: () => navigationToScreen(listItem.index),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: 8),
|
||||
child: isSelected
|
||||
? Stack(
|
||||
children: <Widget>[
|
||||
child,
|
||||
animatedBuilder,
|
||||
],
|
||||
)
|
||||
: child,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _DrawerHeader extends StatelessWidget {
|
||||
const _DrawerHeader({
|
||||
Key? key,
|
||||
required this.iconAnimationController,
|
||||
}) : super(key: key);
|
||||
|
||||
final AnimationController iconAnimationController;
|
||||
|
||||
static const String _tag = "[home_drawer]";
|
||||
|
||||
void _enableDeveloperMode() {
|
||||
Temp.developerMode = true;
|
||||
|
||||
debugPrint("$_tag Developer mode enabled.");
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final List<Color> animatedTextsColors = [
|
||||
LocalDatabaseService.colorSettings.drawerTextColor,
|
||||
Colors.black,
|
||||
Colors.blue,
|
||||
Colors.yellow,
|
||||
Colors.red,
|
||||
LocalDatabaseService.colorSettings.darkBackgroundColor,
|
||||
LocalDatabaseService.colorSettings.boardBackgroundColor,
|
||||
LocalDatabaseService.colorSettings.drawerHighlightItemColor,
|
||||
];
|
||||
|
||||
final rotationTransition = RotationTransition(
|
||||
turns: AlwaysStoppedAnimation<double>(
|
||||
Tween<double>(begin: 0.0, end: 24.0)
|
||||
.animate(
|
||||
CurvedAnimation(
|
||||
parent: iconAnimationController,
|
||||
curve: Curves.fastOutSlowIn,
|
||||
),
|
||||
)
|
||||
.value /
|
||||
360,
|
||||
),
|
||||
);
|
||||
|
||||
final scaleTransition = ScaleTransition(
|
||||
scale: AlwaysStoppedAnimation<double>(
|
||||
1.0 - (iconAnimationController.value) * 0.2,
|
||||
),
|
||||
child: rotationTransition,
|
||||
);
|
||||
|
||||
final animatedBuilder = AnimatedBuilder(
|
||||
animation: iconAnimationController,
|
||||
builder: (_, __) => scaleTransition,
|
||||
);
|
||||
|
||||
final animation = GestureDetector(
|
||||
onDoubleTap: _enableDeveloperMode,
|
||||
child: AnimatedTextKit(
|
||||
animatedTexts: [
|
||||
ColorizeAnimatedText(
|
||||
S.of(context).appName,
|
||||
textStyle: TextStyle(
|
||||
fontSize: LocalDatabaseService.display.fontSize + 16,
|
||||
fontWeight: FontWeight.w600,
|
||||
),
|
||||
colors: animatedTextsColors,
|
||||
speed: const Duration(seconds: 3),
|
||||
),
|
||||
],
|
||||
pause: const Duration(seconds: 3),
|
||||
repeatForever: true,
|
||||
stopPauseOnTap: true,
|
||||
onTap: () => debugPrint("$_tag DoubleTap to enable developer mode."),
|
||||
),
|
||||
);
|
||||
|
||||
return Padding(
|
||||
padding: const EdgeInsets.all(16.0),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: <Widget>[
|
||||
// TODO: can animatedBuilder be removed? does not appear in the widget tree
|
||||
animatedBuilder,
|
||||
Padding(
|
||||
padding: EdgeInsets.only(top: isLargeScreen ? 30 : 8, left: 4),
|
||||
child: ExcludeSemantics(child: animation),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
|
@ -1,150 +0,0 @@
|
|||
/*
|
||||
This file is part of Sanmill.
|
||||
Copyright (C) 2019-2021 The Sanmill developers (see AUTHORS file)
|
||||
|
||||
Sanmill is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
Sanmill is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import 'dart:io';
|
||||
import 'dart:typed_data';
|
||||
|
||||
import 'package:animated_text_kit/animated_text_kit.dart';
|
||||
import 'package:feedback/feedback.dart';
|
||||
import 'package:fluentui_system_icons/fluentui_system_icons.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_email_sender/flutter_email_sender.dart';
|
||||
import 'package:package_info_plus/package_info_plus.dart';
|
||||
import 'package:path_provider/path_provider.dart';
|
||||
import 'package:sanmill/generated/intl/l10n.dart';
|
||||
import 'package:sanmill/mill/game.dart';
|
||||
import 'package:sanmill/models/temporary.dart';
|
||||
import 'package:sanmill/screens/about_page.dart';
|
||||
import 'package:sanmill/screens/game_page/game_page.dart';
|
||||
import 'package:sanmill/screens/game_settings/game_settings_page.dart';
|
||||
import 'package:sanmill/screens/help_screen.dart';
|
||||
import 'package:sanmill/screens/personalization_settings/personalization_settings_page.dart';
|
||||
import 'package:sanmill/screens/rule_settings/rule_settings_page.dart';
|
||||
import 'package:sanmill/services/engine/engine.dart';
|
||||
import 'package:sanmill/services/enviornment_config.dart';
|
||||
import 'package:sanmill/services/storage/storage.dart';
|
||||
import 'package:sanmill/shared/constants.dart';
|
||||
import 'package:sanmill/shared/theme/app_theme.dart';
|
||||
|
||||
part 'package:sanmill/screens/home_drawer.dart';
|
||||
part 'package:sanmill/shared/drawer_controller.dart';
|
||||
|
||||
class NavigationHomeScreen extends StatefulWidget {
|
||||
const NavigationHomeScreen({Key? key}) : super(key: key);
|
||||
@override
|
||||
_NavigationHomeScreenState createState() => _NavigationHomeScreenState();
|
||||
}
|
||||
|
||||
class _NavigationHomeScreenState extends State<NavigationHomeScreen> {
|
||||
late Widget screenView;
|
||||
late DrawerIndex drawerIndex;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
drawerIndex = DrawerIndex.humanVsAi;
|
||||
screenView = const GamePage(EngineType.humanVsAi);
|
||||
super.initState();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Material(
|
||||
color: AppTheme.navigationHomeScreenBackgroundColor,
|
||||
child: DrawerController(
|
||||
screenIndex: drawerIndex,
|
||||
drawerWidth: MediaQuery.of(context).size.width * 0.75,
|
||||
onDrawerCall: changeIndex,
|
||||
// we replace screen view as
|
||||
// we need on navigate starting screens
|
||||
screenView: screenView,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
/// callback from drawer for replace screen
|
||||
/// as user need with passing DrawerIndex (Enum index)
|
||||
void changeIndex(DrawerIndex index) {
|
||||
if (drawerIndex == index && drawerIndex != DrawerIndex.feedback) {
|
||||
return;
|
||||
}
|
||||
|
||||
final drawerMap = {
|
||||
DrawerIndex.humanVsAi: EngineType.humanVsAi,
|
||||
DrawerIndex.humanVsHuman: EngineType.humanVsHuman,
|
||||
DrawerIndex.aiVsAi: EngineType.aiVsAi,
|
||||
};
|
||||
|
||||
drawerIndex = index;
|
||||
|
||||
// TODO: use switch case
|
||||
final engineType = drawerMap[drawerIndex];
|
||||
setState(() {
|
||||
if (engineType != null) {
|
||||
gameInstance.setWhoIsAi(engineType);
|
||||
screenView = GamePage(engineType);
|
||||
} else if (drawerIndex == DrawerIndex.preferences) {
|
||||
screenView = GameSettingsPage();
|
||||
} else if (drawerIndex == DrawerIndex.ruleSettings) {
|
||||
screenView = const RuleSettingsPage();
|
||||
} else if (drawerIndex == DrawerIndex.personalization) {
|
||||
screenView = PersonalizationSettingsPage();
|
||||
} else if (drawerIndex == DrawerIndex.feedback &&
|
||||
!EnvironmentConfig.monkeyTest) {
|
||||
if (Platform.isWindows) {
|
||||
debugPrint("flutter_email_sender does not support Windows.");
|
||||
//_launchFeedback();
|
||||
} else {
|
||||
BetterFeedback.of(context).show((feedback) async {
|
||||
// draft an email and send to developer
|
||||
final screenshotFilePath =
|
||||
await writeImageToStorage(feedback.screenshot);
|
||||
final packageInfo = await PackageInfo.fromPlatform();
|
||||
final _version =
|
||||
'${packageInfo.version} (${packageInfo.buildNumber})';
|
||||
|
||||
final Email email = Email(
|
||||
body: feedback.text,
|
||||
subject: Constants.feedbackSubjectPrefix +
|
||||
_version +
|
||||
Constants.feedbackSubjectSuffix,
|
||||
recipients: [Constants.recipients],
|
||||
attachmentPaths: [screenshotFilePath],
|
||||
);
|
||||
await FlutterEmailSender.send(email);
|
||||
});
|
||||
}
|
||||
} else if (drawerIndex == DrawerIndex.Help &&
|
||||
!EnvironmentConfig.monkeyTest) {
|
||||
screenView = HelpScreen();
|
||||
} else if (drawerIndex == DrawerIndex.About &&
|
||||
!EnvironmentConfig.monkeyTest) {
|
||||
screenView = AboutPage();
|
||||
} else {
|
||||
//do in your way......
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
Future<String> writeImageToStorage(Uint8List feedbackScreenshot) async {
|
||||
final Directory output = await getTemporaryDirectory();
|
||||
final String screenshotFilePath = '${output.path}/sanmill-feedback.png';
|
||||
final File screenshotFile = File(screenshotFilePath);
|
||||
await screenshotFile.writeAsBytes(feedbackScreenshot);
|
||||
return screenshotFilePath;
|
||||
}
|
||||
}
|
|
@ -4,6 +4,8 @@ import 'package:sanmill/services/storage/storage.dart';
|
|||
import 'package:sanmill/shared/theme/app_theme.dart';
|
||||
|
||||
class HelpScreen extends StatelessWidget {
|
||||
const HelpScreen({Key? key}) : super(key: key);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
|
@ -43,6 +43,8 @@ part 'package:sanmill/screens/personalization_settings/point_width_slider.dart';
|
|||
part 'package:sanmill/screens/personalization_settings/language_picker.dart';
|
||||
|
||||
class PersonalizationSettingsPage extends StatelessWidget {
|
||||
const PersonalizationSettingsPage({Key? key}) : super(key: key);
|
||||
|
||||
void setBoardBorderLineWidth(BuildContext context) => showModalBottomSheet(
|
||||
context: context,
|
||||
builder: (_) => const _BoardBorderWidthSlider(),
|
||||
|
|
|
@ -0,0 +1,37 @@
|
|||
/*
|
||||
This file is part of Sanmill.
|
||||
Copyright (C) 2019-2021 The Sanmill developers (see AUTHORS file)
|
||||
|
||||
Sanmill is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
Sanmill is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
/// Although marked as a library this package is tightly integrated into the app
|
||||
library custom_drawer;
|
||||
|
||||
import 'package:animated_text_kit/animated_text_kit.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/gestures.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/rendering.dart';
|
||||
import 'package:sanmill/generated/intl/l10n.dart';
|
||||
import 'package:sanmill/models/temporary.dart';
|
||||
import 'package:sanmill/services/storage/storage.dart';
|
||||
import 'package:sanmill/shared/constants.dart';
|
||||
import 'package:sanmill/shared/theme/app_theme.dart';
|
||||
|
||||
part 'src/controller.dart';
|
||||
part 'src/header.dart';
|
||||
part 'src/item.dart';
|
||||
part 'src/value.dart';
|
||||
part 'src/widget.dart';
|
|
@ -0,0 +1,47 @@
|
|||
/*
|
||||
This file is part of Sanmill.
|
||||
Copyright (C) 2019-2021 The Sanmill developers (see AUTHORS file)
|
||||
|
||||
Sanmill is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
Sanmill is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
part of '../custom_drawer.dart';
|
||||
|
||||
/// Custom Drawer Controller
|
||||
///
|
||||
/// manages the [CustomDrawer] state
|
||||
class CustomDrawerController extends ValueNotifier<CustomDrawerValue> {
|
||||
/// Creates a controller with the initial drawer state (Hidden by default)
|
||||
CustomDrawerController([CustomDrawerValue? value])
|
||||
: super(value ?? CustomDrawerValue.hidden());
|
||||
|
||||
/// shows the drawer
|
||||
void showDrawer() {
|
||||
value = CustomDrawerValue.visible();
|
||||
}
|
||||
|
||||
/// hides the drawer
|
||||
void hideDrawer() {
|
||||
value = CustomDrawerValue.hidden();
|
||||
}
|
||||
|
||||
/// toggles the drawer visibility
|
||||
void toggleDrawer() {
|
||||
if (value.visible) {
|
||||
hideDrawer();
|
||||
} else {
|
||||
showDrawer();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,84 @@
|
|||
/*
|
||||
This file is part of Sanmill.
|
||||
Copyright (C) 2019-2021 The Sanmill developers (see AUTHORS file)
|
||||
|
||||
Sanmill is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
Sanmill is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
part of '../custom_drawer.dart';
|
||||
|
||||
// TODO: [Leptopoda] maybe extend DrawerHeader
|
||||
class CustomDrawerHeader extends StatelessWidget {
|
||||
const CustomDrawerHeader({
|
||||
Key? key,
|
||||
required this.title,
|
||||
}) : super(key: key);
|
||||
|
||||
final String title;
|
||||
|
||||
static const String _tag = "[home_drawer]";
|
||||
|
||||
void _enableDeveloperMode() {
|
||||
Temp.developerMode = true;
|
||||
|
||||
debugPrint("$_tag Developer mode enabled.");
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final List<Color> _animatedTextsColors = [
|
||||
LocalDatabaseService.colorSettings.drawerTextColor,
|
||||
Colors.black,
|
||||
Colors.blue,
|
||||
Colors.yellow,
|
||||
Colors.red,
|
||||
LocalDatabaseService.colorSettings.darkBackgroundColor,
|
||||
LocalDatabaseService.colorSettings.boardBackgroundColor,
|
||||
LocalDatabaseService.colorSettings.drawerHighlightItemColor,
|
||||
];
|
||||
|
||||
final animation = GestureDetector(
|
||||
onDoubleTap: _enableDeveloperMode,
|
||||
child: AnimatedTextKit(
|
||||
animatedTexts: [
|
||||
ColorizeAnimatedText(
|
||||
title,
|
||||
textStyle: TextStyle(
|
||||
fontSize: LocalDatabaseService.display.fontSize + 16,
|
||||
fontWeight: FontWeight.w600,
|
||||
),
|
||||
colors: _animatedTextsColors,
|
||||
speed: const Duration(seconds: 3),
|
||||
),
|
||||
],
|
||||
pause: const Duration(seconds: 3),
|
||||
repeatForever: true,
|
||||
stopPauseOnTap: true,
|
||||
onTap: () => debugPrint("$_tag DoubleTap to enable developer mode."),
|
||||
),
|
||||
);
|
||||
|
||||
final _padding = EdgeInsets.only(
|
||||
bottom: 16.0,
|
||||
top: 16.0 + (isLargeScreen ? 30 : 8),
|
||||
left: 20,
|
||||
right: 16,
|
||||
);
|
||||
|
||||
return Padding(
|
||||
padding: _padding,
|
||||
child: ExcludeSemantics(child: animation),
|
||||
);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,75 @@
|
|||
/*
|
||||
This file is part of Sanmill.
|
||||
Copyright (C) 2019-2021 The Sanmill developers (see AUTHORS file)
|
||||
|
||||
Sanmill is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
Sanmill is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
part of '../custom_drawer.dart';
|
||||
|
||||
class CustomDrawerItem<T> extends StatelessWidget {
|
||||
const CustomDrawerItem({
|
||||
Key? key,
|
||||
required this.groupValue,
|
||||
required this.onChanged,
|
||||
required this.value,
|
||||
required this.title,
|
||||
required this.icon,
|
||||
}) : super(key: key);
|
||||
|
||||
final T groupValue;
|
||||
final Function(T) onChanged;
|
||||
final T value;
|
||||
final String title;
|
||||
final Icon icon;
|
||||
|
||||
bool get selected => groupValue == value;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
// TODO: drawerHighlightTextColor
|
||||
final _color = selected
|
||||
? LocalDatabaseService.colorSettings.drawerTextColor
|
||||
: LocalDatabaseService.colorSettings.drawerTextColor;
|
||||
|
||||
final listItemIcon = Icon(
|
||||
icon.icon,
|
||||
color: _color,
|
||||
);
|
||||
|
||||
final _drawerItem = Row(
|
||||
children: <Widget>[
|
||||
const SizedBox(height: 46.0, width: 6.0),
|
||||
const Padding(padding: EdgeInsets.all(4.0)),
|
||||
listItemIcon,
|
||||
const Padding(padding: EdgeInsets.all(4.0)),
|
||||
Text(
|
||||
title,
|
||||
style: TextStyle(
|
||||
fontWeight: selected ? FontWeight.w700 : FontWeight.w500,
|
||||
fontSize: LocalDatabaseService.display.fontSize,
|
||||
color: _color,
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
|
||||
return InkWell(
|
||||
splashColor: AppTheme.drawerSplashColor,
|
||||
highlightColor: AppTheme.drawerHighlightColor,
|
||||
onTap: () => onChanged(value),
|
||||
child: _drawerItem,
|
||||
);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,43 @@
|
|||
/*
|
||||
This file is part of Sanmill.
|
||||
Copyright (C) 2019-2021 The Sanmill developers (see AUTHORS file)
|
||||
|
||||
Sanmill is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
Sanmill is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
part of '../custom_drawer.dart';
|
||||
|
||||
/// CustomDrawer Value
|
||||
///
|
||||
/// the different states athe [CustomDrawer] can be in
|
||||
class CustomDrawerValue {
|
||||
const CustomDrawerValue({
|
||||
this.visible = false,
|
||||
});
|
||||
|
||||
/// indicates whether drawer visible or not
|
||||
final bool visible;
|
||||
|
||||
/// creates a value with hidden state
|
||||
factory CustomDrawerValue.hidden() {
|
||||
return const CustomDrawerValue();
|
||||
}
|
||||
|
||||
/// creates a value with visible state
|
||||
factory CustomDrawerValue.visible() {
|
||||
return const CustomDrawerValue(
|
||||
visible: true,
|
||||
);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,288 @@
|
|||
/*
|
||||
This file is part of Sanmill.
|
||||
Copyright (C) 2019-2021 The Sanmill developers (see AUTHORS file)
|
||||
|
||||
Sanmill is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
Sanmill is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
part of '../custom_drawer.dart';
|
||||
|
||||
/// CustomDrawer Widget
|
||||
///
|
||||
/// The widget laying out the custom drawer
|
||||
class CustomDrawer extends StatefulWidget {
|
||||
const CustomDrawer({
|
||||
Key? key,
|
||||
required this.child,
|
||||
required this.items,
|
||||
required this.header,
|
||||
this.controller,
|
||||
this.disabledGestures = false,
|
||||
}) : super(key: key);
|
||||
|
||||
/// Child widget. (Usually awidget that represents the main screen)
|
||||
final Widget child;
|
||||
|
||||
/// controller that controls the widget state. By default a new controller will be geneerated.
|
||||
final CustomDrawerController? controller;
|
||||
|
||||
/// disables the gestures.
|
||||
final bool disabledGestures;
|
||||
|
||||
/// items the drawer holds
|
||||
final List<CustomDrawerItem> items;
|
||||
|
||||
/// header widget of the drawer
|
||||
final Widget header;
|
||||
|
||||
@override
|
||||
_CustomDrawerState createState() => _CustomDrawerState();
|
||||
}
|
||||
|
||||
class _CustomDrawerState extends State<CustomDrawer>
|
||||
with SingleTickerProviderStateMixin {
|
||||
// TODO: [Leptopoda] maybe integrate the animation controller into the drawerController
|
||||
late final CustomDrawerController _controller;
|
||||
late final AnimationController _animationController;
|
||||
late final Animation<Offset> _childSlideAnimation;
|
||||
late final Animation<Offset> _overlaySlideAnimation;
|
||||
late double _offsetValue;
|
||||
late Offset _freshPosition;
|
||||
late Offset _startPosition;
|
||||
bool _captured = false;
|
||||
|
||||
static const _duration = Duration(milliseconds: 250);
|
||||
static const _slideThreshold = 0.5;
|
||||
static const _openRatio = 0.75;
|
||||
static const _overlayRadius = 28.0;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
|
||||
_controller = widget.controller ?? CustomDrawerController();
|
||||
_controller.addListener(_handleControllerChanged);
|
||||
|
||||
_animationController = AnimationController(
|
||||
vsync: this,
|
||||
duration: _duration,
|
||||
value: _controller.value.visible ? 1 : 0,
|
||||
);
|
||||
|
||||
_childSlideAnimation = Tween<Offset>(
|
||||
begin: Offset.zero,
|
||||
end: const Offset(_openRatio, 0),
|
||||
).animate(
|
||||
_animationController,
|
||||
);
|
||||
|
||||
_overlaySlideAnimation = Tween<Offset>(
|
||||
begin: const Offset(-1, 0),
|
||||
end: Offset.zero,
|
||||
).animate(
|
||||
_animationController,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final _drawer = Align(
|
||||
alignment: AlignmentDirectional.centerStart,
|
||||
child: FractionallySizedBox(
|
||||
widthFactor: _openRatio,
|
||||
child: Material(
|
||||
color: LocalDatabaseService.colorSettings.drawerColor,
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: <Widget>[
|
||||
widget.header,
|
||||
Divider(height: 1, color: AppTheme.drawerDividerColor),
|
||||
ListView.builder(
|
||||
padding: const EdgeInsets.only(top: 4.0),
|
||||
physics: const BouncingScrollPhysics(),
|
||||
shrinkWrap: true,
|
||||
itemCount: widget.items.length,
|
||||
itemBuilder: _buildItem,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
// TODO: [Leptopoda] move the drawer overlay into the main scaffold so we don't need to deal with positioning
|
||||
final rtl = Directionality.of(context) == TextDirection.rtl;
|
||||
|
||||
/// menu and arrow icon animation overlay
|
||||
final _drawerOverlay = IconButton(
|
||||
icon: AnimatedIcon(
|
||||
icon: AnimatedIcons.arrow_menu,
|
||||
color: AppTheme.drawerAnimationIconColor,
|
||||
progress: ReverseAnimation(_animationController),
|
||||
),
|
||||
padding: EdgeInsets.only(
|
||||
top: MediaQuery.of(context).padding.top + (rtl ? 25 : 10),
|
||||
),
|
||||
tooltip: S.of(context).mainMenu,
|
||||
onPressed: () => _controller.toggleDrawer(),
|
||||
);
|
||||
|
||||
final _mainView = SlideTransition(
|
||||
position: _childSlideAnimation,
|
||||
textDirection: Directionality.of(context),
|
||||
child: ValueListenableBuilder<CustomDrawerValue>(
|
||||
valueListenable: _controller,
|
||||
// TODO: [Leptopdoa] why isn't it working with GestureDetector?
|
||||
builder: (_, value, child) => InkWell(
|
||||
onTap: _controller.hideDrawer,
|
||||
focusColor: Colors.transparent,
|
||||
child: IgnorePointer(
|
||||
ignoring: value.visible,
|
||||
child: child,
|
||||
),
|
||||
),
|
||||
child: DecoratedBox(
|
||||
decoration: BoxDecoration(
|
||||
color: LocalDatabaseService.colorSettings.drawerColor,
|
||||
boxShadow: [
|
||||
BoxShadow(
|
||||
color: AppTheme.drawerBoxerShadowColor,
|
||||
blurRadius: 24,
|
||||
),
|
||||
],
|
||||
),
|
||||
child: Stack(
|
||||
children: <Widget>[
|
||||
widget.child,
|
||||
_drawerOverlay,
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
// TODO: [Leptopdoa] should the geture also apply to the drawer?
|
||||
return Stack(
|
||||
children: <Widget>[
|
||||
_drawer,
|
||||
GestureDetector(
|
||||
onHorizontalDragStart:
|
||||
widget.disabledGestures ? null : _handleDragStart,
|
||||
onHorizontalDragUpdate:
|
||||
widget.disabledGestures ? null : _handleDragUpdate,
|
||||
onHorizontalDragEnd: widget.disabledGestures ? null : _handleDragEnd,
|
||||
onHorizontalDragCancel:
|
||||
widget.disabledGestures ? null : _handleDragCancel,
|
||||
child: _mainView,
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildItem(BuildContext context, int index) {
|
||||
final item = widget.items[index];
|
||||
|
||||
final Widget child;
|
||||
|
||||
if (item.selected) {
|
||||
final overlay = SlideTransition(
|
||||
position: _overlaySlideAnimation,
|
||||
textDirection: Directionality.of(context),
|
||||
child: Container(
|
||||
width: MediaQuery.of(context).size.width * 0.75 - 64,
|
||||
height: 46,
|
||||
decoration: BoxDecoration(
|
||||
color: LocalDatabaseService.colorSettings.drawerHighlightItemColor,
|
||||
borderRadius: const BorderRadiusDirectional.horizontal(
|
||||
end: Radius.circular(_overlayRadius),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
child = Stack(
|
||||
children: <Widget>[
|
||||
overlay,
|
||||
item,
|
||||
],
|
||||
);
|
||||
} else {
|
||||
child = item;
|
||||
}
|
||||
return Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: 8),
|
||||
child: child,
|
||||
);
|
||||
}
|
||||
|
||||
void _handleControllerChanged() {
|
||||
_controller.value.visible
|
||||
? _animationController.forward()
|
||||
: _animationController.reverse();
|
||||
}
|
||||
|
||||
void _handleDragStart(DragStartDetails details) {
|
||||
_captured = true;
|
||||
_startPosition = details.globalPosition;
|
||||
_offsetValue = _animationController.value;
|
||||
}
|
||||
|
||||
void _handleDragUpdate(DragUpdateDetails details) {
|
||||
if (!_captured) return;
|
||||
|
||||
final screenSize = MediaQuery.of(context).size;
|
||||
final rtl = Directionality.of(context) == TextDirection.rtl;
|
||||
|
||||
_freshPosition = details.globalPosition;
|
||||
|
||||
final diff = (_freshPosition - _startPosition).dx;
|
||||
|
||||
_animationController.value = _offsetValue +
|
||||
(diff / (screenSize.width * _openRatio)) * (rtl ? -1 : 1);
|
||||
}
|
||||
|
||||
void _handleDragEnd(DragEndDetails details) {
|
||||
if (!_captured) return;
|
||||
|
||||
_captured = false;
|
||||
|
||||
if (_animationController.value >= _slideThreshold) {
|
||||
if (_controller.value.visible) {
|
||||
_animationController.forward();
|
||||
} else {
|
||||
_controller.showDrawer();
|
||||
}
|
||||
} else {
|
||||
if (!_controller.value.visible) {
|
||||
_animationController.reverse();
|
||||
} else {
|
||||
_controller.hideDrawer();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void _handleDragCancel() {
|
||||
_captured = false;
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_controller.removeListener(_handleControllerChanged);
|
||||
_animationController.dispose();
|
||||
|
||||
_controller.dispose();
|
||||
|
||||
super.dispose();
|
||||
}
|
||||
}
|
|
@ -1,312 +0,0 @@
|
|||
/*
|
||||
This file is part of Sanmill.
|
||||
Copyright (C) 2019-2021 The Sanmill developers (see AUTHORS file)
|
||||
|
||||
Sanmill is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
Sanmill is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
part of 'package:sanmill/screens/navigation_home_screen.dart';
|
||||
|
||||
class DrawerController extends StatefulWidget {
|
||||
const DrawerController({
|
||||
Key? key,
|
||||
this.drawerWidth = AppTheme.drawerWidth,
|
||||
required this.onDrawerCall,
|
||||
required this.screenView,
|
||||
this.animatedIconData = AnimatedIcons.arrow_menu,
|
||||
this.menuView,
|
||||
this.drawerIsOpen,
|
||||
required this.screenIndex,
|
||||
}) : super(key: key);
|
||||
|
||||
final double drawerWidth;
|
||||
final Function(DrawerIndex) onDrawerCall;
|
||||
final Widget screenView;
|
||||
final Function(bool)? drawerIsOpen;
|
||||
final AnimatedIconData animatedIconData;
|
||||
final Widget? menuView;
|
||||
final DrawerIndex screenIndex;
|
||||
|
||||
@override
|
||||
_DrawerControllerState createState() => _DrawerControllerState();
|
||||
}
|
||||
|
||||
class _DrawerControllerState extends State<DrawerController>
|
||||
with TickerProviderStateMixin {
|
||||
late final ScrollController scrollController;
|
||||
late final AnimationController iconAnimationController;
|
||||
late final AnimationController animationController;
|
||||
|
||||
double scrollOffset = 0.0;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
animationController = AnimationController(
|
||||
duration: const Duration(seconds: 2),
|
||||
vsync: this,
|
||||
);
|
||||
|
||||
iconAnimationController =
|
||||
AnimationController(vsync: this, duration: Duration.zero);
|
||||
|
||||
iconAnimationController.animateTo(
|
||||
1.0,
|
||||
duration: Duration.zero,
|
||||
curve: Curves.fastOutSlowIn,
|
||||
);
|
||||
|
||||
scrollController =
|
||||
ScrollController(initialScrollOffset: widget.drawerWidth);
|
||||
|
||||
scrollController.addListener(() {
|
||||
if (scrollController.offset <= 0) {
|
||||
if (scrollOffset != 1.0) {
|
||||
setState(() {
|
||||
scrollOffset = 1.0;
|
||||
try {
|
||||
widget.drawerIsOpen!(true);
|
||||
} catch (_) {}
|
||||
});
|
||||
}
|
||||
iconAnimationController.animateTo(
|
||||
0.0,
|
||||
duration: Duration.zero,
|
||||
curve: Curves.fastOutSlowIn,
|
||||
);
|
||||
} else if (scrollController.offset < widget.drawerWidth.floor()) {
|
||||
iconAnimationController.animateTo(
|
||||
(scrollController.offset * 100 / (widget.drawerWidth)) / 100,
|
||||
duration: Duration.zero,
|
||||
curve: Curves.fastOutSlowIn,
|
||||
);
|
||||
} else {
|
||||
if (scrollOffset != 0.0) {
|
||||
setState(() {
|
||||
scrollOffset = 0.0;
|
||||
try {
|
||||
widget.drawerIsOpen!(false);
|
||||
} catch (_) {}
|
||||
});
|
||||
}
|
||||
iconAnimationController.animateTo(
|
||||
1.0,
|
||||
duration: Duration.zero,
|
||||
curve: Curves.fastOutSlowIn,
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
WidgetsBinding.instance!.addPostFrameCallback((_) => getInitState());
|
||||
super.initState();
|
||||
}
|
||||
|
||||
bool getInitState() {
|
||||
scrollController.jumpTo(
|
||||
widget.drawerWidth,
|
||||
);
|
||||
return true;
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final bool ltr = Directionality.of(context) == TextDirection.ltr;
|
||||
|
||||
// this just menu and arrow icon animation
|
||||
final inkWell = InkWell(
|
||||
borderRadius: BorderRadius.circular(AppBar().preferredSize.height),
|
||||
child: Center(
|
||||
// if you use your own menu view UI you add form initialization
|
||||
child: widget.menuView ??
|
||||
Semantics(
|
||||
label: S.of(context).mainMenu,
|
||||
child: AnimatedIcon(
|
||||
icon: widget.animatedIconData,
|
||||
color: AppTheme.drawerAnimationIconColor,
|
||||
progress: iconAnimationController,
|
||||
),
|
||||
),
|
||||
),
|
||||
onTap: () {
|
||||
FocusScope.of(context).requestFocus(FocusNode());
|
||||
onDrawerClick();
|
||||
},
|
||||
);
|
||||
|
||||
final List<DrawerListItem> drawerItems = [
|
||||
DrawerListItem(
|
||||
index: DrawerIndex.humanVsAi,
|
||||
title: S.of(context).humanVsAi,
|
||||
icon: const Icon(FluentIcons.person_24_regular),
|
||||
),
|
||||
DrawerListItem(
|
||||
index: DrawerIndex.humanVsHuman,
|
||||
title: S.of(context).humanVsHuman,
|
||||
icon: const Icon(FluentIcons.people_24_regular),
|
||||
),
|
||||
DrawerListItem(
|
||||
index: DrawerIndex.aiVsAi,
|
||||
title: S.of(context).aiVsAi,
|
||||
icon: const Icon(FluentIcons.bot_24_regular),
|
||||
),
|
||||
DrawerListItem(
|
||||
index: DrawerIndex.preferences,
|
||||
title: S.of(context).preferences,
|
||||
icon: const Icon(FluentIcons.options_24_regular),
|
||||
),
|
||||
DrawerListItem(
|
||||
index: DrawerIndex.ruleSettings,
|
||||
title: S.of(context).ruleSettings,
|
||||
icon: const Icon(FluentIcons.task_list_ltr_24_regular),
|
||||
),
|
||||
DrawerListItem(
|
||||
index: DrawerIndex.personalization,
|
||||
title: S.of(context).personalization,
|
||||
icon: const Icon(FluentIcons.design_ideas_24_regular),
|
||||
),
|
||||
DrawerListItem(
|
||||
index: DrawerIndex.feedback,
|
||||
title: S.of(context).feedback,
|
||||
icon: const Icon(FluentIcons.chat_warning_24_regular),
|
||||
),
|
||||
DrawerListItem(
|
||||
index: DrawerIndex.Help,
|
||||
title: S.of(context).help,
|
||||
icon: const Icon(FluentIcons.question_circle_24_regular),
|
||||
),
|
||||
DrawerListItem(
|
||||
index: DrawerIndex.About,
|
||||
title: S.of(context).about,
|
||||
icon: const Icon(FluentIcons.info_24_regular),
|
||||
),
|
||||
];
|
||||
|
||||
final animatedBuilder = AnimatedBuilder(
|
||||
animation: iconAnimationController,
|
||||
builder: (BuildContext context, Widget? child) {
|
||||
return Transform(
|
||||
// transform we use for the stable drawer
|
||||
// we not need to move with scroll view
|
||||
transform:
|
||||
Matrix4.translationValues(scrollController.offset, 0.0, 0.0),
|
||||
child: HomeDrawer(
|
||||
screenIndex: widget.screenIndex,
|
||||
iconAnimationController: iconAnimationController,
|
||||
callBackIndex: (DrawerIndex indexType) {
|
||||
onDrawerClick();
|
||||
try {
|
||||
widget.onDrawerCall(indexType);
|
||||
} catch (_) {}
|
||||
},
|
||||
items: drawerItems,
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
var tapOffset = 0;
|
||||
|
||||
if (!ltr) {
|
||||
tapOffset = 10; // TODO: WAR
|
||||
}
|
||||
|
||||
final stack = Stack(
|
||||
children: <Widget>[
|
||||
// this IgnorePointer we use as touch(user Interface) widget.screen View,
|
||||
// for example scrolloffset == 1
|
||||
// means drawer is close we just allow touching all widget.screen View
|
||||
IgnorePointer(
|
||||
ignoring: scrollOffset == 1 || false,
|
||||
child: widget.screenView,
|
||||
),
|
||||
// alternative touch(user Interface) for widget.screen,
|
||||
// for example, drawer is close we need to
|
||||
// tap on a few home screen area and close the drawer
|
||||
if (scrollOffset == 1.0)
|
||||
InkWell(
|
||||
onTap: onDrawerClick,
|
||||
),
|
||||
Padding(
|
||||
padding: EdgeInsets.only(
|
||||
top: MediaQuery.of(context).padding.top + tapOffset,
|
||||
),
|
||||
child: SizedBox(
|
||||
width: kToolbarHeight,
|
||||
height: kToolbarHeight,
|
||||
child: Material(
|
||||
color: Colors.transparent,
|
||||
child: inkWell,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
|
||||
final row = Row(
|
||||
children: <Widget>[
|
||||
SizedBox(
|
||||
width: widget.drawerWidth,
|
||||
// we divided first drawer Width with HomeDrawer
|
||||
// and second full-screen Width with all home screen,
|
||||
// we called screen View
|
||||
height: MediaQuery.of(context).size.height,
|
||||
child: animatedBuilder,
|
||||
),
|
||||
SizedBox(
|
||||
width: MediaQuery.of(context).size.width,
|
||||
height: MediaQuery.of(context).size.height,
|
||||
//full-screen Width with widget.screenView
|
||||
child: Container(
|
||||
decoration: BoxDecoration(
|
||||
color: LocalDatabaseService.colorSettings.drawerColor,
|
||||
boxShadow: <BoxShadow>[
|
||||
BoxShadow(
|
||||
color: AppTheme.drawerBoxerShadowColor,
|
||||
blurRadius: 24,
|
||||
),
|
||||
],
|
||||
),
|
||||
child: stack,
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
|
||||
return Material(
|
||||
color: LocalDatabaseService.colorSettings.drawerColor,
|
||||
child: SingleChildScrollView(
|
||||
controller: scrollController,
|
||||
scrollDirection: Axis.horizontal,
|
||||
physics: const PageScrollPhysics(parent: ClampingScrollPhysics()),
|
||||
child: SizedBox(
|
||||
height: MediaQuery.of(context).size.height,
|
||||
width: MediaQuery.of(context).size.width + widget.drawerWidth,
|
||||
// we use with as screen width and add drawerWidth
|
||||
// (from navigation_home_screen)
|
||||
child: row,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
void onDrawerClick() {
|
||||
// if scrollController.offset != 0.0
|
||||
// then we set to closed the drawer(with animation to offset zero position)
|
||||
// if is not 1 then open the drawer
|
||||
scrollController.animateTo(
|
||||
scrollController.offset == 0.0 ? widget.drawerWidth : 0.0,
|
||||
duration: const Duration(milliseconds: 400),
|
||||
curve: Curves.fastOutSlowIn,
|
||||
);
|
||||
}
|
||||
}
|
|
@ -19,7 +19,7 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:sanmill/generated/intl/l10n.dart';
|
||||
import 'package:sanmill/screens/navigation_home_screen.dart';
|
||||
import 'package:sanmill/screens/home.dart';
|
||||
|
||||
void main() {
|
||||
Widget makeTestableWidget({required Widget child, required Locale locale}) {
|
||||
|
@ -32,7 +32,7 @@ void main() {
|
|||
}
|
||||
|
||||
testWidgets('Widget', (WidgetTester tester) async {
|
||||
const _screen = NavigationHomeScreen();
|
||||
const _screen = Home();
|
||||
await tester.pumpWidget(
|
||||
makeTestableWidget(
|
||||
child: _screen,
|
||||
|
|
Loading…
Reference in New Issue