mirror of
https://github.com/austinried/subtracks.git
synced 2026-11-11 15:29:15 +01:00
v2
This commit is contained in:
@@ -0,0 +1,395 @@
|
||||
import 'package:auto_route/auto_route.dart';
|
||||
import 'package:fast_immutable_collections/fast_immutable_collections.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
|
||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
import 'package:url_launcher/url_launcher.dart';
|
||||
|
||||
import '../../models/support.dart';
|
||||
import '../../services/settings_service.dart';
|
||||
import '../../state/init.dart';
|
||||
import '../../state/settings.dart';
|
||||
import '../app_router.dart';
|
||||
import '../dialogs.dart';
|
||||
|
||||
const kHorizontalPadding = 16.0;
|
||||
|
||||
class SettingsPage extends HookConsumerWidget {
|
||||
const SettingsPage({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
final l = AppLocalizations.of(context);
|
||||
// final downloads = ref.watch(downloadServiceProvider.select(
|
||||
// (value) => value.downloads,
|
||||
// ));
|
||||
|
||||
return Scaffold(
|
||||
body: ListView(
|
||||
children: [
|
||||
const SizedBox(height: 96),
|
||||
_SectionHeader(l.settingsServersName),
|
||||
const _Sources(),
|
||||
_SectionHeader(l.settingsNetworkName),
|
||||
const _Network(),
|
||||
_SectionHeader(l.settingsAboutName),
|
||||
_About(),
|
||||
// const _SectionHeader('Downloads'),
|
||||
// _Section(
|
||||
// children: downloads
|
||||
// .map(
|
||||
// (e) => ListTile(
|
||||
// isThreeLine: true,
|
||||
// title: Text(e.filename ?? e.url),
|
||||
// subtitle: Column(
|
||||
// mainAxisAlignment: MainAxisAlignment.start,
|
||||
// children: [
|
||||
// Row(children: [Text('Progress: ${e.progress}%')]),
|
||||
// Row(children: [Text('Status: ${e.status})')]),
|
||||
// Text('Status: ${e.savedDir}'),
|
||||
// ],
|
||||
// ),
|
||||
// trailing:
|
||||
// CircularProgressIndicator(value: e.progress / 100),
|
||||
// ),
|
||||
// )
|
||||
// .toList(),
|
||||
// ),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _Section extends StatelessWidget {
|
||||
final List<Widget> children;
|
||||
|
||||
const _Section({required this.children});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: [
|
||||
...children,
|
||||
const SizedBox(height: 32),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _SectionHeader extends StatelessWidget {
|
||||
final String title;
|
||||
|
||||
const _SectionHeader(this.title);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final theme = Theme.of(context);
|
||||
|
||||
return Column(
|
||||
children: [
|
||||
SizedBox(
|
||||
width: double.infinity,
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: kHorizontalPadding),
|
||||
child: Text(
|
||||
title,
|
||||
style: theme.textTheme.displaySmall,
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _Network extends StatelessWidget {
|
||||
const _Network();
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return const _Section(
|
||||
children: [
|
||||
_OfflineMode(),
|
||||
_MaxBitrateWifi(),
|
||||
_MaxBitrateMobile(),
|
||||
_StreamFormat(),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _About extends HookConsumerWidget {
|
||||
_About();
|
||||
|
||||
final _homepage = Uri.parse('https://github.com/austinried/subtracks');
|
||||
final _donate = Uri.parse('https://ko-fi.com/austinried');
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
final l = AppLocalizations.of(context);
|
||||
final pkg = ref.watch(packageInfoProvider).requireValue;
|
||||
|
||||
return _Section(
|
||||
children: [
|
||||
ListTile(
|
||||
title: const Text('subtracks'),
|
||||
subtitle: Text(l.settingsAboutVersion(pkg.version)),
|
||||
),
|
||||
ListTile(
|
||||
title: Text(l.settingsAboutActionsLicenses),
|
||||
// trailing: const Icon(Icons.open_in_new_rounded),
|
||||
onTap: () {},
|
||||
),
|
||||
ListTile(
|
||||
title: Text(l.settingsAboutActionsProjectHomepage),
|
||||
subtitle: Text(_homepage.toString()),
|
||||
trailing: const Icon(Icons.open_in_new_rounded),
|
||||
onTap: () => launchUrl(
|
||||
_homepage,
|
||||
mode: LaunchMode.externalApplication,
|
||||
),
|
||||
),
|
||||
ListTile(
|
||||
title: Text(l.settingsAboutActionsSupport),
|
||||
subtitle: Text(_donate.toString()),
|
||||
trailing: const Icon(Icons.open_in_new_rounded),
|
||||
onTap: () => launchUrl(
|
||||
_donate,
|
||||
mode: LaunchMode.externalApplication,
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _MaxBitrateWifi extends HookConsumerWidget {
|
||||
const _MaxBitrateWifi();
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
final bitrate = ref.watch(settingsServiceProvider.select(
|
||||
(value) => value.app.maxBitrateWifi,
|
||||
));
|
||||
final l = AppLocalizations.of(context);
|
||||
|
||||
return _MaxBitrateOption(
|
||||
title: l.settingsNetworkOptionsMaxBitrateWifiTitle,
|
||||
bitrate: bitrate,
|
||||
onChange: (value) {
|
||||
ref.read(settingsServiceProvider.notifier).setMaxBitrateWifi(value);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _MaxBitrateMobile extends HookConsumerWidget {
|
||||
const _MaxBitrateMobile();
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
final bitrate = ref.watch(settingsServiceProvider.select(
|
||||
(value) => value.app.maxBitrateMobile,
|
||||
));
|
||||
final l = AppLocalizations.of(context);
|
||||
|
||||
return _MaxBitrateOption(
|
||||
title: l.settingsNetworkOptionsMaxBitrateMobileTitle,
|
||||
bitrate: bitrate,
|
||||
onChange: (value) {
|
||||
ref.read(settingsServiceProvider.notifier).setMaxBitrateMobile(value);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _MaxBitrateOption extends HookConsumerWidget {
|
||||
final String title;
|
||||
final int bitrate;
|
||||
final void Function(int value) onChange;
|
||||
|
||||
const _MaxBitrateOption({
|
||||
required this.title,
|
||||
required this.bitrate,
|
||||
required this.onChange,
|
||||
});
|
||||
|
||||
static const options = [0, 24, 32, 64, 96, 128, 192, 256, 320];
|
||||
|
||||
String _bitrateText(AppLocalizations l, int bitrate) {
|
||||
return bitrate == 0
|
||||
? l.settingsNetworkValuesUnlimitedKbps
|
||||
: l.settingsNetworkValuesKbps(bitrate.toString());
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
final l = AppLocalizations.of(context);
|
||||
|
||||
return ListTile(
|
||||
title: Text(title),
|
||||
subtitle: Text(_bitrateText(l, bitrate)),
|
||||
onTap: () async {
|
||||
final value = await showDialog<int>(
|
||||
context: context,
|
||||
builder: (context) => MultipleChoiceDialog<int>(
|
||||
title: title,
|
||||
current: bitrate,
|
||||
options: options
|
||||
.map((opt) => MultiChoiceOption.int(
|
||||
title: _bitrateText(l, opt),
|
||||
option: opt,
|
||||
))
|
||||
.toIList(),
|
||||
),
|
||||
);
|
||||
|
||||
if (value != null) {
|
||||
onChange(value);
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _StreamFormat extends HookConsumerWidget {
|
||||
const _StreamFormat();
|
||||
|
||||
static const options = ['', 'mp3', 'opus', 'ogg'];
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
final streamFormat = ref.watch(
|
||||
settingsServiceProvider.select((value) => value.app.streamFormat),
|
||||
);
|
||||
final l = AppLocalizations.of(context);
|
||||
|
||||
return ListTile(
|
||||
title: Text(l.settingsNetworkOptionsStreamFormat),
|
||||
subtitle: Text(
|
||||
streamFormat ?? l.settingsNetworkOptionsStreamFormatServerDefault,
|
||||
),
|
||||
onTap: () async {
|
||||
final value = await showDialog<String>(
|
||||
context: context,
|
||||
builder: (context) => MultipleChoiceDialog<String>(
|
||||
title: l.settingsNetworkOptionsStreamFormat,
|
||||
current: streamFormat ?? '',
|
||||
options: options
|
||||
.map((opt) => MultiChoiceOption.string(
|
||||
title: opt == ''
|
||||
? l.settingsNetworkOptionsStreamFormatServerDefault
|
||||
: opt,
|
||||
option: opt,
|
||||
))
|
||||
.toIList(),
|
||||
),
|
||||
);
|
||||
|
||||
if (value != null) {
|
||||
ref
|
||||
.read(settingsServiceProvider.notifier)
|
||||
.setStreamFormat(value == '' ? null : value);
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _OfflineMode extends HookConsumerWidget {
|
||||
const _OfflineMode();
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
final offline = ref.watch(offlineModeProvider);
|
||||
final l = AppLocalizations.of(context);
|
||||
|
||||
return SwitchListTile(
|
||||
value: offline,
|
||||
title: Text(l.settingsNetworkOptionsOfflineMode),
|
||||
subtitle: offline
|
||||
? Text(l.settingsNetworkOptionsOfflineModeOn)
|
||||
: Text(l.settingsNetworkOptionsOfflineModeOff),
|
||||
onChanged: (value) {
|
||||
ref.read(offlineModeProvider.notifier).setMode(value);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _Sources extends HookConsumerWidget {
|
||||
const _Sources();
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
final sources = ref.watch(settingsServiceProvider.select(
|
||||
(value) => value.sources,
|
||||
));
|
||||
final activeSource = ref.watch(settingsServiceProvider.select(
|
||||
(value) => value.activeSource,
|
||||
));
|
||||
|
||||
final l = AppLocalizations.of(context);
|
||||
|
||||
return _Section(
|
||||
children: [
|
||||
for (var source in sources)
|
||||
RadioListTile<int>(
|
||||
value: source.id,
|
||||
groupValue: activeSource?.id,
|
||||
onChanged: (value) {
|
||||
ref
|
||||
.read(settingsServiceProvider.notifier)
|
||||
.setActiveSource(source.id);
|
||||
},
|
||||
title: Text(source.name),
|
||||
subtitle: Text(
|
||||
source.address.toString(),
|
||||
maxLines: 1,
|
||||
softWrap: false,
|
||||
overflow: TextOverflow.fade,
|
||||
),
|
||||
secondary: IconButton(
|
||||
icon: const Icon(Icons.edit_rounded),
|
||||
onPressed: () {
|
||||
context.pushRoute(SourceRoute(id: source.id));
|
||||
},
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
OutlinedButton.icon(
|
||||
icon: const Icon(Icons.add_rounded),
|
||||
label: Text(l.settingsServersActionsAdd),
|
||||
onPressed: () {
|
||||
context.pushRoute(SourceRoute());
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
// TODO: remove
|
||||
if (kDebugMode)
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
OutlinedButton.icon(
|
||||
icon: const Icon(Icons.add_rounded),
|
||||
label: const Text('Add TEST'),
|
||||
onPressed: () {
|
||||
ref
|
||||
.read(settingsServiceProvider.notifier)
|
||||
.addTestSource('TEST');
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user