@@ -114,8 +114,111 @@ class HomePage extends StatefulWidget {
114114
115115class _HomePageState extends State <HomePage > {
116116 late ModulesManager mm;
117+ bool isListView = true ;
117118 final Future <SharedPreferences > _pref = SharedPreferences .getInstance ();
118119
120+ /// Called from the project list control
121+ /// Switches between list and grid view
122+ void onToggleView () async {
123+ (await _pref).setBool ("isListView" , isListView);
124+ setState (() {
125+ isListView = ! isListView;
126+ });
127+ }
128+
129+ List <PopupMenuEntry <String >> getPopupMenu (HistoryItem item) {
130+ return < PopupMenuEntry <String >> [
131+ const PopupMenuItem <String >(
132+ value: "delete" ,
133+ child: Text ('Delete Project' ),
134+ ),
135+ const PopupMenuItem <String >(
136+ //TODO: Implement this menu
137+ value: "rename" ,
138+ child: Text ('Rename Project' ),
139+ ),
140+ const PopupMenuItem <String >(
141+ //TODO: Implement this menu
142+ value: "export" ,
143+ child: Text ('Export Project' ),
144+ ),
145+ const PopupMenuItem <String >(
146+ //TODO: Implement this menu
147+ value: "remove" ,
148+ child: Text ('Remove from list' )),
149+ ];
150+ }
151+
152+ void onMenuItemSelected (String itemTitle, HistoryItem item) {
153+ switch (itemTitle) {
154+ case "delete" :
155+ if (item.type == HistoryItemType .solution) {
156+ showDialog (
157+ context: context,
158+ builder: (BuildContext context) {
159+ return AlertDialog (
160+ title: Text ("Delete ${item .name }?" ),
161+ content: Text (
162+ "This action cannot be undone!\n folders will be deleted: ${() {
163+ String result = "" ;
164+ for (var folder in item .solution !.folders .keys ) {
165+ result +=
166+ (item .solution !.folders [folder ] as String ) + ", \n " ;
167+ }
168+ return result ;
169+ }()}" ),
170+ actions: [
171+ TextButton (
172+ onPressed: () {
173+ Navigator .pop (context);
174+ },
175+ child: const Text ("No" )),
176+ TextButton (
177+ onPressed: () {
178+ var folders = < String > [];
179+ for (var folder in item.solution! .folders.keys) {
180+ folders.add (item.solution! .slnFolderPath +
181+ Platform .pathSeparator +
182+ item.solution! .folders[folder]! );
183+ }
184+ deleteFolderWithIndicator (context, folders);
185+ // Delete the solution file too
186+ File (item.solution! .slnPath).deleteSync ();
187+
188+ // Quit and refresh
189+ Navigator .pop (context);
190+ refreshRecentProjects ();
191+ },
192+ child: const Text (
193+ "Delete" ,
194+ style: TextStyle (color: Colors .redAccent),
195+ )),
196+ ],
197+ );
198+ });
199+ } else {
200+ //TODO: Handle single file delete
201+ }
202+ break ;
203+ case "remove" :
204+ setState (() {
205+ /// Remove item from the list without deleting the actual file
206+ RecentProjectsManager .instance.projects.remove (item);
207+ RecentProjectsManager .staticCommit ();
208+ });
209+ break ;
210+ }
211+ }
212+
213+ void onHistoryItemTap (HistoryItem p){
214+ if (p.type == HistoryItemType .solution) {
215+ touchFile (File (p.solution! .slnPath), p.solution! );
216+ refreshRecentProjects ();
217+ loadSolution (p.solution! , context);
218+ }
219+ //TODO: Handle single file
220+ }
221+
119222 List <Widget > get projectsWidgetList {
120223 var result = < Widget > [];
121224 for (HistoryItem p in RecentProjectsManager .instance.projects) {
@@ -125,134 +228,76 @@ class _HomePageState extends State<HomePage> {
125228 //debugPrint(p.name);
126229 result.add (Card (
127230 //TODO: refactor this as a widget elsewhere, then reference that widget from here
128- child: ListTile (
129-
130- shape: RoundedRectangleBorder (
131- borderRadius: BorderRadius .circular (4 ),
132- side: BorderSide (color: Theme .of (context).dividerColor),
133- ),
134- onTap: () {
135- if (p.type == HistoryItemType .solution) {
136- touchFile (File (p.solution! .slnPath), p.solution! );
137- refreshRecentProjects ();
138- loadSolution (p.solution! , context);
139- }
140- //TODO: Handle single file
141- },
142- leading: p.type == HistoryItemType .solution
143- ? p.solution! .image ??
144- const Icon (
145- Icons .insert_drive_file,
146- size: 48 ,
147- )
148- : const Icon (
149- Icons .insert_drive_file,
150- size: 48 ,
151- ),
152- title: Text (
153- p.name,
154- style: const TextStyle (fontWeight: FontWeight .bold),
155- ),
156- subtitle: Text ("Last Modified: " +
157- Utils .getFormattedDateTime (dateTime: p.dateModified)),
158- trailing: PopupMenuButton <String >(
159- onSelected: (String result) {
160- switch (result) {
161- case "delete" :
162- if (p.type == HistoryItemType .solution) {
163- showDialog (
164- context: context,
165- builder: (BuildContext context) {
166- return AlertDialog (
167- title: Text ("Delete ${p .name }?" ),
168- content: Text (
169- "This action cannot be undone!\n folders will be deleted: ${() {
170- String result = "" ;
171- for (var folder in p .solution !.folders .keys ) {
172- result += (p .solution !.folders [folder ]
173- as String ) +
174- ", \n " ;
175- }
176- return result ;
177- }()}" ),
178- actions: [
179- TextButton (
180- onPressed: () {
181- Navigator .pop (context);
182- },
183- child: const Text ("No" )),
184- TextButton (
185- onPressed: () {
186- var folders = < String > [];
187- for (var folder
188- in p.solution! .folders.keys) {
189- folders.add (
190- p.solution! .slnFolderPath +
191- Platform .pathSeparator +
192- p.solution! .folders[folder]! );
193- }
194- deleteFolderWithIndicator (
195- context, folders);
196- // Delete the solution file too
197- File (p.solution! .slnPath).deleteSync ();
198-
199- // Quit and refresh
200- Navigator .pop (context);
201- refreshRecentProjects ();
202- },
203- child: const Text (
204- "Delete" ,
205- style:
206- TextStyle (color: Colors .redAccent),
207- )),
208- ],
209- );
210- });
211- } else {
212- //TODO: Handle single file delete
213- }
214- break ;
215- case "remove" :
216- setState (() {
217- /// Remove item from the list without deleting the actual file
218- RecentProjectsManager .instance.projects.remove (p);
219- RecentProjectsManager .staticCommit ();
220- });
221- break ;
222- }
223- },
224- itemBuilder: (BuildContext context) => < PopupMenuEntry <String >> [
225- const PopupMenuItem <String >(
226- value: "delete" ,
227- child: Text ('Delete Project' ),
228- ),
229- const PopupMenuItem <String >(
230- //TODO: Implement this menu
231- value: "rename" ,
232- child: Text ('Rename Project' ),
231+ child: isListView
232+ ? ListTile (
233+ shape: RoundedRectangleBorder (
234+ borderRadius: BorderRadius .circular (4 ),
235+ side: BorderSide (color: Theme .of (context).dividerColor),
233236 ),
234- const PopupMenuItem <String >(
235- //TODO: Implement this menu
236- value: "export" ,
237- child: Text ('Export Project' ),
237+ onTap: () => onHistoryItemTap (p),
238+ leading: p.type == HistoryItemType .solution
239+ ? p.solution! .image ??
240+ const Icon (
241+ Icons .insert_drive_file,
242+ size: 48 ,
243+ )
244+ : const Icon (
245+ Icons .insert_drive_file,
246+ size: 48 ,
247+ ),
248+ title: Text (
249+ p.name,
250+ style: const TextStyle (fontWeight: FontWeight .bold),
238251 ),
239- const PopupMenuItem <String >(
240- //TODO: Implement this menu
241- value: "remove" ,
242- child: Text ('Remove from list' )),
243- ],
244- ))));
245- // IconButton(
246- // onPressed: () {
247- // showMenu(context: context, position: RelativeRect.fromLTRB(
248- // details.globalPosition.dx,
249- // details.globalPosition.dy,
250- // details.globalPosition.dx,
251- // details.globalPosition.dy,
252- // ), items: <PopupMenuEntry<dynamic>>[]);
253- // },
254- // icon: const Icon(FontAwesomeIcons.ellipsisV),
255- // )));
252+ subtitle: Text ("Last Modified: " +
253+ Utils .getFormattedDateTime (dateTime: p.dateModified)),
254+ trailing: PopupMenuButton <String >(
255+ onSelected: (String result) =>
256+ onMenuItemSelected (result, p),
257+ itemBuilder: (BuildContext context) => getPopupMenu (p)))
258+ : SizedBox (
259+ width: 128 ,
260+ height: 128 ,
261+ child: OutlinedButton (
262+ onPressed: () => onHistoryItemTap (p),
263+ child: Stack (children: [
264+ Column (
265+ mainAxisAlignment: MainAxisAlignment .center,
266+ children: [
267+ p.type == HistoryItemType .solution
268+ ? p.solution! .image ??
269+ const Icon (
270+ Icons .insert_drive_file,
271+ size: 48 ,
272+ )
273+ : const Icon (
274+ Icons .insert_drive_file,
275+ size: 48 ,
276+ ),
277+ Text (
278+ p.name,
279+ style: TextStyle (
280+ color: Theme .of (context)
281+ .textTheme
282+ .bodyText1!
283+ .color! ,
284+ fontSize: 12.0 ),
285+ ),
286+ Text (
287+ Utils .getFormattedDateTime (
288+ dateTime: p.dateModified),
289+ style: TextStyle (
290+ color: Theme .of (context).focusColor,
291+ fontSize: 12.0 ),
292+ )
293+ ],
294+ ),
295+ Positioned (
296+ top: 0 ,right: - 16 ,
297+ child: PopupMenuButton <String >(
298+ onSelected: (String result) => onMenuItemSelected (result, p),
299+ itemBuilder: (BuildContext context) => getPopupMenu (p)))
300+ ])))));
256301 }
257302 return result;
258303 }
@@ -308,6 +353,7 @@ class _HomePageState extends State<HomePage> {
308353 /// Check the last opened projects
309354 SharedPreferences .getInstance ().then ((inst) async {
310355 var isAutoOpen = inst.getBool ("openLastProjectOnStartup" );
356+ isListView = inst.getBool ("isListView" ) ?? true ;
311357 if (isAutoOpen != null && isAutoOpen) {
312358 var val = inst.getString ("lastOpenedPath" );
313359 debugPrint ("Loading last opened $val " );
@@ -384,7 +430,7 @@ class _HomePageState extends State<HomePage> {
384430 content: Text ("Can't fetch update from url $url " ),
385431 );
386432 ScaffoldMessenger .of (context).showSnackBar (snackBar);
387- } on SocketException catch (err){
433+ } on SocketException catch (err) {
388434 debugPrint ("[Update Checker] Can't connect to github" );
389435 }
390436 }
@@ -406,8 +452,7 @@ class _HomePageState extends State<HomePage> {
406452 );
407453 });
408454 for (var path in paths) {
409- if (path.endsWith ("." ))
410- path = path.substring (0 ,path.length - 2 );
455+ if (path.endsWith ("." )) path = path.substring (0 , path.length - 2 );
411456 debugPrint (path);
412457 Directory target = Directory (path);
413458 text = path;
@@ -478,6 +523,8 @@ class _HomePageState extends State<HomePage> {
478523 onAddProject: onAddProject,
479524 onRefresh: refreshRecentProjects,
480525 children: projectsWidgetList,
526+ isListView: isListView,
527+ onToggleView: onToggleView,
481528 ),
482529 PluginsBrowser (modulesManager: mm),
483530 SettingsPage (mm),
@@ -491,6 +538,8 @@ class _HomePageState extends State<HomePage> {
491538 onAddProject: onAddProject,
492539 onRefresh: refreshRecentProjects,
493540 children: projectsWidgetList,
541+ isListView: isListView,
542+ onToggleView: onToggleView,
494543 ),
495544 ));
496545 return Scaffold (
@@ -508,12 +557,13 @@ class _HomePageState extends State<HomePage> {
508557 ],
509558 ),
510559 body: Container (
511-
512560 decoration: BoxDecoration (
513561 image: DecorationImage (
514- image: ThemeManager .getImage ("mainBG" )! .image, fit: BoxFit .cover)),
515- child: SingleChildScrollView (child: page,)
516- ),
562+ image: ThemeManager .getImage ("mainBG" )! .image,
563+ fit: BoxFit .cover)),
564+ child: SingleChildScrollView (
565+ child: page,
566+ )),
517567 floatingActionButton: FloatingActionButton (
518568 onPressed: () => showCreateProjectDialog (),
519569 child: const Icon (Icons .create_new_folder),
0 commit comments