merge: 增加瀑布流与下拉刷新
This commit is contained in:
commit
75dacf22ab
3 files changed
+256
-151
No files matched your search
@@ -4,6 +4,7 @@ import 'package:flutter/material.dart';
|
|||||||
import 'package:flutter/services.dart';
|
import 'package:flutter/services.dart';
|
||||||
import 'package:provider/provider.dart';
|
import 'package:provider/provider.dart';
|
||||||
|
|
||||||
|
import '../../core/config/app_config.dart';
|
||||||
import '../../core/data/models/drama.dart';
|
import '../../core/data/models/drama.dart';
|
||||||
import '../../core/data/models/watch_progress.dart';
|
import '../../core/data/models/watch_progress.dart';
|
||||||
import '../../core/data/repositories/watch_history_repository.dart';
|
import '../../core/data/repositories/watch_history_repository.dart';
|
||||||
@@ -135,90 +136,99 @@ class _DiscoverPageState extends State<DiscoverPage> {
|
|||||||
CategoryController categories,
|
CategoryController categories,
|
||||||
List<Drama> allDramas,
|
List<Drama> allDramas,
|
||||||
List<Drama> visible,
|
List<Drama> visible,
|
||||||
) => CustomScrollView(
|
) => RefreshIndicator(
|
||||||
controller: logic.scroll,
|
key: const Key('discover-refresh'),
|
||||||
slivers: [
|
color: AppColors.blue,
|
||||||
SliverToBoxAdapter(child: _search()),
|
backgroundColor: AppColors.card,
|
||||||
SliverToBoxAdapter(
|
onRefresh: () async {
|
||||||
child: Padding(
|
await Future.wait([categories.load(), logic.refreshResume()]);
|
||||||
padding: const EdgeInsets.fromLTRB(0, 14, 0, 12),
|
},
|
||||||
child: _categories(l10n, categories),
|
child: CustomScrollView(
|
||||||
|
controller: logic.scroll,
|
||||||
|
physics: const AlwaysScrollableScrollPhysics(),
|
||||||
|
slivers: [
|
||||||
|
SliverToBoxAdapter(child: _search()),
|
||||||
|
SliverToBoxAdapter(
|
||||||
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.fromLTRB(0, 14, 0, 12),
|
||||||
|
child: _categories(l10n, categories),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
if (logic.resumeRecord != null && logic.resumeDrama(allDramas) != null)
|
||||||
if (logic.resumeRecord != null && logic.resumeDrama(allDramas) != null)
|
SliverToBoxAdapter(
|
||||||
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.fromLTRB(16, 0, 16, 14),
|
||||||
|
child: _continueCard(
|
||||||
|
l10n,
|
||||||
|
logic.resumeDrama(allDramas)!,
|
||||||
|
logic.resumeRecord!,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
SliverToBoxAdapter(
|
SliverToBoxAdapter(
|
||||||
child: Padding(
|
child: Padding(
|
||||||
padding: const EdgeInsets.fromLTRB(16, 0, 16, 14),
|
padding: const EdgeInsets.fromLTRB(16, 0, 16, 14),
|
||||||
child: _continueCard(
|
child: _quickActions(l10n, categories),
|
||||||
l10n,
|
),
|
||||||
logic.resumeDrama(allDramas)!,
|
),
|
||||||
logic.resumeRecord!,
|
SliverToBoxAdapter(
|
||||||
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.fromLTRB(14, 2, 14, 10),
|
||||||
|
child: Row(
|
||||||
|
children: [
|
||||||
|
Container(
|
||||||
|
width: 3,
|
||||||
|
height: 18,
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: AppColors.blue,
|
||||||
|
borderRadius: BorderRadius.circular(2),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(width: 6),
|
||||||
|
Expanded(
|
||||||
|
child: Text(
|
||||||
|
l10n.trendingMicroDramas,
|
||||||
|
maxLines: 1,
|
||||||
|
overflow: TextOverflow.ellipsis,
|
||||||
|
style: const TextStyle(
|
||||||
|
color: AppColors.onSurface,
|
||||||
|
fontSize: AppDimens.previewSectionTitle,
|
||||||
|
fontWeight: FontWeight.w800,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
TextButton(
|
||||||
|
onPressed: () =>
|
||||||
|
categories.selectCode(CategoryController.allCode),
|
||||||
|
style: TextButton.styleFrom(
|
||||||
|
foregroundColor: AppColors.accent,
|
||||||
|
minimumSize: const Size(44, 44),
|
||||||
|
padding: EdgeInsets.zero,
|
||||||
|
),
|
||||||
|
child: Text(
|
||||||
|
'${l10n.all} ›',
|
||||||
|
style: const TextStyle(fontSize: 10),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
SliverToBoxAdapter(
|
_HomeDramaGrid(
|
||||||
child: Padding(
|
l10n: l10n,
|
||||||
padding: const EdgeInsets.fromLTRB(16, 0, 16, 14),
|
categories: categories,
|
||||||
child: _quickActions(l10n, categories),
|
dramas: visible,
|
||||||
),
|
searching: logic.search.text.trim().isNotEmpty,
|
||||||
),
|
onOpen: (drama) => logic.openCard(
|
||||||
SliverToBoxAdapter(
|
context,
|
||||||
child: Padding(
|
drama,
|
||||||
padding: const EdgeInsets.fromLTRB(14, 2, 14, 10),
|
embedded: widget.embedded,
|
||||||
child: Row(
|
onDramaSelected: widget.onDramaSelected,
|
||||||
children: [
|
onAction: widget.onAction,
|
||||||
Container(
|
|
||||||
width: 3,
|
|
||||||
height: 18,
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
color: AppColors.blue,
|
|
||||||
borderRadius: BorderRadius.circular(2),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
const SizedBox(width: 6),
|
|
||||||
Expanded(
|
|
||||||
child: Text(
|
|
||||||
l10n.trendingMicroDramas,
|
|
||||||
maxLines: 1,
|
|
||||||
overflow: TextOverflow.ellipsis,
|
|
||||||
style: const TextStyle(
|
|
||||||
color: AppColors.onSurface,
|
|
||||||
fontSize: AppDimens.previewSectionTitle,
|
|
||||||
fontWeight: FontWeight.w800,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
TextButton(
|
|
||||||
onPressed: () =>
|
|
||||||
categories.selectCode(CategoryController.allCode),
|
|
||||||
style: TextButton.styleFrom(
|
|
||||||
foregroundColor: AppColors.accent,
|
|
||||||
minimumSize: const Size(44, 44),
|
|
||||||
padding: EdgeInsets.zero,
|
|
||||||
),
|
|
||||||
child: Text(
|
|
||||||
'${l10n.all} ›',
|
|
||||||
style: const TextStyle(fontSize: 10),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
],
|
||||||
_HomeDramaGrid(
|
),
|
||||||
l10n: l10n,
|
|
||||||
categories: categories,
|
|
||||||
dramas: visible,
|
|
||||||
searching: logic.search.text.trim().isNotEmpty,
|
|
||||||
onOpen: (drama) => logic.openCard(
|
|
||||||
context,
|
|
||||||
drama,
|
|
||||||
embedded: widget.embedded,
|
|
||||||
onDramaSelected: widget.onDramaSelected,
|
|
||||||
onAction: widget.onAction,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
);
|
);
|
||||||
|
|
||||||
Widget _header(AppLocalizations l10n) {
|
Widget _header(AppLocalizations l10n) {
|
||||||
|
|||||||
@@ -55,33 +55,44 @@ class _HomeDramaGrid extends StatelessWidget {
|
|||||||
}
|
}
|
||||||
return SliverMainAxisGroup(
|
return SliverMainAxisGroup(
|
||||||
slivers: [
|
slivers: [
|
||||||
_dramaGrid(dramas),
|
SliverToBoxAdapter(child: _masonry(dramas)),
|
||||||
const SliverToBoxAdapter(child: SizedBox(height: 24)),
|
const SliverToBoxAdapter(child: SizedBox(height: 24)),
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget _dramaGrid(List<Drama> items) => SliverPadding(
|
Widget _masonry(List<Drama> items) {
|
||||||
key: const Key('home-all-dramas-section'),
|
return Padding(
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 14),
|
key: const Key('home-all-dramas-section'),
|
||||||
sliver: SliverGrid(
|
padding: const EdgeInsets.symmetric(horizontal: 14),
|
||||||
key: const Key('home-all-dramas-grid'),
|
child: Row(
|
||||||
gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
|
key: const Key('home-all-dramas-grid'),
|
||||||
crossAxisCount: 2,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
mainAxisSpacing: 10,
|
children: [
|
||||||
crossAxisSpacing: 10,
|
Expanded(child: _column(items, 0)),
|
||||||
mainAxisExtent: 164,
|
const SizedBox(width: 10),
|
||||||
|
Expanded(child: _column(items, 1)),
|
||||||
|
],
|
||||||
),
|
),
|
||||||
delegate: SliverChildBuilderDelegate(
|
);
|
||||||
(context, index) => _HomeDramaCard(
|
}
|
||||||
|
|
||||||
|
Widget _column(List<Drama> items, int start) {
|
||||||
|
final children = <Widget>[];
|
||||||
|
for (var index = start; index < items.length; index += 2) {
|
||||||
|
children.add(
|
||||||
|
_HomeDramaCard(
|
||||||
l10n: l10n,
|
l10n: l10n,
|
||||||
drama: items[index],
|
drama: items[index],
|
||||||
onTap: () => onOpen(items[index]),
|
onTap: () => onOpen(items[index]),
|
||||||
),
|
),
|
||||||
childCount: items.length,
|
);
|
||||||
),
|
if (index + 2 < items.length) {
|
||||||
),
|
children.add(const SizedBox(height: 10));
|
||||||
);
|
}
|
||||||
|
}
|
||||||
|
return Column(children: children);
|
||||||
|
}
|
||||||
|
|
||||||
Widget _fill(Widget child) =>
|
Widget _fill(Widget child) =>
|
||||||
SliverFillRemaining(hasScrollBody: false, child: child);
|
SliverFillRemaining(hasScrollBody: false, child: child);
|
||||||
@@ -111,47 +122,36 @@ class _HomeDramaCard extends StatelessWidget {
|
|||||||
child: Column(
|
child: Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
SizedBox(
|
ClipRRect(
|
||||||
key: Key('home-drama-cover-${drama.id}'),
|
key: Key('home-drama-cover-${drama.id}'),
|
||||||
height: 120,
|
borderRadius: BorderRadius.circular(10),
|
||||||
child: ClipRRect(
|
child: Stack(
|
||||||
borderRadius: BorderRadius.circular(10),
|
children: [
|
||||||
child: Stack(
|
_DiscoverCoverImage(path: drama.posterUrl),
|
||||||
fit: StackFit.expand,
|
if (free)
|
||||||
children: [
|
Positioned(
|
||||||
ColoredBox(
|
top: 6,
|
||||||
color: AppColors.card,
|
left: 6,
|
||||||
child: RemoteImage(
|
child: Container(
|
||||||
drama.posterUrl,
|
padding: const EdgeInsets.symmetric(
|
||||||
fit: BoxFit.contain,
|
horizontal: 6,
|
||||||
showFallbackIcon: true,
|
vertical: 2,
|
||||||
),
|
),
|
||||||
),
|
decoration: BoxDecoration(
|
||||||
if (free)
|
color: AppColors.blue,
|
||||||
Positioned(
|
borderRadius: BorderRadius.circular(6),
|
||||||
top: 6,
|
),
|
||||||
left: 6,
|
child: Text(
|
||||||
child: Container(
|
l10n.free,
|
||||||
padding: const EdgeInsets.symmetric(
|
style: const TextStyle(
|
||||||
horizontal: 6,
|
color: AppColors.onPrimary,
|
||||||
vertical: 2,
|
fontSize: 8,
|
||||||
),
|
fontWeight: FontWeight.w800,
|
||||||
decoration: BoxDecoration(
|
|
||||||
color: AppColors.blue,
|
|
||||||
borderRadius: BorderRadius.circular(6),
|
|
||||||
),
|
|
||||||
child: Text(
|
|
||||||
l10n.free,
|
|
||||||
style: const TextStyle(
|
|
||||||
color: AppColors.onPrimary,
|
|
||||||
fontSize: 8,
|
|
||||||
fontWeight: FontWeight.w800,
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
),
|
||||||
),
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
const SizedBox(height: 5),
|
const SizedBox(height: 5),
|
||||||
@@ -179,3 +179,41 @@ class _HomeDramaCard extends StatelessWidget {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class _DiscoverCoverImage extends StatelessWidget {
|
||||||
|
const _DiscoverCoverImage({required this.path});
|
||||||
|
|
||||||
|
final String path;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
final url = AppConfig.mediaUrl(path);
|
||||||
|
if (url.isEmpty) return _placeholder();
|
||||||
|
return Image.network(
|
||||||
|
url,
|
||||||
|
width: double.infinity,
|
||||||
|
fit: BoxFit.fitWidth,
|
||||||
|
loadingBuilder: (context, child, progress) {
|
||||||
|
if (progress == null) return child;
|
||||||
|
return _placeholder();
|
||||||
|
},
|
||||||
|
errorBuilder: (_, _, _) => _placeholder(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _placeholder() {
|
||||||
|
return AspectRatio(
|
||||||
|
aspectRatio: 3 / 4,
|
||||||
|
child: ColoredBox(
|
||||||
|
color: AppColors.card,
|
||||||
|
child: const Center(
|
||||||
|
child: Icon(
|
||||||
|
Icons.image_not_supported_outlined,
|
||||||
|
color: AppColors.muted,
|
||||||
|
size: 32,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -12,7 +12,6 @@ import 'package:fruitshorts/core/data/repositories/category_repository.dart';
|
|||||||
import 'package:fruitshorts/core/data/repositories/favorite_repository.dart';
|
import 'package:fruitshorts/core/data/repositories/favorite_repository.dart';
|
||||||
import 'package:fruitshorts/core/data/repositories/playback_repository.dart';
|
import 'package:fruitshorts/core/data/repositories/playback_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/widgets/remote_image.dart';
|
|
||||||
|
|
||||||
import '../support/fake_catalog.dart';
|
import '../support/fake_catalog.dart';
|
||||||
import '../support/fake_interaction_repositories.dart';
|
import '../support/fake_interaction_repositories.dart';
|
||||||
@@ -26,7 +25,7 @@ void main() {
|
|||||||
});
|
});
|
||||||
|
|
||||||
testWidgets(
|
testWidgets(
|
||||||
'should_show_all_dramas_in_two_column_sliver_grid_without_continue_watching',
|
'should_show_all_dramas_in_two_column_masonry_without_continue_watching',
|
||||||
(tester) async {
|
(tester) async {
|
||||||
tester.view.physicalSize = const Size(390, 844);
|
tester.view.physicalSize = const Size(390, 844);
|
||||||
tester.view.devicePixelRatio = 1;
|
tester.view.devicePixelRatio = 1;
|
||||||
@@ -57,41 +56,48 @@ void main() {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
expect(find.text('For You'), findsNothing);
|
expect(find.text('For You'), findsNothing);
|
||||||
final grid = tester.widget<SliverGrid>(
|
final masonry = tester.widget<Row>(
|
||||||
find.byKey(const Key('home-all-dramas-grid')),
|
find.byKey(const Key('home-all-dramas-grid')),
|
||||||
);
|
);
|
||||||
final delegate =
|
expect(masonry.children, hasLength(3));
|
||||||
grid.gridDelegate as SliverGridDelegateWithFixedCrossAxisCount;
|
expect((masonry.children[1] as SizedBox).width, 10);
|
||||||
expect(delegate.crossAxisCount, 2);
|
|
||||||
expect(delegate.crossAxisSpacing, 10);
|
|
||||||
expect(delegate.mainAxisSpacing, 10);
|
|
||||||
expect(delegate.mainAxisExtent, 164);
|
|
||||||
expect(
|
expect(
|
||||||
tester
|
tester
|
||||||
.widget<SliverPadding>(
|
.widget<Padding>(find.byKey(const Key('home-all-dramas-section')))
|
||||||
find.byKey(const Key('home-all-dramas-section')),
|
|
||||||
)
|
|
||||||
.padding,
|
.padding,
|
||||||
const EdgeInsets.symmetric(horizontal: 14),
|
const EdgeInsets.symmetric(horizontal: 14),
|
||||||
);
|
);
|
||||||
expect(find.byType(GridView), findsNothing);
|
expect(find.byType(GridView), findsNothing);
|
||||||
expect(find.byType(CustomScrollView), findsOneWidget);
|
expect(find.byType(CustomScrollView), findsOneWidget);
|
||||||
expect(find.byType(RemoteImage), findsWidgets);
|
final signalCard = find.byKey(
|
||||||
|
const Key('home-drama-card-the-signal-demo'),
|
||||||
expect(
|
|
||||||
tester
|
|
||||||
.getSize(find.byKey(const Key('home-drama-cover-city-lights-demo')))
|
|
||||||
.height,
|
|
||||||
120,
|
|
||||||
);
|
);
|
||||||
|
final cityCard = find.byKey(
|
||||||
|
const Key('home-drama-card-city-lights-demo'),
|
||||||
|
);
|
||||||
|
final vengeanceCard = find.byKey(
|
||||||
|
const Key('home-drama-card-vengeance-demo'),
|
||||||
|
);
|
||||||
|
expect(tester.getTopLeft(signalCard).dy, tester.getTopLeft(cityCard).dy);
|
||||||
|
expect(
|
||||||
|
tester.getTopLeft(vengeanceCard).dx,
|
||||||
|
tester.getTopLeft(signalCard).dx,
|
||||||
|
);
|
||||||
|
expect(
|
||||||
|
tester.getTopLeft(vengeanceCard).dy -
|
||||||
|
tester.getBottomLeft(signalCard).dy,
|
||||||
|
closeTo(10, 1),
|
||||||
|
);
|
||||||
|
|
||||||
final cover = find.byKey(const Key('home-drama-cover-city-lights-demo'));
|
final cover = find.byKey(const Key('home-drama-cover-city-lights-demo'));
|
||||||
|
expect(tester.getSize(cover).height, greaterThan(120));
|
||||||
expect(
|
expect(
|
||||||
tester
|
tester
|
||||||
.widget<RemoteImage>(
|
.widget<Image>(
|
||||||
find.descendant(of: cover, matching: find.byType(RemoteImage)),
|
find.descendant(of: cover, matching: find.byType(Image)),
|
||||||
)
|
)
|
||||||
.fit,
|
.fit,
|
||||||
BoxFit.contain,
|
BoxFit.fitWidth,
|
||||||
);
|
);
|
||||||
expect(
|
expect(
|
||||||
find.descendant(
|
find.descendant(
|
||||||
@@ -104,6 +110,14 @@ void main() {
|
|||||||
find.byKey(const Key('home-drama-title-city-lights-demo')),
|
find.byKey(const Key('home-drama-title-city-lights-demo')),
|
||||||
findsOneWidget,
|
findsOneWidget,
|
||||||
);
|
);
|
||||||
|
expect(
|
||||||
|
tester
|
||||||
|
.getTopLeft(
|
||||||
|
find.byKey(const Key('home-drama-title-city-lights-demo')),
|
||||||
|
)
|
||||||
|
.dy,
|
||||||
|
greaterThan(tester.getBottomLeft(cover).dy),
|
||||||
|
);
|
||||||
expect(
|
expect(
|
||||||
tester
|
tester
|
||||||
.widget<Text>(
|
.widget<Text>(
|
||||||
@@ -346,6 +360,29 @@ void main() {
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
testWidgets('should_refresh_feed_when_pulled_down', (tester) async {
|
||||||
|
tester.view.physicalSize = const Size(390, 844);
|
||||||
|
tester.view.devicePixelRatio = 1;
|
||||||
|
addTearDown(tester.view.resetPhysicalSize);
|
||||||
|
addTearDown(tester.view.resetDevicePixelRatio);
|
||||||
|
|
||||||
|
final counter = _CountingCategoryRepository(
|
||||||
|
_GridCategoryRepository(discoverFeed: _tallDiscoverFeed),
|
||||||
|
);
|
||||||
|
await _pumpApp(tester, categoryRepository: counter);
|
||||||
|
await tester.tap(find.byKey(const Key('tab-discover')));
|
||||||
|
await tester.pumpAndSettle();
|
||||||
|
|
||||||
|
final before = counter.categoryLoads + counter.feedLoads;
|
||||||
|
await tester.drag(find.byType(CustomScrollView), const Offset(0, 320));
|
||||||
|
await tester.pump();
|
||||||
|
await tester.pump(const Duration(seconds: 1));
|
||||||
|
await tester.pumpAndSettle();
|
||||||
|
|
||||||
|
expect(counter.categoryLoads + counter.feedLoads, greaterThan(before));
|
||||||
|
expect(find.byType(RefreshProgressIndicator), findsNothing);
|
||||||
|
});
|
||||||
|
|
||||||
testWidgets('should_show_continue_watching_only_when_a_record_exists', (
|
testWidgets('should_show_continue_watching_only_when_a_record_exists', (
|
||||||
tester,
|
tester,
|
||||||
) async {
|
) async {
|
||||||
@@ -469,3 +506,23 @@ final _tallDiscoverFeed = HomeFeed(
|
|||||||
),
|
),
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
|
|
||||||
|
class _CountingCategoryRepository implements CategoryRepository {
|
||||||
|
_CountingCategoryRepository(this.delegate);
|
||||||
|
|
||||||
|
final CategoryRepository delegate;
|
||||||
|
int categoryLoads = 0;
|
||||||
|
int feedLoads = 0;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<List<ContentCategory>> fetchCategories() {
|
||||||
|
categoryLoads++;
|
||||||
|
return delegate.fetchCategories();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<HomeFeed> fetchDiscoverFeed() {
|
||||||
|
feedLoads++;
|
||||||
|
return delegate.fetchDiscoverFeed();
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in new issue
Block a user