Files
umbrix/lib/features/proxy/active/active_proxy_footer.dart

285 lines
12 KiB
Dart
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import 'package:dartx/dartx.dart';
import 'package:fluentui_system_icons/fluentui_system_icons.dart';
import 'package:flutter/material.dart';
import 'package:gap/gap.dart';
import 'package:hiddify/core/localization/translations.dart';
import 'package:hiddify/core/widget/animated_visibility.dart';
import 'package:hiddify/core/widget/shimmer_skeleton.dart';
import 'package:hiddify/features/proxy/active/active_proxy_notifier.dart';
import 'package:hiddify/features/proxy/active/ip_widget.dart';
import 'package:hiddify/features/proxy/model/proxy_failure.dart';
import 'package:hiddify/features/stats/notifier/stats_notifier.dart';
import 'package:hiddify/gen/fonts.gen.dart';
import 'package:hiddify/utils/utils.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
class ActiveProxyFooter extends HookConsumerWidget {
const ActiveProxyFooter({super.key});
@override
Widget build(BuildContext context, WidgetRef ref) {
final t = ref.watch(translationsProvider);
final theme = Theme.of(context);
final activeProxy = ref.watch(activeProxyNotifierProvider);
final ipInfo = ref.watch(ipInfoNotifierProvider);
final stats = ref.watch(statsNotifierProvider).value;
return AnimatedVisibility(
axis: Axis.vertical,
visible: activeProxy is AsyncData,
child: switch (activeProxy) {
AsyncData(value: final proxy) => Padding(
padding: const EdgeInsets.symmetric(vertical: 12, horizontal: 16),
child: Column(
children: [
// Карточка с информацией о прокси и локации
Card(
elevation: 2,
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(16)),
child: Padding(
padding: const EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// Название прокси
Row(
children: [
Icon(
FluentIcons.arrow_routing_20_regular,
size: 20,
color: theme.colorScheme.primary,
),
const Gap(12),
Expanded(
child: Text(
proxy.selectedName.isNotNullOrBlank
? proxy.selectedName!
: proxy.name,
semanticsLabel: t.proxies.activeProxySemanticLabel,
style: theme.textTheme.titleMedium?.copyWith(
fontWeight: FontWeight.w600,
fontFamily: FontFamily.emoji,
),
overflow: TextOverflow.ellipsis,
),
),
],
),
const Gap(12),
const Divider(height: 1),
const Gap(12),
// Информация о стране
switch (ipInfo) {
AsyncData(value: final info) => InkWell(
onTap: () async {
ref.read(ipInfoNotifierProvider.notifier).refresh();
},
borderRadius: BorderRadius.circular(8),
child: Padding(
padding: const EdgeInsets.symmetric(vertical: 4),
child: Row(
children: [
IPCountryFlag(countryCode: info.countryCode),
const Gap(12),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
info.countryCode.toUpperCase(),
style: theme.textTheme.bodyLarge?.copyWith(
fontWeight: FontWeight.w500,
),
),
if (info.org?.isNotEmpty ?? false)
Text(
info.org!,
style: theme.textTheme.bodySmall?.copyWith(
color: theme.colorScheme.onSurfaceVariant,
),
overflow: TextOverflow.ellipsis,
),
],
),
),
],
),
),
),
AsyncError(error: final UnknownIp _) => InkWell(
onTap: () async {
ref.read(ipInfoNotifierProvider.notifier).refresh();
},
borderRadius: BorderRadius.circular(8),
child: Padding(
padding: const EdgeInsets.symmetric(vertical: 4),
child: Row(
children: [
const Icon(FluentIcons.arrow_sync_20_regular),
const Gap(12),
Text(
t.proxies.checkIp,
style: theme.textTheme.bodyMedium,
),
],
),
),
),
AsyncError() => InkWell(
onTap: () async {
ref.read(ipInfoNotifierProvider.notifier).refresh();
},
borderRadius: BorderRadius.circular(8),
child: Padding(
padding: const EdgeInsets.symmetric(vertical: 4),
child: Row(
children: [
Icon(
FluentIcons.error_circle_20_regular,
color: theme.colorScheme.error,
),
const Gap(12),
Text(
t.proxies.unknownIp,
style: theme.textTheme.bodyMedium?.copyWith(
color: theme.colorScheme.error,
),
),
],
),
),
),
_ => const Padding(
padding: EdgeInsets.symmetric(vertical: 4),
child: Row(
children: [
Icon(FluentIcons.question_circle_20_regular),
Gap(12),
Expanded(
child: ShimmerSkeleton(height: 16, widthFactor: 0.6),
),
],
),
),
},
],
),
),
),
const Gap(12),
// Карточки со статистикой
Row(
children: [
Expanded(
child: Card(
elevation: 2,
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(16)),
child: Padding(
padding: const EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Icon(
FluentIcons.arrow_bidirectional_up_down_20_regular,
size: 24,
color: theme.colorScheme.secondary,
),
const Gap(8),
Text(
(stats?.downlinkTotal ?? 0).size(),
semanticsLabel: t.stats.totalTransferred,
style: theme.textTheme.titleLarge?.copyWith(
fontWeight: FontWeight.bold,
),
),
Text(
t.stats.totalTransferred,
style: theme.textTheme.bodySmall?.copyWith(
color: theme.colorScheme.onSurfaceVariant,
),
),
],
),
),
),
),
const Gap(12),
Expanded(
child: Card(
elevation: 2,
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(16)),
child: Padding(
padding: const EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Icon(
FluentIcons.arrow_download_20_regular,
size: 24,
color: theme.colorScheme.tertiary,
),
const Gap(8),
Text(
(stats?.downlink ?? 0).speed(),
semanticsLabel: t.stats.speed,
style: theme.textTheme.titleLarge?.copyWith(
fontWeight: FontWeight.bold,
),
),
Text(
t.stats.speed,
style: theme.textTheme.bodySmall?.copyWith(
color: theme.colorScheme.onSurfaceVariant,
),
),
],
),
),
),
),
],
),
],
),
),
_ => const SizedBox(),
},
);
}
}
class _InfoProp extends StatelessWidget {
const _InfoProp({
required this.icon,
required this.text,
this.semanticLabel,
});
final IconData icon;
final String text;
final String? semanticLabel;
@override
Widget build(BuildContext context) {
return Semantics(
label: semanticLabel,
child: Row(
children: [
Icon(icon),
const Gap(8),
Flexible(
child: Text(
text,
style: Theme.of(context)
.textTheme
.labelMedium
?.copyWith(fontFamily: FontFamily.emoji),
overflow: TextOverflow.ellipsis,
),
),
],
),
);
}
}