diff --git a/lib/consts.dart b/lib/consts.dart index 01f4d3d..4856aa5 100644 --- a/lib/consts.dart +++ b/lib/consts.dart @@ -1,4 +1,5 @@ -import 'package:ce_mobile/model/workspace.dart'; +import 'package:ce_mobile/model/compiler.dart'; +import 'package:ce_mobile/model/language.dart'; // Default URL const String defaultUrl = 'https://godbolt.org'; @@ -10,7 +11,7 @@ const String compileEndpoint = '/api/compiler'; // Default const Compiler defaultCompiler = - Compiler.create('g132', 'x86-64 gcc 13.2', 'c++', '', '13.2', 'amd64'); + Compiler('g132', 'x86-64 gcc 13.2', 'c++', '', '13.2', 'amd64'); -const Language defaultLanguage = Language.create('c++', 'C++', +const Language defaultLanguage = Language('c++', 'C++', ['.cpp', '.cxx', '.h', '.hpp', '.hxx', '.c', '.cc', '.ixx'], 'cppp'); diff --git a/lib/model/compiler.dart b/lib/model/compiler.dart new file mode 100644 index 0000000..b2d8ff9 --- /dev/null +++ b/lib/model/compiler.dart @@ -0,0 +1,49 @@ +import 'dart:io'; + +import 'package:ce_mobile/consts.dart'; +import 'package:dart_json_mapper/dart_json_mapper.dart'; +import 'package:http/http.dart' as http; + +@jsonSerializable +class Compiler { + final String id; + final String name; + final String lang; + final String compilerType; + final String semver; + final String instructionSet; + + const Compiler(this.id, this.name, this.lang, this.compilerType, this.semver, + this.instructionSet); + + static Future> fetchCompilers() async { + final response = await http.get(Uri.parse('$defaultUrl$compilersEndpoint'), + headers: { + HttpHeaders.acceptHeader: 'application/json' + }); + + if (response.statusCode == HttpStatus.ok) { + return JsonMapper.deserialize>(response.body) + as List; + } else { + throw Exception('Failed to fetch compilers: ${response.statusCode}'); + } + } + + static Future> fetchCompilersForLanguage( + String langName) async { + final response = await http.get( + Uri.parse('$defaultUrl$compilersEndpoint/$langName'), + headers: { + HttpHeaders.acceptHeader: 'application/json' + }); + + if (response.statusCode == HttpStatus.ok) { + return JsonMapper.deserialize>(response.body) + as List; + } else { + throw Exception( + 'Failed to fetch compilers for $langName: ${response.statusCode}'); + } + } +} diff --git a/lib/model/language.dart b/lib/model/language.dart new file mode 100644 index 0000000..4e83e63 --- /dev/null +++ b/lib/model/language.dart @@ -0,0 +1,29 @@ +import 'dart:io'; + +import 'package:ce_mobile/consts.dart'; +import 'package:dart_json_mapper/dart_json_mapper.dart'; +import 'package:http/http.dart' as http; + +@jsonSerializable +class Language { + final String id; + final String name; + final List extensions; + final String monaco; + + const Language(this.id, this.name, this.extensions, this.monaco); + + static Future> fetchLanguages() async { + final response = await http.get(Uri.parse('$defaultUrl$languagesEndpoint'), + headers: { + HttpHeaders.acceptHeader: 'application/json' + }); + + if (response.statusCode == HttpStatus.ok) { + return JsonMapper.deserialize>(response.body) + as List; + } else { + throw Exception('Failed to fetch languages: ${response.statusCode}'); + } + } +} diff --git a/lib/model/workspace.dart b/lib/model/workspace.dart index 387e163..d9f7dd2 100644 --- a/lib/model/workspace.dart +++ b/lib/model/workspace.dart @@ -1,96 +1,20 @@ -import 'dart:developer'; -import 'dart:io'; - -import 'package:http/http.dart' as http; import 'package:ce_mobile/consts.dart'; +import 'package:ce_mobile/model/compiler.dart'; +import 'package:ce_mobile/model/language.dart'; import 'package:dart_json_mapper/dart_json_mapper.dart'; -import 'package:isar/isar.dart'; - -part 'workspace.g.dart'; +import 'package:uuid/uuid.dart'; @jsonSerializable -@embedded -class Compiler { - final String id; - final String name; - final String lang; - final String compilerType; - final String semver; - final String instructionSet; - - const Compiler.create(this.id, this.name, this.lang, this.compilerType, this.semver, - this.instructionSet); - - Compiler() : id = '', name = '', lang = '', compilerType = '', semver = '', instructionSet = ''; - - static Future> fetchCompilers() async { - final response = await http.get(Uri.parse('$defaultUrl$compilersEndpoint'), - headers: { - HttpHeaders.acceptHeader: 'application/json' - }); - - if (response.statusCode == HttpStatus.ok) { - return JsonMapper.deserialize>(response.body) as List; - } else { - throw Exception('Failed to fetch compilers: ${response.statusCode}'); - } - } - - static Future> fetchCompilersForLanguage( - String langName) async { - final response = await http.get( - Uri.parse('$defaultUrl$compilersEndpoint/$langName'), - headers: { - HttpHeaders.acceptHeader: 'application/json' - }); - - if (response.statusCode == HttpStatus.ok) { - return JsonMapper.deserialize>(response.body) as List; - } else { - throw Exception( - 'Failed to fetch compilers for $langName: ${response.statusCode}'); - } - } -} - -@jsonSerializable -@embedded -class Language { - final String id; - final String name; - final List extensions; - final String monaco; - - Language() : id = '', name = '', extensions = List.empty(), monaco = ''; - - const Language.create(this.id, this.name, this.extensions, this.monaco); - - static Future> fetchLanguages() async { - final response = await http.get(Uri.parse('$defaultUrl$languagesEndpoint'), headers: { HttpHeaders.acceptHeader: 'application/json' }); - - if (response.statusCode == HttpStatus.ok) { - return JsonMapper.deserialize>(response.body) as List; - } else { - throw Exception('Failed to fetch languages: ${response.statusCode}'); - } - } -} - -@embedded class WorkspaceFile { String filename; String code; - WorkspaceFile() : filename = '', code = '' { - log('WorkspaceFile empty constructor was called from:\n${StackTrace.current}'); - } - - WorkspaceFile.create(this.filename, this.code); + WorkspaceFile(this.filename, this.code); } -@collection +@jsonSerializable class Workspace { - Id id = Isar.autoIncrement; + String uuid; String name; Compiler currentCompiler; @@ -100,10 +24,10 @@ class Workspace { late List files; - @ignore + @JsonProperty(ignore: true) bool saveOnDisk; - Workspace(this.name, {this.saveOnDisk = true}) : currentCompiler = defaultCompiler, currentLanguage = defaultLanguage { - files = List.of([WorkspaceFile.create('main${currentLanguage.extensions.first}', '')]); + Workspace(this.name, {this.saveOnDisk = true}) : currentCompiler = defaultCompiler, currentLanguage = defaultLanguage, uuid = const Uuid().v4() { + files = List.of([WorkspaceFile('main${currentLanguage.extensions.first}', '')]); } } diff --git a/lib/pages/editor_page.dart b/lib/pages/editor_page.dart index 7bf7e56..230912e 100644 --- a/lib/pages/editor_page.dart +++ b/lib/pages/editor_page.dart @@ -2,7 +2,7 @@ import 'dart:developer'; import 'package:ce_mobile/model/workspace.dart'; import 'package:ce_mobile/services/ce_service.dart'; -import 'package:ce_mobile/services/isar_service.dart'; +import 'package:ce_mobile/services/sembast_service.dart'; import 'package:ce_mobile/widgets/code_editor.dart'; import 'package:flutter/material.dart'; @@ -16,7 +16,7 @@ class EditorPage extends StatefulWidget { } class _EditorPageState extends State { - final IsarService _service = IsarService(); + final SembastService _service = SembastService(); // late PageController _pageController; @@ -55,7 +55,6 @@ class _EditorPageState extends State { return PopScope( onPopInvoked: (didPop) { widget.workspace.lastModified = DateTime.now(); - log(widget.workspace.currentCompiler.name); if (widget.workspace.saveOnDisk) { Future.wait([_service.saveWorkspace(widget.workspace)]); } @@ -63,7 +62,7 @@ class _EditorPageState extends State { child: Scaffold( appBar: AppBar( backgroundColor: Theme.of(context).colorScheme.inversePrimary, - title: Text('Editor [${widget.workspace.name}] (${widget.workspace.currentCompiler.name})'), + title: Text('Editor [${widget.workspace.name}] (${widget.workspace.uuid})'), ), body: PageView.builder( itemCount: _pageCount, diff --git a/lib/pages/welcome_page.dart b/lib/pages/welcome_page.dart index d086fe2..a50f0bf 100644 --- a/lib/pages/welcome_page.dart +++ b/lib/pages/welcome_page.dart @@ -1,8 +1,8 @@ import 'dart:developer'; -import 'package:ce_mobile/services/isar_service.dart'; import 'package:ce_mobile/model/workspace.dart'; import 'package:ce_mobile/pages/editor_page.dart'; +import 'package:ce_mobile/services/sembast_service.dart'; import 'package:ce_mobile/widgets/create_dialog.dart'; import 'package:flutter/material.dart'; import 'package:intl/intl.dart'; @@ -15,7 +15,7 @@ class WelcomePage extends StatefulWidget { } class _WelcomePageState extends State { - final IsarService isarService = IsarService(); + final SembastService _service = SembastService(); @override Widget build(BuildContext context) { @@ -73,31 +73,49 @@ class _WelcomePageState extends State { style: TextStyle(decoration: TextDecoration.underline), )), StreamBuilder( - stream: isarService.streamRecentWorkspaces(), + stream: _service.streamRecentWorkspaces(), builder: (context, snapshot) { if (snapshot.hasData) { final workspaces = snapshot.data; if (workspaces != null) { - return Expanded( - child: ListView.builder( - itemCount: workspaces.length, - itemBuilder: (context, index) { - final workspace = workspaces[index]; + if (workspaces.isNotEmpty) { + return Expanded( + child: ListView.builder( + itemCount: workspaces.length, + itemBuilder: (context, index) { + final workspace = workspaces[index]; - return ListTile( - title: Text('${workspace.name} - ${workspace.currentCompiler.name} - ${workspace.files.first.filename}'), - subtitle: Text( - 'Last modified: ${DateFormat('dd-MM-yyyy kk:mm').format(workspace.lastModified)}'), - onTap: () { - Navigator.push( - context, - MaterialPageRoute( - builder: (context) => EditorPage( - workspace: workspace))); - }, - ); - }), - ); + return ListTile( + title: Text( + '${workspace.name} - ${workspace.currentCompiler.name} - ${workspace.uuid}'), + subtitle: Text( + 'Last modified: ${DateFormat('dd-MM-yyyy kk:mm').format(workspace.lastModified)}'), + onTap: () { + Navigator.push( + context, + MaterialPageRoute( + builder: (context) => EditorPage( + workspace: workspace))); + }, + ); + }), + ); + } else { + return Expanded( + child: Center( + child: Container( + padding: const EdgeInsets.all(4), + decoration: BoxDecoration( + border: Border.all( + color: Theme.of(context).colorScheme.error)), + child: Text( + 'You have no projects.', + style: TextStyle( + color: Theme.of(context).colorScheme.error, + ), + ), + ))); + } } } else if (snapshot.hasError) { log('Error: ${snapshot.error.toString()}'); diff --git a/lib/services/ce_service.dart b/lib/services/ce_service.dart index 93d1adb..e54fe4f 100644 --- a/lib/services/ce_service.dart +++ b/lib/services/ce_service.dart @@ -1,9 +1,9 @@ import 'dart:io'; +import 'package:ce_mobile/consts.dart'; +import 'package:ce_mobile/model/compiler.dart'; import 'package:dart_json_mapper/dart_json_mapper.dart'; -import '../consts.dart'; -import '../model/workspace.dart'; import 'package:http/http.dart' as http; @jsonSerializable @@ -78,7 +78,7 @@ class CEService { final json = JsonMapper.serialize(request); final response = await http.post( - Uri.parse('$defaultUrl$compileEndpoint/g132/compile'), + Uri.parse('$defaultUrl$compileEndpoint/${cc.id}/compile'), body: json, headers: { HttpHeaders.contentTypeHeader: 'application/json', diff --git a/lib/services/isar_service.dart b/lib/services/isar_service.dart deleted file mode 100644 index 8cfb244..0000000 --- a/lib/services/isar_service.dart +++ /dev/null @@ -1,31 +0,0 @@ -import 'package:ce_mobile/model/workspace.dart'; -import 'package:isar/isar.dart'; -import 'package:path_provider/path_provider.dart'; - -class IsarService { - late Future db; - - IsarService() { - db = openDB(); - } - - Future saveWorkspace(Workspace workspace) async { - final isar = await db; - isar.writeTxnSync(() => isar.workspaces.putSync(workspace)); - } - - Stream> streamRecentWorkspaces() async* { - final isar = await db; - yield* isar.workspaces.where().sortByLastModifiedDesc().watch(fireImmediately: true); - } - - Future openDB() async { - final directory = await getApplicationDocumentsDirectory(); - - if (Isar.instanceNames.isEmpty) { - return await Isar.open([WorkspaceSchema], directory: directory.path); - } - - return Future.value(Isar.getInstance()); - } -} diff --git a/lib/services/sembast_service.dart b/lib/services/sembast_service.dart new file mode 100644 index 0000000..0240cb9 --- /dev/null +++ b/lib/services/sembast_service.dart @@ -0,0 +1,61 @@ +import 'dart:async'; +import 'dart:developer'; + +import 'package:ce_mobile/model/workspace.dart'; +import 'package:dart_json_mapper/dart_json_mapper.dart'; +import 'package:path_provider/path_provider.dart'; +import 'package:sembast/sembast.dart'; +import 'package:sembast/sembast_io.dart'; + +class SembastService { + bool isOpen = false; + late Future database; + + SembastService() { + database = openDB(); + } + + Future openDB() async { + final dir = await getApplicationDocumentsDirectory(); + + if (!isOpen) { + isOpen = true; + return await databaseFactoryIo.openDatabase('${dir.path}/workspaces.db'); + } + + return database; + } + + Future saveWorkspace(Workspace workspace) async { + final db = await database; + final store = stringMapStoreFactory.store('workspaces'); + final json = JsonMapper.toMap(workspace) as Map; + + await db.transaction((transaction) async { + final record = store.record(workspace.uuid); + + await record + .put(transaction, json, merge: true) + .then((value) => log('Updated')); + }); + } + + Stream> streamRecentWorkspaces() async* { + final db = await database; + final store = stringMapStoreFactory.store('workspaces'); + + yield* store + .query(finder: Finder(sortOrders: [SortOrder('lastModified', false)])) + .onSnapshots(db) + .transform(StreamTransformer< + List>>, + List>.fromHandlers(handleData: (data, sink) { + sink.add(List.generate(data.length, (index) { + final uuid = data[index].value['uuid']! as String; + final json = JsonMapper.deserialize(data[index].value)!; + json.uuid = uuid; + return json; + })); + })); + } +} diff --git a/lib/widgets/create_dialog.dart b/lib/widgets/create_dialog.dart index 0c4e8e7..705579d 100644 --- a/lib/widgets/create_dialog.dart +++ b/lib/widgets/create_dialog.dart @@ -1,7 +1,10 @@ -import 'package:ce_mobile/services/isar_service.dart'; + import 'package:ce_mobile/model/workspace.dart'; +import 'package:ce_mobile/services/sembast_service.dart'; import 'package:flutter/material.dart'; +import '../pages/editor_page.dart'; + class CreateDialog extends StatefulWidget { const CreateDialog({super.key}); @@ -12,7 +15,7 @@ class CreateDialog extends StatefulWidget { class _CreateDialogState extends State { late TextEditingController _controller; bool _saveOnDisk = true; - final IsarService service = IsarService(); + final SembastService _service = SembastService(); @override void initState() { @@ -77,16 +80,16 @@ class _CreateDialogState extends State { Workspace w = Workspace( _controller.text, saveOnDisk: _saveOnDisk); if (w.saveOnDisk) { - Future.wait([service.saveWorkspace(w)]); + Future.wait([_service.saveWorkspace(w)]); } Navigator.pop(context); - // Navigator.push( - // context, - // MaterialPageRoute( - // builder: (context) => - // EditorPage(workspace: w), - // )); + Navigator.push( + context, + MaterialPageRoute( + builder: (context) => + EditorPage(workspace: w), + )); }, child: const Text('Create')), ], diff --git a/linux/flutter/generated_plugin_registrant.cc b/linux/flutter/generated_plugin_registrant.cc index b898c8c..e71a16d 100644 --- a/linux/flutter/generated_plugin_registrant.cc +++ b/linux/flutter/generated_plugin_registrant.cc @@ -6,10 +6,6 @@ #include "generated_plugin_registrant.h" -#include void fl_register_plugins(FlPluginRegistry* registry) { - g_autoptr(FlPluginRegistrar) isar_flutter_libs_registrar = - fl_plugin_registry_get_registrar_for_plugin(registry, "IsarFlutterLibsPlugin"); - isar_flutter_libs_plugin_register_with_registrar(isar_flutter_libs_registrar); } diff --git a/linux/flutter/generated_plugins.cmake b/linux/flutter/generated_plugins.cmake index cb083af..2e1de87 100644 --- a/linux/flutter/generated_plugins.cmake +++ b/linux/flutter/generated_plugins.cmake @@ -3,7 +3,6 @@ # list(APPEND FLUTTER_PLUGIN_LIST - isar_flutter_libs ) list(APPEND FLUTTER_FFI_PLUGIN_LIST diff --git a/macos/Flutter/GeneratedPluginRegistrant.swift b/macos/Flutter/GeneratedPluginRegistrant.swift index 633bc13..e777c67 100644 --- a/macos/Flutter/GeneratedPluginRegistrant.swift +++ b/macos/Flutter/GeneratedPluginRegistrant.swift @@ -5,10 +5,8 @@ import FlutterMacOS import Foundation -import isar_flutter_libs import path_provider_foundation func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { - IsarFlutterLibsPlugin.register(with: registry.registrar(forPlugin: "IsarFlutterLibsPlugin")) PathProviderPlugin.register(with: registry.registrar(forPlugin: "PathProviderPlugin")) } diff --git a/pubspec.lock b/pubspec.lock index 3a45257..32332c9 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -193,14 +193,6 @@ packages: url: "https://pub.dev" source: hosted version: "2.3.2" - dartx: - dependency: transitive - description: - name: dartx - sha256: "8b25435617027257d43e6508b5fe061012880ddfdaa75a71d607c3de2a13d244" - url: "https://pub.dev" - source: hosted - version: "1.2.0" fake_async: dependency: transitive description: @@ -339,30 +331,6 @@ packages: url: "https://pub.dev" source: hosted version: "1.0.4" - isar: - dependency: "direct main" - description: - name: isar - sha256: "99165dadb2cf2329d3140198363a7e7bff9bbd441871898a87e26914d25cf1ea" - url: "https://pub.dev" - source: hosted - version: "3.1.0+1" - isar_flutter_libs: - dependency: "direct main" - description: - name: isar_flutter_libs - sha256: bc6768cc4b9c61aabff77152e7f33b4b17d2fc93134f7af1c3dd51500fe8d5e8 - url: "https://pub.dev" - source: hosted - version: "3.1.0+1" - isar_generator: - dependency: "direct dev" - description: - name: isar_generator - sha256: "76c121e1295a30423604f2f819bc255bc79f852f3bc8743a24017df6068ad133" - url: "https://pub.dev" - source: hosted - version: "3.1.0+1" js: dependency: transitive description: @@ -468,7 +436,7 @@ packages: source: hosted version: "2.1.0" path: - dependency: transitive + dependency: "direct main" description: name: path sha256: "087ce49c3f0dc39180befefc60fdb4acd8f8620e5682fe2476afd0b3688bb4af" @@ -571,6 +539,14 @@ packages: url: "https://pub.dev" source: hosted version: "4.0.5" + sembast: + dependency: "direct main" + description: + name: sembast + sha256: dbe19600cff55d43f19405be79138c3fd2c08a87b0b152b18609b9427d113a64 + url: "https://pub.dev" + source: hosted + version: "3.7.1" shelf: dependency: transitive description: @@ -592,14 +568,6 @@ packages: description: flutter source: sdk version: "0.0.99" - source_gen: - dependency: transitive - description: - name: source_gen - sha256: "14658ba5f669685cd3d63701d01b31ea748310f7ab854e471962670abcf57832" - url: "https://pub.dev" - source: hosted - version: "1.5.0" source_span: dependency: transitive description: @@ -608,6 +576,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.10.0" + sprintf: + dependency: transitive + description: + name: sprintf + sha256: "1fc9ffe69d4df602376b52949af107d8f5703b77cda567c4d7d86a0693120f23" + url: "https://pub.dev" + source: hosted + version: "7.0.0" stack_trace: dependency: transitive description: @@ -640,6 +616,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.2.0" + synchronized: + dependency: transitive + description: + name: synchronized + sha256: "539ef412b170d65ecdafd780f924e5be3f60032a1128df156adad6c5b373d558" + url: "https://pub.dev" + source: hosted + version: "3.1.0+1" term_glyph: dependency: transitive description: @@ -656,14 +640,6 @@ packages: url: "https://pub.dev" source: hosted version: "0.6.1" - time: - dependency: transitive - description: - name: time - sha256: ad8e018a6c9db36cb917a031853a1aae49467a93e0d464683e029537d848c221 - url: "https://pub.dev" - source: hosted - version: "2.1.4" timing: dependency: transitive description: @@ -680,6 +656,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.3.2" + uuid: + dependency: "direct main" + description: + name: uuid + sha256: "814e9e88f21a176ae1359149021870e87f7cddaf633ab678a5d2b0bff7fd1ba8" + url: "https://pub.dev" + source: hosted + version: "4.4.0" vector_math: dependency: transitive description: @@ -736,14 +720,6 @@ packages: url: "https://pub.dev" source: hosted version: "1.0.4" - xxh3: - dependency: transitive - description: - name: xxh3 - sha256: a92b30944a9aeb4e3d4f3c3d4ddb3c7816ca73475cd603682c4f8149690f56d7 - url: "https://pub.dev" - source: hosted - version: "1.0.1" yaml: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index 08c05fc..89af805 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -37,16 +37,17 @@ dependencies: cupertino_icons: ^1.0.6 http: ^1.2.1 dart_json_mapper: ^2.2.10 - isar: ^3.1.0+1 - isar_flutter_libs: ^3.1.0+1 - path_provider: ^2.1.3 + path_provider: ^2.0.0 + path: ^1.9.0 intl: ^0.18.1 code_text_field: ^1.1.0 google_fonts: ^6.2.1 + sembast: ^3.7.1 + uuid: ^4.4.0 dev_dependencies: build_runner: any - isar_generator: ^3.1.0+1 + flutter_test: sdk: flutter diff --git a/windows/flutter/generated_plugin_registrant.cc b/windows/flutter/generated_plugin_registrant.cc index afc39a1..8b6d468 100644 --- a/windows/flutter/generated_plugin_registrant.cc +++ b/windows/flutter/generated_plugin_registrant.cc @@ -6,9 +6,6 @@ #include "generated_plugin_registrant.h" -#include void RegisterPlugins(flutter::PluginRegistry* registry) { - IsarFlutterLibsPluginRegisterWithRegistrar( - registry->GetRegistrarForPlugin("IsarFlutterLibsPlugin")); } diff --git a/windows/flutter/generated_plugins.cmake b/windows/flutter/generated_plugins.cmake index 2a57005..b93c4c3 100644 --- a/windows/flutter/generated_plugins.cmake +++ b/windows/flutter/generated_plugins.cmake @@ -3,7 +3,6 @@ # list(APPEND FLUTTER_PLUGIN_LIST - isar_flutter_libs ) list(APPEND FLUTTER_FFI_PLUGIN_LIST