Skip to content

Commit 3c1ec73

Browse files
committed
feat(shared): add content limitation bottom sheet
- Implement ContentLimitationBottomSheet widget to handle different user limitation statuses - Add private widgets for anonymous, standard, and premium user limit views - Include navigation to account linking and manage followed items pages - Implement localization placeholders for titles, bodies, and buttons - Create _BaseLimitView for shared layout among different limit views
1 parent 93a8449 commit 3c1ec73

File tree

1 file changed

+173
-0
lines changed

1 file changed

+173
-0
lines changed
Lines changed: 173 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,173 @@
1+
import 'package:flutter/material.dart';
2+
import 'package:flutter_news_app_mobile_client_full_source_code/l10n/l10n.dart';
3+
import 'package:flutter_news_app_mobile_client_full_source_code/router/routes.dart';
4+
import 'package:flutter_news_app_mobile_client_full_source_code/shared/services/content_limitation_service.dart';
5+
import 'package:go_router/go_router.dart';
6+
import 'package:ui_kit/ui_kit.dart';
7+
8+
/// {@template content_limitation_bottom_sheet}
9+
/// A bottom sheet that informs the user about content limitations and provides
10+
/// relevant actions based on their status.
11+
/// {@endtemplate}
12+
class ContentLimitationBottomSheet extends StatelessWidget {
13+
/// {@macro content_limitation_bottom_sheet}
14+
const ContentLimitationBottomSheet({required this.status, super.key});
15+
16+
/// The limitation status that determines the content of the bottom sheet.
17+
final LimitationStatus status;
18+
19+
@override
20+
Widget build(BuildContext context) {
21+
// Use a switch to build the appropriate view based on the status.
22+
// Each case returns a dedicated private widget for clarity.
23+
switch (status) {
24+
case LimitationStatus.anonymousLimitReached:
25+
return const _AnonymousLimitView();
26+
case LimitationStatus.standardUserLimitReached:
27+
return const _StandardUserLimitView();
28+
case LimitationStatus.premiumUserLimitReached:
29+
return const _PremiumUserLimitView();
30+
case LimitationStatus.allowed:
31+
// If the action is allowed, no UI is needed.
32+
return const SizedBox.shrink();
33+
}
34+
}
35+
}
36+
37+
/// A private widget to show when an anonymous user hits a limit.
38+
class _AnonymousLimitView extends StatelessWidget {
39+
const _AnonymousLimitView();
40+
41+
@override
42+
Widget build(BuildContext context) {
43+
final l10n = context.l10n;
44+
final theme = Theme.of(context);
45+
46+
return _BaseLimitView(
47+
icon: Icons.person_add_alt_1_outlined,
48+
// TODO(fulleni): Add l10n.anonymousLimitTitle
49+
title: 'Sign in to Save More',
50+
// TODO(fulleni): Add l10n.anonymousLimitBody
51+
body:
52+
'Create a free account to save and follow unlimited topics, sources, and countries.',
53+
child: ElevatedButton(
54+
onPressed: () {
55+
// Pop the bottom sheet first.
56+
Navigator.of(context).pop();
57+
// Then navigate to the account linking page.
58+
context.pushNamed(Routes.accountLinkingName);
59+
},
60+
style: ElevatedButton.styleFrom(
61+
minimumSize: const Size.fromHeight(AppSpacing.xxl + AppSpacing.sm),
62+
),
63+
// TODO(fulleni): Add l10n.anonymousLimitButton
64+
child: const Text('Sign In & Link Account'),
65+
),
66+
);
67+
}
68+
}
69+
70+
/// A private widget to show when a standard (free) user hits a limit.
71+
class _StandardUserLimitView extends StatelessWidget {
72+
const _StandardUserLimitView();
73+
74+
@override
75+
Widget build(BuildContext context) {
76+
final l10n = context.l10n;
77+
final theme = Theme.of(context);
78+
79+
return _BaseLimitView(
80+
icon: Icons.workspace_premium_outlined,
81+
// TODO(fulleni): Add l10n.standardLimitTitle
82+
title: 'Unlock Unlimited Access',
83+
// TODO(fulleni): Add l10n.standardLimitBody
84+
body:
85+
"You've reached your limit for the free plan. Upgrade to save and follow more.",
86+
child: ElevatedButton(
87+
// TODO(fulleni): Implement account upgrade flow.
88+
// The upgrade flow is not yet implemented, so the button is disabled.
89+
onPressed: null,
90+
style: ElevatedButton.styleFrom(
91+
minimumSize: const Size.fromHeight(AppSpacing.xxl + AppSpacing.sm),
92+
),
93+
// TODO(fulleni): Add l10n.standardLimitButton
94+
child: const Text('Upgrade to Premium'),
95+
),
96+
);
97+
}
98+
}
99+
100+
/// A private widget to show when a premium user hits a limit.
101+
class _PremiumUserLimitView extends StatelessWidget {
102+
const _PremiumUserLimitView();
103+
104+
@override
105+
Widget build(BuildContext context) {
106+
final l10n = context.l10n;
107+
final theme = Theme.of(context);
108+
109+
return _BaseLimitView(
110+
icon: Icons.inventory_2_outlined,
111+
// TODO(fulleni): Add l10n.premiumLimitTitle
112+
title: "You've Reached the Limit",
113+
// TODO(fulleni): Add l10n.premiumLimitBody
114+
body:
115+
'To add new items, please review and manage your existing saved and followed content.',
116+
child: ElevatedButton(
117+
onPressed: () {
118+
// Pop the bottom sheet first.
119+
Navigator.of(context).pop();
120+
// Then navigate to the page for managing followed items.
121+
context.goNamed(Routes.manageFollowedItemsName);
122+
},
123+
style: ElevatedButton.styleFrom(
124+
minimumSize: const Size.fromHeight(AppSpacing.xxl + AppSpacing.sm),
125+
),
126+
// TODO(fulleni): Add l10n.premiumLimitButton
127+
child: const Text('Manage My Content'),
128+
),
129+
);
130+
}
131+
}
132+
133+
/// A base layout for the content limitation views to reduce duplication.
134+
class _BaseLimitView extends StatelessWidget {
135+
const _BaseLimitView({
136+
required this.icon,
137+
required this.title,
138+
required this.body,
139+
required this.child,
140+
});
141+
142+
final IconData icon;
143+
final String title;
144+
final String body;
145+
final Widget child;
146+
147+
@override
148+
Widget build(BuildContext context) {
149+
return Padding(
150+
padding: const EdgeInsets.all(AppSpacing.paddingLarge),
151+
child: Column(
152+
mainAxisSize: MainAxisSize.min,
153+
children: [
154+
Icon(icon, size: AppSpacing.xxl * 1.5, color: Colors.blue),
155+
const SizedBox(height: AppSpacing.lg),
156+
Text(
157+
title,
158+
style: const TextStyle(fontSize: 22),
159+
textAlign: TextAlign.center,
160+
),
161+
const SizedBox(height: AppSpacing.md),
162+
Text(
163+
body,
164+
style: const TextStyle(fontSize: 16),
165+
textAlign: TextAlign.center,
166+
),
167+
const SizedBox(height: AppSpacing.lg),
168+
child,
169+
],
170+
),
171+
);
172+
}
173+
}

0 commit comments

Comments
 (0)