diff --git a/lib/app.dart b/lib/app.dart index 3d36968a..7083eb4a 100644 --- a/lib/app.dart +++ b/lib/app.dart @@ -4,7 +4,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_portal/flutter_portal.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:window_manager/window_manager.dart' hide WindowCaption; -import 'widgets/widgets.dart' show WindowCaption; +import 'widgets/widgets.dart' show WindowCaption, WorkspaceSelector; import 'providers/providers.dart'; import 'extensions/extensions.dart'; import 'screens/screens.dart'; diff --git a/lib/main.dart b/lib/main.dart index 4a8b07fd..6c70bdf8 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -1,35 +1,62 @@ +import 'package:apidash/providers/settings_providers.dart'; import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:google_fonts/google_fonts.dart'; +import 'models/models.dart'; +import 'providers/providers.dart'; import 'services/services.dart'; -import 'consts.dart' show kIsLinux, kIsMacOS, kIsWindows; +import 'consts.dart'; import 'app.dart'; void main() async { WidgetsFlutterBinding.ensureInitialized(); - - await initApp(); - await initWindow(); + final settingsModel = await getSettingsFromSharedPrefs(); + await initApp(settingsModel: settingsModel); + if (kIsDesktop) { + await initWindow(settingsModel: settingsModel); + } runApp( - const ProviderScope( - child: DashApp(), + ProviderScope( + overrides: [ + settingsProvider.overrideWith( + (ref) => ThemeStateNotifier(settingsModel: settingsModel), + ) + ], + child: const DashApp(), ), ); } -Future initApp() async { +Future initApp({SettingsModel? settingsModel}) async { GoogleFonts.config.allowRuntimeFetching = false; - await openBoxes(); - await autoClearHistory(); + await openBoxes( + kIsDesktop, + settingsModel?.workspaceFolderPath, + ); + await autoClearHistory(settingsModel: settingsModel); } -Future initWindow({Size? sz}) async { +Future initWindow({ + Size? sz, + SettingsModel? settingsModel, +}) async { if (kIsLinux) { - await setupInitialWindow(sz: sz); + await setupInitialWindow( + sz: sz ?? settingsModel?.size, + ); } if (kIsMacOS || kIsWindows) { - var win = sz != null ? (sz, const Offset(100, 100)) : getInitialSize(); - await setupWindow(sz: win.$1, off: win.$2); + if (sz != null) { + await setupWindow( + sz: sz, + off: const Offset(100, 100), + ); + } else { + await setupWindow( + sz: settingsModel?.size, + off: settingsModel?.offset, + ); + } } } diff --git a/lib/models/settings_model.dart b/lib/models/settings_model.dart index e82ffdca..e7e5c3b0 100644 --- a/lib/models/settings_model.dart +++ b/lib/models/settings_model.dart @@ -14,6 +14,7 @@ class SettingsModel { this.promptBeforeClosing = true, this.activeEnvironmentId, this.historyRetentionPeriod = HistoryRetentionPeriod.oneWeek, + this.workspaceFolderPath, }); final bool isDark; @@ -26,6 +27,7 @@ class SettingsModel { final bool promptBeforeClosing; final String? activeEnvironmentId; final HistoryRetentionPeriod historyRetentionPeriod; + final String? workspaceFolderPath; SettingsModel copyWith({ bool? isDark, @@ -38,6 +40,7 @@ class SettingsModel { bool? promptBeforeClosing, String? activeEnvironmentId, HistoryRetentionPeriod? historyRetentionPeriod, + String? workspaceFolderPath, }) { return SettingsModel( isDark: isDark ?? this.isDark, @@ -52,6 +55,7 @@ class SettingsModel { activeEnvironmentId: activeEnvironmentId ?? this.activeEnvironmentId, historyRetentionPeriod: historyRetentionPeriod ?? this.historyRetentionPeriod, + workspaceFolderPath: workspaceFolderPath ?? this.workspaceFolderPath, ); } @@ -86,8 +90,7 @@ class SettingsModel { final promptBeforeClosing = data["promptBeforeClosing"] as bool?; final activeEnvironmentId = data["activeEnvironmentId"] as String?; final historyRetentionPeriodStr = data["historyRetentionPeriod"] as String?; - HistoryRetentionPeriod historyRetentionPeriod = - HistoryRetentionPeriod.oneWeek; + HistoryRetentionPeriod? historyRetentionPeriod; if (historyRetentionPeriodStr != null) { try { historyRetentionPeriod = @@ -96,6 +99,7 @@ class SettingsModel { // pass } } + final workspaceFolderPath = data["workspaceFolderPath"] as String?; const sm = SettingsModel(); @@ -109,7 +113,9 @@ class SettingsModel { saveResponses: saveResponses, promptBeforeClosing: promptBeforeClosing, activeEnvironmentId: activeEnvironmentId, - historyRetentionPeriod: historyRetentionPeriod, + historyRetentionPeriod: + historyRetentionPeriod ?? HistoryRetentionPeriod.oneWeek, + workspaceFolderPath: workspaceFolderPath, ); } @@ -127,12 +133,13 @@ class SettingsModel { "promptBeforeClosing": promptBeforeClosing, "activeEnvironmentId": activeEnvironmentId, "historyRetentionPeriod": historyRetentionPeriod.name, + "workspaceFolderPath": workspaceFolderPath, }; } @override String toString() { - return toJson().toString(); + return kJsonEncoder.convert(toJson()); } @override @@ -149,7 +156,8 @@ class SettingsModel { other.saveResponses == saveResponses && other.promptBeforeClosing == promptBeforeClosing && other.activeEnvironmentId == activeEnvironmentId && - other.historyRetentionPeriod == historyRetentionPeriod; + other.historyRetentionPeriod == historyRetentionPeriod && + other.workspaceFolderPath == workspaceFolderPath; } @override @@ -166,6 +174,7 @@ class SettingsModel { promptBeforeClosing, activeEnvironmentId, historyRetentionPeriod, + workspaceFolderPath, ); } } diff --git a/lib/providers/settings_providers.dart b/lib/providers/settings_providers.dart index 6293e04b..43256ab3 100644 --- a/lib/providers/settings_providers.dart +++ b/lib/providers/settings_providers.dart @@ -1,7 +1,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import '../models/models.dart'; -import '../services/services.dart' show hiveHandler, HiveHandler; +import '../services/services.dart'; import '../consts.dart'; final codegenLanguageStateProvider = StateProvider((ref) => @@ -11,14 +11,13 @@ final activeEnvironmentIdStateProvider = StateProvider((ref) => ref.watch(settingsProvider.select((value) => value.activeEnvironmentId))); final StateNotifierProvider - settingsProvider = - StateNotifierProvider((ref) => ThemeStateNotifier(hiveHandler)); + settingsProvider = StateNotifierProvider((ref) => ThemeStateNotifier()); class ThemeStateNotifier extends StateNotifier { - ThemeStateNotifier(this.hiveHandler) : super(const SettingsModel()) { - state = SettingsModel.fromJson(hiveHandler.settings); + ThemeStateNotifier({this.settingsModel}) : super(const SettingsModel()) { + state = settingsModel ?? const SettingsModel(); } - final HiveHandler hiveHandler; + final SettingsModel? settingsModel; Future update({ bool? isDark, @@ -31,6 +30,7 @@ class ThemeStateNotifier extends StateNotifier { bool? promptBeforeClosing, String? activeEnvironmentId, HistoryRetentionPeriod? historyRetentionPeriod, + String? workspaceFolderPath, }) async { state = state.copyWith( isDark: isDark, @@ -43,7 +43,8 @@ class ThemeStateNotifier extends StateNotifier { promptBeforeClosing: promptBeforeClosing, activeEnvironmentId: activeEnvironmentId, historyRetentionPeriod: historyRetentionPeriod, + workspaceFolderPath: workspaceFolderPath, ); - await hiveHandler.saveSettings(state.toJson()); + await setSettingsToSharedPrefs(state); } } diff --git a/lib/services/history_service.dart b/lib/services/history_service.dart index d34d17fb..487b9164 100644 --- a/lib/services/history_service.dart +++ b/lib/services/history_service.dart @@ -1,18 +1,9 @@ import 'package:apidash/models/models.dart'; import 'package:apidash/utils/utils.dart'; -import 'package:apidash/consts.dart'; import 'hive_services.dart'; -Future autoClearHistory() async { - final settingsMap = hiveHandler.settings; - final retentionPeriod = settingsMap['historyRetentionPeriod']; - - HistoryRetentionPeriod historyRetentionPeriod = - HistoryRetentionPeriod.oneWeek; - if (retentionPeriod != null) { - historyRetentionPeriod = - HistoryRetentionPeriod.values.byName(retentionPeriod); - } +Future autoClearHistory({SettingsModel? settingsModel}) async { + final historyRetentionPeriod = settingsModel?.historyRetentionPeriod; DateTime? retentionDate = getRetentionDate(historyRetentionPeriod); if (retentionDate == null) { diff --git a/lib/services/hive_services.dart b/lib/services/hive_services.dart index 1a0f7ac0..3caa3c28 100644 --- a/lib/services/hive_services.dart +++ b/lib/services/hive_services.dart @@ -1,4 +1,3 @@ -import 'package:flutter/material.dart'; import 'package:hive_flutter/hive_flutter.dart'; const String kDataBox = "apidash-data"; @@ -11,54 +10,36 @@ const String kHistoryMetaBox = "apidash-history-meta"; const String kHistoryBoxIds = "historyIds"; const String kHistoryLazyBox = "apidash-history-lazy"; -const String kSettingsBox = "apidash-settings"; - -Future openBoxes() async { - await Hive.initFlutter(); +Future openBoxes( + bool isDesktop, + String? workspaceFolderPath, +) async { + if (isDesktop) { + Hive.init(workspaceFolderPath); + } else { + await Hive.initFlutter(); + } await Hive.openBox(kDataBox); - await Hive.openBox(kSettingsBox); await Hive.openBox(kEnvironmentBox); await Hive.openBox(kHistoryMetaBox); await Hive.openLazyBox(kHistoryLazyBox); } -(Size?, Offset?) getInitialSize() { - Size? sz; - Offset? off; - var settingsBox = Hive.box(kSettingsBox); - double? w = settingsBox.get("width") as double?; - double? h = settingsBox.get("height") as double?; - if (w != null && h != null) { - sz = Size(w, h); - } - double? dx = settingsBox.get("dx") as double?; - double? dy = settingsBox.get("dy") as double?; - if (dx != null && dy != null) { - off = Offset(dx, dy); - } - return (sz, off); -} - final hiveHandler = HiveHandler(); class HiveHandler { late final Box dataBox; - late final Box settingsBox; late final Box environmentBox; late final Box historyMetaBox; late final LazyBox historyLazyBox; HiveHandler() { dataBox = Hive.box(kDataBox); - settingsBox = Hive.box(kSettingsBox); environmentBox = Hive.box(kEnvironmentBox); historyMetaBox = Hive.box(kHistoryMetaBox); historyLazyBox = Hive.lazyBox(kHistoryLazyBox); } - Map get settings => settingsBox.toMap(); - Future saveSettings(Map data) => settingsBox.putAll(data); - dynamic getIds() => dataBox.get(kKeyDataBoxIds); Future setIds(List? ids) => dataBox.put(kKeyDataBoxIds, ids); diff --git a/lib/services/services.dart b/lib/services/services.dart index 7551de9b..fd8ca0b0 100644 --- a/lib/services/services.dart +++ b/lib/services/services.dart @@ -2,3 +2,4 @@ export 'http_service.dart'; export 'hive_services.dart'; export 'history_service.dart'; export 'window_services.dart'; +export 'shared_preferences_services.dart'; diff --git a/lib/services/shared_preferences_services.dart b/lib/services/shared_preferences_services.dart new file mode 100644 index 00000000..af1bbd71 --- /dev/null +++ b/lib/services/shared_preferences_services.dart @@ -0,0 +1,23 @@ +import 'package:apidash/consts.dart'; +import 'package:apidash/models/models.dart'; +import 'package:shared_preferences/shared_preferences.dart'; + +const String kSharedPrefSettingsKey = 'apidash-settings'; + +Future getSettingsFromSharedPrefs() async { + final prefs = await SharedPreferences.getInstance(); + var settingsStr = prefs.getString(kSharedPrefSettingsKey); + if (settingsStr != null) { + var jsonSettings = kJsonDecoder.convert(settingsStr); + var jsonMap = Map.from(jsonSettings); + var settingsModel = SettingsModel.fromJson(jsonMap); + return settingsModel; + } else { + return null; + } +} + +Future setSettingsToSharedPrefs(SettingsModel settingsModel) async { + final prefs = await SharedPreferences.getInstance(); + await prefs.setString(kSharedPrefSettingsKey, settingsModel.toString()); +} diff --git a/lib/utils/history_utils.dart b/lib/utils/history_utils.dart index cce87998..e6db9d38 100644 --- a/lib/utils/history_utils.dart +++ b/lib/utils/history_utils.dart @@ -114,7 +114,7 @@ List getRequestGroup( return requestGroup; } -DateTime? getRetentionDate(HistoryRetentionPeriod retentionPeriod) { +DateTime? getRetentionDate(HistoryRetentionPeriod? retentionPeriod) { DateTime now = DateTime.now(); DateTime today = stripTime(now); diff --git a/lib/widgets/widgets.dart b/lib/widgets/widgets.dart index 06bc4bef..b7a8732e 100644 --- a/lib/widgets/widgets.dart +++ b/lib/widgets/widgets.dart @@ -60,3 +60,4 @@ export 'tabs.dart'; export 'texts.dart'; export 'uint8_audio_player.dart'; export 'window_caption.dart'; +export 'workspace_selector.dart'; diff --git a/lib/widgets/workspace_selector.dart b/lib/widgets/workspace_selector.dart new file mode 100644 index 00000000..1ff7ddd5 --- /dev/null +++ b/lib/widgets/workspace_selector.dart @@ -0,0 +1,62 @@ +import 'package:file_selector/file_selector.dart'; +import 'package:apidash/services/hive_services.dart'; +import 'package:flutter/material.dart'; + +class WorkspaceSelector extends StatefulWidget { + final Future Function(String)? onSelect; + const WorkspaceSelector({ + super.key, + required this.onSelect, + }); + + @override + WorkspaceSelectorState createState() => WorkspaceSelectorState(); +} + +class WorkspaceSelectorState extends State { + void selectFolder() async { + String? selectedDirectory = await getDirectoryPath(); + if (selectedDirectory != null) { + widget.onSelect?.call(selectedDirectory); + } + } + + @override + Widget build(BuildContext context) { + const circularLoader = MaterialApp( + home: Scaffold( + body: Center( + child: CircularProgressIndicator(), + ), + ), + ); + + return FutureBuilder( + future: getHiveSaveFolder(), + builder: (BuildContext context, AsyncSnapshot snapshot) { + if (snapshot.connectionState != ConnectionState.done) { + return circularLoader; + } + + // If there isn't hive selected folder choose it + if (snapshot.data == null) { + selectFolder(); + return circularLoader; + } + + // Once _hiveSaveFolder is set, display DashApp after hive init + return FutureBuilder( + future: openHiveBoxes(snapshot.data!), + builder: (BuildContext context, AsyncSnapshot snapshot) { + // if loading show circularLoader + if (snapshot.connectionState != ConnectionState.done) { + return circularLoader; + } + // Display widget + return widget.child; + }, + ); + }, + ); + } +} diff --git a/pubspec.lock b/pubspec.lock index f4140996..2c257859 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -1215,6 +1215,62 @@ packages: url: "https://pub.dev" source: hosted version: "0.3.8" + shared_preferences: + dependency: "direct main" + description: + name: shared_preferences + sha256: "746e5369a43170c25816cc472ee016d3a66bc13fcf430c0bc41ad7b4b2922051" + url: "https://pub.dev" + source: hosted + version: "2.3.2" + shared_preferences_android: + dependency: transitive + description: + name: shared_preferences_android + sha256: "480ba4345773f56acda9abf5f50bd966f581dac5d514e5fc4a18c62976bbba7e" + url: "https://pub.dev" + source: hosted + version: "2.3.2" + shared_preferences_foundation: + dependency: transitive + description: + name: shared_preferences_foundation + sha256: c4b35f6cb8f63c147312c054ce7c2254c8066745125264f0c88739c417fc9d9f + url: "https://pub.dev" + source: hosted + version: "2.5.2" + shared_preferences_linux: + dependency: transitive + description: + name: shared_preferences_linux + sha256: "580abfd40f415611503cae30adf626e6656dfb2f0cee8f465ece7b6defb40f2f" + url: "https://pub.dev" + source: hosted + version: "2.4.1" + shared_preferences_platform_interface: + dependency: transitive + description: + name: shared_preferences_platform_interface + sha256: "57cbf196c486bc2cf1f02b85784932c6094376284b3ad5779d1b1c6c6a816b80" + url: "https://pub.dev" + source: hosted + version: "2.4.1" + shared_preferences_web: + dependency: transitive + description: + name: shared_preferences_web + sha256: d2ca4132d3946fec2184261726b355836a82c33d7d5b67af32692aff18a4684e + url: "https://pub.dev" + source: hosted + version: "2.4.2" + shared_preferences_windows: + dependency: transitive + description: + name: shared_preferences_windows + sha256: "94ef0f72b2d71bc3e700e025db3710911bd51a71cefb65cc609dd0d9a982e3c1" + url: "https://pub.dev" + source: hosted + version: "2.4.1" shelf: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index 0eadf529..a9bf5998 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -63,6 +63,7 @@ dependencies: provider: ^6.1.2 riverpod: ^2.5.1 scrollable_positioned_list: ^0.3.8 + shared_preferences: ^2.3.2 url_launcher: ^6.2.5 uuid: ^4.3.3 vector_graphics_compiler: ^1.1.9+1 diff --git a/test/providers/ui_providers_test.dart b/test/providers/ui_providers_test.dart index 39ef1ff6..60b67241 100644 --- a/test/providers/ui_providers_test.dart +++ b/test/providers/ui_providers_test.dart @@ -39,7 +39,7 @@ void main() { } return null; }); - await openBoxes(); + await openBoxes(false, null); final flamante = rootBundle.load('google_fonts/OpenSans-Medium.ttf'); final fontLoader = FontLoader('OpenSans')..addFont(flamante); await fontLoader.load();