Add rules prompt when initially connecting to server

merge-requests/69/head
Hank Grabowski 2025-05-20 10:19:44 -04:00
rodzic 415aa4c44e
commit 5f1d579207
6 zmienionych plików z 228 dodań i 11 usunięć

Wyświetl plik

@ -48,6 +48,14 @@ class InstanceRules {
return 'InstanceRules{rules: $rules}';
}
String toListString() {
if (rules.isEmpty) {
return '';
}
return rules.entries.map((e) => '${e.key}: ${e.value}').join('\n');
}
@override
bool operator ==(Object other) =>
identical(this, other) ||

Wyświetl plik

@ -106,7 +106,7 @@ class CredentialSignin extends _$CredentialSignin {
FutureResult<Profile, ExecError> signIn(bool activateProfileOnSuccess) async {
final result =
await credentials.signIn(ref).andThenAsync((signedInCredentials) async {
await credentials.signIn(ref).andThenAsync((signedInCredentials) async {
ref.read(statusServiceProvider.notifier).setStatus(
'Getting user profile from ${signedInCredentials.serverName}');
return await ref.read(
@ -134,6 +134,8 @@ class CredentialSignin extends _$CredentialSignin {
ref.read(loggedOutProfilesProvider.notifier).remove(loginProfile);
await ref.read(secretsServiceProvider).addOrUpdateProfile(loginProfile);
await ref.read(connectionRepoInitProvider(loginProfile).future);
ref.read(
instanceRulesManagerProvider(loginProfile));
await ref
.read(instanceInfoManagerProvider(loginProfile).notifier)
.update();
@ -176,10 +178,14 @@ class ProfileManager extends _$ProfileManager {
.read(secretsServiceProvider)
.addOrUpdateProfile(profile.copyWithLoginUpdate(false));
if (ref.read(loggedInProfilesProvider).isNotEmpty) {
if (ref
.read(loggedInProfilesProvider)
.isNotEmpty) {
ref.read(activeProfileProvider.notifier).setActiveProfile(
ref.read(loggedInProfilesProvider).first,
);
ref
.read(loggedInProfilesProvider)
.first,
);
}
if (withNotification) {
@ -221,7 +227,7 @@ class AccountServicesInitializer extends _$AccountServicesInitializer {
}
final pr =
await ref.read(profileManagerProvider(p).notifier).signIn(false);
await ref.read(profileManagerProvider(p).notifier).signIn(false);
if (pr.isSuccess) {
final profile = pr.value;
if (profile.id.isNotEmpty && profile.id == lastActiveProfile) {
@ -232,14 +238,22 @@ class AccountServicesInitializer extends _$AccountServicesInitializer {
}
}
if (!ref.read(activeProfileProvider.notifier).hasActiveProfile &&
ref.read(loggedInProfilesProvider).isNotEmpty) {
final firstProfile = ref.read(loggedOutProfilesProvider).first;
if (!ref
.read(activeProfileProvider.notifier)
.hasActiveProfile &&
ref
.read(loggedInProfilesProvider)
.isNotEmpty) {
final firstProfile = ref
.read(loggedOutProfilesProvider)
.first;
ref.read(activeProfileProvider.notifier).setActiveProfile(firstProfile);
}
return Result.ok(
ref.read(activeProfileProvider.notifier).hasActiveProfile);
ref
.read(activeProfileProvider.notifier)
.hasActiveProfile);
});
initialized = true;

Wyświetl plik

@ -69,7 +69,7 @@ final activeProfileProvider = NotifierProvider<ActiveProfile, Profile>.internal(
);
typedef _$ActiveProfile = Notifier<Profile>;
String _$credentialSigninHash() => r'e9731ad5b916838122a2a86961af4cfe4bef57b5';
String _$credentialSigninHash() => r'9e82b7bcc5c1bc79fd18a2f65916669ed44760dd';
/// Copied from Dart SDK
class _SystemHash {

Wyświetl plik

@ -1,3 +1,5 @@
import 'dart:convert';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:logging/logging.dart';
import 'package:result_monad/result_monad.dart';
@ -9,6 +11,7 @@ import '../../models/instance_info.dart';
import '../../serializers/mastodon/instance_info_mastodon_extensions.dart';
import '../rp_provider_extension.dart';
import 'friendica_client_services.dart';
import 'network_services.dart';
part 'fediverse_instance_client_services.g.dart';
@ -69,3 +72,19 @@ Future<Result<InstanceRules, ExecError>> instanceRules(
.andThen((page) => instanceRulesfromJson(page.data))
.execErrorCastAsync();
}
@riverpod
Future<Result<InstanceRules, ExecError>> instanceRulesByServerName(
Ref ref,
String serverName,
) async {
_logger.finest(() => 'Getting $serverName rules info');
final url = Uri.parse('https://$serverName/api/v1/instance/rules');
final headers = ref.read(userAgentHeaderProvider);
final request = NetworkRequest(url, headers: headers);
return await ref
.read(httpGetProvider(request).future)
.transform((response) => response.map((data) => jsonDecode(data)))
.andThen((page) => instanceRulesfromJson(page.data))
.execErrorCastAsync();
}

Wyświetl plik

@ -573,5 +573,145 @@ class _InstanceRulesProviderElement
@override
Profile get profile => (origin as InstanceRulesProvider).profile;
}
String _$instanceRulesByServerNameHash() =>
r'54fadb3a1ee5527bde3ae8e9767b3fffe1fe0d77';
/// See also [instanceRulesByServerName].
@ProviderFor(instanceRulesByServerName)
const instanceRulesByServerNameProvider = InstanceRulesByServerNameFamily();
/// See also [instanceRulesByServerName].
class InstanceRulesByServerNameFamily
extends Family<AsyncValue<Result<InstanceRules, ExecError>>> {
/// See also [instanceRulesByServerName].
const InstanceRulesByServerNameFamily();
/// See also [instanceRulesByServerName].
InstanceRulesByServerNameProvider call(
String serverName,
) {
return InstanceRulesByServerNameProvider(
serverName,
);
}
@override
InstanceRulesByServerNameProvider getProviderOverride(
covariant InstanceRulesByServerNameProvider provider,
) {
return call(
provider.serverName,
);
}
static const Iterable<ProviderOrFamily>? _dependencies = null;
@override
Iterable<ProviderOrFamily>? get dependencies => _dependencies;
static const Iterable<ProviderOrFamily>? _allTransitiveDependencies = null;
@override
Iterable<ProviderOrFamily>? get allTransitiveDependencies =>
_allTransitiveDependencies;
@override
String? get name => r'instanceRulesByServerNameProvider';
}
/// See also [instanceRulesByServerName].
class InstanceRulesByServerNameProvider
extends AutoDisposeFutureProvider<Result<InstanceRules, ExecError>> {
/// See also [instanceRulesByServerName].
InstanceRulesByServerNameProvider(
String serverName,
) : this._internal(
(ref) => instanceRulesByServerName(
ref as InstanceRulesByServerNameRef,
serverName,
),
from: instanceRulesByServerNameProvider,
name: r'instanceRulesByServerNameProvider',
debugGetCreateSourceHash:
const bool.fromEnvironment('dart.vm.product')
? null
: _$instanceRulesByServerNameHash,
dependencies: InstanceRulesByServerNameFamily._dependencies,
allTransitiveDependencies:
InstanceRulesByServerNameFamily._allTransitiveDependencies,
serverName: serverName,
);
InstanceRulesByServerNameProvider._internal(
super._createNotifier, {
required super.name,
required super.dependencies,
required super.allTransitiveDependencies,
required super.debugGetCreateSourceHash,
required super.from,
required this.serverName,
}) : super.internal();
final String serverName;
@override
Override overrideWith(
FutureOr<Result<InstanceRules, ExecError>> Function(
InstanceRulesByServerNameRef provider)
create,
) {
return ProviderOverride(
origin: this,
override: InstanceRulesByServerNameProvider._internal(
(ref) => create(ref as InstanceRulesByServerNameRef),
from: from,
name: null,
dependencies: null,
allTransitiveDependencies: null,
debugGetCreateSourceHash: null,
serverName: serverName,
),
);
}
@override
AutoDisposeFutureProviderElement<Result<InstanceRules, ExecError>>
createElement() {
return _InstanceRulesByServerNameProviderElement(this);
}
@override
bool operator ==(Object other) {
return other is InstanceRulesByServerNameProvider &&
other.serverName == serverName;
}
@override
int get hashCode {
var hash = _SystemHash.combine(0, runtimeType.hashCode);
hash = _SystemHash.combine(hash, serverName.hashCode);
return _SystemHash.finish(hash);
}
}
@Deprecated('Will be removed in 3.0. Use Ref instead')
// ignore: unused_element
mixin InstanceRulesByServerNameRef
on AutoDisposeFutureProviderRef<Result<InstanceRules, ExecError>> {
/// The parameter `serverName` of this provider.
String get serverName;
}
class _InstanceRulesByServerNameProviderElement
extends AutoDisposeFutureProviderElement<Result<InstanceRules, ExecError>>
with InstanceRulesByServerNameRef {
_InstanceRulesByServerNameProviderElement(super.provider);
@override
String get serverName =>
(origin as InstanceRulesByServerNameProvider).serverName;
}
// ignore_for_file: type=lint
// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member, deprecated_member_use_from_same_package

Wyświetl plik

@ -11,7 +11,10 @@ import '../models/auth/basic_credentials.dart';
import '../models/auth/credentials_intf.dart';
import '../models/auth/oauth_credentials.dart';
import '../models/auth/profile.dart';
import '../models/instance_info.dart';
import '../riverpod_controllers/account_services.dart';
import '../riverpod_controllers/instance_info_services.dart';
import '../riverpod_controllers/networking/fediverse_instance_client_services.dart';
import '../routes.dart';
import '../utils/snackbar_builder.dart';
@ -276,6 +279,21 @@ class _SignInScreenState extends ConsumerState<SignInScreen> {
child: const Text('Signin'),
)
: const SizedBox(),
if (!signInButtonEnabled && existingProfile != null)
ElevatedButton(
onPressed: () async {
final rules = ref.read(
instanceRulesManagerProvider(existingProfile!));
if (context.mounted) {
await showConfirmDialog(
context,
rules.rules.isEmpty
? "Your server doesn't publish a list of rules we have access to programmatically."
: rules.toListString());
}
},
child: const Text('Show Rules'),
),
const VerticalPadding(),
Text(
'Logged out:',
@ -443,7 +461,24 @@ class _SignInScreenState extends ConsumerState<SignInScreen> {
return;
}
buildSnackbar(context, 'Attempting to sign in account...');
final rulesResult = await ref.read(
instanceRulesByServerNameProvider(serverNameController.text).future);
final rules = rulesResult.getValueOrElse(() => const InstanceRules());
final rulesPrompt =
'Do you agree to abide by the rules of the server you are connecting to as you did when you created the account?\n ${rules.toListString()}';
final confirmRules = await showYesNoDialog(context, rulesPrompt);
if (confirmRules != true) {
if (context.mounted) {
buildSnackbar(context,
'Cannot connect to a server unless you reaffirm you will follow the listed rules');
}
return;
}
if (context.mounted) {
buildSnackbar(context, 'Attempting to sign in account...');
}
final result =
await ref.read(credentialSigninProvider(creds).notifier).signIn(true);
@ -456,6 +491,7 @@ class _SignInScreenState extends ConsumerState<SignInScreen> {
buildSnackbar(context, 'Account signed in...');
}
ref.read(activeProfileProvider.notifier).setActiveProfile(result.value);
ref.read(instanceRulesManagerProvider(result.value));
if (context.mounted) {
context.goNamed(ScreenPaths.timelines);
}