feat: enhance UI, branding assets, onboarding flow, and screen layouts
This commit is contained in:
@@ -1,12 +1,16 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
import 'package:image_picker/image_picker.dart';
|
||||
import 'package:intl/intl.dart';
|
||||
import 'package:lucide_icons_flutter/lucide_icons.dart';
|
||||
|
||||
import '../core/theme.dart';
|
||||
import '../core/permissions.dart';
|
||||
import '../models/compliance.dart';
|
||||
import '../state/compliance_controller.dart';
|
||||
import '../state/session_controller.dart';
|
||||
import '../widgets/common.dart';
|
||||
|
||||
IconData _sectionIcon(ComplianceSection s) => switch (s) {
|
||||
@@ -299,8 +303,12 @@ class ComplianceDetailScreen extends ConsumerWidget {
|
||||
final ComplianceSection section;
|
||||
final String itemId;
|
||||
|
||||
/// Picks a real file: the camera for a new photo, the gallery for an
|
||||
/// existing one. Camera access is requested first, because image_picker
|
||||
/// surfaces a refusal as a plain null and the worker would be left tapping a
|
||||
/// button that silently does nothing.
|
||||
Future<void> _upload(BuildContext context, WidgetRef ref) async {
|
||||
final choice = await showModalBottomSheet<String>(
|
||||
final source = await showModalBottomSheet<ImageSource>(
|
||||
context: context,
|
||||
showDragHandle: true,
|
||||
builder: (context) => SafeArea(
|
||||
@@ -311,29 +319,67 @@ class ComplianceDetailScreen extends ConsumerWidget {
|
||||
leading: const Icon(LucideIcons.camera),
|
||||
title: const Text('Take a photo'),
|
||||
subtitle: const Text('It has to be the real item, worn by you'),
|
||||
onTap: () => Navigator.pop(context, 'camera'),
|
||||
onTap: () => Navigator.pop(context, ImageSource.camera),
|
||||
),
|
||||
ListTile(
|
||||
leading: const Icon(LucideIcons.image),
|
||||
title: const Text('Choose from library'),
|
||||
onTap: () => Navigator.pop(context, 'library'),
|
||||
),
|
||||
ListTile(
|
||||
leading: const Icon(LucideIcons.file),
|
||||
title: const Text('Pick a PDF'),
|
||||
onTap: () => Navigator.pop(context, 'file'),
|
||||
onTap: () => Navigator.pop(context, ImageSource.gallery),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
if (choice == null || !context.mounted) return;
|
||||
if (source == null || !context.mounted) return;
|
||||
|
||||
final ext = choice == 'file' ? 'pdf' : 'jpg';
|
||||
final slug = itemId.toLowerCase();
|
||||
if (source == ImageSource.camera) {
|
||||
final granted = await ref
|
||||
.read(sessionProvider.notifier)
|
||||
.request(AppPermission.camera);
|
||||
if (!context.mounted) return;
|
||||
if (!granted) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(
|
||||
content: const Text('Camera access is off'),
|
||||
action: SnackBarAction(
|
||||
label: 'Settings',
|
||||
onPressed: ref.read(permissionServiceProvider).openSettings,
|
||||
),
|
||||
),
|
||||
);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
final XFile? picked;
|
||||
try {
|
||||
picked = await ImagePicker().pickImage(
|
||||
source: source,
|
||||
// Documents only need to be legible, and a full-resolution phone
|
||||
// photo is a slow upload on a venue's wifi.
|
||||
maxWidth: 2000,
|
||||
imageQuality: 85,
|
||||
);
|
||||
} on PlatformException catch (error) {
|
||||
if (!context.mounted) return;
|
||||
ScaffoldMessenger.of(
|
||||
context,
|
||||
).showSnackBar(SnackBar(content: Text('Could not open: ${error.code}')));
|
||||
return;
|
||||
}
|
||||
// Null means the picker was dismissed, which is not an error.
|
||||
if (picked == null || !context.mounted) return;
|
||||
|
||||
final size = await picked.length();
|
||||
if (!context.mounted) return;
|
||||
ref
|
||||
.read(complianceProvider.notifier)
|
||||
.uploadFile(section, itemId, 'krow-$slug.$ext');
|
||||
.uploadFile(
|
||||
section,
|
||||
itemId,
|
||||
picked.name,
|
||||
meta: '${(size / 1024 / 1024).toStringAsFixed(1)} MB · just now',
|
||||
);
|
||||
ScaffoldMessenger.of(
|
||||
context,
|
||||
).showSnackBar(const SnackBar(content: Text('Uploaded - sent for review')));
|
||||
|
||||
Reference in New Issue
Block a user