🌐 Localized files
This commit is contained in:
@@ -1343,5 +1343,133 @@
|
|||||||
"clearCompleted": "Clear Completed",
|
"clearCompleted": "Clear Completed",
|
||||||
"contentCantEmpty": "Content cannot be empty",
|
"contentCantEmpty": "Content cannot be empty",
|
||||||
"features": "Features",
|
"features": "Features",
|
||||||
"unnamed": "Unnamed"
|
"unnamed": "Unnamed",
|
||||||
|
"fundEnvelopeLoadFailed": "Failed to load fund envelope",
|
||||||
|
"fundEnvelope": "Fund Envelope",
|
||||||
|
"fundEnvelopeRemaining": "Remaining: {} {}",
|
||||||
|
"fundEnvelopeSplit": "Split: {}",
|
||||||
|
"fundEnvelopeSplitEvenly": "Evenly",
|
||||||
|
"fundEnvelopeSplitRandomly": "Randomly",
|
||||||
|
"fundEnvelopeClaimSuccess": "Fund claimed successfully!",
|
||||||
|
"fundEnvelopeStatusCreated": "Created",
|
||||||
|
"fundEnvelopeStatusPartial": "Partially Claimed",
|
||||||
|
"fundEnvelopeStatusCompleted": "Fully Claimed",
|
||||||
|
"fundEnvelopeStatusExpired": "Expired",
|
||||||
|
"fundEnvelopeStatusUnknown": "Unknown",
|
||||||
|
"fundEnvelopeRecipients": "Recipients ({}/{} claimed)",
|
||||||
|
"fundEnvelopeExpiredDaysAgo": {
|
||||||
|
"one": "Expired {} day ago",
|
||||||
|
"other": "Expired {} days ago"
|
||||||
|
},
|
||||||
|
"fundEnvelopeExpiresSoon": "Expires soon",
|
||||||
|
"fundEnvelopeExpiresInHours": {
|
||||||
|
"one": "Expires in {} hour",
|
||||||
|
"other": "Expires in {} hours"
|
||||||
|
},
|
||||||
|
"fundEnvelopeExpiresInDays": {
|
||||||
|
"one": "Expires in {} day",
|
||||||
|
"other": "Expires in {} days"
|
||||||
|
},
|
||||||
|
"fundEnvelopeRemainingWithSplits": "{} {} / {} splits",
|
||||||
|
"fundEnvelopeUnknownUser": "Unknown User",
|
||||||
|
"deleteSite": "Delete Site",
|
||||||
|
"deleteSiteConfirm": "Are you sure you want to delete this site?",
|
||||||
|
"siteDeletedSuccess": "Site deleted successfully",
|
||||||
|
"siteSlug": "Slug",
|
||||||
|
"siteSlugHint": "my-site",
|
||||||
|
"siteSlugRequired": "Please enter a slug",
|
||||||
|
"siteSlugInvalid": "Slug can only contain lowercase letters, numbers, and dashes",
|
||||||
|
"siteName": "Site Name",
|
||||||
|
"siteNameHint": "My Publication Site",
|
||||||
|
"siteNameRequired": "Please enter a site name",
|
||||||
|
"siteMode": "Mode",
|
||||||
|
"siteModeFullyManaged": "Fully Managed",
|
||||||
|
"siteModeSelfManaged": "Self-Managed",
|
||||||
|
"editPublicationSite": "Edit Publication Site",
|
||||||
|
"deletePublicationSite": "Delete Publication Site",
|
||||||
|
"publicationSiteSavedSuccess": "Publication site saved successfully",
|
||||||
|
"publicationSiteDeleteConfirm": "Are you sure you want to delete this publication site? This action cannot be undone.",
|
||||||
|
"publicationSiteDeletedSuccess": "Publication site deleted successfully",
|
||||||
|
"newPublicationSite": "New Publication Site",
|
||||||
|
"siteDetails": "Site Details",
|
||||||
|
"siteInformation": "Site Information",
|
||||||
|
"siteDomain": "Domain",
|
||||||
|
"siteCreated": "Created",
|
||||||
|
"siteUpdated": "Updated",
|
||||||
|
"failedToLoadSite": "Failed to load site",
|
||||||
|
"sitePages": "Pages",
|
||||||
|
"noPagesYet": "No pages yet",
|
||||||
|
"createFirstPage": "Create your first page to get started",
|
||||||
|
"failedToLoadPages": "Failed to load pages",
|
||||||
|
"fileManagement": "File Management",
|
||||||
|
"siteFiles": "Files",
|
||||||
|
"siteFolder": "Folder",
|
||||||
|
"siteRoot": "Root",
|
||||||
|
"noFilesUploadedYet": "No files uploaded yet",
|
||||||
|
"uploadFirstFile": "Upload your first file to get started",
|
||||||
|
"failedToLoadFiles": "Failed to load files",
|
||||||
|
"noFilesFoundInFolder": "No files found in the selected folder",
|
||||||
|
"fileActions": "File Actions",
|
||||||
|
"purgeFiles": "Purge Files",
|
||||||
|
"purgeFilesDescription": "Remove all uploaded files from the site",
|
||||||
|
"deploySite": "Deploy Site",
|
||||||
|
"deploySiteDescription": "Upload and deploy a new version from ZIP archive",
|
||||||
|
"confirmPurge": "Confirm Purge",
|
||||||
|
"purgeFilesConfirm": "This will permanently delete all files uploaded to this site. This action cannot be undone. Are you sure you want to continue?",
|
||||||
|
"purgeAllFiles": "Purge All Files",
|
||||||
|
"allFilesPurgedSuccess": "All files purged successfully",
|
||||||
|
"failedToPurgeFiles": "Failed to purge files: {}",
|
||||||
|
"siteDeployedSuccess": "Site deployed successfully",
|
||||||
|
"failedToDeploySite": "Failed to deploy site: {}",
|
||||||
|
"createPage": "Create Page",
|
||||||
|
"editPage": "Edit Page",
|
||||||
|
"pageType": "Page Type",
|
||||||
|
"htmlPage": "HTML Page",
|
||||||
|
"redirectPage": "Redirect Page",
|
||||||
|
"pageTypeRequired": "Please select a page type",
|
||||||
|
"pagePath": "Page Path",
|
||||||
|
"pagePathHint": "/about, /contact, etc.",
|
||||||
|
"pagePathRequired": "Please enter a page path",
|
||||||
|
"pagePathInvalid": "Page path can only contain letters, numbers, hyphens, underscores, and slashes",
|
||||||
|
"pagePathMustStartWithSlash": "Page path must start with /",
|
||||||
|
"pagePathNoConsecutiveSlashes": "Page path cannot have consecutive slashes",
|
||||||
|
"pageTitle": "Page Title",
|
||||||
|
"pageTitleHint": "About Us, Contact, etc.",
|
||||||
|
"pageTitleRequired": "Please enter a page title",
|
||||||
|
"pageContentHtml": "Page Content (HTML)",
|
||||||
|
"pageContentHint": "<h1>Hello World</h1><p>This is my page content...</p>",
|
||||||
|
"pageContentRequired": "Please enter HTML content for the page",
|
||||||
|
"redirectTarget": "Redirect Target",
|
||||||
|
"redirectTargetHint": "/new-page, https://example.com, etc.",
|
||||||
|
"redirectTargetRequired": "Please enter a redirect target",
|
||||||
|
"redirectTargetInvalid": "Target must be a relative path (/) or absolute URL (http/https)",
|
||||||
|
"deletePage": "Delete Page",
|
||||||
|
"deletePageConfirm": "Are you sure you want to delete this page?",
|
||||||
|
"savePage": "Save Page",
|
||||||
|
"pageCreatedSuccess": "Page created successfully",
|
||||||
|
"pageUpdatedSuccess": "Page updated successfully",
|
||||||
|
"pageDeletedSuccess": "Page deleted successfully",
|
||||||
|
"uploadFiles": "Upload Files",
|
||||||
|
"uploadPath": "Upload Path",
|
||||||
|
"uploadPathHint": "/ (root) or /assets/images/",
|
||||||
|
"uploadPathRequired": "Please enter an upload path",
|
||||||
|
"uploadPathMustStartWithSlash": "Path must start with /",
|
||||||
|
"uploadPathNoSpaces": "Path cannot contain spaces",
|
||||||
|
"uploadPathNoConsecutiveSlashes": "Path cannot have consecutive slashes",
|
||||||
|
"percentCompleted": "{}% completed",
|
||||||
|
"filesToUpload": "{} files to upload",
|
||||||
|
"fileSizeKb": "Size: {} KB",
|
||||||
|
"uploadingEllipsis": "Uploading...",
|
||||||
|
"uploadFilesCount": {
|
||||||
|
"one": "Upload {} File",
|
||||||
|
"other": "Upload {} Files"
|
||||||
|
},
|
||||||
|
"allUploadsCompleted": "All uploads completed",
|
||||||
|
"someUploadsFailed": "Some uploads failed",
|
||||||
|
"uploadingInProgress": "Uploading in progress",
|
||||||
|
"readyToUpload": "Ready to upload",
|
||||||
|
"allFilesUploadedSuccess": "All files uploaded successfully",
|
||||||
|
"lotteryLastNumberSpecial": "The last selected number will be your special number.",
|
||||||
|
"lotteryMultiplierRequired": "Please enter a multiplier",
|
||||||
|
"lotteryMultiplierRange": "Multiplier must be between 1 and 10"
|
||||||
}
|
}
|
||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import 'package:easy_localization/easy_localization.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:gap/gap.dart';
|
import 'package:gap/gap.dart';
|
||||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
@@ -54,7 +55,7 @@ class PublicationSiteDetailScreen extends HookConsumerWidget {
|
|||||||
appBar: AppBar(
|
appBar: AppBar(
|
||||||
title: siteAsync.maybeWhen(
|
title: siteAsync.maybeWhen(
|
||||||
data: (site) => Text(site.name),
|
data: (site) => Text(site.name),
|
||||||
orElse: () => const Text('Site Details'),
|
orElse: () => Text('siteDetails'.tr()),
|
||||||
),
|
),
|
||||||
actions: [
|
actions: [
|
||||||
siteAsync.maybeWhen(
|
siteAsync.maybeWhen(
|
||||||
@@ -105,26 +106,26 @@ class PublicationSiteDetailScreen extends HookConsumerWidget {
|
|||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
Text(
|
Text(
|
||||||
'Site Information',
|
'siteInformation'.tr(),
|
||||||
style: theme.textTheme.titleMedium
|
style: theme.textTheme.titleMedium
|
||||||
?.copyWith(fontWeight: FontWeight.bold),
|
?.copyWith(fontWeight: FontWeight.bold),
|
||||||
),
|
),
|
||||||
const Gap(16),
|
const Gap(16),
|
||||||
InfoRow(
|
InfoRow(
|
||||||
label: 'Name',
|
label: 'name'.tr(),
|
||||||
value: site.name,
|
value: site.name,
|
||||||
icon: Symbols.title,
|
icon: Symbols.title,
|
||||||
),
|
),
|
||||||
const Gap(8),
|
const Gap(8),
|
||||||
InfoRow(
|
InfoRow(
|
||||||
label: 'Slug',
|
label: 'slug'.tr(),
|
||||||
value: site.slug,
|
value: site.slug,
|
||||||
icon: Symbols.tag,
|
icon: Symbols.tag,
|
||||||
monospace: true,
|
monospace: true,
|
||||||
),
|
),
|
||||||
const Gap(8),
|
const Gap(8),
|
||||||
InfoRow(
|
InfoRow(
|
||||||
label: 'Domain',
|
label: 'siteDomain'.tr(),
|
||||||
value: '${site.slug}.solian.page',
|
value: '${site.slug}.solian.page',
|
||||||
icon: Symbols.globe,
|
icon: Symbols.globe,
|
||||||
monospace: true,
|
monospace: true,
|
||||||
@@ -136,31 +137,31 @@ class PublicationSiteDetailScreen extends HookConsumerWidget {
|
|||||||
),
|
),
|
||||||
const Gap(8),
|
const Gap(8),
|
||||||
InfoRow(
|
InfoRow(
|
||||||
label: 'Mode',
|
label: 'siteMode'.tr(),
|
||||||
value:
|
value:
|
||||||
site.mode == 0
|
site.mode == 0
|
||||||
? 'Fully Managed'
|
? 'siteModeFullyManaged'.tr()
|
||||||
: 'Self-Managed',
|
: 'siteModeSelfManaged'.tr(),
|
||||||
icon: Symbols.settings,
|
icon: Symbols.settings,
|
||||||
),
|
),
|
||||||
if (site.description != null &&
|
if (site.description != null &&
|
||||||
site.description!.isNotEmpty) ...[
|
site.description!.isNotEmpty) ...[
|
||||||
const Gap(8),
|
const Gap(8),
|
||||||
InfoRow(
|
InfoRow(
|
||||||
label: 'Description',
|
label: 'description'.tr(),
|
||||||
value: site.description!,
|
value: site.description!,
|
||||||
icon: Symbols.description,
|
icon: Symbols.description,
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
const Gap(8),
|
const Gap(8),
|
||||||
InfoRow(
|
InfoRow(
|
||||||
label: 'Created',
|
label: 'siteCreated'.tr(),
|
||||||
value: site.createdAt.formatSystem(),
|
value: site.createdAt.formatSystem(),
|
||||||
icon: Symbols.calendar_add_on,
|
icon: Symbols.calendar_add_on,
|
||||||
),
|
),
|
||||||
const Gap(8),
|
const Gap(8),
|
||||||
InfoRow(
|
InfoRow(
|
||||||
label: 'Updated',
|
label: 'siteUpdated'.tr(),
|
||||||
value: site.updatedAt.formatSystem(),
|
value: site.updatedAt.formatSystem(),
|
||||||
icon: Symbols.update,
|
icon: Symbols.update,
|
||||||
),
|
),
|
||||||
@@ -191,7 +192,7 @@ class PublicationSiteDetailScreen extends HookConsumerWidget {
|
|||||||
mainAxisAlignment: MainAxisAlignment.center,
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
children: [
|
children: [
|
||||||
Text(
|
Text(
|
||||||
'Failed to load site',
|
'failedToLoadSite'.tr(),
|
||||||
style: Theme.of(context).textTheme.headlineSmall,
|
style: Theme.of(context).textTheme.headlineSmall,
|
||||||
),
|
),
|
||||||
const Gap(16),
|
const Gap(16),
|
||||||
@@ -202,7 +203,7 @@ class PublicationSiteDetailScreen extends HookConsumerWidget {
|
|||||||
() => ref.invalidate(
|
() => ref.invalidate(
|
||||||
publicationSiteDetailProvider(pubName, siteSlug),
|
publicationSiteDetailProvider(pubName, siteSlug),
|
||||||
),
|
),
|
||||||
child: const Text('Retry'),
|
child: Text('retry'.tr()),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -31,20 +31,20 @@ class SiteForm extends HookConsumerWidget {
|
|||||||
children: [
|
children: [
|
||||||
TextFormField(
|
TextFormField(
|
||||||
controller: slugController,
|
controller: slugController,
|
||||||
decoration: const InputDecoration(
|
decoration: InputDecoration(
|
||||||
labelText: 'Slug',
|
labelText: 'siteSlug'.tr(),
|
||||||
hintText: 'my-site',
|
hintText: 'siteSlugHint'.tr(),
|
||||||
border: OutlineInputBorder(
|
border: OutlineInputBorder(
|
||||||
borderRadius: BorderRadius.all(Radius.circular(12)),
|
borderRadius: BorderRadius.all(Radius.circular(12)),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
validator: (value) {
|
validator: (value) {
|
||||||
if (value == null || value.isEmpty) {
|
if (value == null || value.isEmpty) {
|
||||||
return 'Please enter a slug';
|
return 'siteSlugRequired'.tr();
|
||||||
}
|
}
|
||||||
final slugRegex = RegExp(r'^[a-z0-9]+(?:-[a-z0-9]+)*$');
|
final slugRegex = RegExp(r'^[a-z0-9]+(?:-[a-z0-9]+)*$');
|
||||||
if (!slugRegex.hasMatch(value)) {
|
if (!slugRegex.hasMatch(value)) {
|
||||||
return 'Slug can only contain lowercase letters, numbers, and dashes';
|
return 'siteSlugInvalid'.tr();
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
},
|
},
|
||||||
@@ -53,16 +53,16 @@ class SiteForm extends HookConsumerWidget {
|
|||||||
const SizedBox(height: 16),
|
const SizedBox(height: 16),
|
||||||
TextFormField(
|
TextFormField(
|
||||||
controller: nameController,
|
controller: nameController,
|
||||||
decoration: const InputDecoration(
|
decoration: InputDecoration(
|
||||||
labelText: 'Site Name',
|
labelText: 'siteName'.tr(),
|
||||||
hintText: 'My Publication Site',
|
hintText: 'siteNameHint'.tr(),
|
||||||
border: OutlineInputBorder(
|
border: OutlineInputBorder(
|
||||||
borderRadius: BorderRadius.all(Radius.circular(12)),
|
borderRadius: BorderRadius.all(Radius.circular(12)),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
validator: (value) {
|
validator: (value) {
|
||||||
if (value == null || value.isEmpty) {
|
if (value == null || value.isEmpty) {
|
||||||
return 'Please enter a site name';
|
return 'siteNameRequired'.tr();
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
},
|
},
|
||||||
@@ -71,8 +71,8 @@ class SiteForm extends HookConsumerWidget {
|
|||||||
const SizedBox(height: 16),
|
const SizedBox(height: 16),
|
||||||
TextFormField(
|
TextFormField(
|
||||||
controller: descriptionController,
|
controller: descriptionController,
|
||||||
decoration: const InputDecoration(
|
decoration: InputDecoration(
|
||||||
labelText: 'Description',
|
labelText: 'description'.tr(),
|
||||||
alignLabelWithHint: true,
|
alignLabelWithHint: true,
|
||||||
border: OutlineInputBorder(
|
border: OutlineInputBorder(
|
||||||
borderRadius: BorderRadius.all(Radius.circular(12)),
|
borderRadius: BorderRadius.all(Radius.circular(12)),
|
||||||
@@ -84,15 +84,18 @@ class SiteForm extends HookConsumerWidget {
|
|||||||
const SizedBox(height: 16),
|
const SizedBox(height: 16),
|
||||||
DropdownButtonFormField<int>(
|
DropdownButtonFormField<int>(
|
||||||
value: modeController.value,
|
value: modeController.value,
|
||||||
decoration: const InputDecoration(
|
decoration: InputDecoration(
|
||||||
labelText: 'Mode',
|
labelText: 'siteMode'.tr(),
|
||||||
border: OutlineInputBorder(
|
border: OutlineInputBorder(
|
||||||
borderRadius: BorderRadius.all(Radius.circular(12)),
|
borderRadius: BorderRadius.all(Radius.circular(12)),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
items: const [
|
items: [
|
||||||
DropdownMenuItem(value: 0, child: Text('Fully Managed')),
|
DropdownMenuItem(
|
||||||
DropdownMenuItem(value: 1, child: Text('Self-Managed')),
|
value: 0,
|
||||||
|
child: Text('siteModeFullyManaged'.tr()),
|
||||||
|
),
|
||||||
|
DropdownMenuItem(value: 1, child: Text('siteModeSelfManaged'.tr())),
|
||||||
],
|
],
|
||||||
onChanged: (value) {
|
onChanged: (value) {
|
||||||
if (value != null) {
|
if (value != null) {
|
||||||
@@ -104,7 +107,7 @@ class SiteForm extends HookConsumerWidget {
|
|||||||
).padding(all: 20);
|
).padding(all: 20);
|
||||||
|
|
||||||
return SheetScaffold(
|
return SheetScaffold(
|
||||||
titleText: 'Edit Publication Site',
|
titleText: 'editPublicationSite'.tr(),
|
||||||
child: Builder(
|
child: Builder(
|
||||||
builder:
|
builder:
|
||||||
(context) => SingleChildScrollView(
|
(context) => SingleChildScrollView(
|
||||||
@@ -116,7 +119,7 @@ class SiteForm extends HookConsumerWidget {
|
|||||||
TextButton.icon(
|
TextButton.icon(
|
||||||
onPressed: deleteSite,
|
onPressed: deleteSite,
|
||||||
icon: const Icon(Symbols.delete_forever),
|
icon: const Icon(Symbols.delete_forever),
|
||||||
label: const Text('Delete Publication Site'),
|
label: Text('deletePublicationSite'.tr()),
|
||||||
style: TextButton.styleFrom(
|
style: TextButton.styleFrom(
|
||||||
foregroundColor: Colors.red,
|
foregroundColor: Colors.red,
|
||||||
),
|
),
|
||||||
@@ -171,7 +174,7 @@ class SiteForm extends HookConsumerWidget {
|
|||||||
ref.invalidate(siteListNotifierProvider(pubName));
|
ref.invalidate(siteListNotifierProvider(pubName));
|
||||||
|
|
||||||
if (context.mounted) {
|
if (context.mounted) {
|
||||||
showSnackBar('Publication site saved successfully');
|
showSnackBar('publicationSiteSavedSuccess'.tr());
|
||||||
Navigator.pop(context);
|
Navigator.pop(context);
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
@@ -185,8 +188,8 @@ class SiteForm extends HookConsumerWidget {
|
|||||||
if (siteSlug == null) return; // Shouldn't happen for editing
|
if (siteSlug == null) return; // Shouldn't happen for editing
|
||||||
|
|
||||||
final confirmed = await showConfirmAlert(
|
final confirmed = await showConfirmAlert(
|
||||||
'Are you sure you want to delete this publication site? This action cannot be undone.',
|
'publicationSiteDeleteConfirm'.tr(),
|
||||||
'Delete Publication Site',
|
'deletePublicationSite'.tr(),
|
||||||
);
|
);
|
||||||
if (confirmed != true) return;
|
if (confirmed != true) return;
|
||||||
|
|
||||||
@@ -199,7 +202,7 @@ class SiteForm extends HookConsumerWidget {
|
|||||||
ref.invalidate(siteListNotifierProvider(pubName));
|
ref.invalidate(siteListNotifierProvider(pubName));
|
||||||
|
|
||||||
if (context.mounted) {
|
if (context.mounted) {
|
||||||
showSnackBar('Publication site deleted successfully');
|
showSnackBar('publicationSiteDeletedSuccess'.tr());
|
||||||
Navigator.pop(context);
|
Navigator.pop(context);
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
@@ -243,13 +246,13 @@ class SiteForm extends HookConsumerWidget {
|
|||||||
editingSiteSlug,
|
editingSiteSlug,
|
||||||
),
|
),
|
||||||
loading:
|
loading:
|
||||||
() => const SheetScaffold(
|
() => SheetScaffold(
|
||||||
titleText: 'Edit Publication Site',
|
titleText: 'editPublicationSite'.tr(),
|
||||||
child: Center(child: CircularProgressIndicator()),
|
child: Center(child: CircularProgressIndicator()),
|
||||||
),
|
),
|
||||||
error:
|
error:
|
||||||
(error, _) => SheetScaffold(
|
(error, _) => SheetScaffold(
|
||||||
titleText: 'Edit Publication Site',
|
titleText: 'editPublicationSite'.tr(),
|
||||||
child: ResponseErrorWidget(
|
child: ResponseErrorWidget(
|
||||||
error: error.toString(),
|
error: error.toString(),
|
||||||
onRetry: () {
|
onRetry: () {
|
||||||
@@ -327,9 +330,12 @@ class SiteForm extends HookConsumerWidget {
|
|||||||
borderRadius: BorderRadius.all(Radius.circular(12)),
|
borderRadius: BorderRadius.all(Radius.circular(12)),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
items: const [
|
items: [
|
||||||
DropdownMenuItem(value: 0, child: Text('Fully Managed')),
|
DropdownMenuItem(
|
||||||
DropdownMenuItem(value: 1, child: Text('Self-Managed')),
|
value: 0,
|
||||||
|
child: Text('siteModeFullyManaged'.tr()),
|
||||||
|
),
|
||||||
|
DropdownMenuItem(value: 1, child: Text('siteModeSelfManaged'.tr())),
|
||||||
],
|
],
|
||||||
onChanged: (value) {
|
onChanged: (value) {
|
||||||
if (value != null) {
|
if (value != null) {
|
||||||
@@ -348,7 +354,9 @@ class SiteForm extends HookConsumerWidget {
|
|||||||
|
|
||||||
return SheetScaffold(
|
return SheetScaffold(
|
||||||
titleText:
|
titleText:
|
||||||
siteSlug == null ? 'New Publication Site' : 'Edit Publication Site',
|
siteSlug == null
|
||||||
|
? 'newPublicationSite'.tr()
|
||||||
|
: 'editPublicationSite'.tr(),
|
||||||
child: SingleChildScrollView(
|
child: SingleChildScrollView(
|
||||||
child: Column(
|
child: Column(
|
||||||
children: [
|
children: [
|
||||||
@@ -359,7 +367,7 @@ class SiteForm extends HookConsumerWidget {
|
|||||||
TextButton.icon(
|
TextButton.icon(
|
||||||
onPressed: isLoading.value ? null : deleteSite,
|
onPressed: isLoading.value ? null : deleteSite,
|
||||||
icon: const Icon(Symbols.delete_forever),
|
icon: const Icon(Symbols.delete_forever),
|
||||||
label: const Text('Delete Publication Site'),
|
label: Text('deletePublicationSite'.tr()),
|
||||||
style: TextButton.styleFrom(foregroundColor: Colors.red),
|
style: TextButton.styleFrom(foregroundColor: Colors.red),
|
||||||
).alignment(Alignment.centerRight),
|
).alignment(Alignment.centerRight),
|
||||||
const SizedBox(height: 16),
|
const SizedBox(height: 16),
|
||||||
|
|||||||
@@ -74,7 +74,7 @@ class CreatorSiteListScreen extends HookConsumerWidget {
|
|||||||
Widget build(BuildContext context, WidgetRef ref) {
|
Widget build(BuildContext context, WidgetRef ref) {
|
||||||
return AppScaffold(
|
return AppScaffold(
|
||||||
isNoBackground: false,
|
isNoBackground: false,
|
||||||
appBar: AppBar(title: Text('Publication Sites')),
|
appBar: AppBar(title: Text('publicationSites'.tr())),
|
||||||
floatingActionButton: FloatingActionButton(
|
floatingActionButton: FloatingActionButton(
|
||||||
onPressed: () => _createSite(context),
|
onPressed: () => _createSite(context),
|
||||||
child: Icon(Icons.add),
|
child: Icon(Icons.add),
|
||||||
@@ -201,21 +201,19 @@ class _CreatorSiteItem extends HookConsumerWidget {
|
|||||||
context: context,
|
context: context,
|
||||||
builder:
|
builder:
|
||||||
(context) => AlertDialog(
|
(context) => AlertDialog(
|
||||||
title: Text('Delete Site'),
|
title: Text('deleteSite'.tr()),
|
||||||
content: Text(
|
content: Text('deleteSiteConfirm'.tr()),
|
||||||
'Are you sure you want to delete this site?',
|
|
||||||
),
|
|
||||||
actions: [
|
actions: [
|
||||||
TextButton(
|
TextButton(
|
||||||
onPressed:
|
onPressed:
|
||||||
() =>
|
() =>
|
||||||
Navigator.of(context).pop(false),
|
Navigator.of(context).pop(false),
|
||||||
child: Text('Cancel'),
|
child: Text('cancel'.tr()),
|
||||||
),
|
),
|
||||||
TextButton(
|
TextButton(
|
||||||
onPressed:
|
onPressed:
|
||||||
() => Navigator.of(context).pop(true),
|
() => Navigator.of(context).pop(true),
|
||||||
child: Text('Delete'),
|
child: Text('delete'.tr()),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
@@ -225,7 +223,7 @@ class _CreatorSiteItem extends HookConsumerWidget {
|
|||||||
final client = ref.read(apiClientProvider);
|
final client = ref.read(apiClientProvider);
|
||||||
await client.delete('/zone/sites/${site.id}');
|
await client.delete('/zone/sites/${site.id}');
|
||||||
ref.invalidate(siteListNotifierProvider(pubName));
|
ref.invalidate(siteListNotifierProvider(pubName));
|
||||||
showSnackBar('Site deleted successfully');
|
showSnackBar('siteDeletedSuccess'.tr());
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
showErrorAlert(e);
|
showErrorAlert(e);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -534,7 +534,7 @@ class _LotteryPurchaseSheetState extends State<LotteryPurchaseSheet> {
|
|||||||
),
|
),
|
||||||
const Gap(4),
|
const Gap(4),
|
||||||
Text(
|
Text(
|
||||||
'The last selected number will be your special number.',
|
'lotteryLastNumberSpecial'.tr(),
|
||||||
style: Theme.of(context).textTheme.bodySmall?.copyWith(
|
style: Theme.of(context).textTheme.bodySmall?.copyWith(
|
||||||
color: Theme.of(context).colorScheme.secondary,
|
color: Theme.of(context).colorScheme.secondary,
|
||||||
fontWeight: FontWeight.w500,
|
fontWeight: FontWeight.w500,
|
||||||
@@ -738,11 +738,11 @@ class _LotteryPurchaseSheetState extends State<LotteryPurchaseSheet> {
|
|||||||
},
|
},
|
||||||
validator: (value) {
|
validator: (value) {
|
||||||
if (value == null || value.isEmpty) {
|
if (value == null || value.isEmpty) {
|
||||||
return 'Please enter a multiplier';
|
return 'lotteryMultiplierRequired'.tr();
|
||||||
}
|
}
|
||||||
final parsed = int.tryParse(value);
|
final parsed = int.tryParse(value);
|
||||||
if (parsed == null || parsed < 1 || parsed > 10) {
|
if (parsed == null || parsed < 1 || parsed > 10) {
|
||||||
return 'Multiplier must be between 1 and 10';
|
return 'lotteryMultiplierRange'.tr();
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import 'dart:io';
|
import 'dart:io';
|
||||||
|
import 'package:easy_localization/easy_localization.dart';
|
||||||
import 'package:file_picker/file_picker.dart';
|
import 'package:file_picker/file_picker.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:gap/gap.dart';
|
import 'package:gap/gap.dart';
|
||||||
@@ -33,7 +34,7 @@ class FileManagementActionSection extends HookConsumerWidget {
|
|||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
Text(
|
Text(
|
||||||
'File Actions',
|
'fileActions'.tr(),
|
||||||
style: theme.textTheme.titleMedium?.copyWith(
|
style: theme.textTheme.titleMedium?.copyWith(
|
||||||
fontWeight: FontWeight.bold,
|
fontWeight: FontWeight.bold,
|
||||||
),
|
),
|
||||||
@@ -45,10 +46,8 @@ class FileManagementActionSection extends HookConsumerWidget {
|
|||||||
Symbols.delete_forever,
|
Symbols.delete_forever,
|
||||||
color: theme.colorScheme.error,
|
color: theme.colorScheme.error,
|
||||||
),
|
),
|
||||||
title: const Text('Purge Files'),
|
title: Text('purgeFiles'.tr()),
|
||||||
subtitle: const Text(
|
subtitle: Text('purgeFilesDescription'.tr()),
|
||||||
'Remove all uploaded files from the site',
|
|
||||||
),
|
|
||||||
contentPadding: const EdgeInsets.symmetric(horizontal: 24),
|
contentPadding: const EdgeInsets.symmetric(horizontal: 24),
|
||||||
onTap: () => _purgeFiles(context, ref),
|
onTap: () => _purgeFiles(context, ref),
|
||||||
),
|
),
|
||||||
@@ -58,10 +57,8 @@ class FileManagementActionSection extends HookConsumerWidget {
|
|||||||
Symbols.upload,
|
Symbols.upload,
|
||||||
color: theme.colorScheme.primary,
|
color: theme.colorScheme.primary,
|
||||||
),
|
),
|
||||||
title: const Text('Deploy Site'),
|
title: Text('deploySite'.tr()),
|
||||||
subtitle: const Text(
|
subtitle: Text('deploySiteDescription'.tr()),
|
||||||
'Upload and deploy a new version from ZIP archive',
|
|
||||||
),
|
|
||||||
contentPadding: const EdgeInsets.symmetric(horizontal: 24),
|
contentPadding: const EdgeInsets.symmetric(horizontal: 24),
|
||||||
onTap: () => _deploySite(context, ref),
|
onTap: () => _deploySite(context, ref),
|
||||||
),
|
),
|
||||||
@@ -79,21 +76,19 @@ class FileManagementActionSection extends HookConsumerWidget {
|
|||||||
context: context,
|
context: context,
|
||||||
builder:
|
builder:
|
||||||
(context) => AlertDialog(
|
(context) => AlertDialog(
|
||||||
title: const Text('Confirm Purge'),
|
title: Text('confirmPurge'.tr()),
|
||||||
content: const Text(
|
content: Text('purgeFilesConfirm'.tr()),
|
||||||
'This will permanently delete all files uploaded to this site. This action cannot be undone. Are you sure you want to continue?',
|
|
||||||
),
|
|
||||||
actions: [
|
actions: [
|
||||||
TextButton(
|
TextButton(
|
||||||
onPressed: () => Navigator.pop(context, false),
|
onPressed: () => Navigator.pop(context, false),
|
||||||
child: const Text('Cancel'),
|
child: Text('cancel'.tr()),
|
||||||
),
|
),
|
||||||
FilledButton(
|
FilledButton(
|
||||||
onPressed: () => Navigator.pop(context, true),
|
onPressed: () => Navigator.pop(context, true),
|
||||||
style: FilledButton.styleFrom(
|
style: FilledButton.styleFrom(
|
||||||
backgroundColor: Theme.of(context).colorScheme.error,
|
backgroundColor: Theme.of(context).colorScheme.error,
|
||||||
),
|
),
|
||||||
child: const Text('Purge All Files'),
|
child: Text('purgeAllFiles'.tr()),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
@@ -105,13 +100,13 @@ class FileManagementActionSection extends HookConsumerWidget {
|
|||||||
final apiClient = ref.read(apiClientProvider);
|
final apiClient = ref.read(apiClientProvider);
|
||||||
await apiClient.delete('/zone/sites/${site.id}/files/purge');
|
await apiClient.delete('/zone/sites/${site.id}/files/purge');
|
||||||
if (context.mounted) {
|
if (context.mounted) {
|
||||||
showSnackBar('All files purged successfully');
|
showSnackBar('allFilesPurgedSuccess'.tr());
|
||||||
// Refresh the file management section
|
// Refresh the file management section
|
||||||
ref.invalidate(siteFilesProvider(siteId: site.id));
|
ref.invalidate(siteFilesProvider(siteId: site.id));
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
if (context.mounted) {
|
if (context.mounted) {
|
||||||
showSnackBar('Failed to purge files: $e');
|
showSnackBar('failedToPurgeFiles'.tr(args: [e.toString()]));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -147,13 +142,13 @@ class FileManagementActionSection extends HookConsumerWidget {
|
|||||||
);
|
);
|
||||||
|
|
||||||
if (context.mounted) {
|
if (context.mounted) {
|
||||||
showSnackBar('Site deployed successfully');
|
showSnackBar('siteDeployedSuccess'.tr());
|
||||||
// Refresh the file management section
|
// Refresh the file management section
|
||||||
ref.invalidate(siteFilesProvider(siteId: site.id));
|
ref.invalidate(siteFilesProvider(siteId: site.id));
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
if (context.mounted) {
|
if (context.mounted) {
|
||||||
showSnackBar('Failed to deploy site: $e');
|
showSnackBar('failedToDeploySite'.tr(args: [e.toString()]));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import 'dart:io';
|
import 'dart:io';
|
||||||
|
import 'package:easy_localization/easy_localization.dart';
|
||||||
import 'package:file_picker/file_picker.dart';
|
import 'package:file_picker/file_picker.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_hooks/flutter_hooks.dart';
|
import 'package:flutter_hooks/flutter_hooks.dart';
|
||||||
@@ -42,7 +43,7 @@ class FileManagementSection extends HookConsumerWidget {
|
|||||||
Icon(Symbols.folder, size: 20),
|
Icon(Symbols.folder, size: 20),
|
||||||
const Gap(8),
|
const Gap(8),
|
||||||
Text(
|
Text(
|
||||||
'File Management',
|
'fileManagement'.tr(),
|
||||||
style: theme.textTheme.titleMedium?.copyWith(
|
style: theme.textTheme.titleMedium?.copyWith(
|
||||||
fontWeight: FontWeight.bold,
|
fontWeight: FontWeight.bold,
|
||||||
),
|
),
|
||||||
@@ -75,9 +76,7 @@ class FileManagementSection extends HookConsumerWidget {
|
|||||||
files =
|
files =
|
||||||
results.map((m) => m['file'] as File).toList();
|
results.map((m) => m['file'] as File).toList();
|
||||||
if (files.isEmpty) {
|
if (files.isEmpty) {
|
||||||
showSnackBar(
|
showSnackBar('noFilesFoundInFolder'.tr());
|
||||||
'No files found in the selected folder',
|
|
||||||
);
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -112,23 +111,23 @@ class FileManagementSection extends HookConsumerWidget {
|
|||||||
},
|
},
|
||||||
itemBuilder:
|
itemBuilder:
|
||||||
(BuildContext context) => [
|
(BuildContext context) => [
|
||||||
const PopupMenuItem<String>(
|
PopupMenuItem<String>(
|
||||||
value: 'files',
|
value: 'files',
|
||||||
child: Row(
|
child: Row(
|
||||||
children: [
|
children: [
|
||||||
Icon(Symbols.file_copy),
|
Icon(Symbols.file_copy),
|
||||||
Gap(12),
|
Gap(12),
|
||||||
Text('Files'),
|
Text('siteFiles'.tr()),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
const PopupMenuItem<String>(
|
PopupMenuItem<String>(
|
||||||
value: 'folder',
|
value: 'folder',
|
||||||
child: Row(
|
child: Row(
|
||||||
children: [
|
children: [
|
||||||
Icon(Symbols.folder),
|
Icon(Symbols.folder),
|
||||||
Gap(12),
|
Gap(12),
|
||||||
Text('Folder'),
|
Text('siteFolder'.tr()),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@@ -182,7 +181,7 @@ class FileManagementSection extends HookConsumerWidget {
|
|||||||
children: [
|
children: [
|
||||||
InkWell(
|
InkWell(
|
||||||
onTap: () => currentPath.value = null,
|
onTap: () => currentPath.value = null,
|
||||||
child: const Text('Root'),
|
child: Text('siteRoot'.tr()),
|
||||||
),
|
),
|
||||||
...() {
|
...() {
|
||||||
final parts =
|
final parts =
|
||||||
@@ -230,12 +229,12 @@ class FileManagementSection extends HookConsumerWidget {
|
|||||||
),
|
),
|
||||||
const Gap(16),
|
const Gap(16),
|
||||||
Text(
|
Text(
|
||||||
'No files uploaded yet',
|
'noFilesUploadedYet'.tr(),
|
||||||
style: theme.textTheme.bodyLarge,
|
style: theme.textTheme.bodyLarge,
|
||||||
),
|
),
|
||||||
const Gap(8),
|
const Gap(8),
|
||||||
Text(
|
Text(
|
||||||
'Upload your first file to get started',
|
'uploadFirstFile'.tr(),
|
||||||
style: theme.textTheme.bodySmall,
|
style: theme.textTheme.bodySmall,
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
@@ -265,7 +264,7 @@ class FileManagementSection extends HookConsumerWidget {
|
|||||||
(error, stack) => Center(
|
(error, stack) => Center(
|
||||||
child: Column(
|
child: Column(
|
||||||
children: [
|
children: [
|
||||||
Text('Failed to load files'),
|
Text('failedToLoadFiles'.tr()),
|
||||||
const Gap(8),
|
const Gap(8),
|
||||||
ElevatedButton(
|
ElevatedButton(
|
||||||
onPressed:
|
onPressed:
|
||||||
@@ -275,7 +274,7 @@ class FileManagementSection extends HookConsumerWidget {
|
|||||||
path: currentPath.value,
|
path: currentPath.value,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
child: const Text('Retry'),
|
child: Text('retry'.tr()),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import 'package:easy_localization/easy_localization.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:gap/gap.dart';
|
import 'package:gap/gap.dart';
|
||||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
@@ -29,7 +30,7 @@ class PagesSection extends HookConsumerWidget {
|
|||||||
const Icon(Symbols.article, size: 20),
|
const Icon(Symbols.article, size: 20),
|
||||||
const Gap(8),
|
const Gap(8),
|
||||||
Text(
|
Text(
|
||||||
'Pages',
|
'sitePages'.tr(),
|
||||||
style: theme.textTheme.titleMedium?.copyWith(
|
style: theme.textTheme.titleMedium?.copyWith(
|
||||||
fontWeight: FontWeight.bold,
|
fontWeight: FontWeight.bold,
|
||||||
),
|
),
|
||||||
@@ -72,12 +73,12 @@ class PagesSection extends HookConsumerWidget {
|
|||||||
),
|
),
|
||||||
const Gap(16),
|
const Gap(16),
|
||||||
Text(
|
Text(
|
||||||
'No pages yet',
|
'noPagesYet'.tr(),
|
||||||
style: theme.textTheme.bodyLarge,
|
style: theme.textTheme.bodyLarge,
|
||||||
),
|
),
|
||||||
const Gap(8),
|
const Gap(8),
|
||||||
Text(
|
Text(
|
||||||
'Create your first page to get started',
|
'createFirstPage'.tr(),
|
||||||
style: theme.textTheme.bodySmall,
|
style: theme.textTheme.bodySmall,
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
@@ -101,14 +102,14 @@ class PagesSection extends HookConsumerWidget {
|
|||||||
(error, stack) => Center(
|
(error, stack) => Center(
|
||||||
child: Column(
|
child: Column(
|
||||||
children: [
|
children: [
|
||||||
Text('Failed to load pages'),
|
Text('failedToLoadPages'.tr()),
|
||||||
const Gap(8),
|
const Gap(8),
|
||||||
ElevatedButton(
|
ElevatedButton(
|
||||||
onPressed:
|
onPressed:
|
||||||
() => ref.invalidate(
|
() => ref.invalidate(
|
||||||
sitePagesProvider(pubName, site.slug),
|
sitePagesProvider(pubName, site.slug),
|
||||||
),
|
),
|
||||||
child: const Text('Retry'),
|
child: Text('retry'.tr()),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -64,18 +64,16 @@ class SiteActionMenu extends HookConsumerWidget {
|
|||||||
context: context,
|
context: context,
|
||||||
builder:
|
builder:
|
||||||
(context) => AlertDialog(
|
(context) => AlertDialog(
|
||||||
title: const Text('Delete Site'),
|
title: Text('deleteSite'.tr()),
|
||||||
content: const Text(
|
content: Text('publicationSiteDeleteConfirm'.tr()),
|
||||||
'Are you sure you want to delete this publication site? This action cannot be undone.',
|
|
||||||
),
|
|
||||||
actions: [
|
actions: [
|
||||||
TextButton(
|
TextButton(
|
||||||
onPressed: () => Navigator.of(context).pop(false),
|
onPressed: () => Navigator.of(context).pop(false),
|
||||||
child: const Text('Cancel'),
|
child: Text('cancel'.tr()),
|
||||||
),
|
),
|
||||||
TextButton(
|
TextButton(
|
||||||
onPressed: () => Navigator.of(context).pop(true),
|
onPressed: () => Navigator.of(context).pop(true),
|
||||||
child: const Text('Delete'),
|
child: Text('delete'.tr()),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
@@ -86,7 +84,7 @@ class SiteActionMenu extends HookConsumerWidget {
|
|||||||
final client = ref.read(apiClientProvider);
|
final client = ref.read(apiClientProvider);
|
||||||
await client.delete('/zone/sites/${site.id}');
|
await client.delete('/zone/sites/${site.id}');
|
||||||
if (context.mounted) {
|
if (context.mounted) {
|
||||||
showSnackBar('Site deleted successfully');
|
showSnackBar('siteDeletedSuccess'.tr());
|
||||||
Navigator.of(context).pop();
|
Navigator.of(context).pop();
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import 'package:easy_localization/easy_localization.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:gap/gap.dart';
|
import 'package:gap/gap.dart';
|
||||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
@@ -42,20 +43,20 @@ class SiteDetailContent extends HookConsumerWidget {
|
|||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
Text(
|
Text(
|
||||||
'Site Information',
|
'siteInformation'.tr(),
|
||||||
style: theme.textTheme.titleMedium?.copyWith(
|
style: theme.textTheme.titleMedium?.copyWith(
|
||||||
fontWeight: FontWeight.bold,
|
fontWeight: FontWeight.bold,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
const Gap(16),
|
const Gap(16),
|
||||||
InfoRow(
|
InfoRow(
|
||||||
label: 'Name',
|
label: 'name'.tr(),
|
||||||
value: site.name,
|
value: site.name,
|
||||||
icon: Symbols.title,
|
icon: Symbols.title,
|
||||||
),
|
),
|
||||||
const Gap(8),
|
const Gap(8),
|
||||||
InfoRow(
|
InfoRow(
|
||||||
label: 'Slug',
|
label: 'slug'.tr(),
|
||||||
value: site.slug,
|
value: site.slug,
|
||||||
icon: Symbols.tag,
|
icon: Symbols.tag,
|
||||||
monospace: true,
|
monospace: true,
|
||||||
@@ -63,27 +64,30 @@ class SiteDetailContent extends HookConsumerWidget {
|
|||||||
const Gap(8),
|
const Gap(8),
|
||||||
InfoRow(
|
InfoRow(
|
||||||
label: 'Mode',
|
label: 'Mode',
|
||||||
value: site.mode == 0 ? 'Fully Managed' : 'Self-Managed',
|
value:
|
||||||
|
site.mode == 0
|
||||||
|
? 'siteModeFullyManaged'.tr()
|
||||||
|
: 'siteModeSelfManaged'.tr(),
|
||||||
icon: Symbols.settings,
|
icon: Symbols.settings,
|
||||||
),
|
),
|
||||||
if (site.description != null &&
|
if (site.description != null &&
|
||||||
site.description!.isNotEmpty) ...[
|
site.description!.isNotEmpty) ...[
|
||||||
const Gap(8),
|
const Gap(8),
|
||||||
InfoRow(
|
InfoRow(
|
||||||
label: 'Description',
|
label: 'description'.tr(),
|
||||||
value: site.description!,
|
value: site.description!,
|
||||||
icon: Symbols.description,
|
icon: Symbols.description,
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
const Gap(8),
|
const Gap(8),
|
||||||
InfoRow(
|
InfoRow(
|
||||||
label: 'Created',
|
label: 'siteCreated'.tr(),
|
||||||
value: site.createdAt.formatSystem(),
|
value: site.createdAt.formatSystem(),
|
||||||
icon: Symbols.calendar_add_on,
|
icon: Symbols.calendar_add_on,
|
||||||
),
|
),
|
||||||
const Gap(8),
|
const Gap(8),
|
||||||
InfoRow(
|
InfoRow(
|
||||||
label: 'Updated',
|
label: 'siteUpdated'.tr(),
|
||||||
value: site.updatedAt.formatSystem(),
|
value: site.updatedAt.formatSystem(),
|
||||||
icon: Symbols.update,
|
icon: Symbols.update,
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -57,7 +57,7 @@ class FundEnvelopeWidget extends HookConsumerWidget {
|
|||||||
),
|
),
|
||||||
const SizedBox(height: 8),
|
const SizedBox(height: 8),
|
||||||
Text(
|
Text(
|
||||||
'Failed to load fund envelope',
|
'fundEnvelopeLoadFailed'.tr(),
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
color: Theme.of(context).colorScheme.error,
|
color: Theme.of(context).colorScheme.error,
|
||||||
),
|
),
|
||||||
@@ -88,7 +88,7 @@ class FundEnvelopeWidget extends HookConsumerWidget {
|
|||||||
const SizedBox(width: 8),
|
const SizedBox(width: 8),
|
||||||
Expanded(
|
Expanded(
|
||||||
child: Text(
|
child: Text(
|
||||||
'Fund Envelope',
|
'fundEnvelope'.tr(),
|
||||||
style: Theme.of(context).textTheme.titleMedium
|
style: Theme.of(context).textTheme.titleMedium
|
||||||
?.copyWith(fontWeight: FontWeight.w600),
|
?.copyWith(fontWeight: FontWeight.w600),
|
||||||
),
|
),
|
||||||
@@ -116,7 +116,12 @@ class FundEnvelopeWidget extends HookConsumerWidget {
|
|||||||
const SizedBox(height: 4),
|
const SizedBox(height: 4),
|
||||||
if (fund.remainingAmount != fund.totalAmount)
|
if (fund.remainingAmount != fund.totalAmount)
|
||||||
Text(
|
Text(
|
||||||
'Remaining: ${fund.remainingAmount.toStringAsFixed(2)} ${fund.currency}',
|
'fundEnvelopeRemaining'.tr(
|
||||||
|
args: [
|
||||||
|
fund.remainingAmount.toStringAsFixed(2),
|
||||||
|
fund.currency,
|
||||||
|
],
|
||||||
|
),
|
||||||
style: Theme.of(
|
style: Theme.of(
|
||||||
context,
|
context,
|
||||||
).textTheme.bodySmall?.copyWith(
|
).textTheme.bodySmall?.copyWith(
|
||||||
@@ -126,7 +131,13 @@ class FundEnvelopeWidget extends HookConsumerWidget {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
Text(
|
Text(
|
||||||
'Split: ${fund.splitType == 0 ? 'Evenly' : 'Randomly'}',
|
'fundEnvelopeSplit'.tr(
|
||||||
|
args: [
|
||||||
|
fund.splitType == 0
|
||||||
|
? 'fundEnvelopeSplitEvenly'.tr()
|
||||||
|
: 'fundEnvelopeSplitRandomly'.tr(),
|
||||||
|
],
|
||||||
|
),
|
||||||
style: Theme.of(
|
style: Theme.of(
|
||||||
context,
|
context,
|
||||||
).textTheme.bodySmall?.copyWith(
|
).textTheme.bodySmall?.copyWith(
|
||||||
@@ -245,7 +256,7 @@ class FundEnvelopeWidget extends HookConsumerWidget {
|
|||||||
|
|
||||||
if (dialogContext.mounted) {
|
if (dialogContext.mounted) {
|
||||||
Navigator.of(dialogContext).pop();
|
Navigator.of(dialogContext).pop();
|
||||||
showSnackBar('Fund claimed successfully!');
|
showSnackBar('fundEnvelopeClaimSuccess'.tr());
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
showErrorAlert(e);
|
showErrorAlert(e);
|
||||||
@@ -261,23 +272,23 @@ class FundEnvelopeWidget extends HookConsumerWidget {
|
|||||||
|
|
||||||
switch (status) {
|
switch (status) {
|
||||||
case 0:
|
case 0:
|
||||||
text = 'Created';
|
text = 'fundEnvelopeStatusCreated'.tr();
|
||||||
color = Colors.blue;
|
color = Colors.blue;
|
||||||
break;
|
break;
|
||||||
case 1:
|
case 1:
|
||||||
text = 'Partially Claimed';
|
text = 'fundEnvelopeStatusPartial'.tr();
|
||||||
color = Colors.orange;
|
color = Colors.orange;
|
||||||
break;
|
break;
|
||||||
case 2:
|
case 2:
|
||||||
text = 'Fully Claimed';
|
text = 'fundEnvelopeStatusCompleted'.tr();
|
||||||
color = Colors.green;
|
color = Colors.green;
|
||||||
break;
|
break;
|
||||||
case 3:
|
case 3:
|
||||||
text = 'Expired';
|
text = 'fundEnvelopeStatusExpired'.tr();
|
||||||
color = Colors.red;
|
color = Colors.red;
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
text = 'Unknown';
|
text = 'fundEnvelopeStatusUnknown'.tr();
|
||||||
color = Colors.grey;
|
color = Colors.grey;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -339,7 +350,9 @@ class FundEnvelopeWidget extends HookConsumerWidget {
|
|||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
Text(
|
Text(
|
||||||
'Recipients ($claimedCount/$totalCount claimed)',
|
'fundEnvelopeRecipients'.tr(
|
||||||
|
args: [claimedCount.toString(), totalCount.toString()],
|
||||||
|
),
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
fontSize: 12,
|
fontSize: 12,
|
||||||
fontWeight: FontWeight.w500,
|
fontWeight: FontWeight.w500,
|
||||||
@@ -362,15 +375,26 @@ class FundEnvelopeWidget extends HookConsumerWidget {
|
|||||||
final difference = date.difference(now);
|
final difference = date.difference(now);
|
||||||
|
|
||||||
if (difference.isNegative) {
|
if (difference.isNegative) {
|
||||||
return 'Expired ${difference.inDays.abs()} days ago';
|
final days = difference.inDays.abs();
|
||||||
|
return 'fundEnvelopeExpiredDaysAgo'.plural(
|
||||||
|
days,
|
||||||
|
args: [days.toString()],
|
||||||
|
);
|
||||||
} else if (difference.inDays == 0) {
|
} else if (difference.inDays == 0) {
|
||||||
final hours = difference.inHours;
|
final hours = difference.inHours;
|
||||||
if (hours == 0) {
|
if (hours == 0) {
|
||||||
return 'Expires soon';
|
return 'fundEnvelopeExpiresSoon'.tr();
|
||||||
}
|
}
|
||||||
return 'Expires in $hours hour${hours == 1 ? '' : 's'}';
|
return 'fundEnvelopeExpiresInHours'.plural(
|
||||||
|
hours,
|
||||||
|
args: [hours.toString()],
|
||||||
|
);
|
||||||
} else if (difference.inDays < 7) {
|
} else if (difference.inDays < 7) {
|
||||||
return 'Expires in ${difference.inDays} day${difference.inDays == 1 ? '' : 's'}';
|
final days = difference.inDays;
|
||||||
|
return 'fundEnvelopeExpiresInDays'.plural(
|
||||||
|
days,
|
||||||
|
args: [days.toString()],
|
||||||
|
);
|
||||||
} else {
|
} else {
|
||||||
return '${date.day}/${date.month}/${date.year}';
|
return '${date.day}/${date.month}/${date.year}';
|
||||||
}
|
}
|
||||||
@@ -449,7 +473,13 @@ class FundClaimDialog extends HookConsumerWidget {
|
|||||||
|
|
||||||
// Remaining amount
|
// Remaining amount
|
||||||
Text(
|
Text(
|
||||||
'${fund.remainingAmount.toStringAsFixed(2)} ${fund.currency} / $remainingSplits splits',
|
'fundEnvelopeRemainingWithSplits'.tr(
|
||||||
|
args: [
|
||||||
|
fund.remainingAmount.toStringAsFixed(2),
|
||||||
|
fund.currency,
|
||||||
|
remainingSplits.toString(),
|
||||||
|
],
|
||||||
|
),
|
||||||
style: Theme.of(context).textTheme.bodyMedium?.copyWith(
|
style: Theme.of(context).textTheme.bodyMedium?.copyWith(
|
||||||
color: Theme.of(context).colorScheme.secondary,
|
color: Theme.of(context).colorScheme.secondary,
|
||||||
fontWeight: FontWeight.w500,
|
fontWeight: FontWeight.w500,
|
||||||
@@ -503,7 +533,8 @@ class FundClaimDialog extends HookConsumerWidget {
|
|||||||
const SizedBox(width: 8),
|
const SizedBox(width: 8),
|
||||||
Expanded(
|
Expanded(
|
||||||
child: Text(
|
child: Text(
|
||||||
recipient.recipientAccount?.nick ?? 'Unknown User',
|
recipient.recipientAccount?.nick ??
|
||||||
|
'fundEnvelopeUnknownUser'.tr(),
|
||||||
style: Theme.of(
|
style: Theme.of(
|
||||||
context,
|
context,
|
||||||
).textTheme.bodySmall?.copyWith(
|
).textTheme.bodySmall?.copyWith(
|
||||||
@@ -551,7 +582,8 @@ class FundClaimDialog extends HookConsumerWidget {
|
|||||||
const SizedBox(width: 8),
|
const SizedBox(width: 8),
|
||||||
Expanded(
|
Expanded(
|
||||||
child: Text(
|
child: Text(
|
||||||
recipient.recipientAccount?.nick ?? 'Unknown User',
|
recipient.recipientAccount?.nick ??
|
||||||
|
'fundEnvelopeUnknownUser'.tr(),
|
||||||
style: Theme.of(context).textTheme.bodySmall,
|
style: Theme.of(context).textTheme.bodySmall,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|||||||
Reference in New Issue
Block a user