2024-09-04 15:28:59 +00:00
|
|
|
import 'package:dropdown_button2/dropdown_button2.dart';
|
2024-08-26 05:29:17 +00:00
|
|
|
import 'package:flutter/material.dart';
|
2024-08-29 07:02:49 +00:00
|
|
|
import 'package:get/get.dart';
|
2024-08-30 06:05:11 +00:00
|
|
|
import 'package:go_router/go_router.dart';
|
2024-08-29 07:02:49 +00:00
|
|
|
import 'package:rhythm_box/providers/auth.dart';
|
|
|
|
import 'package:rhythm_box/providers/spotify.dart';
|
2024-08-29 14:34:56 +00:00
|
|
|
import 'package:rhythm_box/providers/user_preferences.dart';
|
2024-08-29 07:02:49 +00:00
|
|
|
import 'package:rhythm_box/screens/auth/login.dart';
|
2024-09-04 15:28:59 +00:00
|
|
|
import 'package:rhythm_box/services/database/database.dart';
|
2024-09-06 04:41:35 +00:00
|
|
|
import 'package:rhythm_box/services/sourced_track/sources/netease.dart';
|
2024-08-29 07:02:49 +00:00
|
|
|
import 'package:rhythm_box/widgets/auto_cache_image.dart';
|
2024-08-29 14:34:56 +00:00
|
|
|
import 'package:rhythm_box/widgets/sized_container.dart';
|
2024-08-26 05:29:17 +00:00
|
|
|
|
|
|
|
class SettingsScreen extends StatefulWidget {
|
|
|
|
const SettingsScreen({super.key});
|
|
|
|
|
|
|
|
@override
|
|
|
|
State<SettingsScreen> createState() => _SettingsScreenState();
|
|
|
|
}
|
|
|
|
|
|
|
|
class _SettingsScreenState extends State<SettingsScreen> {
|
2024-08-29 07:02:49 +00:00
|
|
|
late final SpotifyProvider _spotify = Get.find();
|
|
|
|
late final AuthenticationProvider _authenticate = Get.find();
|
2024-08-29 14:34:56 +00:00
|
|
|
late final UserPreferencesProvider _preferences = Get.find();
|
2024-08-29 07:02:49 +00:00
|
|
|
|
|
|
|
bool _isLoggingIn = false;
|
|
|
|
|
2024-08-26 05:29:17 +00:00
|
|
|
@override
|
|
|
|
Widget build(BuildContext context) {
|
2024-08-28 17:45:33 +00:00
|
|
|
return Material(
|
|
|
|
color: Theme.of(context).colorScheme.surface,
|
2024-08-29 14:34:56 +00:00
|
|
|
child: Scaffold(
|
|
|
|
appBar: AppBar(
|
|
|
|
title: const Text('Settings'),
|
|
|
|
centerTitle: MediaQuery.of(context).size.width >= 720,
|
|
|
|
),
|
|
|
|
body: CenteredContainer(
|
2024-09-04 16:04:09 +00:00
|
|
|
child: ListView(
|
2024-08-29 14:34:56 +00:00
|
|
|
children: [
|
|
|
|
Obx(() {
|
|
|
|
if (_authenticate.auth.value == null) {
|
|
|
|
return ListTile(
|
|
|
|
contentPadding: const EdgeInsets.symmetric(horizontal: 24),
|
|
|
|
leading: const Icon(Icons.login),
|
|
|
|
title: const Text('Connect with Spotify'),
|
|
|
|
subtitle:
|
|
|
|
const Text('To explore your own library and more'),
|
|
|
|
trailing: const Icon(Icons.chevron_right),
|
|
|
|
enabled: !_isLoggingIn,
|
|
|
|
onTap: () async {
|
|
|
|
setState(() => _isLoggingIn = true);
|
|
|
|
await universalLogin(context);
|
|
|
|
setState(() => _isLoggingIn = false);
|
|
|
|
},
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
return FutureBuilder(
|
|
|
|
future: _spotify.api.me.get(),
|
|
|
|
builder: (context, snapshot) {
|
|
|
|
if (!snapshot.hasData) {
|
|
|
|
return const ListTile(
|
|
|
|
contentPadding:
|
|
|
|
const EdgeInsets.symmetric(horizontal: 24),
|
|
|
|
leading: SizedBox(
|
|
|
|
width: 20,
|
|
|
|
height: 20,
|
|
|
|
child: CircularProgressIndicator(
|
|
|
|
strokeWidth: 3,
|
|
|
|
),
|
|
|
|
),
|
|
|
|
title: Text('Loading...'),
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
return ListTile(
|
|
|
|
leading: (snapshot.data!.images?.isNotEmpty ?? false)
|
|
|
|
? CircleAvatar(
|
|
|
|
backgroundImage: AutoCacheImage.provider(
|
|
|
|
snapshot.data!.images!.firstOrNull!.url!,
|
|
|
|
),
|
|
|
|
)
|
|
|
|
: const Icon(Icons.account_circle),
|
|
|
|
title: Text(snapshot.data!.displayName!),
|
|
|
|
subtitle: const Text('Connected with your Spotify'),
|
|
|
|
);
|
|
|
|
},
|
|
|
|
);
|
|
|
|
}),
|
2024-09-06 04:41:35 +00:00
|
|
|
Obx(() {
|
|
|
|
if (_authenticate.auth.value == null) {
|
|
|
|
return const SizedBox();
|
|
|
|
}
|
|
|
|
if (_authenticate.auth.value?.neteaseCookie == null) {
|
|
|
|
return ListTile(
|
|
|
|
contentPadding: const EdgeInsets.symmetric(horizontal: 24),
|
|
|
|
leading: const Icon(Icons.cloud_outlined),
|
|
|
|
title: const Text('Connect with Netease Cloud Music'),
|
|
|
|
subtitle: const Text(
|
|
|
|
'Make us able to play more music from Netease Cloud Music'),
|
|
|
|
trailing: const Icon(Icons.chevron_right),
|
|
|
|
enabled: !_isLoggingIn,
|
|
|
|
onTap: () async {
|
|
|
|
setState(() => _isLoggingIn = true);
|
|
|
|
await GoRouter.of(context)
|
|
|
|
.pushNamed('authMobileLoginNetease');
|
|
|
|
setState(() => _isLoggingIn = false);
|
|
|
|
},
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
return FutureBuilder(
|
|
|
|
future: NeteaseSourcedTrack.getClient().get('/user/account'),
|
|
|
|
builder: (context, snapshot) {
|
|
|
|
if (!snapshot.hasData) {
|
|
|
|
return ListTile(
|
|
|
|
contentPadding:
|
|
|
|
const EdgeInsets.symmetric(horizontal: 24),
|
|
|
|
leading: const SizedBox(
|
|
|
|
width: 20,
|
|
|
|
height: 20,
|
|
|
|
child: CircularProgressIndicator(
|
|
|
|
strokeWidth: 3,
|
|
|
|
),
|
|
|
|
),
|
|
|
|
title: const Text('Loading...'),
|
|
|
|
trailing: IconButton(
|
|
|
|
onPressed: () {
|
|
|
|
_authenticate.logoutNetease();
|
|
|
|
},
|
|
|
|
icon: const Icon(Icons.link_off),
|
|
|
|
),
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
return ListTile(
|
|
|
|
leading:
|
|
|
|
snapshot.data!.body['profile']['avatarUrl'] != null
|
|
|
|
? CircleAvatar(
|
|
|
|
backgroundImage: AutoCacheImage.provider(
|
|
|
|
snapshot.data!.body['profile']['avatarUrl'],
|
|
|
|
),
|
|
|
|
)
|
|
|
|
: const Icon(Icons.account_circle),
|
|
|
|
title: Text(snapshot.data!.body['profile']['nickname']),
|
|
|
|
subtitle: const Text(
|
|
|
|
'Connected with your Netease Cloud Music account',
|
|
|
|
),
|
|
|
|
trailing: IconButton(
|
|
|
|
onPressed: () {
|
|
|
|
_authenticate.logoutNetease();
|
|
|
|
},
|
|
|
|
icon: const Icon(Icons.link_off),
|
|
|
|
),
|
|
|
|
);
|
|
|
|
},
|
|
|
|
);
|
|
|
|
}),
|
2024-08-29 14:34:56 +00:00
|
|
|
Obx(() {
|
|
|
|
if (_authenticate.auth.value == null) {
|
|
|
|
return const SizedBox();
|
|
|
|
}
|
|
|
|
|
2024-08-29 07:02:49 +00:00
|
|
|
return ListTile(
|
|
|
|
contentPadding: const EdgeInsets.symmetric(horizontal: 24),
|
2024-08-29 14:34:56 +00:00
|
|
|
leading: const Icon(Icons.logout),
|
|
|
|
title: const Text('Log out'),
|
2024-09-06 04:41:35 +00:00
|
|
|
subtitle: const Text('Disconnect with every account'),
|
2024-08-29 07:02:49 +00:00
|
|
|
trailing: const Icon(Icons.chevron_right),
|
|
|
|
onTap: () async {
|
2024-08-29 14:34:56 +00:00
|
|
|
_authenticate.logout();
|
2024-08-29 07:02:49 +00:00
|
|
|
},
|
|
|
|
);
|
2024-08-29 14:34:56 +00:00
|
|
|
}),
|
|
|
|
const Divider(thickness: 0.3, height: 1),
|
2024-09-04 15:28:59 +00:00
|
|
|
Obx(
|
|
|
|
() => ListTile(
|
|
|
|
contentPadding: const EdgeInsets.symmetric(horizontal: 24),
|
|
|
|
leading: const Icon(Icons.audio_file),
|
|
|
|
title: const Text('Audio Source'),
|
|
|
|
subtitle:
|
|
|
|
const Text('Choose who to provide the songs you played.'),
|
|
|
|
trailing: DropdownButtonHideUnderline(
|
|
|
|
child: DropdownButton2<AudioSource>(
|
|
|
|
isExpanded: true,
|
|
|
|
hint: Text(
|
|
|
|
'Select Item',
|
|
|
|
style: TextStyle(
|
|
|
|
fontSize: 14,
|
|
|
|
color: Theme.of(context).hintColor,
|
|
|
|
),
|
|
|
|
),
|
|
|
|
items: AudioSource.values
|
|
|
|
.map((AudioSource item) =>
|
|
|
|
DropdownMenuItem<AudioSource>(
|
|
|
|
value: item,
|
|
|
|
child: Text(
|
|
|
|
item.label,
|
|
|
|
style: const TextStyle(
|
|
|
|
fontSize: 14,
|
|
|
|
),
|
|
|
|
),
|
|
|
|
))
|
|
|
|
.toList(),
|
|
|
|
value: _preferences.state.value.audioSource,
|
|
|
|
onChanged: (AudioSource? value) {
|
|
|
|
_preferences
|
|
|
|
.setAudioSource(value ?? AudioSource.youtube);
|
|
|
|
},
|
|
|
|
buttonStyleData: const ButtonStyleData(
|
|
|
|
padding: EdgeInsets.symmetric(horizontal: 16),
|
|
|
|
height: 40,
|
|
|
|
width: 140,
|
|
|
|
),
|
|
|
|
menuItemStyleData: const MenuItemStyleData(
|
|
|
|
height: 40,
|
|
|
|
),
|
|
|
|
),
|
|
|
|
),
|
|
|
|
),
|
|
|
|
),
|
|
|
|
const Divider(thickness: 0.3, height: 1),
|
2024-09-07 10:49:05 +00:00
|
|
|
Obx(
|
|
|
|
() => CheckboxListTile(
|
|
|
|
contentPadding: const EdgeInsets.symmetric(horizontal: 24),
|
2024-09-07 11:57:26 +00:00
|
|
|
secondary: const Icon(Icons.update),
|
2024-09-07 10:49:05 +00:00
|
|
|
title: const Text('Override Cache Provider'),
|
|
|
|
subtitle: const Text(
|
|
|
|
'Decide whether use original cached source or query a new one from current audio provider'),
|
2024-09-07 11:57:26 +00:00
|
|
|
value: _preferences.state.value.overrideCacheProvider,
|
2024-09-07 10:49:05 +00:00
|
|
|
onChanged: (value) =>
|
|
|
|
_preferences.setOverrideCacheProvider(value ?? false),
|
|
|
|
),
|
|
|
|
),
|
2024-09-07 11:57:26 +00:00
|
|
|
Obx(
|
|
|
|
() => Column(
|
|
|
|
children: [
|
|
|
|
const ListTile(
|
|
|
|
contentPadding: EdgeInsets.symmetric(horizontal: 24),
|
|
|
|
leading: Icon(Icons.cloud),
|
|
|
|
title: Text('Netease Cloud Music API'),
|
|
|
|
subtitle: Text(
|
|
|
|
'Use your own endpoint to prevent IP throttling and more'),
|
|
|
|
),
|
|
|
|
TextFormField(
|
|
|
|
initialValue: _preferences.state.value.neteaseApiInstance,
|
|
|
|
decoration: const InputDecoration(
|
|
|
|
hintText: 'Endpoint URL',
|
|
|
|
isDense: true,
|
|
|
|
),
|
|
|
|
onChanged: (value) {
|
|
|
|
_preferences.setNeteaseApiInstance(value);
|
|
|
|
},
|
|
|
|
onTapOutside: (_) =>
|
|
|
|
FocusManager.instance.primaryFocus?.unfocus(),
|
|
|
|
).paddingOnly(left: 24, right: 24, bottom: 12),
|
|
|
|
],
|
|
|
|
),
|
|
|
|
),
|
2024-09-07 10:49:05 +00:00
|
|
|
const Divider(thickness: 0.3, height: 1),
|
2024-08-29 15:03:41 +00:00
|
|
|
Obx(
|
|
|
|
() => SwitchListTile(
|
|
|
|
contentPadding: const EdgeInsets.symmetric(horizontal: 24),
|
|
|
|
secondary: const Icon(Icons.all_inclusive),
|
|
|
|
title: const Text('Endless Playback'),
|
|
|
|
subtitle: const Text(
|
|
|
|
'Automatically get more recommendation for you after your queue finish playing'),
|
|
|
|
value: _preferences.state.value.endlessPlayback,
|
|
|
|
onChanged: _preferences.setEndlessPlayback,
|
|
|
|
),
|
|
|
|
),
|
2024-08-29 14:34:56 +00:00
|
|
|
Obx(
|
|
|
|
() => SwitchListTile(
|
|
|
|
contentPadding: const EdgeInsets.symmetric(horizontal: 24),
|
|
|
|
secondary: const Icon(Icons.graphic_eq),
|
|
|
|
title: const Text('Normalize Audio'),
|
|
|
|
subtitle:
|
|
|
|
const Text('Make audio not too loud either too quiet'),
|
|
|
|
value: _preferences.state.value.normalizeAudio,
|
|
|
|
onChanged: _preferences.setNormalizeAudio,
|
|
|
|
),
|
|
|
|
),
|
2024-09-02 12:25:19 +00:00
|
|
|
Obx(
|
|
|
|
() => SwitchListTile(
|
|
|
|
contentPadding: const EdgeInsets.symmetric(horizontal: 24),
|
|
|
|
secondary: const Icon(Icons.screen_lock_portrait),
|
|
|
|
title: const Text('Player Wakelock'),
|
|
|
|
subtitle: const Text(
|
|
|
|
'Keep your screen doesn\'t lock in player screen'),
|
|
|
|
value: _preferences.state.value.playerWakelock,
|
|
|
|
onChanged: _preferences.setPlayerWakelock,
|
|
|
|
),
|
|
|
|
),
|
2024-08-30 06:05:11 +00:00
|
|
|
const Divider(thickness: 0.3, height: 1),
|
|
|
|
ListTile(
|
|
|
|
contentPadding: const EdgeInsets.symmetric(horizontal: 24),
|
|
|
|
leading: const Icon(Icons.info),
|
|
|
|
title: const Text('About'),
|
|
|
|
subtitle: const Text('More information about this app'),
|
|
|
|
trailing: const Icon(Icons.chevron_right),
|
|
|
|
onTap: () async {
|
|
|
|
GoRouter.of(context).pushNamed('about');
|
|
|
|
},
|
|
|
|
),
|
2024-08-29 14:34:56 +00:00
|
|
|
],
|
|
|
|
),
|
2024-08-28 17:45:33 +00:00
|
|
|
),
|
|
|
|
),
|
|
|
|
);
|
2024-08-26 05:29:17 +00:00
|
|
|
}
|
|
|
|
}
|