Refactor terminal and remove duplicate code.

This commit is contained in:
Ankit Mahato
2025-09-28 13:55:32 +05:30
parent dc7aa246d7
commit 3545c75cd5
21 changed files with 91 additions and 253 deletions

View File

@@ -3,7 +3,6 @@ import 'package:apidash_design_system/apidash_design_system.dart';
import 'package:apidash_core/apidash_core.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
// Terminal enums moved here from models to avoid circular imports and simplify usage
const kDiscordUrl = "https://bit.ly/heyfoss";
const kGitUrl = "https://github.com/foss42/apidash";
@@ -86,15 +85,6 @@ enum HistoryRetentionPeriod {
final IconData icon;
}
/// Source category of a terminal entry
enum TerminalSource { network, js, system }
/// Severity level of a terminal entry
enum TerminalLevel { debug, info, warn, error }
/// Phase of a network log lifecycle
enum NetworkPhase { started, progress, completed, failed }
enum ItemMenuOption {
edit("Rename"),
delete("Delete"),
@@ -521,3 +511,6 @@ const kMsgClearHistorySuccess = 'History cleared successfully';
const kMsgClearHistoryError = 'Error clearing history';
const kMsgShareError = "Unable to share";
const kLabelGenerateUI = "Generate UI";
// Terminal Page
const kMsgNoLogs = 'No logs yet';
const kMsgSendToView = 'Send a request to view its details in the console.';

View File

@@ -2,4 +2,3 @@ export 'history_meta_model.dart';
export 'history_request_model.dart';
export 'request_model.dart';
export 'settings_model.dart';
export 'terminal/models.dart';

View File

@@ -1,164 +0,0 @@
import 'package:apidash_core/apidash_core.dart' show APIType, HTTPVerb;
/// Source category of a terminal entry
enum TerminalSource { network, js, system }
/// Severity level of a terminal entry
enum TerminalLevel { debug, info, warn, error }
/// Phase of a network log lifecycle
enum NetworkPhase { started, progress, completed, failed }
class BodyChunk {
BodyChunk({required this.ts, required this.text, required this.sizeBytes});
final DateTime ts;
final String text; // preview text (could be partial)
final int sizeBytes;
}
class NetworkLogData {
NetworkLogData({
required this.phase,
required this.apiType,
required this.method,
required this.url,
this.requestHeaders,
this.requestBodyPreview,
this.responseStatus,
this.responseHeaders,
this.responseBodyPreview,
this.duration,
this.isStreaming = false,
this.sentAt,
this.completedAt,
this.errorMessage,
List<BodyChunk>? chunks,
}) : chunks = chunks ?? <BodyChunk>[];
final NetworkPhase phase;
final APIType apiType;
final HTTPVerb method;
final String url;
final Map<String, String>? requestHeaders;
final String? requestBodyPreview;
final int? responseStatus;
final Map<String, String>? responseHeaders;
final String? responseBodyPreview;
final Duration? duration;
final bool isStreaming;
final DateTime? sentAt;
final DateTime? completedAt;
final String? errorMessage;
final List<BodyChunk> chunks;
NetworkLogData copyWith({
NetworkPhase? phase,
APIType? apiType,
HTTPVerb? method,
String? url,
Map<String, String>? requestHeaders,
String? requestBodyPreview,
int? responseStatus,
Map<String, String>? responseHeaders,
String? responseBodyPreview,
Duration? duration,
bool? isStreaming,
DateTime? sentAt,
DateTime? completedAt,
String? errorMessage,
List<BodyChunk>? chunks,
}) {
return NetworkLogData(
phase: phase ?? this.phase,
apiType: apiType ?? this.apiType,
method: method ?? this.method,
url: url ?? this.url,
requestHeaders: requestHeaders ?? this.requestHeaders,
requestBodyPreview: requestBodyPreview ?? this.requestBodyPreview,
responseStatus: responseStatus ?? this.responseStatus,
responseHeaders: responseHeaders ?? this.responseHeaders,
responseBodyPreview: responseBodyPreview ?? this.responseBodyPreview,
duration: duration ?? this.duration,
isStreaming: isStreaming ?? this.isStreaming,
sentAt: sentAt ?? this.sentAt,
completedAt: completedAt ?? this.completedAt,
errorMessage: errorMessage ?? this.errorMessage,
chunks: chunks ?? this.chunks,
);
}
}
class JsLogData {
JsLogData({
required this.level,
required this.args,
this.stack,
this.context,
this.contextRequestId,
});
final String level; // log | warn | error | fatal
final List<String> args;
final String? stack;
final String? context; // preRequest | postResponse | global
final String? contextRequestId;
}
class SystemLogData {
SystemLogData({required this.category, required this.message, this.stack});
final String category; // ui | provider | io | storage | unknown
final String message;
final String? stack;
}
class TerminalEntry {
TerminalEntry({
required this.id,
required this.ts,
required this.source,
required this.level,
this.requestId,
this.correlationId,
this.tags = const <String>[],
this.network,
this.js,
this.system,
});
final String id;
final DateTime ts;
final TerminalSource source;
final TerminalLevel level;
final String? requestId; // App request id for correlation
final String? correlationId; // Additional correlation if any
final List<String> tags;
final NetworkLogData? network;
final JsLogData? js;
final SystemLogData? system;
TerminalEntry copyWith({
DateTime? ts,
TerminalSource? source,
TerminalLevel? level,
String? requestId,
String? correlationId,
List<String>? tags,
NetworkLogData? network,
JsLogData? js,
SystemLogData? system,
}) {
return TerminalEntry(
id: id,
ts: ts ?? this.ts,
source: source ?? this.source,
level: level ?? this.level,
requestId: requestId ?? this.requestId,
correlationId: correlationId ?? this.correlationId,
tags: tags ?? this.tags,
network: network ?? this.network,
js: js ?? this.js,
system: system ?? this.system,
);
}
}

View File

@@ -3,6 +3,7 @@ import 'package:apidash_core/apidash_core.dart';
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:apidash/consts.dart';
import 'package:apidash/terminal/terminal.dart';
import 'providers.dart';
import '../models/models.dart';
import '../services/services.dart';

View File

@@ -1,8 +1,7 @@
import 'package:apidash/terminal/terminal.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:uuid/uuid.dart';
import 'package:apidash_core/apidash_core.dart';
import '../models/terminal/models.dart';
import '../consts.dart';
final terminalStateProvider =
StateNotifierProvider<TerminalController, TerminalState>((ref) {

View File

@@ -1,15 +1,10 @@
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import '../../models/terminal/models.dart';
import '../../consts.dart';
import '../../providers/terminal_providers.dart';
import '../../providers/collection_providers.dart';
import '../../widgets/button_copy.dart';
import '../../widgets/field_search.dart';
import '../../widgets/terminal_tiles.dart';
import '../../widgets/empty_message.dart';
import 'package:apidash_design_system/apidash_design_system.dart';
import '../../widgets/terminal_level_filter_menu.dart';
import 'package:apidash/terminal/terminal.dart';
import 'package:apidash/widgets/widgets.dart';
import 'package:apidash/providers/providers.dart';
import 'package:apidash/consts.dart';
class TerminalPage extends ConsumerStatefulWidget {
const TerminalPage({super.key});
@@ -109,10 +104,9 @@ class _TerminalPageState extends ConsumerState<TerminalPage> {
Expanded(
child: filtered.isEmpty
? const Center(
child: EmptyMessage(
title: 'No logs yet',
subtitle:
'Send a request to see details here in the console',
child: SimpleText(
title: kMsgNoLogs,
subtitle: kMsgSendToView,
),
)
: ListView.separated(

8
lib/terminal/enums.dart Normal file
View File

@@ -0,0 +1,8 @@
/// Source category of a terminal entry
enum TerminalSource { network, js, system }
/// Severity level of a terminal entry
enum TerminalLevel { debug, info, warn, error }
/// Phase of a network log lifecycle
enum NetworkPhase { started, progress, completed, failed }

View File

@@ -0,0 +1,7 @@
class BodyChunk {
BodyChunk({required this.ts, required this.text, required this.sizeBytes});
final DateTime ts;
final String text; // preview text (could be partial)
final int sizeBytes;
}

View File

@@ -1,4 +1,5 @@
export 'network_log_data.dart';
export 'body_chunk.dart';
export 'js_log_data.dart';
export 'network_log_data.dart';
export 'system_log_data.dart';
export 'terminal_entry.dart';

View File

@@ -1,5 +1,6 @@
import 'package:apidash/consts.dart';
import 'package:apidash_core/apidash_core.dart' show APIType, HTTPVerb;
import '../enums.dart';
import 'body_chunk.dart';
class NetworkLogData {
NetworkLogData({
@@ -72,11 +73,3 @@ class NetworkLogData {
);
}
}
class BodyChunk {
BodyChunk({required this.ts, required this.text, required this.sizeBytes});
final DateTime ts;
final String text; // preview text (could be partial)
final int sizeBytes;
}

View File

@@ -1,4 +1,4 @@
import 'package:apidash/consts.dart';
import '../enums.dart';
import 'network_log_data.dart';
import 'js_log_data.dart';
import 'system_log_data.dart';

View File

@@ -0,0 +1,3 @@
export 'models/models.dart';
export 'widgets/widgets.dart';
export 'enums.dart';

View File

@@ -1,5 +1,5 @@
import 'package:flutter/material.dart';
import '../consts.dart';
import '../enums.dart';
class TerminalLevelFilterMenu extends StatelessWidget {
const TerminalLevelFilterMenu({

View File

@@ -1,11 +1,10 @@
import 'package:flutter/material.dart';
import 'package:apidash_design_system/apidash_design_system.dart';
import 'package:apidash_core/apidash_core.dart';
import '../consts.dart';
import '../models/terminal/models.dart';
import '../utils/ui_utils.dart';
import 'expandable_section.dart';
import 'highlight_text.dart';
import 'package:apidash/utils/utils.dart';
import 'package:apidash/widgets/widgets.dart';
import '../enums.dart';
import '../models/models.dart';
class SystemLogTile extends StatelessWidget {
const SystemLogTile({

View File

@@ -0,0 +1,2 @@
export 'terminal_level_filter_menu.dart';
export 'terminal_tiles.dart';

View File

@@ -1,44 +0,0 @@
import 'package:flutter/material.dart';
class EmptyMessage extends StatelessWidget {
const EmptyMessage({
super.key,
this.title = 'No logs yet',
this.subtitle = 'Send a request to view its details in the console.',
this.icon,
});
final String title;
final String subtitle;
final IconData? icon;
@override
Widget build(BuildContext context) {
final theme = Theme.of(context);
return Center(
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
if (icon != null) ...[
Icon(icon, size: 36, color: theme.colorScheme.outline),
const SizedBox(height: 8),
],
Text(
title,
style: theme.textTheme.titleMedium?.copyWith(
fontWeight: FontWeight.w600,
),
),
const SizedBox(height: 6),
Text(
subtitle,
style: theme.textTheme.bodyMedium?.copyWith(
color: theme.colorScheme.outline,
),
textAlign: TextAlign.center,
),
],
),
);
}
}

View File

@@ -0,0 +1,47 @@
import 'package:flutter/material.dart';
class SimpleText extends StatelessWidget {
const SimpleText({
super.key,
this.title,
this.subtitle,
this.icon,
});
final String? title;
final String? subtitle;
final IconData? icon;
@override
Widget build(BuildContext context) {
final theme = Theme.of(context);
return Center(
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
if (icon != null) ...[
Icon(icon, size: 36, color: theme.colorScheme.outline),
const SizedBox(height: 8),
],
if (title != null) ...[
Text(
title!,
style: theme.textTheme.titleMedium?.copyWith(
fontWeight: FontWeight.w600,
),
),
const SizedBox(height: 6),
],
if (subtitle != null)
Text(
subtitle!,
style: theme.textTheme.bodyMedium?.copyWith(
color: theme.colorScheme.outline,
),
textAlign: TextAlign.center,
),
],
),
);
}
}

View File

@@ -35,8 +35,6 @@ export 'field_read_only.dart';
export 'field_text_bounded.dart';
export 'field_search.dart';
export 'expandable_section.dart';
export 'empty_message.dart';
export 'terminal_tiles.dart';
export 'field_url.dart';
export 'intro_message.dart';
export 'markdown.dart';
@@ -71,6 +69,8 @@ export 'table_map.dart';
export 'table_request_form.dart';
export 'table_request.dart';
export 'tab_label.dart';
export 'text_highlighted_selectable.dart';
export 'text_simple.dart';
export 'texts.dart';
export 'uint8_audio_player.dart';
export 'widget_not_sent.dart';