merge: 合并 K6 导航与观看历史缺陷修复
This commit is contained in:
commit
b7b1c50427
8 files changed
+85
-3
No files matched your search
@@ -5,6 +5,7 @@ import 'package:flutter/foundation.dart';
|
|||||||
import '../models/drama.dart';
|
import '../models/drama.dart';
|
||||||
import '../models/watch_progress.dart';
|
import '../models/watch_progress.dart';
|
||||||
import '../models/watch_record.dart';
|
import '../models/watch_record.dart';
|
||||||
|
import '../remote/api_client.dart';
|
||||||
import '../repositories/catalog_repository.dart';
|
import '../repositories/catalog_repository.dart';
|
||||||
import '../repositories/watch_history_repository.dart';
|
import '../repositories/watch_history_repository.dart';
|
||||||
|
|
||||||
@@ -47,8 +48,18 @@ class WatchRecordsController extends ChangeNotifier {
|
|||||||
records = _uniqueRecords(recent);
|
records = _uniqueRecords(recent);
|
||||||
_notify();
|
_notify();
|
||||||
await _hydrate(records.map((record) => record.dramaId).toList());
|
await _hydrate(records.map((record) => record.dramaId).toList());
|
||||||
} catch (_) {
|
} catch (failure, stackTrace) {
|
||||||
if (!_disposed) error = 'progressError';
|
debugPrint(
|
||||||
|
'[WatchRecordsController] recent failed '
|
||||||
|
'${failure.runtimeType}: $failure',
|
||||||
|
);
|
||||||
|
debugPrintStack(
|
||||||
|
stackTrace: stackTrace,
|
||||||
|
label: '[WatchRecordsController] recent failed',
|
||||||
|
);
|
||||||
|
if (!_disposed) {
|
||||||
|
error = failure is ApiException ? failure.message : 'progressError';
|
||||||
|
}
|
||||||
} finally {
|
} finally {
|
||||||
if (!_disposed) {
|
if (!_disposed) {
|
||||||
loading = false;
|
loading = false;
|
||||||
@@ -169,7 +180,15 @@ class WatchRecordsController extends ChangeNotifier {
|
|||||||
detailFailed: false,
|
detailFailed: false,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
} catch (_) {
|
} catch (failure, stackTrace) {
|
||||||
|
debugPrint(
|
||||||
|
'[WatchRecordsController] detail failed dramaId=$dramaId '
|
||||||
|
'${failure.runtimeType}: $failure',
|
||||||
|
);
|
||||||
|
debugPrintStack(
|
||||||
|
stackTrace: stackTrace,
|
||||||
|
label: '[WatchRecordsController] detail failed dramaId=$dramaId',
|
||||||
|
);
|
||||||
if (_disposed) return;
|
if (_disposed) return;
|
||||||
final current = _recordIndex(dramaId);
|
final current = _recordIndex(dramaId);
|
||||||
if (current < 0) return;
|
if (current < 0) return;
|
||||||
|
|||||||
@@ -17,4 +17,8 @@ abstract final class AppColors {
|
|||||||
static const bar = Color(0xFF101114);
|
static const bar = Color(0xFF101114);
|
||||||
static const card = Color(0xFF17191F);
|
static const card = Color(0xFF17191F);
|
||||||
static const muted = Color(0xFFB7BDC9);
|
static const muted = Color(0xFFB7BDC9);
|
||||||
|
static const outlineDark = Color(0xFF3A3C45);
|
||||||
|
static const outlineVariantDark = Color(0xFF242329);
|
||||||
|
static const outlineLight = Color(0xFF8A8E99);
|
||||||
|
static const outlineVariantLight = Color(0xFFD6D9E2);
|
||||||
}
|
}
|
||||||
@@ -41,6 +41,8 @@ abstract final class AppTheme {
|
|||||||
onSecondaryContainer: AppColors.onPrimary,
|
onSecondaryContainer: AppColors.onPrimary,
|
||||||
surface: _darkBackground,
|
surface: _darkBackground,
|
||||||
onSurface: Colors.white,
|
onSurface: Colors.white,
|
||||||
|
outline: AppColors.outlineDark,
|
||||||
|
outlineVariant: AppColors.outlineVariantDark,
|
||||||
surfaceTint: AppColors.blueSoft,
|
surfaceTint: AppColors.blueSoft,
|
||||||
inversePrimary: AppColors.blueSoft,
|
inversePrimary: AppColors.blueSoft,
|
||||||
)
|
)
|
||||||
@@ -55,6 +57,8 @@ abstract final class AppTheme {
|
|||||||
onSecondaryContainer: AppColors.ink,
|
onSecondaryContainer: AppColors.ink,
|
||||||
surface: _lightBackground,
|
surface: _lightBackground,
|
||||||
onSurface: AppColors.ink,
|
onSurface: AppColors.ink,
|
||||||
|
outline: AppColors.outlineLight,
|
||||||
|
outlineVariant: AppColors.outlineVariantLight,
|
||||||
surfaceTint: AppColors.blue,
|
surfaceTint: AppColors.blue,
|
||||||
inversePrimary: AppColors.blueSoft,
|
inversePrimary: AppColors.blueSoft,
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -371,6 +371,7 @@ class _FailureState extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
const SizedBox(height: 14),
|
const SizedBox(height: 14),
|
||||||
FilledButton(
|
FilledButton(
|
||||||
|
key: const Key('watch-records-retry'),
|
||||||
onPressed: () => unawaited(onRetry()),
|
onPressed: () => unawaited(onRetry()),
|
||||||
child: Text(l10n.retry),
|
child: Text(l10n.retry),
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -55,6 +55,8 @@ void main() {
|
|||||||
final context = tester.element(barFinder);
|
final context = tester.element(barFinder);
|
||||||
expect(decoration.color, AppColors.bar);
|
expect(decoration.color, AppColors.bar);
|
||||||
expect(border.top.color, Theme.of(context).colorScheme.outlineVariant);
|
expect(border.top.color, Theme.of(context).colorScheme.outlineVariant);
|
||||||
|
expect(border.top.color.computeLuminance(), lessThan(0.2));
|
||||||
|
expect(border.top.color, isNot(Colors.white));
|
||||||
expect(
|
expect(
|
||||||
tester
|
tester
|
||||||
.widget<AnimatedContainer>(
|
.widget<AnimatedContainer>(
|
||||||
|
|||||||
@@ -40,6 +40,18 @@ void main() {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('should_define_non_white_outline_tokens_in_both_themes', () {
|
||||||
|
final light = AppTheme.light().colorScheme;
|
||||||
|
final dark = AppTheme.dark().colorScheme;
|
||||||
|
|
||||||
|
expect(light.outline, AppColors.outlineLight);
|
||||||
|
expect(light.outlineVariant, AppColors.outlineVariantLight);
|
||||||
|
expect(dark.outline, AppColors.outlineDark);
|
||||||
|
expect(dark.outlineVariant, AppColors.outlineVariantDark);
|
||||||
|
expect(dark.outlineVariant.computeLuminance(), lessThan(0.2));
|
||||||
|
expect(light.outlineVariant.computeLuminance(), lessThan(0.92));
|
||||||
|
});
|
||||||
|
|
||||||
testWidgets('should_expose_klein_blue_through_widget_theme', (tester) async {
|
testWidgets('should_expose_klein_blue_through_widget_theme', (tester) async {
|
||||||
late ThemeData applied;
|
late ThemeData applied;
|
||||||
await tester.pumpWidget(
|
await tester.pumpWidget(
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import 'package:fruitshorts/core/data/models/home_feed.dart';
|
|||||||
import 'package:fruitshorts/core/data/models/watch_progress.dart';
|
import 'package:fruitshorts/core/data/models/watch_progress.dart';
|
||||||
import 'package:fruitshorts/core/data/repositories/catalog_repository.dart';
|
import 'package:fruitshorts/core/data/repositories/catalog_repository.dart';
|
||||||
import 'package:fruitshorts/core/data/repositories/watch_history_repository.dart';
|
import 'package:fruitshorts/core/data/repositories/watch_history_repository.dart';
|
||||||
|
import 'package:fruitshorts/core/data/remote/api_client.dart';
|
||||||
import 'package:fruitshorts/core/data/services/watch_records_controller.dart';
|
import 'package:fruitshorts/core/data/services/watch_records_controller.dart';
|
||||||
|
|
||||||
WatchProgress _progress(
|
WatchProgress _progress(
|
||||||
@@ -153,6 +154,19 @@ void main() {
|
|||||||
expect(controller.error, 'progressError');
|
expect(controller.error, 'progressError');
|
||||||
expect(controller.records, isEmpty);
|
expect(controller.records, isEmpty);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('should_preserve_backend_message_when_recent_request_fails', () async {
|
||||||
|
final history = ControlledWatchHistory([])
|
||||||
|
..recentError = const ApiException(100000, '账号不存在,请先注册');
|
||||||
|
final controller = WatchRecordsController(
|
||||||
|
history: history,
|
||||||
|
catalog: ScriptedWatchCatalog(),
|
||||||
|
);
|
||||||
|
|
||||||
|
await controller.load();
|
||||||
|
|
||||||
|
expect(controller.error, '账号不存在,请先注册');
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
class ControlledWatchHistory implements WatchHistoryRepository {
|
class ControlledWatchHistory implements WatchHistoryRepository {
|
||||||
@@ -160,6 +174,7 @@ class ControlledWatchHistory implements WatchHistoryRepository {
|
|||||||
|
|
||||||
final List<WatchProgress> items;
|
final List<WatchProgress> items;
|
||||||
bool failRecent = false;
|
bool failRecent = false;
|
||||||
|
Object? recentError;
|
||||||
bool failRemove = false;
|
bool failRemove = false;
|
||||||
bool failClear = false;
|
bool failClear = false;
|
||||||
Completer<void>? removeGate;
|
Completer<void>? removeGate;
|
||||||
@@ -178,6 +193,7 @@ class ControlledWatchHistory implements WatchHistoryRepository {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Future<List<WatchProgress>> recent({int limit = 20}) async {
|
Future<List<WatchProgress>> recent({int limit = 20}) async {
|
||||||
|
if (recentError != null) throw recentError!;
|
||||||
if (failRecent) throw StateError('recent unavailable');
|
if (failRecent) throw StateError('recent unavailable');
|
||||||
return items.take(limit).toList();
|
return items.take(limit).toList();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -126,6 +126,23 @@ void main() {
|
|||||||
expect(find.text('No watch history yet.'), findsOneWidget);
|
expect(find.text('No watch history yet.'), findsOneWidget);
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
|
testWidgets('should_show_backend_message_when_recent_request_fails', (
|
||||||
|
tester,
|
||||||
|
) async {
|
||||||
|
final auth = await _auth(signedIn: true);
|
||||||
|
final history = _FailingRecentHistory();
|
||||||
|
|
||||||
|
await _pumpPage(
|
||||||
|
tester,
|
||||||
|
auth: auth,
|
||||||
|
history: history,
|
||||||
|
catalog: FakeCatalogRepository(),
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(find.text('账号不存在,请先注册'), findsOneWidget);
|
||||||
|
expect(find.byKey(const Key('watch-records-retry')), findsOneWidget);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
WatchProgress _progress(String dramaId) => WatchProgress(
|
WatchProgress _progress(String dramaId) => WatchProgress(
|
||||||
@@ -202,6 +219,13 @@ class CountingWatchHistory extends InMemoryWatchHistoryRepository {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class _FailingRecentHistory extends InMemoryWatchHistoryRepository {
|
||||||
|
@override
|
||||||
|
Future<List<WatchProgress>> recent({int limit = 20}) async {
|
||||||
|
throw const ApiException(100000, '账号不存在,请先注册');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
class _FailingWatchCatalog implements CatalogRepository {
|
class _FailingWatchCatalog implements CatalogRepository {
|
||||||
final _delegate = FakeCatalogRepository();
|
final _delegate = FakeCatalogRepository();
|
||||||
|
|
||||||
|
|||||||
Reference in new issue
Block a user