Add profiles sort option
This commit is contained in:
@@ -5,7 +5,9 @@
|
||||
"toggle": {
|
||||
"enabled": "enabled",
|
||||
"disabled": "disabled"
|
||||
}
|
||||
},
|
||||
"sort": "sort",
|
||||
"sortBy": "sort by"
|
||||
},
|
||||
"home": {
|
||||
"pageTitle": "home",
|
||||
@@ -29,8 +31,13 @@
|
||||
"noTraffic": "no traffic",
|
||||
"gigaByte": "GB"
|
||||
},
|
||||
"sortBy": {
|
||||
"lastUpdate": "Recently updated",
|
||||
"name": "by Name"
|
||||
},
|
||||
"add": {
|
||||
"buttonText": "add new profile",
|
||||
"shortBtnTxt": "add new",
|
||||
"fromClipboard": "add from clipboard",
|
||||
"scanQr": "Scan QR code",
|
||||
"manually": "add manually",
|
||||
|
||||
@@ -5,7 +5,9 @@
|
||||
"toggle": {
|
||||
"enabled": "فعال",
|
||||
"disabled": "غیر فعال"
|
||||
}
|
||||
},
|
||||
"sort": "مرتبسازی",
|
||||
"sortBy": "مرتبسازی براساس"
|
||||
},
|
||||
"home": {
|
||||
"pageTitle": "خانه",
|
||||
@@ -29,8 +31,13 @@
|
||||
"noTraffic": "پایان ترافیک",
|
||||
"gigaByte": "گیگ"
|
||||
},
|
||||
"sortBy": {
|
||||
"lastUpdate": "اخیرا بروز شده",
|
||||
"name": "براساس نام"
|
||||
},
|
||||
"add": {
|
||||
"buttonText": "افزودن پروفایل جدید",
|
||||
"shortBtnTxt": "ایجاد",
|
||||
"fromClipboard": "افزودن از کلیپبورد",
|
||||
"scanQr": "اسکن QR کد",
|
||||
"manually": "افزودن دستی",
|
||||
|
||||
@@ -2,11 +2,17 @@ import 'package:drift/drift.dart';
|
||||
import 'package:hiddify/data/local/data_mappers.dart';
|
||||
import 'package:hiddify/data/local/database.dart';
|
||||
import 'package:hiddify/data/local/tables.dart';
|
||||
import 'package:hiddify/domain/enums.dart';
|
||||
import 'package:hiddify/domain/profiles/profiles.dart';
|
||||
import 'package:hiddify/utils/utils.dart';
|
||||
|
||||
part 'profiles_dao.g.dart';
|
||||
|
||||
Map<SortMode, OrderingMode> orderMap = {
|
||||
SortMode.ascending: OrderingMode.asc,
|
||||
SortMode.descending: OrderingMode.desc
|
||||
};
|
||||
|
||||
@DriftAccessor(tables: [ProfileEntries])
|
||||
class ProfilesDao extends DatabaseAccessor<AppDatabase>
|
||||
with _$ProfilesDaoMixin, InfraLogger {
|
||||
@@ -31,10 +37,24 @@ class ProfilesDao extends DatabaseAccessor<AppDatabase>
|
||||
.watchSingle();
|
||||
}
|
||||
|
||||
Stream<List<Profile>> watchAll() {
|
||||
Stream<List<Profile>> watchAll({
|
||||
ProfilesSort sort = ProfilesSort.lastUpdate,
|
||||
SortMode mode = SortMode.ascending,
|
||||
}) {
|
||||
return (profileEntries.select()
|
||||
..orderBy(
|
||||
[(tbl) => OrderingTerm.desc(tbl.active)],
|
||||
[
|
||||
switch (sort) {
|
||||
ProfilesSort.name => (tbl) => OrderingTerm(
|
||||
expression: tbl.name,
|
||||
mode: orderMap[mode]!,
|
||||
),
|
||||
_ => (tbl) => OrderingTerm(
|
||||
expression: tbl.lastUpdate,
|
||||
mode: orderMap[mode]!,
|
||||
),
|
||||
}
|
||||
],
|
||||
))
|
||||
.map(ProfileMapper.fromEntry)
|
||||
.watch();
|
||||
|
||||
@@ -5,6 +5,7 @@ import 'package:fpdart/fpdart.dart';
|
||||
import 'package:hiddify/data/local/dao/dao.dart';
|
||||
import 'package:hiddify/data/repository/exception_handlers.dart';
|
||||
import 'package:hiddify/domain/clash/clash.dart';
|
||||
import 'package:hiddify/domain/enums.dart';
|
||||
import 'package:hiddify/domain/profiles/profiles.dart';
|
||||
import 'package:hiddify/services/files_editor_service.dart';
|
||||
import 'package:hiddify/utils/utils.dart';
|
||||
@@ -50,9 +51,12 @@ class ProfilesRepositoryImpl
|
||||
}
|
||||
|
||||
@override
|
||||
Stream<Either<ProfileFailure, List<Profile>>> watchAll() {
|
||||
Stream<Either<ProfileFailure, List<Profile>>> watchAll({
|
||||
ProfilesSort sort = ProfilesSort.lastUpdate,
|
||||
SortMode mode = SortMode.ascending,
|
||||
}) {
|
||||
return profilesDao
|
||||
.watchAll()
|
||||
.watchAll(sort: sort, mode: mode)
|
||||
.handleExceptions(ProfileUnexpectedFailure.new);
|
||||
}
|
||||
|
||||
|
||||
1
lib/domain/enums.dart
Normal file
1
lib/domain/enums.dart
Normal file
@@ -0,0 +1 @@
|
||||
enum SortMode { ascending, descending }
|
||||
13
lib/domain/profiles/profile_enums.dart
Normal file
13
lib/domain/profiles/profile_enums.dart
Normal file
@@ -0,0 +1,13 @@
|
||||
import 'package:hiddify/core/locale/locale.dart';
|
||||
|
||||
enum ProfilesSort {
|
||||
lastUpdate,
|
||||
name;
|
||||
|
||||
String present(TranslationsEn t) {
|
||||
return switch (this) {
|
||||
lastUpdate => t.profile.sortBy.lastUpdate,
|
||||
name => t.profile.sortBy.name,
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -1,3 +1,4 @@
|
||||
export 'profile.dart';
|
||||
export 'profile_enums.dart';
|
||||
export 'profiles_failure.dart';
|
||||
export 'profiles_repository.dart';
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import 'package:fpdart/fpdart.dart';
|
||||
import 'package:hiddify/domain/enums.dart';
|
||||
import 'package:hiddify/domain/profiles/profiles.dart';
|
||||
|
||||
abstract class ProfilesRepository {
|
||||
@@ -8,7 +9,10 @@ abstract class ProfilesRepository {
|
||||
|
||||
Stream<Either<ProfileFailure, bool>> watchHasAnyProfile();
|
||||
|
||||
Stream<Either<ProfileFailure, List<Profile>>> watchAll();
|
||||
Stream<Either<ProfileFailure, List<Profile>>> watchAll({
|
||||
ProfilesSort sort = ProfilesSort.lastUpdate,
|
||||
SortMode mode = SortMode.ascending,
|
||||
});
|
||||
|
||||
TaskEither<ProfileFailure, Unit> addByUrl(
|
||||
String url, {
|
||||
|
||||
@@ -2,6 +2,7 @@ import 'dart:async';
|
||||
|
||||
import 'package:fpdart/fpdart.dart';
|
||||
import 'package:hiddify/data/data_providers.dart';
|
||||
import 'package:hiddify/domain/enums.dart';
|
||||
import 'package:hiddify/domain/profiles/profiles.dart';
|
||||
import 'package:hiddify/features/common/active_profile/active_profile_notifier.dart';
|
||||
import 'package:hiddify/utils/utils.dart';
|
||||
@@ -9,12 +10,31 @@ import 'package:riverpod_annotation/riverpod_annotation.dart';
|
||||
|
||||
part 'profiles_notifier.g.dart';
|
||||
|
||||
@riverpod
|
||||
class ProfilesSortNotifier extends _$ProfilesSortNotifier 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 ProfilesNotifier extends _$ProfilesNotifier with AppLogger {
|
||||
@override
|
||||
Stream<List<Profile>> build() {
|
||||
final sort = ref.watch(profilesSortNotifierProvider);
|
||||
return _profilesRepo
|
||||
.watchAll()
|
||||
.watchAll(sort: sort.by, mode: sort.mode)
|
||||
.map((event) => event.getOrElse((l) => throw l));
|
||||
}
|
||||
|
||||
|
||||
@@ -1,7 +1,15 @@
|
||||
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/enums.dart';
|
||||
import 'package:hiddify/domain/failures.dart';
|
||||
import 'package:hiddify/domain/profiles/profiles.dart';
|
||||
import 'package:hiddify/features/common/common.dart';
|
||||
import 'package:hiddify/features/profiles/notifier/notifier.dart';
|
||||
import 'package:hiddify/utils/utils.dart';
|
||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
import 'package:recase/recase.dart';
|
||||
|
||||
class ProfilesModal extends HookConsumerWidget {
|
||||
const ProfilesModal({
|
||||
@@ -13,11 +21,12 @@ class ProfilesModal extends HookConsumerWidget {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
final t = ref.watch(translationsProvider);
|
||||
final asyncProfiles = ref.watch(profilesNotifierProvider);
|
||||
|
||||
return Scaffold(
|
||||
backgroundColor: Colors.transparent,
|
||||
body: CustomScrollView(
|
||||
return Stack(
|
||||
children: [
|
||||
CustomScrollView(
|
||||
controller: scrollController,
|
||||
slivers: [
|
||||
switch (asyncProfiles) {
|
||||
@@ -28,11 +37,106 @@ class ProfilesModal extends HookConsumerWidget {
|
||||
},
|
||||
itemCount: profiles.length,
|
||||
),
|
||||
// TODO: handle loading and error
|
||||
AsyncError(:final error) => SliverErrorBodyPlaceholder(
|
||||
t.presentError(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.titleCase),
|
||||
),
|
||||
FilledButton.icon(
|
||||
onPressed: () {
|
||||
showDialog(
|
||||
context: context,
|
||||
builder: (context) {
|
||||
return const ProfilesSortModal();
|
||||
},
|
||||
);
|
||||
},
|
||||
icon: const Icon(Icons.filter_list),
|
||||
label: Text(t.general.sort.titleCase),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class ProfilesSortModal extends HookConsumerWidget {
|
||||
const ProfilesSortModal({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
final t = ref.watch(translationsProvider);
|
||||
|
||||
return AlertDialog(
|
||||
title: Text(t.general.sortBy.titleCase),
|
||||
content: Consumer(
|
||||
builder: (context, ref, child) {
|
||||
final sort = ref.watch(profilesSortNotifierProvider);
|
||||
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) {
|
||||
ref
|
||||
.read(profilesSortNotifierProvider.notifier)
|
||||
.toggleMode();
|
||||
} else {
|
||||
ref
|
||||
.read(profilesSortNotifierProvider.notifier)
|
||||
.changeSort(e);
|
||||
}
|
||||
},
|
||||
selected: selected,
|
||||
trailing: selected
|
||||
? IconButton(
|
||||
onPressed: () {
|
||||
ref
|
||||
.read(profilesSortNotifierProvider.notifier)
|
||||
.toggleMode();
|
||||
},
|
||||
icon: AnimatedRotation(
|
||||
turns: arrowTurn,
|
||||
duration: const Duration(milliseconds: 100),
|
||||
child: const Icon(Icons.arrow_upward),
|
||||
),
|
||||
)
|
||||
: null,
|
||||
);
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user