refactor: accounts login

This commit is contained in:
Rongjian Zhang 2019-09-26 22:14:14 +08:00
parent 0d5fef8dd7
commit 2de9884756
8 changed files with 112 additions and 177 deletions

View File

@ -107,7 +107,7 @@ class _HomeState extends State<Home> {
return SearchScreen();
case 4:
return UserScreen(
Provider.of<SettingsModel>(context).activeLogin,
Provider.of<SettingsModel>(context).activeAccount.login,
isMe: true,
);
}
@ -131,7 +131,7 @@ class _HomeState extends State<Home> {
}
// print(settings.activeLogin);
if (settings.activeLogin == null) {
if (settings.activeAccount == null) {
return MaterialApp(theme: themData, home: LoginScreen());
}

View File

@ -5,22 +5,21 @@ part 'account.g.dart';
@JsonSerializable()
class AccountModel {
String avatarUrl;
String token;
@JsonKey(ignore: true)
String platform;
@JsonKey(ignore: true)
String domain;
@JsonKey(ignore: true)
String token;
String login;
String avatarUrl;
equals(AccountModel a) =>
a.platform == platform && a.domain == domain && a.login == login;
AccountModel({
@required this.avatarUrl,
@required this.platform,
@required this.domain,
@required this.token,
this.platform,
this.domain,
this.login,
@required this.login,
@required this.avatarUrl,
});
factory AccountModel.fromJson(Map<String, dynamic> json) =>

View File

@ -8,8 +8,19 @@ part of 'account.dart';
AccountModel _$AccountModelFromJson(Map<String, dynamic> json) {
return AccountModel(
avatarUrl: json['avatarUrl'] as String, token: json['token'] as String);
platform: json['platform'] as String,
domain: json['domain'] as String,
token: json['token'] as String,
login: json['login'] as String,
avatarUrl: json['avatarUrl'] as String,
);
}
Map<String, dynamic> _$AccountModelToJson(AccountModel instance) =>
<String, dynamic>{'avatarUrl': instance.avatarUrl, 'token': instance.token};
<String, dynamic>{
'platform': instance.platform,
'domain': instance.domain,
'token': instance.token,
'login': instance.login,
'avatarUrl': instance.avatarUrl,
};

View File

@ -38,33 +38,22 @@ class PlatformType {
// }
class SettingsModel with ChangeNotifier {
bool ready = false;
Map<String, AccountModel> githubAccountMap;
Map<String, Map<String, Map<String, AccountModel>>> accountMap;
String activePlatform;
String activeDomain;
String activeLogin;
static const _apiPrefix = 'https://api.github.com';
List<AccountModel> _accounts;
int activeAccountIndex;
StreamSubscription<Uri> _sub;
bool loading = false;
// PlatformType platformType;
String _apiPrefix = 'https://api.github.com';
String get token {
if (activeLogin == null) return null;
switch (activePlatform) {
case PlatformType.github:
return githubAccountMap[activeLogin].token;
default:
return accountMap[activePlatform][activeDomain][activeLogin].token;
}
List<AccountModel> get accounts => _accounts;
bool get ready => _accounts != null;
AccountModel get activeAccount {
if (activeAccountIndex == null || _accounts == null) return null;
return _accounts[activeAccountIndex];
}
String get token => activeAccount.token;
// https://developer.github.com/apps/building-oauth-apps/authorizing-oauth-apps/#web-application-flow
Future<void> _onSchemeDetected(Uri uri) async {
await closeWebView();
@ -72,10 +61,8 @@ class SettingsModel with ChangeNotifier {
loading = true;
notifyListeners();
// get token by code
var code = uri.queryParameters['code'];
// print(code);
var res = await http.post(
// Get token by code
final res = await http.post(
'https://github.com/login/oauth/access_token',
headers: {
HttpHeaders.acceptHeader: 'application/json',
@ -84,18 +71,17 @@ class SettingsModel with ChangeNotifier {
body: json.encode({
'client_id': clientId,
'client_secret': clientSecret,
'code': code,
'code': uri.queryParameters['code'],
'state': _oauthState,
}),
);
// print(res.body);
var data = json.decode(res.body);
_loginWithToken(data['access_token']);
final token = json.decode(res.body)['access_token'] as String;
await _loginWithToken(token);
}
Future<void> _loginWithToken(String token) async {
// get login and avatar url
var queryData = await query('''
// Get login and avatar url
final queryData = await query('''
{
viewer {
login
@ -103,56 +89,53 @@ class SettingsModel with ChangeNotifier {
}
}
''', token);
String login = queryData['viewer']['login'];
String avatarUrl = queryData['viewer']['avatarUrl'];
githubAccountMap[login] = AccountModel(avatarUrl: avatarUrl, token: token);
final account = AccountModel(
platform: PlatformType.github,
domain: 'github.com',
token: token,
login: queryData['viewer']['login'] as String,
avatarUrl: queryData['viewer']['avatarUrl'] as String,
);
// write
SharedPreferences prefs = await SharedPreferences.getInstance();
await prefs.setString(
StorageKeys.github,
json.encode(githubAccountMap
.map((login, account) => MapEntry(login, account.toJson()))));
// TODO: duplicated
// if (_accounts.where(account.equals).isNotEmpty) {}
_accounts.add(account);
// Write to perfs
final prefs = await SharedPreferences.getInstance();
await prefs.setString(StorageKeys.accounts, json.encode(_accounts));
loading = false;
notifyListeners();
}
Future<void> loginToGitlab(String domain, String token) async {
loading = true;
notifyListeners();
try {
var res = await http
loading = true;
notifyListeners();
final res = await http
.get('$domain/api/v4/user', headers: {'Private-Token': token});
var info = json.decode(res.body);
final info = json.decode(res.body);
if (info['message'] != null) {
throw info['message'];
}
String login = info['username'];
String avatarUrl = info['avatar_url'];
final account = AccountModel(
platform: PlatformType.gitlab,
domain: domain,
token: token,
login: info['username'] as String,
avatarUrl: info['avatar_url'] as String,
);
if (accountMap[PlatformType.gitlab] == null)
accountMap[PlatformType.gitlab] = {};
if (accountMap[PlatformType.gitlab][domain] == null)
accountMap[PlatformType.gitlab][domain] = {};
_accounts.add(account);
accountMap[PlatformType.gitlab][domain][login] =
AccountModel(token: token, avatarUrl: avatarUrl);
SharedPreferences prefs = await SharedPreferences.getInstance();
String str = json.encode(accountMap.map((type, v0) {
return MapEntry(type, v0.map((domain, v1) {
return MapEntry(domain, v1.map((login, v2) {
return MapEntry(login, v2.toJson());
}));
}));
}));
await prefs.setString(StorageKeys.account, str);
final prefs = await SharedPreferences.getInstance();
await prefs.setString(StorageKeys.accounts, json.encode(_accounts));
} catch (err) {
print(err);
// TODO: show errors
@ -163,65 +146,30 @@ class SettingsModel with ChangeNotifier {
}
void init() async {
// Listen scheme
_sub = getUriLinksStream().listen(_onSchemeDetected, onError: (err) {
print(err);
});
var prefs = await SharedPreferences.getInstance();
// read GitHub accounts
// Read accounts
try {
String str = prefs.getString(StorageKeys.github);
print('read github: $str');
Map<String, dynamic> data = json.decode(str ?? '{}');
githubAccountMap = data.map<String, AccountModel>((login, _accountMap) =>
MapEntry(login, AccountModel.fromJson(_accountMap)));
String str = prefs.getString(StorageKeys.accounts);
print('read accounts: $str');
_accounts = (json.decode(str ?? '[]') as List)
.map((item) => AccountModel.fromJson(item))
.toList();
} catch (err) {
print(err);
githubAccountMap = {};
_accounts = [];
}
// read accounts
try {
String str = prefs.getString(StorageKeys.account);
print('read account: $str');
var data = Map<String, Map<String, dynamic>>.from(
Map<String, dynamic>.from(json.decode(str ?? '{}')));
accountMap = {};
data.forEach((platform, v0) {
accountMap[platform] = {};
v0.forEach((domain, v1) {
accountMap[platform][domain] = {};
v1.forEach((login, v2) {
accountMap[platform][domain][login] = AccountModel.fromJson(v2);
});
});
});
// TODO: type cast
// accountMap = data.map((type, v0) {
// return MapEntry(type, v0.map((domain, v1) {
// return MapEntry(domain, v1.map((login, v2) {
// return MapEntry(login, AccountModel.fromJson(v2));
// }));
// }));
// });
} catch (err) {
print(err);
accountMap = {};
}
ready = true;
notifyListeners();
}
void setActiveAccount(String platform, String domain, String login) {
activePlatform = platform;
activeDomain = domain;
activeLogin = login;
void setActiveAccountIndex(int index) {
activeAccountIndex = index;
notifyListeners();
}
@ -237,7 +185,7 @@ class SettingsModel with ChangeNotifier {
_token = token;
}
if (_token == null) {
throw Exception('token is null');
throw 'token is null';
}
final res = await http
@ -253,7 +201,7 @@ class SettingsModel with ChangeNotifier {
final data = json.decode(res.body);
if (data['errors'] != null) {
throw Exception(data['errors'][0]['message']);
throw data['errors'][0]['message'];
}
return data['data'];

View File

@ -5,7 +5,6 @@ import 'package:git_touch/widgets/app_bar_title.dart';
import 'package:provider/provider.dart';
import '../widgets/link.dart';
import '../widgets/loading.dart';
import '../models/account.dart';
import '../widgets/avatar.dart';
// import 'login_gitlab.dart';
@ -15,39 +14,39 @@ class LoginScreen extends StatefulWidget {
}
class _LoginScreenState extends State<LoginScreen> {
Widget _buildAccountItem(AccountModel account) {
var settings = Provider.of<SettingsModel>(context);
Widget _buildAccountItem(int index) {
final settings = Provider.of<SettingsModel>(context);
final account = settings.accounts[index];
return Link(
onTap: () {
// Navigator.of(context).pop();
settings.setActiveAccount(
account.platform, account.domain, account.login);
settings.setActiveAccountIndex(index);
},
child: Container(
padding: EdgeInsets.all(10),
decoration: BoxDecoration(
border: Border(bottom: BorderSide(color: Colors.black12)),
),
child: Row(children: <Widget>[
Avatar(url: account.avatarUrl, size: 24),
Padding(padding: EdgeInsets.only(left: 10)),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Text(account.login, style: TextStyle(fontSize: 20)),
Padding(padding: EdgeInsets.only(top: 6)),
Text(account.domain)
],
child: Row(
children: <Widget>[
Avatar(url: account.avatarUrl, size: 24),
Padding(padding: EdgeInsets.only(left: 10)),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Text(account.login, style: TextStyle(fontSize: 20)),
Padding(padding: EdgeInsets.only(top: 6)),
Text(account.domain)
],
),
),
),
(settings.activePlatform == account.platform &&
settings.activeDomain == account.domain &&
settings.activeLogin == account.login)
? Icon(Icons.check)
: Container(),
]),
(index == settings.activeAccountIndex)
? Icon(Icons.check)
: Container(),
],
),
),
);
}
@ -73,22 +72,7 @@ class _LoginScreenState extends State<LoginScreen> {
@override
Widget build(BuildContext context) {
var settings = Provider.of<SettingsModel>(context);
List<AccountModel> accounts = [];
settings.accountMap.forEach((platform, v0) {
v0.forEach((domain, v1) {
v1.forEach((login, v2) {
accounts.add(AccountModel(
avatarUrl: v2.avatarUrl,
token: v2.token,
platform: platform,
domain: domain,
login: login,
));
});
});
});
final settings = Provider.of<SettingsModel>(context);
return SingleScaffold(
title: AppBarTitle('Select account'),
@ -97,15 +81,7 @@ class _LoginScreenState extends State<LoginScreen> {
: Container(
child: Column(
children: [
...settings.githubAccountMap.entries
.map<Widget>((entry) => _buildAccountItem(AccountModel(
avatarUrl: entry.value.avatarUrl,
token: entry.value.token,
platform: PlatformType.github,
domain: 'https://github.com',
login: entry.key)))
.toList(),
...accounts.map(_buildAccountItem),
...List.generate(settings.accounts.length, _buildAccountItem),
_buildAddItem(
text: 'GitHub Account',
onTap: settings.redirectToGithubOauth,

View File

@ -49,8 +49,8 @@ class NewsScreenState extends State<NewsScreen> {
}
Future<ListPayload<EventPayload, int>> fetchEvents([int page = 1]) async {
var settings = Provider.of<SettingsModel>(context);
var login = settings.activeLogin;
final settings = Provider.of<SettingsModel>(context);
final login = settings.activeAccount.login;
List data = await settings.getWithCredentials(
'/users/$login/received_events?page=$page&per_page=$pageSize');
// print(data.length);

View File

@ -13,6 +13,7 @@ export 'package:flutter_vector_icons/flutter_vector_icons.dart';
final monospaceFont = Platform.isIOS ? 'Menlo' : 'monospace'; // FIXME:
class StorageKeys {
static const accounts = 'accounts';
static const account = 'account';
static const github = 'github';
static const theme = 'theme';

View File

@ -42,8 +42,8 @@ dependencies:
dev_dependencies:
flutter_test:
sdk: flutter
build_runner: ^1.1.3
json_serializable: ^2.0.1
build_runner: ^1.7.1
json_serializable: ^3.2.2
# For information on the generic Dart part of this file, see the
# following page: https://www.dartlang.org/tools/pub/pubspec