From 802d9e4607abf9644f4e63457a67be8e589d90b8 Mon Sep 17 00:00:00 2001 From: Ashita Prasad Date: Sun, 8 Sep 2024 15:17:21 +0530 Subject: [PATCH 1/6] workspace --- lib/app.dart | 2 +- lib/main.dart | 53 ++++++++++++---- lib/models/settings_model.dart | 19 ++++-- lib/providers/settings_providers.dart | 15 ++--- lib/services/history_service.dart | 13 +--- lib/services/hive_services.dart | 37 +++-------- lib/services/services.dart | 1 + lib/services/shared_preferences_services.dart | 23 +++++++ lib/utils/history_utils.dart | 2 +- lib/widgets/widgets.dart | 1 + lib/widgets/workspace_selector.dart | 62 +++++++++++++++++++ pubspec.lock | 56 +++++++++++++++++ pubspec.yaml | 1 + test/providers/ui_providers_test.dart | 2 +- 14 files changed, 220 insertions(+), 67 deletions(-) create mode 100644 lib/services/shared_preferences_services.dart create mode 100644 lib/widgets/workspace_selector.dart diff --git a/lib/app.dart b/lib/app.dart index 3d36968a3..7083eb4a2 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 4a8b07fdb..6c70bdf89 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 e82ffdca4..e7e5c3b00 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 6293e04b7..43256ab32 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 d34d17fb8..487b91640 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 1a0f7ac03..3caa3c283 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 7551de9be..fd8ca0b0f 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 000000000..af1bbd71e --- /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 cce87998d..e6db9d387 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 06bc4bef9..b7a8732e5 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 000000000..1ff7ddd52 --- /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 f41409963..2c2578596 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 0eadf5292..a9bf5998c 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 39ef1ff68..60b67241b 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(); From 59fdbae41d84f23b8a6d7c9476428a680a8934ce Mon Sep 17 00:00:00 2001 From: Ashita Prasad Date: Mon, 9 Sep 2024 04:03:52 +0530 Subject: [PATCH 2/6] Workspace selector feature --- lib/app.dart | 53 ++++++--- lib/consts.dart | 5 + lib/main.dart | 28 +++-- lib/models/settings_model.dart | 18 +++ lib/services/hive_services.dart | 28 +++-- lib/widgets/field_outlined.dart | 54 +++++++++ lib/widgets/widgets.dart | 1 + lib/widgets/workspace_selector.dart | 167 ++++++++++++++++++++-------- 8 files changed, 273 insertions(+), 81 deletions(-) create mode 100644 lib/widgets/field_outlined.dart diff --git a/lib/app.dart b/lib/app.dart index 7083eb4a2..a2011dcda 100644 --- a/lib/app.dart +++ b/lib/app.dart @@ -6,6 +6,7 @@ import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:window_manager/window_manager.dart' hide WindowCaption; import 'widgets/widgets.dart' show WindowCaption, WorkspaceSelector; import 'providers/providers.dart'; +import 'services/services.dart'; import 'extensions/extensions.dart'; import 'screens/screens.dart'; import 'consts.dart'; @@ -107,29 +108,49 @@ class DashApp extends ConsumerWidget { Widget build(BuildContext context, WidgetRef ref) { final isDarkMode = ref.watch(settingsProvider.select((value) => value.isDark)); + final workspaceFolderPath = ref + .watch(settingsProvider.select((value) => value.workspaceFolderPath)); + final showWorkspaceSelector = kIsDesktop && (workspaceFolderPath == null); return Portal( child: MaterialApp( debugShowCheckedModeBanner: false, theme: kLightMaterialAppTheme, darkTheme: kDarkMaterialAppTheme, themeMode: isDarkMode ? ThemeMode.dark : ThemeMode.light, - home: Stack( - children: [ - !kIsLinux && !kIsMobile - ? const App() - : context.isMediumWindow - ? const MobileDashboard() - : const Dashboard(), - if (kIsWindows) - SizedBox( - height: 29, - child: WindowCaption( - backgroundColor: Colors.transparent, - brightness: isDarkMode ? Brightness.dark : Brightness.light, - ), + home: showWorkspaceSelector + ? WorkspaceSelector( + onContinue: (val) async { + await openBoxes(kIsDesktop, val); + ref + .read(settingsProvider.notifier) + .update(workspaceFolderPath: val); + }, + onCancel: () async { + try { + await windowManager.destroy(); + } catch (e) { + debugPrint(e.toString()); + } + }, + ) + : Stack( + children: [ + !kIsLinux && !kIsMobile + ? const App() + : context.isMediumWindow + ? const MobileDashboard() + : const Dashboard(), + if (kIsWindows) + SizedBox( + height: 29, + child: WindowCaption( + backgroundColor: Colors.transparent, + brightness: + isDarkMode ? Brightness.dark : Brightness.light, + ), + ), + ], ), - ], - ), ), ); } diff --git a/lib/consts.dart b/lib/consts.dart index 1681069a2..cb6791876 100644 --- a/lib/consts.dart +++ b/lib/consts.dart @@ -744,6 +744,9 @@ const kLabelSaving = "Saving"; const kLabelSaved = "Saved"; const kLabelCode = "Code"; const kLabelDuplicate = "Duplicate"; +const kLabelSelect = "Select"; +const kLabelContinue = "Continue"; +const kLabelCancel = "Cancel"; // Request Pane const kLabelRequest = "Request"; const kLabelHideCode = "Hide Code"; @@ -778,3 +781,5 @@ const kNullResponseModelError = "Error: Response data does not exist."; const kMsgNullBody = "Response body is missing (null)."; const kMsgNoContent = "No content"; const kMsgUnknowContentType = "Unknown Response Content-Type"; +// Workspace Selector +const kMsgSelectWorkspace = "Create your workspace"; diff --git a/lib/main.dart b/lib/main.dart index 6c70bdf89..b5fa019fd 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -10,11 +10,14 @@ import 'app.dart'; void main() async { WidgetsFlutterBinding.ensureInitialized(); - final settingsModel = await getSettingsFromSharedPrefs(); - await initApp(settingsModel: settingsModel); + var settingsModel = await getSettingsFromSharedPrefs(); + final initStatus = await initApp(settingsModel: settingsModel); if (kIsDesktop) { await initWindow(settingsModel: settingsModel); } + if (!initStatus) { + settingsModel = settingsModel?.copyWithPath(workspaceFolderPath: null); + } runApp( ProviderScope( @@ -28,13 +31,22 @@ void main() async { ); } -Future initApp({SettingsModel? settingsModel}) async { +Future initApp({SettingsModel? settingsModel}) async { GoogleFonts.config.allowRuntimeFetching = false; - await openBoxes( - kIsDesktop, - settingsModel?.workspaceFolderPath, - ); - await autoClearHistory(settingsModel: settingsModel); + try { + final openBoxesStatus = await openBoxes( + kIsDesktop, + settingsModel?.workspaceFolderPath, + ); + debugPrint("openBoxesStatus: $openBoxesStatus"); + if (openBoxesStatus) { + await autoClearHistory(settingsModel: settingsModel); + } + return openBoxesStatus; + } catch (e) { + debugPrint("initApp failed due to $e"); + return false; + } } Future initWindow({ diff --git a/lib/models/settings_model.dart b/lib/models/settings_model.dart index e7e5c3b00..6784f2141 100644 --- a/lib/models/settings_model.dart +++ b/lib/models/settings_model.dart @@ -59,6 +59,24 @@ class SettingsModel { ); } + SettingsModel copyWithPath({ + String? workspaceFolderPath, + }) { + return SettingsModel( + isDark: isDark, + alwaysShowCollectionPaneScrollbar: alwaysShowCollectionPaneScrollbar, + size: size, + defaultUriScheme: defaultUriScheme, + defaultCodeGenLang: defaultCodeGenLang, + offset: offset, + saveResponses: saveResponses, + promptBeforeClosing: promptBeforeClosing, + activeEnvironmentId: activeEnvironmentId, + historyRetentionPeriod: historyRetentionPeriod, + workspaceFolderPath: workspaceFolderPath, + ); + } + factory SettingsModel.fromJson(Map data) { final isDark = data["isDark"] as bool?; final alwaysShowCollectionPaneScrollbar = diff --git a/lib/services/hive_services.dart b/lib/services/hive_services.dart index 3caa3c283..fab185403 100644 --- a/lib/services/hive_services.dart +++ b/lib/services/hive_services.dart @@ -10,19 +10,29 @@ const String kHistoryMetaBox = "apidash-history-meta"; const String kHistoryBoxIds = "historyIds"; const String kHistoryLazyBox = "apidash-history-lazy"; -Future openBoxes( +Future openBoxes( bool isDesktop, String? workspaceFolderPath, ) async { - if (isDesktop) { - Hive.init(workspaceFolderPath); - } else { - await Hive.initFlutter(); + try { + if (isDesktop) { + if (workspaceFolderPath != null) { + Hive.init(workspaceFolderPath); + } else { + return false; + } + } else { + await Hive.initFlutter(); + } + + await Hive.openBox(kDataBox); + await Hive.openBox(kEnvironmentBox); + await Hive.openBox(kHistoryMetaBox); + await Hive.openLazyBox(kHistoryLazyBox); + return true; + } catch (e) { + return false; } - await Hive.openBox(kDataBox); - await Hive.openBox(kEnvironmentBox); - await Hive.openBox(kHistoryMetaBox); - await Hive.openLazyBox(kHistoryLazyBox); } final hiveHandler = HiveHandler(); diff --git a/lib/widgets/field_outlined.dart b/lib/widgets/field_outlined.dart new file mode 100644 index 000000000..0c9815433 --- /dev/null +++ b/lib/widgets/field_outlined.dart @@ -0,0 +1,54 @@ +import 'package:flutter/material.dart'; +import 'package:apidash/consts.dart'; + +class OutlinedField extends StatelessWidget { + const OutlinedField({ + super.key, + this.keyId, + this.initialValue, + this.hintText, + this.onChanged, + this.colorScheme, + }); + + final String? keyId; + final String? initialValue; + final String? hintText; + final void Function(String)? onChanged; + final ColorScheme? colorScheme; + + @override + Widget build(BuildContext context) { + var clrScheme = colorScheme ?? Theme.of(context).colorScheme; + return TextFormField( + key: keyId != null ? Key(keyId!) : null, + initialValue: initialValue, + style: kCodeStyle.copyWith( + color: clrScheme.onSurface, + ), + decoration: InputDecoration( + hintStyle: kCodeStyle.copyWith( + color: clrScheme.outline.withOpacity( + kHintOpacity, + ), + ), + hintText: hintText, + contentPadding: kP10, + focusedBorder: OutlineInputBorder( + borderSide: BorderSide( + color: clrScheme.primary.withOpacity( + kHintOpacity, + ), + ), + ), + enabledBorder: OutlineInputBorder( + borderSide: BorderSide( + color: clrScheme.surfaceContainerHighest, + ), + ), + isDense: true, + ), + onChanged: onChanged, + ); + } +} diff --git a/lib/widgets/widgets.dart b/lib/widgets/widgets.dart index b7a8732e5..ebf56e8ea 100644 --- a/lib/widgets/widgets.dart +++ b/lib/widgets/widgets.dart @@ -31,6 +31,7 @@ export 'field_cell_obscurable.dart'; export 'field_cell.dart'; export 'field_header.dart'; export 'field_json_search.dart'; +export 'field_outlined.dart'; export 'field_raw.dart'; export 'field_read_only.dart'; export 'field_url.dart'; diff --git a/lib/widgets/workspace_selector.dart b/lib/widgets/workspace_selector.dart index 1ff7ddd52..2d41b3119 100644 --- a/lib/widgets/workspace_selector.dart +++ b/lib/widgets/workspace_selector.dart @@ -1,62 +1,133 @@ -import 'package:file_selector/file_selector.dart'; -import 'package:apidash/services/hive_services.dart'; +import 'package:apidash/consts.dart'; import 'package:flutter/material.dart'; +import 'package:file_selector/file_selector.dart'; +import 'package:flutter_hooks/flutter_hooks.dart'; +import 'package:path/path.dart' as p; +import 'field_outlined.dart'; -class WorkspaceSelector extends StatefulWidget { - final Future Function(String)? onSelect; +class WorkspaceSelector extends HookWidget { const WorkspaceSelector({ super.key, - required this.onSelect, + required this.onContinue, + this.onCancel, }); - @override - WorkspaceSelectorState createState() => WorkspaceSelectorState(); -} - -class WorkspaceSelectorState extends State { - void selectFolder() async { - String? selectedDirectory = await getDirectoryPath(); - if (selectedDirectory != null) { - widget.onSelect?.call(selectedDirectory); - } - } + final Future Function(String)? onContinue; + final Future Function()? onCancel; @override Widget build(BuildContext context) { - const circularLoader = MaterialApp( - home: Scaffold( - body: Center( - child: CircularProgressIndicator(), + var selectedDirectory = useState(null); + var workspaceName = useState(null); + return Scaffold( + body: Center( + child: SizedBox( + width: 400, + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + const Text( + kMsgSelectWorkspace, + style: kTextStyleButton, + ), + kVSpacer20, + Row( + children: [ + Text( + "CHOOSE DIRECTORY", + style: kCodeStyle.copyWith( + fontSize: 12, + ), + ), + ], + ), + kVSpacer5, + Row( + children: [ + Expanded( + child: Container( + decoration: BoxDecoration( + border: Border.all( + width: 1, + color: Theme.of(context).colorScheme.primaryContainer, + ), + borderRadius: kBorderRadius6, + ), + padding: kP4, + child: Text( + style: kTextStyleButtonSmall, + selectedDirectory.value ?? "", + maxLines: 4, + overflow: TextOverflow.ellipsis, + ), + ), + ), + kHSpacer10, + FilledButton.tonalIcon( + onPressed: () async { + selectedDirectory.value = await getDirectoryPath(); + }, + label: const Text(kLabelSelect), + icon: const Icon(Icons.folder_rounded), + ), + ], + ), + kVSpacer10, + Row( + children: [ + Text( + "WORKSPACE NAME [OPTIONAL]\n(FOLDER WILL BE CREATED IN THE SELECTED DIRECTORY)", + style: kCodeStyle.copyWith( + fontSize: 12, + ), + ), + ], + ), + kVSpacer5, + OutlinedField( + keyId: "workspace-name", + onChanged: (value) { + workspaceName.value = value.trim(); + }, + colorScheme: Theme.of(context).colorScheme, + ), + kVSpacer40, + Row( + mainAxisSize: MainAxisSize.min, + children: [ + FilledButton( + onPressed: selectedDirectory.value == null + ? null + : () async { + String finalPath = selectedDirectory.value!; + if (workspaceName.value != null && + workspaceName.value!.trim().isNotEmpty) { + finalPath = + p.join(finalPath, workspaceName.value); + } + await onContinue?.call(finalPath); + }, + child: const Text(kLabelContinue), + ), + kHSpacer10, + FilledButton( + onPressed: onCancel, + style: FilledButton.styleFrom( + backgroundColor: + Theme.of(context).brightness == Brightness.dark + ? kColorDarkDanger + : kColorLightDanger, + surfaceTintColor: kColorRed, + foregroundColor: + Theme.of(context).colorScheme.onPrimary), + child: const Text(kLabelCancel), + ) + ], + ) + ], + ), ), ), ); - - 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; - }, - ); - }, - ); } } From 9a056c37024cc2f2b7588d8f0923cfffb853fbea Mon Sep 17 00:00:00 2001 From: Ashita Prasad Date: Mon, 9 Sep 2024 04:11:07 +0530 Subject: [PATCH 3/6] Update settings_model_test.dart --- test/models/settings_model_test.dart | 20 ++++++++++++++++++-- 1 file changed, 18 insertions(+), 2 deletions(-) diff --git a/test/models/settings_model_test.dart b/test/models/settings_model_test.dart index 6df013359..0a1a33a99 100644 --- a/test/models/settings_model_test.dart +++ b/test/models/settings_model_test.dart @@ -15,6 +15,7 @@ void main() { promptBeforeClosing: true, activeEnvironmentId: null, historyRetentionPeriod: HistoryRetentionPeriod.oneWeek, + workspaceFolderPath: null, ); test('Testing toJson()', () { @@ -31,6 +32,7 @@ void main() { "promptBeforeClosing": true, "activeEnvironmentId": null, "historyRetentionPeriod": "oneWeek", + "workspaceFolderPath": null, }; expect(sm.toJson(), expectedResult); }); @@ -49,6 +51,7 @@ void main() { "promptBeforeClosing": true, "activeEnvironmentId": null, "historyRetentionPeriod": "oneWeek", + "workspaceFolderPath": null, }; expect(SettingsModel.fromJson(input), sm); }); @@ -75,8 +78,21 @@ void main() { }); test('Testing toString()', () { - const expectedResult = - "{isDark: false, alwaysShowCollectionPaneScrollbar: true, width: 300.0, height: 200.0, dx: 100.0, dy: 150.0, defaultUriScheme: http, defaultCodeGenLang: curl, saveResponses: true, promptBeforeClosing: true, activeEnvironmentId: null, historyRetentionPeriod: oneWeek}"; + const expectedResult = '''{ + "isDark": false, + "alwaysShowCollectionPaneScrollbar": true, + "width": 300.0, + "height": 200.0, + "dx": 100.0, + "dy": 150.0, + "defaultUriScheme": "http", + "defaultCodeGenLang": "curl", + "saveResponses": true, + "promptBeforeClosing": true, + "activeEnvironmentId": null, + "historyRetentionPeriod": "oneWeek", + "workspaceFolderPath": null +}'''; expect(sm.toString(), expectedResult); }); From 83cdc130e54673dfcaae2e3ba47513f3fd5301af Mon Sep 17 00:00:00 2001 From: Ashita Prasad Date: Mon, 9 Sep 2024 04:46:46 +0530 Subject: [PATCH 4/6] Update integration test --- integration_test/test_helper.dart | 17 +++++++++++++---- lib/main.dart | 12 +++++++++--- lib/services/hive_services.dart | 4 ++-- 3 files changed, 24 insertions(+), 9 deletions(-) diff --git a/integration_test/test_helper.dart b/integration_test/test_helper.dart index 70cafa9da..4cb151dcf 100644 --- a/integration_test/test_helper.dart +++ b/integration_test/test_helper.dart @@ -1,3 +1,5 @@ +import 'package:apidash/models/settings_model.dart'; +import 'package:apidash/providers/providers.dart'; import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_test/flutter_test.dart'; @@ -34,17 +36,24 @@ class ApidashTestHelper { final binding = IntegrationTestWidgetsFlutterBinding.ensureInitialized(); binding.framePolicy = LiveTestWidgetsFlutterBindingFramePolicy.fullyLive; - await app.initApp(); + await app.initApp(false); await app.initWindow(sz: size); return binding; } static Future loadApp(WidgetTester tester) async { - await app.initApp(); + await app.initApp(false); await tester.pumpWidget( - const ProviderScope( - child: DashApp(), + ProviderScope( + overrides: [ + settingsProvider.overrideWith( + (ref) => ThemeStateNotifier( + settingsModel: const SettingsModel() + .copyWithPath(workspaceFolderPath: "test")), + ) + ], + child: const DashApp(), ), ); } diff --git a/lib/main.dart b/lib/main.dart index b5fa019fd..51fbd8409 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -11,7 +11,10 @@ import 'app.dart'; void main() async { WidgetsFlutterBinding.ensureInitialized(); var settingsModel = await getSettingsFromSharedPrefs(); - final initStatus = await initApp(settingsModel: settingsModel); + final initStatus = await initApp( + kIsDesktop, + settingsModel: settingsModel, + ); if (kIsDesktop) { await initWindow(settingsModel: settingsModel); } @@ -31,11 +34,14 @@ void main() async { ); } -Future initApp({SettingsModel? settingsModel}) async { +Future initApp( + bool initializeUsingPath, { + SettingsModel? settingsModel, +}) async { GoogleFonts.config.allowRuntimeFetching = false; try { final openBoxesStatus = await openBoxes( - kIsDesktop, + initializeUsingPath, settingsModel?.workspaceFolderPath, ); debugPrint("openBoxesStatus: $openBoxesStatus"); diff --git a/lib/services/hive_services.dart b/lib/services/hive_services.dart index fab185403..cc7f89603 100644 --- a/lib/services/hive_services.dart +++ b/lib/services/hive_services.dart @@ -11,11 +11,11 @@ const String kHistoryBoxIds = "historyIds"; const String kHistoryLazyBox = "apidash-history-lazy"; Future openBoxes( - bool isDesktop, + bool initializeUsingPath, String? workspaceFolderPath, ) async { try { - if (isDesktop) { + if (initializeUsingPath) { if (workspaceFolderPath != null) { Hive.init(workspaceFolderPath); } else { From 3fa51a39c40d2d17237df9fb9e9dbaf25b5b2ef0 Mon Sep 17 00:00:00 2001 From: Ashita Prasad Date: Mon, 9 Sep 2024 05:13:24 +0530 Subject: [PATCH 5/6] Update main.dart --- lib/main.dart | 2 ++ 1 file changed, 2 insertions(+) diff --git a/lib/main.dart b/lib/main.dart index 51fbd8409..e1e70ec6a 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -40,6 +40,8 @@ Future initApp( }) async { GoogleFonts.config.allowRuntimeFetching = false; try { + debugPrint("initializeUsingPath: $initializeUsingPath"); + debugPrint("workspaceFolderPath: ${settingsModel?.workspaceFolderPath}"); final openBoxesStatus = await openBoxes( initializeUsingPath, settingsModel?.workspaceFolderPath, From 8820d26885dc1a5b93123a72ea4bdf6b79772f54 Mon Sep 17 00:00:00 2001 From: Ashita Prasad Date: Mon, 9 Sep 2024 05:13:42 +0530 Subject: [PATCH 6/6] Add clearSharedPrefs() --- integration_test/test_helper.dart | 2 ++ lib/screens/settings_page.dart | 2 ++ lib/services/shared_preferences_services.dart | 5 +++++ 3 files changed, 9 insertions(+) diff --git a/integration_test/test_helper.dart b/integration_test/test_helper.dart index 4cb151dcf..f05a7cf7a 100644 --- a/integration_test/test_helper.dart +++ b/integration_test/test_helper.dart @@ -1,5 +1,6 @@ import 'package:apidash/models/settings_model.dart'; import 'package:apidash/providers/providers.dart'; +import 'package:apidash/services/services.dart'; import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_test/flutter_test.dart'; @@ -132,6 +133,7 @@ void apidashWidgetTest( size: width != null ? Size(width, kMinWindowSize.height) : null); await ApidashTestHelper.loadApp(widgetTester); await test(widgetTester, ApidashTestHelper(widgetTester)); + await clearSharedPrefs(); }, semanticsEnabled: false, ); diff --git a/lib/screens/settings_page.dart b/lib/screens/settings_page.dart index 8e9c84864..933b54b98 100644 --- a/lib/screens/settings_page.dart +++ b/lib/screens/settings_page.dart @@ -1,6 +1,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import '../providers/providers.dart'; +import '../services/services.dart'; import '../widgets/widgets.dart'; import '../common/utils.dart'; import '../consts.dart'; @@ -206,6 +207,7 @@ class SettingsPage extends ConsumerWidget { TextButton( onPressed: () async { Navigator.pop(context, 'Yes'); + await clearSharedPrefs(); await ref .read(collectionStateNotifierProvider .notifier) diff --git a/lib/services/shared_preferences_services.dart b/lib/services/shared_preferences_services.dart index af1bbd71e..89d4b590d 100644 --- a/lib/services/shared_preferences_services.dart +++ b/lib/services/shared_preferences_services.dart @@ -21,3 +21,8 @@ Future setSettingsToSharedPrefs(SettingsModel settingsModel) async { final prefs = await SharedPreferences.getInstance(); await prefs.setString(kSharedPrefSettingsKey, settingsModel.toString()); } + +Future clearSharedPrefs() async { + final prefs = await SharedPreferences.getInstance(); + await prefs.remove(kSharedPrefSettingsKey); +}