Compare commits

...
5 Commits
Author SHA1 Message Date
riccardoandClaude Opus 5 bdaecd4d26 Convert HEIC gallery photos to JPEG on iOS
Use the compatible asset representation so the picker transcodes HEIC
to JPEG before upload, a format the backend resizes and browsers display.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-16 16:16:13 +02:00
riccardoandClaude Opus 5 e2ed8547c5 Set Apple team ID for iOS signing
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-16 16:16:13 +02:00
riccardoandClaude Opus 5 38a868a962 Add back button to dashboard header
The dashboard is a hidden tab screen with the tab bar hidden, so on iOS
there was no way to return home (no hardware back, no swipe gesture on
tabs). Match the construction-site header pattern.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-16 15:11:55 +02:00
riccardoandClaude Opus 5 5c66e9d5cb Upgrade to Expo SDK 57 for Xcode 27 support
- expo 57.0.23, react-native 0.86.3, react 19.2.3
- Remove @react-navigation/* direct deps (unsupported by expo-router 56+)
- Drop newArchEnabled and android.edgeToEdgeEnabled (removed from schema)
- Enable ios.enableSceneSupport (UIScene lifecycle required by iOS 27 SDK)
- Remove deprecated expo-build-properties deploymentTarget (default 16.4)
- Add withPodsDeploymentTarget plugin: raise CocoaPods resource bundle
  targets (RNCAsyncStorage, RNSVG, SDWebImage) to the app minimum
- Replace removed StyleSheet.absoluteFillObject with absoluteFill

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-16 12:54:53 +02:00
riccardoandClaude Opus 5 2653c7cad6 Add expo-build-properties, rename slug and bundle ids
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-16 12:41:32 +02:00
7 changed files with 2171 additions and 3000 deletions
+19 -6
View File
@@ -1,29 +1,28 @@
{ {
"expo": { "expo": {
"name": "Fonsi Costruzioni", "name": "Fonsi Costruzioni",
"slug": "fonsi_app", "slug": "fonsi-app",
"version": "1.0", "version": "1.0",
"orientation": "portrait", "orientation": "portrait",
"icon": "./assets/images/icon.png", "icon": "./assets/images/icon.png",
"scheme": "fonsiapp", "scheme": "fonsiapp",
"userInterfaceStyle": "automatic", "userInterfaceStyle": "automatic",
"newArchEnabled": true,
"ios": { "ios": {
"supportsTablet": true, "supportsTablet": true,
"bundleIdentifier": "com.pcrt.fonsi-app" "bundleIdentifier": "com.pcrt.fonsi.app",
"appleTeamId": "T96LGS238W"
}, },
"android": { "android": {
"adaptiveIcon": { "adaptiveIcon": {
"foregroundImage": "./assets/images/adaptive-icon.png", "foregroundImage": "./assets/images/adaptive-icon.png",
"backgroundColor": "#ffffff" "backgroundColor": "#ffffff"
}, },
"edgeToEdgeEnabled": true,
"predictiveBackGestureEnabled": false, "predictiveBackGestureEnabled": false,
"permissions": [ "permissions": [
"android.permission.CAMERA", "android.permission.CAMERA",
"android.permission.RECORD_AUDIO" "android.permission.RECORD_AUDIO"
], ],
"package": "com.pcrt.fonsi_app" "package": "com.pcrt.fonsi.app"
}, },
"web": { "web": {
"output": "static", "output": "static",
@@ -52,7 +51,21 @@
"recordAudioAndroid": true "recordAudioAndroid": true
} }
], ],
"expo-font" "expo-font",
[
"expo-build-properties",
{
"ios": {
"enableSceneSupport": true
}
}
],
"expo-image",
"expo-secure-store",
"expo-sharing",
"expo-status-bar",
"expo-web-browser",
"./plugins/withPodsDeploymentTarget"
], ],
"experiments": { "experiments": {
"typedRoutes": true, "typedRoutes": true,
+2
View File
@@ -117,6 +117,8 @@ export default function ActivityFormScreen() {
allowsMultipleSelection: true, allowsMultipleSelection: true,
selectionLimit: limit, selectionLimit: limit,
quality: 0.8, quality: 0.8,
// iOS: converte HEIC in JPEG, formato gestito dal backend e visualizzabile sul web
preferredAssetRepresentationMode: ImagePicker.UIImagePickerPreferredAssetRepresentationMode.Compatible,
}); });
if (!result.canceled && result.assets) { if (!result.canceled && result.assets) {
+10 -2
View File
@@ -2,7 +2,8 @@ import React, { useState, useEffect, useMemo, useRef } from 'react';
import { View, Text, ScrollView, TouchableOpacity, ActivityIndicator, Dimensions } from 'react-native'; import { View, Text, ScrollView, TouchableOpacity, ActivityIndicator, Dimensions } from 'react-native';
import { SafeAreaView } from 'react-native-safe-area-context'; import { SafeAreaView } from 'react-native-safe-area-context';
import { StatusBar } from 'expo-status-bar'; import { StatusBar } from 'expo-status-bar';
import { ChevronDown, ChevronUp, LayoutDashboard } from 'lucide-react-native'; import { useRouter } from 'expo-router';
import { ChevronDown, ChevronLeft, ChevronUp, LayoutDashboard } from 'lucide-react-native';
import api from '@/utils/api'; import api from '@/utils/api';
import EchartWrapper from '@/components/EchartWrapper'; import EchartWrapper from '@/components/EchartWrapper';
import { import {
@@ -22,6 +23,7 @@ const TOOLTIP_BASE = {
const screenWidth = Dimensions.get("window").width; const screenWidth = Dimensions.get("window").width;
export default function DashboardScreen() { export default function DashboardScreen() {
const router = useRouter();
const [chartData, setChartData] = useState<any>({ const [chartData, setChartData] = useState<any>({
costiRicavi: null, costiRicavi: null,
@@ -206,7 +208,13 @@ export default function DashboardScreen() {
<SafeAreaView edges={['top']} className="pt-5"> <SafeAreaView edges={['top']} className="pt-5">
<View className="pb-6 px-6 z-10 flex-row justify-between items-center"> <View className="pb-6 px-6 z-10 flex-row justify-between items-center">
<View> <TouchableOpacity
onPress={() => router.navigate('/')}
className="p-2 -ml-2 rounded-full active:bg-white/20 w-12 items-center justify-center"
>
<ChevronLeft size={28} color="white" pointerEvents="none" />
</TouchableOpacity>
<View className="flex-1 px-2">
<Text className="text-white text-md font-semibold uppercase tracking-wider mb-1">Grafici e Statistiche</Text> <Text className="text-white text-md font-semibold uppercase tracking-wider mb-1">Grafici e Statistiche</Text>
<Text className="text-yellow-400 text-3xl font-bold leading-tight">Dashboard</Text> <Text className="text-yellow-400 text-3xl font-bold leading-tight">Dashboard</Text>
</View> </View>
+3 -3
View File
@@ -52,7 +52,7 @@ export default function QrScanModal({ visible, onClose, onScan }: QrScanModalPro
<View className="flex-1 bg-black"> <View className="flex-1 bg-black">
{/* Camera Full Screen */} {/* Camera Full Screen */}
<CameraView <CameraView
style={StyleSheet.absoluteFillObject} style={StyleSheet.absoluteFill}
facing="back" facing="back"
onBarcodeScanned={scanned ? undefined : handleBarCodeScanned} onBarcodeScanned={scanned ? undefined : handleBarCodeScanned}
barcodeScannerSettings={{ barcodeScannerSettings={{
@@ -61,7 +61,7 @@ export default function QrScanModal({ visible, onClose, onScan }: QrScanModalPro
/> />
{/* Mask Overlay with transparent cutout */} {/* Mask Overlay with transparent cutout */}
<View style={StyleSheet.absoluteFillObject} pointerEvents="none"> <View style={StyleSheet.absoluteFill} pointerEvents="none">
<View className="flex-1 bg-black/60" /> <View className="flex-1 bg-black/60" />
<View style={{ height: squareSize }} className="flex-row"> <View style={{ height: squareSize }} className="flex-row">
<View className="flex-1 bg-black/60" /> <View className="flex-1 bg-black/60" />
@@ -85,7 +85,7 @@ export default function QrScanModal({ visible, onClose, onScan }: QrScanModalPro
</View> </View>
{/* UI Overlay */} {/* UI Overlay */}
<SafeAreaView style={StyleSheet.absoluteFillObject} pointerEvents="box-none"> <SafeAreaView style={StyleSheet.absoluteFill} pointerEvents="box-none">
<View className="flex-1 justify-between pt-8"> <View className="flex-1 justify-between pt-8">
{/* Header */} {/* Header */}
<View className="items-center"> <View className="items-center">
+2067 -2951
View File
File diff suppressed because it is too large Load Diff
+36 -38
View File
@@ -1,5 +1,5 @@
{ {
"name": "fonsi_app", "name": "fonsi-app",
"main": "expo-router/entry", "main": "expo-router/entry",
"version": "1.0", "version": "1.0",
"scripts": { "scripts": {
@@ -17,56 +17,54 @@
"dependencies": { "dependencies": {
"@expo/vector-icons": "^15.0.3", "@expo/vector-icons": "^15.0.3",
"@react-native-async-storage/async-storage": "2.2.0", "@react-native-async-storage/async-storage": "2.2.0",
"@react-native-community/netinfo": "11.4.1", "@react-native-community/netinfo": "12.0.1",
"@react-navigation/bottom-tabs": "^7.4.0",
"@react-navigation/elements": "^2.6.3",
"@react-navigation/native": "^7.1.8",
"@wuba/react-native-echarts": "^3.1.1", "@wuba/react-native-echarts": "^3.1.1",
"axios": "^1.13.2", "axios": "^1.13.2",
"babel-preset-expo": "~54.0.10", "babel-preset-expo": "~57.0.0",
"echarts": "^5.5.0", "echarts": "^5.5.0",
"expo": "~54.0.36", "expo": "~57.0.23",
"expo-camera": "~17.0.10", "expo-build-properties": "~57.0.20",
"expo-constants": "~18.0.10", "expo-camera": "~57.0.5",
"expo-dev-client": "~6.0.20", "expo-constants": "~57.0.18",
"expo-document-picker": "~14.0.8", "expo-dev-client": "~57.0.19",
"expo-file-system": "~19.0.23", "expo-document-picker": "~57.0.2",
"expo-font": "~14.0.12", "expo-file-system": "~57.0.7",
"expo-haptics": "~15.0.7", "expo-font": "~57.0.4",
"expo-image": "~3.0.10", "expo-haptics": "~57.0.3",
"expo-image-picker": "~17.0.11", "expo-image": "~57.0.5",
"expo-linking": "~8.0.11", "expo-image-picker": "~57.0.18",
"expo-router": "~6.0.24", "expo-linking": "~57.0.10",
"expo-secure-store": "~15.0.8", "expo-router": "~57.0.21",
"expo-sharing": "~14.0.8", "expo-secure-store": "~57.0.4",
"expo-splash-screen": "~31.0.11", "expo-sharing": "~57.0.20",
"expo-status-bar": "~3.0.8", "expo-splash-screen": "~57.0.9",
"expo-symbols": "~1.0.7", "expo-status-bar": "~57.0.1",
"expo-system-ui": "~6.0.8", "expo-symbols": "~57.0.3",
"expo-web-browser": "~15.0.9", "expo-system-ui": "~57.0.4",
"expo-web-browser": "~57.0.3",
"lucide-react-native": "^0.563.0", "lucide-react-native": "^0.563.0",
"nativewind": "^4.2.1", "nativewind": "^4.2.1",
"prettier-plugin-tailwindcss": "^0.5.14", "prettier-plugin-tailwindcss": "^0.5.14",
"react": "19.1.0", "react": "19.2.3",
"react-dom": "19.1.0", "react-dom": "19.2.3",
"react-native": "0.81.5", "react-native": "0.86.3",
"react-native-gesture-handler": "~2.28.0", "react-native-gesture-handler": "~2.32.0",
"react-native-image-viewing": "^0.2.2", "react-native-image-viewing": "^0.2.2",
"react-native-keyboard-controller": "1.18.5", "react-native-keyboard-controller": "1.21.9",
"react-native-reanimated": "~4.1.1", "react-native-reanimated": "4.5.1",
"react-native-safe-area-context": "~5.6.0", "react-native-safe-area-context": "~5.7.0",
"react-native-screens": "~4.16.0", "react-native-screens": "~4.26.0",
"react-native-svg": "15.12.1", "react-native-svg": "15.15.4",
"react-native-ui-datepicker": "^3.1.2", "react-native-ui-datepicker": "^3.1.2",
"react-native-web": "~0.21.0", "react-native-web": "~0.21.0",
"react-native-worklets": "0.5.1", "react-native-worklets": "0.10.1",
"tailwindcss": "^3.4.18" "tailwindcss": "^3.4.18"
}, },
"devDependencies": { "devDependencies": {
"@types/react": "~19.1.0", "@types/react": "~19.2.14",
"eslint": "^9.25.0", "eslint": "^9.25.0",
"eslint-config-expo": "~10.0.0", "eslint-config-expo": "~57.0.2",
"typescript": "~5.9.2" "typescript": "~6.0.3"
}, },
"private": true "private": true
} }
+34
View File
@@ -0,0 +1,34 @@
const { withPodfile } = require('expo/config-plugins');
const MARKER = '# @generated withPodsDeploymentTarget';
// CocoaPods resource bundle targets (e.g. RNCAsyncStorage_resources, RNSVGFilters)
// keep the deployment target declared in their podspec, ignoring `platform :ios`.
// Xcode 27 rejects targets below iOS 15, so raise every pod target to the app's minimum.
const SNIPPET = `
${MARKER}
min_ios_target = podfile_properties['ios.deploymentTarget'] || '16.4'
installer.pods_project.targets.each do |target|
target.build_configurations.each do |build_config|
current = build_config.build_settings['IPHONEOS_DEPLOYMENT_TARGET']
if current.nil? || Gem::Version.new(current) < Gem::Version.new(min_ios_target)
build_config.build_settings['IPHONEOS_DEPLOYMENT_TARGET'] = min_ios_target
end
end
end
`;
module.exports = function withPodsDeploymentTarget(config) {
return withPodfile(config, (config) => {
const podfile = config.modResults;
if (podfile.contents.includes(MARKER)) {
return config;
}
const anchor = /post_install do \|installer\|\n/;
if (!anchor.test(podfile.contents)) {
throw new Error('withPodsDeploymentTarget: post_install block not found in Podfile');
}
podfile.contents = podfile.contents.replace(anchor, (match) => match + SNIPPET);
return config;
});
};