Refactor profiles
This commit is contained in:
@@ -0,0 +1,83 @@
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:fpdart/fpdart.dart';
|
||||
import 'package:hiddify/data/data_providers.dart';
|
||||
import 'package:hiddify/features/profile/data/profile_data_providers.dart';
|
||||
import 'package:hiddify/features/profile/data/profile_repository.dart';
|
||||
import 'package:hiddify/features/profile/model/profile_entity.dart';
|
||||
import 'package:hiddify/features/profile/model/profile_sort_enum.dart';
|
||||
import 'package:hiddify/utils/utils.dart';
|
||||
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
||||
|
||||
part 'profiles_overview_notifier.g.dart';
|
||||
|
||||
@riverpod
|
||||
class ProfilesOverviewSortNotifier extends _$ProfilesOverviewSortNotifier
|
||||
with AppLogger {
|
||||
@override
|
||||
({ProfilesSort by, SortMode mode}) build() {
|
||||
return (by: ProfilesSort.lastUpdate, mode: SortMode.descending);
|
||||
}
|
||||
|
||||
void changeSort(ProfilesSort sortBy) =>
|
||||
state = (by: sortBy, mode: state.mode);
|
||||
|
||||
void toggleMode() => state = (
|
||||
by: state.by,
|
||||
mode: state.mode == SortMode.ascending
|
||||
? SortMode.descending
|
||||
: SortMode.ascending
|
||||
);
|
||||
}
|
||||
|
||||
@riverpod
|
||||
class ProfilesOverviewNotifier extends _$ProfilesOverviewNotifier
|
||||
with AppLogger {
|
||||
@override
|
||||
Stream<List<ProfileEntity>> build() {
|
||||
final sort = ref.watch(profilesOverviewSortNotifierProvider);
|
||||
return _profilesRepo
|
||||
.watchAll(sort: sort.by, sortMode: sort.mode)
|
||||
.map((event) => event.getOrElse((l) => throw l));
|
||||
}
|
||||
|
||||
ProfileRepository get _profilesRepo =>
|
||||
ref.read(profileRepositoryProvider).requireValue;
|
||||
|
||||
Future<Unit> selectActiveProfile(String id) async {
|
||||
loggy.debug('changing active profile to: [$id]');
|
||||
return _profilesRepo.setAsActive(id).getOrElse((err) {
|
||||
loggy.warning('failed to set [$id] as active profile', err);
|
||||
throw err;
|
||||
}).run();
|
||||
}
|
||||
|
||||
Future<void> deleteProfile(ProfileEntity profile) async {
|
||||
loggy.debug('deleting profile: ${profile.name}');
|
||||
await _profilesRepo.deleteById(profile.id).match(
|
||||
(err) {
|
||||
loggy.warning('failed to delete profile', err);
|
||||
throw err;
|
||||
},
|
||||
(_) {
|
||||
loggy.info(
|
||||
'successfully deleted profile, was active? [${profile.active}]',
|
||||
);
|
||||
return unit;
|
||||
},
|
||||
).run();
|
||||
}
|
||||
|
||||
Future<void> exportConfigToClipboard(ProfileEntity profile) async {
|
||||
await ref.read(coreFacadeProvider).generateConfig(profile.id).match(
|
||||
(err) {
|
||||
loggy.warning('error generating config', err);
|
||||
throw err;
|
||||
},
|
||||
(configJson) async {
|
||||
await Clipboard.setData(ClipboardData(text: configJson));
|
||||
},
|
||||
).run();
|
||||
}
|
||||
}
|
||||
140
lib/features/profile/overview/profiles_overview_page.dart
Normal file
140
lib/features/profile/overview/profiles_overview_page.dart
Normal file
@@ -0,0 +1,140 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:gap/gap.dart';
|
||||
import 'package:hiddify/core/core_providers.dart';
|
||||
import 'package:hiddify/core/router/router.dart';
|
||||
import 'package:hiddify/domain/failures.dart';
|
||||
import 'package:hiddify/features/profile/model/profile_sort_enum.dart';
|
||||
import 'package:hiddify/features/profile/overview/profiles_overview_notifier.dart';
|
||||
import 'package:hiddify/features/profile/widget/profile_tile.dart';
|
||||
import 'package:hiddify/utils/placeholders.dart';
|
||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
|
||||
class ProfilesOverviewModal extends HookConsumerWidget {
|
||||
const ProfilesOverviewModal({
|
||||
super.key,
|
||||
this.scrollController,
|
||||
});
|
||||
|
||||
final ScrollController? scrollController;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
final t = ref.watch(translationsProvider);
|
||||
final asyncProfiles = ref.watch(profilesOverviewNotifierProvider);
|
||||
|
||||
return Stack(
|
||||
children: [
|
||||
CustomScrollView(
|
||||
controller: scrollController,
|
||||
slivers: [
|
||||
switch (asyncProfiles) {
|
||||
AsyncData(value: final profiles) => SliverList.builder(
|
||||
itemBuilder: (context, index) {
|
||||
final profile = profiles[index];
|
||||
return ProfileTile(profile: profile);
|
||||
},
|
||||
itemCount: profiles.length,
|
||||
),
|
||||
AsyncError(:final error) => SliverErrorBodyPlaceholder(
|
||||
t.presentShortError(error),
|
||||
),
|
||||
AsyncLoading() => const SliverLoadingBodyPlaceholder(),
|
||||
_ => const SliverToBoxAdapter(),
|
||||
},
|
||||
const SliverGap(48),
|
||||
],
|
||||
),
|
||||
Positioned.fill(
|
||||
child: Align(
|
||||
alignment: Alignment.bottomCenter,
|
||||
child: ButtonBar(
|
||||
alignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
FilledButton.icon(
|
||||
onPressed: () {
|
||||
const AddProfileRoute().push(context);
|
||||
},
|
||||
icon: const Icon(Icons.add),
|
||||
label: Text(t.profile.add.shortBtnTxt),
|
||||
),
|
||||
FilledButton.icon(
|
||||
onPressed: () {
|
||||
showDialog(
|
||||
context: context,
|
||||
builder: (context) {
|
||||
return const ProfilesSortModal();
|
||||
},
|
||||
);
|
||||
},
|
||||
icon: const Icon(Icons.sort),
|
||||
label: Text(t.general.sort),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class ProfilesSortModal extends HookConsumerWidget {
|
||||
const ProfilesSortModal({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
final t = ref.watch(translationsProvider);
|
||||
final sortNotifier =
|
||||
ref.watch(profilesOverviewSortNotifierProvider.notifier);
|
||||
|
||||
return AlertDialog(
|
||||
title: Text(t.general.sortBy),
|
||||
content: Consumer(
|
||||
builder: (context, ref, child) {
|
||||
final sort = ref.watch(profilesOverviewSortNotifierProvider);
|
||||
return SingleChildScrollView(
|
||||
child: Column(
|
||||
children: [
|
||||
...ProfilesSort.values.map(
|
||||
(e) {
|
||||
final selected = sort.by == e;
|
||||
final double arrowTurn =
|
||||
sort.mode == SortMode.ascending ? 0 : 0.5;
|
||||
|
||||
return ListTile(
|
||||
title: Text(e.present(t)),
|
||||
onTap: () {
|
||||
if (selected) {
|
||||
sortNotifier.toggleMode();
|
||||
} else {
|
||||
sortNotifier.changeSort(e);
|
||||
}
|
||||
},
|
||||
selected: selected,
|
||||
leading: Icon(e.icon),
|
||||
trailing: selected
|
||||
? IconButton(
|
||||
onPressed: () {
|
||||
sortNotifier.toggleMode();
|
||||
},
|
||||
icon: AnimatedRotation(
|
||||
turns: arrowTurn,
|
||||
duration: const Duration(milliseconds: 100),
|
||||
child: Icon(
|
||||
Icons.arrow_upward,
|
||||
semanticLabel: sort.mode.name,
|
||||
),
|
||||
),
|
||||
)
|
||||
: null,
|
||||
);
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user