Files
iiEsaywebUIapp/lib/shared/services/brand_service.dart

350 lines
10 KiB
Dart
Raw Normal View History

2025-08-10 01:20:45 +05:30
import 'package:flutter/material.dart';
import '../theme/theme_extensions.dart';
import 'package:flutter/cupertino.dart';
import 'dart:io' show Platform;
import '../theme/color_tokens.dart';
import '../theme/tweakcn_themes.dart';
2025-08-10 01:20:45 +05:30
/// Centralized service for consistent brand identity throughout the app
/// Uses the hub icon as the primary brand element
class BrandService {
BrandService._();
2025-08-16 15:51:27 +05:30
/// Primary brand icon - the hub icon (consistent across platforms)
static IconData get primaryIcon => Icons.hub;
2025-08-10 01:20:45 +05:30
/// Alternative brand icons for different contexts
2025-08-16 15:51:27 +05:30
static IconData get primaryIconOutlined => Icons.hub_outlined;
2025-08-10 01:20:45 +05:30
static IconData get connectivityIcon =>
2025-08-16 15:51:27 +05:30
Platform.isIOS ? CupertinoIcons.wifi : Icons.wifi;
2025-08-10 01:20:45 +05:30
static IconData get networkIcon =>
2025-08-16 15:51:27 +05:30
Platform.isIOS ? CupertinoIcons.globe : Icons.public;
2025-08-10 01:20:45 +05:30
/// Brand colors - these should be accessed through context.conduitTheme in UI components
static Color primaryBrandColor({
BuildContext? context,
Brightness? brightness,
}) {
final palette = _resolvePalette(context);
final resolvedBrightness = brightness ?? _resolveBrightness(context);
return palette.variantFor(resolvedBrightness).primary;
}
static Color secondaryBrandColor({
BuildContext? context,
Brightness? brightness,
}) {
final palette = _resolvePalette(context);
final resolvedBrightness = brightness ?? _resolveBrightness(context);
return palette.variantFor(resolvedBrightness).secondary;
}
static Color accentBrandColor({
BuildContext? context,
Brightness? brightness,
}) {
final palette = _resolvePalette(context);
final resolvedBrightness = brightness ?? _resolveBrightness(context);
return palette.variantFor(resolvedBrightness).accent;
}
2025-08-10 01:20:45 +05:30
/// Creates a branded icon with consistent styling
static Widget createBrandIcon({
double size = 24,
Color? color,
IconData? icon,
bool useGradient = false,
bool addShadow = false,
BuildContext? context,
2025-08-10 01:20:45 +05:30
}) {
final iconData = icon ?? primaryIcon;
final resolvedColor = color ?? primaryBrandColor(context: context);
2025-08-10 01:20:45 +05:30
Widget iconWidget = Icon(
iconData,
size: size,
color: useGradient ? null : resolvedColor,
2025-08-10 01:20:45 +05:30
);
if (useGradient) {
iconWidget = ShaderMask(
blendMode: BlendMode.srcIn,
shaderCallback: (bounds) => LinearGradient(
colors: [
primaryBrandColor(context: context),
secondaryBrandColor(context: context),
],
2025-08-10 01:20:45 +05:30
).createShader(bounds),
child: Icon(iconData, size: size),
);
}
if (addShadow) {
iconWidget = Container(
decoration: BoxDecoration(
boxShadow: [
BoxShadow(
color: primaryBrandColor(context: context).withValues(alpha: 0.3),
2025-08-10 01:20:45 +05:30
blurRadius: size * 0.3,
offset: Offset(0, size * 0.1),
),
],
),
child: iconWidget,
);
}
return iconWidget;
}
/// Creates a branded avatar with the hub icon
static Widget createBrandAvatar({
double size = 40,
Color? backgroundColor,
Color? iconColor,
bool useGradient = true,
String? fallbackText,
BuildContext? context,
}) {
final bgColor = backgroundColor ?? primaryBrandColor(context: context);
final tokens = _resolveTokens(context);
2025-08-10 01:20:45 +05:30
final iColor =
iconColor ??
(context?.conduitTheme.textInverse ?? tokens.neutralTone00);
2025-08-10 01:20:45 +05:30
return Container(
width: size,
height: size,
decoration: BoxDecoration(
gradient: useGradient
? LinearGradient(
begin: Alignment.topLeft,
end: Alignment.bottomRight,
colors: [
primaryBrandColor(context: context),
secondaryBrandColor(context: context),
],
2025-08-10 01:20:45 +05:30
)
: null,
color: useGradient ? null : bgColor,
borderRadius: BorderRadius.circular(size / 2),
boxShadow: [
BoxShadow(
color: primaryBrandColor(context: context).withValues(alpha: 0.3),
2025-08-10 01:20:45 +05:30
blurRadius: size * 0.2,
offset: Offset(0, size * 0.1),
),
],
),
child: fallbackText != null && fallbackText.isNotEmpty
? Center(
child: Text(
fallbackText.toUpperCase(),
style: TextStyle(
color: iColor,
fontSize: size * 0.4,
fontWeight: FontWeight.w600,
),
),
)
: Icon(primaryIcon, size: size * 0.5, color: iColor),
);
}
/// Creates a branded loading indicator
static Widget createBrandLoadingIndicator({
double size = 24,
double strokeWidth = 2,
Color? color,
BuildContext? context,
2025-08-10 01:20:45 +05:30
}) {
return SizedBox(
width: size,
height: size,
child: CircularProgressIndicator(
strokeWidth: strokeWidth,
valueColor: AlwaysStoppedAnimation<Color>(
color ?? primaryBrandColor(context: context),
),
2025-08-10 01:20:45 +05:30
),
);
}
/// Creates a branded empty state icon
static Widget createBrandEmptyStateIcon({
double size = 80,
Color? color,
bool showBackground = true,
BuildContext? context,
}) {
final tokens = _resolveTokens(context);
2025-08-10 01:20:45 +05:30
final iconColor =
color ?? (context?.conduitTheme.iconSecondary ?? tokens.neutralTone80);
2025-08-10 01:20:45 +05:30
if (!showBackground) {
return createBrandIcon(
size: size,
color: iconColor,
icon: primaryIconOutlined,
context: context,
2025-08-10 01:20:45 +05:30
);
}
return Container(
width: size,
height: size,
decoration: BoxDecoration(
color: context?.conduitTheme.surfaceBackground ?? tokens.neutralTone10,
2025-08-10 01:20:45 +05:30
borderRadius: BorderRadius.circular(size / 2),
border: Border.all(
color: context?.conduitTheme.dividerColor ?? tokens.neutralTone40,
2025-08-10 01:20:45 +05:30
width: 2,
),
),
child: createBrandIcon(
size: size * 0.5,
color: iconColor,
icon: primaryIconOutlined,
context: context,
2025-08-10 01:20:45 +05:30
),
);
}
/// Creates a branded button with hub icon
static Widget createBrandButton({
required String text,
required VoidCallback? onPressed,
bool isLoading = false,
IconData? icon,
double? width,
bool isSecondary = false,
BuildContext? context,
}) {
final theme = context?.conduitTheme;
final tokens = _resolveTokens(context);
2025-08-10 01:20:45 +05:30
return SizedBox(
width: width,
height: 48,
child: ElevatedButton.icon(
onPressed: isLoading ? null : onPressed,
icon: isLoading
? createBrandLoadingIndicator(size: IconSize.sm, context: context)
2025-08-10 01:20:45 +05:30
: createBrandIcon(
size: IconSize.md,
icon: icon ?? primaryIcon,
color: theme?.textInverse ?? tokens.neutralTone00,
context: context,
2025-08-10 01:20:45 +05:30
),
label: Text(text),
style: ElevatedButton.styleFrom(
backgroundColor: isSecondary
? (theme?.buttonSecondary ?? tokens.neutralTone20)
: (theme?.buttonPrimary ?? primaryBrandColor(context: context)),
foregroundColor: theme?.buttonPrimaryText ?? tokens.brandOn60,
disabledBackgroundColor:
theme?.buttonDisabled ?? tokens.neutralTone40,
2025-08-10 01:20:45 +05:30
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(AppBorderRadius.md),
),
elevation: Elevation.none,
),
),
);
}
/// Brand-specific semantic labels for accessibility
static String get brandName => 'Conduit';
static String get brandDescription => 'Your AI Conversation Hub';
static String get connectionLabel => 'Hub Connection';
static String get networkLabel => 'Network Hub';
/// Creates branded AppBar with consistent styling
static PreferredSizeWidget createBrandAppBar({
required String title,
List<Widget>? actions,
Widget? leading,
bool centerTitle = true,
double elevation = 0,
BuildContext? context,
}) {
return AppBar(
title: Text(
title,
2025-09-24 12:00:49 +05:30
style: context != null
2025-08-16 15:51:27 +05:30
? context.conduitTheme.headingSmall?.copyWith(
color: context.conduitTheme.textPrimary,
fontWeight: FontWeight.w600,
)
: TextStyle(
fontSize: AppTypography.headlineSmall,
fontWeight: FontWeight.w600,
),
2025-08-10 01:20:45 +05:30
),
centerTitle: centerTitle,
elevation: elevation,
backgroundColor: context?.conduitTheme.surfaceBackground,
surfaceTintColor: Colors.transparent,
shadowColor: Colors.transparent,
leading: leading,
actions: actions,
);
}
/// Creates a branded splash screen logo
static Widget createSplashLogo({
double size = 140,
bool animate = true,
BuildContext? context,
}) {
final theme = context?.conduitTheme;
final tokens = _resolveTokens(context);
final baseColor =
theme?.buttonPrimary ??
primaryBrandColor(context: context, brightness: Brightness.dark);
final accentColor =
theme?.buttonPrimary.withValues(alpha: 0.8) ??
secondaryBrandColor(context: context, brightness: Brightness.dark);
2025-08-10 01:20:45 +05:30
return Container(
width: size,
height: size,
decoration: BoxDecoration(
gradient: LinearGradient(
begin: Alignment.topLeft,
end: Alignment.bottomRight,
colors: [baseColor, accentColor],
2025-08-10 01:20:45 +05:30
),
borderRadius: BorderRadius.circular(size / 2),
boxShadow: context != null
? ConduitShadows.glow(context)
: ConduitShadows.glowWithTokens(tokens),
2025-08-10 01:20:45 +05:30
),
child: Icon(
primaryIcon,
size: size * 0.5,
color: theme?.textInverse ?? tokens.neutralTone00,
2025-08-10 01:20:45 +05:30
),
);
}
static TweakcnThemeDefinition _resolvePalette(BuildContext? context) {
if (context == null) {
return TweakcnThemes.t3Chat;
}
final extension = Theme.of(context).extension<AppPaletteThemeExtension>();
return extension?.palette ?? TweakcnThemes.t3Chat;
}
static Brightness _resolveBrightness(BuildContext? context) {
return context != null ? Theme.of(context).brightness : Brightness.light;
}
static AppColorTokens _resolveTokens(BuildContext? context) {
final palette = _resolvePalette(context);
final brightness = _resolveBrightness(context);
return brightness == Brightness.dark
? AppColorTokens.dark(theme: palette)
: AppColorTokens.light(theme: palette);
}
2025-08-10 01:20:45 +05:30
}