Skip to content

Commit df67eda

Browse files
committed
feat(entity_details): revamp entity details page
- Improved UI for better readability - Added follow button to app bar - Display description in dialog on mobile
1 parent f106c4f commit df67eda

File tree

1 file changed

+99
-63
lines changed

1 file changed

+99
-63
lines changed

lib/entity_details/view/entity_details_page.dart

Lines changed: 99 additions & 63 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import 'package:flutter/foundation.dart' show kIsWeb; // Added
12
import 'package:flutter/material.dart';
23
import 'package:flutter_bloc/flutter_bloc.dart';
34
import 'package:go_router/go_router.dart'; // Added
@@ -145,81 +146,116 @@ class _EntityDetailsViewState extends State<EntityDetailsView> {
145146
? (state.entity as Source).description
146147
: null;
147148

148-
final String? entityIconUrl = (state.entity is Category && (state.entity as Category).iconUrl != null)
149+
final String? entityIconUrl = (state.entity is Category &&
150+
(state.entity as Category).iconUrl != null)
149151
? (state.entity as Category).iconUrl
150-
: null; // Source model does not have iconUrl
152+
: null;
153+
154+
final followButton = IconButton(
155+
icon: Icon(
156+
state.isFollowing
157+
? Icons.check_circle // Filled when following
158+
: Icons.add_circle_outline,
159+
color: state.isFollowing
160+
? theme.colorScheme.primary
161+
: theme.colorScheme.onSurfaceVariant,
162+
),
163+
tooltip: state.isFollowing
164+
? l10n.unfollowButtonLabel
165+
: l10n.followButtonLabel,
166+
onPressed: () {
167+
context
168+
.read<EntityDetailsBloc>()
169+
.add(const EntityDetailsToggleFollowRequested());
170+
},
171+
);
172+
173+
final Widget appBarTitleWidget = Row(
174+
mainAxisSize: MainAxisSize.min,
175+
children: [
176+
if (entityIconUrl != null)
177+
Padding(
178+
padding: const EdgeInsets.only(right: AppSpacing.sm),
179+
child: ClipRRect(
180+
borderRadius: BorderRadius.circular(AppSpacing.xs),
181+
child: Image.network(
182+
entityIconUrl,
183+
width: kToolbarHeight - 16, // AppBar height minus padding
184+
height: kToolbarHeight - 16,
185+
fit: BoxFit.cover,
186+
errorBuilder: (context, error, stackTrace) =>
187+
const Icon(Icons.category_outlined, size: kToolbarHeight - 20),
188+
),
189+
),
190+
)
191+
else if (state.entityType == EntityType.category)
192+
Padding(
193+
padding: const EdgeInsets.only(right: AppSpacing.sm),
194+
child: Icon(Icons.category_outlined, size: kToolbarHeight - 20, color: theme.colorScheme.onSurface),
195+
)
196+
else if (state.entityType == EntityType.source)
197+
Padding(
198+
padding: const EdgeInsets.only(right: AppSpacing.sm),
199+
child: Icon(Icons.source_outlined, size: kToolbarHeight - 20, color: theme.colorScheme.onSurface),
200+
),
201+
Flexible(
202+
child: Text(
203+
appBarTitle,
204+
overflow: TextOverflow.ellipsis,
205+
),
206+
),
207+
if (description != null && description.isNotEmpty)
208+
Tooltip(
209+
message: description,
210+
child: IconButton(
211+
icon: Icon(Icons.info_outline, color: theme.colorScheme.onSurfaceVariant),
212+
onPressed: () {
213+
// On mobile, show dialog for description
214+
if (!kIsWeb) { // kIsWeb can be used to differentiate behavior
215+
showDialog<void>(
216+
context: context,
217+
builder: (BuildContext dialogContext) {
218+
return AlertDialog(
219+
title: Text(appBarTitle),
220+
content: SingleChildScrollView(child: Text(description)),
221+
actions: <Widget>[
222+
TextButton(
223+
child: Text(MaterialLocalizations.of(dialogContext).closeButtonLabel),
224+
onPressed: () {
225+
Navigator.of(dialogContext).pop();
226+
},
227+
),
228+
],
229+
);
230+
},
231+
);
232+
}
233+
},
234+
),
235+
),
236+
],
237+
);
151238

152239
return CustomScrollView(
153240
controller: _scrollController,
154241
slivers: [
155242
SliverAppBar(
156-
title: Text(appBarTitle),
243+
title: appBarTitleWidget,
157244
pinned: true,
158-
expandedHeight: entityIconUrl != null ? 200.0 : kToolbarHeight,
159-
flexibleSpace: entityIconUrl != null
160-
? FlexibleSpaceBar(
161-
background: Image.network(
162-
entityIconUrl,
163-
fit: BoxFit.cover,
164-
errorBuilder: (context, error, stackTrace) =>
165-
const Icon(Icons.image_not_supported_outlined, size: 48),
166-
),
167-
)
168-
: null,
245+
actions: [followButton],
246+
// Removed expandedHeight and flexibleSpace for a standard AppBar
169247
),
170-
SliverToBoxAdapter(
248+
SliverToBoxAdapter( // This adapter is now just for spacing and the section title/divider
171249
child: Padding(
172-
padding: const EdgeInsets.all(AppSpacing.paddingMedium),
250+
padding: const EdgeInsets.symmetric(horizontal: AppSpacing.paddingMedium)
251+
.copyWith(top: AppSpacing.lg), // Add top padding
173252
child: Column(
174-
crossAxisAlignment: CrossAxisAlignment.start,
253+
crossAxisAlignment: CrossAxisAlignment.start,
175254
children: [
176-
Row(
177-
mainAxisAlignment: MainAxisAlignment.spaceBetween,
178-
crossAxisAlignment: CrossAxisAlignment.start,
179-
children: [
180-
Expanded(
181-
child: Text(
182-
appBarTitle,
183-
style: theme.textTheme.headlineMedium,
184-
),
185-
),
186-
const SizedBox(width: AppSpacing.md),
187-
ElevatedButton.icon(
188-
icon: Icon(
189-
state.isFollowing
190-
? Icons.check_circle // Filled when following
191-
: Icons.add_circle_outline,
192-
),
193-
label: Text(
194-
state.isFollowing
195-
? l10n.unfollowButtonLabel
196-
: l10n.followButtonLabel,
197-
),
198-
style: ElevatedButton.styleFrom(
199-
backgroundColor: state.isFollowing
200-
? theme.colorScheme.secondaryContainer
201-
: theme.colorScheme.primaryContainer,
202-
foregroundColor: state.isFollowing
203-
? theme.colorScheme.onSecondaryContainer
204-
: theme.colorScheme.onPrimaryContainer,
205-
),
206-
onPressed: () {
207-
context
208-
.read<EntityDetailsBloc>()
209-
.add(const EntityDetailsToggleFollowRequested());
210-
},
211-
),
212-
],
213-
),
214-
if (description != null && description.isNotEmpty) ...[
215-
const SizedBox(height: AppSpacing.md),
216-
Text(description, style: theme.textTheme.bodyMedium),
217-
],
218-
const SizedBox(height: AppSpacing.lg), // Increased spacing
219-
if (state.headlines.isNotEmpty || state.headlinesStatus == EntityHeadlinesStatus.loadingMore)
220-
Text(l10n.headlinesSectionTitle, style: theme.textTheme.titleLarge), // Section title
221-
if (state.headlines.isNotEmpty || state.headlinesStatus == EntityHeadlinesStatus.loadingMore)
255+
if (state.headlines.isNotEmpty || state.headlinesStatus == EntityHeadlinesStatus.loadingMore) ...[
256+
Text(l10n.headlinesSectionTitle, style: theme.textTheme.titleLarge),
222257
const Divider(height: AppSpacing.md),
258+
]
223259
],
224260
),
225261
),

0 commit comments

Comments
 (0)