git-touch-android-ios-app/lib/models/auth.dart

764 lines
21 KiB
Dart
Raw Normal View History

2019-02-07 07:35:19 +01:00
import 'dart:async';
2022-09-17 14:35:45 +02:00
import 'dart:convert';
2021-01-17 15:08:32 +01:00
import 'package:ferry/ferry.dart';
2022-09-17 14:35:45 +02:00
import 'package:fimber/fimber.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/widgets.dart';
2022-09-24 09:36:22 +02:00
import 'package:git_touch/models/account.dart';
2020-02-02 07:08:58 +01:00
import 'package:git_touch/models/bitbucket.dart';
2020-01-29 10:33:54 +01:00
import 'package:git_touch/models/gitea.dart';
2020-10-13 18:43:11 +02:00
import 'package:git_touch/models/gitee.dart';
2022-09-24 09:36:22 +02:00
import 'package:git_touch/models/gitlab.dart';
import 'package:git_touch/models/gogs.dart';
import 'package:git_touch/utils/utils.dart';
import 'package:github/github.dart';
import 'package:gql_http_link/gql_http_link.dart';
2019-02-07 07:35:19 +01:00
import 'package:http/http.dart' as http;
import 'package:nanoid/nanoid.dart';
import 'package:shared_preferences/shared_preferences.dart';
2022-09-17 14:35:45 +02:00
import 'package:uni_links/uni_links.dart';
// import 'package:in_app_review/in_app_review.dart';
import 'package:universal_io/io.dart';
import 'package:url_launcher/url_launcher.dart';
2020-01-27 06:24:01 +01:00
const clientId = 'df930d7d2e219f26142a';
2019-02-21 14:21:16 +01:00
class PlatformType {
static const github = 'github';
static const gitlab = 'gitlab';
2020-02-02 07:08:58 +01:00
static const bitbucket = 'bitbucket';
2020-01-29 10:33:54 +01:00
static const gitea = 'gitea';
2020-10-13 18:43:11 +02:00
static const gitee = 'gitee';
2021-01-23 15:08:05 +01:00
static const gogs = 'gogs';
}
2020-01-29 05:32:35 +01:00
class DataWithPage<T> {
2020-02-02 09:40:12 +01:00
DataWithPage({
2021-05-16 09:16:35 +02:00
required this.data,
required this.cursor,
required this.hasMore,
required this.total,
2020-02-02 10:30:48 +01:00
});
2022-09-21 18:28:21 +02:00
T data;
int cursor;
bool hasMore;
int total;
2020-02-02 10:30:48 +01:00
}
2019-09-27 14:52:38 +02:00
class AuthModel with ChangeNotifier {
2019-09-26 16:14:14 +02:00
static const _apiPrefix = 'https://api.github.com';
2019-02-21 14:21:16 +01:00
// static final inAppReview = InAppReview.instance;
var hasRequestedReview = false;
2021-05-30 18:03:53 +02:00
List<Account> _accounts = [];
2021-05-16 09:16:35 +02:00
int? activeAccountIndex;
late StreamSubscription<Uri?> _sub;
bool loading = false;
2019-02-07 07:35:19 +01:00
2021-05-16 09:16:35 +02:00
List<Account>? get accounts => _accounts;
Account? get activeAccount {
2021-05-30 18:03:53 +02:00
if (activeAccountIndex == null || _accounts.isEmpty) return null;
return _accounts[activeAccountIndex!];
2019-02-07 07:35:19 +01:00
}
2019-01-30 07:46:18 +01:00
2021-05-16 09:16:35 +02:00
String get token => activeAccount!.token;
2019-09-26 16:14:14 +02:00
2019-09-27 14:52:38 +02:00
_addAccount(Account account) async {
2021-05-16 09:16:35 +02:00
_accounts = [...accounts!, account];
// Save
final prefs = await SharedPreferences.getInstance();
await prefs.setString(StorageKeys.accounts, json.encode(_accounts));
}
2020-02-01 11:30:32 +01:00
removeAccount(int index) async {
if (activeAccountIndex == index) {
activeAccountIndex = null;
}
2021-05-30 18:03:53 +02:00
_accounts.removeAt(index);
2020-02-01 11:30:32 +01:00
// Save
final prefs = await SharedPreferences.getInstance();
await prefs.setString(StorageKeys.accounts, json.encode(_accounts));
notifyListeners();
}
2019-02-07 07:35:19 +01:00
// https://developer.github.com/apps/building-oauth-apps/authorizing-oauth-apps/#web-application-flow
2021-05-16 09:16:35 +02:00
Future<void> _onSchemeDetected(Uri? uri) async {
2022-09-06 18:40:53 +02:00
await closeInAppWebView();
2019-09-08 14:07:35 +02:00
loading = true;
notifyListeners();
2019-09-26 16:14:14 +02:00
// Get token by code
final res = await http.post(
2021-05-16 06:09:27 +02:00
Uri.parse('https://git-touch-oauth.vercel.app/api/token'),
2019-02-07 07:35:19 +01:00
headers: {
HttpHeaders.acceptHeader: 'application/json',
HttpHeaders.contentTypeHeader: 'application/json',
},
body: json.encode({
'client_id': clientId,
2021-05-16 09:16:35 +02:00
'code': uri!.queryParameters['code'],
'state': _oauthState,
2019-02-07 07:35:19 +01:00
}),
);
2019-09-26 16:14:14 +02:00
final token = json.decode(res.body)['access_token'] as String;
2019-10-03 06:55:17 +02:00
await loginWithToken(token);
}
2019-02-07 07:35:19 +01:00
2019-10-03 06:55:17 +02:00
Future<void> loginWithToken(String token) async {
2020-02-01 10:00:30 +01:00
try {
final queryData = await query('''
2019-02-07 07:35:19 +01:00
{
viewer {
login
avatarUrl
}
}
''', token);
2019-02-07 07:35:19 +01:00
2020-02-01 10:00:30 +01:00
await _addAccount(Account(
platform: PlatformType.github,
domain: 'https://github.com',
token: token,
login: queryData['viewer']['login'] as String,
avatarUrl: queryData['viewer']['avatarUrl'] as String,
));
} finally {
loading = false;
notifyListeners();
}
2019-02-07 07:35:19 +01:00
}
2019-02-21 14:21:16 +01:00
Future<void> loginToGitlab(String domain, String token) async {
2020-02-06 07:38:43 +01:00
domain = domain.trim();
token = token.trim();
2020-02-01 10:00:30 +01:00
loading = true;
notifyListeners();
2019-02-21 14:21:16 +01:00
try {
2021-05-16 06:09:27 +02:00
final res = await http.get(Uri.parse('$domain/api/v4/user'),
headers: {'Private-Token': token});
2019-09-26 16:14:14 +02:00
final info = json.decode(res.body);
2019-02-21 14:21:16 +01:00
if (info['message'] != null) {
throw info['message'];
}
if (info['error'] != null) {
2022-09-06 18:40:53 +02:00
throw info['error'] + '. ' + (info['error_description'] ?? '');
}
2019-12-30 13:50:31 +01:00
final user = GitlabUser.fromJson(info);
2019-09-27 14:52:38 +02:00
await _addAccount(Account(
2019-09-26 16:14:14 +02:00
platform: PlatformType.gitlab,
domain: domain,
token: token,
2021-05-16 09:16:35 +02:00
login: user.username!,
avatarUrl: user.avatarUrl!,
2019-12-30 13:50:31 +01:00
gitlabId: user.id,
));
2019-02-21 14:21:16 +01:00
} finally {
2019-09-08 14:07:35 +02:00
loading = false;
notifyListeners();
2019-02-21 14:21:16 +01:00
}
}
2019-12-11 16:37:29 +01:00
Future<String> fetchWithGitlabToken(String p) async {
2021-05-16 06:09:27 +02:00
final res = await http.get(Uri.parse(p), headers: {'Private-Token': token});
2019-12-11 16:37:29 +01:00
return res.body;
}
Future fetchGitlab(String p,
{isPost = false, Map<String, dynamic> body = const {}}) async {
http.Response res;
if (isPost) {
res = await http.post(
2021-05-16 09:16:35 +02:00
Uri.parse('${activeAccount!.domain}/api/v4$p'),
headers: {
'Private-Token': token,
HttpHeaders.contentTypeHeader: 'application/json'
},
body: jsonEncode(body),
);
} else {
2021-05-16 09:16:35 +02:00
res = await http.get(Uri.parse('${activeAccount!.domain}/api/v4$p'),
headers: {'Private-Token': token});
}
2019-11-01 18:12:17 +01:00
final info = json.decode(utf8.decode(res.bodyBytes));
2020-01-30 05:53:07 +01:00
if (info is Map && info['message'] != null) throw info['message'];
2019-11-01 18:12:17 +01:00
return info;
}
2020-01-29 05:32:35 +01:00
Future<DataWithPage> fetchGitlabWithPage(String p) async {
2021-05-16 09:16:35 +02:00
final res = await http.get(Uri.parse('${activeAccount!.domain}/api/v4$p'),
2020-01-29 05:32:35 +01:00
headers: {'Private-Token': token});
final next = int.tryParse(
2021-05-30 18:41:51 +02:00
res.headers['X-Next-Pages'] ?? res.headers['x-next-page'] ?? '');
2020-01-29 05:32:35 +01:00
final info = json.decode(utf8.decode(res.bodyBytes));
2020-01-30 05:53:07 +01:00
if (info is Map && info['message'] != null) throw info['message'];
return DataWithPage(
data: info,
2021-05-30 18:41:51 +02:00
cursor: next ?? 1,
hasMore: next != null,
2021-05-30 18:41:51 +02:00
total: int.tryParse(
res.headers['X-Total'] ?? res.headers['x-total'] ?? '') ??
TOTAL_COUNT_FALLBACK,
);
2020-01-29 05:32:35 +01:00
}
2020-01-29 10:33:54 +01:00
Future loginToGitea(String domain, String token) async {
2020-02-06 07:38:43 +01:00
domain = domain.trim();
token = token.trim();
2020-01-29 10:33:54 +01:00
try {
loading = true;
notifyListeners();
2021-05-16 06:09:27 +02:00
final res = await http.get(Uri.parse('$domain/api/v1/user'),
2020-01-29 10:33:54 +01:00
headers: {'Authorization': 'token $token'});
final info = json.decode(res.body);
if (info['message'] != null) {
throw info['message'];
}
final user = GiteaUser.fromJson(info);
await _addAccount(Account(
platform: PlatformType.gitea,
domain: domain,
token: token,
2021-05-16 09:16:35 +02:00
login: user.login!,
avatarUrl: user.avatarUrl!,
2020-01-29 10:33:54 +01:00
));
} finally {
loading = false;
notifyListeners();
}
}
Future fetchGitea(
String p, {
requestType = 'GET',
Map<String, dynamic> body = const {},
}) async {
2021-05-16 09:16:35 +02:00
late http.Response res;
2022-09-24 07:41:46 +02:00
final headers = <String, String>{
'Authorization': 'token $token',
HttpHeaders.contentTypeHeader: 'application/json'
};
switch (requestType) {
case 'DELETE':
{
await http.delete(
2021-05-16 09:16:35 +02:00
Uri.parse('${activeAccount!.domain}/api/v1$p'),
headers: headers,
);
break;
}
case 'POST':
{
res = await http.post(
2021-05-16 09:16:35 +02:00
Uri.parse('${activeAccount!.domain}/api/v1$p'),
headers: headers,
body: jsonEncode(body),
);
break;
}
case 'PATCH':
{
res = await http.patch(
2021-05-16 09:16:35 +02:00
Uri.parse('${activeAccount!.domain}/api/v1$p'),
headers: headers,
body: jsonEncode(body),
);
break;
}
default:
{
2021-05-16 09:16:35 +02:00
res = await http.get(Uri.parse('${activeAccount!.domain}/api/v1$p'),
headers: headers);
break;
}
}
if (requestType != 'DELETE') {
final info = json.decode(utf8.decode(res.bodyBytes));
return info;
}
return;
2019-12-04 15:02:22 +01:00
}
Future<DataWithPage> fetchGiteaWithPage(String path,
2021-05-16 09:16:35 +02:00
{int? page, int? limit}) async {
2021-01-23 15:08:05 +01:00
page = page ?? 1;
limit = limit ?? PAGE_SIZE;
2021-01-23 15:08:05 +01:00
2021-05-16 09:16:35 +02:00
var uri = Uri.parse('${activeAccount!.domain}/api/v1$path');
2021-01-23 15:08:05 +01:00
uri = uri.replace(
queryParameters: {
'page': page.toString(),
'limit': limit.toString(),
...uri.queryParameters,
},
);
final res = await http.get(uri, headers: {'Authorization': 'token $token'});
final info = json.decode(utf8.decode(res.bodyBytes));
return DataWithPage(
data: info,
cursor: page + 1,
2022-09-06 18:28:12 +02:00
hasMore: info is List && info.isNotEmpty,
2021-05-30 18:41:51 +02:00
total: int.tryParse(res.headers['x-total-count'] ?? '') ??
TOTAL_COUNT_FALLBACK,
2021-01-23 15:08:05 +01:00
);
}
Future loginToGogs(String domain, String token) async {
domain = domain.trim();
token = token.trim();
try {
loading = true;
notifyListeners();
2021-05-16 06:09:27 +02:00
final res = await http.get(Uri.parse('$domain/api/v1/user'),
2021-01-23 15:08:05 +01:00
headers: {'Authorization': 'token $token'});
final info = json.decode(res.body);
if (info['message'] != null) {
throw info['message'];
}
final user = GogsUser.fromJson(info);
await _addAccount(Account(
platform: PlatformType.gogs,
domain: domain,
token: token,
2021-05-16 09:16:35 +02:00
login: user.username!,
avatarUrl: user.avatarUrl!,
2021-01-23 15:08:05 +01:00
));
} finally {
loading = false;
notifyListeners();
}
}
// TODO: refactor
Future fetchGogs(
String p, {
requestType = 'GET',
Map<String, dynamic> body = const {},
}) async {
2021-05-16 09:16:35 +02:00
late http.Response res;
2022-09-24 07:41:46 +02:00
final headers = <String, String>{
2021-01-23 15:08:05 +01:00
'Authorization': 'token $token',
HttpHeaders.contentTypeHeader: 'application/json'
};
switch (requestType) {
case 'DELETE':
{
await http.delete(
2021-05-16 09:16:35 +02:00
Uri.parse('${activeAccount!.domain}/api/v1$p'),
2021-01-23 15:08:05 +01:00
headers: headers,
);
break;
}
case 'POST':
{
res = await http.post(
2021-05-16 09:16:35 +02:00
Uri.parse('${activeAccount!.domain}/api/v1$p'),
2021-01-23 15:08:05 +01:00
headers: headers,
body: jsonEncode(body),
);
break;
}
case 'PATCH':
{
res = await http.patch(
2021-05-16 09:16:35 +02:00
Uri.parse('${activeAccount!.domain}/api/v1$p'),
2021-01-23 15:08:05 +01:00
headers: headers,
body: jsonEncode(body),
);
break;
}
default:
{
2021-05-16 09:16:35 +02:00
res = await http.get(Uri.parse('${activeAccount!.domain}/api/v1$p'),
2021-01-23 15:08:05 +01:00
headers: headers);
break;
}
}
if (requestType != 'DELETE') {
final info = json.decode(utf8.decode(res.bodyBytes));
return info;
}
return;
}
Future<DataWithPage> fetchGogsWithPage(String path,
2021-05-16 09:16:35 +02:00
{int? page, int? limit}) async {
page = page ?? 1;
limit = limit ?? PAGE_SIZE;
2021-05-16 09:16:35 +02:00
var uri = Uri.parse('${activeAccount!.domain}/api/v1$path');
uri = uri.replace(
queryParameters: {
'page': page.toString(),
'limit': limit.toString(),
...uri.queryParameters,
},
);
final res = await http.get(uri, headers: {'Authorization': 'token $token'});
2020-02-01 08:21:42 +01:00
final info = json.decode(utf8.decode(res.bodyBytes));
2020-02-01 08:21:42 +01:00
return DataWithPage(
data: info,
cursor: page + 1,
2022-09-06 18:28:12 +02:00
hasMore: info is List && info.isNotEmpty,
2021-05-30 18:41:51 +02:00
total: int.tryParse(res.headers['x-total-count'] ?? '') ??
TOTAL_COUNT_FALLBACK,
2020-02-01 08:21:42 +01:00
);
}
Future fetchGitee(
String p, {
requestType = 'GET',
Map<String, dynamic> body = const {},
}) async {
http.Response res;
2022-09-24 07:41:46 +02:00
final headers = <String, String>{
'Authorization': 'token $token',
HttpHeaders.contentTypeHeader: 'application/json'
};
switch (requestType) {
case 'DELETE':
{
await http.delete(
2021-05-16 09:16:35 +02:00
Uri.parse('${activeAccount!.domain}/api/v5$p'),
headers: headers,
);
return;
}
case 'PUT':
{
await http.put(
2021-05-16 09:16:35 +02:00
Uri.parse('${activeAccount!.domain}/api/v5$p'),
headers: headers,
);
return;
}
case 'POST':
{
res = await http.post(
2021-05-16 09:16:35 +02:00
Uri.parse('${activeAccount!.domain}/api/v5$p'),
headers: headers,
body: jsonEncode(body),
);
break;
}
case 'PATCH':
{
res = await http.patch(
2021-05-16 09:16:35 +02:00
Uri.parse('${activeAccount!.domain}/api/v5$p'),
headers: headers,
body: jsonEncode(body),
);
break;
}
case 'NO CONTENT':
{
2021-05-16 09:16:35 +02:00
res = await http.get(Uri.parse('${activeAccount!.domain}/api/v5$p'),
headers: headers);
return res;
}
default:
{
2021-05-16 09:16:35 +02:00
res = await http.get(Uri.parse('${activeAccount!.domain}/api/v5$p'),
headers: headers);
break;
}
}
final info = json.decode(utf8.decode(res.bodyBytes));
return info;
2020-10-17 11:35:08 +02:00
}
2020-10-17 12:09:36 +02:00
Future<DataWithPage> fetchGiteeWithPage(String path,
2021-05-16 09:16:35 +02:00
{int? page, int? limit}) async {
2020-10-17 12:09:36 +02:00
page = page ?? 1;
limit = limit ?? PAGE_SIZE;
2020-10-17 12:09:36 +02:00
2021-05-16 09:16:35 +02:00
var uri = Uri.parse('${activeAccount!.domain}/api/v5$path');
2020-10-17 12:09:36 +02:00
uri = uri.replace(
queryParameters: {
'page': page.toString(),
'per_page': limit.toString(),
...uri.queryParameters,
},
);
final res = await http.get(uri, headers: {'Authorization': 'token $token'});
final info = json.decode(utf8.decode(res.bodyBytes));
2020-10-17 14:46:47 +02:00
final totalPage = int.tryParse(res.headers['total_page'] ?? '');
2021-05-30 18:41:51 +02:00
final totalCount =
int.tryParse(res.headers['total_count'] ?? '') ?? TOTAL_COUNT_FALLBACK;
2020-10-17 14:46:47 +02:00
2020-10-17 12:09:36 +02:00
return DataWithPage(
data: info,
cursor: page + 1,
2020-10-17 14:46:47 +02:00
hasMore: totalPage == null ? info.length > limit : totalPage > page,
total: totalCount,
2020-10-17 12:09:36 +02:00
);
}
2020-02-02 07:08:58 +01:00
Future loginToBb(String domain, String username, String appPassword) async {
2020-02-06 07:38:43 +01:00
domain = domain.trim();
username = username.trim();
appPassword = appPassword.trim();
2020-02-02 07:08:58 +01:00
try {
loading = true;
notifyListeners();
final uri = Uri.parse('$domain/api/2.0/user')
.replace(userInfo: '$username:$appPassword');
final res = await http.get(uri);
if (res.statusCode >= 400) {
throw 'status ${res.statusCode}';
}
final info = json.decode(res.body);
final user = BbUser.fromJson(info);
await _addAccount(Account(
platform: PlatformType.bitbucket,
domain: domain,
2021-05-16 09:16:35 +02:00
token: user.username!,
2020-02-02 07:08:58 +01:00
login: username,
2021-05-16 09:16:35 +02:00
avatarUrl: user.avatarUrl!,
2020-02-02 07:08:58 +01:00
appPassword: appPassword,
accountId: user.accountId,
2020-02-02 07:08:58 +01:00
));
} finally {
loading = false;
notifyListeners();
}
}
Future<http.Response> fetchBb(
String p, {
isPost = false,
Map<String, dynamic> body = const {},
}) async {
2020-02-02 10:30:48 +01:00
if (p.startsWith('/') && !p.startsWith('/api')) p = '/api/2.0$p';
2020-02-02 09:40:12 +01:00
final input = Uri.parse(p);
2021-05-16 09:16:35 +02:00
final uri = Uri.parse(activeAccount!.domain).replace(
userInfo: '${activeAccount!.login}:${activeAccount!.appPassword}',
2020-02-02 09:40:12 +01:00
path: input.path,
queryParameters: {
'pagelen': PAGE_SIZE.toString(),
...input.queryParameters
},
2020-02-02 09:40:12 +01:00
);
if (isPost) {
return http.post(
uri,
headers: {HttpHeaders.contentTypeHeader: 'application/json'},
body: jsonEncode(body),
);
}
2020-02-02 12:50:00 +01:00
return http.get(uri);
2020-02-02 09:40:12 +01:00
}
Future fetchBbJson(
String p, {
isPost = false,
Map<String, dynamic> body = const {},
}) async {
final res = await fetchBb(
p,
isPost: isPost,
body: body,
);
2020-02-02 12:50:00 +01:00
return json.decode(utf8.decode(res.bodyBytes));
}
2021-06-13 20:13:11 +02:00
Future<ListPayload<dynamic, String?>> fetchBbWithPage(String p) async {
2020-02-02 12:50:00 +01:00
final data = await fetchBbJson(p);
final v = BbPagination.fromJson(data);
2021-06-13 20:13:11 +02:00
return ListPayload(
2020-02-02 10:30:48 +01:00
cursor: v.next,
2021-06-13 20:13:11 +02:00
items: v.values,
2020-02-02 09:40:12 +01:00
hasMore: v.next != null,
);
}
2020-10-13 18:43:11 +02:00
Future loginToGitee(String token) async {
token = token.trim();
try {
loading = true;
notifyListeners();
2021-05-16 06:09:27 +02:00
final res = await http.get(Uri.parse('https://gitee.com/api/v5/user'),
2020-10-13 18:43:11 +02:00
headers: {'Authorization': 'token $token'});
final info = json.decode(res.body);
if (info['message'] != null) {
throw info['message'];
}
final user = GiteeUser.fromJson(info);
await _addAccount(Account(
2020-10-17 11:27:13 +02:00
platform: PlatformType.gitee,
2020-10-13 18:43:11 +02:00
domain: 'https://gitee.com',
token: token,
2021-05-16 09:16:35 +02:00
login: user.login!,
avatarUrl: user.avatarUrl!,
2020-10-13 18:43:11 +02:00
));
} finally {
loading = false;
notifyListeners();
}
}
2019-11-05 08:09:54 +01:00
Future<void> init() async {
2019-09-26 16:14:14 +02:00
// Listen scheme
2021-05-16 07:23:31 +02:00
_sub = uriLinkStream.listen(_onSchemeDetected, onError: (err) {
Fimber.e('getUriLinksStream failed', ex: err);
2019-09-08 14:07:35 +02:00
});
2022-09-24 07:41:46 +02:00
final prefs = await SharedPreferences.getInstance();
2019-02-07 07:35:19 +01:00
2019-09-26 16:14:14 +02:00
// Read accounts
2019-02-21 14:21:16 +01:00
try {
2022-09-24 07:41:46 +02:00
final str = prefs.getString(StorageKeys.accounts);
2020-02-08 08:00:44 +01:00
// Fimber.d('read accounts: $str');
2019-09-26 16:14:14 +02:00
_accounts = (json.decode(str ?? '[]') as List)
2019-09-27 14:52:38 +02:00
.map((item) => Account.fromJson(item))
2019-09-26 16:14:14 +02:00
.toList();
activeAccountIndex = prefs.getInt(StorageKeys.iDefaultAccount);
if (activeAccount != null) {
_activeTab = prefs.getInt(
2021-05-16 09:16:35 +02:00
StorageKeys.getDefaultStartTabKey(activeAccount!.platform)) ??
0;
}
2019-02-21 14:21:16 +01:00
} catch (err) {
Fimber.e('prefs getAccount failed', ex: err);
2019-09-26 16:14:14 +02:00
_accounts = [];
2019-02-21 14:21:16 +01:00
}
2019-09-08 14:07:35 +02:00
notifyListeners();
2019-02-07 07:35:19 +01:00
}
2019-10-03 06:24:09 +02:00
@override
void dispose() {
_sub.cancel();
super.dispose();
}
Future<void> setDefaultAccount(int v) async {
final prefs = await SharedPreferences.getInstance();
await prefs.setInt(StorageKeys.iDefaultAccount, v);
Fimber.d('write default account: $v');
notifyListeners();
}
2020-02-01 07:44:15 +01:00
var rootKey = UniqueKey();
2021-02-14 14:23:15 +01:00
reloadApp() {
rootKey = UniqueKey();
notifyListeners();
}
2020-05-01 12:05:49 +02:00
setActiveAccountAndReload(int index) async {
2020-02-01 07:44:15 +01:00
// https://stackoverflow.com/a/50116077
rootKey = UniqueKey();
2019-09-26 16:14:14 +02:00
activeAccountIndex = index;
2021-05-16 09:16:35 +02:00
setDefaultAccount(activeAccountIndex!);
2020-05-01 12:05:49 +02:00
final prefs = await SharedPreferences.getInstance();
_activeTab = prefs.getInt(
2021-05-16 09:16:35 +02:00
StorageKeys.getDefaultStartTabKey(activeAccount!.platform)) ??
2020-05-01 12:05:49 +02:00
0;
_ghClient = null;
2022-10-01 19:26:34 +02:00
_ghGqlClient = null;
_glGqlClient = null;
2019-09-08 14:07:35 +02:00
notifyListeners();
2020-12-13 06:08:17 +01:00
// TODO: strategy
// waiting for 1min to request review
2020-12-13 06:08:17 +01:00
// if (!hasRequestedReview) {
// hasRequestedReview = true;
// Timer(Duration(minutes: 1), () async {
// if (await inAppReview.isAvailable()) {
// inAppReview.requestReview();
// }
// });
// }
2019-02-07 07:35:19 +01:00
}
// http timeout
2022-09-06 18:28:12 +02:00
final _timeoutDuration = const Duration(seconds: 10);
// var _timeoutDuration = Duration(seconds: 1);
2021-05-16 09:16:35 +02:00
GitHub? _ghClient;
2021-06-14 08:56:42 +02:00
GitHub get ghClient {
2022-09-06 18:28:12 +02:00
_ghClient ??= GitHub(auth: Authentication.withToken(token));
2021-06-14 08:56:42 +02:00
return _ghClient!;
}
2022-10-01 19:26:34 +02:00
Client? _ghGqlClient;
Client get ghGqlClient {
return _ghGqlClient ??= Client(
2022-09-06 18:40:53 +02:00
link: HttpLink(
'$_apiPrefix/graphql',
defaultHeaders: {HttpHeaders.authorizationHeader: 'token $token'},
),
// https://ferrygraphql.com/docs/fetch-policies#default-fetchpolicies
2022-10-01 19:26:34 +02:00
defaultFetchPolicies: {OperationType.query: FetchPolicy.NetworkOnly},
2022-09-06 18:40:53 +02:00
);
2022-10-01 19:26:34 +02:00
}
2019-11-06 14:27:37 +01:00
2022-10-01 19:26:34 +02:00
Client? _glGqlClient;
Client get glGqlClient {
return _glGqlClient ??= Client(
link: HttpLink(
Uri.parse(activeAccount!.domain)
.replace(path: '/api/graphql')
.toString(),
defaultHeaders: {'Private-Token': token},
),
// https://ferrygraphql.com/docs/fetch-policies#default-fetchpolicies
defaultFetchPolicies: {OperationType.query: FetchPolicy.NetworkOnly},
);
}
2019-11-06 14:27:37 +01:00
2022-09-06 18:28:12 +02:00
Future<dynamic> query(String query, [String? token]) async {
token ??= token;
2019-02-07 07:35:19 +01:00
final res = await http
2022-09-06 18:28:12 +02:00
.post(Uri.parse('$_apiPrefix/graphql'),
headers: {
2022-09-06 18:28:12 +02:00
HttpHeaders.authorizationHeader: 'token $token',
HttpHeaders.contentTypeHeader: 'application/json'
},
body: json.encode({'query': query}))
.timeout(_timeoutDuration);
// Fimber.d(res.body);
2019-02-07 07:35:19 +01:00
final data = json.decode(res.body);
if (data['errors'] != null) {
2019-09-26 16:14:14 +02:00
throw data['errors'][0]['message'];
2019-02-07 07:35:19 +01:00
}
2019-02-10 12:15:50 +01:00
2019-02-07 07:35:19 +01:00
return data['data'];
}
2021-05-16 09:16:35 +02:00
String? _oauthState;
void redirectToGithubOauth([publicOnly = false]) {
_oauthState = nanoid();
final repoScope = publicOnly ? 'public_repo' : 'repo';
final scope = Uri.encodeComponent(
['user', repoScope, 'read:org', 'notifications'].join(','));
2022-06-26 08:23:50 +02:00
launchStringUrl(
2019-09-21 19:02:14 +02:00
'https://github.com/login/oauth/authorize?client_id=$clientId&redirect_uri=gittouch://login&scope=$scope&state=$_oauthState',
);
}
2020-05-01 12:05:49 +02:00
int _activeTab = 0;
int get activeTab => _activeTab;
Future<void> setActiveTab(int v) async {
_activeTab = v;
final prefs = await SharedPreferences.getInstance();
await prefs.setInt(
2021-05-16 09:16:35 +02:00
StorageKeys.getDefaultStartTabKey(activeAccount!.platform), v);
Fimber.d('write default start tab for ${activeAccount!.platform}: $v');
2020-05-01 12:05:49 +02:00
notifyListeners();
}
2019-01-30 07:46:18 +01:00
}