-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Mastodon sync feature is implemented (#23)
* Update flutter.versionCode to 4 and flutter.versionName to 1.0.1 * update dio client with timeouts * update: add flutter_web_auth dependency * update: rename NoteSyncSettings to TelegramSyncSettings * add: implement Mastodon user account API and services * add mastodon config done * remove refreshToken from MastodonUserAccount * Add Mastodon sync type feature and improve settings UI
- Loading branch information
Showing
22 changed files
with
713 additions
and
21 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,29 @@ | ||
import 'package:dio/dio.dart'; | ||
import '../app_config.dart'; | ||
import '../dio_client.dart'; | ||
import '../entities/mastodon_application.dart'; | ||
|
||
class MastodonApplicationApi { | ||
static final Dio _dio = DioClient.getInstance(); | ||
|
||
Future<Response> createApplication(String instanceUrl) async { | ||
final options = Options( | ||
headers: {'AllowAnonymous': true}, | ||
); | ||
// register an application on the instance | ||
return await _dio.post('$instanceUrl/api/v1/apps', data: { | ||
'client_name': 'Happy Notes', | ||
'redirect_uris': AppConfig.mastodonRedirectUri(instanceUrl), | ||
'scopes': 'read write follow', | ||
'website': 'https://happynotes.shukebeta.com' | ||
}); | ||
} | ||
|
||
Future<Response> get(String instanceUrl) async { | ||
return await _dio.get('/mastodonApplication/get', queryParameters: {'instanceUrl': instanceUrl}); | ||
} | ||
|
||
Future<Response> save(MastodonApplication app) async { | ||
return await _dio.post('/mastodonApplication/save', data: app.toJson()); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,54 @@ | ||
import 'package:dio/dio.dart'; | ||
import 'package:happy_notes/entities/mastodon_user_account.dart'; | ||
import '../dio_client.dart'; | ||
|
||
class MastodonUserAccountApi { | ||
static final Dio _dio = DioClient.getInstance(); | ||
|
||
Future<Response> setState(String state) async { | ||
final options = Options( | ||
headers: {'X-State': state}, | ||
); | ||
// we don't have a separate api file for mastodon auth | ||
return await _dio.post('/mastodonAuth/setState', options: options); | ||
} | ||
|
||
Future<Response> getAll() async { | ||
return await _dio.get('/mastodonUserAccount/getAll'); | ||
} | ||
|
||
Future<Response> add(MastodonUserAccount account) async { | ||
var data = _getPostData(account); | ||
return await _dio.post('/mastodonUserAccount/add', data: data); | ||
} | ||
|
||
Future<Response> nextSyncType(MastodonUserAccount account) async { | ||
var data = _getPostData(account); | ||
return await _dio.post('/mastodonUserAccount/nextSyncType', data: data); | ||
} | ||
|
||
Future<Response> activate(MastodonUserAccount account) async { | ||
var data = _getPostData(account); | ||
return await _dio.post('/mastodonUserAccount/activate', data: data); | ||
} | ||
|
||
Map<String, Object?> _getPostData(MastodonUserAccount account) { | ||
return { | ||
'userId': account.userId, | ||
'instanceUrl': account.instanceUrl, | ||
'scope': account.scope, | ||
'accessToken': account.accessToken, | ||
'tokenType': account.tokenType, | ||
}; | ||
} | ||
|
||
Future<Response> disable(MastodonUserAccount account) async { | ||
var data = _getPostData(account); | ||
return await _dio.post('/mastodonUserAccount/disable', data: data); | ||
} | ||
|
||
Future<Response> delete(MastodonUserAccount account) async { | ||
var data = _getPostData(account); | ||
return await _dio.delete('/mastodonUserAccount/delete', data: data); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,63 @@ | ||
class MastodonApplication { | ||
int? id; | ||
final String instanceUrl; | ||
final int applicationId; | ||
final String clientId; | ||
final String clientSecret; | ||
final String redirectUri; | ||
final String scopes; | ||
final String name; | ||
final String website; | ||
int? createAt; | ||
int? updateAt; | ||
int? maxTootChars = 500; | ||
|
||
MastodonApplication({ | ||
this.id, | ||
required this.instanceUrl, | ||
required this.applicationId, | ||
required this.clientId, | ||
required this.clientSecret, | ||
required this.redirectUri, | ||
required this.scopes, | ||
required this.name, | ||
required this.website, | ||
this.createAt, | ||
this.updateAt, | ||
this.maxTootChars, | ||
}); | ||
|
||
factory MastodonApplication.fromJson(Map<String, dynamic> json) { | ||
return MastodonApplication( | ||
id: json['id'], | ||
instanceUrl: json['instanceUrl'], | ||
applicationId: json['applicationId'], | ||
clientId: json['clientId'], | ||
clientSecret: json['clientSecret'], | ||
maxTootChars: json['maxTootChars'], | ||
redirectUri: json['redirectUri'], | ||
scopes: json['scopes'], | ||
name: json['name'], | ||
website: json['website'], | ||
createAt: json['createAt'], | ||
updateAt: json['updateAt'], | ||
); | ||
} | ||
|
||
Map<String, dynamic> toJson() { | ||
final Map<String, dynamic> data = <String, dynamic>{}; | ||
data['id'] = id; | ||
data['instanceUrl'] = instanceUrl; | ||
data['applicationId'] = applicationId; | ||
data['clientId'] = clientId; | ||
data['clientSecret'] = clientSecret; | ||
data['maxTootChars'] = maxTootChars; | ||
data['redirectUri'] = redirectUri; | ||
data['scopes'] = scopes; | ||
data['name'] = name; | ||
data['website'] = website; | ||
data['createAt'] = createAt; | ||
data['updateAt'] = updateAt; | ||
return data; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,63 @@ | ||
class MastodonUserAccount { | ||
int? id; | ||
int? userId; | ||
final int? status; | ||
final int syncType; | ||
final String? instanceUrl; | ||
final String? scope; | ||
|
||
String accessToken; | ||
String tokenType; | ||
String? statusText = ''; | ||
|
||
String? get syncTypeText { | ||
switch (syncType) { | ||
case 1: | ||
return 'All'; | ||
case 2: | ||
return 'Public note only'; | ||
case 3: | ||
return 'Note with tag Mastodon only'; | ||
default: | ||
return 'Unknown'; | ||
} | ||
} | ||
|
||
bool get isActive { | ||
return statusText == 'Normal'; | ||
} | ||
|
||
bool get isDisabled { | ||
return statusText == 'Disabled' || (statusText ?? '').contains('Inactive'); | ||
} | ||
|
||
bool get isTested { | ||
return statusText == 'Created' || statusText == 'Normal' || statusText == 'Disabled'; | ||
} | ||
|
||
MastodonUserAccount({ | ||
this.id, | ||
this.userId, | ||
required this.instanceUrl, | ||
required this.scope, | ||
required this.accessToken, | ||
required this.tokenType, | ||
required this.syncType, | ||
this.status, | ||
this.statusText, | ||
}); | ||
|
||
factory MastodonUserAccount.fromJson(Map<String, dynamic> json) { | ||
return MastodonUserAccount( | ||
id: json['id'], | ||
userId: json['userId'], | ||
instanceUrl: json['instanceUrl'], | ||
scope: json['scope'], | ||
accessToken: json['accessToken'], | ||
tokenType: json['tokenType'], | ||
status: json['status'], | ||
syncType: json['syncType'], | ||
statusText: json['statusText'], | ||
); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,69 @@ | ||
import 'package:flutter/material.dart'; | ||
import 'package:happy_notes/entities/mastodon_user_account.dart'; | ||
import '../../dependency_injection.dart'; | ||
import '../../services/mastodon_service.dart'; | ||
import '../../utils/util.dart'; | ||
|
||
class AddMastodonUserAccount extends StatefulWidget { | ||
final MastodonUserAccount? setting; | ||
|
||
const AddMastodonUserAccount({super.key, this.setting}); | ||
|
||
@override | ||
AddMastodonUserAccountState createState() => AddMastodonUserAccountState(); | ||
} | ||
|
||
class AddMastodonUserAccountState extends State<AddMastodonUserAccount> { | ||
final _mastodonService = locator<MastodonService>(); | ||
final TextEditingController _instanceController = TextEditingController(text: 'https://mastodon.social'); | ||
|
||
@override | ||
Widget build(BuildContext context) { | ||
return Scaffold( | ||
appBar: AppBar( | ||
title: const Text('Add Sync Setting - Mastodon'), | ||
), | ||
body: Padding( | ||
padding: const EdgeInsets.all(16.0), | ||
child: Column( | ||
crossAxisAlignment: CrossAxisAlignment.stretch, | ||
children: [ | ||
TextField( | ||
controller: _instanceController, | ||
decoration: const InputDecoration( | ||
labelText: 'Mastodon Instance URL', | ||
hintText: 'https://mastodon.social', | ||
), | ||
), | ||
const SizedBox(height: 16), | ||
ElevatedButton( | ||
onPressed: () async { | ||
await _initializeAuthorization(context); | ||
}, | ||
child: const Text('Authorize'), | ||
), | ||
], | ||
), | ||
), | ||
); | ||
} | ||
|
||
Future<void> _initializeAuthorization(BuildContext context) async { | ||
var scaffoldMessengerState = ScaffoldMessenger.of(context); | ||
var navigator = Navigator.of(context); | ||
try { | ||
var instanceUrl = _instanceController.text.toLowerCase().trim(); | ||
if (!instanceUrl.startsWith('http://') && !instanceUrl.startsWith('https://')) { | ||
instanceUrl = 'https://$instanceUrl'; | ||
} | ||
if (instanceUrl.endsWith('/')) { | ||
instanceUrl = instanceUrl.substring(0, instanceUrl.length - 1); | ||
} | ||
await _mastodonService.authorize(instanceUrl); | ||
Util.showInfo(scaffoldMessengerState, 'Authorization successful'); | ||
navigator.pop(); | ||
} catch (e) { | ||
Util.showError(scaffoldMessengerState, 'Authorization failed: $e'); | ||
} | ||
} | ||
} |
Oops, something went wrong.