223 lines
7.3 KiB
Dart
223 lines
7.3 KiB
Dart
import 'package:aves/widgets/about/app_ref.dart';
|
|
import 'package:aves/widgets/about/credits.dart';
|
|
import 'package:aves/widgets/about/translators.dart';
|
|
import 'package:aves/widgets/about/tv_license_page.dart';
|
|
import 'package:aves/widgets/common/basic/insets.dart';
|
|
import 'package:aves/widgets/common/basic/scaffold.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/extensions/build_context.dart';
|
|
import 'package:aves/widgets/common/identity/buttons/outlined_button.dart';
|
|
import 'package:aves/widgets/navigation/tv_rail.dart';
|
|
import 'package:flutter/material.dart';
|
|
import 'package:package_info_plus/package_info_plus.dart';
|
|
import 'package:provider/provider.dart';
|
|
|
|
class AboutTvPage extends StatelessWidget {
|
|
const AboutTvPage({super.key});
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
return AvesScaffold(
|
|
body: AvesPopScope(
|
|
handlers: const [TvNavigationPopHandler.pop],
|
|
child: Row(
|
|
children: [
|
|
TvRail(
|
|
controller: context.read<TvRailController>(),
|
|
),
|
|
Expanded(
|
|
child: Column(
|
|
children: [
|
|
const SizedBox(height: 8),
|
|
DirectionalSafeArea(
|
|
start: false,
|
|
bottom: false,
|
|
child: AppBar(
|
|
automaticallyImplyLeading: false,
|
|
title: Text(context.l10n.aboutPageTitle),
|
|
elevation: 0,
|
|
primary: false,
|
|
),
|
|
),
|
|
Expanded(
|
|
child: MediaQuery.removePadding(
|
|
context: context,
|
|
removeLeft: true,
|
|
removeTop: true,
|
|
removeRight: true,
|
|
removeBottom: true,
|
|
child: const _Content(),
|
|
),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
);
|
|
}
|
|
}
|
|
|
|
class _Content extends StatefulWidget {
|
|
const _Content();
|
|
|
|
@override
|
|
State<_Content> createState() => _ContentState();
|
|
}
|
|
|
|
enum _Section { links, credits, translators, licenses }
|
|
|
|
class _ContentState extends State<_Content> {
|
|
final FocusNode _railFocusNode = FocusNode();
|
|
final ValueNotifier<int> _railIndexNotifier = ValueNotifier(0);
|
|
late Future<PackageInfo> _packageInfoLoader;
|
|
|
|
static const double railWidth = 256;
|
|
|
|
@override
|
|
void initState() {
|
|
super.initState();
|
|
_packageInfoLoader = PackageInfo.fromPlatform();
|
|
}
|
|
|
|
@override
|
|
void dispose() {
|
|
_railIndexNotifier.dispose();
|
|
_railFocusNode.dispose();
|
|
super.dispose();
|
|
}
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
return ValueListenableBuilder<int>(
|
|
valueListenable: _railIndexNotifier,
|
|
builder: (context, selectedIndex, child) {
|
|
final rail = Focus(
|
|
focusNode: _railFocusNode,
|
|
skipTraversal: true,
|
|
canRequestFocus: false,
|
|
child: ConstrainedBox(
|
|
constraints: BoxConstraints.loose(const Size.fromWidth(railWidth)),
|
|
child: Center(
|
|
child: ListView.builder(
|
|
itemBuilder: (context, index) {
|
|
final isSelected = index == selectedIndex;
|
|
final theme = Theme.of(context);
|
|
final colors = theme.colorScheme;
|
|
return ListTile(
|
|
title: DefaultTextStyle(
|
|
style: theme.textTheme.bodyLarge!.copyWith(
|
|
color: isSelected ? colors.primary : colors.onSurface.withOpacity(0.64),
|
|
),
|
|
child: _getTitle(_Section.values[index]),
|
|
),
|
|
selected: isSelected,
|
|
contentPadding: const EdgeInsets.symmetric(vertical: 8, horizontal: 24),
|
|
onTap: () => _railIndexNotifier.value = index,
|
|
shape: const RoundedRectangleBorder(
|
|
borderRadius: BorderRadius.all(Radius.circular(123)),
|
|
),
|
|
// tileColor: theme.scaffoldBackgroundColor,
|
|
);
|
|
},
|
|
itemCount: _Section.values.length,
|
|
),
|
|
),
|
|
),
|
|
);
|
|
|
|
return Row(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
rail,
|
|
Expanded(
|
|
child: MediaQuery.removePadding(
|
|
context: context,
|
|
removeLeft: !context.isRtl,
|
|
removeRight: context.isRtl,
|
|
child: _getBody(_Section.values[selectedIndex]),
|
|
),
|
|
),
|
|
],
|
|
);
|
|
},
|
|
);
|
|
}
|
|
|
|
Widget _getTitle(_Section key) {
|
|
switch (key) {
|
|
case _Section.links:
|
|
return FutureBuilder<PackageInfo>(
|
|
future: _packageInfoLoader,
|
|
builder: (context, snapshot) {
|
|
return Text('${context.l10n.appName} ${snapshot.data?.version}');
|
|
},
|
|
);
|
|
case _Section.credits:
|
|
return Text(context.l10n.aboutCreditsSectionTitle);
|
|
case _Section.translators:
|
|
return Text(context.l10n.aboutTranslatorsSectionTitle);
|
|
case _Section.licenses:
|
|
return Text(context.l10n.aboutLicensesSectionTitle);
|
|
}
|
|
}
|
|
|
|
Widget _getBody(_Section key) {
|
|
switch (key) {
|
|
case _Section.links:
|
|
return Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: AppReference.buildLinks(context)
|
|
.map((v) => Padding(
|
|
padding: const EdgeInsets.all(16),
|
|
child: v,
|
|
))
|
|
.toList(),
|
|
);
|
|
case _Section.credits:
|
|
return Padding(
|
|
padding: const EdgeInsets.all(16),
|
|
child: AboutCredits.buildBody(context),
|
|
);
|
|
case _Section.translators:
|
|
return Padding(
|
|
padding: const EdgeInsets.all(16),
|
|
child: AboutTranslators.buildBody(context),
|
|
);
|
|
case _Section.licenses:
|
|
return Padding(
|
|
padding: const EdgeInsets.all(16),
|
|
child: Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
Text(context.l10n.aboutLicensesBanner),
|
|
const SizedBox(height: 16),
|
|
Center(
|
|
child: AvesOutlinedButton(
|
|
label: context.l10n.aboutLicensesShowAllButtonLabel,
|
|
onPressed: () => Navigator.maybeOf(context)?.push(
|
|
MaterialPageRoute(
|
|
builder: (context) {
|
|
final theme = Theme.of(context);
|
|
final listTileTheme = theme.listTileTheme;
|
|
return Theme(
|
|
data: theme.copyWith(
|
|
listTileTheme: listTileTheme.copyWith(
|
|
tileColor: theme.scaffoldBackgroundColor,
|
|
),
|
|
),
|
|
child: const TvLicensePage(),
|
|
);
|
|
},
|
|
),
|
|
),
|
|
),
|
|
),
|
|
],
|
|
),
|
|
);
|
|
}
|
|
}
|
|
}
|