feat: mobile-like window size and always-visible stats

- Changed window size to mobile phone format (400x800)
- Removed width condition for ActiveProxyFooter - now always visible
- Added run-umbrix.sh launch script with icon copying
- Stats cards now display on all screen sizes
This commit is contained in:
Umbrix Developer
2026-01-17 13:09:20 +03:00
parent ec5ebbd54b
commit 76a374950f
245 changed files with 7931 additions and 1315 deletions

View File

@@ -1,16 +1,17 @@
import 'package:fluentui_system_icons/fluentui_system_icons.dart';
import 'package:flutter/material.dart';
import 'package:flutter_adaptive_scaffold/flutter_adaptive_scaffold.dart';
import 'package:hiddify/gen/assets.gen.dart';
import 'package:hiddify/core/localization/translations.dart';
import 'package:hiddify/core/router/router.dart';
import 'package:hiddify/features/stats/widget/side_bar_stats_overview.dart';
import 'package:hiddify/core/router/routes.dart';
import 'package:go_router/go_router.dart';
import 'package:umbrix/gen/assets.gen.dart';
import 'package:umbrix/core/localization/translations.dart';
import 'package:umbrix/core/router/router.dart';
import 'package:umbrix/features/stats/widget/side_bar_stats_overview.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:hiddify/core/theme/theme_preferences.dart';
import 'package:hiddify/core/theme/app_theme_mode.dart';
import 'package:hiddify/core/localization/locale_preferences.dart';
import 'package:hiddify/core/localization/locale_extensions.dart';
import 'package:umbrix/core/theme/theme_preferences.dart';
import 'package:umbrix/core/theme/app_theme_mode.dart';
import 'package:umbrix/core/localization/locale_preferences.dart';
import 'package:umbrix/core/localization/locale_extensions.dart';
import 'package:umbrix/utils/utils.dart';
abstract interface class RootScaffold {
static final stateKey = GlobalKey<ScaffoldState>();
@@ -27,6 +28,10 @@ class AdaptiveRootScaffold extends HookConsumerWidget {
Widget build(BuildContext context, WidgetRef ref) {
final t = ref.watch(translationsProvider);
final interceptBackToHome = !PlatformUtils.isDesktop;
final proxiesLocation = const ProxiesRoute().location;
final perAppProxyLocation = const PerAppProxyRoute().location;
final selectedIndex = getCurrentIndex(context);
final destinations = [
@@ -63,8 +68,8 @@ class AdaptiveRootScaffold extends HookConsumerWidget {
switchTab(index, context);
},
destinations: destinations,
drawerDestinationRange: useMobileRouter ? (3, null) : (0, null),
bottomDestinationRange: (0, 3),
drawerDestinationRange: (3, null), // Настройки и О программе всегда в drawer
bottomDestinationRange: (0, 3), // Первые 3 пункта в bottom nav
useBottomSheet: useMobileRouter,
sidebarTrailing: const Expanded(
child: Align(
@@ -72,7 +77,27 @@ class AdaptiveRootScaffold extends HookConsumerWidget {
child: SideBarStatsOverview(),
),
),
body: navigator,
body: BackButtonListener(
onBackButtonPressed: () async {
if (!interceptBackToHome) return false;
final location = GoRouterState.of(context).uri.path;
final shouldGoHome = location.startsWith(proxiesLocation) || location.startsWith(perAppProxyLocation);
assert(() {
debugPrint(
'BACK_INTERCEPT AdaptiveRootScaffold location=$location shouldGoHome=$shouldGoHome',
);
return true;
}());
if (shouldGoHome) {
const HomeRoute().go(context);
return true;
}
return false;
},
child: navigator,
),
);
}
}
@@ -111,43 +136,42 @@ class _CustomAdaptiveScaffold extends HookConsumerWidget {
Widget build(BuildContext context, WidgetRef ref) {
return Scaffold(
key: RootScaffold.stateKey,
drawer: Breakpoints.small.isActive(context)
? Drawer(
width: (MediaQuery.sizeOf(context).width * 0.88).clamp(1, 304),
drawer: Drawer(
width: (MediaQuery.sizeOf(context).width * 0.88).clamp(1, 304),
child: Column(
children: [
// Логотип и название приложения
Container(
padding: const EdgeInsets.symmetric(vertical: 32),
child: Column(
children: [
// Логотип и название приложения
Container(
padding: const EdgeInsets.symmetric(vertical: 32),
child: Column(
children: [
Container(
width: 80,
height: 80,
padding: const EdgeInsets.all(12),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(20),
color: Theme.of(context).colorScheme.primaryContainer,
),
child: Assets.images.umbrixLogo.image(
fit: BoxFit.contain,
),
),
const SizedBox(height: 16),
Text(
'Umbrix',
style: Theme.of(context).textTheme.headlineSmall?.copyWith(
fontWeight: FontWeight.bold,
),
),
],
width: 80,
height: 80,
padding: const EdgeInsets.all(12),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(20),
color: Theme.of(context).colorScheme.primaryContainer,
),
child: Assets.images.umbrixLogo.image(
fit: BoxFit.contain,
),
),
// Список пунктов меню
Expanded(
child: ListView(
padding: const EdgeInsets.symmetric(horizontal: 16),
children: [
const SizedBox(height: 16),
Text(
'Umbrix',
style: Theme.of(context).textTheme.headlineSmall?.copyWith(
fontWeight: FontWeight.bold,
),
),
],
),
),
// Список пунктов меню
Expanded(
child: ListView(
padding: const EdgeInsets.symmetric(horizontal: 16),
children: [
// О программе
Builder(
builder: (context) {
@@ -190,29 +214,11 @@ class _CustomAdaptiveScaffold extends HookConsumerWidget {
),
],
),
)
: null,
),
body: AdaptiveLayout(
primaryNavigation: SlotLayout(
config: <Breakpoint, SlotLayoutConfig>{
Breakpoints.medium: SlotLayout.from(
key: const Key('primaryNavigation'),
builder: (_) => AdaptiveScaffold.standardNavigationRail(
selectedIndex: selectedIndex,
destinations: destinations.map((dest) => AdaptiveScaffold.toRailDestination(dest)).toList(),
onDestinationSelected: onSelectedIndexChange,
),
),
Breakpoints.large: SlotLayout.from(
key: const Key('primaryNavigation1'),
builder: (_) => AdaptiveScaffold.standardNavigationRail(
extended: true,
selectedIndex: selectedIndex,
destinations: destinations.map((dest) => AdaptiveScaffold.toRailDestination(dest)).toList(),
onDestinationSelected: onSelectedIndexChange,
trailing: sidebarTrailing,
),
),
// Убираем боковую навигацию для Desktop
},
),
body: SlotLayout(
@@ -226,14 +232,12 @@ class _CustomAdaptiveScaffold extends HookConsumerWidget {
},
),
),
// AdaptiveLayout bottom sheet has accessibility issues
bottomNavigationBar: useBottomSheet && Breakpoints.small.isActive(context)
? NavigationBar(
selectedIndex: selectedWithOffset(bottomDestinationRange) ?? 0,
destinations: destinationsSlice(bottomDestinationRange),
onDestinationSelected: (index) => selectWithOffset(index, bottomDestinationRange),
)
: null,
// Нижняя навигация - первые 3 пункта для всех платформ
bottomNavigationBar: NavigationBar(
selectedIndex: selectedWithOffset(bottomDestinationRange) ?? 0,
destinations: destinationsSlice(bottomDestinationRange),
onDestinationSelected: (index) => selectWithOffset(index, bottomDestinationRange),
),
);
}
}

View File

@@ -1,15 +1,15 @@
import 'package:fluentui_system_icons/fluentui_system_icons.dart';
import 'package:flutter/material.dart';
import 'package:hiddify/core/analytics/analytics_controller.dart';
import 'package:hiddify/core/localization/locale_extensions.dart';
import 'package:hiddify/core/localization/locale_preferences.dart';
import 'package:hiddify/core/localization/translations.dart';
import 'package:hiddify/core/model/region.dart';
import 'package:hiddify/core/preferences/actions_at_closing.dart';
import 'package:hiddify/core/preferences/general_preferences.dart';
import 'package:hiddify/core/theme/app_theme_mode.dart';
import 'package:hiddify/core/theme/theme_preferences.dart';
import 'package:hiddify/features/config_option/data/config_option_repository.dart';
import 'package:umbrix/core/analytics/analytics_controller.dart';
import 'package:umbrix/core/localization/locale_extensions.dart';
import 'package:umbrix/core/localization/locale_preferences.dart';
import 'package:umbrix/core/localization/translations.dart';
import 'package:umbrix/core/model/region.dart';
import 'package:umbrix/core/preferences/actions_at_closing.dart';
import 'package:umbrix/core/preferences/general_preferences.dart';
import 'package:umbrix/core/theme/app_theme_mode.dart';
import 'package:umbrix/core/theme/theme_preferences.dart';
import 'package:umbrix/features/config_option/data/config_option_repository.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
class LocalePrefTile extends ConsumerWidget {

View File

@@ -1,8 +1,8 @@
import 'package:flutter/material.dart';
import 'package:go_router/go_router.dart';
import 'package:hiddify/core/router/router.dart';
import 'package:hiddify/features/common/adaptive_root_scaffold.dart';
import 'package:hiddify/utils/utils.dart';
import 'package:umbrix/core/router/router.dart';
import 'package:umbrix/features/common/adaptive_root_scaffold.dart';
import 'package:umbrix/utils/utils.dart';
bool showDrawerButton(BuildContext context) {
if (!useMobileRouter) return true;

View File

@@ -4,8 +4,8 @@ import 'package:dartx/dartx.dart';
import 'package:fluentui_system_icons/fluentui_system_icons.dart';
import 'package:flutter/material.dart';
// import 'package:flutter_easy_permission/easy_permissions.dart';
import 'package:hiddify/core/localization/translations.dart';
import 'package:hiddify/utils/utils.dart';
import 'package:umbrix/core/localization/translations.dart';
import 'package:umbrix/utils/utils.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:mobile_scanner/mobile_scanner.dart';
// import 'package:permission_handler/permission_handler.dart';