Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
59 commits
Select commit Hold shift + click to select a range
285d582
feat(l10n): add new analytics localization strings
fulleni Dec 21, 2025
a447baa
build(serialization): sync
fulleni Dec 21, 2025
3b26fc9
feat(analytics): implement analytics service and data repositories
fulleni Dec 21, 2025
de4d716
feat(analytics): add analytics service to app providers
fulleni Dec 21, 2025
110bce4
feat(shared): add AnalyticsService for managing analytics data
fulleni Dec 21, 2025
4824e6a
feat(ui_kit): add AnalyticsCardShell widget
fulleni Dec 21, 2025
c308477
feat(analytics): add analytics card slot widget
fulleni Dec 21, 2025
ef6d255
feat(analytics): add chart card widget
fulleni Dec 21, 2025
aaa05fd
feat(analytics): add KPI card widget
fulleni Dec 21, 2025
caab043
chore: barrels
fulleni Dec 21, 2025
8788766
feat(analytics): add ranked list card widget
fulleni Dec 21, 2025
118ed27
feat(analytics): add ranked list card support to AnalyticsCardSlot
fulleni Dec 21, 2025
b8cf680
feat(overview): add OverviewBloc to the app state
fulleni Dec 21, 2025
d2a0a54
feat(user_management): add analytics dashboard to users page
fulleni Dec 21, 2025
7b4a586
feat(overview): implement main dashboard layout and KPIs
fulleni Dec 21, 2025
16b734f
feat(analytics): add AnalyticsDashboardStrip widget
fulleni Dec 21, 2025
b0e012a
refactor(user_management): replace custom analytics dashboard layout …
fulleni Dec 21, 2025
12f0e84
build(dependencies): add fl_chart dependency
fulleni Dec 21, 2025
d6abf80
feat(community_management): add analytics dashboard strip to app revi…
fulleni Dec 21, 2025
8cc0122
feat(community_management): add analytics dashboard strip to engageme…
fulleni Dec 21, 2025
3799881
feat(community_management): add analytics dashboard strip to reports …
fulleni Dec 21, 2025
8de276d
feat(content-management): add analytics dashboard strip to headlines …
fulleni Dec 21, 2025
e95609c
feat(content-management): add analytics dashboard strip to sources page
fulleni Dec 21, 2025
65bff97
feat(content_management): add analytics dashboard strip to topics page
fulleni Dec 21, 2025
6d3af8f
chore(deps): update core dependency and fl_chart version
fulleni Dec 21, 2025
de54383
refactor(app): remove unused OverviewBloc
fulleni Dec 21, 2025
652891f
perf(analytics): ensure in-flight maps are updated after async operat…
fulleni Dec 21, 2025
a8b26f7
feat(analytics): replace custom chart with fl_chart package
fulleni Dec 21, 2025
06e164e
feat(analytics): enhance AnalyticsCardShell with slot and time frame …
fulleni Dec 21, 2025
909acb5
refactor(analytics): upgrade RankedListCard to AnalyticsCardShell
fulleni Dec 21, 2025
139a037
refactor(analytics): improve chart card usability and appearance
fulleni Dec 21, 2025
d82d2d8
refactor(analytics): revamp KPI card layout and interaction
fulleni Dec 21, 2025
14747b7
refactor(analytics): improve AnalyticsCardSlot widget
fulleni Dec 21, 2025
1f43244
fix(community_management): update layout of analytics dashboard strip
fulleni Dec 21, 2025
8406fad
fix(community_management): update layout of analytics dashboard strip
fulleni Dec 21, 2025
7eb30c9
fix(community_management): update layout of analytics dashboard strip
fulleni Dec 21, 2025
8e91dd6
fix(content_management): update layout of analytics dashboard strip
fulleni Dec 21, 2025
b0e72f3
fix(content_management): update layout of analytics dashboard strip
fulleni Dec 21, 2025
9f1e096
fix(content_management): update layout of analytics dashboard strip
fulleni Dec 21, 2025
056184e
fix(users_management): update layout of analytics dashboard strip
fulleni Dec 21, 2025
ca3b782
refactor(overview): implement responsive layout for dashboard
fulleni Dec 21, 2025
1d3bdeb
feat(localization): add new AR and EN translations for dashboard feat…
fulleni Dec 21, 2025
2e78fe8
build(serialization): sync
fulleni Dec 21, 2025
45018e8
feat(analytics): add localized titles to chart cards
fulleni Dec 21, 2025
dcfaa70
refactor(analytics): improve KPI card layout and localization
fulleni Dec 21, 2025
10a0f65
refactor(analytics): improve ranked list card localization and layout
fulleni Dec 21, 2025
4c1c812
feat(analytics): add horizontal time frame selector position
fulleni Dec 21, 2025
ce8a4dd
refactor(bootstrap): temporarily replace fixture data with sample cha…
fulleni Dec 21, 2025
f13181d
feat(bootstrap): add app constants validation
fulleni Dec 21, 2025
3196adb
refactor(analytics): enhance responsiveness of dashboard strip
fulleni Dec 21, 2025
a34cc1f
feat(const): add new layout and UI constants
fulleni Dec 21, 2025
443986d
refactor: remove redundant SizedBox wrapper
fulleni Dec 21, 2025
349348b
style(analytics): adjust padding for chart card content
fulleni Dec 21, 2025
77682f3
fix(analytics): enable RTL support for chart cards
fulleni Dec 21, 2025
b2d3b77
refactor(bootstrap): use chart cards fixtures
fulleni Dec 21, 2025
ebeef7c
style: format
fulleni Dec 21, 2025
db526a2
build(deps): update core dependency
fulleni Dec 21, 2025
8997676
feat(analytics): add navigation on ranked list item tap
fulleni Dec 21, 2025
a15e358
docs(README): update content and operational intelligence sections
fulleni Dec 21, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 16 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
<p align="center">
<a href="https://flutter-news-app-full-source-code.github.io/flutter-news-app-web-dashboard-full-source-code/"><img src="https://img.shields.io/badge/LIVE_DEMO-VIEW-orange?style=for-the-badge" alt="Live Demo: View"></a>
<a href="https://flutter-news-app-full-source-code.github.io/docs/web-dashboard/local-setup/"><img src="https://img.shields.io/badge/DOCUMENTATION-READ-slategray?style=for-the-badge" alt="Documentation: Read"></a>
<img src="https://img.shields.io/badge/coverage-_%25-red?style=for-the-badge" alt="">
<!-- <img src="https://img.shields.io/badge/coverage-_%25-red?style=for-the-badge" alt=""> -->
</p>
<p align="center">
<a href="LICENSE"><img src="https://img.shields.io/badge/TRIAL_LICENSE-VIEW_TERMS-blue?style=for-the-badge" alt="Trial License: View Terms"></a>
Expand All @@ -22,13 +22,25 @@ This dashboard provides a complete, production-ready command center for your ent

Explore the high-level domains below to see how.

<details>
<summary><strong>📊 Operational Intelligence</strong></summary>

### 📈 Dashboard Overview
A centralized command center providing a real-time pulse on your entire news operation.
- **Unified Business Intelligence:** View pre-aggregated metrics that combine user behavior data with operational stats for a holistic performance picture.
- **High-Performance Visualization:** Visualize growth and trends with interactive charts that load instantly, powered by an optimized ETL backend engine.
- **Top Content Ranking:** Instantly identify your highest-performing headlines, sources, and topics to double down on what works.
> **Your Advantage:** Move from reactive management to proactive strategy. The dashboard delivers fast, actionable insights without the latency of direct provider queries, helping you spot trends early and optimize your content strategy.

</details>

<details>
<summary><strong>✍️ Content & Editorial Management</strong></summary>

### 📰 Complete Editorial Control
Manage the entire lifecycle of your content from a single, intuitive interface. This is more than just a database editor; it's a complete content operations hub.
- **Full Content Lifecycle:** Seamlessly draft, publish, edit, archive, and restore all content assets, including headlines, topics, and news sources.
- **At-a-Glance Operational Overview:** A centralized dashboard provides a real-time snapshot of your content ecosystem, including key statistics and shortcuts for common editorial tasks.
- **Contextual Performance Metrics:** Make informed editorial decisions with real-time views, likes, and engagement data integrated directly into your content lists.
> **Your Advantage:** Gain granular control over your entire content pipeline. This centralized system streamlines your editorial workflow, ensures content consistency, and simplifies asset management.

</details>
Expand All @@ -39,6 +51,7 @@ Manage the entire lifecycle of your content from a single, intuitive interface.
### 👥 Granular User & Role Management
Effortlessly manage your entire user base with a dedicated user management system. View all registered users, filter them by email or role, and dynamically adjust their dashboard permissions.
- **Full User Roster:** See a comprehensive list of all users, including their email, app subscription level, and current dashboard role.
- **User Growth Insights:** Track registration trends and active user metrics alongside your user roster to understand audience growth.
- **Dynamic Role Promotion:** Promote trusted users to a "Publisher" role, granting them content management capabilities without full administrative access.
- **Powerful Filtering:** Quickly locate specific users or user segments with multi-faceted filtering by email, app role, and dashboard role.
> **Your Advantage:** Delegate content creation responsibilities securely, build out your editorial team, and maintain a clear overview of all system users and their permissions, all from a single, centralized interface.
Expand All @@ -51,6 +64,7 @@ Effortlessly manage your entire user base with a dedicated user management syste
### 💬 Comprehensive Moderation Hub
Directly manage all user-generated content from a centralized command center. Review, moderate, and act on user interactions to maintain a healthy and constructive community environment.
- **Unified Content Review:** Seamlessly moderate all incoming user engagements (reactions and comments), content reports, and app review feedback from a single, intuitive interface.
- **Community Health Monitoring:** Visualize engagement rates and report resolution times to maintain a healthy community ecosystem.
- **Streamlined Moderation Workflow:** Quickly approve or reject comments, resolve user-submitted reports, and analyze feedback with a consistent set of tools designed for rapid decision-making.
- **Direct User Insight:** Gain a clear, unfiltered view of user sentiment, content issues, and overall satisfaction by directly engaging with their feedback and reports.
> **Your Advantage:** Foster a positive community, protect your brand by quickly addressing problematic content, and gather valuable user insights to improve your content strategy, all from one integrated hub.
Expand Down
14 changes: 4 additions & 10 deletions lib/app/view/app.dart
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,7 @@ import 'package:flutter_news_app_web_dashboard_full_source_code/content_manageme
import 'package:flutter_news_app_web_dashboard_full_source_code/content_management/bloc/sources_filter/sources_filter_bloc.dart';
import 'package:flutter_news_app_web_dashboard_full_source_code/content_management/bloc/topics_filter/topics_filter_bloc.dart';
import 'package:flutter_news_app_web_dashboard_full_source_code/l10n/app_localizations.dart';
// import 'package:flutter_news_app_web_dashboard_full_source_code/overview/bloc/overview_bloc.dart';
import 'package:flutter_news_app_web_dashboard_full_source_code/router/router.dart';
import 'package:flutter_news_app_web_dashboard_full_source_code/shared/services/pending_deletions_service.dart';
import 'package:flutter_news_app_web_dashboard_full_source_code/shared/services/pending_updates_service.dart';
import 'package:flutter_news_app_web_dashboard_full_source_code/shared/shared.dart';
import 'package:flutter_news_app_web_dashboard_full_source_code/user_management/bloc/user_filter/user_filter_bloc.dart';
import 'package:flutter_news_app_web_dashboard_full_source_code/user_management/bloc/user_management_bloc.dart';
Expand All @@ -46,6 +43,7 @@ class App extends StatelessWidget {
required DataRepository<Engagement> engagementsRepository,
required DataRepository<Report> reportsRepository,
required DataRepository<AppReview> appReviewsRepository,
required AnalyticsService analyticsService,
required KVStorageService storageService,
required AppEnvironment environment,
required PendingDeletionsService pendingDeletionsService,
Expand All @@ -64,6 +62,7 @@ class App extends StatelessWidget {
_engagementsRepository = engagementsRepository,
_reportsRepository = reportsRepository,
_appReviewsRepository = appReviewsRepository,
_analyticsService = analyticsService,
_environment = environment,
_pendingDeletionsService = pendingDeletionsService;

Expand All @@ -81,6 +80,7 @@ class App extends StatelessWidget {
final DataRepository<Engagement> _engagementsRepository;
final DataRepository<Report> _reportsRepository;
final DataRepository<AppReview> _appReviewsRepository;
final AnalyticsService _analyticsService;
final KVStorageService _kvStorageService;
final AppEnvironment _environment;

Expand All @@ -104,6 +104,7 @@ class App extends StatelessWidget {
RepositoryProvider.value(value: _engagementsRepository),
RepositoryProvider.value(value: _reportsRepository),
RepositoryProvider.value(value: _appReviewsRepository),
RepositoryProvider.value(value: _analyticsService),
RepositoryProvider.value(value: _kvStorageService),
RepositoryProvider(
create: (context) => const ThrottledFetchingService(),
Expand Down Expand Up @@ -159,13 +160,6 @@ class App extends StatelessWidget {
pendingDeletionsService: context.read<PendingDeletionsService>(),
),
),
// BlocProvider(
// create: (context) => OverviewBloc(
// headlinesRepository: context.read<DataRepository<Headline>>(),
// topicsRepository: context.read<DataRepository<Topic>>(),
// sourcesRepository: context.read<DataRepository<Source>>(),
// ),
// ),
// The UserFilterBloc is provided here to be available for both the
// UserManagementBloc and the UI components.
BlocProvider(create: (_) => UserFilterBloc()),
Expand Down
63 changes: 63 additions & 0 deletions lib/bootstrap.dart
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ import 'package:flutter_news_app_web_dashboard_full_source_code/app/app.dart';
import 'package:flutter_news_app_web_dashboard_full_source_code/app/config/config.dart'
as app_config;
import 'package:flutter_news_app_web_dashboard_full_source_code/bloc_observer.dart';
import 'package:flutter_news_app_web_dashboard_full_source_code/shared/constants/app_constants.dart';
import 'package:flutter_news_app_web_dashboard_full_source_code/shared/services/analytics_service.dart';
import 'package:flutter_news_app_web_dashboard_full_source_code/shared/services/pending_deletions_service.dart';
import 'package:http_client/http_client.dart';
import 'package:kv_storage_shared_preferences/kv_storage_shared_preferences.dart';
Expand All @@ -27,6 +29,9 @@ Future<Widget> bootstrap(
WidgetsFlutterBinding.ensureInitialized();
Bloc.observer = const AppBlocObserver();

// Validate application constants to ensure layout integrity.
AppConstants.validate();

timeago.setLocaleMessages('en', EnTimeagoMessages());
timeago.setLocaleMessages('ar', ArTimeagoMessages());

Expand Down Expand Up @@ -67,6 +72,9 @@ Future<Widget> bootstrap(
DataClient<Engagement> engagementsClient;
DataClient<Report> reportsClient;
DataClient<AppReview> appReviewsClient;
DataClient<KpiCardData> kpiCardsClient;
DataClient<ChartCardData> chartCardsClient;
DataClient<RankedListCardData> rankedListCardsClient;

if (appConfig.environment == app_config.AppEnvironment.demo) {
headlinesClient = DataInMemory<Headline>(
Expand Down Expand Up @@ -142,6 +150,24 @@ Future<Widget> bootstrap(
initialData: getAppReviewsFixturesData(),
logger: Logger('DataInMemory<AppReview>'),
);
kpiCardsClient = DataInMemory<KpiCardData>(
toJson: (i) => i.toJson(),
getId: (i) => i.id.name,
initialData: getKpiCardsFixturesData(),
logger: Logger('DataInMemory<KpiCardData>'),
);
chartCardsClient = DataInMemory<ChartCardData>(
toJson: (i) => i.toJson(),
getId: (i) => i.id.name,
initialData: getChartCardsFixturesData(),
logger: Logger('DataInMemory<ChartCardData>'),
);
rankedListCardsClient = DataInMemory<RankedListCardData>(
toJson: (i) => i.toJson(),
getId: (i) => i.id.name,
initialData: getRankedListCardsFixturesData(),
logger: Logger('DataInMemory<RankedListCardData>'),
);
} else {
headlinesClient = DataApi<Headline>(
httpClient: httpClient!,
Expand Down Expand Up @@ -228,6 +254,27 @@ Future<Widget> bootstrap(
toJson: (appReview) => appReview.toJson(),
logger: Logger('DataApi<AppReview>'),
);
kpiCardsClient = DataApi<KpiCardData>(
httpClient: httpClient,
modelName: 'kpi_card',
fromJson: KpiCardData.fromJson,
toJson: (item) => item.toJson(),
logger: Logger('DataApi<KpiCardData>'),
);
chartCardsClient = DataApi<ChartCardData>(
httpClient: httpClient,
modelName: 'chart_card',
fromJson: ChartCardData.fromJson,
toJson: (item) => item.toJson(),
logger: Logger('DataApi<ChartCardData>'),
);
rankedListCardsClient = DataApi<RankedListCardData>(
httpClient: httpClient,
modelName: 'ranked_list_card',
fromJson: RankedListCardData.fromJson,
toJson: (item) => item.toJson(),
logger: Logger('DataApi<RankedListCardData>'),
);
}

pendingDeletionsService = PendingDeletionsServiceImpl(
Expand Down Expand Up @@ -265,6 +312,21 @@ Future<Widget> bootstrap(
final appReviewsRepository = DataRepository<AppReview>(
dataClient: appReviewsClient,
);
final kpiCardsRepository = DataRepository<KpiCardData>(
dataClient: kpiCardsClient,
);
final chartCardsRepository = DataRepository<ChartCardData>(
dataClient: chartCardsClient,
);
final rankedListCardsRepository = DataRepository<RankedListCardData>(
dataClient: rankedListCardsClient,
);

final analyticsService = AnalyticsService(
kpiRepository: kpiCardsRepository,
chartRepository: chartCardsRepository,
rankedListRepository: rankedListCardsRepository,
);

return App(
authenticationRepository: authenticationRepository,
Expand All @@ -280,6 +342,7 @@ Future<Widget> bootstrap(
engagementsRepository: engagementsRepository,
reportsRepository: reportsRepository,
appReviewsRepository: appReviewsRepository,
analyticsService: analyticsService,
storageService: kvStorage,
environment: environment,
pendingDeletionsService: pendingDeletionsService,
Expand Down
14 changes: 14 additions & 0 deletions lib/community_management/view/app_reviews_page.dart
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import 'package:flutter_news_app_web_dashboard_full_source_code/community_manage
import 'package:flutter_news_app_web_dashboard_full_source_code/l10n/app_localizations.dart';
import 'package:flutter_news_app_web_dashboard_full_source_code/l10n/l10n.dart';
import 'package:flutter_news_app_web_dashboard_full_source_code/shared/extensions/app_review_feedback_extension.dart';
import 'package:flutter_news_app_web_dashboard_full_source_code/shared/widgets/analytics/analytics_dashboard_strip.dart';
import 'package:intl/intl.dart';
import 'package:ui_kit/ui_kit.dart';

Expand Down Expand Up @@ -103,6 +104,19 @@ class _AppReviewsPageState extends State<AppReviewsPage> {

return Column(
children: [
// Analytics Dashboard Strip
const AnalyticsDashboardStrip(
kpiCards: [
KpiCardId.engagementsAppReviewsTotalFeedback,
KpiCardId.engagementsAppReviewsPositiveFeedback,
KpiCardId.engagementsAppReviewsStoreRequests,
],
chartCards: [
ChartCardId.engagementsAppReviewsFeedbackOverTime,
ChartCardId.engagementsAppReviewsPositiveVsNegative,
ChartCardId.engagementsAppReviewsStoreRequestsOverTime,
],
),
if (state.appReviewsStatus == CommunityManagementStatus.loading &&
state.appReviews.isNotEmpty)
const LinearProgressIndicator(),
Expand Down
14 changes: 14 additions & 0 deletions lib/community_management/view/engagements_page.dart
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import 'package:flutter_news_app_web_dashboard_full_source_code/community_manage
import 'package:flutter_news_app_web_dashboard_full_source_code/community_management/widgets/community_action_buttons.dart';
import 'package:flutter_news_app_web_dashboard_full_source_code/l10n/app_localizations.dart';
import 'package:flutter_news_app_web_dashboard_full_source_code/l10n/l10n.dart';
import 'package:flutter_news_app_web_dashboard_full_source_code/shared/widgets/analytics/analytics_dashboard_strip.dart';
import 'package:intl/intl.dart';
import 'package:ui_kit/ui_kit.dart';

Expand Down Expand Up @@ -102,6 +103,19 @@ class _EngagementsPageState extends State<EngagementsPage> {

return Column(
children: [
// Analytics Dashboard Strip
const AnalyticsDashboardStrip(
kpiCards: [
KpiCardId.engagementsTotalReactions,
KpiCardId.engagementsTotalComments,
KpiCardId.engagementsAverageEngagementRate,
],
chartCards: [
ChartCardId.engagementsReactionsOverTime,
ChartCardId.engagementsCommentsOverTime,
ChartCardId.engagementsReactionsByType,
],
),
if (state.engagementsStatus ==
CommunityManagementStatus.loading &&
state.engagements.isNotEmpty)
Expand Down
14 changes: 14 additions & 0 deletions lib/community_management/view/reports_page.dart
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import 'package:flutter_news_app_web_dashboard_full_source_code/community_manage
import 'package:flutter_news_app_web_dashboard_full_source_code/l10n/app_localizations.dart';
import 'package:flutter_news_app_web_dashboard_full_source_code/l10n/l10n.dart';
import 'package:flutter_news_app_web_dashboard_full_source_code/shared/extensions/extensions.dart';
import 'package:flutter_news_app_web_dashboard_full_source_code/shared/widgets/analytics/analytics_dashboard_strip.dart';
import 'package:intl/intl.dart';
import 'package:ui_kit/ui_kit.dart';

Expand Down Expand Up @@ -98,6 +99,19 @@ class _ReportsPageState extends State<ReportsPage> {

return Column(
children: [
// Analytics Dashboard Strip
const AnalyticsDashboardStrip(
kpiCards: [
KpiCardId.engagementsReportsPending,
KpiCardId.engagementsReportsResolved,
KpiCardId.engagementsReportsAverageResolutionTime,
],
chartCards: [
ChartCardId.engagementsReportsSubmittedOverTime,
ChartCardId.engagementsReportsResolutionTimeOverTime,
ChartCardId.engagementsReportsByReason,
],
),
if (state.reportsStatus == CommunityManagementStatus.loading &&
state.reports.isNotEmpty)
const LinearProgressIndicator(),
Expand Down
14 changes: 14 additions & 0 deletions lib/content_management/view/headlines_page.dart
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import 'package:flutter_news_app_web_dashboard_full_source_code/content_manageme
import 'package:flutter_news_app_web_dashboard_full_source_code/l10n/app_localizations.dart';
import 'package:flutter_news_app_web_dashboard_full_source_code/l10n/l10n.dart';
import 'package:flutter_news_app_web_dashboard_full_source_code/router/routes.dart';
import 'package:flutter_news_app_web_dashboard_full_source_code/shared/widgets/analytics/analytics_dashboard_strip.dart';
import 'package:go_router/go_router.dart';
import 'package:intl/intl.dart';
import 'package:ui_kit/ui_kit.dart';
Expand Down Expand Up @@ -114,6 +115,19 @@ class _HeadlinesPageState extends State<HeadlinesPage> {

return Column(
children: [
// Analytics Dashboard Strip
const AnalyticsDashboardStrip(
kpiCards: [
KpiCardId.contentHeadlinesTotalPublished,
KpiCardId.contentHeadlinesTotalViews,
KpiCardId.contentHeadlinesTotalLikes,
],
chartCards: [
ChartCardId.contentHeadlinesViewsOverTime,
ChartCardId.contentHeadlinesLikesOverTime,
ChartCardId.contentHeadlinesViewsByTopic,
],
),
if (state.headlinesStatus == ContentManagementStatus.loading &&
state.headlines.isNotEmpty)
const LinearProgressIndicator(),
Expand Down
Loading
Loading