1- import 'dart:io ' ;
1+ import 'dart:convert ' ;
22import 'package:flutter/material.dart' ;
3- import '../../core/storage_service.dart' ;
4- import 'document_viewer_screen.dart' ;
5- import 'settings_screen.dart' ; // Importuj svoj novi settings fajl
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' ;
67
7- class HomeScreen extends StatefulWidget {
8- const HomeScreen ({super .key});
8+ class DocumentViewerScreen extends StatefulWidget {
9+ final String ? fileName;
10+ const DocumentViewerScreen ({super .key, this .fileName});
911
1012 @override
11- State <HomeScreen > createState () => _HomeScreenState ();
13+ State <DocumentViewerScreen > createState () => _DocumentViewerScreenState ();
1214}
1315
14- class _HomeScreenState extends State <HomeScreen > {
15- // Funkcija za osvežavanje liste nakon kreiranja ili brisanja
16- void _refreshList () {
17- setState (() {});
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);
1828 }
1929
20- // Dijalog za kreiranje novog dokumenta
21- Future <void > _showCreateDialog () async {
22- String newFileName = "" ;
23- return showDialog (
24- context: context,
25- builder: (context) {
26- return AlertDialog (
27- title: const Text ("New Document" , style: TextStyle (fontWeight: FontWeight .w300)),
28- content: TextField (
29- autofocus: true ,
30- onChanged: (value) => newFileName = value,
31- decoration: const InputDecoration (
32- hintText: "Enter file name" ,
33- border: UnderlineInputBorder (),
34- ),
35- ),
36- actions: [
37- TextButton (
38- onPressed: () => Navigator .pop (context),
39- child: const Text ("Cancel" ),
40- ),
41- TextButton (
42- onPressed: () {
43- if (newFileName.isNotEmpty) {
44- Navigator .pop (context);
45- Navigator .push (
46- context,
47- MaterialPageRoute (
48- builder: (_) => DocumentViewerScreen (
49- fileName: newFileName.endsWith ('.txt' )
50- ? newFileName
51- : '$newFileName .txt' ,
52- ),
53- ),
54- ).then ((_) => _refreshList ());
55- }
56- },
57- child: const Text ("Create" ),
58- ),
59- ],
60- );
61- },
62- );
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+ }
44+ }
45+
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+ }
6364 }
6465
6566 @override
6667 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+
6772 return Scaffold (
73+ resizeToAvoidBottomInset: false ,
6874 appBar: AppBar (
69- title: const Text ("f.Sentence" , style: TextStyle (fontWeight: FontWeight .w300)),
70- centerTitle: true ,
71- // Hamburger ikona
72- leading: Builder (
73- builder: (context) => IconButton (
74- icon: const Icon (Icons .menu),
75- onPressed: () => Scaffold .of (context).openDrawer (),
76- ),
75+ title: Text (
76+ widget.fileName ?? 'f.Sentence' ,
77+ style: const TextStyle (fontWeight: FontWeight .w300),
7778 ),
79+ actions: [
80+ IconButton (
81+ icon: const Icon (Icons .share_outlined),
82+ tooltip: 'Share as text' ,
83+ onPressed: _shareDocument,
84+ ),
85+ ],
7886 ),
79- // Meni sa leve strane
80- drawer: Drawer (
81- child: Column (
82- children: [
83- DrawerHeader (
84- decoration: BoxDecoration (
85- color: Theme .of (context).colorScheme.surfaceContainerHighest.withOpacity (0.5 ),
86- ),
87- child: const Center (
88- child: Text (
89- "f.Sentence" ,
90- style: TextStyle (fontSize: 28 , fontWeight: FontWeight .w200),
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+ ),
91103 ),
92104 ),
93105 ),
94- ListTile (
95- leading: const Icon (Icons .settings_outlined),
96- title: const Text ("Settings" ),
97- onTap: () {
98- Navigator .pop (context); // Zatvori meni
99- Navigator .push (
100- context,
101- MaterialPageRoute (builder: (_) => const SettingsScreen ()),
102- ).then ((_) => _refreshList ());
103- },
104- ),
105- const Spacer (),
106- const Padding (
107- padding: EdgeInsets .all (16.0 ),
108- child: Text (
109- "v1.0.1" ,
110- style: TextStyle (color: Colors .grey, fontSize: 12 ),
111- ),
112- ),
113- ],
114- ),
115- ),
116- body: FutureBuilder <List <File >>(
117- future: StorageService .getLocalFiles (),
118- builder: (context, snapshot) {
119- if (snapshot.connectionState == ConnectionState .waiting) {
120- return const Center (child: CircularProgressIndicator ());
121- }
122-
123- if (! snapshot.hasData || snapshot.data! .isEmpty) {
124- return Center (
125- child: Column (
126- mainAxisAlignment: MainAxisAlignment .center,
127- children: [
128- Icon (Icons .note_add_outlined, size: 64 , color: Colors .grey.withOpacity (0.5 )),
129- const SizedBox (height: 16 ),
130- const Text ("No documents yet." , style: TextStyle (color: Colors .grey)),
131- ],
132- ),
133- );
134- }
135-
136- final files = snapshot.data! ;
137-
138- return ListView .builder (
139- padding: const EdgeInsets .symmetric (horizontal: 16 , vertical: 8 ),
140- itemCount: files.length,
141- itemBuilder: (context, index) {
142- final file = files[index];
143- final fileName = file.path.split ('/' ).last;
106+ ),
144107
145- return Card (
146- elevation: 0 ,
147- shape: RoundedRectangleBorder (
148- borderRadius: BorderRadius .circular (16 ),
149- side: BorderSide (
150- color: Theme .of (context).colorScheme.outlineVariant,
151- width: 1 ,
152- ),
153- ),
154- margin: const EdgeInsets .only (bottom: 12 ),
155- child: ListTile (
156- contentPadding: const EdgeInsets .symmetric (horizontal: 20 , vertical: 8 ),
157- leading: CircleAvatar (
158- backgroundColor: Theme .of (context).colorScheme.primaryContainer,
159- child: Icon (Icons .article_outlined,
160- color: Theme .of (context).colorScheme.onPrimaryContainer),
161- ),
162- title: Text (
163- fileName,
164- style: const TextStyle (fontWeight: FontWeight .w400),
165- ),
166- subtitle: Text (
167- "Last modified: ${file .lastModifiedSync ().day }.${file .lastModifiedSync ().month }.${file .lastModifiedSync ().year }" ,
168- style: TextStyle (fontSize: 12 , color: Colors .grey[600 ]),
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,
169126 ),
170- onTap : () {
171- Navigator . push (
172- context ,
173- MaterialPageRoute (
174- builder : (_) => DocumentViewerScreen (fileName : fileName) ,
127+ child : Center (
128+ child : SingleChildScrollView (
129+ scrollDirection : Axis .horizontal ,
130+ child : FleatherToolbar . basic (
131+ controller : _controller ! ,
175132 ),
176- ). then ((_) => _refreshList ());
177- } ,
133+ ),
134+ ) ,
178135 ),
179- );
180- },
181- );
182- },
183- ),
184- floatingActionButton: FloatingActionButton .extended (
185- onPressed: _showCreateDialog,
186- label: const Text ("New Note" ),
187- icon: const Icon (Icons .add),
188- elevation: 2 ,
136+ ),
137+ ),
138+ ),
139+ ],
189140 ),
190141 );
191142 }
192- }
143+
144+ @override
145+ void dispose () {
146+ _controller? .removeListener (_autoSave);
147+ _controller? .dispose ();
148+ _focusNode.dispose ();
149+ super .dispose ();
150+ }
151+ }
0 commit comments