Rebrand to iiEasy: naming, logo, l10n, docs, assets
Some checks failed
L10n / l10n (push) Has been cancelled

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-02-19 23:21:16 +05:00
parent cd536ff8f2
commit 5947edec45
119 changed files with 1180 additions and 541 deletions

View File

@@ -405,12 +405,7 @@ class _AuthenticationPageState extends ConsumerState<AuthenticationPage> {
Widget _buildWelcomeSection() {
return Column(
children: [
BrandService.createBrandIcon(
size: 48,
useGradient: false,
addShadow: false,
context: context,
),
BrandService.createLogoImage(size: 48, context: context),
const SizedBox(height: Spacing.lg),
Text(
AppLocalizations.of(context)!.signIn,

View File

@@ -604,12 +604,7 @@ class _ServerConnectionPageState extends ConsumerState<ServerConnectionPage> {
alignment: Alignment.center,
children: [
// Brand logo
BrandService.createBrandIcon(
size: 56,
useGradient: false,
addShadow: false,
context: context,
),
BrandService.createLogoImage(size: 56, context: context),
// Reviewer mode badge
if (reviewerMode)
Positioned(

View File

@@ -1,25 +1,18 @@
import 'package:flutter/material.dart';
import '../../../shared/theme/theme_extensions.dart';
import '../../../shared/widgets/ii_easy_loading_logo.dart';
class SplashLauncherPage extends StatelessWidget {
const SplashLauncherPage({super.key});
@override
Widget build(BuildContext context) {
final isDark = context.conduitTheme.isDark;
return Scaffold(
backgroundColor: context.conduitTheme.surfaceBackground,
body: Center(
child: SizedBox(
width: 28,
height: 28,
child: CircularProgressIndicator(
strokeWidth: 2.5,
valueColor: AlwaysStoppedAnimation<Color>(
context.conduitTheme.loadingIndicator,
),
),
),
),
backgroundColor: isDark
? const Color(0xFF0A0A0A)
: Colors.white,
body: IiEasyLoadingLogo(isDark: isDark),
);
}
}

View File

@@ -35,9 +35,6 @@ import '../../../shared/widgets/model_avatar.dart';
/// Profile page (You tab) showing user info and main actions
/// Enhanced with production-grade design tokens for better cohesion
class ProfilePage extends ConsumerWidget {
static const _githubSponsorsUrl = 'https://github.com/sponsors/cogwheel0';
static const _buyMeACoffeeUrl = 'https://www.buymeacoffee.com/cogwheel0';
const ProfilePage({super.key});
@override
@@ -117,113 +114,10 @@ class ProfilePage extends ConsumerWidget {
_buildProfileHeader(context, userData, api),
const SizedBox(height: Spacing.xl),
_buildAccountSection(context, ref),
const SizedBox(height: Spacing.xl),
_buildSupportSection(context),
],
);
}
Widget _buildSupportSection(BuildContext context) {
final theme = context.conduitTheme;
final textTheme =
theme.bodySmall?.copyWith(
color: theme.sidebarForeground.withValues(alpha: 0.75),
) ??
TextStyle(color: theme.sidebarForeground.withValues(alpha: 0.75));
final supportTiles = [
_buildSupportOption(
context,
icon: UiUtils.platformIcon(
ios: CupertinoIcons.gift,
android: Icons.coffee,
),
title: AppLocalizations.of(context)!.buyMeACoffeeTitle,
subtitle: AppLocalizations.of(context)!.buyMeACoffeeSubtitle,
url: _buyMeACoffeeUrl,
color: theme.warning,
),
_buildSupportOption(
context,
icon: UiUtils.platformIcon(
ios: CupertinoIcons.heart,
android: Icons.favorite_border,
),
title: AppLocalizations.of(context)!.githubSponsorsTitle,
subtitle: AppLocalizations.of(context)!.githubSponsorsSubtitle,
url: _githubSponsorsUrl,
color: theme.success,
),
];
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
AppLocalizations.of(context)!.supportConduit,
style: theme.headingSmall?.copyWith(color: theme.sidebarForeground),
),
const SizedBox(height: Spacing.xs),
Text(
AppLocalizations.of(context)!.supportConduitSubtitle,
style: textTheme,
),
const SizedBox(height: Spacing.sm),
for (var i = 0; i < supportTiles.length; i++) ...[
supportTiles[i],
if (i != supportTiles.length - 1) const SizedBox(height: Spacing.md),
],
],
);
}
Widget _buildSupportOption(
BuildContext context, {
required IconData icon,
required String title,
required String subtitle,
required String url,
required Color color,
}) {
final theme = context.conduitTheme;
return _ProfileSettingTile(
onTap: () => _openExternalLink(context, url),
leading: _buildIconBadge(context, icon, color: color),
title: title,
subtitle: subtitle,
trailing: Icon(
UiUtils.platformIcon(
ios: CupertinoIcons.arrow_up_right,
android: Icons.open_in_new,
),
color: theme.iconSecondary,
size: IconSize.small,
),
);
}
Future<void> _openExternalLink(BuildContext context, String url) async {
try {
final launched = await launchUrlString(
url,
mode: LaunchMode.externalApplication,
);
if (!launched && context.mounted) {
UiUtils.showMessage(
context,
AppLocalizations.of(context)!.errorMessage,
);
}
} on PlatformException catch (_) {
if (!context.mounted) return;
UiUtils.showMessage(context, AppLocalizations.of(context)!.errorMessage);
} catch (_) {
if (!context.mounted) return;
UiUtils.showMessage(context, AppLocalizations.of(context)!.errorMessage);
}
}
Widget _buildProfileHeader(
BuildContext context,
dynamic user,
@@ -551,8 +445,8 @@ class ProfilePage extends ConsumerWidget {
try {
final info = await PackageInfo.fromPlatform();
// Update dialog with dynamic version each time
// GitHub repo URL source of truth
const githubUrl = 'https://github.com/cogwheel0/conduit';
// Developer / app info URL
const githubUrl = 'https://iiEasy.ru';
if (!context.mounted) return;
await showDialog<void>(
@@ -606,6 +500,13 @@ class ProfilePage extends ConsumerWidget {
],
),
),
const SizedBox(height: Spacing.sm),
Text(
'Based on Conduit',
style: ctx.conduitTheme.bodySmall?.copyWith(
color: ctx.sidebarTheme.foreground.withValues(alpha: 0.6),
),
),
],
),
actions: [

View File

@@ -1,6 +1,7 @@
{
"@@locale": "de",
"appTitle": "Conduit",
"appTitle": "iiEasy",
"appSlogan": "Zukunft. Einfach.",
"retry": "Erneut versuchen",
"back": "Zurück",
"you": "Du",
@@ -15,8 +16,8 @@
"description": "Untertitel mit den verfügbaren Aktionen, wenn der Server nicht erreichbar ist"
},
"account": "Konto",
"supportConduit": "Conduit unterstützen",
"supportConduitSubtitle": "Hilf, die Weiterentwicklung und neue Funktionen zu finanzieren.",
"supportConduit": "iiEasy unterstützen",
"supportConduitSubtitle": "Unterstütze iiEasy durch Finanzierung der Weiterentwicklung.",
"githubSponsorsTitle": "GitHub Sponsors",
"githubSponsorsSubtitle": "Werde monatliche*r Sponsor*in und unterstütze die Roadmap.",
"buyMeACoffeeTitle": "Buy Me a Coffee",
@@ -50,7 +51,7 @@
"password": "Passwort",
"signInWithToken": "Mit Token anmelden",
"connectToServer": "Mit Server verbinden",
"enterServerAddress": "Gib die Adresse deines Open-WebUI-Servers ein, um zu beginnen",
"enterServerAddress": "Gib die Adresse deines iiEasyWeb-Servers ein, um zu beginnen",
"serverUrl": "Server-URL",
"serverUrlHint": "https://dein-server.com",
"enterServerUrlSemantic": "Gib deine Server-URL oder IP-Adresse ein",
@@ -116,7 +117,7 @@
"voicePromptTapStart": "Tippe auf \"Starten\", um zu beginnen",
"voiceActionStop": "Stopp",
"voiceActionStart": "Starten",
"messageHintText": "Frag Conduit",
"messageHintText": "Frag iiEasy",
"stopGenerating": "Generierung stoppen",
"codeCopiedToClipboard": "Code in die Zwischenablage kopiert.",
"send": "Senden",
@@ -204,7 +205,7 @@
"deleteFolderMessage": "Dieser Ordner und seine Zuordnungen werden entfernt.",
"failedToDeleteFolder": "Ordner konnte nicht gelöscht werden",
"aboutApp": "Über",
"aboutAppSubtitle": "Conduit Informationen und Links",
"aboutAppSubtitle": "iiEasy Informationen und Links",
"web": "Web",
"imageGen": "Bild-Gen",
"pinned": "Angeheftet",
@@ -280,7 +281,7 @@
"themeLight": "Hell",
"currentlyUsingDarkTheme": "Aktuell dunkles Thema",
"currentlyUsingLightTheme": "Aktuell helles Thema",
"aboutConduit": "Über Conduit",
"aboutConduit": "Über iiEasy",
"versionLabel": "Version: {version} ({build})",
"@versionLabel": {
"placeholders": {
@@ -317,7 +318,7 @@
"androidAssistantTitle": "Android digital assistant",
"androidAssistantDescription": "Choose what happens when you trigger the Android digital assistant.",
"androidAssistantOverlayOption": "Show quick overlay (default)",
"androidAssistantNewChatOption": "Open Conduit with a new chat",
"androidAssistantNewChatOption": "Open iiEasy with a new chat",
"androidAssistantVoiceCallOption": "Start a voice call",
"sttSettings": "Sprache zu Text",
"sttEngineLabel": "Erkennungs-Engine",
@@ -650,11 +651,11 @@
"@nextLabel": {
"description": "Label for navigating to the next item."
},
"themePaletteConduitLabel": "Conduit",
"themePaletteConduitLabel": "iiEasy",
"@themePaletteConduitLabel": {
"description": "Palette name for the default Conduit theme."
},
"themePaletteConduitDescription": "Schlichtes neutrales Design für Conduit.",
"themePaletteConduitDescription": "Schlichtes neutrales Design für iiEasy.",
"@themePaletteConduitDescription": {
"description": "Description of the Conduit palette."
},

View File

@@ -1,9 +1,13 @@
{
"@@locale": "en",
"appTitle": "Conduit",
"appTitle": "iiEasy",
"@appTitle": {
"description": "Application name displayed in the app and OS UI."
},
"appSlogan": "Future. Simple.",
"@appSlogan": {
"description": "Brand slogan shown on loading/splash."
},
"retry": "Retry",
"@retry": {
"description": "Button label to try an action again."
@@ -36,11 +40,11 @@
"@account": {
"description": "Section header for account-related options."
},
"supportConduit": "Support Conduit",
"supportConduit": "Support iiEasy",
"@supportConduit": {
"description": "Section header inviting the user to financially support the project."
},
"supportConduitSubtitle": "Keep Conduit independent by funding ongoing development.",
"supportConduitSubtitle": "Keep iiEasy independent by funding ongoing development.",
"@supportConduitSubtitle": {
"description": "Subtitle explaining why donations are helpful."
},
@@ -232,7 +236,7 @@
"@connectToServer": {
"description": "Call-to-action button for server connection."
},
"enterServerAddress": "Enter your Open-WebUI server address to get started",
"enterServerAddress": "Enter your iiEasyWeb server address to get started",
"@enterServerAddress": {
"description": "Instruction telling user to provide server URL to begin."
},
@@ -554,7 +558,7 @@
"@voiceCallErrorHelp": {
"description": "Guidance shown when the voice call encounters an error."
},
"messageHintText": "Ask Conduit",
"messageHintText": "Ask iiEasy",
"@messageHintText": {
"description": "Short placeholder text in the message input."
},
@@ -920,7 +924,7 @@
"@aboutApp": {
"description": "Settings tile title to view app information."
},
"aboutAppSubtitle": "Conduit information and links",
"aboutAppSubtitle": "iiEasy information and links",
"@aboutAppSubtitle": {
"description": "Subtitle/description for the About section."
},
@@ -1173,11 +1177,11 @@
"@themePalette": {
"description": "Title for selecting the app color palette."
},
"themePaletteConduitLabel": "Conduit",
"themePaletteConduitLabel": "iiEasy",
"@themePaletteConduitLabel": {
"description": "Palette name for the default Conduit theme."
},
"themePaletteConduitDescription": "Clean neutral theme designed for Conduit.",
"themePaletteConduitDescription": "Clean neutral theme designed for iiEasy.",
"@themePaletteConduitDescription": {
"description": "Description of the Conduit palette."
},
@@ -1225,7 +1229,7 @@
"@currentlyUsingLightTheme": {
"description": "Status text indicating light theme is active."
},
"aboutConduit": "About Conduit",
"aboutConduit": "About iiEasy",
"@aboutConduit": {
"description": "Dialog title for app information."
},
@@ -1241,7 +1245,7 @@
}
}
},
"githubRepository": "GitHub Repository",
"githubRepository": "iiEasy.ru",
"@githubRepository": {
"description": "Link label pointing to the app repository."
},
@@ -1325,7 +1329,7 @@
"@androidAssistantOverlayOption": {
"description": "Option label for keeping the current assistant overlay."
},
"androidAssistantNewChatOption": "Open Conduit with a new chat",
"androidAssistantNewChatOption": "Open iiEasy with a new chat",
"@androidAssistantNewChatOption": {
"description": "Option label for opening the app to a fresh chat from the assistant trigger."
},

View File

@@ -1,6 +1,7 @@
{
"@@locale": "es",
"appTitle": "Conduit",
"appTitle": "iiEasy",
"appSlogan": "Futuro. Simple.",
"retry": "Reintentar",
"back": "Atrás",
"you": "Tú",
@@ -15,8 +16,8 @@
"description": "Subtítulo que explica las acciones disponibles cuando no se puede acceder al servidor"
},
"account": "Cuenta",
"supportConduit": "Apoyar Conduit",
"supportConduitSubtitle": "Mantén Conduit independiente financiando el desarrollo continuo.",
"supportConduit": "Apoyar iiEasy",
"supportConduitSubtitle": "Mantén iiEasy independiente financiando el desarrollo continuo.",
"githubSponsorsTitle": "GitHub Sponsors",
"githubSponsorsSubtitle": "Conviértete en un patrocinador recurrente para financiar elementos del roadmap.",
"buyMeACoffeeTitle": "Buy Me a Coffee",
@@ -50,7 +51,7 @@
"password": "Contraseña",
"signInWithToken": "Iniciar sesión con token",
"connectToServer": "Conectar al servidor",
"enterServerAddress": "Ingresa la dirección de tu servidor Open-WebUI para comenzar",
"enterServerAddress": "Ingresa la dirección de tu servidor iiEasyWeb para comenzar",
"serverUrl": "URL del servidor",
"serverUrlHint": "https://tu-servidor.com",
"enterServerUrlSemantic": "Ingresa la URL o dirección IP de tu servidor",
@@ -116,7 +117,7 @@
"voicePromptTapStart": "Toca Iniciar para comenzar",
"voiceActionStop": "Detener",
"voiceActionStart": "Iniciar",
"messageHintText": "Pregunta a Conduit",
"messageHintText": "Pregunta a iiEasy",
"stopGenerating": "Detener generación",
"codeCopiedToClipboard": "Código copiado al portapapeles.",
"send": "Enviar",
@@ -204,7 +205,7 @@
"deleteFolderMessage": "Esta carpeta y sus referencias de asignación se eliminarán.",
"failedToDeleteFolder": "No se pudo eliminar la carpeta",
"aboutApp": "Acerca de",
"aboutAppSubtitle": "Información y enlaces de Conduit",
"aboutAppSubtitle": "Información y enlaces de iiEasy",
"web": "Web",
"imageGen": "Generación de imágenes",
"pinned": "Anclado",
@@ -280,7 +281,7 @@
"themeLight": "Claro",
"currentlyUsingDarkTheme": "Usando actualmente el tema oscuro",
"currentlyUsingLightTheme": "Usando actualmente el tema claro",
"aboutConduit": "Acerca de Conduit",
"aboutConduit": "Acerca de iiEasy",
"versionLabel": "Versión: {version} ({build})",
"@versionLabel": {
"placeholders": {
@@ -317,7 +318,7 @@
"androidAssistantTitle": "Android digital assistant",
"androidAssistantDescription": "Choose what happens when you trigger the Android digital assistant.",
"androidAssistantOverlayOption": "Show quick overlay (default)",
"androidAssistantNewChatOption": "Open Conduit with a new chat",
"androidAssistantNewChatOption": "Open iiEasy with a new chat",
"androidAssistantVoiceCallOption": "Start a voice call",
"sttSettings": "Voz a texto",
"sttEngineLabel": "Motor de reconocimiento",
@@ -650,11 +651,11 @@
"@nextLabel": {
"description": "Label for navigating to the next item."
},
"themePaletteConduitLabel": "Conduit",
"themePaletteConduitLabel": "iiEasy",
"@themePaletteConduitLabel": {
"description": "Palette name for the default Conduit theme."
},
"themePaletteConduitDescription": "Tema neutro y limpio diseñado para Conduit.",
"themePaletteConduitDescription": "Tema neutro y limpio diseñado para iiEasy.",
"@themePaletteConduitDescription": {
"description": "Description of the Conduit palette."
},

View File

@@ -1,6 +1,7 @@
{
"@@locale": "fr",
"appTitle": "Conduit",
"appTitle": "iiEasy",
"appSlogan": "Avenir. Simple.",
"retry": "Réessayer",
"back": "Retour",
"you": "Vous",
@@ -15,8 +16,8 @@
"description": "Sous-titre expliquant les actions possibles quand le serveur est injoignable"
},
"account": "Compte",
"supportConduit": "Soutenir Conduit",
"supportConduitSubtitle": "Financez le développement continu et les nouvelles fonctionnalités.",
"supportConduit": "Soutenir iiEasy",
"supportConduitSubtitle": "Soutenez iiEasy en finançant le développement continu.",
"githubSponsorsTitle": "GitHub Sponsors",
"githubSponsorsSubtitle": "Devenez sponsor récurrent pour soutenir la feuille de route.",
"buyMeACoffeeTitle": "Buy Me a Coffee",
@@ -50,7 +51,7 @@
"password": "Mot de passe",
"signInWithToken": "Se connecter avec un jeton",
"connectToServer": "Se connecter au serveur",
"enterServerAddress": "Saisissez l'adresse de votre serveur Open-WebUI pour commencer",
"enterServerAddress": "Saisissez l'adresse de votre serveur iiEasyWeb pour commencer",
"serverUrl": "URL du serveur",
"serverUrlHint": "https://votre-serveur.com",
"enterServerUrlSemantic": "Saisissez l'URL ou l'adresse IP de votre serveur",
@@ -116,7 +117,7 @@
"voicePromptTapStart": "Appuyez sur \"Démarrer\" pour commencer",
"voiceActionStop": "Arrêter",
"voiceActionStart": "Démarrer",
"messageHintText": "Demander à Conduit",
"messageHintText": "Demander à iiEasy",
"stopGenerating": "Arrêter la génération",
"codeCopiedToClipboard": "Code copié dans le presse-papiers.",
"send": "Envoyer",
@@ -204,7 +205,7 @@
"deleteFolderMessage": "Ce dossier et ses associations seront supprimés.",
"failedToDeleteFolder": "Échec de la suppression du dossier",
"aboutApp": "À propos",
"aboutAppSubtitle": "Informations et liens Conduit",
"aboutAppSubtitle": "Informations et liens iiEasy",
"web": "Web",
"imageGen": "Gén. image",
"pinned": "Épinglé",
@@ -280,7 +281,7 @@
"themeLight": "Clair",
"currentlyUsingDarkTheme": "Thème sombre actuellement utilisé",
"currentlyUsingLightTheme": "Thème clair actuellement utilisé",
"aboutConduit": "À propos de Conduit",
"aboutConduit": "À propos de iiEasy",
"versionLabel": "Version : {version} ({build})",
"@versionLabel": {
"placeholders": {
@@ -317,7 +318,7 @@
"androidAssistantTitle": "Android digital assistant",
"androidAssistantDescription": "Choose what happens when you trigger the Android digital assistant.",
"androidAssistantOverlayOption": "Show quick overlay (default)",
"androidAssistantNewChatOption": "Open Conduit with a new chat",
"androidAssistantNewChatOption": "Open iiEasy with a new chat",
"androidAssistantVoiceCallOption": "Start a voice call",
"sttSettings": "Voix vers texte",
"sttEngineLabel": "Moteur de reconnaissance",
@@ -650,11 +651,11 @@
"@nextLabel": {
"description": "Label for navigating to the next item."
},
"themePaletteConduitLabel": "Conduit",
"themePaletteConduitLabel": "iiEasy",
"@themePaletteConduitLabel": {
"description": "Palette name for the default Conduit theme."
},
"themePaletteConduitDescription": "Thème neutre et épuré conçu pour Conduit.",
"themePaletteConduitDescription": "Thème neutre et épuré conçu pour iiEasy.",
"@themePaletteConduitDescription": {
"description": "Description of the Conduit palette."
},

View File

@@ -1,6 +1,7 @@
{
"@@locale": "it",
"appTitle": "Conduit",
"appTitle": "iiEasy",
"appSlogan": "Futuro. Semplice.",
"retry": "Riprova",
"back": "Indietro",
"you": "Tu",
@@ -15,8 +16,8 @@
"description": "Sottotitolo che spiega le azioni disponibili quando il server non è raggiungibile"
},
"account": "Account",
"supportConduit": "Sostieni Conduit",
"supportConduitSubtitle": "Mantieni Conduit indipendente finanziando lo sviluppo continuo.",
"supportConduit": "Sostieni iiEasy",
"supportConduitSubtitle": "Mantieni iiEasy indipendente finanziando lo sviluppo continuo.",
"githubSponsorsTitle": "GitHub Sponsors",
"githubSponsorsSubtitle": "Diventa sponsor ricorrente per supportare la roadmap.",
"buyMeACoffeeTitle": "Buy Me a Coffee",
@@ -50,7 +51,7 @@
"password": "Password",
"signInWithToken": "Accedi con token",
"connectToServer": "Connetti al server",
"enterServerAddress": "Inserisci l'indirizzo del server Open-WebUI per iniziare",
"enterServerAddress": "Inserisci l'indirizzo del server iiEasyWeb per iniziare",
"serverUrl": "URL del server",
"serverUrlHint": "https://tuo-server.com",
"enterServerUrlSemantic": "Inserisci l'URL o l'indirizzo IP del server",
@@ -116,7 +117,7 @@
"voicePromptTapStart": "Tocca \"Avvia\" per iniziare",
"voiceActionStop": "Stop",
"voiceActionStart": "Avvia",
"messageHintText": "Chiedi a Conduit",
"messageHintText": "Chiedi a iiEasy",
"stopGenerating": "Interrompi generazione",
"codeCopiedToClipboard": "Codice copiato negli appunti.",
"send": "Invia",
@@ -204,7 +205,7 @@
"deleteFolderMessage": "Questa cartella e le sue associazioni verranno rimosse.",
"failedToDeleteFolder": "Impossibile eliminare la cartella",
"aboutApp": "Informazioni",
"aboutAppSubtitle": "Informazioni e link di Conduit",
"aboutAppSubtitle": "Informazioni e link di iiEasy",
"web": "Web",
"imageGen": "Gen. immagini",
"pinned": "Fissati",
@@ -280,7 +281,7 @@
"themeLight": "Chiaro",
"currentlyUsingDarkTheme": "Attualmente tema scuro",
"currentlyUsingLightTheme": "Attualmente tema chiaro",
"aboutConduit": "Informazioni su Conduit",
"aboutConduit": "Informazioni su iiEasy",
"versionLabel": "Versione: {version} ({build})",
"@versionLabel": {
"placeholders": {
@@ -317,7 +318,7 @@
"androidAssistantTitle": "Android digital assistant",
"androidAssistantDescription": "Choose what happens when you trigger the Android digital assistant.",
"androidAssistantOverlayOption": "Show quick overlay (default)",
"androidAssistantNewChatOption": "Open Conduit with a new chat",
"androidAssistantNewChatOption": "Open iiEasy with a new chat",
"androidAssistantVoiceCallOption": "Start a voice call",
"sttSettings": "Voce in testo",
"sttEngineLabel": "Motore di riconoscimento",
@@ -650,11 +651,11 @@
"@nextLabel": {
"description": "Label for navigating to the next item."
},
"themePaletteConduitLabel": "Conduit",
"themePaletteConduitLabel": "iiEasy",
"@themePaletteConduitLabel": {
"description": "Palette name for the default Conduit theme."
},
"themePaletteConduitDescription": "Tema neutro e pulito progettato per Conduit.",
"themePaletteConduitDescription": "Tema neutro e pulito progettato per iiEasy.",
"@themePaletteConduitDescription": {
"description": "Description of the Conduit palette."
},

View File

@@ -1,6 +1,7 @@
{
"@@locale": "ko",
"appTitle": "Conduit",
"appTitle": "iiEasy",
"appSlogan": "미래. 단순함.",
"retry": "다시 시도",
"back": "뒤로",
"you": "프로필",
@@ -9,8 +10,8 @@
"connectionIssueTitle": "서버에 연결할 수 없습니다",
"connectionIssueSubtitle": "다시 연결하거나 로그아웃하여 다른 서버를 선택하세요.",
"account": "계정",
"supportConduit": "Conduit 지원하기",
"supportConduitSubtitle": "지속적인 개발을 위해 자금을 지원하여 Conduit을 독립적으로 유지하세요.",
"supportConduit": "iiEasy 지원하기",
"supportConduitSubtitle": "지속적인 개발을 위해 자금을 지원하여 iiEasy를 독립적으로 유지하세요.",
"githubSponsorsTitle": "GitHub Sponsors",
"githubSponsorsSubtitle": "반복 후원자가 되어 로드맵 항목에 자금을 지원하세요.",
"buyMeACoffeeTitle": "Buy Me a Coffee",
@@ -91,7 +92,7 @@
"password": "비밀번호",
"signInWithToken": "토큰으로 로그인",
"connectToServer": "서버 연결",
"enterServerAddress": "시작하려면 Open-WebUI 서버 주소를 입력하세요",
"enterServerAddress": "시작하려면 iiEasyWeb 서버 주소를 입력하세요",
"serverUrl": "서버 URL",
"serverUrlHint": "https://your-server.com",
"enterServerUrlSemantic": "서버 URL 또는 IP 주소를 입력하세요",
@@ -179,7 +180,7 @@
"voiceCallSpeaking": "말하는 중",
"voiceCallDisconnected": "연결 끊김",
"voiceCallErrorHelp": "다음을 확인하세요:\n• 마이크 권한이 부여되었는지\n• 기기에서 음성 인식이 사용 가능한지\n• 서버에 연결되어 있는지",
"messageHintText": "Conduit에게 물어보기",
"messageHintText": "iiEasy에게 물어보기",
"stopGenerating": "생성 중지",
"send": "전송",
"codeCopiedToClipboard": "코드가 클립보드에 복사되었습니다.",
@@ -314,7 +315,7 @@
"deleteFolderMessage": "이 폴더와 할당 참조가 제거됩니다.",
"failedToDeleteFolder": "폴더 삭제 실패",
"aboutApp": "정보",
"aboutAppSubtitle": "Conduit 정보 및 링크",
"aboutAppSubtitle": "iiEasy 정보 및 링크",
"web": "웹",
"imageGen": "이미지 생성",
"pinned": "고정됨",
@@ -381,8 +382,8 @@
},
"themeDark": "다크",
"themePalette": "강조 색상 팔레트",
"themePaletteConduitLabel": "Conduit",
"themePaletteConduitDescription": "Conduit을 위해 설계된 깔끔한 중립 테마.",
"themePaletteConduitLabel": "iiEasy",
"themePaletteConduitDescription": "iiEasy을 위해 설계된 깔끔한 중립 테마.",
"themePaletteClaudeLabel": "Claude",
"themePaletteClaudeDescription": "Claude 웹 클라이언트에서 가져온 따뜻하고 촉각적인 팔레트.",
"themePaletteT3ChatLabel": "T3 Chat",
@@ -394,7 +395,7 @@
"themeLight": "라이트",
"currentlyUsingDarkTheme": "현재 다크 테마 사용 중",
"currentlyUsingLightTheme": "현재 라이트 테마 사용 중",
"aboutConduit": "Conduit 정보",
"aboutConduit": "iiEasy 정보",
"versionLabel": "버전: {version} ({build})",
"@versionLabel": {
"description": "Displays version and build number in the About dialog.",
@@ -443,7 +444,7 @@
"androidAssistantTitle": "Android digital assistant",
"androidAssistantDescription": "Choose what happens when you trigger the Android digital assistant.",
"androidAssistantOverlayOption": "Show quick overlay (default)",
"androidAssistantNewChatOption": "Open Conduit with a new chat",
"androidAssistantNewChatOption": "Open iiEasy with a new chat",
"androidAssistantVoiceCallOption": "Start a voice call",
"sttSettings": "음성 텍스트 변환",
"sttEngineLabel": "인식 엔진",

View File

@@ -1,6 +1,7 @@
{
"@@locale": "nl",
"appTitle": "Conduit",
"appTitle": "iiEasy",
"appSlogan": "Toekomst. Eenvoudig.",
"retry": "Opnieuw proberen",
"back": "Terug",
"you": "Jij",
@@ -15,8 +16,8 @@
"description": "Ondertitel die beschikbare acties uitlegt wanneer de server niet bereikbaar is"
},
"account": "Account",
"supportConduit": "Ondersteun Conduit",
"supportConduitSubtitle": "Houd Conduit onafhankelijk door doorlopende ontwikkeling te financieren.",
"supportConduit": "Ondersteun iiEasy",
"supportConduitSubtitle": "Houd iiEasy onafhankelijk door doorlopende ontwikkeling te financieren.",
"githubSponsorsTitle": "GitHub Sponsors",
"githubSponsorsSubtitle": "Word een terugkerende sponsor om roadmap-items te financieren.",
"buyMeACoffeeTitle": "Buy Me a Coffee",
@@ -50,7 +51,7 @@
"password": "Wachtwoord",
"signInWithToken": "Inloggen met token",
"connectToServer": "Verbinden met server",
"enterServerAddress": "Voer je Open-WebUI serveradres in om te beginnen",
"enterServerAddress": "Voer je iiEasyWeb serveradres in om te beginnen",
"serverUrl": "Server-URL",
"serverUrlHint": "https://jouw-server.com",
"enterServerUrlSemantic": "Voer je server-URL of IP-adres in",
@@ -116,7 +117,7 @@
"voicePromptTapStart": "Tik op Start om te beginnen",
"voiceActionStop": "Stop",
"voiceActionStart": "Start",
"messageHintText": "Vraag Conduit",
"messageHintText": "Vraag iiEasy",
"stopGenerating": "Stop met genereren",
"codeCopiedToClipboard": "Code gekopieerd naar klembord.",
"send": "Verzenden",
@@ -204,7 +205,7 @@
"deleteFolderMessage": "Deze map en zijn toewijzingen worden verwijderd.",
"failedToDeleteFolder": "Kan map niet verwijderen",
"aboutApp": "Over",
"aboutAppSubtitle": "Conduit informatie en links",
"aboutAppSubtitle": "iiEasy informatie en links",
"web": "Web",
"imageGen": "Afbeeldingsgeneratie",
"pinned": "Vastgepind",
@@ -280,7 +281,7 @@
"themeLight": "Licht",
"currentlyUsingDarkTheme": "Momenteel donker thema in gebruik",
"currentlyUsingLightTheme": "Momenteel licht thema in gebruik",
"aboutConduit": "Over Conduit",
"aboutConduit": "Over iiEasy",
"versionLabel": "Versie: {version} ({build})",
"@versionLabel": {
"placeholders": {
@@ -317,7 +318,7 @@
"androidAssistantTitle": "Android digital assistant",
"androidAssistantDescription": "Choose what happens when you trigger the Android digital assistant.",
"androidAssistantOverlayOption": "Show quick overlay (default)",
"androidAssistantNewChatOption": "Open Conduit with a new chat",
"androidAssistantNewChatOption": "Open iiEasy with a new chat",
"androidAssistantVoiceCallOption": "Start a voice call",
"sttSettings": "Spraak naar tekst",
"sttEngineLabel": "Herkenningsengine",
@@ -650,11 +651,11 @@
"@nextLabel": {
"description": "Label for navigating to the next item."
},
"themePaletteConduitLabel": "Conduit",
"themePaletteConduitLabel": "iiEasy",
"@themePaletteConduitLabel": {
"description": "Palette name for the default Conduit theme."
},
"themePaletteConduitDescription": "Strak neutraal thema ontworpen voor Conduit.",
"themePaletteConduitDescription": "Strak neutraal thema ontworpen voor iiEasy.",
"@themePaletteConduitDescription": {
"description": "Description of the Conduit palette."
},

View File

@@ -1,9 +1,10 @@
{
"@@locale": "ru",
"appTitle": "Conduit",
"appTitle": "iiEasy",
"retry": "Повторить",
"back": "Назад",
"you": "Вы",
"appSlogan": "Будущее. Просто.",
"loadingProfile": "Загрузка профиля...",
"pleaseCheckConnection": "Пожалуйста, проверьте соединение и повторите попытку",
"connectionIssueTitle": "Не удается подключиться к серверу",
@@ -15,8 +16,8 @@
"description": "Подзаголовок с доступными действиями при недоступности сервера"
},
"account": "Аккаунт",
"supportConduit": "Поддержать Conduit",
"supportConduitSubtitle": "Сохраните независимость Conduit, финансируя разработку.",
"supportConduit": "Поддержать iiEasy",
"supportConduitSubtitle": "Поддержите iiEasy, финансируя разработку.",
"githubSponsorsTitle": "GitHub Sponsors",
"githubSponsorsSubtitle": "Станьте регулярным спонсором для финансирования дорожной карты.",
"buyMeACoffeeTitle": "Buy Me a Coffee",
@@ -50,7 +51,7 @@
"password": "Пароль",
"signInWithToken": "Войти с помощью токена",
"connectToServer": "Подключиться к серверу",
"enterServerAddress": "Введите адрес вашего сервера Open-WebUI для начала",
"enterServerAddress": "Введите адрес вашего сервера iiEasyWeb для начала",
"serverUrl": "URL сервера",
"serverUrlHint": "https://your-server.com",
"enterServerUrlSemantic": "Введите URL или IP-адрес вашего сервера",
@@ -116,7 +117,7 @@
"voicePromptTapStart": "Нажмите «Начать» для запуска",
"voiceActionStop": "Стоп",
"voiceActionStart": "Начать",
"messageHintText": "Спросите Conduit",
"messageHintText": "Спросите iiEasy",
"stopGenerating": "Остановить генерацию",
"codeCopiedToClipboard": "Код скопирован в буфер обмена.",
"send": "Отправить",
@@ -204,7 +205,7 @@
"deleteFolderMessage": "Эта папка и ее ссылки будут удалены.",
"failedToDeleteFolder": "Не удалось удалить папку",
"aboutApp": "О",
"aboutAppSubtitle": "Информация и ссылки Conduit",
"aboutAppSubtitle": "Информация и ссылки iiEasy",
"web": "Веб",
"imageGen": "Генерация изображений",
"pinned": "Закреплено",
@@ -280,7 +281,7 @@
"themeLight": "Светлая",
"currentlyUsingDarkTheme": "Используется темная тема",
"currentlyUsingLightTheme": "Используется светлая тема",
"aboutConduit": "О Conduit",
"aboutConduit": "О iiEasy",
"versionLabel": "Версия: {version} ({build})",
"@versionLabel": {
"placeholders": {
@@ -317,7 +318,7 @@
"androidAssistantTitle": "Android digital assistant",
"androidAssistantDescription": "Choose what happens when you trigger the Android digital assistant.",
"androidAssistantOverlayOption": "Show quick overlay (default)",
"androidAssistantNewChatOption": "Open Conduit with a new chat",
"androidAssistantNewChatOption": "Open iiEasy with a new chat",
"androidAssistantVoiceCallOption": "Start a voice call",
"sttSettings": "Речь в текст",
"sttEngineLabel": "Движок распознавания",
@@ -650,11 +651,11 @@
"@nextLabel": {
"description": "Label for navigating to the next item."
},
"themePaletteConduitLabel": "Conduit",
"themePaletteConduitLabel": "iiEasy",
"@themePaletteConduitLabel": {
"description": "Palette name for the default Conduit theme."
},
"themePaletteConduitDescription": "Нейтральная чистая тема, созданная для Conduit.",
"themePaletteConduitDescription": "Нейтральная чистая тема, созданная для iiEasy.",
"@themePaletteConduitDescription": {
"description": "Description of the Conduit palette."
},

View File

@@ -1,6 +1,7 @@
{
"@@locale": "zh",
"appTitle": "Conduit",
"appTitle": "iiEasy",
"appSlogan": "未来。简单。",
"retry": "重试",
"back": "返回",
"you": "你",
@@ -15,8 +16,8 @@
"description": "当无法访问服务器时解释可用操作的副标题"
},
"account": "账户",
"supportConduit": "支持 Conduit",
"supportConduitSubtitle": "通过资助持续开发来保持 Conduit 的独立性。",
"supportConduit": "支持 iiEasy",
"supportConduitSubtitle": "通过资助持续开发来保持 iiEasy 的独立性。",
"githubSponsorsTitle": "GitHub 赞助",
"githubSponsorsSubtitle": "成为定期赞助者以资助路线图项目。",
"buyMeACoffeeTitle": "Buy Me a Coffee",
@@ -50,7 +51,7 @@
"password": "密码",
"signInWithToken": "使用令牌登录",
"connectToServer": "连接到服务器",
"enterServerAddress": "输入您的 Open-WebUI 服务器地址以开始",
"enterServerAddress": "输入您的 iiEasyWeb 服务器地址以开始",
"serverUrl": "服务器 URL",
"serverUrlHint": "https://your-server.com",
"enterServerUrlSemantic": "输入您的服务器 URL 或 IP 地址",
@@ -116,7 +117,7 @@
"voicePromptTapStart": "点击开始以开始",
"voiceActionStop": "停止",
"voiceActionStart": "开始",
"messageHintText": "问 Conduit",
"messageHintText": "问 iiEasy",
"stopGenerating": "停止生成",
"codeCopiedToClipboard": "代码已复制到剪贴板。",
"send": "发送",
@@ -204,7 +205,7 @@
"deleteFolderMessage": "此文件夹及其分配引用将被删除。",
"failedToDeleteFolder": "无法删除文件夹",
"aboutApp": "关于",
"aboutAppSubtitle": "Conduit 信息和链接",
"aboutAppSubtitle": "iiEasy 信息和链接",
"web": "网页",
"imageGen": "图像生成",
"pinned": "已置顶",
@@ -280,7 +281,7 @@
"themeLight": "浅色",
"currentlyUsingDarkTheme": "当前使用深色主题",
"currentlyUsingLightTheme": "当前使用浅色主题",
"aboutConduit": "关于 Conduit",
"aboutConduit": "关于 iiEasy",
"versionLabel": "版本:{version}{build}",
"@versionLabel": {
"placeholders": {
@@ -317,7 +318,7 @@
"androidAssistantTitle": "Android digital assistant",
"androidAssistantDescription": "Choose what happens when you trigger the Android digital assistant.",
"androidAssistantOverlayOption": "Show quick overlay (default)",
"androidAssistantNewChatOption": "Open Conduit with a new chat",
"androidAssistantNewChatOption": "Open iiEasy with a new chat",
"androidAssistantVoiceCallOption": "Start a voice call",
"sttSettings": "语音转文字",
"sttEngineLabel": "识别引擎",
@@ -650,11 +651,11 @@
"@nextLabel": {
"description": "Label for navigating to the next item."
},
"themePaletteConduitLabel": "Conduit",
"themePaletteConduitLabel": "iiEasy",
"@themePaletteConduitLabel": {
"description": "Palette name for the default Conduit theme."
},
"themePaletteConduitDescription": "为 Conduit 设计的简洁中性色主题。",
"themePaletteConduitDescription": "为 iiEasy 设计的简洁中性色主题。",
"@themePaletteConduitDescription": {
"description": "Description of the Conduit palette."
},

View File

@@ -1,6 +1,7 @@
{
"@@locale": "zh_Hant",
"appTitle": "Conduit",
"appTitle": "iiEasy",
"appSlogan": "未來。簡單。",
"retry": "重試",
"back": "返回",
"you": "你",
@@ -15,8 +16,8 @@
"description": "当无法访问服务器时解释可用操作的副标题"
},
"account": "賬戶",
"supportConduit": "支持 Conduit",
"supportConduitSubtitle": "通過資助持續開發來保持 Conduit 的獨立性。",
"supportConduit": "支持 iiEasy",
"supportConduitSubtitle": "通過資助持續開發來保持 iiEasy 的獨立性。",
"githubSponsorsTitle": "GitHub 贊助",
"githubSponsorsSubtitle": "成爲定期贊助者以資助路線圖項目。",
"buyMeACoffeeTitle": "Buy Me a Coffee",
@@ -50,7 +51,7 @@
"password": "密碼",
"signInWithToken": "使用令牌登錄",
"connectToServer": "連接到服務器",
"enterServerAddress": "輸入您的 Open-WebUI 服務器地址以開始",
"enterServerAddress": "輸入您的 iiEasyWeb 服務器地址以開始",
"serverUrl": "服務器 URL",
"serverUrlHint": "https://your-server.com",
"enterServerUrlSemantic": "輸入您的服務器 URL 或 IP 地址",
@@ -116,7 +117,7 @@
"voicePromptTapStart": "點擊開始以開始",
"voiceActionStop": "停止",
"voiceActionStart": "開始",
"messageHintText": "問 Conduit",
"messageHintText": "問 iiEasy",
"stopGenerating": "停止生成",
"codeCopiedToClipboard": "代碼已複製到剪貼板。",
"send": "發送",
@@ -204,7 +205,7 @@
"deleteFolderMessage": "此文件夾及其分配引用將被刪除。",
"failedToDeleteFolder": "無法刪除文件夾",
"aboutApp": "關於",
"aboutAppSubtitle": "Conduit 信息和鏈接",
"aboutAppSubtitle": "iiEasy 信息和鏈接",
"web": "網頁",
"imageGen": "圖像生成",
"pinned": "已置頂",
@@ -280,7 +281,7 @@
"themeLight": "淺色",
"currentlyUsingDarkTheme": "當前使用深色主題",
"currentlyUsingLightTheme": "當前使用淺色主題",
"aboutConduit": "關於 Conduit",
"aboutConduit": "關於 iiEasy",
"versionLabel": "版本:{version}{build}",
"@versionLabel": {
"placeholders": {
@@ -317,7 +318,7 @@
"androidAssistantTitle": "Android digital assistant",
"androidAssistantDescription": "Choose what happens when you trigger the Android digital assistant.",
"androidAssistantOverlayOption": "Show quick overlay (default)",
"androidAssistantNewChatOption": "Open Conduit with a new chat",
"androidAssistantNewChatOption": "Open iiEasy with a new chat",
"androidAssistantVoiceCallOption": "Start a voice call",
"sttSettings": "語音轉文字",
"sttEngineLabel": "識別引擎",
@@ -650,11 +651,11 @@
"@nextLabel": {
"description": "Label for navigating to the next item."
},
"themePaletteConduitLabel": "Conduit",
"themePaletteConduitLabel": "iiEasy",
"@themePaletteConduitLabel": {
"description": "Palette name for the default Conduit theme."
},
"themePaletteConduitDescription": "爲 Conduit 設計的簡潔中性色主題。",
"themePaletteConduitDescription": "爲 iiEasy 設計的簡潔中性色主題。",
"@themePaletteConduitDescription": {
"description": "Description of the Conduit palette."
},

View File

@@ -1,6 +1,7 @@
import 'package:flutter/material.dart';
import '../theme/theme_extensions.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter_svg/flutter_svg.dart';
import 'dart:io' show Platform;
import '../theme/color_tokens.dart';
import '../theme/tweakcn_themes.dart';
@@ -97,6 +98,21 @@ class BrandService {
return iconWidget;
}
/// iiEasy logo image (SVG): light theme uses logo.svg, dark uses logo_dark.svg.
static Widget createLogoImage({
required double size,
BuildContext? context,
}) {
final isDark = context != null && Theme.of(context).brightness == Brightness.dark;
final asset = isDark ? 'assets/icons/logo_dark.svg' : 'assets/icons/logo.svg';
return SvgPicture.asset(
asset,
width: size,
height: size,
fit: BoxFit.contain,
);
}
/// Creates a branded avatar with the hub icon
static Widget createBrandAvatar({
double size = 40,

View File

@@ -0,0 +1,186 @@
import 'dart:math' as math;
import 'package:flutter/material.dart';
import 'package:conduit/l10n/app_localizations.dart';
/// Animated iiEasy logo: 3 rotating dashed rings, exactly as in the reference HTML.
/// - Outer: rotate 0° → 360°, 10s
/// - Middle: rotate 30° → -330°, 12s
/// - Inner: rotate 60° → 420°, 8s
class IiEasyLoadingLogo extends StatefulWidget {
final bool isDark;
const IiEasyLoadingLogo({super.key, required this.isDark});
@override
State<IiEasyLoadingLogo> createState() => _IiEasyLoadingLogoState();
}
class _IiEasyLoadingLogoState extends State<IiEasyLoadingLogo>
with TickerProviderStateMixin {
late AnimationController _outerController;
late AnimationController _middleController;
late AnimationController _innerController;
@override
void initState() {
super.initState();
_outerController = AnimationController(
vsync: this,
duration: const Duration(seconds: 10),
)..repeat();
_middleController = AnimationController(
vsync: this,
duration: const Duration(seconds: 12),
)..repeat();
_innerController = AnimationController(
vsync: this,
duration: const Duration(seconds: 8),
)..repeat();
}
@override
void dispose() {
_outerController.dispose();
_middleController.dispose();
_innerController.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
final strokeColor = widget.isDark ? Colors.white : const Color(0xFF1F2937);
final textColor = widget.isDark ? Colors.white : const Color(0xFF1F2937);
final sloganColor = widget.isDark ? Colors.grey.shade300 : Colors.grey.shade700;
return ColoredBox(
color: widget.isDark ? const Color(0xFF000000) : Colors.white,
child: SafeArea(
child: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
SizedBox(
width: 200,
height: 200,
child: AnimatedBuilder(
animation: Listenable.merge([
_outerController,
_middleController,
_innerController,
]),
builder: (context, _) {
return CustomPaint(
painter: _RingsPainter(
strokeColor: strokeColor,
outerTurns: _outerController.value,
middleTurns: _middleController.value,
innerTurns: _innerController.value,
),
size: const Size(200, 200),
);
},
),
),
const SizedBox(height: 32),
Text(
'iiEasy',
style: TextStyle(
fontSize: 48,
fontWeight: FontWeight.bold,
color: textColor,
letterSpacing: -1,
),
),
const SizedBox(height: 8),
Text(
AppLocalizations.of(context)?.appSlogan ?? 'Future. Simple.',
style: TextStyle(
fontSize: 20,
fontWeight: FontWeight.w500,
color: sloganColor,
letterSpacing: 0.5,
),
),
],
),
),
),
);
}
}
/// Draws 3 dashed circles with rotation exactly as in HTML.
/// viewBox 0 0 200 200, center (100, 100).
class _RingsPainter extends CustomPainter {
_RingsPainter({
required this.strokeColor,
required this.outerTurns,
required this.middleTurns,
required this.innerTurns,
});
final Color strokeColor;
final double outerTurns; // 0..1 → 0°..360°
final double middleTurns; // 0..1 → 30°..-330° (so use 30/360 + (1-t)*2 - 1?)
final double innerTurns; // 0..1 → 60°..420°
static const double cx = 100;
static const double cy = 100;
@override
void paint(Canvas canvas, Size size) {
final scale = size.width / 200;
canvas.save();
canvas.scale(scale);
final paint = Paint()
..color = strokeColor
..style = PaintingStyle.stroke
..strokeWidth = 6
..strokeCap = StrokeCap.butt;
// Outer: 0° → 360° in 10s. angle = outerTurns * 2*pi
_drawDashedCircle(canvas, paint, cx, cy, 70, [15, 85], outerTurns * 2 * math.pi);
// Middle: 30° → -330° in 12s. So angle = 30° + (1 - middleTurns) * 360° going backwards
// 30° = 30/360 * 2pi, -330° = -330/360 * 2pi = 30/360 * 2pi - 2pi
final middleAngle = (30 / 360) * 2 * math.pi - middleTurns * 2 * math.pi;
_drawDashedCircle(canvas, paint, cx, cy, 50, [12, 58], middleAngle);
// Inner: 60° → 420° in 8s. angle = 60° + innerTurns * 360°
final innerAngle = (60 / 360) * 2 * math.pi + innerTurns * 2 * math.pi;
_drawDashedCircle(canvas, paint, cx, cy, 30, [8, 32], innerAngle);
canvas.restore();
}
void _drawDashedCircle(Canvas canvas, Paint paint, double cx, double cy,
double r, List<double> dashArray, double startAngleRad) {
const twoPi = 2 * math.pi;
// SVG stroke-dasharray: dash and gap in user units along the path.
// Arc length = r * angle, so angle = length / r.
final dashAngle = dashArray[0] / r;
final gapAngle = dashArray[1] / r;
final periodAngle = dashAngle + gapAngle;
double angle = startAngleRad;
final rect = Rect.fromCircle(center: Offset(cx, cy), radius: r);
while (angle < startAngleRad + twoPi) {
final sweep = math.min(dashAngle, startAngleRad + twoPi - angle);
if (sweep > 0.001) {
final path = Path()..arcTo(rect, angle, sweep, false);
canvas.drawPath(path, paint);
}
angle += periodAngle;
}
}
@override
bool shouldRepaint(_RingsPainter oldDelegate) {
return oldDelegate.outerTurns != outerTurns ||
oldDelegate.middleTurns != middleTurns ||
oldDelegate.innerTurns != innerTurns ||
oldDelegate.strokeColor != strokeColor;
}
}