Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Account improvements and anonymous instances #677

Merged
merged 13 commits into from
Sep 21, 2023
Merged
Show file tree
Hide file tree
Changes from 10 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
- Automatically save drafts for posts and comments - contribution from @micahmo
- Highlight the currently selected page in the navigation drawer - contribution from @micahmo
- Newly created comments get inserted into comment list correctly without losing your scroll position. If comment is top level, the list scrolls to your comment. The comment also gets highlighted - contribution from @ajsosa
- Improved account switching and added anonymous browsing mode for any intance - contribution from @micahmo

### Changed

Expand Down
8 changes: 7 additions & 1 deletion lib/account/bloc/account_bloc.dart
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,13 @@ class AccountBloc extends Bloc<AccountEvent, AccountState> {
throw Exception('Error: Timeout when attempting to fetch account details');
});

return emit(state.copyWith(status: AccountStatus.success, subsciptions: subsciptions, personView: fullPersonView.personView));
// This eliminates an issue which has plagued me a lot which is that there's a race condition
// with so many calls to GetAccountInformation, we can return success for the new and old account.
if (fullPersonView.personView.person.id == (await fetchActiveProfileAccount())?.userId) {
return emit(state.copyWith(status: AccountStatus.success, subsciptions: subsciptions, personView: fullPersonView.personView));
} else {
return emit(state.copyWith(status: AccountStatus.success));
}
} catch (e) {
exception = e;
attemptCount++;
Expand Down
8 changes: 6 additions & 2 deletions lib/account/pages/account_page.dart
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,9 @@ import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:thunder/account/bloc/account_bloc.dart';
import 'package:thunder/account/utils/profiles.dart';
import 'package:thunder/core/auth/bloc/auth_bloc.dart';
import 'package:thunder/thunder/bloc/thunder_bloc.dart';
import 'package:thunder/user/pages/user_page.dart';
import 'package:flutter_gen/gen_l10n/app_localizations.dart';

class AccountPage extends StatefulWidget {
const AccountPage({super.key});
Expand All @@ -21,6 +23,7 @@ class _AccountPageState extends State<AccountPage> {

AuthState authState = context.read<AuthBloc>().state;
AccountState accountState = context.read<AccountBloc>().state;
String anonymousInstance = context.watch<ThunderBloc>().state.currentAnonymousInstance;

return MultiBlocListener(
listeners: [
Expand All @@ -46,11 +49,12 @@ class _AccountPageState extends State<AccountPage> {
children: [
Icon(Icons.people_rounded, size: 100, color: theme.dividerColor),
const SizedBox(height: 16),
const Text('Add an account to see your profile', textAlign: TextAlign.center),
Text(AppLocalizations.of(context)!.browsingAnonymously(anonymousInstance), textAlign: TextAlign.center),
Text(AppLocalizations.of(context)!.addAccountToSeeProfile, textAlign: TextAlign.center),
const SizedBox(height: 24.0),
ElevatedButton(
style: ElevatedButton.styleFrom(minimumSize: const Size.fromHeight(60)),
child: const Text('Manage Accounts'),
child: Text(AppLocalizations.of(context)!.manageAccounts),
onPressed: () => showProfileModalSheet(context),
)
],
Expand Down
165 changes: 98 additions & 67 deletions lib/account/pages/login_page.dart
Original file line number Diff line number Diff line change
Expand Up @@ -6,17 +6,22 @@ import 'package:flutter/services.dart';

import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:go_router/go_router.dart';
import 'package:shared_preferences/shared_preferences.dart';

import 'package:thunder/core/auth/bloc/auth_bloc.dart';
import 'package:thunder/core/enums/local_settings.dart';
import 'package:thunder/core/singletons/preferences.dart';
import 'package:thunder/shared/snackbar.dart';
import 'package:thunder/thunder/bloc/thunder_bloc.dart';
import 'package:thunder/utils/instance.dart';
import 'package:thunder/utils/text_input_formatter.dart';
import 'package:flutter_gen/gen_l10n/app_localizations.dart';

class LoginPage extends StatefulWidget {
final VoidCallback popRegister;
final bool anonymous;

const LoginPage({super.key, required this.popRegister});
const LoginPage({super.key, required this.popRegister, this.anonymous = false});

@override
State<LoginPage> createState() => _LoginPageState();
Expand Down Expand Up @@ -48,15 +53,15 @@ class _LoginPageState extends State<LoginPage> {
_instanceTextEditingController = TextEditingController();

_usernameTextEditingController.addListener(() {
if (_usernameTextEditingController.text.isNotEmpty && _passwordTextEditingController.text.isNotEmpty && _instanceTextEditingController.text.isNotEmpty) {
if (_instanceTextEditingController.text.isNotEmpty && (widget.anonymous || (_usernameTextEditingController.text.isNotEmpty && _passwordTextEditingController.text.isNotEmpty))) {
setState(() => fieldsFilledIn = true);
} else {
setState(() => fieldsFilledIn = false);
}
});

_passwordTextEditingController.addListener(() {
if (_usernameTextEditingController.text.isNotEmpty && _passwordTextEditingController.text.isNotEmpty && _instanceTextEditingController.text.isNotEmpty) {
if (_instanceTextEditingController.text.isNotEmpty && (widget.anonymous || (_usernameTextEditingController.text.isNotEmpty && _passwordTextEditingController.text.isNotEmpty))) {
setState(() => fieldsFilledIn = true);
} else {
setState(() => fieldsFilledIn = false);
Expand All @@ -69,7 +74,7 @@ class _LoginPageState extends State<LoginPage> {
currentInstance = _instanceTextEditingController.text;
}

if (_usernameTextEditingController.text.isNotEmpty && _passwordTextEditingController.text.isNotEmpty && _instanceTextEditingController.text.isNotEmpty) {
if (_instanceTextEditingController.text.isNotEmpty && (widget.anonymous || (_usernameTextEditingController.text.isNotEmpty && _passwordTextEditingController.text.isNotEmpty))) {
setState(() => fieldsFilledIn = true);
} else {
setState(() => fieldsFilledIn = false);
Expand Down Expand Up @@ -132,7 +137,7 @@ class _LoginPageState extends State<LoginPage> {
});

showSnackbar(context, AppLocalizations.of(context)!.loginFailed(state.errorMessage ?? AppLocalizations.of(context)!.missingErrorMessage));
} else if (state.status == AuthStatus.success) {
} else if (state.status == AuthStatus.success && context.read<AuthBloc>().state.isLoggedIn) {
context.pop();

showSnackbar(context, AppLocalizations.of(context)!.loginSucceeded);
Expand Down Expand Up @@ -182,74 +187,79 @@ class _LoginPageState extends State<LoginPage> {
errorMaxLines: 2,
),
enableSuggestions: false,
onSubmitted: (_instanceTextEditingController.text.isNotEmpty && widget.anonymous) ? (_) => _addAnonymousInstance() : null,
),
const SizedBox(height: 35.0),
AutofillGroup(
child: Column(
children: <Widget>[
TextField(
textInputAction: TextInputAction.next,
keyboardType: TextInputType.url,
autocorrect: false,
controller: _usernameTextEditingController,
autofillHints: const [AutofillHints.username],
decoration: const InputDecoration(
isDense: true,
border: OutlineInputBorder(),
labelText: 'Username',
if (!widget.anonymous) ...[
const SizedBox(height: 35.0),
AutofillGroup(
child: Column(
children: <Widget>[
TextField(
textInputAction: TextInputAction.next,
keyboardType: TextInputType.url,
autocorrect: false,
controller: _usernameTextEditingController,
autofillHints: const [AutofillHints.username],
decoration: const InputDecoration(
isDense: true,
border: OutlineInputBorder(),
labelText: 'Username',
),
enableSuggestions: false,
),
enableSuggestions: false,
),
const SizedBox(height: 12.0),
TextField(
onSubmitted:
(!isLoading && _passwordTextEditingController.text.isNotEmpty && _passwordTextEditingController.text.isNotEmpty && _instanceTextEditingController.text.isNotEmpty)
? (_) => _handleLogin()
: null,
autocorrect: false,
controller: _passwordTextEditingController,
obscureText: !showPassword,
enableSuggestions: false,
maxLength: 60, // This is what lemmy retricts password length to
autofillHints: const [AutofillHints.password],
decoration: InputDecoration(
isDense: true,
border: const OutlineInputBorder(),
labelText: 'Password',
suffixIcon: Padding(
padding: const EdgeInsets.symmetric(horizontal: 4.0),
child: IconButton(
icon: Icon(
showPassword ? Icons.visibility_rounded : Icons.visibility_off_rounded,
semanticLabel: showPassword ? 'Hide Password' : 'Show Password',
const SizedBox(height: 12.0),
TextField(
onSubmitted:
(!isLoading && _passwordTextEditingController.text.isNotEmpty && _passwordTextEditingController.text.isNotEmpty && _instanceTextEditingController.text.isNotEmpty)
? (_) => _handleLogin()
: (_instanceTextEditingController.text.isNotEmpty && widget.anonymous)
? (_) => _addAnonymousInstance()
: null,
autocorrect: false,
controller: _passwordTextEditingController,
obscureText: !showPassword,
enableSuggestions: false,
maxLength: 60, // This is what lemmy retricts password length to
autofillHints: const [AutofillHints.password],
decoration: InputDecoration(
isDense: true,
border: const OutlineInputBorder(),
labelText: 'Password',
suffixIcon: Padding(
padding: const EdgeInsets.symmetric(horizontal: 4.0),
child: IconButton(
icon: Icon(
showPassword ? Icons.visibility_rounded : Icons.visibility_off_rounded,
semanticLabel: showPassword ? 'Hide Password' : 'Show Password',
),
onPressed: () {
setState(() {
showPassword = !showPassword;
});
},
),
onPressed: () {
setState(() {
showPassword = !showPassword;
});
},
),
),
),
),
],
],
),
),
),
const SizedBox(height: 12.0),
TextField(
autocorrect: false,
controller: _totpTextEditingController,
maxLength: 6,
keyboardType: TextInputType.number,
inputFormatters: <TextInputFormatter>[FilteringTextInputFormatter.digitsOnly],
decoration: const InputDecoration(
isDense: true,
border: OutlineInputBorder(),
labelText: 'TOTP (optional)',
hintText: '000000',
const SizedBox(height: 12.0),
TextField(
autocorrect: false,
controller: _totpTextEditingController,
maxLength: 6,
keyboardType: TextInputType.number,
inputFormatters: <TextInputFormatter>[FilteringTextInputFormatter.digitsOnly],
decoration: const InputDecoration(
isDense: true,
border: OutlineInputBorder(),
labelText: 'TOTP (optional)',
hintText: '000000',
),
enableSuggestions: false,
),
enableSuggestions: false,
),
],
const SizedBox(height: 12.0),
const SizedBox(height: 32.0),
ElevatedButton(
Expand All @@ -262,8 +272,11 @@ class _LoginPageState extends State<LoginPage> {
),
onPressed: (!isLoading && _passwordTextEditingController.text.isNotEmpty && _passwordTextEditingController.text.isNotEmpty && _instanceTextEditingController.text.isNotEmpty)
? _handleLogin
: null,
child: Text('Login', style: theme.textTheme.titleMedium?.copyWith(color: !isLoading && fieldsFilledIn ? theme.colorScheme.onPrimary : theme.colorScheme.primary)),
: (_instanceTextEditingController.text.isNotEmpty && widget.anonymous)
? () => _addAnonymousInstance()
: null,
child: Text(widget.anonymous ? AppLocalizations.of(context)!.add : AppLocalizations.of(context)!.login,
style: theme.textTheme.titleMedium?.copyWith(color: !isLoading && fieldsFilledIn ? theme.colorScheme.onPrimary : theme.colorScheme.primary)),
),
TextButton(
style: ElevatedButton.styleFrom(minimumSize: const Size.fromHeight(60)),
Expand Down Expand Up @@ -292,4 +305,22 @@ class _LoginPageState extends State<LoginPage> {
),
);
}

void _addAnonymousInstance() async {
if (await isLemmyInstance(_instanceTextEditingController.text)) {
final SharedPreferences prefs = (await UserPreferences.instance).sharedPreferences;
List<String> anonymousInstances = prefs.getStringList(LocalSettings.anonymousInstances.name) ?? ['lemmy.ml'];
if (anonymousInstances.contains(_instanceTextEditingController.text)) {
setState(() {
instanceValidated = false;
instanceError = AppLocalizations.of(context)!.instanceHasAlreadyBenAdded(currentInstance ?? '');
});
} else {
context.read<AuthBloc>().add(LogOutOfAllAccounts());
context.read<ThunderBloc>().add(OnAddAnonymousInstance(_instanceTextEditingController.text));
context.read<ThunderBloc>().add(OnSetCurrentAnonymousInstance(_instanceTextEditingController.text));
widget.popRegister();
}
}
}
}
Loading