tv: scrollable policy, focus improvements, pick page review
This commit is contained in:
parent
f09f3ede28
commit
7cf5538408
23 changed files with 313 additions and 167 deletions
|
@ -5,6 +5,7 @@ import 'package:aves/widgets/about/credits.dart';
|
||||||
import 'package:aves/widgets/about/licenses.dart';
|
import 'package:aves/widgets/about/licenses.dart';
|
||||||
import 'package:aves/widgets/about/translators.dart';
|
import 'package:aves/widgets/about/translators.dart';
|
||||||
import 'package:aves/widgets/common/basic/insets.dart';
|
import 'package:aves/widgets/common/basic/insets.dart';
|
||||||
|
import 'package:aves/widgets/common/basic/tv_edge_focus.dart';
|
||||||
import 'package:aves/widgets/common/behaviour/pop/scope.dart';
|
import 'package:aves/widgets/common/behaviour/pop/scope.dart';
|
||||||
import 'package:aves/widgets/common/behaviour/pop/tv_navigation.dart';
|
import 'package:aves/widgets/common/behaviour/pop/tv_navigation.dart';
|
||||||
import 'package:aves/widgets/common/extensions/build_context.dart';
|
import 'package:aves/widgets/common/extensions/build_context.dart';
|
||||||
|
@ -28,7 +29,8 @@ class AboutPage extends StatelessWidget {
|
||||||
sliver: SliverList(
|
sliver: SliverList(
|
||||||
delegate: SliverChildListDelegate(
|
delegate: SliverChildListDelegate(
|
||||||
[
|
[
|
||||||
AppReference(showLogo: !useTvLayout),
|
const TvEdgeFocus(),
|
||||||
|
const AppReference(),
|
||||||
if (!settings.useTvLayout) ...[
|
if (!settings.useTvLayout) ...[
|
||||||
const Divider(),
|
const Divider(),
|
||||||
const BugReport(),
|
const BugReport(),
|
||||||
|
|
|
@ -10,12 +10,7 @@ import 'package:flutter/material.dart';
|
||||||
import 'package:package_info_plus/package_info_plus.dart';
|
import 'package:package_info_plus/package_info_plus.dart';
|
||||||
|
|
||||||
class AppReference extends StatefulWidget {
|
class AppReference extends StatefulWidget {
|
||||||
final bool showLogo;
|
const AppReference({super.key});
|
||||||
|
|
||||||
const AppReference({
|
|
||||||
super.key,
|
|
||||||
required this.showLogo,
|
|
||||||
});
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
State<AppReference> createState() => _AppReferenceState();
|
State<AppReference> createState() => _AppReferenceState();
|
||||||
|
@ -24,6 +19,13 @@ class AppReference extends StatefulWidget {
|
||||||
class _AppReferenceState extends State<AppReference> {
|
class _AppReferenceState extends State<AppReference> {
|
||||||
late Future<PackageInfo> _packageInfoLoader;
|
late Future<PackageInfo> _packageInfoLoader;
|
||||||
|
|
||||||
|
static const _appTitleStyle = TextStyle(
|
||||||
|
fontSize: 20,
|
||||||
|
fontWeight: FontWeight.normal,
|
||||||
|
letterSpacing: 1.0,
|
||||||
|
fontFeatures: [FontFeature.enable('smcp')],
|
||||||
|
);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
super.initState();
|
super.initState();
|
||||||
|
@ -44,28 +46,19 @@ class _AppReferenceState extends State<AppReference> {
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget _buildAvesLine() {
|
Widget _buildAvesLine() {
|
||||||
const style = TextStyle(
|
|
||||||
fontSize: 20,
|
|
||||||
fontWeight: FontWeight.normal,
|
|
||||||
letterSpacing: 1.0,
|
|
||||||
fontFeatures: [FontFeature.enable('smcp')],
|
|
||||||
);
|
|
||||||
|
|
||||||
return FutureBuilder<PackageInfo>(
|
return FutureBuilder<PackageInfo>(
|
||||||
future: _packageInfoLoader,
|
future: _packageInfoLoader,
|
||||||
builder: (context, snapshot) {
|
builder: (context, snapshot) {
|
||||||
return Row(
|
return Row(
|
||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: MainAxisSize.min,
|
||||||
children: [
|
children: [
|
||||||
if (widget.showLogo) ...[
|
AvesLogo(
|
||||||
AvesLogo(
|
size: _appTitleStyle.fontSize! * MediaQuery.textScaleFactorOf(context) * 1.3,
|
||||||
size: style.fontSize! * MediaQuery.textScaleFactorOf(context) * 1.3,
|
),
|
||||||
),
|
const SizedBox(width: 8),
|
||||||
const SizedBox(width: 8),
|
|
||||||
],
|
|
||||||
Text(
|
Text(
|
||||||
'${context.l10n.appName} ${snapshot.data?.version}',
|
'${context.l10n.appName} ${snapshot.data?.version}',
|
||||||
style: style,
|
style: _appTitleStyle,
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import 'package:aves/utils/constants.dart';
|
import 'package:aves/widgets/about/title.dart';
|
||||||
import 'package:aves/widgets/common/basic/link_chip.dart';
|
import 'package:aves/widgets/common/basic/link_chip.dart';
|
||||||
import 'package:aves/widgets/common/extensions/build_context.dart';
|
import 'package:aves/widgets/common/extensions/build_context.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
@ -14,13 +14,7 @@ class AboutCredits extends StatelessWidget {
|
||||||
child: Column(
|
child: Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
ConstrainedBox(
|
AboutSectionTitle(text: l10n.aboutCreditsSectionTitle),
|
||||||
constraints: const BoxConstraints(minHeight: kMinInteractiveDimension),
|
|
||||||
child: Align(
|
|
||||||
alignment: AlignmentDirectional.centerStart,
|
|
||||||
child: Text(l10n.aboutCreditsSectionTitle, style: Constants.knownTitleTextStyle),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
const SizedBox(height: 8),
|
const SizedBox(height: 8),
|
||||||
Text.rich(
|
Text.rich(
|
||||||
TextSpan(
|
TextSpan(
|
||||||
|
|
|
@ -1,8 +1,9 @@
|
||||||
import 'package:aves/app_flavor.dart';
|
import 'package:aves/app_flavor.dart';
|
||||||
|
import 'package:aves/model/settings/settings.dart';
|
||||||
import 'package:aves/ref/brand_colors.dart';
|
import 'package:aves/ref/brand_colors.dart';
|
||||||
import 'package:aves/theme/colors.dart';
|
import 'package:aves/theme/colors.dart';
|
||||||
import 'package:aves/utils/constants.dart';
|
|
||||||
import 'package:aves/utils/dependencies.dart';
|
import 'package:aves/utils/dependencies.dart';
|
||||||
|
import 'package:aves/widgets/about/title.dart';
|
||||||
import 'package:aves/widgets/common/basic/link_chip.dart';
|
import 'package:aves/widgets/common/basic/link_chip.dart';
|
||||||
import 'package:aves/widgets/common/extensions/build_context.dart';
|
import 'package:aves/widgets/common/extensions/build_context.dart';
|
||||||
import 'package:aves/widgets/common/identity/aves_expansion_tile.dart';
|
import 'package:aves/widgets/common/identity/aves_expansion_tile.dart';
|
||||||
|
@ -50,30 +51,32 @@ class _LicensesState extends State<Licenses> {
|
||||||
[
|
[
|
||||||
_buildHeader(),
|
_buildHeader(),
|
||||||
const SizedBox(height: 16),
|
const SizedBox(height: 16),
|
||||||
AvesExpansionTile(
|
if (!settings.useTvLayout) ...[
|
||||||
title: context.l10n.aboutLicensesAndroidLibrariesSectionTitle,
|
AvesExpansionTile(
|
||||||
highlightColor: colors.fromBrandColor(BrandColors.android),
|
title: context.l10n.aboutLicensesAndroidLibrariesSectionTitle,
|
||||||
expandedNotifier: _expandedNotifier,
|
highlightColor: colors.fromBrandColor(BrandColors.android),
|
||||||
children: _platform.map((package) => LicenseRow(package: package)).toList(),
|
expandedNotifier: _expandedNotifier,
|
||||||
),
|
children: _platform.map((package) => LicenseRow(package: package)).toList(),
|
||||||
AvesExpansionTile(
|
),
|
||||||
title: context.l10n.aboutLicensesFlutterPluginsSectionTitle,
|
AvesExpansionTile(
|
||||||
highlightColor: colors.fromBrandColor(BrandColors.flutter),
|
title: context.l10n.aboutLicensesFlutterPluginsSectionTitle,
|
||||||
expandedNotifier: _expandedNotifier,
|
highlightColor: colors.fromBrandColor(BrandColors.flutter),
|
||||||
children: _flutterPlugins.map((package) => LicenseRow(package: package)).toList(),
|
expandedNotifier: _expandedNotifier,
|
||||||
),
|
children: _flutterPlugins.map((package) => LicenseRow(package: package)).toList(),
|
||||||
AvesExpansionTile(
|
),
|
||||||
title: context.l10n.aboutLicensesFlutterPackagesSectionTitle,
|
AvesExpansionTile(
|
||||||
highlightColor: colors.fromBrandColor(BrandColors.flutter),
|
title: context.l10n.aboutLicensesFlutterPackagesSectionTitle,
|
||||||
expandedNotifier: _expandedNotifier,
|
highlightColor: colors.fromBrandColor(BrandColors.flutter),
|
||||||
children: _flutterPackages.map((package) => LicenseRow(package: package)).toList(),
|
expandedNotifier: _expandedNotifier,
|
||||||
),
|
children: _flutterPackages.map((package) => LicenseRow(package: package)).toList(),
|
||||||
AvesExpansionTile(
|
),
|
||||||
title: context.l10n.aboutLicensesDartPackagesSectionTitle,
|
AvesExpansionTile(
|
||||||
highlightColor: colors.fromBrandColor(BrandColors.flutter),
|
title: context.l10n.aboutLicensesDartPackagesSectionTitle,
|
||||||
expandedNotifier: _expandedNotifier,
|
highlightColor: colors.fromBrandColor(BrandColors.flutter),
|
||||||
children: _dartPackages.map((package) => LicenseRow(package: package)).toList(),
|
expandedNotifier: _expandedNotifier,
|
||||||
),
|
children: _dartPackages.map((package) => LicenseRow(package: package)).toList(),
|
||||||
|
),
|
||||||
|
],
|
||||||
Center(
|
Center(
|
||||||
child: AvesOutlinedButton(
|
child: AvesOutlinedButton(
|
||||||
label: context.l10n.aboutLicensesShowAllButtonLabel,
|
label: context.l10n.aboutLicensesShowAllButtonLabel,
|
||||||
|
@ -104,13 +107,7 @@ class _LicensesState extends State<Licenses> {
|
||||||
child: Column(
|
child: Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
ConstrainedBox(
|
AboutSectionTitle(text: context.l10n.aboutLicensesSectionTitle),
|
||||||
constraints: const BoxConstraints(minHeight: kMinInteractiveDimension),
|
|
||||||
child: Align(
|
|
||||||
alignment: AlignmentDirectional.centerStart,
|
|
||||||
child: Text(context.l10n.aboutLicensesSectionTitle, style: Constants.knownTitleTextStyle),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
const SizedBox(height: 8),
|
const SizedBox(height: 8),
|
||||||
Text(context.l10n.aboutLicensesBanner),
|
Text(context.l10n.aboutLicensesBanner),
|
||||||
],
|
],
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
import 'package:aves/model/settings/settings.dart';
|
||||||
import 'package:aves/widgets/common/basic/markdown_container.dart';
|
import 'package:aves/widgets/common/basic/markdown_container.dart';
|
||||||
import 'package:aves/widgets/common/extensions/build_context.dart';
|
import 'package:aves/widgets/common/extensions/build_context.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
@ -14,6 +15,7 @@ class PolicyPage extends StatefulWidget {
|
||||||
|
|
||||||
class _PolicyPageState extends State<PolicyPage> {
|
class _PolicyPageState extends State<PolicyPage> {
|
||||||
late Future<String> _termsLoader;
|
late Future<String> _termsLoader;
|
||||||
|
final ScrollController _scrollController = ScrollController();
|
||||||
|
|
||||||
static const termsPath = 'assets/terms.md';
|
static const termsPath = 'assets/terms.md';
|
||||||
static const termsDirection = TextDirection.ltr;
|
static const termsDirection = TextDirection.ltr;
|
||||||
|
@ -28,26 +30,72 @@ class _PolicyPageState extends State<PolicyPage> {
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
appBar: AppBar(
|
appBar: AppBar(
|
||||||
|
automaticallyImplyLeading: !settings.useTvLayout,
|
||||||
title: Text(context.l10n.policyPageTitle),
|
title: Text(context.l10n.policyPageTitle),
|
||||||
),
|
),
|
||||||
body: SafeArea(
|
body: SafeArea(
|
||||||
child: Center(
|
child: FocusableActionDetector(
|
||||||
child: FutureBuilder<String>(
|
autofocus: true,
|
||||||
future: _termsLoader,
|
shortcuts: const {
|
||||||
builder: (context, snapshot) {
|
SingleActivator(LogicalKeyboardKey.arrowUp): _ScrollIntent.up(),
|
||||||
if (snapshot.hasError || snapshot.connectionState != ConnectionState.done) return const SizedBox();
|
SingleActivator(LogicalKeyboardKey.arrowDown): _ScrollIntent.down(),
|
||||||
final terms = snapshot.data!;
|
},
|
||||||
return Padding(
|
actions: {
|
||||||
padding: const EdgeInsets.symmetric(vertical: 8),
|
_ScrollIntent: CallbackAction<_ScrollIntent>(onInvoke: _onScrollIntent),
|
||||||
child: MarkdownContainer(
|
},
|
||||||
data: terms,
|
child: Center(
|
||||||
textDirection: termsDirection,
|
child: FutureBuilder<String>(
|
||||||
),
|
future: _termsLoader,
|
||||||
);
|
builder: (context, snapshot) {
|
||||||
},
|
if (snapshot.hasError || snapshot.connectionState != ConnectionState.done) return const SizedBox();
|
||||||
|
final terms = snapshot.data!;
|
||||||
|
return Padding(
|
||||||
|
padding: const EdgeInsets.symmetric(vertical: 8),
|
||||||
|
child: MarkdownContainer(
|
||||||
|
scrollController: _scrollController,
|
||||||
|
data: terms,
|
||||||
|
textDirection: termsDirection,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void _onScrollIntent(_ScrollIntent intent) {
|
||||||
|
late int factor;
|
||||||
|
switch (intent.type) {
|
||||||
|
case _ScrollDirection.up:
|
||||||
|
factor = -1;
|
||||||
|
break;
|
||||||
|
case _ScrollDirection.down:
|
||||||
|
factor = 1;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
_scrollController.animateTo(
|
||||||
|
_scrollController.offset + factor * 150,
|
||||||
|
duration: const Duration(milliseconds: 500),
|
||||||
|
curve: Curves.easeOutCubic,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class _ScrollIntent extends Intent {
|
||||||
|
const _ScrollIntent({
|
||||||
|
required this.type,
|
||||||
|
});
|
||||||
|
|
||||||
|
const _ScrollIntent.up() : type = _ScrollDirection.up;
|
||||||
|
|
||||||
|
const _ScrollIntent.down() : type = _ScrollDirection.down;
|
||||||
|
|
||||||
|
final _ScrollDirection type;
|
||||||
|
}
|
||||||
|
|
||||||
|
enum _ScrollDirection {
|
||||||
|
up,
|
||||||
|
down,
|
||||||
}
|
}
|
||||||
|
|
37
lib/widgets/about/title.dart
Normal file
37
lib/widgets/about/title.dart
Normal file
|
@ -0,0 +1,37 @@
|
||||||
|
import 'package:aves/model/settings/settings.dart';
|
||||||
|
import 'package:aves/utils/constants.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
|
class AboutSectionTitle extends StatelessWidget {
|
||||||
|
final String text;
|
||||||
|
|
||||||
|
const AboutSectionTitle({
|
||||||
|
super.key,
|
||||||
|
required this.text,
|
||||||
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
Widget child = Container(
|
||||||
|
alignment: AlignmentDirectional.centerStart,
|
||||||
|
constraints: const BoxConstraints(minHeight: kMinInteractiveDimension),
|
||||||
|
child: Text(text, style: Constants.knownTitleTextStyle),
|
||||||
|
);
|
||||||
|
|
||||||
|
if (settings.useTvLayout) {
|
||||||
|
child = InkWell(
|
||||||
|
borderRadius: const BorderRadius.all(Radius.circular(123)),
|
||||||
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.symmetric(horizontal: 16),
|
||||||
|
child: Row(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: [
|
||||||
|
child,
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return child;
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,6 +1,7 @@
|
||||||
import 'dart:math';
|
import 'dart:math';
|
||||||
|
|
||||||
import 'package:aves/utils/constants.dart';
|
import 'package:aves/utils/constants.dart';
|
||||||
|
import 'package:aves/widgets/about/title.dart';
|
||||||
import 'package:aves/widgets/common/basic/text/change_highlight.dart';
|
import 'package:aves/widgets/common/basic/text/change_highlight.dart';
|
||||||
import 'package:aves/widgets/common/extensions/build_context.dart';
|
import 'package:aves/widgets/common/extensions/build_context.dart';
|
||||||
import 'package:collection/collection.dart';
|
import 'package:collection/collection.dart';
|
||||||
|
@ -55,23 +56,16 @@ class AboutTranslators extends StatelessWidget {
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final l10n = context.l10n;
|
|
||||||
return Padding(
|
return Padding(
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 16),
|
padding: const EdgeInsets.symmetric(horizontal: 16),
|
||||||
child: Column(
|
child: Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
ConstrainedBox(
|
AboutSectionTitle(text: context.l10n.aboutTranslatorsSectionTitle),
|
||||||
constraints: const BoxConstraints(minHeight: kMinInteractiveDimension),
|
|
||||||
child: Align(
|
|
||||||
alignment: AlignmentDirectional.centerStart,
|
|
||||||
child: Text(l10n.aboutTranslatorsSectionTitle, style: Constants.knownTitleTextStyle),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
const SizedBox(height: 8),
|
const SizedBox(height: 8),
|
||||||
_RandomTextSpanHighlighter(
|
_RandomTextSpanHighlighter(
|
||||||
spans: translators.map((v) => v.name).toList(),
|
spans: translators.map((v) => v.name).toList(),
|
||||||
highlightColor: Theme.of(context).colorScheme.onPrimary,
|
color: Theme.of(context).colorScheme.onPrimary,
|
||||||
),
|
),
|
||||||
const SizedBox(height: 16),
|
const SizedBox(height: 16),
|
||||||
],
|
],
|
||||||
|
@ -82,11 +76,11 @@ class AboutTranslators extends StatelessWidget {
|
||||||
|
|
||||||
class _RandomTextSpanHighlighter extends StatefulWidget {
|
class _RandomTextSpanHighlighter extends StatefulWidget {
|
||||||
final List<String> spans;
|
final List<String> spans;
|
||||||
final Color highlightColor;
|
final Color color;
|
||||||
|
|
||||||
const _RandomTextSpanHighlighter({
|
const _RandomTextSpanHighlighter({
|
||||||
required this.spans,
|
required this.spans,
|
||||||
required this.highlightColor,
|
required this.color,
|
||||||
});
|
});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
@ -103,18 +97,21 @@ class _RandomTextSpanHighlighterState extends State<_RandomTextSpanHighlighter>
|
||||||
void initState() {
|
void initState() {
|
||||||
super.initState();
|
super.initState();
|
||||||
|
|
||||||
|
final color = widget.color;
|
||||||
_baseStyle = TextStyle(
|
_baseStyle = TextStyle(
|
||||||
|
color: color.withOpacity(.7),
|
||||||
shadows: [
|
shadows: [
|
||||||
Shadow(
|
Shadow(
|
||||||
color: widget.highlightColor.withOpacity(0),
|
color: color.withOpacity(0),
|
||||||
blurRadius: 0,
|
blurRadius: 0,
|
||||||
)
|
)
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
final highlightStyle = TextStyle(
|
final highlightStyle = TextStyle(
|
||||||
|
color: color.withOpacity(1),
|
||||||
shadows: [
|
shadows: [
|
||||||
Shadow(
|
Shadow(
|
||||||
color: widget.highlightColor,
|
color: color.withOpacity(1),
|
||||||
blurRadius: 3,
|
blurRadius: 3,
|
||||||
)
|
)
|
||||||
],
|
],
|
||||||
|
@ -133,7 +130,7 @@ class _RandomTextSpanHighlighterState extends State<_RandomTextSpanHighlighter>
|
||||||
..repeat(reverse: true);
|
..repeat(reverse: true);
|
||||||
_animatedStyle = ShadowedTextStyleTween(begin: _baseStyle, end: highlightStyle).animate(CurvedAnimation(
|
_animatedStyle = ShadowedTextStyleTween(begin: _baseStyle, end: highlightStyle).animate(CurvedAnimation(
|
||||||
parent: _controller,
|
parent: _controller,
|
||||||
curve: Curves.linear,
|
curve: Curves.easeInOutCubic,
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -540,6 +540,7 @@ class _AvesAppState extends State<AvesApp> with WidgetsBindingObserver {
|
||||||
? 'profile'
|
? 'profile'
|
||||||
: 'debug',
|
: 'debug',
|
||||||
'has_mobile_services': mobileServices.isServiceAvailable,
|
'has_mobile_services': mobileServices.isServiceAvailable,
|
||||||
|
'is_television': device.isTelevision,
|
||||||
'locales': WidgetsBinding.instance.window.locales.join(', '),
|
'locales': WidgetsBinding.instance.window.locales.join(', '),
|
||||||
'time_zone': '${now.timeZoneName} (${now.timeZoneOffset})',
|
'time_zone': '${now.timeZoneName} (${now.timeZoneOffset})',
|
||||||
});
|
});
|
||||||
|
|
|
@ -58,6 +58,7 @@ class EntrySetActionDelegate with FeedbackMixin, PermissionAwareMixin, SizeAware
|
||||||
}) {
|
}) {
|
||||||
final canWrite = !settings.isReadOnly;
|
final canWrite = !settings.isReadOnly;
|
||||||
final isMain = appMode == AppMode.main;
|
final isMain = appMode == AppMode.main;
|
||||||
|
final useTvLayout = settings.useTvLayout;
|
||||||
switch (action) {
|
switch (action) {
|
||||||
// general
|
// general
|
||||||
case EntrySetAction.configureView:
|
case EntrySetAction.configureView:
|
||||||
|
@ -70,9 +71,9 @@ class EntrySetActionDelegate with FeedbackMixin, PermissionAwareMixin, SizeAware
|
||||||
return isSelecting && selectedItemCount == itemCount;
|
return isSelecting && selectedItemCount == itemCount;
|
||||||
// browsing
|
// browsing
|
||||||
case EntrySetAction.searchCollection:
|
case EntrySetAction.searchCollection:
|
||||||
return !settings.useTvLayout && appMode.canNavigate && !isSelecting;
|
return !useTvLayout && appMode.canNavigate && !isSelecting;
|
||||||
case EntrySetAction.toggleTitleSearch:
|
case EntrySetAction.toggleTitleSearch:
|
||||||
return !isSelecting;
|
return !useTvLayout && !isSelecting;
|
||||||
case EntrySetAction.addShortcut:
|
case EntrySetAction.addShortcut:
|
||||||
return isMain && !isSelecting && device.canPinShortcut && !isTrash;
|
return isMain && !isSelecting && device.canPinShortcut && !isTrash;
|
||||||
case EntrySetAction.emptyBin:
|
case EntrySetAction.emptyBin:
|
||||||
|
@ -83,7 +84,7 @@ class EntrySetActionDelegate with FeedbackMixin, PermissionAwareMixin, SizeAware
|
||||||
case EntrySetAction.stats:
|
case EntrySetAction.stats:
|
||||||
return isMain;
|
return isMain;
|
||||||
case EntrySetAction.rescan:
|
case EntrySetAction.rescan:
|
||||||
return !settings.useTvLayout && isMain && !isTrash;
|
return !useTvLayout && isMain && !isTrash;
|
||||||
// selecting
|
// selecting
|
||||||
case EntrySetAction.share:
|
case EntrySetAction.share:
|
||||||
case EntrySetAction.toggleFavourite:
|
case EntrySetAction.toggleFavourite:
|
||||||
|
|
|
@ -27,7 +27,7 @@ import 'package:aves/widgets/common/action_mixins/size_aware.dart';
|
||||||
import 'package:aves/widgets/common/extensions/build_context.dart';
|
import 'package:aves/widgets/common/extensions/build_context.dart';
|
||||||
import 'package:aves/widgets/dialogs/aves_confirmation_dialog.dart';
|
import 'package:aves/widgets/dialogs/aves_confirmation_dialog.dart';
|
||||||
import 'package:aves/widgets/dialogs/aves_selection_dialog.dart';
|
import 'package:aves/widgets/dialogs/aves_selection_dialog.dart';
|
||||||
import 'package:aves/widgets/filter_grids/album_pick.dart';
|
import 'package:aves/widgets/dialogs/pick_dialogs/album_pick_page.dart';
|
||||||
import 'package:aves/widgets/viewer/notifications.dart';
|
import 'package:aves/widgets/viewer/notifications.dart';
|
||||||
import 'package:collection/collection.dart';
|
import 'package:collection/collection.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
|
|
@ -6,11 +6,13 @@ import 'package:flutter_markdown/flutter_markdown.dart';
|
||||||
class MarkdownContainer extends StatelessWidget {
|
class MarkdownContainer extends StatelessWidget {
|
||||||
final String data;
|
final String data;
|
||||||
final TextDirection? textDirection;
|
final TextDirection? textDirection;
|
||||||
|
final ScrollController? scrollController;
|
||||||
|
|
||||||
const MarkdownContainer({
|
const MarkdownContainer({
|
||||||
super.key,
|
super.key,
|
||||||
required this.data,
|
required this.data,
|
||||||
this.textDirection,
|
this.textDirection,
|
||||||
|
this.scrollController,
|
||||||
});
|
});
|
||||||
|
|
||||||
static const double maxWidth = 460;
|
static const double maxWidth = 460;
|
||||||
|
@ -44,6 +46,7 @@ class MarkdownContainer extends StatelessWidget {
|
||||||
data: data,
|
data: data,
|
||||||
selectable: true,
|
selectable: true,
|
||||||
onTapLink: (text, href, title) => AvesApp.launchUrl(href),
|
onTapLink: (text, href, title) => AvesApp.launchUrl(href),
|
||||||
|
controller: scrollController,
|
||||||
shrinkWrap: true,
|
shrinkWrap: true,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|
15
lib/widgets/common/basic/tv_edge_focus.dart
Normal file
15
lib/widgets/common/basic/tv_edge_focus.dart
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
import 'package:aves/model/settings/settings.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:provider/provider.dart';
|
||||||
|
|
||||||
|
// to be placed at the edges of lists and grids,
|
||||||
|
// so that TV can reach them with D-pad
|
||||||
|
class TvEdgeFocus extends StatelessWidget {
|
||||||
|
const TvEdgeFocus({super.key});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
final useTvLayout = context.select<Settings, bool>((s) => s.useTvLayout);
|
||||||
|
return useTvLayout ? const Focus(child: SizedBox()) : const SizedBox();
|
||||||
|
}
|
||||||
|
}
|
|
@ -34,18 +34,10 @@ class SectionHeader<T> extends StatelessWidget {
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
Widget child = _buildContent(context);
|
Widget child = _buildContent(context);
|
||||||
if (settings.useTvLayout) {
|
if (settings.useTvLayout) {
|
||||||
final primaryColor = Theme.of(context).colorScheme.primary;
|
child = InkWell(
|
||||||
child = Material(
|
onTap: _onTap(context),
|
||||||
type: MaterialType.transparency,
|
borderRadius: const BorderRadius.all(Radius.circular(123)),
|
||||||
child: InkResponse(
|
child: child,
|
||||||
onTap: _onTap(context),
|
|
||||||
containedInkWell: true,
|
|
||||||
highlightShape: BoxShape.rectangle,
|
|
||||||
borderRadius: const BorderRadius.all(Radius.circular(123)),
|
|
||||||
hoverColor: primaryColor.withOpacity(0.04),
|
|
||||||
splashColor: primaryColor.withOpacity(0.12),
|
|
||||||
child: child,
|
|
||||||
),
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
return Container(
|
return Container(
|
||||||
|
|
|
@ -8,7 +8,7 @@ class CaptionedButton extends StatefulWidget {
|
||||||
final Animation<double> scale;
|
final Animation<double> scale;
|
||||||
final Widget captionText;
|
final Widget captionText;
|
||||||
final CaptionedIconButtonBuilder iconButtonBuilder;
|
final CaptionedIconButtonBuilder iconButtonBuilder;
|
||||||
final bool showCaption;
|
final bool autofocus, showCaption;
|
||||||
final VoidCallback? onPressed;
|
final VoidCallback? onPressed;
|
||||||
|
|
||||||
static const EdgeInsets padding = EdgeInsets.symmetric(horizontal: 8);
|
static const EdgeInsets padding = EdgeInsets.symmetric(horizontal: 8);
|
||||||
|
@ -21,6 +21,7 @@ class CaptionedButton extends StatefulWidget {
|
||||||
CaptionedIconButtonBuilder? iconButtonBuilder,
|
CaptionedIconButtonBuilder? iconButtonBuilder,
|
||||||
String? caption,
|
String? caption,
|
||||||
Widget? captionText,
|
Widget? captionText,
|
||||||
|
this.autofocus = false,
|
||||||
this.showCaption = true,
|
this.showCaption = true,
|
||||||
required this.onPressed,
|
required this.onPressed,
|
||||||
}) : assert(icon != null || iconButtonBuilder != null),
|
}) : assert(icon != null || iconButtonBuilder != null),
|
||||||
|
@ -57,6 +58,7 @@ class CaptionedButton extends StatefulWidget {
|
||||||
class _CaptionedButtonState extends State<CaptionedButton> {
|
class _CaptionedButtonState extends State<CaptionedButton> {
|
||||||
final FocusNode _focusNode = FocusNode();
|
final FocusNode _focusNode = FocusNode();
|
||||||
final ValueNotifier<bool> _focusedNotifier = ValueNotifier(false);
|
final ValueNotifier<bool> _focusedNotifier = ValueNotifier(false);
|
||||||
|
bool _didAutofocus = false;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
|
@ -65,12 +67,21 @@ class _CaptionedButtonState extends State<CaptionedButton> {
|
||||||
_focusNode.addListener(_onFocusChanged);
|
_focusNode.addListener(_onFocusChanged);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void didChangeDependencies() {
|
||||||
|
super.didChangeDependencies();
|
||||||
|
_handleAutofocus();
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void didUpdateWidget(covariant CaptionedButton oldWidget) {
|
void didUpdateWidget(covariant CaptionedButton oldWidget) {
|
||||||
super.didUpdateWidget(oldWidget);
|
super.didUpdateWidget(oldWidget);
|
||||||
if (oldWidget.onPressed != widget.onPressed) {
|
if (oldWidget.onPressed != widget.onPressed) {
|
||||||
_updateTraversal();
|
_updateTraversal();
|
||||||
}
|
}
|
||||||
|
if (oldWidget.autofocus != widget.autofocus) {
|
||||||
|
_handleAutofocus();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
@ -120,6 +131,13 @@ class _CaptionedButtonState extends State<CaptionedButton> {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void _handleAutofocus() {
|
||||||
|
if (!_didAutofocus && widget.autofocus) {
|
||||||
|
FocusScope.of(context).autofocus(_focusNode);
|
||||||
|
_didAutofocus = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void _onFocusChanged() => _focusedNotifier.value = _focusNode.hasFocus;
|
void _onFocusChanged() => _focusedNotifier.value = _focusNode.hasFocus;
|
||||||
|
|
||||||
void _updateTraversal() {
|
void _updateTraversal() {
|
||||||
|
|
|
@ -13,6 +13,7 @@ import 'package:aves/theme/icons.dart';
|
||||||
import 'package:aves/widgets/common/basic/menu.dart';
|
import 'package:aves/widgets/common/basic/menu.dart';
|
||||||
import 'package:aves/widgets/common/extensions/build_context.dart';
|
import 'package:aves/widgets/common/extensions/build_context.dart';
|
||||||
import 'package:aves/widgets/common/identity/aves_filter_chip.dart';
|
import 'package:aves/widgets/common/identity/aves_filter_chip.dart';
|
||||||
|
import 'package:aves/widgets/common/identity/buttons/captioned_button.dart';
|
||||||
import 'package:aves/widgets/common/identity/empty.dart';
|
import 'package:aves/widgets/common/identity/empty.dart';
|
||||||
import 'package:aves/widgets/common/providers/selection_provider.dart';
|
import 'package:aves/widgets/common/providers/selection_provider.dart';
|
||||||
import 'package:aves/widgets/dialogs/filter_editors/create_album_dialog.dart';
|
import 'package:aves/widgets/dialogs/filter_editors/create_album_dialog.dart';
|
||||||
|
@ -128,6 +129,53 @@ class _AlbumPickPageState extends State<_AlbumPickPage> {
|
||||||
Selection<FilterGridItem<AlbumFilter>> selection,
|
Selection<FilterGridItem<AlbumFilter>> selection,
|
||||||
AlbumChipSetActionDelegate actionDelegate,
|
AlbumChipSetActionDelegate actionDelegate,
|
||||||
) {
|
) {
|
||||||
|
final itemCount = actionDelegate.allItems.length;
|
||||||
|
final isSelecting = selection.isSelecting;
|
||||||
|
final selectedItems = selection.selectedItems;
|
||||||
|
final selectedFilters = selectedItems.map((v) => v.filter).toSet();
|
||||||
|
|
||||||
|
bool isVisible(ChipSetAction action) => actionDelegate.isVisible(
|
||||||
|
action,
|
||||||
|
appMode: appMode,
|
||||||
|
isSelecting: isSelecting,
|
||||||
|
itemCount: itemCount,
|
||||||
|
selectedFilters: selectedFilters,
|
||||||
|
);
|
||||||
|
|
||||||
|
return settings.useTvLayout
|
||||||
|
? _buildTelevisionActions(
|
||||||
|
context: context,
|
||||||
|
isVisible: isVisible,
|
||||||
|
actionDelegate: actionDelegate,
|
||||||
|
)
|
||||||
|
: _buildMobileActions(
|
||||||
|
context: context,
|
||||||
|
isVisible: isVisible,
|
||||||
|
actionDelegate: actionDelegate,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
List<Widget> _buildTelevisionActions({
|
||||||
|
required BuildContext context,
|
||||||
|
required bool Function(ChipSetAction action) isVisible,
|
||||||
|
required AlbumChipSetActionDelegate actionDelegate,
|
||||||
|
}) {
|
||||||
|
return [
|
||||||
|
...ChipSetActions.general,
|
||||||
|
].where(isVisible).map((action) {
|
||||||
|
return CaptionedButton(
|
||||||
|
icon: action.getIcon(),
|
||||||
|
caption: action.getText(context),
|
||||||
|
onPressed: () => actionDelegate.onActionSelected(context, {}, action),
|
||||||
|
);
|
||||||
|
}).toList();
|
||||||
|
}
|
||||||
|
|
||||||
|
List<Widget> _buildMobileActions({
|
||||||
|
required BuildContext context,
|
||||||
|
required bool Function(ChipSetAction action) isVisible,
|
||||||
|
required AlbumChipSetActionDelegate actionDelegate,
|
||||||
|
}) {
|
||||||
return [
|
return [
|
||||||
if (widget.moveType != null)
|
if (widget.moveType != null)
|
||||||
IconButton(
|
IconButton(
|
||||||
|
@ -149,7 +197,7 @@ class _AlbumPickPageState extends State<_AlbumPickPage> {
|
||||||
child: PopupMenuButton<ChipSetAction>(
|
child: PopupMenuButton<ChipSetAction>(
|
||||||
itemBuilder: (context) {
|
itemBuilder: (context) {
|
||||||
return [
|
return [
|
||||||
FilterGridAppBar.toMenuItem(context, ChipSetAction.configureView, enabled: true),
|
...ChipSetActions.general.where(isVisible).map((action) => FilterGridAppBar.toMenuItem(context, action, enabled: true)),
|
||||||
const PopupMenuDivider(),
|
const PopupMenuDivider(),
|
||||||
FilterGridAppBar.toMenuItem(context, ChipSetAction.toggleTitleSearch, enabled: true),
|
FilterGridAppBar.toMenuItem(context, ChipSetAction.toggleTitleSearch, enabled: true),
|
||||||
];
|
];
|
|
@ -1,4 +1,5 @@
|
||||||
import 'package:aves/image_providers/app_icon_image_provider.dart';
|
import 'package:aves/image_providers/app_icon_image_provider.dart';
|
||||||
|
import 'package:aves/model/settings/settings.dart';
|
||||||
import 'package:aves/services/common/services.dart';
|
import 'package:aves/services/common/services.dart';
|
||||||
import 'package:aves/utils/android_file_utils.dart';
|
import 'package:aves/utils/android_file_utils.dart';
|
||||||
import 'package:aves/widgets/common/basic/query_bar.dart';
|
import 'package:aves/widgets/common/basic/query_bar.dart';
|
||||||
|
@ -37,8 +38,10 @@ class _AppPickPageState extends State<AppPickPage> {
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
|
final useTvLayout = settings.useTvLayout;
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
appBar: AppBar(
|
appBar: AppBar(
|
||||||
|
automaticallyImplyLeading: !useTvLayout,
|
||||||
title: Text(context.l10n.appPickDialogTitle),
|
title: Text(context.l10n.appPickDialogTitle),
|
||||||
),
|
),
|
||||||
body: SafeArea(
|
body: SafeArea(
|
||||||
|
@ -57,7 +60,7 @@ class _AppPickPageState extends State<AppPickPage> {
|
||||||
final packages = allPackages.where((package) => package.categoryLauncher).toList()..sort((a, b) => compareAsciiUpperCase(_displayName(a), _displayName(b)));
|
final packages = allPackages.where((package) => package.categoryLauncher).toList()..sort((a, b) => compareAsciiUpperCase(_displayName(a), _displayName(b)));
|
||||||
return Column(
|
return Column(
|
||||||
children: [
|
children: [
|
||||||
QueryBar(queryNotifier: _queryNotifier),
|
if (!useTvLayout) QueryBar(queryNotifier: _queryNotifier),
|
||||||
ValueListenableBuilder<String>(
|
ValueListenableBuilder<String>(
|
||||||
valueListenable: _queryNotifier,
|
valueListenable: _queryNotifier,
|
||||||
builder: (context, query, child) {
|
builder: (context, query, child) {
|
||||||
|
|
|
@ -70,6 +70,7 @@ abstract class ChipSetActionDelegate<T extends CollectionFilter> with FeedbackMi
|
||||||
final selectedItemCount = selectedFilters.length;
|
final selectedItemCount = selectedFilters.length;
|
||||||
final hasSelection = selectedFilters.isNotEmpty;
|
final hasSelection = selectedFilters.isNotEmpty;
|
||||||
final isMain = appMode == AppMode.main;
|
final isMain = appMode == AppMode.main;
|
||||||
|
final useTvLayout = settings.useTvLayout;
|
||||||
switch (action) {
|
switch (action) {
|
||||||
// general
|
// general
|
||||||
case ChipSetAction.configureView:
|
case ChipSetAction.configureView:
|
||||||
|
@ -82,9 +83,9 @@ abstract class ChipSetActionDelegate<T extends CollectionFilter> with FeedbackMi
|
||||||
return isSelecting && selectedItemCount == itemCount;
|
return isSelecting && selectedItemCount == itemCount;
|
||||||
// browsing
|
// browsing
|
||||||
case ChipSetAction.search:
|
case ChipSetAction.search:
|
||||||
return !settings.useTvLayout && appMode.canNavigate && !isSelecting;
|
return !useTvLayout && appMode.canNavigate && !isSelecting;
|
||||||
case ChipSetAction.toggleTitleSearch:
|
case ChipSetAction.toggleTitleSearch:
|
||||||
return !isSelecting;
|
return !useTvLayout && !isSelecting;
|
||||||
case ChipSetAction.createAlbum:
|
case ChipSetAction.createAlbum:
|
||||||
return false;
|
return false;
|
||||||
// browsing or selecting
|
// browsing or selecting
|
||||||
|
|
|
@ -115,15 +115,23 @@ class FilterGridPage<T extends CollectionFilter> extends StatelessWidget {
|
||||||
);
|
);
|
||||||
|
|
||||||
if (useTvLayout) {
|
if (useTvLayout) {
|
||||||
|
final canNavigate = context.select<ValueNotifier<AppMode>, bool>((v) => v.value.canNavigate);
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
body: Row(
|
body: canNavigate
|
||||||
children: [
|
? Row(
|
||||||
TvRail(
|
children: [
|
||||||
controller: context.read<TvRailController>(),
|
TvRail(
|
||||||
),
|
controller: context.read<TvRailController>(),
|
||||||
Expanded(child: body),
|
),
|
||||||
],
|
Expanded(child: body),
|
||||||
),
|
],
|
||||||
|
)
|
||||||
|
: DirectionalSafeArea(
|
||||||
|
top: false,
|
||||||
|
end: false,
|
||||||
|
bottom: false,
|
||||||
|
child: body,
|
||||||
|
),
|
||||||
resizeToAvoidBottomInset: false,
|
resizeToAvoidBottomInset: false,
|
||||||
extendBody: true,
|
extendBody: true,
|
||||||
);
|
);
|
||||||
|
|
|
@ -277,11 +277,12 @@ class _ContentState extends State<_Content> with SingleTickerProviderStateMixin
|
||||||
MapAction.zoomIn,
|
MapAction.zoomIn,
|
||||||
MapAction.zoomOut,
|
MapAction.zoomOut,
|
||||||
]
|
]
|
||||||
.map((action) => Padding(
|
.mapIndexed((i, action) => Padding(
|
||||||
padding: const EdgeInsets.symmetric(vertical: 8),
|
padding: const EdgeInsets.symmetric(vertical: 8),
|
||||||
child: CaptionedButton(
|
child: CaptionedButton(
|
||||||
icon: action.getIcon(),
|
icon: action.getIcon(),
|
||||||
caption: action.getText(context),
|
caption: action.getText(context),
|
||||||
|
autofocus: i == 0,
|
||||||
onPressed: () => MapActionDelegate(_mapController).onActionSelected(context, action),
|
onPressed: () => MapActionDelegate(_mapController).onActionSelected(context, action),
|
||||||
),
|
),
|
||||||
))
|
))
|
||||||
|
|
|
@ -3,7 +3,7 @@ import 'package:aves/model/source/collection_source.dart';
|
||||||
import 'package:aves/theme/icons.dart';
|
import 'package:aves/theme/icons.dart';
|
||||||
import 'package:aves/widgets/common/extensions/build_context.dart';
|
import 'package:aves/widgets/common/extensions/build_context.dart';
|
||||||
import 'package:aves/widgets/common/identity/buttons/outlined_button.dart';
|
import 'package:aves/widgets/common/identity/buttons/outlined_button.dart';
|
||||||
import 'package:aves/widgets/filter_grids/album_pick.dart';
|
import 'package:aves/widgets/dialogs/pick_dialogs/album_pick_page.dart';
|
||||||
import 'package:aves/widgets/navigation/drawer/tile.dart';
|
import 'package:aves/widgets/navigation/drawer/tile.dart';
|
||||||
import 'package:aves/widgets/settings/navigation/drawer_editor_banner.dart';
|
import 'package:aves/widgets/settings/navigation/drawer_editor_banner.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
|
|
@ -111,45 +111,37 @@ class _MimeDonutState extends State<MimeDonut> with AutomaticKeepAliveClientMixi
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
final primaryColor = Theme.of(context).colorScheme.primary;
|
|
||||||
final legend = SizedBox(
|
final legend = SizedBox(
|
||||||
width: dim,
|
width: dim,
|
||||||
child: Column(
|
child: Column(
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: seriesData
|
children: seriesData
|
||||||
.map((d) => Material(
|
.map((d) => InkWell(
|
||||||
type: MaterialType.transparency,
|
onTap: () => widget.onFilterSelection(MimeFilter(d.mimeType)),
|
||||||
child: InkResponse(
|
borderRadius: const BorderRadius.all(Radius.circular(123)),
|
||||||
onTap: () => widget.onFilterSelection(MimeFilter(d.mimeType)),
|
child: Row(
|
||||||
containedInkWell: true,
|
mainAxisSize: MainAxisSize.min,
|
||||||
highlightShape: BoxShape.rectangle,
|
children: [
|
||||||
borderRadius: const BorderRadius.all(Radius.circular(123)),
|
Icon(AIcons.disc, color: d.color),
|
||||||
hoverColor: primaryColor.withOpacity(0.04),
|
const SizedBox(width: 8),
|
||||||
splashColor: primaryColor.withOpacity(0.12),
|
Flexible(
|
||||||
child: Row(
|
child: Text(
|
||||||
mainAxisSize: MainAxisSize.min,
|
d.displayText,
|
||||||
children: [
|
overflow: TextOverflow.fade,
|
||||||
Icon(AIcons.disc, color: d.color),
|
softWrap: false,
|
||||||
const SizedBox(width: 8),
|
maxLines: 1,
|
||||||
Flexible(
|
|
||||||
child: Text(
|
|
||||||
d.displayText,
|
|
||||||
overflow: TextOverflow.fade,
|
|
||||||
softWrap: false,
|
|
||||||
maxLines: 1,
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
const SizedBox(width: 8),
|
),
|
||||||
Text(
|
const SizedBox(width: 8),
|
||||||
numberFormat.format(d.entryCount),
|
Text(
|
||||||
style: TextStyle(
|
numberFormat.format(d.entryCount),
|
||||||
color: Theme.of(context).textTheme.bodySmall!.color,
|
style: TextStyle(
|
||||||
),
|
color: Theme.of(context).textTheme.bodySmall!.color,
|
||||||
),
|
),
|
||||||
const SizedBox(width: 4),
|
),
|
||||||
],
|
const SizedBox(width: 4),
|
||||||
),
|
],
|
||||||
),
|
),
|
||||||
))
|
))
|
||||||
.toList(),
|
.toList(),
|
||||||
|
|
|
@ -15,6 +15,7 @@ import 'package:aves/theme/icons.dart';
|
||||||
import 'package:aves/utils/constants.dart';
|
import 'package:aves/utils/constants.dart';
|
||||||
import 'package:aves/widgets/collection/collection_page.dart';
|
import 'package:aves/widgets/collection/collection_page.dart';
|
||||||
import 'package:aves/widgets/common/basic/insets.dart';
|
import 'package:aves/widgets/common/basic/insets.dart';
|
||||||
|
import 'package:aves/widgets/common/basic/tv_edge_focus.dart';
|
||||||
import 'package:aves/widgets/common/extensions/build_context.dart';
|
import 'package:aves/widgets/common/extensions/build_context.dart';
|
||||||
import 'package:aves/widgets/common/extensions/media_query.dart';
|
import 'package:aves/widgets/common/extensions/media_query.dart';
|
||||||
import 'package:aves/widgets/common/identity/aves_filter_chip.dart';
|
import 'package:aves/widgets/common/identity/aves_filter_chip.dart';
|
||||||
|
@ -96,6 +97,7 @@ class _StatsPageState extends State<StatsPage> {
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
|
final useTvLayout = settings.useTvLayout;
|
||||||
return ValueListenableBuilder<bool>(
|
return ValueListenableBuilder<bool>(
|
||||||
valueListenable: _isPageAnimatingNotifier,
|
valueListenable: _isPageAnimatingNotifier,
|
||||||
builder: (context, animating, child) {
|
builder: (context, animating, child) {
|
||||||
|
@ -196,6 +198,7 @@ class _StatsPageState extends State<StatsPage> {
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
children: [
|
children: [
|
||||||
|
const TvEdgeFocus(),
|
||||||
mimeDonuts,
|
mimeDonuts,
|
||||||
Histogram(
|
Histogram(
|
||||||
entries: entries,
|
entries: entries,
|
||||||
|
@ -218,7 +221,7 @@ class _StatsPageState extends State<StatsPage> {
|
||||||
|
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
appBar: AppBar(
|
appBar: AppBar(
|
||||||
automaticallyImplyLeading: !settings.useTvLayout,
|
automaticallyImplyLeading: !useTvLayout,
|
||||||
title: Text(l10n.statsPageTitle),
|
title: Text(l10n.statsPageTitle),
|
||||||
),
|
),
|
||||||
body: GestureAreaProtectorStack(
|
body: GestureAreaProtectorStack(
|
||||||
|
@ -274,23 +277,15 @@ class _StatsPageState extends State<StatsPage> {
|
||||||
style: Constants.knownTitleTextStyle,
|
style: Constants.knownTitleTextStyle,
|
||||||
);
|
);
|
||||||
if (settings.useTvLayout) {
|
if (settings.useTvLayout) {
|
||||||
final primaryColor = Theme.of(context).colorScheme.primary;
|
|
||||||
header = Container(
|
header = Container(
|
||||||
padding: const EdgeInsets.symmetric(vertical: 12),
|
padding: const EdgeInsets.symmetric(vertical: 12),
|
||||||
alignment: AlignmentDirectional.centerStart,
|
alignment: AlignmentDirectional.centerStart,
|
||||||
child: Material(
|
child: InkWell(
|
||||||
type: MaterialType.transparency,
|
onTap: onHeaderPressed,
|
||||||
child: InkResponse(
|
borderRadius: const BorderRadius.all(Radius.circular(123)),
|
||||||
onTap: onHeaderPressed,
|
child: Padding(
|
||||||
containedInkWell: true,
|
padding: const EdgeInsets.all(16),
|
||||||
highlightShape: BoxShape.rectangle,
|
child: header,
|
||||||
borderRadius: const BorderRadius.all(Radius.circular(123)),
|
|
||||||
hoverColor: primaryColor.withOpacity(0.04),
|
|
||||||
splashColor: primaryColor.withOpacity(0.12),
|
|
||||||
child: Padding(
|
|
||||||
padding: const EdgeInsets.all(16),
|
|
||||||
child: header,
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
|
@ -31,7 +31,7 @@ import 'package:aves/widgets/dialogs/aves_confirmation_dialog.dart';
|
||||||
import 'package:aves/widgets/dialogs/aves_dialog.dart';
|
import 'package:aves/widgets/dialogs/aves_dialog.dart';
|
||||||
import 'package:aves/widgets/dialogs/entry_editors/rename_entry_dialog.dart';
|
import 'package:aves/widgets/dialogs/entry_editors/rename_entry_dialog.dart';
|
||||||
import 'package:aves/widgets/dialogs/export_entry_dialog.dart';
|
import 'package:aves/widgets/dialogs/export_entry_dialog.dart';
|
||||||
import 'package:aves/widgets/filter_grids/album_pick.dart';
|
import 'package:aves/widgets/dialogs/pick_dialogs/album_pick_page.dart';
|
||||||
import 'package:aves/widgets/viewer/action/entry_info_action_delegate.dart';
|
import 'package:aves/widgets/viewer/action/entry_info_action_delegate.dart';
|
||||||
import 'package:aves/widgets/viewer/action/printer.dart';
|
import 'package:aves/widgets/viewer/action/printer.dart';
|
||||||
import 'package:aves/widgets/viewer/action/single_entry_editor.dart';
|
import 'package:aves/widgets/viewer/action/single_entry_editor.dart';
|
||||||
|
|
Loading…
Reference in a new issue