Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
61 changes: 45 additions & 16 deletions lib/src/app/shell/navigation/navigation_settings.dart
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,6 @@ part of 'navigation_utils.dart';
///
/// Additionally, the [settingToHighlight] parameter can be used to highlight a specific setting when the page is opened.
void navigateToSettingPage(BuildContext context, LocalSettings setting, {LocalSettings? settingToHighlight}) {
final routeScope = resolveAccountAwareRouteScope(context, useActiveAccount: true, includeThunderCubit: true);
final account = routeScope.account;

final gestureCubit = context.read<GesturePreferencesCubit>();
final themeCubit = context.read<ThemePreferencesCubit>();
final reduceAnimations = themeCubit.state.reduceAnimations;
final enableFullScreenSwipeNavigationGesture = gestureCubit.state.enableFullScreenSwipeNavigationGesture;

String pageToNav = {
LocalSettingsCategories.posts: SETTINGS_APPEARANCE_POSTS_PAGE,
LocalSettingsCategories.comments: SETTINGS_APPEARANCE_COMMENTS_PAGE,
Expand All @@ -33,6 +25,36 @@ void navigateToSettingPage(BuildContext context, LocalSettings setting, {LocalSe
}[setting.category] ??
SETTINGS_GENERAL_PAGE;

final usesEffectiveAccount = {
SETTINGS_ACCOUNT_PAGE,
SETTINGS_ACCOUNT_LANGUAGES_PAGE,
SETTINGS_ACCOUNT_BLOCKLIST_PAGE,
SETTINGS_ACCOUNT_MEDIA_PAGE,
SETTINGS_USER_LABELS_PAGE,
}.contains(pageToNav);

final routeScope = resolveAccountAwareRouteScope(
context,
useActiveAccount: !usesEffectiveAccount,
includeThunderCubit: true,
);

final account = routeScope.account;

FeatureAccountCubit? inheritedFeatureAccountCubit;
if (usesEffectiveAccount) {
try {
inheritedFeatureAccountCubit = context.read<FeatureAccountCubit>();
} catch (_) {
inheritedFeatureAccountCubit = null;
}
}

final gestureCubit = context.read<GesturePreferencesCubit>();
final themeCubit = context.read<ThemePreferencesCubit>();
final reduceAnimations = themeCubit.state.reduceAnimations;
final enableFullScreenSwipeNavigationGesture = gestureCubit.state.enableFullScreenSwipeNavigationGesture;

if (pageToNav == SETTINGS_ABOUT_PAGE) {
Navigator.of(context).push(
SwipeablePageRoute(
Expand Down Expand Up @@ -82,13 +104,19 @@ void navigateToSettingPage(BuildContext context, LocalSettings setting, {LocalSe
),
);
} else {
final needsAccountSettingsCubit = pageToNav == SETTINGS_ACCOUNT_PAGE || pageToNav == SETTINGS_ACCOUNT_LANGUAGES_PAGE;
final hasAccountSettingsCubit = needsAccountSettingsCubit && context.findAncestorWidgetOfExactType<BlocProvider<AccountSettingsCubit>>() != null;
final accountSettingsCubit = !needsAccountSettingsCubit
? null
: hasAccountSettingsCubit
? context.read<AccountSettingsCubit>()
: createAccountSettingsCubit(account, initialSiteResponse: routeScope.profileBloc?.state.siteResponse);
final needsAccountSettingsCubit = pageToNav == SETTINGS_ACCOUNT_LANGUAGES_PAGE;
AccountSettingsCubit? inheritedAccountSettingsCubit;

if (needsAccountSettingsCubit) {
try {
inheritedAccountSettingsCubit = context.read<AccountSettingsCubit>();
} catch (_) {
inheritedAccountSettingsCubit = null;
}
}

final accountSettingsCubit =
!needsAccountSettingsCubit ? null : inheritedAccountSettingsCubit ?? createAccountSettingsCubit(account, initialSiteResponse: routeScope.profileBloc?.state.siteResponse);

Navigator.of(context).push(
SwipeablePageRoute(
Expand All @@ -98,8 +126,9 @@ void navigateToSettingPage(BuildContext context, LocalSettings setting, {LocalSe
builder: (context) => MultiBlocProvider(
providers: routeScope.providers(
provideThunderCubit: true,
provideFeatureAccountCubit: pageToNav != SETTINGS_ACCOUNT_LANGUAGES_PAGE,
provideFeatureAccountCubit: inheritedFeatureAccountCubit == null,
extraProviders: [
if (inheritedFeatureAccountCubit != null) BlocProvider<FeatureAccountCubit>.value(value: inheritedFeatureAccountCubit),
if (accountSettingsCubit != null) BlocProvider<AccountSettingsCubit>.value(value: accountSettingsCubit),
],
),
Expand Down
1 change: 1 addition & 0 deletions lib/src/features/account/account.dart
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,6 @@ export 'presentation/widgets/widgets.dart';
export 'presentation/utils/profile_utils.dart';
export 'package:thunder/src/foundation/contracts/account.dart';
export 'domain/models/account_media.dart';
export 'domain/models/account_settings_update.dart';
export 'domain/repositories/account_repository.dart';
export 'data/repositories/account_repository_impl.dart';
Original file line number Diff line number Diff line change
Expand Up @@ -58,37 +58,11 @@ class AccountRepositoryImpl implements AccountRepository {
}

@override
Future<void> saveSettings({
String? bio,
String? email,
String? matrixUserId,
String? displayName,
FeedListType? defaultFeedListType,
PostSortType? defaultPostSortType,
bool? showNsfw,
bool? showReadPosts,
bool? showScores,
bool? botAccount,
bool? showBotAccounts,
List<int>? discussionLanguages,
}) async {
Future<void> saveSettings(AccountSettingsUpdate update) async {
final l10n = _localizationService.l10n;
if (account.anonymous) throw Exception(l10n.userNotLoggedIn);

await _api.saveUserSettings(
bio: bio,
email: email,
matrixUserId: matrixUserId,
displayName: displayName,
defaultFeedListType: defaultFeedListType,
defaultPostSortType: defaultPostSortType,
showNsfw: showNsfw,
showReadPosts: showReadPosts,
showScores: showScores,
botAccount: botAccount,
showBotAccounts: showBotAccounts,
discussionLanguages: discussionLanguages,
);
await _api.saveUserSettings(update);
}

@override
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import 'package:thunder/src/foundation/primitives/primitives.dart';

class AccountSettingsUpdate {
const AccountSettingsUpdate({
this.displayName,
this.bio,
this.defaultFeedListType,
this.defaultPostSortType,
this.showNsfw,
this.showNsfl,
this.showReadPosts,
this.showBotAccounts,
this.discussionLanguages,
});

/// The user's display name.
final String? displayName;

/// The user's bio.
final String? bio;

/// The user's default feed list type.
final FeedListType? defaultFeedListType;

/// The user's default post sort type.
final PostSortType? defaultPostSortType;

/// Whether to show NSFW content.
final bool? showNsfw;

/// Whether to show NSFL content.
final bool? showNsfl;

/// Whether to show read posts.
final bool? showReadPosts;

/// Whether to show bot accounts.
final bool? showBotAccounts;

/// The user's discussion languages.
final List<int>? discussionLanguages;
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import 'package:thunder/src/foundation/primitives/primitives.dart';
import 'package:thunder/src/features/account/domain/models/account_settings_update.dart';
import 'package:thunder/src/features/account/domain/models/account_media.dart';

//// Interface for an account repository
Expand All @@ -13,20 +14,7 @@ abstract class AccountRepository {
Future<AccountMedia> media({int? page, int? limit});

/// Saves the user's settings.
Future<void> saveSettings({
String? bio,
String? email,
String? matrixUserId,
String? displayName,
FeedListType? defaultFeedListType,
PostSortType? defaultPostSortType,
bool? showNsfw,
bool? showReadPosts,
bool? showScores,
bool? botAccount,
bool? showBotAccounts,
List<int>? discussionLanguages,
});
Future<void> saveSettings(AccountSettingsUpdate update);

/// Imports the settings to the user's profile.
Future<bool> importSettings(String settings);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,11 @@ import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:thunder/src/foundation/primitives/primitives.dart';
import 'package:thunder/l10n/generated/app_localizations.dart';

import 'package:thunder/src/features/account/account.dart';
import 'package:thunder/src/features/session/api.dart';
import 'package:thunder/src/shared/input_dialogs.dart';
import 'package:thunder/src/features/user/user.dart';
import 'package:thunder/src/foundation/config/config.dart';
import 'package:thunder/packages/ui/ui.dart' show showSnackbar, showThunderDialog;
import 'package:thunder/packages/ui/ui.dart' show showThunderDialog;

class DiscussionLanguageSelector extends StatefulWidget {
const DiscussionLanguageSelector({super.key});
Expand All @@ -24,90 +24,91 @@ class _DiscussionLanguageSelector extends State<DiscussionLanguageSelector> {
final theme = Theme.of(context);
final l10n = AppLocalizations.of(context)!;

return BlocConsumer<AccountSettingsCubit, AccountSettingsState>(
listener: (context, state) {
if (state.status == AccountSettingsStatus.failure) {
showSnackbar(state.errorMessage ?? l10n.unexpectedError);
} else if (state.status == AccountSettingsStatus.success) {
context.read<ProfileBloc>().add(FetchProfileSettings());
}
},
builder: (context, state) {
final languages = state.siteResponse?.allLanguages ?? const <ThunderLanguage>[];
final selectedLanguages = state.siteResponse?.myUser?.discussionLanguages ?? [];
final discussionLanguages = selectedLanguages.map((id) => languages.firstWhere((language) => language.id == id)).toList();
return AccountSettingsListener(
child: BlocBuilder<AccountSettingsCubit, AccountSettingsState>(
builder: (context, state) {
final updating = state.status == AccountSettingsStatus.updating;

return Scaffold(
floatingActionButton: FloatingActionButton(
onPressed: () => showLanguageInputDialog(
context,
title: l10n.addDiscussionLanguage,
account: context.read<ProfileBloc>().state.account,
excludedLanguageIds: [-1],
suggestions: languages,
onLanguageSelected: (language) {
List<ThunderLanguage> updatedDiscussionLanguages = List.from(discussionLanguages)..add(language);
context.read<AccountSettingsCubit>().updateSettings(discussionLanguages: updatedDiscussionLanguages.map((e) => e.id).toList());
},
final languages = state.siteResponse?.allLanguages ?? const <ThunderLanguage>[];
final selectedLanguages = state.siteResponse?.myUser?.discussionLanguages ?? [];
final discussionLanguages = selectedLanguages.map((id) => languages.firstWhere((language) => language.id == id)).toList();

return Scaffold(
floatingActionButton: FloatingActionButton(
onPressed: updating
? null
: () => showLanguageInputDialog(
context,
title: l10n.addDiscussionLanguage,
account: resolveEffectiveAccount(context),
excludedLanguageIds: [-1],
suggestions: languages,
onLanguageSelected: (language) {
List<ThunderLanguage> updatedDiscussionLanguages = List.from(discussionLanguages)..add(language);
context.read<AccountSettingsCubit>().updateSettings(discussionLanguages: updatedDiscussionLanguages.map((e) => e.id).toList());
},
),
child: const Icon(Icons.add_rounded),
),
child: const Icon(Icons.add_rounded),
),
body: CustomScrollView(
slivers: [
SliverAppBar(
pinned: true,
floating: true,
centerTitle: false,
toolbarHeight: APP_BAR_HEIGHT,
scrolledUnderElevation: 0.0,
title: Text(l10n.discussionLanguages),
),
if (discussionLanguages.isEmpty)
SliverToBoxAdapter(
child: Padding(
padding: const EdgeInsets.only(left: 28.0, right: 20.0, bottom: 20.0),
child: Text(
l10n.noDiscussionLanguages,
style: TextStyle(color: theme.hintColor),
body: CustomScrollView(
slivers: [
SliverAppBar(
pinned: true,
floating: true,
centerTitle: false,
toolbarHeight: APP_BAR_HEIGHT,
scrolledUnderElevation: 0.0,
title: Text(l10n.discussionLanguages),
),
if (discussionLanguages.isEmpty)
SliverToBoxAdapter(
child: Padding(
padding: const EdgeInsets.only(left: 28.0, right: 20.0, bottom: 20.0),
child: Text(
l10n.noDiscussionLanguages,
style: TextStyle(color: theme.hintColor),
),
),
),
),
SliverList.builder(
itemCount: discussionLanguages.length,
itemBuilder: (context, index) => ListTile(
contentPadding: const EdgeInsetsDirectional.only(start: 20.0, end: 12.0),
title: Text(discussionLanguages[index].name, overflow: TextOverflow.ellipsis),
trailing: IconButton(
icon: Icon(Icons.clear, semanticLabel: l10n.remove),
onPressed: () {
// Warn user against removing 'Undetermined' language when other discussion languages are selected.
// This filters out most content. If no discussion languages are selected, all content is displayed.
if (discussionLanguages[index].id == 0 && discussionLanguages.length > 1) {
showThunderDialog(
context: context,
title: l10n.warning,
contentText: l10n.deselectUndeterminedWarning,
primaryButtonText: l10n.remove,
onPrimaryButtonPressed: (dialogContext, setPrimaryButtonEnabled) {
final updatedDiscussionLanguages = discussionLanguages.where((element) => element != discussionLanguages[index]).toList();
context.read<AccountSettingsCubit>().updateSettings(discussionLanguages: updatedDiscussionLanguages.map((e) => e.id).toList());
Navigator.of(dialogContext).pop();
},
secondaryButtonText: l10n.cancel,
onSecondaryButtonPressed: (dialogContext) => Navigator.of(dialogContext).pop(),
);
} else {
final updatedDiscussionLanguages = discussionLanguages.where((element) => element != discussionLanguages[index]).toList();
context.read<AccountSettingsCubit>().updateSettings(discussionLanguages: updatedDiscussionLanguages.map((e) => e.id).toList());
}
},
SliverList.builder(
itemCount: discussionLanguages.length,
itemBuilder: (context, index) => ListTile(
contentPadding: const EdgeInsetsDirectional.only(start: 20.0, end: 12.0),
title: Text(discussionLanguages[index].name, overflow: TextOverflow.ellipsis),
trailing: IconButton(
icon: Icon(Icons.clear, semanticLabel: l10n.remove),
onPressed: updating
? null
: () {
// Warn user against removing 'Undetermined' language when other discussion languages are selected.
// This filters out most content. If no discussion languages are selected, all content is displayed.
if (discussionLanguages[index].id == 0 && discussionLanguages.length > 1) {
showThunderDialog(
context: context,
title: l10n.warning,
contentText: l10n.deselectUndeterminedWarning,
primaryButtonText: l10n.remove,
onPrimaryButtonPressed: (dialogContext, setPrimaryButtonEnabled) {
final updatedDiscussionLanguages = discussionLanguages.where((element) => element != discussionLanguages[index]).toList();
context.read<AccountSettingsCubit>().updateSettings(discussionLanguages: updatedDiscussionLanguages.map((e) => e.id).toList());
Navigator.of(dialogContext).pop();
},
secondaryButtonText: l10n.cancel,
onSecondaryButtonPressed: (dialogContext) => Navigator.of(dialogContext).pop(),
);
} else {
final updatedDiscussionLanguages = discussionLanguages.where((element) => element != discussionLanguages[index]).toList();
context.read<AccountSettingsCubit>().updateSettings(discussionLanguages: updatedDiscussionLanguages.map((e) => e.id).toList());
}
},
),
),
),
),
],
),
);
},
],
),
);
},
),
);
}
}
Loading
Loading