mirror of
https://github.com/feeddeck/feeddeck.git
synced 2026-05-02 20:37:58 -05:00
This commit introduces a new "profile-v2" edge function and deprecates the "profile-v1" edge function. We decided to provide a new edge function for all profile related operation to improve the handling of the different operation the edge function is responsible for. The main differences to the "profile-v1" functions are: - Pass the operation id within the request url instead of the body of the request. - Move the operations for accounts (e.g. GitHub) to seperate files, to improve the readability of the function. - Do not require the "sourceType" anymore, instead we are using the operation id and request method to determine the operation which should be executed - The request body is now only used for the data of the corresponding account and not for any other information like the operation id and source information.
311 lines
10 KiB
Dart
311 lines
10 KiB
Dart
import 'package:flutter/material.dart';
|
|
|
|
import 'package:flutter_markdown/flutter_markdown.dart';
|
|
import 'package:provider/provider.dart';
|
|
|
|
import 'package:feeddeck/models/source.dart';
|
|
import 'package:feeddeck/repositories/profile_repository.dart';
|
|
import 'package:feeddeck/utils/api_exception.dart';
|
|
import 'package:feeddeck/utils/constants.dart';
|
|
import 'package:feeddeck/utils/openurl.dart';
|
|
import 'package:feeddeck/widgets/general/elevated_button_progress_indicator.dart';
|
|
import 'package:feeddeck/widgets/settings/accounts/utils/settings_accounts_actions.dart';
|
|
import 'package:feeddeck/widgets/settings/accounts/utils/settings_accounts_item.dart';
|
|
|
|
const _helpText = '''
|
|
Provide a GitHub token to connect your GitHub account to your profile. You can
|
|
create a token on in the
|
|
[developer settings](https://github.com/settings/tokens) section in the GitHub
|
|
account settings.
|
|
|
|
The token needs the following permissions: **notifications**,
|
|
**read:discussion**, **read:org**, **read:project**, **read:user**, **repo**.
|
|
You can also use a token with less permissions, but then some features might not
|
|
work.
|
|
''';
|
|
|
|
/// The [SettingsAccountsGithub] widget implements the GitHub account section of
|
|
/// the settings page. Here the user can add / remove his GitHub account to his
|
|
/// profile.
|
|
class SettingsAccountsGithub extends StatelessWidget {
|
|
const SettingsAccountsGithub({super.key});
|
|
|
|
Future<void> _deleteAccount(BuildContext context) async {
|
|
try {
|
|
await Provider.of<ProfileRepository>(
|
|
context,
|
|
listen: false,
|
|
).githubDeleteAccount();
|
|
} catch (_) {}
|
|
}
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
ProfileRepository profile = Provider.of<ProfileRepository>(
|
|
context,
|
|
listen: true,
|
|
);
|
|
|
|
return SettingsAccountsItem(
|
|
name: FDSourceType.github.toLocalizedString(),
|
|
isConnected: profile.accountGithub,
|
|
onTap: profile.status == FDProfileStatus.uninitialized
|
|
? null
|
|
: () {
|
|
/// If the user has already connected his GitHub account, we show the
|
|
/// accounts so that a user can re-connect his GitHub account or delete
|
|
/// the stored token from his profile.
|
|
if (profile.accountGithub) {
|
|
showModalBottomSheet(
|
|
context: context,
|
|
isScrollControlled: true,
|
|
isDismissible: true,
|
|
useSafeArea: true,
|
|
elevation: 0,
|
|
backgroundColor: Colors.transparent,
|
|
constraints: const BoxConstraints(
|
|
maxWidth: Constants.centeredFormMaxWidth,
|
|
),
|
|
builder: (BuildContext context) {
|
|
return SettingsAccountsActions(
|
|
delete: () {
|
|
return _deleteAccount(context);
|
|
},
|
|
reconnect: () {
|
|
return showModalBottomSheet(
|
|
context: context,
|
|
isScrollControlled: true,
|
|
isDismissible: true,
|
|
useSafeArea: true,
|
|
backgroundColor: Colors.transparent,
|
|
shape: const RoundedRectangleBorder(
|
|
borderRadius: BorderRadius.vertical(
|
|
top: Radius.circular(Constants.spacingMiddle),
|
|
),
|
|
),
|
|
clipBehavior: Clip.antiAliasWithSaveLayer,
|
|
constraints: const BoxConstraints(
|
|
maxWidth: Constants.centeredFormMaxWidth,
|
|
),
|
|
builder: (BuildContext context) {
|
|
return const SettingsAccountsGithubAdd();
|
|
},
|
|
);
|
|
},
|
|
);
|
|
},
|
|
);
|
|
} else {
|
|
/// If the user has not yet connected his GitHub account, we show the
|
|
/// modal bottom sheet with the form to add a GitHub token to his
|
|
/// profile.
|
|
showModalBottomSheet(
|
|
context: context,
|
|
isScrollControlled: true,
|
|
isDismissible: true,
|
|
useSafeArea: true,
|
|
backgroundColor: Colors.transparent,
|
|
shape: const RoundedRectangleBorder(
|
|
borderRadius: BorderRadius.vertical(
|
|
top: Radius.circular(Constants.spacingMiddle),
|
|
),
|
|
),
|
|
clipBehavior: Clip.antiAliasWithSaveLayer,
|
|
constraints: const BoxConstraints(
|
|
maxWidth: Constants.centeredFormMaxWidth,
|
|
),
|
|
builder: (BuildContext context) {
|
|
return const SettingsAccountsGithubAdd();
|
|
},
|
|
);
|
|
}
|
|
},
|
|
);
|
|
}
|
|
}
|
|
|
|
/// The [SettingsAccountsGithubAdd] widget displays a modal bottom sheet with a
|
|
/// form which can be used by the user to provide an token for his GitHub
|
|
/// account. When the user clicks on "Add account" button we call the function
|
|
/// to add the provided token to his account.
|
|
class SettingsAccountsGithubAdd extends StatefulWidget {
|
|
const SettingsAccountsGithubAdd({super.key});
|
|
|
|
@override
|
|
State<SettingsAccountsGithubAdd> createState() =>
|
|
_SettingsAccountsGithubAddState();
|
|
}
|
|
|
|
class _SettingsAccountsGithubAddState extends State<SettingsAccountsGithubAdd> {
|
|
final _formKey = GlobalKey<FormState>();
|
|
final _tokenController = TextEditingController();
|
|
bool _isLoading = false;
|
|
String _error = '';
|
|
|
|
/// [_validateToken] validates the token provided via the [TextField] of the
|
|
/// [_tokenController]. The token field can not be empty.
|
|
String? _validateToken(String? value) {
|
|
if (value == null || value.isEmpty) {
|
|
return 'Token is required';
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
/// [_addAccount] adds the provided GitHub token to the users profile. When we
|
|
/// are able to add the token we close the modal bottom sheet. In case of an
|
|
/// error we show the error message to the user.
|
|
Future<void> _addAccount() async {
|
|
if (_formKey.currentState != null && _formKey.currentState!.validate()) {
|
|
setState(() {
|
|
_isLoading = true;
|
|
_error = '';
|
|
});
|
|
|
|
try {
|
|
await Provider.of<ProfileRepository>(
|
|
context,
|
|
listen: false,
|
|
).githubAddAccount(_tokenController.text);
|
|
|
|
setState(() {
|
|
_isLoading = false;
|
|
_error = '';
|
|
});
|
|
|
|
if (!mounted) return;
|
|
Navigator.of(context).pop();
|
|
} on ApiException catch (err) {
|
|
setState(() {
|
|
_isLoading = false;
|
|
_error = 'Failed to add account: ${err.message}';
|
|
});
|
|
} catch (err) {
|
|
setState(() {
|
|
_isLoading = false;
|
|
_error = 'Failed to add account: ${err.toString()}';
|
|
});
|
|
}
|
|
}
|
|
}
|
|
|
|
/// [_buildError] returns a widget to display the [_error] when it is not an
|
|
/// empty string.
|
|
Widget _buildError() {
|
|
if (_error != '') {
|
|
return Padding(
|
|
padding: const EdgeInsets.all(Constants.spacingMiddle),
|
|
child: Text(
|
|
_error,
|
|
style: const TextStyle(
|
|
color: Constants.error,
|
|
),
|
|
),
|
|
);
|
|
}
|
|
|
|
return Container();
|
|
}
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
return Scaffold(
|
|
appBar: AppBar(
|
|
automaticallyImplyLeading: false,
|
|
shape: const Border(
|
|
bottom: BorderSide(
|
|
color: Constants.dividerColor,
|
|
width: 1,
|
|
),
|
|
),
|
|
title: Text(
|
|
FDSourceType.github.toLocalizedString(),
|
|
),
|
|
actions: [
|
|
IconButton(
|
|
icon: const Icon(
|
|
Icons.close,
|
|
),
|
|
onPressed: () {
|
|
Navigator.of(context).pop();
|
|
},
|
|
),
|
|
],
|
|
),
|
|
body: Column(
|
|
children: [
|
|
Expanded(
|
|
child: Padding(
|
|
padding: const EdgeInsets.all(Constants.spacingMiddle),
|
|
child: SingleChildScrollView(
|
|
child: Form(
|
|
key: _formKey,
|
|
child: Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
MarkdownBody(
|
|
selectable: true,
|
|
data: _helpText,
|
|
onTapLink: (text, href, title) {
|
|
try {
|
|
if (href != null) {
|
|
openUrl(href);
|
|
}
|
|
} catch (_) {}
|
|
},
|
|
),
|
|
const SizedBox(
|
|
height: Constants.spacingMiddle,
|
|
),
|
|
TextFormField(
|
|
controller: _tokenController,
|
|
keyboardType: TextInputType.text,
|
|
autocorrect: false,
|
|
enableSuggestions: true,
|
|
maxLines: 1,
|
|
decoration: const InputDecoration(
|
|
border: OutlineInputBorder(),
|
|
labelText: 'Token',
|
|
),
|
|
validator: (value) => _validateToken(value),
|
|
onFieldSubmitted: (value) => _addAccount(),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
),
|
|
),
|
|
),
|
|
const SizedBox(
|
|
height: Constants.spacingSmall,
|
|
),
|
|
const Divider(
|
|
color: Constants.dividerColor,
|
|
height: 1,
|
|
thickness: 1,
|
|
),
|
|
_buildError(),
|
|
Padding(
|
|
padding: const EdgeInsets.all(Constants.spacingMiddle),
|
|
child: ElevatedButton.icon(
|
|
style: ElevatedButton.styleFrom(
|
|
maximumSize: const Size.fromHeight(
|
|
Constants.elevatedButtonSize,
|
|
),
|
|
minimumSize: const Size.fromHeight(
|
|
Constants.elevatedButtonSize,
|
|
),
|
|
),
|
|
label: const Text('Add Account'),
|
|
onPressed: _isLoading ? null : _addAccount,
|
|
icon: _isLoading
|
|
? const ElevatedButtonProgressIndicator()
|
|
: const Icon(Icons.add),
|
|
),
|
|
),
|
|
],
|
|
),
|
|
);
|
|
}
|
|
}
|