mirror of
https://github.com/Xevion/paperless-mobile.git
synced 2025-12-07 22:07:49 -06:00
feat: Add response delay generator to mock_server
This commit is contained in:
@@ -1,6 +1,7 @@
|
|||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
|
|
||||||
import 'package:collection/collection.dart';
|
import 'package:collection/collection.dart';
|
||||||
|
import 'package:flutter/cupertino.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
import 'package:paperless_mobile/core/navigation/push_routes.dart';
|
import 'package:paperless_mobile/core/navigation/push_routes.dart';
|
||||||
@@ -75,6 +76,17 @@ class _DocumentSearchPageState extends State<DocumentSearchPage> {
|
|||||||
},
|
},
|
||||||
).padded(),
|
).padded(),
|
||||||
],
|
],
|
||||||
|
bottom: PreferredSize(
|
||||||
|
preferredSize: Size.fromHeight(1),
|
||||||
|
child: BlocBuilder<DocumentSearchCubit, DocumentSearchState>(
|
||||||
|
builder: (context, state) {
|
||||||
|
if (state.isLoading) {
|
||||||
|
return const LinearProgressIndicator();
|
||||||
|
}
|
||||||
|
return const SizedBox.shrink();
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
body: Column(
|
body: Column(
|
||||||
children: [
|
children: [
|
||||||
@@ -117,24 +129,17 @@ class _DocumentSearchPageState extends State<DocumentSearchPage> {
|
|||||||
childCount: historyMatches.length,
|
childCount: historyMatches.length,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
if (state.isLoading)
|
SliverList(
|
||||||
const SliverToBoxAdapter(
|
delegate: SliverChildBuilderDelegate(
|
||||||
child: Center(
|
(context, index) => ListTile(
|
||||||
child: CircularProgressIndicator(),
|
title: Text(suggestions[index]),
|
||||||
),
|
leading: const Icon(Icons.search),
|
||||||
)
|
onTap: () => _selectSuggestion(suggestions[index]),
|
||||||
else
|
trailing: _buildInsertSuggestionButton(suggestions[index]),
|
||||||
SliverList(
|
|
||||||
delegate: SliverChildBuilderDelegate(
|
|
||||||
(context, index) => ListTile(
|
|
||||||
title: Text(suggestions[index]),
|
|
||||||
leading: const Icon(Icons.search),
|
|
||||||
onTap: () => _selectSuggestion(suggestions[index]),
|
|
||||||
trailing: _buildInsertSuggestionButton(suggestions[index]),
|
|
||||||
),
|
|
||||||
childCount: suggestions.length,
|
|
||||||
),
|
),
|
||||||
|
childCount: suggestions.length,
|
||||||
),
|
),
|
||||||
|
),
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -75,7 +75,14 @@ Future<void> _initHive() async {
|
|||||||
|
|
||||||
void main() async {
|
void main() async {
|
||||||
if (kDebugMode) {
|
if (kDebugMode) {
|
||||||
await LocalMockApiServer().start();
|
// URL: http://localhost:3131
|
||||||
|
// Login: admin:test
|
||||||
|
await LocalMockApiServer(
|
||||||
|
RandomDelayGenerator(
|
||||||
|
const Duration(milliseconds: 100),
|
||||||
|
const Duration(milliseconds: 800),
|
||||||
|
),
|
||||||
|
).start();
|
||||||
}
|
}
|
||||||
await _initHive();
|
await _initHive();
|
||||||
final widgetsBinding = WidgetsFlutterBinding.ensureInitialized();
|
final widgetsBinding = WidgetsFlutterBinding.ensureInitialized();
|
||||||
|
|||||||
@@ -1,10 +1,13 @@
|
|||||||
library mock_server;
|
library mock_server;
|
||||||
|
|
||||||
|
export 'response_delay_generator.dart';
|
||||||
|
|
||||||
import 'dart:convert';
|
import 'dart:convert';
|
||||||
import 'dart:math';
|
import 'dart:math';
|
||||||
import 'package:http/http.dart' as http;
|
import 'package:http/http.dart' as http;
|
||||||
|
|
||||||
import 'package:logging/logging.dart';
|
import 'package:logging/logging.dart';
|
||||||
|
import 'package:mock_server/response_delay_generator.dart';
|
||||||
|
|
||||||
import 'package:shelf/shelf.dart';
|
import 'package:shelf/shelf.dart';
|
||||||
|
|
||||||
@@ -23,27 +26,29 @@ class LocalMockApiServer {
|
|||||||
|
|
||||||
static get baseUrl => 'http://$host:$port/';
|
static get baseUrl => 'http://$host:$port/';
|
||||||
|
|
||||||
|
final DelayGenerator _delayGenerator;
|
||||||
|
|
||||||
late shelf_router.Router app;
|
late shelf_router.Router app;
|
||||||
Future<Map<String, dynamic>> loadFixture(String name) async {
|
Future<Map<String, dynamic>> loadFixture(String name) async {
|
||||||
var fixture = await rootBundle.loadString('packages/mock_server/fixtures/$name.json');
|
var fixture = await rootBundle.loadString('packages/mock_server/fixtures/$name.json');
|
||||||
return json.decode(fixture);
|
return json.decode(fixture);
|
||||||
}
|
}
|
||||||
|
|
||||||
LocalMockApiServer() {
|
LocalMockApiServer([this._delayGenerator = const ZeroDelayGenerator()]) {
|
||||||
app = shelf_router.Router();
|
app = shelf_router.Router();
|
||||||
|
|
||||||
Map<String, dynamic> createdTags = {};
|
Map<String, dynamic> createdTags = {};
|
||||||
|
|
||||||
app.get('/api/', (Request req) async {
|
app.get('/api/', (Request req) async {
|
||||||
log.info('Responding to /api');
|
log.info('Responding to /api');
|
||||||
return JsonMockResponse.ok({});
|
return JsonMockResponse.ok({}, _delayGenerator.nextDelay());
|
||||||
});
|
});
|
||||||
|
|
||||||
app.post('/api/token/', (Request req) async {
|
app.post('/api/token/', (Request req) async {
|
||||||
log.info('Responding to /api/token/');
|
log.info('Responding to /api/token/');
|
||||||
var body = await req.bodyJsonMap();
|
var body = await req.bodyJsonMap();
|
||||||
if (body?['username'] == 'admin' && body?['password'] == 'test') {
|
if (body?['username'] == 'admin' && body?['password'] == 'test') {
|
||||||
return JsonMockResponse.ok({'token': 'testToken'});
|
return JsonMockResponse.ok({'token': 'testToken'}, _delayGenerator.nextDelay());
|
||||||
} else {
|
} else {
|
||||||
return Response.unauthorized('Unauthorized');
|
return Response.unauthorized('Unauthorized');
|
||||||
}
|
}
|
||||||
@@ -52,37 +57,37 @@ class LocalMockApiServer {
|
|||||||
app.get('/api/ui_settings/', (Request req) async {
|
app.get('/api/ui_settings/', (Request req) async {
|
||||||
log.info('Responding to /api/ui_settings/');
|
log.info('Responding to /api/ui_settings/');
|
||||||
var data = await loadFixture('ui_settings');
|
var data = await loadFixture('ui_settings');
|
||||||
return JsonMockResponse.ok(data);
|
return JsonMockResponse.ok(data, _delayGenerator.nextDelay());
|
||||||
});
|
});
|
||||||
|
|
||||||
app.get('/api/users/<userId>/', (Request req, String userId) async {
|
app.get('/api/users/<userId>/', (Request req, String userId) async {
|
||||||
log.info('Responding to /api/users/<userId>/');
|
log.info('Responding to /api/users/<userId>/');
|
||||||
var data = await loadFixture('user-1');
|
var data = await loadFixture('user-1');
|
||||||
return JsonMockResponse.ok(data);
|
return JsonMockResponse.ok(data, _delayGenerator.nextDelay());
|
||||||
});
|
});
|
||||||
|
|
||||||
app.get('/api/users/', (Request req, String userId) async {
|
app.get('/api/users/', (Request req, String userId) async {
|
||||||
log.info('Responding to /api/users/');
|
log.info('Responding to /api/users/');
|
||||||
var data = await loadFixture('users');
|
var data = await loadFixture('users');
|
||||||
return JsonMockResponse.ok(data);
|
return JsonMockResponse.ok(data, _delayGenerator.nextDelay());
|
||||||
});
|
});
|
||||||
|
|
||||||
app.get('/api/groups/', (Request req, String userId) async {
|
app.get('/api/groups/', (Request req, String userId) async {
|
||||||
log.info('Responding to /api/groups/');
|
log.info('Responding to /api/groups/');
|
||||||
var data = await loadFixture('groups');
|
var data = await loadFixture('groups');
|
||||||
return JsonMockResponse.ok(data);
|
return JsonMockResponse.ok(data, _delayGenerator.nextDelay());
|
||||||
});
|
});
|
||||||
|
|
||||||
app.get('/api/correspondents/', (Request req) async {
|
app.get('/api/correspondents/', (Request req) async {
|
||||||
log.info('Responding to /api/correspondents/');
|
log.info('Responding to /api/correspondents/');
|
||||||
var data = await loadFixture('correspondents');
|
var data = await loadFixture('correspondents');
|
||||||
return JsonMockResponse.ok(data);
|
return JsonMockResponse.ok(data, _delayGenerator.nextDelay());
|
||||||
});
|
});
|
||||||
|
|
||||||
app.get('/api/document_types/', (Request req) async {
|
app.get('/api/document_types/', (Request req) async {
|
||||||
log.info('Responding to /api/document_types/');
|
log.info('Responding to /api/document_types/');
|
||||||
var data = await loadFixture('doc_types');
|
var data = await loadFixture('doc_types');
|
||||||
return JsonMockResponse.ok(data);
|
return JsonMockResponse.ok(data, _delayGenerator.nextDelay());
|
||||||
});
|
});
|
||||||
|
|
||||||
app.get('/api/tags/', (Request req) async {
|
app.get('/api/tags/', (Request req) async {
|
||||||
@@ -91,7 +96,7 @@ class LocalMockApiServer {
|
|||||||
var data = await loadFixture("tags");
|
var data = await loadFixture("tags");
|
||||||
createdTags = data;
|
createdTags = data;
|
||||||
}
|
}
|
||||||
return JsonMockResponse.ok(createdTags);
|
return JsonMockResponse.ok(createdTags, _delayGenerator.nextDelay());
|
||||||
});
|
});
|
||||||
|
|
||||||
app.post('/api/tags/', (Request req) async {
|
app.post('/api/tags/', (Request req) async {
|
||||||
@@ -156,25 +161,25 @@ class LocalMockApiServer {
|
|||||||
app.get('/api/storage_paths/', (Request req) async {
|
app.get('/api/storage_paths/', (Request req) async {
|
||||||
log.info('Responding to /api/storage_paths/');
|
log.info('Responding to /api/storage_paths/');
|
||||||
var data = await loadFixture('storage_paths');
|
var data = await loadFixture('storage_paths');
|
||||||
return JsonMockResponse.ok(data);
|
return JsonMockResponse.ok(data, _delayGenerator.nextDelay());
|
||||||
});
|
});
|
||||||
|
|
||||||
app.get('/api/storage_paths/', (Request req) async {
|
app.get('/api/storage_paths/', (Request req) async {
|
||||||
log.info('Responding to /api/storage_paths/');
|
log.info('Responding to /api/storage_paths/');
|
||||||
var data = await loadFixture('storage_paths');
|
var data = await loadFixture('storage_paths');
|
||||||
return JsonMockResponse.ok(data);
|
return JsonMockResponse.ok(data, _delayGenerator.nextDelay());
|
||||||
});
|
});
|
||||||
|
|
||||||
app.get('/api/saved_views/', (Request req) async {
|
app.get('/api/saved_views/', (Request req) async {
|
||||||
log.info('Responding to /api/saved_views/');
|
log.info('Responding to /api/saved_views/');
|
||||||
var data = await loadFixture('saved_views');
|
var data = await loadFixture('saved_views');
|
||||||
return JsonMockResponse.ok(data);
|
return JsonMockResponse.ok(data, _delayGenerator.nextDelay());
|
||||||
});
|
});
|
||||||
|
|
||||||
app.get('/api/documents/', (Request req) async {
|
app.get('/api/documents/', (Request req) async {
|
||||||
log.info('Responding to /api/documents/');
|
log.info('Responding to /api/documents/');
|
||||||
var data = await loadFixture('documents');
|
var data = await loadFixture('documents');
|
||||||
return JsonMockResponse.ok(data);
|
return JsonMockResponse.ok(data, _delayGenerator.nextDelay());
|
||||||
});
|
});
|
||||||
|
|
||||||
app.get('/api/documents/<docId>/thumb/', (Request req, String docId) async {
|
app.get('/api/documents/<docId>/thumb/', (Request req, String docId) async {
|
||||||
@@ -194,39 +199,39 @@ class LocalMockApiServer {
|
|||||||
app.get('/api/documents/<docId>/metadata/', (Request req, String docId) async {
|
app.get('/api/documents/<docId>/metadata/', (Request req, String docId) async {
|
||||||
log.info('Responding to /api/documents/<docId>/metadata/');
|
log.info('Responding to /api/documents/<docId>/metadata/');
|
||||||
var data = await loadFixture('metadata');
|
var data = await loadFixture('metadata');
|
||||||
return JsonMockResponse.ok(data);
|
return JsonMockResponse.ok(data, _delayGenerator.nextDelay());
|
||||||
});
|
});
|
||||||
|
|
||||||
//This is not yet used in the app
|
//This is not yet used in the app
|
||||||
app.get('/api/documents/<docId>/suggestions/', (Request req, String docId) async {
|
app.get('/api/documents/<docId>/suggestions/', (Request req, String docId) async {
|
||||||
log.info('Responding to /api/documents/<docId>/suggestions/');
|
log.info('Responding to /api/documents/<docId>/suggestions/');
|
||||||
var data = await loadFixture('suggestions');
|
var data = await loadFixture('suggestions');
|
||||||
return JsonMockResponse.ok(data);
|
return JsonMockResponse.ok(data, _delayGenerator.nextDelay());
|
||||||
});
|
});
|
||||||
|
|
||||||
//This is not yet used in the app
|
//This is not yet used in the app
|
||||||
app.get('/api/documents/<docId>/notes/', (Request req, String docId) async {
|
app.get('/api/documents/<docId>/notes/', (Request req, String docId) async {
|
||||||
log.info('Responding to /api/documents/<docId>/notes/');
|
log.info('Responding to /api/documents/<docId>/notes/');
|
||||||
var data = await loadFixture('notes');
|
var data = await loadFixture('notes');
|
||||||
return JsonMockResponse.ok(data);
|
return JsonMockResponse.ok(data, _delayGenerator.nextDelay());
|
||||||
});
|
});
|
||||||
|
|
||||||
app.get('/api/tasks/', (Request req) async {
|
app.get('/api/tasks/', (Request req) async {
|
||||||
log.info('Responding to /api/tasks/');
|
log.info('Responding to /api/tasks/');
|
||||||
var data = await loadFixture('tasks');
|
var data = await loadFixture('tasks');
|
||||||
return JsonMockResponse.ok(data);
|
return JsonMockResponse.ok(data, _delayGenerator.nextDelay());
|
||||||
});
|
});
|
||||||
|
|
||||||
app.get('/api/statistics/', (Request req) async {
|
app.get('/api/statistics/', (Request req) async {
|
||||||
log.info('Responding to /api/statistics/');
|
log.info('Responding to /api/statistics/');
|
||||||
var data = await loadFixture('statistics');
|
var data = await loadFixture('statistics');
|
||||||
return JsonMockResponse.ok(data);
|
return JsonMockResponse.ok(data, _delayGenerator.nextDelay());
|
||||||
});
|
});
|
||||||
|
|
||||||
app.get('/api/statistics/', (Request req) async {
|
app.get('/api/statistics/', (Request req) async {
|
||||||
log.info('Responding to /api/statistics/');
|
log.info('Responding to /api/statistics/');
|
||||||
var data = await loadFixture('statistics');
|
var data = await loadFixture('statistics');
|
||||||
return JsonMockResponse.ok(data);
|
return JsonMockResponse.ok(data, _delayGenerator.nextDelay());
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -264,8 +269,8 @@ extension on Request {
|
|||||||
}
|
}
|
||||||
|
|
||||||
extension JsonMockResponse on Response {
|
extension JsonMockResponse on Response {
|
||||||
static ok<T>(T json, {int delay = 800}) async {
|
static ok<T>(T json, Duration delay) async {
|
||||||
await Future.delayed(Duration(milliseconds: delay)); // Emulate lag
|
await Future.delayed(delay); // Emulate lag
|
||||||
|
|
||||||
return Response.ok(
|
return Response.ok(
|
||||||
jsonEncode(json),
|
jsonEncode(json),
|
||||||
|
|||||||
46
packages/mock_server/lib/response_delay_generator.dart
Normal file
46
packages/mock_server/lib/response_delay_generator.dart
Normal file
@@ -0,0 +1,46 @@
|
|||||||
|
import 'dart:math';
|
||||||
|
|
||||||
|
abstract interface class DelayGenerator {
|
||||||
|
Duration nextDelay();
|
||||||
|
}
|
||||||
|
|
||||||
|
class RandomDelayGenerator implements DelayGenerator {
|
||||||
|
/// Minimum allowed response delay
|
||||||
|
final Duration minDelay;
|
||||||
|
|
||||||
|
/// Maximum allowed response delay
|
||||||
|
final Duration maxDelay;
|
||||||
|
|
||||||
|
final Random _random = Random();
|
||||||
|
RandomDelayGenerator(this.minDelay, this.maxDelay);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Duration nextDelay() {
|
||||||
|
return Duration(
|
||||||
|
milliseconds: minDelay.inMilliseconds +
|
||||||
|
_random.nextInt(
|
||||||
|
maxDelay.inMilliseconds - minDelay.inMilliseconds,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class ConstantDelayGenerator implements DelayGenerator {
|
||||||
|
final Duration delay;
|
||||||
|
|
||||||
|
const ConstantDelayGenerator(this.delay);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Duration nextDelay() {
|
||||||
|
return delay;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class ZeroDelayGenerator implements DelayGenerator {
|
||||||
|
const ZeroDelayGenerator();
|
||||||
|
|
||||||
|
@override
|
||||||
|
Duration nextDelay() {
|
||||||
|
return Duration.zero;
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user