diff --git a/lib/main.dart b/lib/main.dart index 7d3d1f1..298c7de 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -107,7 +107,7 @@ class _HomeState extends State { return SearchScreen(); case 4: return UserScreen( - Provider.of(context).activeLogin, + Provider.of(context).activeAccount.login, isMe: true, ); } @@ -131,7 +131,7 @@ class _HomeState extends State { } // print(settings.activeLogin); - if (settings.activeLogin == null) { + if (settings.activeAccount == null) { return MaterialApp(theme: themData, home: LoginScreen()); } diff --git a/lib/models/account.dart b/lib/models/account.dart index 3ef5efd..cb0f5ac 100644 --- a/lib/models/account.dart +++ b/lib/models/account.dart @@ -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 json) => diff --git a/lib/models/account.g.dart b/lib/models/account.g.dart index 0c5118e..e5b608a 100644 --- a/lib/models/account.g.dart +++ b/lib/models/account.g.dart @@ -8,8 +8,19 @@ part of 'account.dart'; AccountModel _$AccountModelFromJson(Map 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 _$AccountModelToJson(AccountModel instance) => - {'avatarUrl': instance.avatarUrl, 'token': instance.token}; + { + 'platform': instance.platform, + 'domain': instance.domain, + 'token': instance.token, + 'login': instance.login, + 'avatarUrl': instance.avatarUrl, + }; diff --git a/lib/models/settings.dart b/lib/models/settings.dart index cd82f76..bffa471 100644 --- a/lib/models/settings.dart +++ b/lib/models/settings.dart @@ -38,33 +38,22 @@ class PlatformType { // } class SettingsModel with ChangeNotifier { - bool ready = false; - - Map githubAccountMap; - Map>> accountMap; - - String activePlatform; - String activeDomain; - String activeLogin; + static const _apiPrefix = 'https://api.github.com'; + List _accounts; + int activeAccountIndex; StreamSubscription _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 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 _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 _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 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 data = json.decode(str ?? '{}'); - githubAccountMap = data.map((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>.from( - Map.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']; diff --git a/lib/screens/login.dart b/lib/screens/login.dart index 719ded0..4172b24 100644 --- a/lib/screens/login.dart +++ b/lib/screens/login.dart @@ -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 { - Widget _buildAccountItem(AccountModel account) { - var settings = Provider.of(context); + Widget _buildAccountItem(int index) { + final settings = Provider.of(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: [ - Avatar(url: account.avatarUrl, size: 24), - Padding(padding: EdgeInsets.only(left: 10)), - Expanded( - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text(account.login, style: TextStyle(fontSize: 20)), - Padding(padding: EdgeInsets.only(top: 6)), - Text(account.domain) - ], + child: Row( + children: [ + Avatar(url: account.avatarUrl, size: 24), + Padding(padding: EdgeInsets.only(left: 10)), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + 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 { @override Widget build(BuildContext context) { - var settings = Provider.of(context); - - List 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(context); return SingleScaffold( title: AppBarTitle('Select account'), @@ -97,15 +81,7 @@ class _LoginScreenState extends State { : Container( child: Column( children: [ - ...settings.githubAccountMap.entries - .map((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, diff --git a/lib/screens/news.dart b/lib/screens/news.dart index 58b9bcd..9b7f5fb 100644 --- a/lib/screens/news.dart +++ b/lib/screens/news.dart @@ -49,8 +49,8 @@ class NewsScreenState extends State { } Future> fetchEvents([int page = 1]) async { - var settings = Provider.of(context); - var login = settings.activeLogin; + final settings = Provider.of(context); + final login = settings.activeAccount.login; List data = await settings.getWithCredentials( '/users/$login/received_events?page=$page&per_page=$pageSize'); // print(data.length); diff --git a/lib/utils/utils.dart b/lib/utils/utils.dart index 121f300..c29fe66 100644 --- a/lib/utils/utils.dart +++ b/lib/utils/utils.dart @@ -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'; diff --git a/pubspec.yaml b/pubspec.yaml index be77fbb..67a723f 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -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