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:path_provider/path_provider.dart';
|
||||||
import 'package:sanmill/generated/intl/l10n.dart';
|
import 'package:sanmill/generated/intl/l10n.dart';
|
||||||
import 'package:sanmill/models/display.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/audios.dart';
|
||||||
import 'package:sanmill/services/enviornment_config.dart';
|
import 'package:sanmill/services/enviornment_config.dart';
|
||||||
import 'package:sanmill/services/language_info.dart';
|
import 'package:sanmill/services/language_info.dart';
|
||||||
|
@ -103,7 +103,7 @@ class SanmillApp extends StatelessWidget {
|
||||||
snackBar: SnackBar(
|
snackBar: SnackBar(
|
||||||
content: Text(S.of(context).tapBackAgainToLeave),
|
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';
|
import 'package:url_launcher/url_launcher.dart';
|
||||||
|
|
||||||
class AboutPage extends StatelessWidget {
|
class AboutPage extends StatelessWidget {
|
||||||
final String tag = "[about] ";
|
const AboutPage({Key? key}) : super(key: key);
|
||||||
|
|
||||||
|
static const String tag = "[about] ";
|
||||||
|
|
||||||
String get mode {
|
String get mode {
|
||||||
if (kDebugMode) {
|
if (kDebugMode) {
|
||||||
|
|
|
@ -242,7 +242,6 @@ class Board extends StatelessWidget {
|
||||||
|
|
||||||
final bool ltr = Directionality.of(context) == TextDirection.ltr;
|
final bool ltr = Directionality.of(context) == TextDirection.ltr;
|
||||||
|
|
||||||
|
|
||||||
if (ltr) {
|
if (ltr) {
|
||||||
for (final file in ['a', 'b', 'c', 'd', 'e', 'f', 'g']) {
|
for (final file in ['a', 'b', 'c', 'd', 'e', 'f', 'g']) {
|
||||||
for (final rank in ['7', '6', '5', '4', '3', '2', '1']) {
|
for (final rank in ['7', '6', '5', '4', '3', '2', '1']) {
|
||||||
|
|
|
@ -53,6 +53,7 @@ double boardWidth = 0.0;
|
||||||
class GamePage extends StatefulWidget {
|
class GamePage extends StatefulWidget {
|
||||||
final EngineType engineType;
|
final EngineType engineType;
|
||||||
|
|
||||||
|
// TODO: use gameInstamce.enginetype
|
||||||
const GamePage(this.engineType, {Key? key}) : super(key: key);
|
const GamePage(this.engineType, {Key? key}) : super(key: key);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
@ -886,12 +887,10 @@ class _GamePageState extends State<GamePage>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
void onOptionButtonPressed() {
|
void onOptionButtonPressed() => Navigator.push(
|
||||||
Navigator.push(
|
|
||||||
context,
|
context,
|
||||||
MaterialPageRoute(builder: (context) => GameSettingsPage()),
|
MaterialPageRoute(builder: (context) => const GameSettingsPage()),
|
||||||
);
|
);
|
||||||
}
|
|
||||||
|
|
||||||
void onMoveButtonPressed() {
|
void onMoveButtonPressed() {
|
||||||
final List<Widget> _historyNavigation = [
|
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';
|
part 'package:sanmill/screens/game_settings/move_time_slider.dart';
|
||||||
|
|
||||||
class GameSettingsPage extends StatelessWidget {
|
class GameSettingsPage extends StatelessWidget {
|
||||||
|
const GameSettingsPage({Key? key}) : super(key: key);
|
||||||
static const List<String> _algorithmNames = ['Alpha-Beta', 'PVS', 'MTD(f)'];
|
static const List<String> _algorithmNames = ['Alpha-Beta', 'PVS', 'MTD(f)'];
|
||||||
|
|
||||||
static const String _tag = "[game_settings_page]";
|
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';
|
import 'package:sanmill/shared/theme/app_theme.dart';
|
||||||
|
|
||||||
class HelpScreen extends StatelessWidget {
|
class HelpScreen extends StatelessWidget {
|
||||||
|
const HelpScreen({Key? key}) : super(key: key);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Scaffold(
|
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';
|
part 'package:sanmill/screens/personalization_settings/language_picker.dart';
|
||||||
|
|
||||||
class PersonalizationSettingsPage extends StatelessWidget {
|
class PersonalizationSettingsPage extends StatelessWidget {
|
||||||
|
const PersonalizationSettingsPage({Key? key}) : super(key: key);
|
||||||
|
|
||||||
void setBoardBorderLineWidth(BuildContext context) => showModalBottomSheet(
|
void setBoardBorderLineWidth(BuildContext context) => showModalBottomSheet(
|
||||||
context: context,
|
context: context,
|
||||||
builder: (_) => const _BoardBorderWidthSlider(),
|
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/material.dart';
|
||||||
import 'package:flutter_test/flutter_test.dart';
|
import 'package:flutter_test/flutter_test.dart';
|
||||||
import 'package:sanmill/generated/intl/l10n.dart';
|
import 'package:sanmill/generated/intl/l10n.dart';
|
||||||
import 'package:sanmill/screens/navigation_home_screen.dart';
|
import 'package:sanmill/screens/home.dart';
|
||||||
|
|
||||||
void main() {
|
void main() {
|
||||||
Widget makeTestableWidget({required Widget child, required Locale locale}) {
|
Widget makeTestableWidget({required Widget child, required Locale locale}) {
|
||||||
|
@ -32,7 +32,7 @@ void main() {
|
||||||
}
|
}
|
||||||
|
|
||||||
testWidgets('Widget', (WidgetTester tester) async {
|
testWidgets('Widget', (WidgetTester tester) async {
|
||||||
const _screen = NavigationHomeScreen();
|
const _screen = Home();
|
||||||
await tester.pumpWidget(
|
await tester.pumpWidget(
|
||||||
makeTestableWidget(
|
makeTestableWidget(
|
||||||
child: _screen,
|
child: _screen,
|
||||||
|
|
Loading…
Reference in New Issue