first working prototype
Some checks failed
Build CI / Build (push) Has been cancelled

This commit is contained in:
2026-02-11 22:59:25 +05:30
parent 6a10685033
commit 73827ea62c
13 changed files with 281 additions and 32 deletions

View File

@@ -0,0 +1,2 @@
const String kProjectsKey = "projects";
const String kEditorsKey = "editors";

View File

@@ -1,13 +1,19 @@
import "package:flutter/material.dart"; import "package:flutter/material.dart";
import "package:process_run/which.dart"; import "package:process_run/which.dart";
import "package:prod/models/rand.dart";
class Editor { class Editor {
final String id;
final String name; final String name;
final String command; final String command;
final String commandTemplate; final String commandTemplate;
// final Icon icon; // final Icon icon;
const Editor(this.name, this.command, this.commandTemplate); const Editor(this.name, this.command, this.commandTemplate, this.id);
factory Editor.create(String name, String command, String commandTemplate) {
return Editor(name, command, commandTemplate, getRandomString(5));
}
bool validateCommand() { bool validateCommand() {
final String? fullPath = whichSync(command); final String? fullPath = whichSync(command);
@@ -17,4 +23,29 @@ class Editor {
return true; return true;
} }
} }
Map<String, dynamic> toJson() {
return {
"name": name,
"command": command,
"commandTemplate": commandTemplate,
"id": id,
};
}
factory Editor.fromJson(Map<String, dynamic> data) {
if (!data.containsKey("name") ||
!data.containsKey("command") ||
!data.containsKey("commandTemplate") ||
!data.containsKey("id")) {
print("Found invalid editor config when parsing: $data");
return Editor("null", "null", "null", "null");
}
return Editor(
data["name"] as String,
data["command"] as String,
data["commandTemplate"] as String,
data["id"] as String,
);
}
} }

View File

@@ -1,27 +1,75 @@
import 'package:flutter/foundation.dart'; import 'package:flutter/foundation.dart';
import 'package:prod/models/editor.dart'; import 'package:prod/models/editor.dart';
import 'package:prod/models/project.dart'; import 'package:prod/models/project.dart';
import "package:shared_preferences/shared_preferences.dart";
import "package:prod/models/constants.dart";
// import "package:json_annotation/json_annotation.dart";
import "dart:convert";
class GlobalModel extends ChangeNotifier { class GlobalModel extends ChangeNotifier {
late List<Project> projects; late List<Project> projects;
late List<bool> hoverShow; late List<bool> hoverShow;
late List<Editor> editors; late List<Editor> editors;
late SharedPreferences prefs;
bool importedData = false;
GlobalModel() { GlobalModel() {
projects = []; SharedPreferences.getInstance().then((a) {
editors = []; print("Loaded sp");
hoverShow = List.filled(projects.length, false, growable: true); prefs = a;
if (prefs.containsKey(kProjectsKey)) {
String prjData = prefs.getString(kProjectsKey)!;
List<dynamic> data = jsonDecode(prjData);
projects = [];
for (var d in data) {
projects.add(Project.fromJson(d));
}
} else {
projects = [];
prefs.setString(kProjectsKey, jsonEncode(projects));
}
print(projects);
if (prefs.containsKey(kEditorsKey)) {
String edtData = prefs.getString(kEditorsKey)!;
List<dynamic> data = jsonDecode(edtData);
editors = [];
for (var d in data) {
editors.add(Editor.fromJson(d));
}
} else {
editors = [];
prefs.setString(kEditorsKey, jsonEncode(editors));
}
hoverShow = List.filled(projects.length, false, growable: true);
print("loaded data");
importedData = true;
notifyListeners();
});
}
void saveProjectState() {
var arst = projects.map((a) => a.toJson()).toList();
prefs.setString(kProjectsKey, jsonEncode(arst));
}
void saveEditorState() {
var arst = editors.map((a) => a.toJson()).toList();
prefs.setString(kEditorsKey, jsonEncode(arst));
} }
void addPrj(Project prj) { void addPrj(Project prj) {
projects.add(prj); projects.add(prj);
hoverShow.add(false); hoverShow.add(false);
saveProjectState();
notifyListeners(); notifyListeners();
} }
void delPrj(int index) { void delPrj(int index) {
projects.removeAt(index); projects.removeAt(index);
hoverShow.removeAt(index); hoverShow.removeAt(index);
saveProjectState();
notifyListeners(); notifyListeners();
} }
@@ -50,11 +98,13 @@ class GlobalModel extends ChangeNotifier {
void addEdt(Editor edt) { void addEdt(Editor edt) {
editors.add(edt); editors.add(edt);
saveEditorState();
notifyListeners(); notifyListeners();
} }
void delEdt(int index) { void delEdt(int index) {
editors.removeAt(index); editors.removeAt(index);
saveEditorState();
notifyListeners(); notifyListeners();
} }

View File

@@ -6,7 +6,7 @@ class Project {
final String name; final String name;
final String language; final String language;
final File path; final File path;
final List<Editor> editors; final List<String> editors;
final bool isGit; final bool isGit;
final bool enableTerminal; final bool enableTerminal;
@@ -19,14 +19,15 @@ class Project {
this.enableTerminal, this.enableTerminal,
); );
factory Project.validated( factory Project.newValidated(
String name, String name,
String lang, String lang,
String path, String path,
List<Editor> editors, List<String> editors,
bool enableTerminal, bool enableTerminal,
) { ) {
final File fpath = File(path); final File fpath = File(path);
print(fpath.absolute.path);
if (fpath.existsSync()) { if (fpath.existsSync()) {
print("Project not found!!!"); print("Project not found!!!");
} else { } else {
@@ -38,6 +39,28 @@ class Project {
return Project(name, lang, fpath, editors, isGit, enableTerminal); return Project(name, lang, fpath, editors, isGit, enableTerminal);
} }
Map<String, dynamic> toJson() {
return {
"name": name,
"language": language,
"path": path.path,
"editors": editors,
"isGit": isGit,
"enableTerminal": enableTerminal,
};
}
factory Project.fromJson(Map<String, dynamic> data) {
return Project(
data["name"] as String,
data["language"] as String,
File(data["path"] as String),
(data["editors"] as List<dynamic>).map((a) => a.toString()).toList(),
data["isGit"] as bool,
data["enableTerminal"] as bool,
);
}
// bool validatePath(){ // bool validatePath(){
// return File // return File
// } // }

11
lib/models/rand.dart Normal file
View File

@@ -0,0 +1,11 @@
import 'dart:math';
const _chars = 'AaBbCcDdEeFfGgHhIiJjKkLlMmNnOoPpQqRrSsTtUuVvWwXxYyZz1234567890';
Random _rnd = Random();
String getRandomString(int length) => String.fromCharCodes(
Iterable.generate(
length,
(_) => _chars.codeUnitAt(_rnd.nextInt(_chars.length)),
),
);

View File

@@ -2,6 +2,7 @@ import "package:flutter/material.dart";
import "package:prod/models/editor.dart"; import "package:prod/models/editor.dart";
import "package:prod/models/globalModel.dart"; import "package:prod/models/globalModel.dart";
import "package:prod/widgets/editorCard.dart"; import "package:prod/widgets/editorCard.dart";
import "package:prod/widgets/edtFAB.dart";
import "package:provider/provider.dart"; import "package:provider/provider.dart";
class EditorEditor extends StatelessWidget { class EditorEditor extends StatelessWidget {
@@ -11,7 +12,8 @@ class EditorEditor extends StatelessWidget {
Widget build(BuildContext context) { Widget build(BuildContext context) {
GlobalModel gm = Provider.of(context); GlobalModel gm = Provider.of(context);
return Scaffold( return Scaffold(
appBar: AppBar(title: Text("PROject Dashboard")), appBar: AppBar(title: Text("/ PROject Dashboard / Editors")),
floatingActionButton: EditorFAB(),
body: gm.lenEdt > 0 body: gm.lenEdt > 0
? LayoutBuilder( ? LayoutBuilder(
builder: (context, constraints) { builder: (context, constraints) {

View File

@@ -15,18 +15,21 @@ class HomePage extends StatelessWidget {
GlobalModel gm = Provider.of<GlobalModel>(context); GlobalModel gm = Provider.of<GlobalModel>(context);
return SafeArea( return SafeArea(
child: Scaffold( child: Scaffold(
appBar: AppBar(title: Text("PROject Dashboard")), appBar: AppBar(title: Text("/ PROject Dashboard")),
drawer: ProdDrawer(), drawer: ProdDrawer(),
floatingActionButton: ProjFAB(), floatingActionButton: ProjFAB(),
body: Padding( body: Padding(
padding: const EdgeInsets.all(8.0), padding: const EdgeInsets.all(8.0),
child: gm.lenPrj > 0 child: !gm.importedData
? Container()
: gm.lenPrj > 0
? LayoutBuilder( ? LayoutBuilder(
builder: (context, constraints) { builder: (context, constraints) {
int cols = (constraints.maxWidth / 200).floor(); int cols = (constraints.maxWidth / 300).floor();
return GridView.builder( return GridView.builder(
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount( gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: cols, crossAxisCount: cols,
childAspectRatio: 2,
crossAxisSpacing: 10, crossAxisSpacing: 10,
mainAxisSpacing: 10, mainAxisSpacing: 10,
), ),

View File

@@ -15,13 +15,16 @@ class EditorCard extends StatelessWidget {
GlobalModel gm = Provider.of<GlobalModel>(context); GlobalModel gm = Provider.of<GlobalModel>(context);
final Editor edt = gm.nthEdt(id); final Editor edt = gm.nthEdt(id);
return YaruBanner( return YaruBanner(
onTap: () {}, onTap: () {
gm.delEdt(id);
},
child: Center( child: Center(
child: Column( child: Column(
mainAxisAlignment: .start, mainAxisAlignment: .start,
crossAxisAlignment: .start, crossAxisAlignment: .start,
children: [ children: [
Text("${edt.name}", style: TextStyle(fontSize: 30)), Text("${edt.name}", style: TextStyle(fontSize: 30)),
Text("${edt.command}"),
Text("${edt.commandTemplate}"), Text("${edt.commandTemplate}"),
], ],
), ),

View File

@@ -1,5 +1,7 @@
import "package:flutter/material.dart"; import "package:flutter/material.dart";
import "package:prod/models/editor.dart"; import "package:prod/models/editor.dart";
import "package:prod/models/globalModel.dart";
import "package:provider/provider.dart";
class EditorFAB extends StatelessWidget { class EditorFAB extends StatelessWidget {
const EditorFAB({super.key}); const EditorFAB({super.key});
@@ -17,6 +19,68 @@ class EditorFAB extends StatelessWidget {
// true, // true,
// ), // ),
// ); // );
TextEditingController nameController = TextEditingController();
TextEditingController commandController = TextEditingController();
TextEditingController commandTemplateController =
TextEditingController();
showDialog(
context: context,
builder: (context) => SimpleDialog(
title: Text("Add Editor"),
children: [
Padding(
padding: const EdgeInsets.all(8.0),
child: TextField(
autofocus: true,
controller: nameController,
decoration: InputDecoration(labelText: "Editor Name"),
),
),
Padding(
padding: const EdgeInsets.all(8.0),
child: TextField(
controller: commandController,
decoration: InputDecoration(labelText: "Command"),
),
),
Padding(
padding: const EdgeInsets.all(8.0),
child: TextField(
controller: commandTemplateController,
decoration: InputDecoration(labelText: "Command Template"),
),
),
Padding(
child: const Text("Example: zed -n \$path"),
padding: const EdgeInsets.all(9.0),
),
Row(
mainAxisAlignment: .end,
children: [
TextButton(
child: Text("Cancel"),
onPressed: () => Navigator.pop(context),
),
TextButton(
child: Text("Add"),
onPressed: () {
Provider.of<GlobalModel>(context, listen: false).addEdt(
Editor.create(
nameController.text,
commandController.text,
commandTemplateController.text,
),
);
Navigator.pop(context);
},
),
],
),
],
),
);
}, },
child: Icon(Icons.add), child: Icon(Icons.add),
); );

View File

@@ -8,15 +8,58 @@ class ProjFAB extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
TextEditingController nameController = TextEditingController();
TextEditingController locationController = TextEditingController();
return FloatingActionButton( return FloatingActionButton(
onPressed: () { onPressed: () {
Provider.of<GlobalModel>(context, listen: false).addPrj( showDialog(
Project.validated( context: context,
"Kimi", builder: (context) => SimpleDialog(
"Rust", title: Text("Add Project"),
"/home/arrow/Gitted/cowin", children: [
[], Padding(
true, padding: const EdgeInsets.all(8.0),
child: TextField(
autofocus: true,
controller: nameController,
decoration: InputDecoration(labelText: "Project Name"),
),
),
Padding(
padding: const EdgeInsets.all(8.0),
child: TextField(
controller: locationController,
decoration: InputDecoration(labelText: "Path"),
),
),
Row(
mainAxisAlignment: .end,
children: [
TextButton(
child: Text("Cancel"),
onPressed: () => Navigator.pop(context),
),
TextButton(
child: Text("Add"),
onPressed: () {
Provider.of<GlobalModel>(context, listen: false).addPrj(
Project.newValidated(
nameController.text,
"Rust",
locationController.text,
[],
true,
),
);
Navigator.pop(context);
},
),
],
),
],
), ),
); );
}, },

View File

@@ -1,4 +1,5 @@
import "package:flutter/material.dart"; import "package:flutter/material.dart";
import "package:prod/models/editor.dart";
import "package:prod/models/globalModel.dart"; import "package:prod/models/globalModel.dart";
import "package:prod/models/project.dart"; import "package:prod/models/project.dart";
import "package:provider/provider.dart"; import "package:provider/provider.dart";
@@ -16,32 +17,36 @@ class ProjectCard extends StatelessWidget {
return YaruBanner( return YaruBanner(
padding: .only( padding: .only(
left: kYaruPagePadding, left: kYaruPagePadding,
top: kYaruPagePadding, top: kYaruPagePadding * 0.5,
bottom: kYaruPagePadding, bottom: kYaruPagePadding,
right: kYaruPagePadding, right: kYaruPagePadding,
), ),
onHover: (st) => gm.setHoverShow(id, st), onHover: (st) => gm.setHoverShow(id, st),
onTap: () { onTap: () {
var shell = Shell(); var shell = Shell();
shell.run("konsole -e nvim ${prj.path.path}"); Editor edt1 = gm.editors[0];
String comm = edt1.commandTemplate.replaceAll("\$path", prj.path.path);
shell.run(comm);
}, },
child: Center( child: Center(
child: Column( child: Column(
mainAxisAlignment: .start, mainAxisAlignment: .start,
crossAxisAlignment: .start, crossAxisAlignment: .start,
children: [ children: [
Row( Text(
mainAxisAlignment: .spaceBetween, "${prj.name}",
children: [ style: TextStyle(fontSize: 30),
Text("${prj.name}", style: TextStyle(fontSize: 30)), overflow: .ellipsis,
gm.getHoverShow(id)
? ElevatedButton(
child: Icon(Icons.close),
onPressed: () => print("pressed delete"),
)
: Container(),
],
), ),
// Row(
// mainAxisAlignment: .spaceBetween,
// children: [
// Flexible(
// child: Container(
// ),
// ),
// ],
// ),
Row( Row(
spacing: 10, spacing: 10,
children: [ children: [
@@ -50,6 +55,15 @@ class ProjectCard extends StatelessWidget {
], ],
), ),
gm.getHoverShow(id) ? Text("${prj.path.path}") : Container(), gm.getHoverShow(id) ? Text("${prj.path.path}") : Container(),
gm.getHoverShow(id)
? IconButton(
icon: Icon(Icons.close),
onPressed: () => gm.delPrj(id),
style: IconButton.styleFrom(
overlayColor: Color(0xffff0000),
),
)
: Container(),
], ],
), ),
), ),

View File

@@ -14,6 +14,7 @@ class SListTile extends StatelessWidget {
leading: Icon(icon), leading: Icon(icon),
title: Text(title), title: Text(title),
onTap: () { onTap: () {
Navigator.pop(context);
Navigator.pushNamed(context, route); Navigator.pushNamed(context, route);
}, },
); );

View File

@@ -38,6 +38,8 @@ dependencies:
yaru: ^9.0.1 yaru: ^9.0.1
path: ^1.9.1 path: ^1.9.1
provider: ^6.1.5+1 provider: ^6.1.5+1
shared_preferences: ^2.5.4
json_annotation: ^4.10.0
dev_dependencies: dev_dependencies:
flutter_test: flutter_test: