wip: history request groups

This commit is contained in:
DenserMeerkat
2024-07-14 23:16:29 +05:30
parent 78c5fc6a94
commit d5feb0b091
31 changed files with 1284 additions and 71 deletions

View File

@ -68,6 +68,7 @@ const kFormDataButtonLabelTextStyle = TextStyle(
const kTextStylePopupMenuItem = TextStyle(fontSize: 16);
const kBorderRadius4 = BorderRadius.all(Radius.circular(4));
const kBorderRadius6 = BorderRadius.all(Radius.circular(6));
const kBorderRadius8 = BorderRadius.all(Radius.circular(8));
final kBorderRadius10 = BorderRadius.circular(10);
const kBorderRadius12 = BorderRadius.all(Radius.circular(12));
@ -88,7 +89,7 @@ const kPv8 = EdgeInsets.symmetric(vertical: 8);
const kPv6 = EdgeInsets.symmetric(vertical: 6);
const kPv2 = EdgeInsets.symmetric(vertical: 2);
const kPh2 = EdgeInsets.symmetric(horizontal: 2);
const kPt24o8 = EdgeInsets.only(top: 24, left: 8.0, right: 8.0, bottom: 8.0);
const kPt28o8 = EdgeInsets.only(top: 28, left: 8.0, right: 8.0, bottom: 8.0);
const kPt5o10 =
EdgeInsets.only(left: 10.0, right: 10.0, top: 5.0, bottom: 10.0);
const kPh4 = EdgeInsets.symmetric(horizontal: 4);

View File

@ -0,0 +1,21 @@
import 'package:apidash/consts.dart';
import 'package:freezed_annotation/freezed_annotation.dart';
part 'history_meta_model.freezed.dart';
part 'history_meta_model.g.dart';
@freezed
class HistoryMetaModel with _$HistoryMetaModel {
const factory HistoryMetaModel({
required String historyId,
@Default("") String name,
required String url,
required HTTPVerb method,
required int responseStatus,
required DateTime timeStamp,
}) = _HistoryMetaModel;
factory HistoryMetaModel.fromJson(Map<String, Object?> json) =>
_$HistoryMetaModelFromJson(json);
}

View File

@ -0,0 +1,261 @@
// coverage:ignore-file
// GENERATED CODE - DO NOT MODIFY BY HAND
// ignore_for_file: type=lint
// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark
part of 'history_meta_model.dart';
// **************************************************************************
// FreezedGenerator
// **************************************************************************
T _$identity<T>(T value) => value;
final _privateConstructorUsedError = UnsupportedError(
'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#adding-getters-and-methods-to-our-models');
HistoryMetaModel _$HistoryMetaModelFromJson(Map<String, dynamic> json) {
return _HistoryMetaModel.fromJson(json);
}
/// @nodoc
mixin _$HistoryMetaModel {
String get historyId => throw _privateConstructorUsedError;
String get name => throw _privateConstructorUsedError;
String get url => throw _privateConstructorUsedError;
HTTPVerb get method => throw _privateConstructorUsedError;
int get responseStatus => throw _privateConstructorUsedError;
DateTime get timeStamp => throw _privateConstructorUsedError;
Map<String, dynamic> toJson() => throw _privateConstructorUsedError;
@JsonKey(ignore: true)
$HistoryMetaModelCopyWith<HistoryMetaModel> get copyWith =>
throw _privateConstructorUsedError;
}
/// @nodoc
abstract class $HistoryMetaModelCopyWith<$Res> {
factory $HistoryMetaModelCopyWith(
HistoryMetaModel value, $Res Function(HistoryMetaModel) then) =
_$HistoryMetaModelCopyWithImpl<$Res, HistoryMetaModel>;
@useResult
$Res call(
{String historyId,
String name,
String url,
HTTPVerb method,
int responseStatus,
DateTime timeStamp});
}
/// @nodoc
class _$HistoryMetaModelCopyWithImpl<$Res, $Val extends HistoryMetaModel>
implements $HistoryMetaModelCopyWith<$Res> {
_$HistoryMetaModelCopyWithImpl(this._value, this._then);
// ignore: unused_field
final $Val _value;
// ignore: unused_field
final $Res Function($Val) _then;
@pragma('vm:prefer-inline')
@override
$Res call({
Object? historyId = null,
Object? name = null,
Object? url = null,
Object? method = null,
Object? responseStatus = null,
Object? timeStamp = null,
}) {
return _then(_value.copyWith(
historyId: null == historyId
? _value.historyId
: historyId // ignore: cast_nullable_to_non_nullable
as String,
name: null == name
? _value.name
: name // ignore: cast_nullable_to_non_nullable
as String,
url: null == url
? _value.url
: url // ignore: cast_nullable_to_non_nullable
as String,
method: null == method
? _value.method
: method // ignore: cast_nullable_to_non_nullable
as HTTPVerb,
responseStatus: null == responseStatus
? _value.responseStatus
: responseStatus // ignore: cast_nullable_to_non_nullable
as int,
timeStamp: null == timeStamp
? _value.timeStamp
: timeStamp // ignore: cast_nullable_to_non_nullable
as DateTime,
) as $Val);
}
}
/// @nodoc
abstract class _$$HistoryMetaModelImplCopyWith<$Res>
implements $HistoryMetaModelCopyWith<$Res> {
factory _$$HistoryMetaModelImplCopyWith(_$HistoryMetaModelImpl value,
$Res Function(_$HistoryMetaModelImpl) then) =
__$$HistoryMetaModelImplCopyWithImpl<$Res>;
@override
@useResult
$Res call(
{String historyId,
String name,
String url,
HTTPVerb method,
int responseStatus,
DateTime timeStamp});
}
/// @nodoc
class __$$HistoryMetaModelImplCopyWithImpl<$Res>
extends _$HistoryMetaModelCopyWithImpl<$Res, _$HistoryMetaModelImpl>
implements _$$HistoryMetaModelImplCopyWith<$Res> {
__$$HistoryMetaModelImplCopyWithImpl(_$HistoryMetaModelImpl _value,
$Res Function(_$HistoryMetaModelImpl) _then)
: super(_value, _then);
@pragma('vm:prefer-inline')
@override
$Res call({
Object? historyId = null,
Object? name = null,
Object? url = null,
Object? method = null,
Object? responseStatus = null,
Object? timeStamp = null,
}) {
return _then(_$HistoryMetaModelImpl(
historyId: null == historyId
? _value.historyId
: historyId // ignore: cast_nullable_to_non_nullable
as String,
name: null == name
? _value.name
: name // ignore: cast_nullable_to_non_nullable
as String,
url: null == url
? _value.url
: url // ignore: cast_nullable_to_non_nullable
as String,
method: null == method
? _value.method
: method // ignore: cast_nullable_to_non_nullable
as HTTPVerb,
responseStatus: null == responseStatus
? _value.responseStatus
: responseStatus // ignore: cast_nullable_to_non_nullable
as int,
timeStamp: null == timeStamp
? _value.timeStamp
: timeStamp // ignore: cast_nullable_to_non_nullable
as DateTime,
));
}
}
/// @nodoc
@JsonSerializable()
class _$HistoryMetaModelImpl implements _HistoryMetaModel {
const _$HistoryMetaModelImpl(
{required this.historyId,
this.name = "",
required this.url,
required this.method,
required this.responseStatus,
required this.timeStamp});
factory _$HistoryMetaModelImpl.fromJson(Map<String, dynamic> json) =>
_$$HistoryMetaModelImplFromJson(json);
@override
final String historyId;
@override
@JsonKey()
final String name;
@override
final String url;
@override
final HTTPVerb method;
@override
final int responseStatus;
@override
final DateTime timeStamp;
@override
String toString() {
return 'HistoryMetaModel(historyId: $historyId, name: $name, url: $url, method: $method, responseStatus: $responseStatus, timeStamp: $timeStamp)';
}
@override
bool operator ==(Object other) {
return identical(this, other) ||
(other.runtimeType == runtimeType &&
other is _$HistoryMetaModelImpl &&
(identical(other.historyId, historyId) ||
other.historyId == historyId) &&
(identical(other.name, name) || other.name == name) &&
(identical(other.url, url) || other.url == url) &&
(identical(other.method, method) || other.method == method) &&
(identical(other.responseStatus, responseStatus) ||
other.responseStatus == responseStatus) &&
(identical(other.timeStamp, timeStamp) ||
other.timeStamp == timeStamp));
}
@JsonKey(ignore: true)
@override
int get hashCode => Object.hash(
runtimeType, historyId, name, url, method, responseStatus, timeStamp);
@JsonKey(ignore: true)
@override
@pragma('vm:prefer-inline')
_$$HistoryMetaModelImplCopyWith<_$HistoryMetaModelImpl> get copyWith =>
__$$HistoryMetaModelImplCopyWithImpl<_$HistoryMetaModelImpl>(
this, _$identity);
@override
Map<String, dynamic> toJson() {
return _$$HistoryMetaModelImplToJson(
this,
);
}
}
abstract class _HistoryMetaModel implements HistoryMetaModel {
const factory _HistoryMetaModel(
{required final String historyId,
final String name,
required final String url,
required final HTTPVerb method,
required final int responseStatus,
required final DateTime timeStamp}) = _$HistoryMetaModelImpl;
factory _HistoryMetaModel.fromJson(Map<String, dynamic> json) =
_$HistoryMetaModelImpl.fromJson;
@override
String get historyId;
@override
String get name;
@override
String get url;
@override
HTTPVerb get method;
@override
int get responseStatus;
@override
DateTime get timeStamp;
@override
@JsonKey(ignore: true)
_$$HistoryMetaModelImplCopyWith<_$HistoryMetaModelImpl> get copyWith =>
throw _privateConstructorUsedError;
}

View File

@ -0,0 +1,38 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'history_meta_model.dart';
// **************************************************************************
// JsonSerializableGenerator
// **************************************************************************
_$HistoryMetaModelImpl _$$HistoryMetaModelImplFromJson(
Map<String, dynamic> json) =>
_$HistoryMetaModelImpl(
historyId: json['historyId'] as String,
name: json['name'] as String? ?? "",
url: json['url'] as String,
method: $enumDecode(_$HTTPVerbEnumMap, json['method']),
responseStatus: (json['responseStatus'] as num).toInt(),
timeStamp: DateTime.parse(json['timeStamp'] as String),
);
Map<String, dynamic> _$$HistoryMetaModelImplToJson(
_$HistoryMetaModelImpl instance) =>
<String, dynamic>{
'historyId': instance.historyId,
'name': instance.name,
'url': instance.url,
'method': _$HTTPVerbEnumMap[instance.method]!,
'responseStatus': instance.responseStatus,
'timeStamp': instance.timeStamp.toIso8601String(),
};
const _$HTTPVerbEnumMap = {
HTTPVerb.get: 'get',
HTTPVerb.head: 'head',
HTTPVerb.post: 'post',
HTTPVerb.put: 'put',
HTTPVerb.patch: 'patch',
HTTPVerb.delete: 'delete',
};

View File

@ -0,0 +1,23 @@
import 'package:freezed_annotation/freezed_annotation.dart';
import 'models.dart';
part 'history_request_model.freezed.dart';
part 'history_request_model.g.dart';
@freezed
class HistoryRequestModel with _$HistoryRequestModel {
@JsonSerializable(
explicitToJson: true,
anyMap: true,
)
const factory HistoryRequestModel({
required String historyId,
required HistoryMetaModel metaData,
required HttpRequestModel httpRequestModel,
required HttpResponseModel httpResponseModel,
}) = _HistoryRequestModel;
factory HistoryRequestModel.fromJson(Map<String, Object?> json) =>
_$HistoryRequestModelFromJson(json);
}

View File

@ -0,0 +1,258 @@
// coverage:ignore-file
// GENERATED CODE - DO NOT MODIFY BY HAND
// ignore_for_file: type=lint
// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark
part of 'history_request_model.dart';
// **************************************************************************
// FreezedGenerator
// **************************************************************************
T _$identity<T>(T value) => value;
final _privateConstructorUsedError = UnsupportedError(
'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#adding-getters-and-methods-to-our-models');
HistoryRequestModel _$HistoryRequestModelFromJson(Map<String, dynamic> json) {
return _HistoryRequestModel.fromJson(json);
}
/// @nodoc
mixin _$HistoryRequestModel {
String get historyId => throw _privateConstructorUsedError;
HistoryMetaModel get metaData => throw _privateConstructorUsedError;
HttpRequestModel get httpRequestModel => throw _privateConstructorUsedError;
HttpResponseModel get httpResponseModel => throw _privateConstructorUsedError;
Map<String, dynamic> toJson() => throw _privateConstructorUsedError;
@JsonKey(ignore: true)
$HistoryRequestModelCopyWith<HistoryRequestModel> get copyWith =>
throw _privateConstructorUsedError;
}
/// @nodoc
abstract class $HistoryRequestModelCopyWith<$Res> {
factory $HistoryRequestModelCopyWith(
HistoryRequestModel value, $Res Function(HistoryRequestModel) then) =
_$HistoryRequestModelCopyWithImpl<$Res, HistoryRequestModel>;
@useResult
$Res call(
{String historyId,
HistoryMetaModel metaData,
HttpRequestModel httpRequestModel,
HttpResponseModel httpResponseModel});
$HistoryMetaModelCopyWith<$Res> get metaData;
$HttpRequestModelCopyWith<$Res> get httpRequestModel;
$HttpResponseModelCopyWith<$Res> get httpResponseModel;
}
/// @nodoc
class _$HistoryRequestModelCopyWithImpl<$Res, $Val extends HistoryRequestModel>
implements $HistoryRequestModelCopyWith<$Res> {
_$HistoryRequestModelCopyWithImpl(this._value, this._then);
// ignore: unused_field
final $Val _value;
// ignore: unused_field
final $Res Function($Val) _then;
@pragma('vm:prefer-inline')
@override
$Res call({
Object? historyId = null,
Object? metaData = null,
Object? httpRequestModel = null,
Object? httpResponseModel = null,
}) {
return _then(_value.copyWith(
historyId: null == historyId
? _value.historyId
: historyId // ignore: cast_nullable_to_non_nullable
as String,
metaData: null == metaData
? _value.metaData
: metaData // ignore: cast_nullable_to_non_nullable
as HistoryMetaModel,
httpRequestModel: null == httpRequestModel
? _value.httpRequestModel
: httpRequestModel // ignore: cast_nullable_to_non_nullable
as HttpRequestModel,
httpResponseModel: null == httpResponseModel
? _value.httpResponseModel
: httpResponseModel // ignore: cast_nullable_to_non_nullable
as HttpResponseModel,
) as $Val);
}
@override
@pragma('vm:prefer-inline')
$HistoryMetaModelCopyWith<$Res> get metaData {
return $HistoryMetaModelCopyWith<$Res>(_value.metaData, (value) {
return _then(_value.copyWith(metaData: value) as $Val);
});
}
@override
@pragma('vm:prefer-inline')
$HttpRequestModelCopyWith<$Res> get httpRequestModel {
return $HttpRequestModelCopyWith<$Res>(_value.httpRequestModel, (value) {
return _then(_value.copyWith(httpRequestModel: value) as $Val);
});
}
@override
@pragma('vm:prefer-inline')
$HttpResponseModelCopyWith<$Res> get httpResponseModel {
return $HttpResponseModelCopyWith<$Res>(_value.httpResponseModel, (value) {
return _then(_value.copyWith(httpResponseModel: value) as $Val);
});
}
}
/// @nodoc
abstract class _$$HistoryRequestModelImplCopyWith<$Res>
implements $HistoryRequestModelCopyWith<$Res> {
factory _$$HistoryRequestModelImplCopyWith(_$HistoryRequestModelImpl value,
$Res Function(_$HistoryRequestModelImpl) then) =
__$$HistoryRequestModelImplCopyWithImpl<$Res>;
@override
@useResult
$Res call(
{String historyId,
HistoryMetaModel metaData,
HttpRequestModel httpRequestModel,
HttpResponseModel httpResponseModel});
@override
$HistoryMetaModelCopyWith<$Res> get metaData;
@override
$HttpRequestModelCopyWith<$Res> get httpRequestModel;
@override
$HttpResponseModelCopyWith<$Res> get httpResponseModel;
}
/// @nodoc
class __$$HistoryRequestModelImplCopyWithImpl<$Res>
extends _$HistoryRequestModelCopyWithImpl<$Res, _$HistoryRequestModelImpl>
implements _$$HistoryRequestModelImplCopyWith<$Res> {
__$$HistoryRequestModelImplCopyWithImpl(_$HistoryRequestModelImpl _value,
$Res Function(_$HistoryRequestModelImpl) _then)
: super(_value, _then);
@pragma('vm:prefer-inline')
@override
$Res call({
Object? historyId = null,
Object? metaData = null,
Object? httpRequestModel = null,
Object? httpResponseModel = null,
}) {
return _then(_$HistoryRequestModelImpl(
historyId: null == historyId
? _value.historyId
: historyId // ignore: cast_nullable_to_non_nullable
as String,
metaData: null == metaData
? _value.metaData
: metaData // ignore: cast_nullable_to_non_nullable
as HistoryMetaModel,
httpRequestModel: null == httpRequestModel
? _value.httpRequestModel
: httpRequestModel // ignore: cast_nullable_to_non_nullable
as HttpRequestModel,
httpResponseModel: null == httpResponseModel
? _value.httpResponseModel
: httpResponseModel // ignore: cast_nullable_to_non_nullable
as HttpResponseModel,
));
}
}
/// @nodoc
@JsonSerializable(explicitToJson: true, anyMap: true)
class _$HistoryRequestModelImpl implements _HistoryRequestModel {
const _$HistoryRequestModelImpl(
{required this.historyId,
required this.metaData,
required this.httpRequestModel,
required this.httpResponseModel});
factory _$HistoryRequestModelImpl.fromJson(Map<String, dynamic> json) =>
_$$HistoryRequestModelImplFromJson(json);
@override
final String historyId;
@override
final HistoryMetaModel metaData;
@override
final HttpRequestModel httpRequestModel;
@override
final HttpResponseModel httpResponseModel;
@override
String toString() {
return 'HistoryRequestModel(historyId: $historyId, metaData: $metaData, httpRequestModel: $httpRequestModel, httpResponseModel: $httpResponseModel)';
}
@override
bool operator ==(Object other) {
return identical(this, other) ||
(other.runtimeType == runtimeType &&
other is _$HistoryRequestModelImpl &&
(identical(other.historyId, historyId) ||
other.historyId == historyId) &&
(identical(other.metaData, metaData) ||
other.metaData == metaData) &&
(identical(other.httpRequestModel, httpRequestModel) ||
other.httpRequestModel == httpRequestModel) &&
(identical(other.httpResponseModel, httpResponseModel) ||
other.httpResponseModel == httpResponseModel));
}
@JsonKey(ignore: true)
@override
int get hashCode => Object.hash(
runtimeType, historyId, metaData, httpRequestModel, httpResponseModel);
@JsonKey(ignore: true)
@override
@pragma('vm:prefer-inline')
_$$HistoryRequestModelImplCopyWith<_$HistoryRequestModelImpl> get copyWith =>
__$$HistoryRequestModelImplCopyWithImpl<_$HistoryRequestModelImpl>(
this, _$identity);
@override
Map<String, dynamic> toJson() {
return _$$HistoryRequestModelImplToJson(
this,
);
}
}
abstract class _HistoryRequestModel implements HistoryRequestModel {
const factory _HistoryRequestModel(
{required final String historyId,
required final HistoryMetaModel metaData,
required final HttpRequestModel httpRequestModel,
required final HttpResponseModel httpResponseModel}) =
_$HistoryRequestModelImpl;
factory _HistoryRequestModel.fromJson(Map<String, dynamic> json) =
_$HistoryRequestModelImpl.fromJson;
@override
String get historyId;
@override
HistoryMetaModel get metaData;
@override
HttpRequestModel get httpRequestModel;
@override
HttpResponseModel get httpResponseModel;
@override
@JsonKey(ignore: true)
_$$HistoryRequestModelImplCopyWith<_$HistoryRequestModelImpl> get copyWith =>
throw _privateConstructorUsedError;
}

View File

@ -0,0 +1,27 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'history_request_model.dart';
// **************************************************************************
// JsonSerializableGenerator
// **************************************************************************
_$HistoryRequestModelImpl _$$HistoryRequestModelImplFromJson(Map json) =>
_$HistoryRequestModelImpl(
historyId: json['historyId'] as String,
metaData: HistoryMetaModel.fromJson(
Map<String, Object?>.from(json['metaData'] as Map)),
httpRequestModel: HttpRequestModel.fromJson(
Map<String, Object?>.from(json['httpRequestModel'] as Map)),
httpResponseModel: HttpResponseModel.fromJson(
Map<String, Object?>.from(json['httpResponseModel'] as Map)),
);
Map<String, dynamic> _$$HistoryRequestModelImplToJson(
_$HistoryRequestModelImpl instance) =>
<String, dynamic>{
'historyId': instance.historyId,
'metaData': instance.metaData.toJson(),
'httpRequestModel': instance.httpRequestModel.toJson(),
'httpResponseModel': instance.httpResponseModel.toJson(),
};

View File

@ -1,5 +1,7 @@
export 'environment_model.dart';
export 'form_data_model.dart';
export 'history_meta_model.dart';
export 'history_request_model.dart';
export 'http_request_model.dart';
export 'http_response_model.dart';
export 'name_value_model.dart';

View File

@ -240,12 +240,28 @@ class CollectionStateNotifier
httpResponseModel: responseModel,
isWorking: false,
);
String newHistoryId = getNewUuid();
HistoryRequestModel model = HistoryRequestModel(
historyId: newHistoryId,
metaData: HistoryMetaModel(
historyId: newHistoryId,
name: requestModel.name,
url: substitutedHttpRequestModel.url,
method: substitutedHttpRequestModel.method,
responseStatus: statusCode,
timeStamp: DateTime.now(),
),
httpRequestModel: substitutedHttpRequestModel,
httpResponseModel: responseModel,
);
ref.read(historyMetaStateNotifier.notifier).addHistoryRequest(model);
}
// update state with response data
map = {...state!};
map[id] = newRequestModel;
state = map;
ref.read(hasUnsavedChangesProvider.notifier).state = true;
}

View File

@ -0,0 +1,91 @@
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:apidash/models/models.dart';
import '../services/services.dart' show hiveHandler, HiveHandler;
import '../utils/history_utils.dart';
final selectedHistoryIdStateProvider = StateProvider<String?>((ref) => null);
final selectedRequestGroupStateProvider = StateProvider<String?>((ref) {
final selectedHistoryId = ref.watch(selectedHistoryIdStateProvider);
if (selectedHistoryId == null) {
return null;
}
final historyMetaState = ref.read(historyMetaStateNotifier);
return getHistoryRequestKey(historyMetaState![selectedHistoryId]!);
});
final selectedHistoryRequestModelProvider =
StateProvider<HistoryRequestModel?>((ref) => null);
final historySequenceProvider =
StateProvider<Map<DateTime, List<HistoryMetaModel>>?>((ref) {
final historyMetas = ref.watch(historyMetaStateNotifier);
return getTemporalGroups(historyMetas?.values.toList());
});
final StateNotifierProvider<HistoryMetaStateNotifier,
Map<String, HistoryMetaModel>?> historyMetaStateNotifier =
StateNotifierProvider((ref) => HistoryMetaStateNotifier(ref, hiveHandler));
class HistoryMetaStateNotifier
extends StateNotifier<Map<String, HistoryMetaModel>?> {
HistoryMetaStateNotifier(this.ref, this.hiveHandler) : super(null) {
var status = loadHistoryMetas();
Future.microtask(() {
if (status) {
final temporalGroups = getTemporalGroups(state?.values.toList());
final latestRequestId = getLatestRequestId(temporalGroups);
if (latestRequestId != null) {
loadHistoryRequest(latestRequestId);
}
}
});
}
final Ref ref;
final HiveHandler hiveHandler;
bool loadHistoryMetas() {
List<String>? historyIds = hiveHandler.getHistoryIds();
if (historyIds == null || historyIds.isEmpty) {
state = null;
return false;
} else {
Map<String, HistoryMetaModel> historyMetaMap = {};
for (var historyId in historyIds) {
var jsonModel = hiveHandler.getHistoryMeta(historyId);
if (jsonModel != null) {
var jsonMap = Map<String, Object?>.from(jsonModel);
var historyMetaModelFromJson = HistoryMetaModel.fromJson(jsonMap);
historyMetaMap[historyId] = historyMetaModelFromJson;
}
}
state = historyMetaMap;
return true;
}
}
Future<void> loadHistoryRequest(String id) async {
var jsonModel = await hiveHandler.getHistoryRequest(id);
if (jsonModel != null) {
var jsonMap = Map<String, Object?>.from(jsonModel);
var historyRequestModelFromJson = HistoryRequestModel.fromJson(jsonMap);
ref.read(selectedHistoryRequestModelProvider.notifier).state =
historyRequestModelFromJson;
ref.read(selectedHistoryIdStateProvider.notifier).state = id;
}
}
void addHistoryRequest(HistoryRequestModel model) async {
final id = model.historyId;
state = {
...state ?? {},
id: model.metaData,
};
final List<String> updatedHistoryKeys =
state == null ? [id] : [...state!.keys, id];
hiveHandler.setHistoryIds(updatedHistoryKeys);
hiveHandler.setHistoryMeta(id, model.metaData.toJson());
await hiveHandler.setHistoryRequest(id, model.toJson());
}
}

View File

@ -1,4 +1,5 @@
export 'collection_providers.dart';
export 'environment_providers.dart';
export 'history_providers.dart';
export 'settings_providers.dart';
export 'ui_providers.dart';

View File

@ -1,29 +0,0 @@
import 'package:apidash/consts.dart';
import 'package:flutter/material.dart';
import 'package:apidash/widgets/widgets.dart';
showAboutAppDialog(
BuildContext context,
) {
showDialog(
context: context,
builder: (context) {
return AlertDialog(
contentPadding: kPt20 + kPh20,
content: Container(
width: double.infinity,
height: double.infinity,
constraints: const BoxConstraints(maxWidth: 540, maxHeight: 544),
child: const IntroMessage(),
),
actions: [
TextButton(
onPressed: () {
Navigator.of(context).pop();
},
child: const Text("Close"),
),
],
);
});
}

View File

@ -1,4 +1,4 @@
import 'package:apidash/screens/about_dialog.dart';
import 'package:apidash/screens/history/history_page.dart';
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:apidash/providers/providers.dart';
@ -118,7 +118,9 @@ class Dashboard extends ConsumerWidget {
EnvironmentPage(
scaffoldKey: mobileScaffoldKey,
),
const SizedBox(),
HistoryPage(
scaffoldKey: mobileScaffoldKey,
),
const SettingsPage(),
],
),

View File

@ -19,7 +19,7 @@ class EnvironmentEditor extends ConsumerWidget {
padding: context.isMediumWindow
? kPb10
: (kIsMacOS || kIsWindows)
? kPt24o8
? kPt28o8
: kP8,
child: Column(
children: [
@ -65,34 +65,40 @@ class EnvironmentEditor extends ConsumerWidget {
kVSpacer5,
Expanded(
child: Container(
padding: context.isMediumWindow ? null : kPv6,
margin: context.isMediumWindow ? null : kP4,
decoration: context.isMediumWindow
? null
: BoxDecoration(
border: Border.all(
color: Theme.of(context).colorScheme.outlineVariant,
width: 1,
),
borderRadius: kBorderRadius12,
),
child: const Column(
children: [
kHSpacer40,
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
child: Card(
margin: EdgeInsets.zero,
color: kColorTransparent,
surfaceTintColor: kColorTransparent,
shape: RoundedRectangleBorder(
side: BorderSide(
color:
Theme.of(context).colorScheme.surfaceContainerHighest,
),
borderRadius: kBorderRadius12,
),
elevation: 0,
child: const Padding(
padding: kPv6,
child: Column(
children: [
SizedBox(width: 30),
Text("Variable"),
SizedBox(width: 30),
Text("Value"),
SizedBox(width: 40),
kHSpacer40,
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
SizedBox(width: 30),
Text("Variable"),
SizedBox(width: 30),
Text("Value"),
SizedBox(width: 40),
],
),
kHSpacer40,
Divider(),
Expanded(child: EditEnvironmentVariables())
],
),
kHSpacer40,
Divider(),
Expanded(child: EditEnvironmentVariables())
],
),
),
),
),

View File

@ -0,0 +1,10 @@
import 'package:flutter/material.dart';
class HistoryDetails extends StatelessWidget {
const HistoryDetails({super.key});
@override
Widget build(BuildContext context) {
return Container();
}
}

View File

@ -0,0 +1,41 @@
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:apidash/widgets/widgets.dart';
import 'package:apidash/extensions/extensions.dart';
import 'package:apidash/providers/providers.dart';
import 'history_pane.dart';
import 'history_viewer.dart';
class HistoryPage extends ConsumerWidget {
const HistoryPage({
super.key,
required this.scaffoldKey,
});
final GlobalKey<ScaffoldState> scaffoldKey;
@override
Widget build(BuildContext context, WidgetRef ref) {
final historyModel = ref.watch(selectedHistoryRequestModelProvider);
if (context.isMediumWindow) {
return DrawerSplitView(
scaffoldKey: scaffoldKey,
mainContent: const HistoryViewer(),
title: Text(historyModel?.historyId ?? 'History'),
leftDrawerContent: const HistoryPane(),
actions: const [SizedBox(width: 16)],
onDrawerChanged: (value) =>
ref.read(leftDrawerStateProvider.notifier).state = value,
);
}
return const Column(
children: [
Expanded(
child: DashboardSplitView(
sidebarWidget: HistoryPane(),
mainWidget: HistoryViewer(),
),
),
],
);
}
}

View File

@ -0,0 +1,96 @@
import 'package:flutter/material.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:apidash/extensions/extensions.dart';
import 'package:apidash/providers/providers.dart';
import 'package:apidash/widgets/widgets.dart';
import 'package:apidash/utils/utils.dart';
import 'package:apidash/consts.dart';
class HistoryPane extends ConsumerWidget {
const HistoryPane({
super.key,
});
@override
Widget build(BuildContext context, WidgetRef ref) {
return Padding(
padding: (!context.isMediumWindow && kIsMacOS
? kP24CollectionPane
: kP8CollectionPane) +
(context.isMediumWindow ? kPb70 : EdgeInsets.zero),
child: const Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [kVSpacer10, Expanded(child: HistoryList()), kVSpacer5],
),
);
}
}
class HistoryList extends HookConsumerWidget {
const HistoryList({
super.key,
});
@override
Widget build(BuildContext context, WidgetRef ref) {
final historySequence = ref.watch(historySequenceProvider);
final alwaysShowHistoryPaneScrollbar = ref.watch(settingsProvider
.select((value) => value.alwaysShowCollectionPaneScrollbar));
final List<DateTime>? sortedHistoryKeys = historySequence?.keys.toList();
sortedHistoryKeys?.sort((a, b) => b.compareTo(a));
ScrollController scrollController = useScrollController();
return Scrollbar(
controller: scrollController,
thumbVisibility: alwaysShowHistoryPaneScrollbar,
radius: const Radius.circular(12),
child: ListView(
padding: context.isMediumWindow
? EdgeInsets.only(
bottom: MediaQuery.paddingOf(context).bottom,
right: 8,
)
: kPe8,
controller: scrollController,
children: sortedHistoryKeys != null
? sortedHistoryKeys.map((date) {
var items = historySequence![date]!;
final requestGroups = getRequestGroups(items);
return Column(
children: [
ExpansionTile(
title: Text(
humanizeDate(date),
),
children: requestGroups.values.map((item) {
return Padding(
padding: kPv2 + kPh4,
child: SidebarHistoryCard(
id: item.first.historyId,
models: item,
method: item.first.method,
selectedId:
ref.watch(selectedRequestGroupStateProvider),
requestGroupSize: item.length,
onTap: () {
ref
.read(historyMetaStateNotifier.notifier)
.loadHistoryRequest(item.first.historyId);
},
),
);
}).toList(),
),
],
);
}).toList()
: [
const Text(
'No history',
style: TextStyle(color: Colors.grey),
)
],
),
);
}
}

View File

@ -0,0 +1,10 @@
import 'package:flutter/material.dart';
class HistoryRequests extends StatelessWidget {
const HistoryRequests({super.key});
@override
Widget build(BuildContext context) {
return Container();
}
}

View File

@ -0,0 +1,35 @@
import 'package:flutter/material.dart';
import 'package:apidash/extensions/extensions.dart';
import 'package:apidash/widgets/widgets.dart';
import 'package:apidash/consts.dart';
import 'history_details.dart';
import 'history_requests.dart';
class HistoryViewer extends StatelessWidget {
const HistoryViewer({super.key});
@override
Widget build(BuildContext context) {
if (context.isMediumWindow) {
return const HistoryDetails();
}
return Padding(
padding: kIsMacOS || kIsWindows ? kPt28o8 : kP8,
child: Card(
color: kColorTransparent,
surfaceTintColor: kColorTransparent,
shape: RoundedRectangleBorder(
side: BorderSide(
color: Theme.of(context).colorScheme.surfaceContainerHighest,
),
borderRadius: kBorderRadius12,
),
elevation: 0,
child: const HistorySplitView(
sidebarWidget: HistoryRequests(),
mainWidget: HistoryDetails(),
),
),
);
}
}

View File

@ -27,7 +27,7 @@ class RequestEditor extends StatelessWidget {
),
)
: Padding(
padding: kIsMacOS || kIsWindows ? kPt24o8 : kP8,
padding: kIsMacOS || kIsWindows ? kPt28o8 : kP8,
child: const Column(
children: [
RequestEditorTopBar(),

View File

@ -1,3 +1,4 @@
import 'package:apidash/screens/history/history_page.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
@ -39,7 +40,7 @@ class _MobileDashboardState extends ConsumerState<MobileDashboard> {
),
if (context.isMediumWindow)
AnimatedPositioned(
bottom: railIdx > 1
bottom: railIdx > 2
? 0
: isLeftDrawerOpen
? 0
@ -72,17 +73,9 @@ class PageBranch extends ConsumerWidget {
return EnvironmentPage(
scaffoldKey: scaffoldKey,
);
// case 2:
// // TODO: Implement history page
// return const PageBase(
// title: 'History',
// scaffoldBody: SizedBox(),
// );
case 2:
// TODO: Implement history page
return const PageBase(
title: 'History',
scaffoldBody: SizedBox(),
return HistoryPage(
scaffoldKey: scaffoldKey,
);
case 3:
return const PageBase(

View File

@ -1,4 +1,3 @@
import 'package:apidash/screens/about_dialog.dart';
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import '../providers/providers.dart';

View File

@ -3,9 +3,14 @@ import 'package:hive_flutter/hive_flutter.dart';
const String kDataBox = "apidash-data";
const String kKeyDataBoxIds = "ids";
const String kEnvironmentBox = "apidash-environments";
const String kKeyEnvironmentBoxIds = "environmentIds";
const String kHistoryMetaBox = "apidash-history-meta";
const String kHistoryBoxIds = "historyIds";
const String kHistoryLazyBox = "apidash-history-lazy";
const String kSettingsBox = "apidash-settings";
Future<void> openBoxes() async {
@ -13,6 +18,8 @@ Future<void> openBoxes() async {
await Hive.openBox(kDataBox);
await Hive.openBox(kSettingsBox);
await Hive.openBox(kEnvironmentBox);
await Hive.openBox(kHistoryMetaBox);
await Hive.openLazyBox(kHistoryLazyBox);
}
(Size?, Offset?) getInitialSize() {
@ -38,11 +45,15 @@ 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();
@ -69,6 +80,25 @@ class HiveHandler {
Future<void> deleteEnvironment(String id) => environmentBox.delete(id);
dynamic getHistoryIds() => historyMetaBox.get(kHistoryBoxIds);
Future<void> setHistoryIds(List<String>? ids) =>
historyMetaBox.put(kHistoryBoxIds, ids);
dynamic getHistoryMeta(String id) => historyMetaBox.get(id);
Future<void> setHistoryMeta(
String id, Map<String, dynamic>? historyMetaJson) =>
historyMetaBox.put(id, historyMetaJson);
Future<void> deleteHistoryMeta(String id) => historyMetaBox.delete(id);
Future<dynamic> getHistoryRequest(String id) async =>
await historyLazyBox.get(id);
Future<void> setHistoryRequest(
String id, Map<String, dynamic>? historyRequestJsoon) =>
historyLazyBox.put(id, historyRequestJsoon);
Future<void> deleteHistoryReqyest(String id) => historyLazyBox.delete(id);
Future clear() async {
await dataBox.clear();
await environmentBox.clear();

View File

@ -1,10 +1,18 @@
import 'dart:typed_data';
import 'dart:convert';
import 'package:collection/collection.dart';
import 'package:intl/intl.dart';
import '../models/models.dart';
import '../consts.dart';
import 'package:http/http.dart' as http;
String humanizeDate(DateTime? date) {
if (date == null) {
return "";
}
return DateFormat('MMMM d, yyyy').format(date);
}
String humanizeDuration(Duration? duration) {
if (duration == null) {
return "";

View File

@ -0,0 +1,86 @@
import 'package:apidash/models/models.dart';
import 'package:apidash/utils/convert_utils.dart';
DateTime stripTime(DateTime dateTime) {
return DateTime(dateTime.year, dateTime.month, dateTime.day);
}
String getHistoryRequestName(HistoryMetaModel model) {
if (model.name.isNotEmpty) {
return model.name;
} else {
return model.url;
}
}
String getHistoryRequestKey(HistoryMetaModel model) {
String timeStamp = humanizeDate(model.timeStamp);
if (model.name.isNotEmpty) {
return model.name + model.method.name + timeStamp;
} else {
return model.url + model.method.name + timeStamp;
}
}
String? getLatestRequestId(
Map<DateTime, List<HistoryMetaModel>> temporalGroups) {
if (temporalGroups.isEmpty) {
return null;
}
List<DateTime> keys = temporalGroups.keys.toList();
keys.sort((a, b) => b.compareTo(a));
return temporalGroups[keys.first]!.first.historyId;
}
DateTime getDateTimeKey(List<DateTime> keys, DateTime currentKey) {
if (keys.isEmpty) return currentKey;
for (DateTime key in keys) {
if (key.year == currentKey.year &&
key.month == currentKey.month &&
key.day == currentKey.day) {
return key;
}
}
return stripTime(currentKey);
}
Map<DateTime, List<HistoryMetaModel>> getTemporalGroups(
List<HistoryMetaModel>? models) {
Map<DateTime, List<HistoryMetaModel>> temporalGroups = {};
if (models?.isEmpty ?? true) {
return temporalGroups;
}
for (HistoryMetaModel model in models!) {
List<DateTime> existingKeys = temporalGroups.keys.toList();
DateTime key = getDateTimeKey(existingKeys, model.timeStamp);
if (existingKeys.contains(key)) {
temporalGroups[key]!.add(model);
} else {
temporalGroups[stripTime(key)] = [model];
}
}
temporalGroups.forEach((key, value) {
value.sort((a, b) => b.timeStamp.compareTo(a.timeStamp));
});
return temporalGroups;
}
Map<String, List<HistoryMetaModel>> getRequestGroups(
List<HistoryMetaModel>? models) {
Map<String, List<HistoryMetaModel>> historyGroups = {};
if (models?.isEmpty ?? true) {
return historyGroups;
}
for (HistoryMetaModel model in models!) {
String key = getHistoryRequestKey(model);
if (historyGroups.containsKey(key)) {
historyGroups[key]!.add(model);
} else {
historyGroups[key] = [model];
}
}
historyGroups.forEach((key, value) {
value.sort((a, b) => b.timeStamp.compareTo(a.timeStamp));
});
return historyGroups;
}

View File

@ -1,6 +1,7 @@
export 'ui_utils.dart';
export 'convert_utils.dart';
export 'header_utils.dart';
export 'history_utils.dart';
export 'http_utils.dart';
export 'file_utils.dart';
export 'window_utils.dart';

View File

@ -0,0 +1,110 @@
import 'package:apidash/models/history_meta_model.dart';
import 'package:flutter/material.dart';
import 'package:apidash/consts.dart';
import 'package:apidash/utils/utils.dart';
import 'texts.dart' show MethodBox;
class SidebarHistoryCard extends StatelessWidget {
const SidebarHistoryCard({
super.key,
required this.id,
required this.models,
required this.method,
this.selectedId,
this.requestGroupSize = 1,
this.onTap,
});
final String id;
final List<HistoryMetaModel> models;
final HTTPVerb method;
final String? selectedId;
final int requestGroupSize;
final Function()? onTap;
@override
Widget build(BuildContext context) {
final Color color = Theme.of(context).colorScheme.surface;
final Color colorVariant =
Theme.of(context).colorScheme.surfaceContainerHighest.withOpacity(0.5);
final model = models.first;
final Color surfaceTint = Theme.of(context).colorScheme.primary;
bool isSelected = selectedId == getHistoryRequestKey(model);
final String name = getHistoryRequestName(model);
return Tooltip(
message: name,
triggerMode: TooltipTriggerMode.manual,
waitDuration: const Duration(seconds: 1),
child: Card(
shape: const RoundedRectangleBorder(
borderRadius: kBorderRadius8,
),
elevation: isSelected ? 1 : 0,
surfaceTintColor: isSelected ? surfaceTint : null,
color: isSelected
? Theme.of(context).colorScheme.brightness == Brightness.dark
? colorVariant
: color
: color,
margin: EdgeInsets.zero,
child: InkWell(
onTap: onTap,
borderRadius: kBorderRadius8,
hoverColor: colorVariant,
focusColor: colorVariant.withOpacity(0.5),
child: Padding(
padding: const EdgeInsets.only(
left: 6,
right: 6,
top: 5,
bottom: 5,
),
child: SizedBox(
height: 20,
child: Row(
children: [
MethodBox(method: method),
kHSpacer4,
Expanded(
child: Text(
name,
softWrap: false,
overflow: TextOverflow.fade,
),
),
requestGroupSize > 1 ? kHSpacer4 : const SizedBox.shrink(),
Visibility(
visible: requestGroupSize > 1,
child: Container(
padding: kPh4,
constraints: const BoxConstraints(minWidth: 24),
decoration: BoxDecoration(
color: Theme.of(context).colorScheme.primaryContainer,
borderRadius: kBorderRadius6,
),
child: Center(
child: Text(
requestGroupSize == 2
? requestGroupSize.toString()
: "9+",
style: Theme.of(context)
.textTheme
.labelSmall
?.copyWith(
fontWeight: FontWeight.bold,
color: Theme.of(context)
.colorScheme
.onPrimaryContainer,
)),
),
),
),
],
),
),
),
),
),
);
}
}

View File

@ -0,0 +1,65 @@
import 'package:flutter/material.dart';
import 'package:multi_split_view/multi_split_view.dart';
import 'package:apidash/consts.dart';
class HistorySplitView extends StatefulWidget {
const HistorySplitView({
super.key,
required this.sidebarWidget,
required this.mainWidget,
});
final Widget sidebarWidget;
final Widget mainWidget;
@override
HistorySplitViewState createState() => HistorySplitViewState();
}
class HistorySplitViewState extends State<HistorySplitView> {
final MultiSplitViewController _controller = MultiSplitViewController(
areas: [
Area(id: "sidebar", min: 200, size: 220, max: 300),
Area(id: "main"),
],
);
@override
void initState() {
super.initState();
}
@override
Widget build(BuildContext context) {
return MultiSplitViewTheme(
data: MultiSplitViewThemeData(
dividerThickness: 3,
dividerPainter: DividerPainters.background(
color: Theme.of(context).colorScheme.surfaceContainerHighest,
highlightedColor: Theme.of(context).colorScheme.outline.withOpacity(
kHintOpacity,
),
animationEnabled: false,
),
),
child: MultiSplitView(
controller: _controller,
sizeOverflowPolicy: SizeOverflowPolicy.shrinkFirst,
sizeUnderflowPolicy: SizeUnderflowPolicy.stretchLast,
builder: (context, area) {
return switch (area.id) {
"sidebar" => widget.sidebarWidget,
"main" => widget.mainWidget,
_ => Container(),
};
},
),
);
}
@override
void dispose() {
_controller.dispose();
super.dispose();
}
}

View File

@ -6,6 +6,7 @@ export 'button_save_download.dart';
export 'button_send.dart';
export 'card_request_details.dart';
export 'card_sidebar_environment.dart';
export 'card_sidebar_history.dart';
export 'card_sidebar_request.dart';
export 'checkbox.dart';
export 'code_previewer.dart';
@ -42,6 +43,7 @@ export 'snackbars.dart';
export 'splitview_drawer.dart';
export 'splitview_dashboard.dart';
export 'splitview_equal.dart';
export 'splitview_history.dart';
export 'suggestions_menu.dart';
export 'tables.dart';
export 'tabs.dart';

View File

@ -648,6 +648,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "4.2.0"
intl:
dependency: "direct main"
description:
name: intl
sha256: d6f56758b7d3014a48af9701c085700aac781a92a87a62b1333b46d8879661cf
url: "https://pub.dev"
source: hosted
version: "0.19.0"
io:
dependency: transitive
description:

View File

@ -38,7 +38,7 @@ dependencies:
just_audio_mpv: ^0.1.7
just_audio_windows: ^0.2.0
freezed_annotation: ^2.4.1
json_annotation: ^4.8.1
json_annotation: ^4.9.0
printing: ^5.12.0
package_info_plus: ^8.0.0
flutter_typeahead: ^5.2.0
@ -64,6 +64,7 @@ dependencies:
flutter_hooks: ^0.20.5
flutter_portal: ^1.1.4
mention_tag_text_field: ^0.0.5
intl: ^0.19.0
dependency_overrides:
web: ^0.5.0