From d8125fc647c13b41118be966f2e7e72170bcc1ae Mon Sep 17 00:00:00 2001 From: Deepak8858 Date: Tue, 17 Feb 2026 18:20:21 +0000 Subject: [PATCH] feat: add document history gallery and onboarding persistence --- lib/main.dart | 69 ++++++------- lib/models/scanned_document.dart | 42 ++++++++ lib/screens/home_screen.dart | 170 +++++++++++++++++++++++++++++++ pubspec.yaml | 2 + 4 files changed, 243 insertions(+), 40 deletions(-) create mode 100644 lib/models/scanned_document.dart create mode 100644 lib/screens/home_screen.dart diff --git a/lib/main.dart b/lib/main.dart index 65b5748..bb4da38 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -1,5 +1,8 @@ import 'package:flutter/material.dart'; import 'package:flutter_genius_scan/flutter_genius_scan.dart'; +import 'package:shared_preferences/shared_preferences.dart'; +import 'screens/home_screen.dart'; +import 'package:flutter_dotenv/flutter_dotenv.dart' as DotEnv; import 'components/care_view.dart'; import 'components/center_next_button.dart'; import 'components/mood_diary_vew.dart'; @@ -7,28 +10,36 @@ import 'components/relax_view.dart'; import 'components/splash_view.dart'; import 'components/top_back_skip_view.dart'; import 'components/welcome_view.dart'; -import 'package:flutter_dotenv/flutter_dotenv.dart' as DotEnv; void main() async { + WidgetsFlutterBinding.ensureInitialized(); await DotEnv.dotenv.load(fileName: ".env"); - runApp(MyApp()); -} - -class MyApp extends StatefulWidget { - @override - _MyAppState createState() => _MyAppState(); + + final prefs = await SharedPreferences.getInstance(); + final bool isFirstRun = prefs.getBool('isFirstRun') ?? true; + + runApp(MyApp(isFirstRun: isFirstRun)); } -class _MyAppState extends State { +class MyApp extends StatelessWidget { + final bool isFirstRun; + + const MyApp({Key? key, required this.isFirstRun}) : super(key: key); @override Widget build(BuildContext context) { return MaterialApp( - home: Scaffold( - body: IntroductionAnimationScreen()), + title: 'Paper', + debugShowCheckedModeBanner: false, + theme: ThemeData( + primarySwatch: Colors.blue, + visualDensity: VisualDensity.adaptivePlatformDensity, + ), + home: isFirstRun ? IntroductionAnimationScreen() : HomeScreen(), ); } } + class IntroductionAnimationScreen extends StatefulWidget { const IntroductionAnimationScreen({Key? key}) : super(key: key); @@ -60,7 +71,6 @@ class _IntroductionAnimationScreenState @override Widget build(BuildContext context) { - print(_animationController?.value); return Scaffold( backgroundColor: Color(0xffF7EBE1), body: ClipRect( @@ -132,37 +142,16 @@ class _IntroductionAnimationScreenState _animationController?.animateTo(0.8); } else if (_animationController!.value > 0.6 && _animationController!.value <= 0.8) { - _signUpClick(); + _finishOnboarding(); } } - void _signUpClick() { - Navigator.pop(context); + void _finishOnboarding() async { + final prefs = await SharedPreferences.getInstance(); + await prefs.setBool('isFirstRun', false); + Navigator.pushReplacement( + context, + MaterialPageRoute(builder: (context) => HomeScreen()), + ); } } - -// class MyScaffoldBody extends StatelessWidget { -// @override -// Widget build(BuildContext context) { -// return Center( -// child: ElevatedButton( -// onPressed: () { -// FlutterGeniusScan.scanWithConfiguration({ -// 'source': 'camera', -// 'multiPage': true, -// }).then((result) { -// String pdfUrl = result['pdfUrl']; -// OpenFile.open(pdfUrl.replaceAll("file://", '')).then( -// (result) => debugPrint(result.message), -// onError: (error) => displayError(context, error)); -// }, onError: (error) => displayError(context, error)); -// }, -// child: Text("Scan a doc"), -// )); -// } -// -// void displayError(BuildContext context, PlatformException error) { -// ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text(error.message!))); -// } -// } - diff --git a/lib/models/scanned_document.dart b/lib/models/scanned_document.dart new file mode 100644 index 0000000..69e5938 --- /dev/null +++ b/lib/models/scanned_document.dart @@ -0,0 +1,42 @@ +import 'dart:convert'; + +class ScannedDocument { + final String id; + final String name; + final String filePath; + final String thumbnailPath; + final DateTime dateTime; + + ScannedDocument({ + required this.id, + required this.name, + required this.filePath, + required this.thumbnailPath, + required this.dateTime, + }); + + Map toMap() { + return { + 'id': id, + 'name': name, + 'filePath': filePath, + 'thumbnailPath': thumbnailPath, + 'dateTime': dateTime.toIso8601String(), + }; + } + + factory ScannedDocument.fromMap(Map map) { + return ScannedDocument( + id: map['id'], + name: map['name'], + filePath: map['filePath'], + thumbnailPath: map['thumbnailPath'], + dateTime: DateTime.parse(map['dateTime']), + ); + } + + String toJson() => json.encode(toMap()); + + factory ScannedDocument.fromJson(String source) => + ScannedDocument.fromMap(json.decode(source)); +} diff --git a/lib/screens/home_screen.dart b/lib/screens/home_screen.dart new file mode 100644 index 0000000..fe76a0f --- /dev/null +++ b/lib/screens/home_screen.dart @@ -0,0 +1,170 @@ +import 'dart:io'; +import 'package:flutter/material.dart'; +import 'package:flutter_genius_scan/flutter_genius_scan.dart'; +import 'package:open_file/open_file.dart'; +import 'package:shared_preferences/shared_preferences.dart'; +import 'package:path_provider/path_provider.dart'; +import '../models/scanned_document.dart'; +import 'dart:convert'; + +class HomeScreen extends StatefulWidget { + @override + _HomeScreenState createState() => _HomeScreenState(); +} + +class _HomeScreenState extends State { + List _documents = []; + + @override + void initState() { + super.initState(); + _loadDocuments(); + } + + Future _loadDocuments() async { + final prefs = await SharedPreferences.getInstance(); + final String? docsJson = prefs.getString('scanned_documents'); + if (docsJson != null) { + final List decoded = json.decode(docsJson); + setState(() { + _documents = decoded.map((item) => ScannedDocument.fromMap(item)).toList(); + // Sort by date descending + _documents.sort((a, b) => b.dateTime.compareTo(a.dateTime)); + }); + } + } + + Future _saveDocuments() async { + final prefs = await SharedPreferences.getInstance(); + final String encoded = json.encode(_documents.map((doc) => doc.toMap()).toList()); + await prefs.setString('scanned_documents', encoded); + } + + void _startScan() async { + try { + final result = await FlutterGeniusScan.scanWithConfiguration({ + 'source': 'camera', + 'multiPage': true, + }); + + final String pdfUrl = result['pdfUrl']; + final String filePath = pdfUrl.replaceAll("file://", ""); + + // For simplicity in this feature, we'll use the PDF itself or a placeholder as thumbnail + // Real implementation would extract a thumbnail image. + final newDoc = ScannedDocument( + id: DateTime.now().millisecondsSinceEpoch.toString(), + name: "Scan ${DateTime.now().toString().split('.')[0]}", + filePath: filePath, + thumbnailPath: filePath, // Placeholder + dateTime: DateTime.now(), + ); + + setState(() { + _documents.insert(0, newDoc); + }); + _saveDocuments(); + + OpenFile.open(filePath); + } catch (e) { + ScaffoldMessenger.of(context).showSnackBar( + SnackBar(content: Text("Scan failed: $e")), + ); + } + } + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + title: Text("My Documents", style: TextStyle(color: Colors.black, fontWeight: FontWeight.bold)), + backgroundColor: Colors.white, + elevation: 0, + centerTitle: true, + ), + backgroundColor: Colors.white, + body: _documents.isEmpty + ? Center( + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Icon(Icons.description_outlined, size: 64, color: Colors.grey), + SizedBox(height: 16), + Text("No scans yet", style: TextStyle(color: Colors.grey, fontSize: 18)), + ], + ), + ) + : Padding( + padding: const EdgeInsets.all(16.0), + child: GridView.builder( + gridDelegate: SliverGridDelegateWithFixedCrossAxisCount( + crossAxisCount: 2, + crossAxisSpacing: 16, + mainAxisSpacing: 16, + childAspectRatio: 0.8, + ), + itemCount: _documents.length, + itemBuilder: (context, index) { + final doc = _documents[index]; + return GestureDetector( + onTap: () => OpenFile.open(doc.filePath), + child: Container( + decoration: BoxDecoration( + color: Colors.grey[100], + borderRadius: BorderRadius.circular(12), + boxShadow: [ + BoxShadow( + color: Colors.black.withOpacity(0.05), + blurRadius: 5, + offset: Offset(0, 2), + ), + ], + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Expanded( + child: Container( + decoration: BoxDecoration( + color: Colors.grey[300], + borderRadius: BorderRadius.vertical(top: Radius.circular(12)), + ), + child: Center( + child: Icon(Icons.picture_as_pdf, size: 48, color: Colors.red[400]), + ), + ), + ), + Padding( + padding: const EdgeInsets.all(8.0), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + doc.name, + style: TextStyle(fontWeight: FontWeight.bold, fontSize: 14), + maxLines: 1, + overflow: TextOverflow.ellipsis, + ), + Text( + "${doc.dateTime.day}/${doc.dateTime.month}/${doc.dateTime.year}", + style: TextStyle(color: Colors.grey, fontSize: 12), + ), + ], + ), + ), + ], + ), + ), + ); + }, + ), + ), + floatingActionButton: FloatingActionButton.extended( + onPressed: _startScan, + label: Text("Scan"), + icon: Icon(Icons.camera_alt), + backgroundColor: Colors.black, + ), + ); + } +} diff --git a/pubspec.yaml b/pubspec.yaml index efbef9b..e349bf1 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -37,6 +37,8 @@ dependencies: font_awesome_flutter: ^9.0.0 flutter_rating_bar: ^4.0.0 intl: ^0.17.0 + shared_preferences: ^2.0.12 + path_provider: ^2.0.8 dev_dependencies: