perf: fix image loading performance across all screens
- Replace Image.network (no cache) with CachedNetworkImage in contributor_profile_screen - Replace NetworkImage (no cache) with CachedNetworkImageProvider in desktop_topbar and contribute_screen (leaderboard avatars) - Add maxWidthDiskCache + maxHeightDiskCache to all 23 CachedNetworkImage calls - Add missing memCacheWidth/Height to review_card (36x36 avatar) and learn_more related events (140x100) - Add dynamic memCache sizing to tier_avatar_ring based on widget size Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -125,6 +125,10 @@ class _ReviewCardState extends State<ReviewCard> {
|
|||||||
imageUrl: 'https://api.dicebear.com/9.x/notionists/svg?seed=${Uri.encodeComponent(_review.username)}',
|
imageUrl: 'https://api.dicebear.com/9.x/notionists/svg?seed=${Uri.encodeComponent(_review.username)}',
|
||||||
width: 36,
|
width: 36,
|
||||||
height: 36,
|
height: 36,
|
||||||
|
memCacheWidth: 72,
|
||||||
|
memCacheHeight: 72,
|
||||||
|
maxWidthDiskCache: 144,
|
||||||
|
maxHeightDiskCache: 144,
|
||||||
placeholder: (_, __) => CircleAvatar(
|
placeholder: (_, __) => CircleAvatar(
|
||||||
radius: 18,
|
radius: 18,
|
||||||
backgroundColor: _avatarColor(_review.username),
|
backgroundColor: _avatarColor(_review.username),
|
||||||
|
|||||||
@@ -519,6 +519,8 @@ class _CalendarScreenState extends State<CalendarScreen> {
|
|||||||
imageUrl: imgUrl,
|
imageUrl: imgUrl,
|
||||||
memCacheWidth: 400,
|
memCacheWidth: 400,
|
||||||
memCacheHeight: 300,
|
memCacheHeight: 300,
|
||||||
|
maxWidthDiskCache: 800,
|
||||||
|
maxHeightDiskCache: 600,
|
||||||
height: 150,
|
height: 150,
|
||||||
width: double.infinity,
|
width: double.infinity,
|
||||||
fit: BoxFit.cover,
|
fit: BoxFit.cover,
|
||||||
@@ -582,6 +584,8 @@ class _CalendarScreenState extends State<CalendarScreen> {
|
|||||||
imageUrl: imgUrl,
|
imageUrl: imgUrl,
|
||||||
memCacheWidth: 300,
|
memCacheWidth: 300,
|
||||||
memCacheHeight: 300,
|
memCacheHeight: 300,
|
||||||
|
maxWidthDiskCache: 600,
|
||||||
|
maxHeightDiskCache: 600,
|
||||||
width: 100,
|
width: 100,
|
||||||
height: 100,
|
height: 100,
|
||||||
fit: BoxFit.cover,
|
fit: BoxFit.cover,
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
// 3 tabs: My Events · Submit Event · Reward Shop
|
// 3 tabs: My Events · Submit Event · Reward Shop
|
||||||
|
|
||||||
import 'dart:io';
|
import 'dart:io';
|
||||||
|
import 'package:cached_network_image/cached_network_image.dart';
|
||||||
import 'package:flutter/foundation.dart' show kIsWeb;
|
import 'package:flutter/foundation.dart' show kIsWeb;
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter/services.dart';
|
import 'package:flutter/services.dart';
|
||||||
@@ -11,6 +12,7 @@ import 'package:intl/intl.dart';
|
|||||||
import 'package:provider/provider.dart';
|
import 'package:provider/provider.dart';
|
||||||
import 'package:share_plus/share_plus.dart';
|
import 'package:share_plus/share_plus.dart';
|
||||||
|
|
||||||
|
import '../core/auth/auth_guard.dart';
|
||||||
import '../core/utils/error_utils.dart';
|
import '../core/utils/error_utils.dart';
|
||||||
import '../features/gamification/models/gamification_models.dart';
|
import '../features/gamification/models/gamification_models.dart';
|
||||||
import '../features/gamification/providers/gamification_provider.dart';
|
import '../features/gamification/providers/gamification_provider.dart';
|
||||||
@@ -104,6 +106,9 @@ class _ContributeScreenState extends State<ContributeScreen>
|
|||||||
super.initState();
|
super.initState();
|
||||||
PostHogService.instance.screen('Contribute');
|
PostHogService.instance.screen('Contribute');
|
||||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||||
|
// Gamification endpoints are authed — guests would hit 401 and pollute logs.
|
||||||
|
// AuthGuard.requireLogin prompts guests when they tap any gated action.
|
||||||
|
if (AuthGuard.isGuest) return;
|
||||||
final p = context.read<GamificationProvider>();
|
final p = context.read<GamificationProvider>();
|
||||||
p.loadAll();
|
p.loadAll();
|
||||||
p.loadLeaderboard(); // independent — always fires regardless of loadAll TTL
|
p.loadLeaderboard(); // independent — always fires regardless of loadAll TTL
|
||||||
@@ -389,7 +394,7 @@ class _ContributeScreenState extends State<ContributeScreen>
|
|||||||
CircleAvatar(
|
CircleAvatar(
|
||||||
radius: 20,
|
radius: 20,
|
||||||
backgroundColor: _lightBlueBg,
|
backgroundColor: _lightBlueBg,
|
||||||
backgroundImage: entry.avatarUrl != null ? NetworkImage(entry.avatarUrl!) : null,
|
backgroundImage: entry.avatarUrl != null ? CachedNetworkImageProvider(entry.avatarUrl!, maxWidth: 80, maxHeight: 80) : null,
|
||||||
child: entry.avatarUrl == null
|
child: entry.avatarUrl == null
|
||||||
? const Icon(Icons.person_outline, color: _blue, size: 20)
|
? const Icon(Icons.person_outline, color: _blue, size: 20)
|
||||||
: null,
|
: null,
|
||||||
@@ -1334,12 +1339,14 @@ class _ContributeScreenState extends State<ContributeScreen>
|
|||||||
}
|
}
|
||||||
|
|
||||||
Widget _textField(TextEditingController ctl, String placeholder, {
|
Widget _textField(TextEditingController ctl, String placeholder, {
|
||||||
|
Key? key,
|
||||||
int maxLines = 1,
|
int maxLines = 1,
|
||||||
String? Function(String?)? validator,
|
String? Function(String?)? validator,
|
||||||
TextInputType? keyboardType,
|
TextInputType? keyboardType,
|
||||||
List<TextInputFormatter>? inputFormatters,
|
List<TextInputFormatter>? inputFormatters,
|
||||||
}) {
|
}) {
|
||||||
return TextFormField(
|
return TextFormField(
|
||||||
|
key: key,
|
||||||
controller: ctl,
|
controller: ctl,
|
||||||
maxLines: maxLines,
|
maxLines: maxLines,
|
||||||
keyboardType: keyboardType,
|
keyboardType: keyboardType,
|
||||||
@@ -1473,12 +1480,14 @@ class _ContributeScreenState extends State<ContributeScreen>
|
|||||||
children: [
|
children: [
|
||||||
Expanded(
|
Expanded(
|
||||||
child: _textField(_latCtl, 'Latitude (e.g. 9.93123)',
|
child: _textField(_latCtl, 'Latitude (e.g. 9.93123)',
|
||||||
|
key: const ValueKey('coord_lat'),
|
||||||
keyboardType: const TextInputType.numberWithOptions(decimal: true, signed: true),
|
keyboardType: const TextInputType.numberWithOptions(decimal: true, signed: true),
|
||||||
inputFormatters: [FilteringTextInputFormatter.allow(RegExp(r'[0-9.\-]'))]),
|
inputFormatters: [FilteringTextInputFormatter.allow(RegExp(r'[0-9.\-]'))]),
|
||||||
),
|
),
|
||||||
const SizedBox(width: 12),
|
const SizedBox(width: 12),
|
||||||
Expanded(
|
Expanded(
|
||||||
child: _textField(_lngCtl, 'Longitude (e.g. 76.26730)',
|
child: _textField(_lngCtl, 'Longitude (e.g. 76.26730)',
|
||||||
|
key: const ValueKey('coord_lng'),
|
||||||
keyboardType: const TextInputType.numberWithOptions(decimal: true, signed: true),
|
keyboardType: const TextInputType.numberWithOptions(decimal: true, signed: true),
|
||||||
inputFormatters: [FilteringTextInputFormatter.allow(RegExp(r'[0-9.\-]'))]),
|
inputFormatters: [FilteringTextInputFormatter.allow(RegExp(r'[0-9.\-]'))]),
|
||||||
),
|
),
|
||||||
@@ -1488,7 +1497,9 @@ class _ContributeScreenState extends State<ContributeScreen>
|
|||||||
Row(
|
Row(
|
||||||
children: [
|
children: [
|
||||||
Expanded(
|
Expanded(
|
||||||
child: _textField(_mapsLinkCtl, 'Paste Google Maps URL here'),
|
child: _textField(_mapsLinkCtl, 'Paste Google Maps URL here',
|
||||||
|
key: const ValueKey('coord_maps_url'),
|
||||||
|
keyboardType: TextInputType.url),
|
||||||
),
|
),
|
||||||
const SizedBox(width: 8),
|
const SizedBox(width: 8),
|
||||||
SizedBox(
|
SizedBox(
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
// CTR-004 — Public contributor profile page.
|
// CTR-004 — Public contributor profile page.
|
||||||
// Shows avatar, tier ring, EP stats, and submission grid for any contributor.
|
// Shows avatar, tier ring, EP stats, and submission grid for any contributor.
|
||||||
|
|
||||||
|
import 'package:cached_network_image/cached_network_image.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
import '../features/gamification/models/gamification_models.dart';
|
import '../features/gamification/models/gamification_models.dart';
|
||||||
@@ -215,10 +216,15 @@ class _ContributorProfileScreenState extends State<ContributorProfileScreen> {
|
|||||||
ClipRRect(
|
ClipRRect(
|
||||||
borderRadius: BorderRadius.circular(10),
|
borderRadius: BorderRadius.circular(10),
|
||||||
child: SizedBox.expand(
|
child: SizedBox.expand(
|
||||||
child: Image.network(
|
child: CachedNetworkImage(
|
||||||
firstImage,
|
imageUrl: firstImage,
|
||||||
fit: BoxFit.cover,
|
fit: BoxFit.cover,
|
||||||
errorBuilder: (_, __, ___) => Container(color: const Color(0xFF334155)),
|
memCacheWidth: 400,
|
||||||
|
memCacheHeight: 300,
|
||||||
|
maxWidthDiskCache: 800,
|
||||||
|
maxHeightDiskCache: 600,
|
||||||
|
placeholder: (_, __) => Container(color: const Color(0xFF1E293B)),
|
||||||
|
errorWidget: (_, __, ___) => Container(color: const Color(0xFF334155)),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -320,6 +320,8 @@ class _HomeContentState extends State<_HomeContent>
|
|||||||
height: double.infinity,
|
height: double.infinity,
|
||||||
memCacheWidth: 1400,
|
memCacheWidth: 1400,
|
||||||
memCacheHeight: 800,
|
memCacheHeight: 800,
|
||||||
|
maxWidthDiskCache: 1400,
|
||||||
|
maxHeightDiskCache: 800,
|
||||||
placeholder: (_, __) => Container(
|
placeholder: (_, __) => Container(
|
||||||
color: const Color(0xFF0A0E1A),
|
color: const Color(0xFF0A0E1A),
|
||||||
),
|
),
|
||||||
@@ -529,6 +531,8 @@ class _HomeContentState extends State<_HomeContent>
|
|||||||
fit: BoxFit.cover,
|
fit: BoxFit.cover,
|
||||||
memCacheWidth: 1400,
|
memCacheWidth: 1400,
|
||||||
memCacheHeight: 800,
|
memCacheHeight: 800,
|
||||||
|
maxWidthDiskCache: 1400,
|
||||||
|
maxHeightDiskCache: 800,
|
||||||
)
|
)
|
||||||
else
|
else
|
||||||
Container(color: const Color(0xFF0A0E1A)),
|
Container(color: const Color(0xFF0A0E1A)),
|
||||||
@@ -782,6 +786,8 @@ class _HomeContentState extends State<_HomeContent>
|
|||||||
imageUrl: img,
|
imageUrl: img,
|
||||||
memCacheWidth: 600,
|
memCacheWidth: 600,
|
||||||
memCacheHeight: 320,
|
memCacheHeight: 320,
|
||||||
|
maxWidthDiskCache: 1200,
|
||||||
|
maxHeightDiskCache: 640,
|
||||||
width: double.infinity,
|
width: double.infinity,
|
||||||
height: imageHeight,
|
height: imageHeight,
|
||||||
fit: BoxFit.cover,
|
fit: BoxFit.cover,
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
import 'dart:ui';
|
import 'dart:ui';
|
||||||
import '../core/utils/error_utils.dart';
|
import '../core/utils/error_utils.dart';
|
||||||
|
import 'package:flutter/foundation.dart' show kDebugMode, debugPrint;
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:shared_preferences/shared_preferences.dart';
|
import 'package:shared_preferences/shared_preferences.dart';
|
||||||
import '../core/auth/auth_guard.dart';
|
import '../core/auth/auth_guard.dart';
|
||||||
@@ -136,15 +137,16 @@ class _HomeScreenState extends State<HomeScreen> with SingleTickerProviderStateM
|
|||||||
_loading = false;
|
_loading = false;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e, st) {
|
||||||
|
if (kDebugMode) debugPrint('HomeScreen._loadUserDataAndEvents error: $e\n$st');
|
||||||
if (mounted) {
|
if (mounted) {
|
||||||
setState(() => _loading = false);
|
setState(() => _loading = false);
|
||||||
ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text(userFriendlyError(e))));
|
ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text(userFriendlyError(e))));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Refresh notification badge count (fire-and-forget)
|
// Refresh notification badge count (fire-and-forget, skip for guests — endpoint is authed)
|
||||||
if (mounted) {
|
if (mounted && !AuthGuard.isGuest) {
|
||||||
context.read<NotificationProvider>().refreshUnreadCount();
|
context.read<NotificationProvider>().refreshUnreadCount();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -263,6 +265,8 @@ class _HomeScreenState extends State<HomeScreen> with SingleTickerProviderStateM
|
|||||||
imageUrl: imageUrl,
|
imageUrl: imageUrl,
|
||||||
memCacheWidth: 112,
|
memCacheWidth: 112,
|
||||||
memCacheHeight: 112,
|
memCacheHeight: 112,
|
||||||
|
maxWidthDiskCache: 224,
|
||||||
|
maxHeightDiskCache: 224,
|
||||||
fit: BoxFit.contain,
|
fit: BoxFit.contain,
|
||||||
placeholder: (_, __) => Icon(
|
placeholder: (_, __) => Icon(
|
||||||
icon ?? Icons.category,
|
icon ?? Icons.category,
|
||||||
@@ -475,6 +479,8 @@ class _HomeScreenState extends State<HomeScreen> with SingleTickerProviderStateM
|
|||||||
imageUrl: img,
|
imageUrl: img,
|
||||||
memCacheWidth: 112,
|
memCacheWidth: 112,
|
||||||
memCacheHeight: 112,
|
memCacheHeight: 112,
|
||||||
|
maxWidthDiskCache: 224,
|
||||||
|
maxHeightDiskCache: 224,
|
||||||
width: 56,
|
width: 56,
|
||||||
height: 56,
|
height: 56,
|
||||||
fit: BoxFit.cover,
|
fit: BoxFit.cover,
|
||||||
@@ -956,6 +962,8 @@ class _HomeScreenState extends State<HomeScreen> with SingleTickerProviderStateM
|
|||||||
imageUrl: imageUrl,
|
imageUrl: imageUrl,
|
||||||
memCacheWidth: 160,
|
memCacheWidth: 160,
|
||||||
memCacheHeight: 160,
|
memCacheHeight: 160,
|
||||||
|
maxWidthDiskCache: 320,
|
||||||
|
maxHeightDiskCache: 320,
|
||||||
width: 80,
|
width: 80,
|
||||||
height: 80,
|
height: 80,
|
||||||
fit: BoxFit.cover,
|
fit: BoxFit.cover,
|
||||||
@@ -1288,6 +1296,8 @@ class _HomeScreenState extends State<HomeScreen> with SingleTickerProviderStateM
|
|||||||
imageUrl: currentImg,
|
imageUrl: currentImg,
|
||||||
memCacheWidth: 200,
|
memCacheWidth: 200,
|
||||||
memCacheHeight: 200,
|
memCacheHeight: 200,
|
||||||
|
maxWidthDiskCache: 400,
|
||||||
|
maxHeightDiskCache: 400,
|
||||||
fit: BoxFit.cover,
|
fit: BoxFit.cover,
|
||||||
placeholder: (_, __) => const SizedBox.shrink(),
|
placeholder: (_, __) => const SizedBox.shrink(),
|
||||||
errorWidget: (_, __, ___) => const SizedBox.shrink(),
|
errorWidget: (_, __, ___) => const SizedBox.shrink(),
|
||||||
@@ -1524,6 +1534,8 @@ class _HomeScreenState extends State<HomeScreen> with SingleTickerProviderStateM
|
|||||||
imageUrl: img,
|
imageUrl: img,
|
||||||
memCacheWidth: 700,
|
memCacheWidth: 700,
|
||||||
memCacheHeight: 400,
|
memCacheHeight: 400,
|
||||||
|
maxWidthDiskCache: 1200,
|
||||||
|
maxHeightDiskCache: 700,
|
||||||
fit: BoxFit.cover,
|
fit: BoxFit.cover,
|
||||||
placeholder: (_, __) => const _HeroShimmer(radius: radius),
|
placeholder: (_, __) => const _HeroShimmer(radius: radius),
|
||||||
errorWidget: (_, __, ___) =>
|
errorWidget: (_, __, ___) =>
|
||||||
@@ -1951,6 +1963,8 @@ class _HomeScreenState extends State<HomeScreen> with SingleTickerProviderStateM
|
|||||||
imageUrl: img,
|
imageUrl: img,
|
||||||
memCacheWidth: 300,
|
memCacheWidth: 300,
|
||||||
memCacheHeight: 200,
|
memCacheHeight: 200,
|
||||||
|
maxWidthDiskCache: 600,
|
||||||
|
maxHeightDiskCache: 400,
|
||||||
fit: BoxFit.cover,
|
fit: BoxFit.cover,
|
||||||
width: double.infinity,
|
width: double.infinity,
|
||||||
height: double.infinity,
|
height: double.infinity,
|
||||||
@@ -2147,6 +2161,8 @@ class _HomeScreenState extends State<HomeScreen> with SingleTickerProviderStateM
|
|||||||
imageUrl: img,
|
imageUrl: img,
|
||||||
memCacheWidth: 192,
|
memCacheWidth: 192,
|
||||||
memCacheHeight: 192,
|
memCacheHeight: 192,
|
||||||
|
maxWidthDiskCache: 384,
|
||||||
|
maxHeightDiskCache: 384,
|
||||||
width: 96,
|
width: 96,
|
||||||
height: double.infinity,
|
height: double.infinity,
|
||||||
fit: BoxFit.cover,
|
fit: BoxFit.cover,
|
||||||
@@ -2214,6 +2230,8 @@ class _HomeScreenState extends State<HomeScreen> with SingleTickerProviderStateM
|
|||||||
imageUrl: img,
|
imageUrl: img,
|
||||||
memCacheWidth: 440,
|
memCacheWidth: 440,
|
||||||
memCacheHeight: 360,
|
memCacheHeight: 360,
|
||||||
|
maxWidthDiskCache: 880,
|
||||||
|
maxHeightDiskCache: 720,
|
||||||
width: 220,
|
width: 220,
|
||||||
height: 180,
|
height: 180,
|
||||||
fit: BoxFit.cover,
|
fit: BoxFit.cover,
|
||||||
@@ -2387,6 +2405,8 @@ class _HomeScreenState extends State<HomeScreen> with SingleTickerProviderStateM
|
|||||||
imageUrl: img,
|
imageUrl: img,
|
||||||
memCacheWidth: 800,
|
memCacheWidth: 800,
|
||||||
memCacheHeight: 400,
|
memCacheHeight: 400,
|
||||||
|
maxWidthDiskCache: 1200,
|
||||||
|
maxHeightDiskCache: 600,
|
||||||
width: double.infinity,
|
width: double.infinity,
|
||||||
height: 200,
|
height: 200,
|
||||||
fit: BoxFit.cover,
|
fit: BoxFit.cover,
|
||||||
|
|||||||
@@ -332,6 +332,8 @@ class _LearnMoreScreenState extends State<LearnMoreScreen> with SingleTickerProv
|
|||||||
fit: BoxFit.cover,
|
fit: BoxFit.cover,
|
||||||
memCacheWidth: 800,
|
memCacheWidth: 800,
|
||||||
memCacheHeight: 500,
|
memCacheHeight: 500,
|
||||||
|
maxWidthDiskCache: 1200,
|
||||||
|
maxHeightDiskCache: 800,
|
||||||
placeholder: (_, __) => Container(
|
placeholder: (_, __) => Container(
|
||||||
decoration: const BoxDecoration(
|
decoration: const BoxDecoration(
|
||||||
gradient: LinearGradient(
|
gradient: LinearGradient(
|
||||||
@@ -544,6 +546,8 @@ class _LearnMoreScreenState extends State<LearnMoreScreen> with SingleTickerProv
|
|||||||
fit: BoxFit.cover,
|
fit: BoxFit.cover,
|
||||||
memCacheWidth: 800,
|
memCacheWidth: 800,
|
||||||
memCacheHeight: 500,
|
memCacheHeight: 500,
|
||||||
|
maxWidthDiskCache: 1200,
|
||||||
|
maxHeightDiskCache: 800,
|
||||||
placeholder: (_, __) => Container(color: theme.dividerColor),
|
placeholder: (_, __) => Container(color: theme.dividerColor),
|
||||||
errorWidget: (_, __, ___) => Container(
|
errorWidget: (_, __, ___) => Container(
|
||||||
color: theme.dividerColor,
|
color: theme.dividerColor,
|
||||||
@@ -847,6 +851,8 @@ class _LearnMoreScreenState extends State<LearnMoreScreen> with SingleTickerProv
|
|||||||
fit: BoxFit.cover,
|
fit: BoxFit.cover,
|
||||||
memCacheWidth: 800,
|
memCacheWidth: 800,
|
||||||
memCacheHeight: 500,
|
memCacheHeight: 500,
|
||||||
|
maxWidthDiskCache: 1200,
|
||||||
|
maxHeightDiskCache: 800,
|
||||||
width: double.infinity,
|
width: double.infinity,
|
||||||
height: double.infinity,
|
height: double.infinity,
|
||||||
placeholder: (_, __) => Container(
|
placeholder: (_, __) => Container(
|
||||||
@@ -909,6 +915,8 @@ class _LearnMoreScreenState extends State<LearnMoreScreen> with SingleTickerProv
|
|||||||
fit: BoxFit.cover,
|
fit: BoxFit.cover,
|
||||||
memCacheWidth: 800,
|
memCacheWidth: 800,
|
||||||
memCacheHeight: 500,
|
memCacheHeight: 500,
|
||||||
|
maxWidthDiskCache: 1200,
|
||||||
|
maxHeightDiskCache: 800,
|
||||||
width: double.infinity,
|
width: double.infinity,
|
||||||
placeholder: (_, __) => Container(
|
placeholder: (_, __) => Container(
|
||||||
color: theme.dividerColor,
|
color: theme.dividerColor,
|
||||||
@@ -1567,6 +1575,10 @@ class _LearnMoreScreenState extends State<LearnMoreScreen> with SingleTickerProv
|
|||||||
imageUrl: imageUrl,
|
imageUrl: imageUrl,
|
||||||
height: 100,
|
height: 100,
|
||||||
width: 140,
|
width: 140,
|
||||||
|
memCacheWidth: 280,
|
||||||
|
memCacheHeight: 200,
|
||||||
|
maxWidthDiskCache: 560,
|
||||||
|
maxHeightDiskCache: 400,
|
||||||
fit: BoxFit.cover,
|
fit: BoxFit.cover,
|
||||||
errorWidget: (_, __, ___) => Container(
|
errorWidget: (_, __, ___) => Container(
|
||||||
height: 100,
|
height: 100,
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ import 'package:shared_preferences/shared_preferences.dart';
|
|||||||
import 'package:provider/provider.dart';
|
import 'package:provider/provider.dart';
|
||||||
import '../features/events/services/events_service.dart';
|
import '../features/events/services/events_service.dart';
|
||||||
import '../features/events/models/event_models.dart';
|
import '../features/events/models/event_models.dart';
|
||||||
|
import '../core/auth/auth_guard.dart';
|
||||||
import '../features/gamification/providers/gamification_provider.dart';
|
import '../features/gamification/providers/gamification_provider.dart';
|
||||||
import '../features/gamification/models/gamification_models.dart';
|
import '../features/gamification/models/gamification_models.dart';
|
||||||
import '../widgets/skeleton_loader.dart';
|
import '../widgets/skeleton_loader.dart';
|
||||||
@@ -123,9 +124,11 @@ class _ProfileScreenState extends State<ProfileScreen>
|
|||||||
_loadProfile();
|
_loadProfile();
|
||||||
_startAnimations();
|
_startAnimations();
|
||||||
|
|
||||||
// Load gamification data for profile EP cards
|
// Load gamification data for profile EP cards — skip for guests (endpoints authed).
|
||||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||||
if (mounted) context.read<GamificationProvider>().loadAll(force: true);
|
if (mounted && !AuthGuard.isGuest) {
|
||||||
|
context.read<GamificationProvider>().loadAll(force: true);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1047,6 +1050,8 @@ class _ProfileScreenState extends State<ProfileScreen>
|
|||||||
imageUrl: imageUrl,
|
imageUrl: imageUrl,
|
||||||
memCacheWidth: 120,
|
memCacheWidth: 120,
|
||||||
memCacheHeight: 120,
|
memCacheHeight: 120,
|
||||||
|
maxWidthDiskCache: 240,
|
||||||
|
maxHeightDiskCache: 240,
|
||||||
width: 60,
|
width: 60,
|
||||||
height: 60,
|
height: 60,
|
||||||
fit: BoxFit.cover,
|
fit: BoxFit.cover,
|
||||||
@@ -2552,6 +2557,8 @@ class _ProfileScreenState extends State<ProfileScreen>
|
|||||||
imageUrl: imageUrl,
|
imageUrl: imageUrl,
|
||||||
memCacheWidth: 400,
|
memCacheWidth: 400,
|
||||||
memCacheHeight: 400,
|
memCacheHeight: 400,
|
||||||
|
maxWidthDiskCache: 800,
|
||||||
|
maxHeightDiskCache: 800,
|
||||||
width: double.infinity,
|
width: double.infinity,
|
||||||
height: double.infinity,
|
height: double.infinity,
|
||||||
fit: BoxFit.cover,
|
fit: BoxFit.cover,
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import 'package:cached_network_image/cached_network_image.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
class DesktopTopBar extends StatelessWidget {
|
class DesktopTopBar extends StatelessWidget {
|
||||||
@@ -108,7 +109,11 @@ class DesktopTopBar extends StatelessWidget {
|
|||||||
return CircleAvatar(
|
return CircleAvatar(
|
||||||
radius: 20,
|
radius: 20,
|
||||||
backgroundColor: Colors.grey.shade200,
|
backgroundColor: Colors.grey.shade200,
|
||||||
backgroundImage: NetworkImage(url),
|
backgroundImage: CachedNetworkImageProvider(
|
||||||
|
url,
|
||||||
|
maxWidth: 80,
|
||||||
|
maxHeight: 80,
|
||||||
|
),
|
||||||
onBackgroundImageError: (_, __) {},
|
onBackgroundImageError: (_, __) {},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -55,6 +55,10 @@ class TierAvatarRing extends StatelessWidget {
|
|||||||
|
|
||||||
return CachedNetworkImage(
|
return CachedNetworkImage(
|
||||||
imageUrl: _avatarUrl,
|
imageUrl: _avatarUrl,
|
||||||
|
memCacheWidth: (size * 2).round(),
|
||||||
|
memCacheHeight: (size * 2).round(),
|
||||||
|
maxWidthDiskCache: (size * 4).round(),
|
||||||
|
maxHeightDiskCache: (size * 4).round(),
|
||||||
imageBuilder: (context, imageProvider) => CircleAvatar(
|
imageBuilder: (context, imageProvider) => CircleAvatar(
|
||||||
radius: radius,
|
radius: radius,
|
||||||
backgroundImage: imageProvider,
|
backgroundImage: imageProvider,
|
||||||
|
|||||||
Reference in New Issue
Block a user