1- import 'dart:convert ' ;
1+ import 'dart:io ' ;
22import 'package:flutter/material.dart' ;
3- import 'package:fleather/fleather.dart' ;
4- import 'package:parchment/parchment.dart' ;
5- import 'package:hive/hive.dart' ;
6- import 'package:share_plus/share_plus.dart' ;
3+ import '../../core/storage_service.dart' ;
4+ import 'document_viewer_screen.dart' ;
5+ import 'settings_screen.dart' ;
76
8- class DocumentViewerScreen extends StatefulWidget {
9- final String ? fileName;
10- const DocumentViewerScreen ({super .key, this .fileName});
7+ class HomeScreen extends StatefulWidget {
8+ const HomeScreen ({super .key});
119
1210 @override
13- State <DocumentViewerScreen > createState () => _DocumentViewerScreenState ();
11+ State <HomeScreen > createState () => _HomeScreenState ();
1412}
1513
16- class _DocumentViewerScreenState extends State <DocumentViewerScreen > {
17- FleatherController ? _controller;
18- late Box _box;
19- final String _defaultDocName = "new_note" ;
20- final FocusNode _focusNode = FocusNode ();
21-
22- @override
23- void initState () {
24- super .initState ();
25- _box = Hive .box ('documents_box' );
26- _loadDocument ();
27- _controller! .addListener (_autoSave);
28- }
29-
30- void _loadDocument () {
31- final String key = widget.fileName ?? _defaultDocName;
32- final String ? savedData = _box.get (key);
33-
34- if (savedData != null ) {
35- try {
36- final doc = ParchmentDocument .fromJson (jsonDecode (savedData));
37- _controller = FleatherController (document: doc);
38- } catch (e) {
39- _controller = FleatherController ();
40- }
41- } else {
42- _controller = FleatherController ();
43- }
14+ class _HomeScreenState extends State <HomeScreen > {
15+ void _refreshList () {
16+ setState (() {});
4417 }
4518
46- void _autoSave () {
47- final String key = widget.fileName ?? _defaultDocName;
48- final deltaData = jsonEncode (_controller! .document.toDelta ());
49- _box.put (key, deltaData);
50- }
51-
52- // Funkcija koja pretvara bogat tekst u običan string za deljenje
53- void _shareDocument () {
54- final String plainText = _controller! .document.toPlainText ();
55- final String title = widget.fileName ?? 'Untitled Note' ;
56-
57- if (plainText.trim ().isNotEmpty) {
58- Share .share (plainText, subject: title);
59- } else {
60- ScaffoldMessenger .of (context).showSnackBar (
61- const SnackBar (content: Text ("Cannot share an empty note" )),
62- );
63- }
19+ Future <void > _showCreateDialog () async {
20+ String newFileName = "" ;
21+ return showDialog (
22+ context: context,
23+ builder: (context) {
24+ return AlertDialog (
25+ title: const Text ("New Document" , style: TextStyle (fontWeight: FontWeight .w300)),
26+ content: TextField (
27+ autofocus: true ,
28+ onChanged: (value) => newFileName = value,
29+ decoration: const InputDecoration (
30+ hintText: "Enter file name" ,
31+ border: UnderlineInputBorder (),
32+ ),
33+ ),
34+ actions: [
35+ TextButton (
36+ onPressed: () => Navigator .pop (context),
37+ child: const Text ("Cancel" ),
38+ ),
39+ TextButton (
40+ onPressed: () {
41+ if (newFileName.isNotEmpty) {
42+ Navigator .pop (context);
43+ Navigator .push (
44+ context,
45+ MaterialPageRoute (
46+ builder: (_) => DocumentViewerScreen (
47+ fileName: newFileName.endsWith ('.txt' )
48+ ? newFileName
49+ : '$newFileName .txt' ,
50+ ),
51+ ),
52+ ).then ((_) => _refreshList ());
53+ }
54+ },
55+ child: const Text ("Create" ),
56+ ),
57+ ],
58+ );
59+ },
60+ );
6461 }
6562
6663 @override
6764 Widget build (BuildContext context) {
68- final bottomInset = MediaQuery .of (context).viewInsets.bottom;
69- final bool isKeyboardVisible = bottomInset > 0 ;
70- final safeBottomPadding = MediaQuery .of (context).padding.bottom;
71-
7265 return Scaffold (
73- resizeToAvoidBottomInset: false ,
7466 appBar: AppBar (
75- title: Text (
76- widget.fileName ?? 'f.Sentence' ,
77- style: const TextStyle (fontWeight: FontWeight .w300),
78- ),
79- actions: [
80- IconButton (
81- icon: const Icon (Icons .share_outlined),
82- tooltip: 'Share as text' ,
83- onPressed: _shareDocument,
67+ title: const Text ("f.Sentence" , style: TextStyle (fontWeight: FontWeight .w300)),
68+ centerTitle: true ,
69+ leading: Builder (
70+ builder: (context) => IconButton (
71+ icon: const Icon (Icons .menu),
72+ onPressed: () => Scaffold .of (context).openDrawer (),
8473 ),
85- ] ,
74+ ) ,
8675 ),
87- body: Stack (
88- children: [
89- Positioned .fill (
90- child: Padding (
91- padding: const EdgeInsets .symmetric (horizontal: 16 ),
92- child: GestureDetector (
93- onTap: () => _focusNode.requestFocus (),
94- child: FleatherEditor (
95- controller: _controller! ,
96- focusNode: _focusNode,
97- readOnly: false ,
98- enableInteractiveSelection: true ,
99- padding: EdgeInsets .only (
100- top: 16 ,
101- bottom: isKeyboardVisible ? bottomInset + 80 : 100 ,
102- ),
76+ drawer: Drawer (
77+ child: Column (
78+ children: [
79+ DrawerHeader (
80+ decoration: BoxDecoration (
81+ color: Theme .of (context).colorScheme.surfaceContainerHighest.withOpacity (0.5 ),
82+ ),
83+ child: const Center (
84+ child: Text (
85+ "f.Sentence" ,
86+ style: TextStyle (fontSize: 28 , fontWeight: FontWeight .w200),
10387 ),
10488 ),
10589 ),
106- ),
90+ ListTile (
91+ leading: const Icon (Icons .settings_outlined),
92+ title: const Text ("Settings" ),
93+ onTap: () {
94+ Navigator .pop (context);
95+ Navigator .push (
96+ context,
97+ MaterialPageRoute (builder: (_) => const SettingsScreen ()),
98+ ).then ((_) => _refreshList ());
99+ },
100+ ),
101+ const Spacer (),
102+ const Padding (
103+ padding: EdgeInsets .all (16.0 ),
104+ child: Text (
105+ "v1.0.1" ,
106+ style: TextStyle (color: Colors .grey, fontSize: 12 ),
107+ ),
108+ ),
109+ ],
110+ ),
111+ ),
112+ body: FutureBuilder <List <File >>(
113+ future: StorageService .getLocalFiles (),
114+ builder: (context, snapshot) {
115+ if (snapshot.connectionState == ConnectionState .waiting) {
116+ return const Center (child: CircularProgressIndicator ());
117+ }
118+
119+ if (! snapshot.hasData || snapshot.data! .isEmpty) {
120+ return Center (
121+ child: Column (
122+ mainAxisAlignment: MainAxisAlignment .center,
123+ children: [
124+ Icon (Icons .note_add_outlined, size: 64 , color: Colors .grey.withOpacity (0.5 )),
125+ const SizedBox (height: 16 ),
126+ const Text ("No documents yet." , style: TextStyle (color: Colors .grey)),
127+ ],
128+ ),
129+ );
130+ }
107131
108- Positioned (
109- bottom: isKeyboardVisible
110- ? bottomInset + 16
111- : safeBottomPadding + 16 ,
112- left: 16 ,
113- right: 16 ,
114- child: Material (
115- elevation: 6 ,
116- shadowColor: Colors .black38,
117- borderRadius: BorderRadius .circular (30 ),
118- color: Theme .of (context).colorScheme.surfaceContainerHighest,
119- clipBehavior: Clip .antiAlias,
120- child: Container (
121- height: 56 ,
122- padding: const EdgeInsets .symmetric (horizontal: 12 ),
123- child: Theme (
124- data: Theme .of (context).copyWith (
125- dividerColor: Colors .transparent,
132+ final files = snapshot.data! ;
133+
134+ return ListView .builder (
135+ padding: const EdgeInsets .symmetric (horizontal: 16 , vertical: 8 ),
136+ itemCount: files.length,
137+ itemBuilder: (context, index) {
138+ final file = files[index];
139+ final fileName = file.path.split ('/' ).last;
140+
141+ return Card (
142+ elevation: 0 ,
143+ shape: RoundedRectangleBorder (
144+ borderRadius: BorderRadius .circular (16 ),
145+ side: BorderSide (
146+ color: Theme .of (context).colorScheme.outlineVariant,
147+ width: 1 ,
126148 ),
127- child: Center (
128- child: SingleChildScrollView (
129- scrollDirection: Axis .horizontal,
130- child: FleatherToolbar .basic (
131- controller: _controller! ,
132- ),
133- ),
149+ ),
150+ margin: const EdgeInsets .only (bottom: 12 ),
151+ child: ListTile (
152+ contentPadding: const EdgeInsets .symmetric (horizontal: 20 , vertical: 8 ),
153+ leading: CircleAvatar (
154+ backgroundColor: Theme .of (context).colorScheme.primaryContainer,
155+ child: Icon (Icons .article_outlined,
156+ color: Theme .of (context).colorScheme.onPrimaryContainer),
134157 ),
158+ title: Text (
159+ fileName,
160+ style: const TextStyle (fontWeight: FontWeight .w400),
161+ ),
162+ subtitle: Text (
163+ "Modified: ${file .lastModifiedSync ().day }.${file .lastModifiedSync ().month }.${file .lastModifiedSync ().year }" ,
164+ style: TextStyle (fontSize: 12 , color: Colors .grey[600 ]),
165+ ),
166+ onTap: () {
167+ Navigator .push (
168+ context,
169+ MaterialPageRoute (
170+ builder: (_) => DocumentViewerScreen (fileName: fileName),
171+ ),
172+ ).then ((_) => _refreshList ());
173+ },
135174 ),
136- ),
137- ),
138- ),
139- ],
175+ );
176+ },
177+ );
178+ },
179+ ),
180+ floatingActionButton: FloatingActionButton .extended (
181+ onPressed: _showCreateDialog,
182+ label: const Text ("New Note" ),
183+ icon: const Icon (Icons .add),
184+ elevation: 2 ,
140185 ),
141186 );
142187 }
143-
144- @override
145- void dispose () {
146- _controller? .removeListener (_autoSave);
147- _controller? .dispose ();
148- _focusNode.dispose ();
149- super .dispose ();
150- }
151188}
0 commit comments