✨ Netease cloud music login
This commit is contained in:
156
lib/screens/auth/login_netease.dart
Normal file
156
lib/screens/auth/login_netease.dart
Normal file
@ -0,0 +1,156 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:gap/gap.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
import 'package:rhythm_box/providers/auth.dart';
|
||||
import 'package:rhythm_box/providers/error_notifier.dart';
|
||||
import 'package:rhythm_box/services/sourced_track/sources/netease.dart';
|
||||
import 'package:rhythm_box/widgets/sized_container.dart';
|
||||
|
||||
class LoginNeteaseScreen extends StatefulWidget {
|
||||
const LoginNeteaseScreen({super.key});
|
||||
|
||||
@override
|
||||
State<LoginNeteaseScreen> createState() => _LoginNeteaseScreenState();
|
||||
}
|
||||
|
||||
class _LoginNeteaseScreenState extends State<LoginNeteaseScreen> {
|
||||
final TextEditingController _phoneController = TextEditingController();
|
||||
final TextEditingController _phoneRegionController = TextEditingController();
|
||||
final TextEditingController _passwordController = TextEditingController();
|
||||
|
||||
late final AuthenticationProvider _auth = Get.find();
|
||||
|
||||
bool _isLogging = false;
|
||||
|
||||
Future<void> _sentCaptcha() async {
|
||||
setState(() => _isLogging = true);
|
||||
|
||||
final phone = _phoneController.text;
|
||||
var region = _phoneRegionController.text;
|
||||
if (region.isEmpty) region = '86';
|
||||
final client = NeteaseSourcedTrack.getClient();
|
||||
final resp = await client.get(
|
||||
'/captcha/sent?phone=$phone&ctcode=$region×tamp=${DateTime.now().millisecondsSinceEpoch}',
|
||||
);
|
||||
|
||||
if (resp.statusCode != 200 || resp.body?['code'] != 200) {
|
||||
Get.find<ErrorNotifier>().showError(
|
||||
resp.bodyString ?? resp.status.toString(),
|
||||
);
|
||||
}
|
||||
|
||||
setState(() => _isLogging = false);
|
||||
}
|
||||
|
||||
Future<void> _performLogin() async {
|
||||
setState(() => _isLogging = true);
|
||||
|
||||
final phone = _phoneController.text;
|
||||
final password = _passwordController.text;
|
||||
var region = _phoneRegionController.text;
|
||||
if (region.isEmpty) region = '86';
|
||||
final client = NeteaseSourcedTrack.getClient();
|
||||
final resp = await client.get(
|
||||
'/login/cellphone?phone=$phone&captcha=$password&countrycode=$region×tamp=${DateTime.now().millisecondsSinceEpoch}',
|
||||
);
|
||||
|
||||
if (resp.statusCode != 200 || resp.body?['code'] != 200) {
|
||||
Get.find<ErrorNotifier>().showError(
|
||||
resp.bodyString ?? resp.status.toString(),
|
||||
);
|
||||
setState(() => _isLogging = false);
|
||||
return;
|
||||
}
|
||||
|
||||
await _auth.setNeteaseCredentials(resp.body['cookie']);
|
||||
|
||||
setState(() => _isLogging = false);
|
||||
|
||||
GoRouter.of(context).goNamed('settings');
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_phoneController.dispose();
|
||||
_phoneRegionController.dispose();
|
||||
_passwordController.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
title: const Text('Connect with Netease Cloud Music'),
|
||||
),
|
||||
body: CenteredContainer(
|
||||
maxWidth: 320,
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Row(
|
||||
children: [
|
||||
SizedBox(
|
||||
width: 64,
|
||||
child: TextField(
|
||||
controller: _phoneRegionController,
|
||||
decoration: const InputDecoration(
|
||||
border: OutlineInputBorder(),
|
||||
hintText: '86',
|
||||
isDense: true,
|
||||
),
|
||||
onTapOutside: (_) =>
|
||||
FocusManager.instance.primaryFocus?.unfocus(),
|
||||
),
|
||||
),
|
||||
const Gap(8),
|
||||
Expanded(
|
||||
child: TextField(
|
||||
controller: _phoneController,
|
||||
decoration: const InputDecoration(
|
||||
border: OutlineInputBorder(),
|
||||
label: Text('Phone Number'),
|
||||
isDense: true,
|
||||
),
|
||||
onTapOutside: (_) =>
|
||||
FocusManager.instance.primaryFocus?.unfocus(),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
const Gap(8),
|
||||
Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: TextField(
|
||||
controller: _passwordController,
|
||||
obscureText: true,
|
||||
decoration: const InputDecoration(
|
||||
border: OutlineInputBorder(),
|
||||
label: Text('Captcha Code'),
|
||||
isDense: true,
|
||||
),
|
||||
onTapOutside: (_) =>
|
||||
FocusManager.instance.primaryFocus?.unfocus(),
|
||||
),
|
||||
),
|
||||
const Gap(8),
|
||||
IconButton(
|
||||
onPressed: _isLogging ? null : () => _sentCaptcha(),
|
||||
icon: const Icon(Icons.sms),
|
||||
tooltip: 'Get Captcha',
|
||||
),
|
||||
],
|
||||
),
|
||||
const Gap(8),
|
||||
TextButton(
|
||||
onPressed: _isLogging ? null : () => _performLogin(),
|
||||
child: const Text('Login'),
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
@ -72,8 +72,8 @@ class _ExploreScreenState extends State<ExploreScreen> {
|
||||
}
|
||||
|
||||
if (_auth.auth.value != null) {
|
||||
final customEndpoint =
|
||||
CustomSpotifyEndpoints(_auth.auth.value?.accessToken.value ?? '');
|
||||
final customEndpoint = CustomSpotifyEndpoints(
|
||||
_auth.auth.value?.spotifyAccessToken.value ?? '');
|
||||
final forYouView = await customEndpoint.getView(
|
||||
'made-for-x-hub',
|
||||
market: market,
|
||||
|
@ -7,6 +7,7 @@ import 'package:rhythm_box/providers/spotify.dart';
|
||||
import 'package:rhythm_box/providers/user_preferences.dart';
|
||||
import 'package:rhythm_box/screens/auth/login.dart';
|
||||
import 'package:rhythm_box/services/database/database.dart';
|
||||
import 'package:rhythm_box/services/sourced_track/sources/netease.dart';
|
||||
import 'package:rhythm_box/widgets/auto_cache_image.dart';
|
||||
import 'package:rhythm_box/widgets/sized_container.dart';
|
||||
|
||||
@ -86,6 +87,75 @@ class _SettingsScreenState extends State<SettingsScreen> {
|
||||
},
|
||||
);
|
||||
}),
|
||||
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),
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
}),
|
||||
Obx(() {
|
||||
if (_authenticate.auth.value == null) {
|
||||
return const SizedBox();
|
||||
@ -95,7 +165,7 @@ class _SettingsScreenState extends State<SettingsScreen> {
|
||||
contentPadding: const EdgeInsets.symmetric(horizontal: 24),
|
||||
leading: const Icon(Icons.logout),
|
||||
title: const Text('Log out'),
|
||||
subtitle: const Text('Disconnect with this Spotify account'),
|
||||
subtitle: const Text('Disconnect with every account'),
|
||||
trailing: const Icon(Icons.chevron_right),
|
||||
onTap: () async {
|
||||
_authenticate.logout();
|
||||
|
Reference in New Issue
Block a user