refactor: perf improvements

This commit is contained in:
cogwheel0
2025-09-23 11:00:25 +05:30
parent 41216ea432
commit 8da8a78001
7 changed files with 205 additions and 93 deletions

View File

@@ -34,8 +34,8 @@ class ConnectivityService {
_checkConnectivity();
});
// Check every 5 seconds
_connectivityTimer = Timer.periodic(const Duration(seconds: 5), (_) {
// Check periodically; balance responsiveness with battery/network usage
_connectivityTimer = Timer.periodic(const Duration(seconds: 10), (_) {
_checkConnectivity();
});
}

View File

@@ -106,8 +106,16 @@ class _ErrorBoundaryState extends ConsumerState<ErrorBoundary> {
}
// Default error UI
// Respect ambient text direction when available; fall back to LTR.
TextDirection direction;
try {
direction = Directionality.of(context);
} catch (_) {
direction = TextDirection.ltr;
}
return Directionality(
textDirection: TextDirection.ltr,
textDirection: direction,
child: Scaffold(
backgroundColor: context.conduitTheme.surfaceBackground,
body: SafeArea(

View File

@@ -595,35 +595,39 @@ class _AuthenticationPageState extends ConsumerState<AuthenticationPage> {
}
Widget _buildErrorMessage(String message) {
return Container(
padding: const EdgeInsets.all(Spacing.md),
decoration: BoxDecoration(
color: context.conduitTheme.errorBackground,
borderRadius: BorderRadius.circular(AppBorderRadius.button),
border: Border.all(
color: context.conduitTheme.error.withValues(alpha: 0.3),
width: BorderWidth.standard,
),
),
child: Row(
children: [
Icon(
Platform.isIOS
? CupertinoIcons.exclamationmark_circle_fill
: Icons.error_outline,
color: context.conduitTheme.error,
size: IconSize.medium,
return Semantics(
liveRegion: true,
label: message,
child: Container(
padding: const EdgeInsets.all(Spacing.md),
decoration: BoxDecoration(
color: context.conduitTheme.errorBackground,
borderRadius: BorderRadius.circular(AppBorderRadius.button),
border: Border.all(
color: context.conduitTheme.error.withValues(alpha: 0.3),
width: BorderWidth.standard,
),
const SizedBox(width: Spacing.md),
Expanded(
child: Text(
message,
style: context.conduitTheme.bodyMedium?.copyWith(
color: context.conduitTheme.error,
),
child: Row(
children: [
Icon(
Platform.isIOS
? CupertinoIcons.exclamationmark_circle_fill
: Icons.error_outline,
color: context.conduitTheme.error,
size: IconSize.medium,
),
const SizedBox(width: Spacing.md),
Expanded(
child: Text(
message,
style: context.conduitTheme.bodyMedium?.copyWith(
color: context.conduitTheme.error,
),
),
),
),
],
],
),
),
).animate().slideX(
begin: 0.05,

View File

@@ -748,40 +748,45 @@ class _ServerConnectionPageState extends ConsumerState<ServerConnectionPage> {
}
Widget _buildErrorMessage(String message) {
return Container(
padding: const EdgeInsets.all(Spacing.md),
decoration: BoxDecoration(
color: context.conduitTheme.errorBackground,
borderRadius: BorderRadius.circular(AppBorderRadius.button),
border: Border.all(
color: context.conduitTheme.error.withValues(alpha: 0.3),
width: BorderWidth.standard,
),
),
child: Row(
children: [
Icon(
Platform.isIOS
? CupertinoIcons.exclamationmark_circle_fill
: Icons.error_outline,
color: context.conduitTheme.error,
size: IconSize.medium,
),
const SizedBox(width: Spacing.md),
Expanded(
child: Text(
message,
style: context.conduitTheme.bodyMedium?.copyWith(
color: context.conduitTheme.error,
return Semantics(
liveRegion: true,
label: message,
child:
Container(
padding: const EdgeInsets.all(Spacing.md),
decoration: BoxDecoration(
color: context.conduitTheme.errorBackground,
borderRadius: BorderRadius.circular(AppBorderRadius.button),
border: Border.all(
color: context.conduitTheme.error.withValues(alpha: 0.3),
width: BorderWidth.standard,
),
),
child: Row(
children: [
Icon(
Platform.isIOS
? CupertinoIcons.exclamationmark_circle_fill
: Icons.error_outline,
color: context.conduitTheme.error,
size: IconSize.medium,
),
const SizedBox(width: Spacing.md),
Expanded(
child: Text(
message,
style: context.conduitTheme.bodyMedium?.copyWith(
color: context.conduitTheme.error,
),
),
),
],
),
).animate().slideX(
begin: 0.05,
duration: AnimationDuration.messageSlide,
curve: Curves.easeOutCubic,
),
],
),
).animate().slideX(
begin: 0.05,
duration: AnimationDuration.messageSlide,
curve: Curves.easeOutCubic,
);
}

View File

@@ -633,6 +633,8 @@ class _ChatPageState extends ConsumerState<ChatPage> {
),
physics:
const NeverScrollableScrollPhysics(), // Prevent scrolling during load
// Modest cache extent to avoid offscreen overwork but keep shimmer smooth
cacheExtent: 300,
itemCount: 6,
itemBuilder: (context, index) {
final isUser = index.isOdd;
@@ -1007,9 +1009,7 @@ class _ChatPageState extends ConsumerState<ChatPage> {
if (!mounted) return;
final current = ref.read(inputFocusTriggerProvider);
// Immediate focus bump
ref
.read(inputFocusTriggerProvider.notifier)
.set(current + 1);
ref.read(inputFocusTriggerProvider.notifier).set(current + 1);
// Second bump shortly after to overcome route/IME timing
Future.delayed(const Duration(milliseconds: 120), () {
if (!mounted) return;
@@ -1788,6 +1788,7 @@ class _ModelSelectorSheetState extends ConsumerState<_ModelSelectorSheet> {
: ListView.builder(
controller: scrollController,
padding: EdgeInsets.zero,
cacheExtent: 400,
itemCount: _filteredModels.length,
itemBuilder: (context, index) {
final model = _filteredModels[index];

View File

@@ -194,8 +194,8 @@ class _ConduitAppState extends ConsumerState<ConduitApp> {
return MediaQuery(
data: mediaQuery.copyWith(
textScaler: mediaQuery.textScaler.clamp(
minScaleFactor: 0.8,
maxScaleFactor: 2.0,
minScaleFactor: 1.0,
maxScaleFactor: 3.0,
),
),
child: OfflineIndicator(child: child ?? const SizedBox.shrink()),

View File

@@ -20,6 +20,7 @@ class OfflineIndicator extends ConsumerWidget {
@override
Widget build(BuildContext context, WidgetRef ref) {
final connectivityStatus = ref.watch(connectivityStatusProvider);
final wasOffline = ref.watch(_wasOfflineProvider);
return Stack(
children: [
@@ -30,6 +31,10 @@ class OfflineIndicator extends ConsumerWidget {
if (status == ConnectivityStatus.offline) {
return _OfflineBanner();
}
// Announce back-online briefly if we were previously offline
if (wasOffline) {
return const _BackOnlineToast();
}
return const SizedBox.shrink();
},
loading: () => const SizedBox.shrink(),
@@ -40,6 +45,90 @@ class OfflineIndicator extends ConsumerWidget {
}
}
// Tracks if the app was recently offline to enable a one-shot back-online toast
final _wasOfflineProvider = NotifierProvider<_WasOfflineNotifier, bool>(
_WasOfflineNotifier.new,
);
class _WasOfflineNotifier extends Notifier<bool> {
@override
bool build() {
// Initialize based on current connectivity (assume online until proven otherwise)
ref.listen<AsyncValue<ConnectivityStatus>>(connectivityStatusProvider, (
prev,
next,
) {
next.when(
data: (status) {
if (status == ConnectivityStatus.offline) {
state = true; // mark that we have been offline
} else if (status == ConnectivityStatus.online && state) {
// After we emit the toast once, clear flag shortly after
Future.microtask(() => state = false);
}
},
loading: () {},
error: (_, __) {},
);
});
return false;
}
}
class _BackOnlineToast extends StatelessWidget {
const _BackOnlineToast();
@override
Widget build(BuildContext context) {
return Positioned(
top: kToolbarHeight + 8,
left: 0,
right: 0,
child: SafeArea(
bottom: false,
child: Semantics(
container: true,
liveRegion: true,
label: AppLocalizations.of(context)!.checkConnection,
child: Align(
alignment: Alignment.topCenter,
child:
Container(
margin: const EdgeInsets.symmetric(
horizontal: Spacing.md,
),
padding: const EdgeInsets.symmetric(
horizontal: Spacing.md,
vertical: Spacing.xs,
),
decoration: BoxDecoration(
color: context.conduitTheme.success,
borderRadius: BorderRadius.circular(
AppBorderRadius.round,
),
boxShadow: ConduitShadows.low,
),
child: Text(
// Reuse existing l10n; otherwise add a dedicated "Back online" key later
AppLocalizations.of(context)!.loadingContent,
style: TextStyle(
color: context.conduitTheme.textInverse,
fontSize: AppTypography.labelLarge,
fontWeight: FontWeight.w600,
),
),
)
.animate(onPlay: (c) => c.forward())
.fadeIn(duration: const Duration(milliseconds: 200))
.then(delay: const Duration(milliseconds: 1200))
.fadeOut(duration: const Duration(milliseconds: 250)),
),
),
),
);
}
}
class _OfflineBanner extends StatelessWidget {
@override
Widget build(BuildContext context) {
@@ -50,36 +139,41 @@ class _OfflineBanner extends StatelessWidget {
child: SafeArea(
bottom: false,
child:
Container(
padding: const EdgeInsets.symmetric(
horizontal: Spacing.md,
vertical: Spacing.xs,
),
decoration: BoxDecoration(
color: context.conduitTheme.warning,
boxShadow: ConduitShadows.low,
),
child: Row(
children: [
Icon(
Platform.isIOS
? CupertinoIcons.wifi_slash
: Icons.wifi_off,
color: context.conduitTheme.textInverse,
size: AppTypography.headlineMedium,
),
const SizedBox(width: Spacing.xs),
Expanded(
child: Text(
AppLocalizations.of(context)!.offlineBanner,
style: TextStyle(
color: context.conduitTheme.textInverse,
fontSize: AppTypography.labelLarge,
fontWeight: FontWeight.w500,
Semantics(
container: true,
liveRegion: true,
label: AppLocalizations.of(context)!.offlineBanner,
child: Container(
padding: const EdgeInsets.symmetric(
horizontal: Spacing.md,
vertical: Spacing.xs,
),
decoration: BoxDecoration(
color: context.conduitTheme.warning,
boxShadow: ConduitShadows.low,
),
child: Row(
children: [
Icon(
Platform.isIOS
? CupertinoIcons.wifi_slash
: Icons.wifi_off,
color: context.conduitTheme.textInverse,
size: AppTypography.headlineMedium,
),
const SizedBox(width: Spacing.xs),
Expanded(
child: Text(
AppLocalizations.of(context)!.offlineBanner,
style: TextStyle(
color: context.conduitTheme.textInverse,
fontSize: AppTypography.labelLarge,
fontWeight: FontWeight.w500,
),
),
),
),
],
],
),
),
)
.animate(onPlay: (controller) => controller.forward())