Compare commits

..

4 Commits

Author SHA1 Message Date
d7746d14e4 🚀 Launch 3.5.0+151 2025-12-06 21:52:30 +08:00
648d5225f6 🐛 Ensure mobile site management request permission 2025-12-06 21:48:16 +08:00
9d4d0f2e48 🐛 Fix inconsistence alert 2025-12-06 21:44:43 +08:00
fe386163f4 💄 Optimize designs in developer hub 2025-12-06 21:39:50 +08:00
17 changed files with 631 additions and 581 deletions

View File

@@ -12,6 +12,8 @@
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN" android:maxSdkVersion="30" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"
android:maxSdkVersion="29" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="com.google.android.c2dm.permission.RECEIVE" />

View File

@@ -257,6 +257,8 @@ PODS:
- path_provider_foundation (0.0.1):
- Flutter
- FlutterMacOS
- permission_handler_apple (9.3.0):
- Flutter
- pointer_interceptor_ios (0.0.1):
- Flutter
- PromisesObjC (2.4.0)
@@ -351,6 +353,7 @@ DEPENDENCIES:
- package_info_plus (from `.symlinks/plugins/package_info_plus/ios`)
- pasteboard (from `.symlinks/plugins/pasteboard/ios`)
- path_provider_foundation (from `.symlinks/plugins/path_provider_foundation/darwin`)
- permission_handler_apple (from `.symlinks/plugins/permission_handler_apple/ios`)
- pointer_interceptor_ios (from `.symlinks/plugins/pointer_interceptor_ios/ios`)
- protocol_handler_ios (from `.symlinks/plugins/protocol_handler_ios/ios`)
- receive_sharing_intent (from `.symlinks/plugins/receive_sharing_intent/ios`)
@@ -458,6 +461,8 @@ EXTERNAL SOURCES:
:path: ".symlinks/plugins/pasteboard/ios"
path_provider_foundation:
:path: ".symlinks/plugins/path_provider_foundation/darwin"
permission_handler_apple:
:path: ".symlinks/plugins/permission_handler_apple/ios"
pointer_interceptor_ios:
:path: ".symlinks/plugins/pointer_interceptor_ios/ios"
protocol_handler_ios:
@@ -539,6 +544,7 @@ SPEC CHECKSUMS:
package_info_plus: af8e2ca6888548050f16fa2f1938db7b5a5df499
pasteboard: 49088aeb6119d51f976a421db60d8e1ab079b63c
path_provider_foundation: bb55f6dbba17d0dccd6737fe6f7f34fbd0376880
permission_handler_apple: 4ed2196e43d0651e8ff7ca3483a069d469701f2d
pointer_interceptor_ios: da06a662d5bfd329602b45b2ab41bc0fb5fdb0f0
PromisesObjC: f5707f49cb48b9636751c5b2e7d227e43fba9f47
PromisesSwift: 9d77319bbe72ebf6d872900551f7eeba9bce2851

View File

@@ -167,22 +167,10 @@ class _CreatorSiteItem extends HookConsumerWidget {
],
),
onTap: () async {
final confirmed = await showDialog<bool>(
context: context,
builder: (context) => AlertDialog(
title: Text('deleteSite'.tr()),
content: Text('deleteSiteConfirm'.tr()),
actions: [
TextButton(
onPressed: () => Navigator.of(context).pop(false),
child: Text('cancel'.tr()),
),
TextButton(
onPressed: () => Navigator.of(context).pop(true),
child: Text('delete'.tr()),
),
],
),
final confirmed = await showConfirmAlert(
'publicationSiteDeleteConfirm'.tr(),
'deleteSite'.tr(),
isDanger: true,
);
if (confirmed == true) {
try {

View File

@@ -39,8 +39,7 @@ class AppDetailScreen extends HookConsumerWidget {
actions: [
IconButton(
icon: const Icon(Symbols.edit),
onPressed:
appData.value == null
onPressed: appData.value == null
? null
: () {
context.pushNamed(
@@ -85,21 +84,31 @@ class AppDetailScreen extends HookConsumerWidget {
controller: tabController,
physics: const NeverScrollableScrollPhysics(),
children: [
_AppOverview(app: app),
AppSecretsScreen(
Align(
alignment: Alignment.topCenter,
child: ConstrainedBox(
constraints: const BoxConstraints(maxWidth: 640),
child: _AppOverview(app: app),
),
),
Align(
alignment: Alignment.topCenter,
child: ConstrainedBox(
constraints: const BoxConstraints(maxWidth: 640),
child: AppSecretsScreen(
publisherName: publisherName,
projectId: projectId,
appId: appId,
),
),
),
],
);
},
loading: () => const Center(child: CircularProgressIndicator()),
error:
(err, stack) => ResponseErrorWidget(
error: (err, stack) => ResponseErrorWidget(
error: err,
onRetry:
() => ref.invalidate(
onRetry: () => ref.invalidate(
customAppProvider(publisherName, projectId, appId),
),
),
@@ -115,6 +124,7 @@ class _AppOverview extends StatelessWidget {
@override
Widget build(BuildContext context) {
return SingleChildScrollView(
padding: EdgeInsets.zero,
child: Column(
children: [
AspectRatio(
@@ -125,8 +135,7 @@ class _AppOverview extends StatelessWidget {
children: [
Container(
color: Theme.of(context).colorScheme.surfaceContainer,
child:
app.background != null
child: app.background != null
? CloudFileWidget(
item: app.background!,
fit: BoxFit.cover,

View File

@@ -2,6 +2,7 @@ import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:easy_localization/easy_localization.dart';
import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:gap/gap.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:island/models/custom_app_secret.dart';
import 'package:island/pods/network.dart';
@@ -53,8 +54,7 @@ class AppSecretsScreen extends HookConsumerWidget {
showModalBottomSheet(
context: context,
isScrollControlled: true,
builder:
(context) => SheetScaffold(
builder: (context) => SheetScaffold(
titleText: 'newSecretGenerated'.tr(),
child: Padding(
padding: const EdgeInsets.all(20.0),
@@ -114,23 +114,39 @@ class AppSecretsScreen extends HookConsumerWidget {
controller: descriptionController,
decoration: InputDecoration(
labelText: 'description'.tr(),
border: OutlineInputBorder(
borderRadius: const BorderRadius.all(
Radius.circular(12),
),
),
),
autofocus: true,
),
const SizedBox(height: 20),
const Gap(16),
TextFormField(
controller: expiresInController,
decoration: InputDecoration(
labelText: 'expiresIn'.tr(),
border: OutlineInputBorder(
borderRadius: const BorderRadius.all(
Radius.circular(12),
),
),
),
keyboardType: TextInputType.number,
),
const SizedBox(height: 20),
SwitchListTile(
const Gap(16),
Card(
margin: EdgeInsets.zero,
child: SwitchListTile(
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(8),
),
title: Text('isOidc'.tr()),
value: isOidc.value,
onChanged: (value) => isOidc.value = value,
),
),
const SizedBox(height: 20),
FilledButton.icon(
onPressed: () async {
@@ -175,13 +191,8 @@ class AppSecretsScreen extends HookConsumerWidget {
return secrets.when(
data: (data) {
return RefreshIndicator(
onRefresh:
() => ref.refresh(
customAppSecretsProvider(
publisherName,
projectId,
appId,
).future,
onRefresh: () => ref.refresh(
customAppSecretsProvider(publisherName, projectId, appId).future,
),
child: Column(
children: [
@@ -240,11 +251,9 @@ class AppSecretsScreen extends HookConsumerWidget {
);
},
loading: () => const Center(child: CircularProgressIndicator()),
error:
(err, stack) => ResponseErrorWidget(
error: (err, stack) => ResponseErrorWidget(
error: err,
onRetry:
() => ref.invalidate(
onRetry: () => ref.invalidate(
customAppSecretsProvider(publisherName, projectId, appId),
),
),

View File

@@ -76,8 +76,7 @@ class CustomAppsScreen extends HookConsumerWidget {
showModalBottomSheet(
context: context,
isScrollControlled: true,
builder:
(context) => SheetScaffold(
builder: (context) => SheetScaffold(
titleText: 'createCustomApp'.tr(),
child: NewCustomAppScreen(
publisherName: publisherName,
@@ -95,10 +94,8 @@ class CustomAppsScreen extends HookConsumerWidget {
);
}
return ExtendedRefreshIndicator(
onRefresh:
() => ref.refresh(
customAppsProvider(publisherName, projectId).future,
),
onRefresh: () =>
ref.refresh(customAppsProvider(publisherName, projectId).future),
child: Column(
children: [
const Gap(8),
@@ -110,8 +107,7 @@ class CustomAppsScreen extends HookConsumerWidget {
showModalBottomSheet(
context: context,
isScrollControlled: true,
builder:
(context) => SheetScaffold(
builder: (context) => SheetScaffold(
titleText: 'createCustomApp'.tr(),
child: NewCustomAppScreen(
publisherName: publisherName,
@@ -145,32 +141,21 @@ class CustomAppsScreen extends HookConsumerWidget {
);
},
child: Column(
children: [
SizedBox(
height: 150,
child: Stack(
fit: StackFit.expand,
children: [
if (app.background != null)
CloudFileWidget(
AspectRatio(
aspectRatio: 16 / 7,
child: CloudFileWidget(
item: app.background!,
fit: BoxFit.cover,
).clipRRect(topLeft: 8, topRight: 8),
if (app.picture != null)
Positioned(
left: 16,
bottom: 16,
child: ProfilePictureWidget(
fileId: app.picture!.id,
radius: 40,
fallbackIcon: Symbols.apps,
),
),
],
),
),
ListTile(
title: Text(app.name),
leading: ProfilePictureWidget(
fileId: app.picture?.id,
fallbackIcon: Symbols.apps,
),
subtitle: Text(
app.slug,
style: GoogleFonts.robotoMono(fontSize: 12),
@@ -180,8 +165,7 @@ class CustomAppsScreen extends HookConsumerWidget {
right: 12,
),
trailing: PopupMenuButton(
itemBuilder:
(context) => [
itemBuilder: (context) => [
PopupMenuItem(
value: 'edit',
child: Row(
@@ -203,9 +187,7 @@ class CustomAppsScreen extends HookConsumerWidget {
const SizedBox(width: 12),
Text(
'delete',
style: TextStyle(
color: Colors.red,
),
style: TextStyle(color: Colors.red),
).tr(),
],
),
@@ -216,8 +198,7 @@ class CustomAppsScreen extends HookConsumerWidget {
showModalBottomSheet(
context: context,
isScrollControlled: true,
builder:
(context) => SheetScaffold(
builder: (context) => SheetScaffold(
titleText: 'editCustomApp'.tr(),
child: EditAppScreen(
publisherName: publisherName,
@@ -264,13 +245,10 @@ class CustomAppsScreen extends HookConsumerWidget {
);
},
loading: () => const Center(child: CircularProgressIndicator()),
error:
(err, stack) => ResponseErrorWidget(
error: (err, stack) => ResponseErrorWidget(
error: err,
onRetry:
() => ref.invalidate(
customAppsProvider(publisherName, projectId),
),
onRetry: () =>
ref.invalidate(customAppsProvider(publisherName, projectId)),
),
);
}

View File

@@ -36,8 +36,7 @@ class BotDetailScreen extends HookConsumerWidget {
actions: [
IconButton(
icon: const Icon(Symbols.edit),
onPressed:
botData.value == null
onPressed: botData.value == null
? null
: () {
context.pushNamed(
@@ -84,23 +83,32 @@ class BotDetailScreen extends HookConsumerWidget {
controller: tabController,
physics: const NeverScrollableScrollPhysics(),
children: [
_BotOverview(bot: bot),
BotKeysScreen(
Align(
alignment: Alignment.topCenter,
child: ConstrainedBox(
constraints: const BoxConstraints(maxWidth: 640),
child: _BotOverview(bot: bot),
),
),
Align(
alignment: Alignment.topCenter,
child: ConstrainedBox(
constraints: const BoxConstraints(maxWidth: 640),
child: BotKeysScreen(
publisherName: publisherName,
projectId: projectId,
botId: botId,
),
),
),
],
);
},
loading: () => const Center(child: CircularProgressIndicator()),
error:
(err, stack) => ResponseErrorWidget(
error: (err, stack) => ResponseErrorWidget(
error: err,
onRetry:
() => ref.invalidate(
botProvider(publisherName, projectId, botId),
),
onRetry: () =>
ref.invalidate(botProvider(publisherName, projectId, botId)),
),
),
);
@@ -124,8 +132,7 @@ class _BotOverview extends StatelessWidget {
children: [
Container(
color: Theme.of(context).colorScheme.surfaceContainer,
child:
bot.account.profile.background != null
child: bot.account.profile.background != null
? CloudFileWidget(
item: bot.account.profile.background!,
fit: BoxFit.cover,

View File

@@ -53,8 +53,7 @@ class BotKeysScreen extends HookConsumerWidget {
showModalBottomSheet(
context: context,
isScrollControlled: true,
builder:
(context) => SheetScaffold(
builder: (context) => SheetScaffold(
titleText: 'newKeyGenerated'.tr(),
child: Padding(
padding: const EdgeInsets.all(20.0),
@@ -94,8 +93,8 @@ class BotKeysScreen extends HookConsumerWidget {
showModalBottomSheet(
context: context,
isScrollControlled: true,
builder:
(context) => SheetScaffold(
builder: (context) => SheetScaffold(
heightFactor: 0.7,
titleText: 'newBotKey'.tr(),
child: Padding(
padding: const EdgeInsets.all(20.0),
@@ -105,7 +104,12 @@ class BotKeysScreen extends HookConsumerWidget {
children: [
TextFormField(
controller: keyNameController,
decoration: InputDecoration(labelText: 'keyName'.tr()),
decoration: InputDecoration(
labelText: 'keyName'.tr(),
border: const OutlineInputBorder(
borderRadius: BorderRadius.all(Radius.circular(12)),
),
),
autofocus: true,
),
const SizedBox(height: 20),
@@ -189,22 +193,15 @@ class BotKeysScreen extends HookConsumerWidget {
ListTile(
leading: const Icon(Symbols.add),
title: Text('newBotKey'.tr()),
trailing: const Icon(Symbols.chevron_right),
onTap: createKey,
),
const Divider(height: 1),
Expanded(
child:
data.isEmpty
child: data.isEmpty
? Center(child: Text('noBotKeys'.tr()))
: RefreshIndicator(
onRefresh:
() => ref.refresh(
botKeysProvider(
publisherName,
projectId,
botId,
).future,
onRefresh: () => ref.refresh(
botKeysProvider(publisherName, projectId, botId).future,
),
child: ListView.builder(
padding: EdgeInsets.zero,
@@ -219,8 +216,7 @@ class BotKeysScreen extends HookConsumerWidget {
right: 12,
),
trailing: PopupMenuButton(
itemBuilder:
(context) => [
itemBuilder: (context) => [
PopupMenuItem(
value: 'rotate',
child: Row(
@@ -242,9 +238,7 @@ class BotKeysScreen extends HookConsumerWidget {
const Gap(12),
Text(
'revoke'.tr(),
style: TextStyle(
color: Colors.red,
),
style: TextStyle(color: Colors.red),
),
],
),
@@ -267,13 +261,10 @@ class BotKeysScreen extends HookConsumerWidget {
);
},
loading: () => const Center(child: CircularProgressIndicator()),
error:
(err, stack) => ResponseErrorWidget(
error: (err, stack) => ResponseErrorWidget(
error: err,
onRetry:
() => ref.invalidate(
botKeysProvider(publisherName, projectId, botId),
),
onRetry: () =>
ref.invalidate(botKeysProvider(publisherName, projectId, botId)),
),
);
}

View File

@@ -54,8 +54,7 @@ class BotsScreen extends HookConsumerWidget {
showModalBottomSheet(
context: context,
isScrollControlled: true,
builder:
(context) => SheetScaffold(
builder: (context) => SheetScaffold(
titleText: 'createBot'.tr(),
child: NewBotScreen(
publisherName: publisherName,
@@ -73,8 +72,8 @@ class BotsScreen extends HookConsumerWidget {
);
}
return ExtendedRefreshIndicator(
onRefresh:
() => ref.refresh(botsProvider(publisherName, projectId).future),
onRefresh: () =>
ref.refresh(botsProvider(publisherName, projectId).future),
child: Column(
children: [
const Gap(8),
@@ -86,8 +85,7 @@ class BotsScreen extends HookConsumerWidget {
showModalBottomSheet(
context: context,
isScrollControlled: true,
builder:
(context) => SheetScaffold(
builder: (context) => SheetScaffold(
titleText: 'createBot'.tr(),
child: NewBotScreen(
publisherName: publisherName,
@@ -108,23 +106,30 @@ class BotsScreen extends HookConsumerWidget {
itemBuilder: (context, index) {
final bot = data[index];
return Card(
child: ListTile(
shape: const RoundedRectangleBorder(
borderRadius: BorderRadius.all(Radius.circular(8.0)),
child: Column(
children: [
if (bot.account.profile.background != null)
AspectRatio(
aspectRatio: 16 / 7,
child: CloudFileWidget(
item: bot.account.profile.background!,
fit: BoxFit.cover,
).clipRRect(topLeft: 8, topRight: 8),
),
leading: CircleAvatar(
child:
bot.account.profile.picture != null
? ProfilePictureWidget(
file: bot.account.profile.picture!,
)
: const Icon(Symbols.smart_toy),
ListTile(
shape: const RoundedRectangleBorder(
borderRadius: BorderRadius.all(
Radius.circular(8.0),
),
),
leading: ProfilePictureWidget(
fallbackIcon: Symbols.smart_toy,
file: bot.account.profile.picture,
),
title: Text(bot.account.nick),
subtitle: Text(bot.account.name),
trailing: PopupMenuButton(
itemBuilder:
(context) => [
itemBuilder: (context) => [
PopupMenuItem(
value: 'edit',
child: Row(
@@ -157,8 +162,7 @@ class BotsScreen extends HookConsumerWidget {
showModalBottomSheet(
context: context,
isScrollControlled: true,
builder:
(context) => SheetScaffold(
builder: (context) => SheetScaffold(
titleText: 'editBot'.tr(),
child: EditBotScreen(
publisherName: publisherName,
@@ -175,7 +179,9 @@ class BotsScreen extends HookConsumerWidget {
isDanger: true,
).then((confirm) {
if (confirm) {
final client = ref.read(apiClientProvider);
final client = ref.read(
apiClientProvider,
);
client.delete(
'/develop/developers/$publisherName/projects/$projectId/bots/${bot.id}',
);
@@ -198,6 +204,8 @@ class BotsScreen extends HookConsumerWidget {
);
},
),
],
),
);
},
),
@@ -207,11 +215,9 @@ class BotsScreen extends HookConsumerWidget {
);
},
loading: () => const Center(child: CircularProgressIndicator()),
error:
(err, stack) => ResponseErrorWidget(
error: (err, stack) => ResponseErrorWidget(
error: err,
onRetry:
() => ref.invalidate(botsProvider(publisherName, projectId)),
onRetry: () => ref.invalidate(botsProvider(publisherName, projectId)),
),
);
}

View File

@@ -24,6 +24,16 @@ class ProjectDetailView extends HookConsumerWidget {
@override
Widget build(BuildContext context, WidgetRef ref) {
final tabController = useTabController(initialLength: 2);
final currentDest = useState(0);
useEffect(() {
tabController.addListener(() {
if (tabController.indexIsChanging) {
currentDest.value = tabController.index;
}
});
return null;
});
final isWide = isWideScreen(context);
@@ -38,14 +48,13 @@ class ProjectDetailView extends HookConsumerWidget {
child: NavigationRail(
extended: isWiderScreen(context),
scrollable: true,
labelType:
isWiderScreen(context)
labelType: isWiderScreen(context)
? null
: NavigationRailLabelType.selected,
backgroundColor: Colors.transparent,
selectedIndex: tabController.index,
onDestinationSelected:
(index) => tabController.animateTo(index),
selectedIndex: currentDest.value,
onDestinationSelected: (index) =>
tabController.animateTo(index),
destinations: [
NavigationRailDestination(
icon: Icon(Icons.apps),

View File

@@ -1,4 +1,7 @@
import 'dart:io';
import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:go_router/go_router.dart';
@@ -8,6 +11,7 @@ import 'package:island/models/post.dart';
import 'package:island/screens/posts/compose.dart';
import 'package:island/screens/posts/post_detail.dart';
import 'package:island/services/compose_storage_db.dart';
import 'package:island/services/responsive.dart';
import 'package:island/widgets/content/sheet.dart';
import 'package:island/widgets/post/compose_card.dart';
import 'package:island/widgets/post/compose_shared.dart';
@@ -171,7 +175,14 @@ class PostComposeSheet extends HookConsumerWidget {
),
];
// Tablet will show a virtual keyboard, so we adjust the height factor accordingly
final isTablet =
isWideScreen(context) &&
!kIsWeb &&
(Platform.isAndroid || Platform.isAndroid);
return SheetScaffold(
heightFactor: isTablet ? 0.95 : 0.8,
titleText: 'postCompose'.tr(),
actions: actions,
child: PostComposeCard(

View File

@@ -1,6 +1,7 @@
import 'dart:io';
import 'package:easy_localization/easy_localization.dart';
import 'package:file_picker/file_picker.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:gap/gap.dart';
@@ -10,6 +11,7 @@ import 'package:island/pods/site_files.dart';
import 'package:island/widgets/alert.dart';
import 'package:island/widgets/sites/file_upload_dialog.dart';
import 'package:island/widgets/sites/file_item.dart';
import 'package:permission_handler/permission_handler.dart';
import 'package:material_symbols_icons/symbols.dart';
import 'package:path/path.dart' as p;
@@ -53,6 +55,9 @@ class FileManagementSection extends HookConsumerWidget {
PopupMenuButton<String>(
icon: const Icon(Symbols.upload),
onSelected: (String choice) async {
if (!kIsWeb) {
await Permission.storage.request();
}
List<File> files = [];
List<Map<String, dynamic>>? results;
if (choice == 'files') {
@@ -65,17 +70,17 @@ class FileManagementSection extends HookConsumerWidget {
selectedFiles.files.isEmpty) {
return; // User canceled
}
files =
selectedFiles.files
files = selectedFiles.files
.map((f) => File(f.path!))
.toList();
} else if (choice == 'folder') {
final dirPath =
await FilePicker.platform.getDirectoryPath();
final dirPath = await FilePicker.platform
.getDirectoryPath();
if (dirPath == null) return;
results = await _getFilesRecursive(dirPath);
files =
results.map((m) => m['file'] as File).toList();
files = results
.map((m) => m['file'] as File)
.toList();
if (files.isEmpty) {
showSnackBar('noFilesFoundInFolder'.tr());
return;
@@ -88,15 +93,11 @@ class FileManagementSection extends HookConsumerWidget {
showModalBottomSheet(
context: context,
isScrollControlled: true,
builder:
(context) => FileUploadDialog(
builder: (context) => FileUploadDialog(
selectedFiles: files,
site: site,
relativePaths:
results
?.map(
(m) => m['relativePath'] as String,
)
relativePaths: results
?.map((m) => m['relativePath'] as String)
.toList(),
onUploadComplete: () {
// Refresh file list
@@ -110,8 +111,7 @@ class FileManagementSection extends HookConsumerWidget {
),
);
},
itemBuilder:
(BuildContext context) => [
itemBuilder: (BuildContext context) => [
PopupMenuItem<String>(
value: 'files',
child: Row(
@@ -156,8 +156,7 @@ class FileManagementSection extends HookConsumerWidget {
IconButton(
icon: Icon(Symbols.arrow_back),
onPressed: () {
final pathParts =
currentPath.value!
final pathParts = currentPath.value!
.split('/')
.where((part) => part.isNotEmpty)
.toList();
@@ -165,8 +164,7 @@ class FileManagementSection extends HookConsumerWidget {
currentPath.value = null;
} else {
pathParts.removeLast();
currentPath.value =
pathParts.isEmpty
currentPath.value = pathParts.isEmpty
? null
: pathParts.join('/');
}
@@ -185,8 +183,7 @@ class FileManagementSection extends HookConsumerWidget {
child: Text('siteRoot'.tr()),
),
...() {
final parts =
currentPath.value!
final parts = currentPath.value!
.split('/')
.where((part) => part.isNotEmpty)
.toList();
@@ -200,8 +197,8 @@ class FileManagementSection extends HookConsumerWidget {
widgets.addAll([
const Text(' / '),
InkWell(
onTap:
() => currentPath.value = pathToSet,
onTap: () =>
currentPath.value = pathToSet,
child: Text(part),
),
]);
@@ -253,23 +250,21 @@ class FileManagementSection extends HookConsumerWidget {
return FileItem(
file: file,
site: site,
onNavigateDirectory:
(path) => currentPath.value = path,
onNavigateDirectory: (path) =>
currentPath.value = path,
);
},
);
},
loading:
() => const Center(child: CircularProgressIndicator()),
error:
(error, stack) => Center(
loading: () =>
const Center(child: CircularProgressIndicator()),
error: (error, stack) => Center(
child: Column(
children: [
Text('failedToLoadFiles'.tr()),
const Gap(8),
ElevatedButton(
onPressed:
() => ref.invalidate(
onPressed: () => ref.invalidate(
siteFilesProvider(
siteId: site.id,
path: currentPath.value,

View File

@@ -19,8 +19,7 @@ class SiteActionMenu extends HookConsumerWidget {
@override
Widget build(BuildContext context, WidgetRef ref) {
return PopupMenuButton<String>(
itemBuilder:
(context) => [
itemBuilder: (context) => [
PopupMenuItem(
value: 'edit',
child: Row(
@@ -52,31 +51,18 @@ class SiteActionMenu extends HookConsumerWidget {
showModalBottomSheet(
context: context,
isScrollControlled: true,
builder:
(context) => SiteForm(pubName: pubName, siteSlug: site.slug),
builder: (context) =>
SiteForm(pubName: pubName, siteSlug: site.slug),
).then((_) {
// Refresh site data after potential edit
ref.invalidate(publicationSiteDetailProvider(pubName, site.slug));
});
break;
case 'delete':
final confirmed = await showDialog<bool>(
context: context,
builder:
(context) => AlertDialog(
title: Text('deleteSite'.tr()),
content: Text('publicationSiteDeleteConfirm'.tr()),
actions: [
TextButton(
onPressed: () => Navigator.of(context).pop(false),
child: Text('cancel'.tr()),
),
TextButton(
onPressed: () => Navigator.of(context).pop(true),
child: Text('delete'.tr()),
),
],
),
final confirmed = await showConfirmAlert(
'publicationSiteDeleteConfirm'.tr(),
'deleteSite'.tr(),
isDanger: true,
);
if (confirmed == true) {

View File

@@ -1917,6 +1917,54 @@ packages:
url: "https://pub.dev"
source: hosted
version: "3.1.0+3"
permission_handler:
dependency: "direct main"
description:
name: permission_handler
sha256: bc917da36261b00137bbc8896bf1482169cd76f866282368948f032c8c1caae1
url: "https://pub.dev"
source: hosted
version: "12.0.1"
permission_handler_android:
dependency: transitive
description:
name: permission_handler_android
sha256: "1e3bc410ca1bf84662104b100eb126e066cb55791b7451307f9708d4007350e6"
url: "https://pub.dev"
source: hosted
version: "13.0.1"
permission_handler_apple:
dependency: transitive
description:
name: permission_handler_apple
sha256: f000131e755c54cf4d84a5d8bd6e4149e262cc31c5a8b1d698de1ac85fa41023
url: "https://pub.dev"
source: hosted
version: "9.4.7"
permission_handler_html:
dependency: transitive
description:
name: permission_handler_html
sha256: "38f000e83355abb3392140f6bc3030660cfaef189e1f87824facb76300b4ff24"
url: "https://pub.dev"
source: hosted
version: "0.1.3+5"
permission_handler_platform_interface:
dependency: transitive
description:
name: permission_handler_platform_interface
sha256: eb99b295153abce5d683cac8c02e22faab63e50679b937fa1bf67d58bb282878
url: "https://pub.dev"
source: hosted
version: "4.3.0"
permission_handler_windows:
dependency: transitive
description:
name: permission_handler_windows
sha256: "1a790728016f79a41216d88672dbc5df30e686e811ad4e698bfc51f76ad91f1e"
url: "https://pub.dev"
source: hosted
version: "0.2.1"
petitparser:
dependency: transitive
description:

View File

@@ -16,7 +16,7 @@ publish_to: "none" # Remove this line if you wish to publish to pub.dev
# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html
# In Windows, build-name is used as the major, minor, and patch parts
# of the product and file versions while build-number is used as the build suffix.
version: 3.5.0+150
version: 3.5.0+151
environment:
sdk: ^3.8.0
@@ -171,6 +171,7 @@ dependencies:
http_parser: ^4.1.2
flutter_code_editor: ^0.3.5
skeletonizer: ^2.1.1
permission_handler: ^12.0.1
dev_dependencies:
flutter_test:

View File

@@ -24,6 +24,7 @@
#include <media_kit_libs_windows_video/media_kit_libs_windows_video_plugin_c_api.h>
#include <media_kit_video/media_kit_video_plugin_c_api.h>
#include <pasteboard/pasteboard_plugin.h>
#include <permission_handler_windows/permission_handler_windows_plugin.h>
#include <protocol_handler_windows/protocol_handler_windows_plugin_c_api.h>
#include <record_windows/record_windows_plugin_c_api.h>
#include <screen_retriever_windows/screen_retriever_windows_plugin_c_api.h>
@@ -73,6 +74,8 @@ void RegisterPlugins(flutter::PluginRegistry* registry) {
registry->GetRegistrarForPlugin("MediaKitVideoPluginCApi"));
PasteboardPluginRegisterWithRegistrar(
registry->GetRegistrarForPlugin("PasteboardPlugin"));
PermissionHandlerWindowsPluginRegisterWithRegistrar(
registry->GetRegistrarForPlugin("PermissionHandlerWindowsPlugin"));
ProtocolHandlerWindowsPluginCApiRegisterWithRegistrar(
registry->GetRegistrarForPlugin("ProtocolHandlerWindowsPluginCApi"));
RecordWindowsPluginCApiRegisterWithRegistrar(

View File

@@ -21,6 +21,7 @@ list(APPEND FLUTTER_PLUGIN_LIST
media_kit_libs_windows_video
media_kit_video
pasteboard
permission_handler_windows
protocol_handler_windows
record_windows
screen_retriever_windows