mirror of
https://github.com/foss42/apidash.git
synced 2025-05-30 13:27:09 +08:00
Workspace selector feature
This commit is contained in:
53
lib/app.dart
53
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,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
@ -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";
|
||||
|
@ -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<void> initApp({SettingsModel? settingsModel}) async {
|
||||
Future<bool> 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<void> initWindow({
|
||||
|
@ -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<dynamic, dynamic> data) {
|
||||
final isDark = data["isDark"] as bool?;
|
||||
final alwaysShowCollectionPaneScrollbar =
|
||||
|
@ -10,19 +10,29 @@ const String kHistoryMetaBox = "apidash-history-meta";
|
||||
const String kHistoryBoxIds = "historyIds";
|
||||
const String kHistoryLazyBox = "apidash-history-lazy";
|
||||
|
||||
Future<void> openBoxes(
|
||||
Future<bool> 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();
|
||||
|
54
lib/widgets/field_outlined.dart
Normal file
54
lib/widgets/field_outlined.dart
Normal file
@ -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,
|
||||
);
|
||||
}
|
||||
}
|
@ -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';
|
||||
|
@ -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<void> 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<WorkspaceSelector> {
|
||||
void selectFolder() async {
|
||||
String? selectedDirectory = await getDirectoryPath();
|
||||
if (selectedDirectory != null) {
|
||||
widget.onSelect?.call(selectedDirectory);
|
||||
}
|
||||
}
|
||||
final Future<void> Function(String)? onContinue;
|
||||
final Future<void> Function()? onCancel;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
const circularLoader = MaterialApp(
|
||||
home: Scaffold(
|
||||
body: Center(
|
||||
child: CircularProgressIndicator(),
|
||||
var selectedDirectory = useState<String?>(null);
|
||||
var workspaceName = useState<String?>(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<String?>(
|
||||
future: getHiveSaveFolder(),
|
||||
builder: (BuildContext context, AsyncSnapshot<String?> 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<void>(
|
||||
future: openHiveBoxes(snapshot.data!),
|
||||
builder: (BuildContext context, AsyncSnapshot<void> snapshot) {
|
||||
// if loading show circularLoader
|
||||
if (snapshot.connectionState != ConnectionState.done) {
|
||||
return circularLoader;
|
||||
}
|
||||
// Display widget
|
||||
return widget.child;
|
||||
},
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
Reference in New Issue
Block a user