Compare commits
15 Commits
5b2702daed
...
refactor-n
| Author | SHA1 | Date | |
|---|---|---|---|
| 123c006a1e | |||
| 415811f592 | |||
| 31066a4d8f | |||
| b700c2de8d | |||
| fda5b8fe2e | |||
| b7a525056a | |||
| 7a11e829b3 | |||
| 361b61a694 | |||
| 0cb060c89c | |||
| 4b9cbf65f9 | |||
| 813fc9dd38 | |||
| f574d6197b | |||
| 2fac3117a4 | |||
| 7b072a219d | |||
| 23d3356e6b |
@@ -1,52 +1,90 @@
|
||||
name: Build and Release FLUX Windows
|
||||
name: Build and Release FLUX (Multi-Platform)
|
||||
on:
|
||||
push:
|
||||
tags:
|
||||
- 'v*' # Si attiva solo quando crei un tag tipo v1.0.0
|
||||
- 'v*'
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: windows-native # Richiama esattamente l'etichetta del PC del collega
|
||||
# -----------------------------------------------------------------
|
||||
# JOB 1: WINDOWS (Gira sul PC del collega appena si libera)
|
||||
# -----------------------------------------------------------------
|
||||
build-windows:
|
||||
runs-on: windows-native
|
||||
steps:
|
||||
- name: Checkout del codice
|
||||
uses: actions/checkout@v3
|
||||
|
||||
# 1. Facciamo una build finta. Genererà i symlinks e poi fallirà per il bug,
|
||||
# ma il 'continue-on-error' dirà a Gitea di ignorare l'errore e andare avanti.
|
||||
- name: Pre-build per generare l'ambiente (Fallirà apposta)
|
||||
continue-on-error: true
|
||||
- name: Crea file .env
|
||||
run: |
|
||||
Set-Content -Path ".env" -Value "${{ secrets.ENV_FILE_CONTENT }}"
|
||||
|
||||
- name: Build Flutter Windows
|
||||
run: flutter build windows --release
|
||||
|
||||
# 2. Ora la cartella ephemeral esiste! Applichiamo il fix.
|
||||
- name: Fix bug CMake di pdfx
|
||||
- name: Build Windows Installer
|
||||
run: |
|
||||
$cmakeFile = "windows\flutter\ephemeral\.plugin_symlinks\pdfx\windows\DownloadProject.cmake"
|
||||
$TagVersion = "${{ github.ref_name }}".Substring(1)
|
||||
& "C:\Program Files (x86)\Inno Setup 6\ISCC.exe" "/DMyAppVersion=$TagVersion" "win_installer.iss"
|
||||
|
||||
if (Test-Path $cmakeFile) {
|
||||
(Get-Content $cmakeFile) -replace 'VERSION 2.8.2', 'VERSION 3.5' | Set-Content $cmakeFile
|
||||
Write-Host "Il cerotto CMake è stato applicato con successo!"
|
||||
} else {
|
||||
Write-Error "File non trovato. La pre-build non ha generato i symlink."
|
||||
exit 1
|
||||
}
|
||||
|
||||
# 3. Ora che il file è corretto, la build vera arriverà fino in fondo
|
||||
- name: Build Flutter Definitiva
|
||||
env:
|
||||
CMAKE_BUILD_PARALLEL_LEVEL: 2
|
||||
run: flutter build windows --release
|
||||
|
||||
- name: Compila Installer Inno Setup
|
||||
run: |
|
||||
$TagVersion = "${{ gitea.ref_name }}".Substring(1)
|
||||
"C:\Program Files (x86)\Inno Setup 6\ISCC.exe" /DMyAppVersion=$TagVersion "win_installer.iss"
|
||||
|
||||
# Usa un'action standard per caricare il file su Gitea Releases
|
||||
- name: Upload Release Asset
|
||||
# Nel dubbio usiamo l'action per caricare l'asset
|
||||
- name: Upload Windows Asset
|
||||
uses: https://gitea.com/actions/release-action@main
|
||||
with:
|
||||
files: "build/windows/installer/FLUX_Setup_x64.exe"
|
||||
api_key: ${{ secrets.GITEA_TOKEN }}
|
||||
- name: Pulisci Workspace
|
||||
if: always() # Esegue questo step anche se la build fallisce
|
||||
files: "build/windows/installer/FluxInstaller.exe"
|
||||
api_key: ${{ secrets.MYRELEASE_TOKEN }}
|
||||
|
||||
- name: Pulisci Workspace Windows
|
||||
if: always()
|
||||
run: Remove-Item -Recurse -Force ./*
|
||||
|
||||
# -----------------------------------------------------------------
|
||||
# JOB 2: ANDROID APK (Gira sul tuo MacBook)
|
||||
# -----------------------------------------------------------------
|
||||
build-android:
|
||||
runs-on: macos-runner # <--- Etichetta del tuo Mac
|
||||
steps:
|
||||
- name: Checkout del codice
|
||||
uses: actions/checkout@v3
|
||||
|
||||
# Logica Bash per Mac: usiamo le virgolette singole forti per evitare escape strani
|
||||
- name: Crea file .env
|
||||
run: |
|
||||
cat << 'EOF' > .env
|
||||
${{ secrets.ENV_FILE_CONTENT }}
|
||||
EOF
|
||||
|
||||
- name: Build Flutter APK
|
||||
run: flutter build apk --release
|
||||
|
||||
# Carichiamo l'APK universale o quelli splittati nelle release di Gitea
|
||||
- name: Upload Android Asset
|
||||
uses: https://gitea.com/actions/release-action@main
|
||||
with:
|
||||
files: "build/app/outputs/flutter-apk/app-release.apk"
|
||||
api_key: ${{ secrets.MYRELEASE_TOKEN }}
|
||||
|
||||
# -----------------------------------------------------------------
|
||||
# JOB 3: WEB & CLOUDFLARE DEPLOY (Gira sul tuo MacBook)
|
||||
# -----------------------------------------------------------------
|
||||
build-web:
|
||||
runs-on: macos-runner # <--- Etichetta del tuo Mac
|
||||
steps:
|
||||
- name: Checkout del codice
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Crea file .env
|
||||
run: |
|
||||
cat << 'EOF' > .env
|
||||
${{ secrets.ENV_FILE_CONTENT }}
|
||||
EOF
|
||||
|
||||
- name: Build Flutter Web
|
||||
run: flutter build web --release
|
||||
|
||||
# Sfruttiamo npx (incluso in Node.js) per lanciare wrangler al volo senza installarlo globalmente
|
||||
# Sto assumendo che usi Cloudflare Pages che è perfetto per Flutter Web statico
|
||||
- name: Deploy su Cloudflare Pages
|
||||
env:
|
||||
CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }}
|
||||
run: |
|
||||
npx wrangler pages deploy build/web --project-name="flux" --branch="main"
|
||||
@@ -1,97 +1,370 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flux/core/routes/routes.dart';
|
||||
import 'package:flux/core/utils/extensions.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
|
||||
// ==========================================
|
||||
// 1. IL GUSCIO (QUELLO CHE PASSI AL ROUTER)
|
||||
// ==========================================
|
||||
class AppShell extends StatelessWidget {
|
||||
final Widget child;
|
||||
|
||||
const AppShell({super.key, required this.child});
|
||||
|
||||
// Calcoliamo l'indice attivo in base all'URL corrente!
|
||||
int _calculateSelectedIndex(BuildContext context) {
|
||||
final String location = GoRouterState.of(context).uri.path;
|
||||
if (location.startsWith('/master-data')) return 1;
|
||||
if (location.startsWith('/settings')) return 2;
|
||||
return 0; // Default: Dashboard
|
||||
}
|
||||
|
||||
void _onItemTapped(int index, BuildContext context) {
|
||||
switch (index) {
|
||||
case 0:
|
||||
context.go('/');
|
||||
break;
|
||||
case 1:
|
||||
context.go('/master-data');
|
||||
break;
|
||||
case 2:
|
||||
context.go('/settings');
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final currentIndex = _calculateSelectedIndex(context);
|
||||
// Breakpoint: se lo schermo è più largo di 600px, usiamo la Rail laterale
|
||||
final isDesktop = MediaQuery.sizeOf(context).width >= 600;
|
||||
// Breakpoint a 900px: sotto è Mobile/Tablet (Drawer), sopra è Desktop (Sidebar)
|
||||
final isDesktop = MediaQuery.sizeOf(context).width >= 900;
|
||||
final currentPath = GoRouterState.of(context).uri.path;
|
||||
|
||||
return Scaffold(
|
||||
// Su mobile usiamo un'AppBar minimale per avere il bottone "Hamburger" nativo
|
||||
appBar: isDesktop
|
||||
? null
|
||||
: AppBar(
|
||||
title: const Text(
|
||||
"FLUX",
|
||||
style: TextStyle(fontWeight: FontWeight.bold),
|
||||
),
|
||||
centerTitle: true,
|
||||
elevation: 0,
|
||||
backgroundColor: Theme.of(context).colorScheme.surface,
|
||||
surfaceTintColor: Colors.transparent,
|
||||
),
|
||||
drawer: isDesktop
|
||||
? null
|
||||
: Drawer(
|
||||
// Su mobile inietta il menu qui!
|
||||
child: AppMenu(currentPath: currentPath, isDrawer: true),
|
||||
),
|
||||
body: isDesktop
|
||||
? Row(
|
||||
children: [
|
||||
NavigationRail(
|
||||
selectedIndex: currentIndex,
|
||||
onDestinationSelected: (index) =>
|
||||
_onItemTapped(index, context),
|
||||
labelType: NavigationRailLabelType.all,
|
||||
destinations: [
|
||||
NavigationRailDestination(
|
||||
icon: Icon(Icons.dashboard_outlined),
|
||||
selectedIcon: Icon(Icons.dashboard),
|
||||
label: Text(context.l10n.commonDashboard),
|
||||
),
|
||||
NavigationRailDestination(
|
||||
icon: Icon(Icons.folder_special_outlined),
|
||||
selectedIcon: Icon(Icons.folder_special),
|
||||
label: Text(context.l10n.commonMasterData),
|
||||
),
|
||||
NavigationRailDestination(
|
||||
icon: Icon(Icons.settings_outlined),
|
||||
selectedIcon: Icon(Icons.settings),
|
||||
label: Text(context.l10n.commonSettings),
|
||||
),
|
||||
],
|
||||
),
|
||||
// Su desktop inietta il menu a sinistra!
|
||||
AppMenu(currentPath: currentPath, isDrawer: false),
|
||||
const VerticalDivider(thickness: 1, width: 1),
|
||||
// Il contenuto della pagina
|
||||
Expanded(child: child),
|
||||
],
|
||||
)
|
||||
: child, // Su mobile il contenuto prende tutto lo schermo...
|
||||
// ... e mettiamo la barra in basso!
|
||||
bottomNavigationBar: isDesktop
|
||||
? null
|
||||
: NavigationBar(
|
||||
selectedIndex: currentIndex,
|
||||
onDestinationSelected: (index) => _onItemTapped(index, context),
|
||||
destinations: [
|
||||
NavigationDestination(
|
||||
icon: Icon(Icons.dashboard_outlined),
|
||||
selectedIcon: Icon(Icons.dashboard),
|
||||
label: context.l10n.commonDashboard,
|
||||
),
|
||||
NavigationDestination(
|
||||
icon: Icon(Icons.folder_special_outlined),
|
||||
selectedIcon: Icon(Icons.folder_special),
|
||||
label: context.l10n.commonMasterData,
|
||||
),
|
||||
NavigationDestination(
|
||||
icon: Icon(Icons.settings_outlined),
|
||||
selectedIcon: Icon(Icons.settings),
|
||||
label: context.l10n.commonSettings,
|
||||
),
|
||||
],
|
||||
),
|
||||
: child, // Su mobile il child prende tutto lo schermo sotto l'AppBar
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class AppMenu extends StatefulWidget {
|
||||
final String currentPath; // Lo usiamo ancora per capire cosa accendere
|
||||
final bool isDrawer;
|
||||
|
||||
const AppMenu({super.key, required this.currentPath, required this.isDrawer});
|
||||
|
||||
@override
|
||||
State<AppMenu> createState() => _AppMenuState();
|
||||
}
|
||||
|
||||
class _AppMenuState extends State<AppMenu> {
|
||||
bool _isCollapsed = false;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final theme = Theme.of(context);
|
||||
final bool effectivelyCollapsed = _isCollapsed && !widget.isDrawer;
|
||||
|
||||
return AnimatedContainer(
|
||||
duration: const Duration(milliseconds: 200),
|
||||
curve: Curves.easeInOut,
|
||||
width: effectivelyCollapsed ? 72 : 260,
|
||||
child: SafeArea(
|
||||
child: Column(
|
||||
children: [
|
||||
// --- HEADER ---
|
||||
Container(
|
||||
height: 80,
|
||||
padding: const EdgeInsets.symmetric(horizontal: 20.0),
|
||||
alignment: effectivelyCollapsed
|
||||
? Alignment.center
|
||||
: Alignment.centerLeft,
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Icon(Icons.bolt, color: theme.colorScheme.primary, size: 32),
|
||||
if (!effectivelyCollapsed) ...[
|
||||
const SizedBox(width: 12),
|
||||
Text(
|
||||
"FLUX",
|
||||
style: theme.textTheme.titleLarge?.copyWith(
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
],
|
||||
],
|
||||
),
|
||||
),
|
||||
|
||||
// --- VOCI DI MENU ---
|
||||
Expanded(
|
||||
child: SingleChildScrollView(
|
||||
scrollDirection: Axis.horizontal,
|
||||
physics: const NeverScrollableScrollPhysics(),
|
||||
child: SizedBox(
|
||||
width: effectivelyCollapsed ? 72 : 260,
|
||||
child: ListView(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 8.0),
|
||||
children: [
|
||||
_buildRouteItem(
|
||||
title: context.l10n.commonDashboard,
|
||||
icon: Icons.dashboard_outlined,
|
||||
routeName: Routes.home, // <--- Usiamo la tua costante!
|
||||
pathToCheck:
|
||||
'/', // Il path da controllare per colorarlo
|
||||
isCollapsed: effectivelyCollapsed,
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
|
||||
// --- IL MENU GERARCHICO (ANAGRAFICHE) ---
|
||||
_buildHierarchicalItem(
|
||||
title: context.l10n.commonMasterData,
|
||||
icon: Icons.folder_special_outlined,
|
||||
basePathToCheck:
|
||||
'/master-data', // Se il path inizia così, espandi
|
||||
isCollapsed: effectivelyCollapsed,
|
||||
subItems: [
|
||||
_SubMenuItem(
|
||||
"Clienti",
|
||||
Routes.customers,
|
||||
'/master-data/customers',
|
||||
),
|
||||
_SubMenuItem(
|
||||
"Fornitori",
|
||||
Routes.providers,
|
||||
'/master-data/providers',
|
||||
),
|
||||
_SubMenuItem(
|
||||
"Prodotti",
|
||||
Routes.products,
|
||||
'/master-data/products',
|
||||
),
|
||||
],
|
||||
),
|
||||
|
||||
const SizedBox(height: 8),
|
||||
_buildRouteItem(
|
||||
title: context.l10n.commonSettings,
|
||||
icon: Icons.settings_outlined,
|
||||
routeName: Routes.settings,
|
||||
pathToCheck: '/settings',
|
||||
isCollapsed: effectivelyCollapsed,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
// --- PULSANTE TOGGLE (Solo Desktop) ---
|
||||
if (!widget.isDrawer)
|
||||
Padding(
|
||||
padding: const EdgeInsets.all(8.0),
|
||||
child: IconButton(
|
||||
tooltip: _isCollapsed ? 'Espandi Menu' : 'Riduci Menu',
|
||||
icon: Icon(
|
||||
_isCollapsed
|
||||
? Icons.keyboard_double_arrow_right
|
||||
: Icons.keyboard_double_arrow_left,
|
||||
color: theme.iconTheme.color?.withValues(alpha: 0.5),
|
||||
),
|
||||
onPressed: () {
|
||||
setState(() {
|
||||
_isCollapsed = !_isCollapsed;
|
||||
});
|
||||
},
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
// ==========================================
|
||||
// WIDGET HELPER AGGIORNATI
|
||||
// ==========================================
|
||||
|
||||
Widget _buildRouteItem({
|
||||
required String title,
|
||||
required IconData icon,
|
||||
required String routeName,
|
||||
required String pathToCheck,
|
||||
required bool isCollapsed,
|
||||
}) {
|
||||
final isSelected = widget.currentPath == pathToCheck;
|
||||
final theme = Theme.of(context);
|
||||
|
||||
if (isCollapsed) {
|
||||
return Tooltip(
|
||||
message: title,
|
||||
preferBelow: false,
|
||||
child: InkWell(
|
||||
onTap: () {
|
||||
if (widget.isDrawer) Navigator.pop(context);
|
||||
context.goNamed(routeName); // <--- goNamed!
|
||||
},
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
child: Container(
|
||||
height: 48,
|
||||
alignment: Alignment.center,
|
||||
decoration: BoxDecoration(
|
||||
color: isSelected
|
||||
? theme.colorScheme.primaryContainer.withValues(alpha: 0.4)
|
||||
: Colors.transparent,
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
),
|
||||
child: Icon(
|
||||
icon,
|
||||
color: isSelected ? theme.colorScheme.primary : null,
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
return ListTile(
|
||||
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(8)),
|
||||
selectedTileColor: theme.colorScheme.primaryContainer.withValues(
|
||||
alpha: 0.4,
|
||||
),
|
||||
selected: isSelected,
|
||||
leading: Icon(icon, color: isSelected ? theme.colorScheme.primary : null),
|
||||
title: Text(
|
||||
title,
|
||||
style: TextStyle(
|
||||
fontWeight: isSelected ? FontWeight.bold : FontWeight.normal,
|
||||
),
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.clip,
|
||||
),
|
||||
onTap: () {
|
||||
if (widget.isDrawer) Navigator.pop(context);
|
||||
context.goNamed(routeName); // <--- goNamed!
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildHierarchicalItem({
|
||||
required String title,
|
||||
required IconData icon,
|
||||
required String basePathToCheck,
|
||||
required bool isCollapsed,
|
||||
required List<_SubMenuItem> subItems,
|
||||
}) {
|
||||
final isSelected = widget.currentPath.startsWith(basePathToCheck);
|
||||
final theme = Theme.of(context);
|
||||
|
||||
if (isCollapsed) {
|
||||
return PopupMenuButton<String>(
|
||||
tooltip: title,
|
||||
offset: const Offset(60, 0),
|
||||
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)),
|
||||
onSelected: (routeName) {
|
||||
// Il routeName arriva dal value del menu
|
||||
if (widget.isDrawer) Navigator.pop(context);
|
||||
context.goNamed(routeName); // <--- goNamed!
|
||||
},
|
||||
itemBuilder: (context) => subItems
|
||||
.map(
|
||||
(item) => PopupMenuItem(
|
||||
value: item
|
||||
.routeName, // Passiamo il nome della rotta (Routes.customers)
|
||||
child: Text(
|
||||
item.title,
|
||||
style: TextStyle(
|
||||
fontWeight: widget.currentPath == item.pathToCheck
|
||||
? FontWeight.bold
|
||||
: FontWeight.normal,
|
||||
color: widget.currentPath == item.pathToCheck
|
||||
? theme.colorScheme.primary
|
||||
: null,
|
||||
),
|
||||
),
|
||||
),
|
||||
)
|
||||
.toList(),
|
||||
child: Container(
|
||||
height: 48,
|
||||
alignment: Alignment.center,
|
||||
decoration: BoxDecoration(
|
||||
color: isSelected
|
||||
? theme.colorScheme.primaryContainer.withValues(alpha: 0.4)
|
||||
: Colors.transparent,
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
),
|
||||
child: Icon(
|
||||
icon,
|
||||
color: isSelected ? theme.colorScheme.primary : null,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
return Theme(
|
||||
data: theme.copyWith(dividerColor: Colors.transparent),
|
||||
child: ExpansionTile(
|
||||
initiallyExpanded: isSelected,
|
||||
maintainState: true,
|
||||
tilePadding: const EdgeInsets.symmetric(horizontal: 16),
|
||||
leading: Icon(
|
||||
icon,
|
||||
color: isSelected ? theme.colorScheme.primary : null,
|
||||
),
|
||||
title: Text(
|
||||
title,
|
||||
style: TextStyle(
|
||||
fontWeight: isSelected ? FontWeight.bold : FontWeight.normal,
|
||||
),
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.clip,
|
||||
),
|
||||
children: subItems.map((item) {
|
||||
final isSubSelected = widget.currentPath == item.pathToCheck;
|
||||
return Padding(
|
||||
padding: const EdgeInsets.only(left: 32.0, bottom: 4.0),
|
||||
child: ListTile(
|
||||
dense: true,
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
),
|
||||
selectedTileColor: theme.colorScheme.primaryContainer.withValues(
|
||||
alpha: 0.2,
|
||||
),
|
||||
selected: isSubSelected,
|
||||
title: Text(
|
||||
item.title,
|
||||
style: TextStyle(
|
||||
fontWeight: isSubSelected
|
||||
? FontWeight.bold
|
||||
: FontWeight.normal,
|
||||
color: isSubSelected
|
||||
? theme.colorScheme.primary
|
||||
: theme.textTheme.bodyMedium?.color,
|
||||
),
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.clip,
|
||||
),
|
||||
onTap: () {
|
||||
if (widget.isDrawer) Navigator.pop(context);
|
||||
context.goNamed(item.routeName); // <--- goNamed!
|
||||
},
|
||||
),
|
||||
);
|
||||
}).toList(),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// Struttura dati per le voci dei sottomenu aggiornata
|
||||
class _SubMenuItem {
|
||||
final String title;
|
||||
final String routeName; // Es: Routes.customers
|
||||
final String pathToCheck; // Es: '/master-data/customers'
|
||||
_SubMenuItem(this.title, this.routeName, this.pathToCheck);
|
||||
}
|
||||
|
||||
@@ -126,78 +126,89 @@ class AppRouter {
|
||||
ShellRoute(
|
||||
builder: (context, state, child) => AppShell(child: child),
|
||||
routes: [
|
||||
// ==========================================
|
||||
// 1. DASHBOARD
|
||||
// ==========================================
|
||||
GoRoute(
|
||||
path: '/',
|
||||
name: Routes.home,
|
||||
builder: (context, state) => const HomeScreen(),
|
||||
),
|
||||
|
||||
// ==========================================
|
||||
// 2. HUB ANAGRAFICHE E SOTTO-ROTTE
|
||||
// ==========================================
|
||||
GoRoute(
|
||||
path: '/master-data',
|
||||
name: Routes.masterData,
|
||||
builder: (context, state) => const MasterDataHubScreen(),
|
||||
routes: [
|
||||
GoRoute(
|
||||
path: 'products', // Diventa /master-data/products
|
||||
path:
|
||||
'customers', // Niente slash iniziale per le sottorotte! -> /master-data/customers
|
||||
name: Routes.customers,
|
||||
builder: (context, state) => const CustomersListScreen(),
|
||||
),
|
||||
GoRoute(
|
||||
path: 'providers', // -> /master-data/providers
|
||||
name: Routes.providers,
|
||||
builder: (context, state) => const ProviderListScreen(),
|
||||
),
|
||||
GoRoute(
|
||||
path: 'products', // -> /master-data/products
|
||||
name: Routes.products,
|
||||
builder: (context, state) {
|
||||
context.read<ProductsCubit>().refreshCubit();
|
||||
|
||||
return const ProductsScreen();
|
||||
},
|
||||
),
|
||||
GoRoute(
|
||||
path: 'company-settings',
|
||||
path: 'staff', // -> /master-data/staff
|
||||
name: Routes.staff,
|
||||
builder: (context, state) => const StaffScreen(),
|
||||
),
|
||||
GoRoute(
|
||||
path:
|
||||
'stores', // Sistemata l'inversione path/name -> /master-data/stores
|
||||
name: Routes.stores,
|
||||
builder: (context, state) => const StoresScreen(),
|
||||
),
|
||||
GoRoute(
|
||||
path: 'company-settings', // -> /master-data/company-settings
|
||||
name: Routes.companySettings,
|
||||
builder: (context, state) => BlocProvider(
|
||||
create: (context) => CompanySettingsCubit(),
|
||||
child: const CompanySettingsScreen(),
|
||||
),
|
||||
),
|
||||
GoRoute(
|
||||
path: 'staff',
|
||||
name: Routes.staff, // Diventa /master-data/staff
|
||||
builder: (context, state) => const StaffScreen(),
|
||||
),
|
||||
GoRoute(
|
||||
path: Routes.stores,
|
||||
name: 'stores', // Diventa /master-data/stores
|
||||
builder: (context, state) => const StoresScreen(),
|
||||
),
|
||||
GoRoute(
|
||||
path: '/providers',
|
||||
name: Routes.providers,
|
||||
builder: (context, state) => const ProviderListScreen(),
|
||||
),
|
||||
],
|
||||
),
|
||||
|
||||
// ==========================================
|
||||
// 3. IMPOSTAZIONI
|
||||
// ==========================================
|
||||
GoRoute(
|
||||
path: '/settings',
|
||||
name: Routes.settings,
|
||||
builder: (context, state) => const SettingsScreen(),
|
||||
routes: [
|
||||
GoRoute(
|
||||
path: 'themeSettings',
|
||||
path: 'themeSettings', // -> /settings/themeSettings
|
||||
name: Routes.themeSettings,
|
||||
builder: (context, state) => const ThemeSettingsView(),
|
||||
),
|
||||
],
|
||||
),
|
||||
|
||||
// ==========================================
|
||||
// 4. SCHERMATE PRINCIPALI EXTRA NELLA SHELL
|
||||
// (Accessibili ad es. dalla dashboard, mantengono la sidebar)
|
||||
// ==========================================
|
||||
GoRoute(
|
||||
path: '/operations',
|
||||
name: Routes.operations,
|
||||
builder: (context, state) => const OperationListScreen(),
|
||||
),
|
||||
GoRoute(
|
||||
path: '/customers',
|
||||
name: Routes.customers,
|
||||
builder: (context, state) =>
|
||||
const CustomersListScreen(), // O come si chiama il tuo widget della lista!
|
||||
),
|
||||
GoRoute(
|
||||
path: '/tickets',
|
||||
name: Routes.tickets,
|
||||
|
||||
@@ -50,7 +50,7 @@ class CustomerRepository {
|
||||
''')
|
||||
.eq('company_id', companyId)
|
||||
.eq('is_active', true)
|
||||
.order('name');
|
||||
.order('name', ascending: true);
|
||||
|
||||
return (response as List).map((c) => CustomerModel.fromMap(c)).toList();
|
||||
} catch (e) {
|
||||
|
||||
@@ -45,13 +45,13 @@ class _LatestOperationsCardContent extends StatelessWidget {
|
||||
return Card(
|
||||
elevation: 0,
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
side: BorderSide(color: theme.dividerColor.withValues(alpha: 0.5)),
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
side: BorderSide(color: theme.dividerColor.withValues(alpha: 0.3)),
|
||||
),
|
||||
child: InkWell(
|
||||
onTap: () => context.pushNamed(Routes.operations),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(16.0),
|
||||
padding: const EdgeInsets.all(12.0),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
|
||||
@@ -12,6 +12,7 @@ import 'package:flux/features/master_data/staff/blocs/staff_cubit.dart';
|
||||
import 'package:flux/features/master_data/staff/models/staff_member_model.dart';
|
||||
import 'package:flux/features/notes/data/notes_repository.dart';
|
||||
import 'package:flux/features/notes/models/note_model.dart';
|
||||
import 'package:flux/features/notes/ui/dashboard_notes_widget.dart';
|
||||
import 'package:get_it/get_it.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
|
||||
@@ -73,13 +74,7 @@ class HomeScreen extends StatelessWidget {
|
||||
color: Colors.orange,
|
||||
context: context,
|
||||
),
|
||||
_buildDashboardWidget(
|
||||
title: context.l10n.commonStickyNotes,
|
||||
icon: Icons.sticky_note_2_outlined,
|
||||
color: Colors.yellow.shade700,
|
||||
context: context,
|
||||
onTap: () => context.pushNamed(Routes.notes),
|
||||
),
|
||||
DashboardNotesWidget(),
|
||||
_buildDashboardWidget(
|
||||
title: context.l10n.homeMyTasks,
|
||||
icon: Icons.check_box_outlined,
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:flux/core/routes/routes.dart';
|
||||
import 'package:flux/core/theme/theme.dart';
|
||||
import 'package:flux/features/notes/blocs/notes_bloc.dart';
|
||||
import 'package:flux/features/notes/models/note_model.dart';
|
||||
import 'package:go_router/go_router.dart'; // Supponendo tu usi GoRouter per la navigazione
|
||||
@@ -9,66 +11,93 @@ class DashboardNotesWidget extends StatelessWidget {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
// Intestazione del riquadro
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Text(
|
||||
'Le mie Note',
|
||||
style: Theme.of(
|
||||
context,
|
||||
).textTheme.titleLarge?.copyWith(fontWeight: FontWeight.bold),
|
||||
),
|
||||
TextButton(
|
||||
onPressed: () {
|
||||
// Vai alla bacheca completa
|
||||
context.push('/notes');
|
||||
},
|
||||
child: const Text('Vedi tutte'),
|
||||
),
|
||||
],
|
||||
return Card(
|
||||
elevation: 0,
|
||||
clipBehavior: Clip.antiAlias,
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
side: BorderSide(
|
||||
color: Theme.of(context).dividerColor.withValues(alpha: 0.3),
|
||||
style: BorderStyle.solid,
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
),
|
||||
child: InkWell(
|
||||
onTap: () => context.pushNamed(Routes.notes),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(12.0),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
// Intestazione del riquadro
|
||||
Row(
|
||||
children: [
|
||||
Container(
|
||||
padding: const EdgeInsets.all(8),
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.yellow.shade700.withValues(alpha: 0.1),
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
),
|
||||
child: Icon(
|
||||
Icons.sticky_note_2_outlined,
|
||||
size: 16,
|
||||
color: Colors.yellow.shade700,
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 12),
|
||||
Text(
|
||||
'Le mie Note',
|
||||
style: TextStyle(
|
||||
fontWeight: FontWeight.bold,
|
||||
fontSize: 16,
|
||||
color: context.primaryText,
|
||||
),
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
|
||||
// Il corpo del widget collegato al Bloc
|
||||
BlocBuilder<NotesBloc, NotesState>(
|
||||
builder: (context, state) {
|
||||
if (state.status == NotesStatus.loading && state.notes.isEmpty) {
|
||||
return const Center(child: CircularProgressIndicator());
|
||||
}
|
||||
// Il corpo del widget collegato al Bloc
|
||||
BlocBuilder<NotesBloc, NotesState>(
|
||||
builder: (context, state) {
|
||||
if (state.status == NotesStatus.loading &&
|
||||
state.notes.isEmpty) {
|
||||
return const Center(child: CircularProgressIndicator());
|
||||
}
|
||||
|
||||
if (state.status == NotesStatus.failure) {
|
||||
return const Center(
|
||||
child: Text(
|
||||
'Errore nel caricamento delle note.',
|
||||
style: TextStyle(color: Colors.red),
|
||||
),
|
||||
);
|
||||
}
|
||||
if (state.status == NotesStatus.failure) {
|
||||
return const Center(
|
||||
child: Text(
|
||||
'Errore nel caricamento delle note.',
|
||||
style: TextStyle(color: Colors.red),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
if (state.notes.isEmpty) {
|
||||
return _buildEmptyState(context);
|
||||
}
|
||||
if (state.notes.isEmpty) {
|
||||
return _buildEmptyState(context);
|
||||
}
|
||||
|
||||
// Prendiamo solo le prime 4 note per non intaccare troppo spazio in Dashboard
|
||||
final displayNotes = state.notes.take(4).toList();
|
||||
// Prendiamo solo le prime 4 note per non intaccare troppo spazio in Dashboard
|
||||
final displayNotes = state.notes.take(4).toList();
|
||||
|
||||
return SizedBox(
|
||||
height: 140, // Altezza fissa per lo scroll orizzontale
|
||||
child: ListView.builder(
|
||||
scrollDirection: Axis.horizontal,
|
||||
itemCount: displayNotes.length,
|
||||
itemBuilder: (context, index) {
|
||||
return _buildMiniPostIt(context, displayNotes[index]);
|
||||
return SizedBox(
|
||||
height: 140, // Altezza fissa per lo scroll orizzontale
|
||||
child: ListView.builder(
|
||||
scrollDirection: Axis.horizontal,
|
||||
itemCount: displayNotes.length,
|
||||
itemBuilder: (context, index) {
|
||||
return _buildMiniPostIt(context, displayNotes[index]);
|
||||
},
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
},
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -2,8 +2,6 @@ import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:flux/core/blocs/session/session_cubit.dart';
|
||||
import 'package:flux/core/utils/debouncer.dart';
|
||||
import 'package:flux/core/widgets/shared_forms/attachments_section.dart';
|
||||
import 'package:flux/features/attachments/blocs/attachments_bloc.dart';
|
||||
import 'package:flux/features/master_data/staff/blocs/staff_cubit.dart';
|
||||
import 'package:flux/features/notes/blocs/notes_bloc.dart';
|
||||
import 'package:flux/features/notes/models/note_model.dart';
|
||||
@@ -109,6 +107,11 @@ class _NoteFormScreenState extends State<NoteFormScreen> {
|
||||
}
|
||||
}
|
||||
|
||||
void _deleteNote() {
|
||||
_notesBloc.add(NoteDeletedRequested(widget.note.id!));
|
||||
Navigator.pop(context);
|
||||
}
|
||||
|
||||
void _exportNote() {
|
||||
// La logica di export che abbiamo concordato ieri!
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
@@ -298,6 +301,14 @@ class _NoteFormScreenState extends State<NoteFormScreen> {
|
||||
const SizedBox(width: 16),
|
||||
|
||||
// Azioni spostate dentro la nota!
|
||||
IconButton(
|
||||
icon: const Icon(
|
||||
Icons.delete_outline,
|
||||
color: Colors.black87,
|
||||
),
|
||||
tooltip: 'Elimina',
|
||||
onPressed: _deleteNote,
|
||||
),
|
||||
IconButton(
|
||||
icon: Icon(
|
||||
_isPinned ? Icons.push_pin : Icons.push_pin_outlined,
|
||||
@@ -369,7 +380,7 @@ class _NoteFormScreenState extends State<NoteFormScreen> {
|
||||
),
|
||||
),
|
||||
value: _isSharedAll,
|
||||
activeColor: Colors.black87,
|
||||
activeThumbColor: Colors.black87,
|
||||
contentPadding: EdgeInsets.zero,
|
||||
onChanged: (val) {
|
||||
setState(() {
|
||||
|
||||
@@ -56,7 +56,7 @@ class NotesListScreen extends StatelessWidget {
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
backgroundColor:
|
||||
Colors.grey.shade50, // Sfondo neutro per far risaltare i post-it
|
||||
Colors.grey.shade900, // Sfondo neutro per far risaltare i post-it
|
||||
appBar: AppBar(
|
||||
title: const Text(
|
||||
'Bacheca Note',
|
||||
@@ -157,7 +157,11 @@ class NotesListScreen extends StatelessWidget {
|
||||
);
|
||||
|
||||
return GestureDetector(
|
||||
onTap: () => context.push('/notes/edit', extra: note),
|
||||
onTap: () => context.pushNamed(
|
||||
Routes.noteForm,
|
||||
pathParameters: {'id': note.id!},
|
||||
extra: note,
|
||||
),
|
||||
child: Container(
|
||||
decoration: BoxDecoration(
|
||||
color: noteColor,
|
||||
|
||||
@@ -5,6 +5,7 @@ import 'package:flux/core/blocs/session/session_cubit.dart';
|
||||
import 'package:flux/core/routes/routes.dart';
|
||||
import 'package:flux/core/theme/theme.dart';
|
||||
import 'package:flux/features/settings/blocs/settings_cubit.dart';
|
||||
import 'package:get_it/get_it.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
|
||||
class SettingsScreen extends StatelessWidget {
|
||||
@@ -17,20 +18,53 @@ class SettingsScreen extends StatelessWidget {
|
||||
body: ListView(
|
||||
padding: const EdgeInsets.all(16),
|
||||
children: [
|
||||
_settingsSection('Account', [
|
||||
_settingsSection('Azienda', [
|
||||
_settingsTile(
|
||||
icon: Icons.person,
|
||||
title: 'Profilo Utente',
|
||||
subtitle: 'Configura i tuoi dati',
|
||||
title: 'Impostazioni Azienda',
|
||||
icon: Icons.business,
|
||||
subtitle: 'Configura i dati aziendali',
|
||||
context: context,
|
||||
onTap: () {},
|
||||
onTap: () => context.pushNamed(Routes.companySettings),
|
||||
),
|
||||
const Divider(height: 30),
|
||||
_settingsTile(
|
||||
title: 'Impostazione Negozi',
|
||||
icon: Icons.store,
|
||||
subtitle: 'Crea o configura i negozi',
|
||||
context: context,
|
||||
onTap: () => context.pushNamed(Routes.stores),
|
||||
),
|
||||
const Divider(height: 30),
|
||||
_settingsTile(
|
||||
title: 'Impostazione Staff / Utenti',
|
||||
icon: Icons.group,
|
||||
subtitle:
|
||||
'Configura i membri dei negozi o invita nuovi utenti in azienda',
|
||||
context: context,
|
||||
onTap: () => context.pushNamed(Routes.staff),
|
||||
),
|
||||
]),
|
||||
const SizedBox(height: 20),
|
||||
_settingsSection('Applicazione', [
|
||||
BlocBuilder<SettingsCubit, SettingsState>(
|
||||
builder: (context, state) => CheckboxListTile(
|
||||
value: state.isSingleUserMode,
|
||||
title: const Text(
|
||||
'Modalità utente singolo (dispositivo personale)',
|
||||
|
||||
title: Row(
|
||||
children: [
|
||||
const Icon(Icons.person, color: FluxColors.primaryBlue),
|
||||
const SizedBox(width: 12),
|
||||
Text(
|
||||
'Modalità utente singolo (dispositivo personale)',
|
||||
style: Theme.of(context).textTheme.titleLarge,
|
||||
),
|
||||
],
|
||||
),
|
||||
subtitle: Padding(
|
||||
padding: const EdgeInsets.only(left: 36),
|
||||
child: Text(
|
||||
'Utente ${GetIt.I.get<SessionCubit>().state.currentStaffMember?.name ?? 'Nessuno'} selezionato automaticamente',
|
||||
),
|
||||
),
|
||||
onChanged: (value) {
|
||||
context.read<SessionCubit>().setIsSingleUserMode(value!);
|
||||
@@ -39,16 +73,6 @@ class SettingsScreen extends StatelessWidget {
|
||||
),
|
||||
),
|
||||
const Divider(height: 30),
|
||||
_settingsTile(
|
||||
title: 'Impostazioni Azienda',
|
||||
icon: Icons.business,
|
||||
subtitle: 'Configura i dati aziendali',
|
||||
context: context,
|
||||
onTap: () => context.pushNamed(Routes.companySettings),
|
||||
),
|
||||
]),
|
||||
const SizedBox(height: 16),
|
||||
_settingsSection('Applicazione', [
|
||||
_settingsTile(
|
||||
icon: Icons.dark_mode,
|
||||
title: 'Tema (FLUX Dark)',
|
||||
|
||||
@@ -31,7 +31,7 @@ class TicketRepository {
|
||||
.from(_tableName)
|
||||
.select('''
|
||||
*,
|
||||
${Tables.customers} (*),
|
||||
customer:${Tables.customers}!ticket_customer_id_fkey (*),
|
||||
${Tables.shippingDocuments} (*, ${Tables.attachments} (*)),
|
||||
created_by:${Tables.staffMembers}!ticket_staff_id_fkey (*),
|
||||
assigned_to:${Tables.staffMembers}!ticket_assigned_to_id_fkey (*),
|
||||
@@ -89,7 +89,7 @@ class TicketRepository {
|
||||
.from(_tableName)
|
||||
.select('''
|
||||
*,
|
||||
${Tables.customers} (*),
|
||||
customer:${Tables.customers}!ticket_customer_id_fkey (*),
|
||||
${Tables.shippingDocuments} (*, ${Tables.attachments} (*)),
|
||||
created_by:${Tables.staffMembers}!ticket_staff_id_fkey (*),
|
||||
assigned_to:${Tables.staffMembers}!ticket_assigned_to_id_fkey (*),
|
||||
@@ -200,7 +200,7 @@ class TicketRepository {
|
||||
.from(_tableName)
|
||||
.select('''
|
||||
*,
|
||||
${Tables.customers} (*),
|
||||
customer:${Tables.customers}!ticket_customer_id_fkey (*),
|
||||
target_model:${Tables.models}!ticket_model_id_1_fkey (*),
|
||||
source_model:${Tables.models}!ticket_model_id_2_fkey (*),
|
||||
created_by:${Tables.staffMembers}!ticket_staff_id_fkey (*),
|
||||
|
||||
@@ -55,6 +55,12 @@ class _TicketFormScreenState extends State<TicketFormScreen> {
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
// TRUCCO ANTI-RACE-CONDITION:
|
||||
// Se il ticket arriva già "pronto" (via extra), popoliamo i controller SUBITO,
|
||||
// senza aspettare il listener del BLoC che si perderebbe l'emissione sincrona.
|
||||
if (widget.existingTicket != null) {
|
||||
_syncTextControllers(widget.existingTicket!);
|
||||
}
|
||||
context.read<TicketFormCubit>().initForm(
|
||||
existingTicket: widget.existingTicket,
|
||||
id: widget.ticketId,
|
||||
|
||||
41
pubspec.lock
41
pubspec.lock
@@ -125,10 +125,10 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: code_assets
|
||||
sha256: "83ccdaa064c980b5596c35dd64a8d3ecc68620174ab9b90b6343b753aa721687"
|
||||
sha256: dad6bf6b9f4f378b0a69edbf42584d336efd1a9ce15deb1ba591cbb1b5ff440f
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.0.0"
|
||||
version: "1.1.0"
|
||||
collection:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
@@ -365,14 +365,6 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "9.2.1"
|
||||
glob:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: glob
|
||||
sha256: c3f1ee72c96f8f78935e18aa8cecced9ab132419e8625dc187e1c2408efc20de
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.1.3"
|
||||
go_router:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
@@ -409,10 +401,10 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: hooks
|
||||
sha256: "025f060e86d2d4c3c47b56e33caf7f93bf9283340f26d23424ebcfccf34f621e"
|
||||
sha256: a41af4e8fc687cd6d33de9751eb936c8c0204ebe2bcb6c15ecf707504bf47f31
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.0.3"
|
||||
version: "2.0.0"
|
||||
http:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -621,14 +613,6 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.0.0"
|
||||
native_toolchain_c:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: native_toolchain_c
|
||||
sha256: "6ba77bb18063eebe9de401f5e6437e95e1438af0a87a3a39084fbd37c90df572"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.17.6"
|
||||
nested:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -641,10 +625,10 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: objective_c
|
||||
sha256: "100a1c87616ab6ed41ec263b083c0ef3261ee6cd1dc3b0f35f8ddfa4f996fe52"
|
||||
sha256: "6cb691c686fa2838c6deb34980d426145c2a5d537491cb83d463c33cdbc726ed"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "9.3.0"
|
||||
version: "9.4.1"
|
||||
package_config:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -752,10 +736,11 @@ packages:
|
||||
pdfx:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: pdfx
|
||||
sha256: "29db9b71d46bf2335e001f91693f2c3fbbf0760e4c2eb596bf4bafab211471c1"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
path: "packages/pdfx"
|
||||
ref: main
|
||||
resolved-ref: "900ad0799ebd12d6c87189275fcb6155ce8a9374"
|
||||
url: "https://github.com/ScerIO/packages.flutter.git"
|
||||
source: git
|
||||
version: "2.9.2"
|
||||
permission_handler:
|
||||
dependency: "direct main"
|
||||
@@ -1118,10 +1103,10 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: url_launcher_android
|
||||
sha256: "3bb000251e55d4a209aa0e2e563309dc9bb2befea2295fd0cec1f51760aac572"
|
||||
sha256: "17bc677f0b301615530dd1d67e0a9828cafa2d0b6b6eae4cd3679b7eac4a273c"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "6.3.29"
|
||||
version: "6.3.30"
|
||||
url_launcher_ios:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
||||
10
pubspec.yaml
10
pubspec.yaml
@@ -1,7 +1,7 @@
|
||||
name: flux
|
||||
description: "Gestione attività negozio di telefonia"
|
||||
publish_to: 'none'
|
||||
version: 1.0.6+6
|
||||
version: 1.0.12+12
|
||||
|
||||
environment:
|
||||
sdk: ^3.11.3
|
||||
@@ -39,6 +39,14 @@ dependencies:
|
||||
flutter_launcher_icons: ^0.14.4
|
||||
package_info_plus: ^9.0.1
|
||||
flutter_staggered_grid_view: ^0.7.0
|
||||
|
||||
dependency_overrides:
|
||||
pdfx:
|
||||
git:
|
||||
url: https://github.com/ScerIO/packages.flutter.git
|
||||
ref: main
|
||||
path: packages/pdfx
|
||||
|
||||
dev_dependencies:
|
||||
flutter_test:
|
||||
sdk: flutter
|
||||
|
||||
@@ -9,7 +9,7 @@ DefaultDirName={autopf}\Flux
|
||||
|
||||
; Cartella di output dell'installer e nome del file generato
|
||||
OutputDir=.\build\windows\installer
|
||||
OutputBaseFilename=FLUX_Setup_v{#MyAppVersion}
|
||||
OutputBaseFilename=FluxInstaller
|
||||
|
||||
; Compressione e impostazioni grafiche
|
||||
Compression=lzma2/max
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
# Project-level configuration.
|
||||
cmake_minimum_required(VERSION 3.14)
|
||||
project(flux LANGUAGES CXX)
|
||||
add_compile_definitions(_SILENCE_EXPERIMENTAL_COROUTINE_DEPRECATION_WARNINGS)
|
||||
|
||||
# The name of the executable created for the application. Change this to change
|
||||
# the on-disk name of your application.
|
||||
|
||||
Reference in New Issue
Block a user