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.BLUETOOTH_ADMIN" android:maxSdkVersion="30" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"
android:maxSdkVersion="29" /> 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.POST_NOTIFICATIONS" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" /> <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="com.google.android.c2dm.permission.RECEIVE" /> <uses-permission android:name="com.google.android.c2dm.permission.RECEIVE" />
@@ -159,4 +161,4 @@
<data android:mimeType="text/plain" /> <data android:mimeType="text/plain" />
</intent> </intent>
</queries> </queries>
</manifest> </manifest>

View File

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

View File

@@ -167,22 +167,10 @@ class _CreatorSiteItem extends HookConsumerWidget {
], ],
), ),
onTap: () async { onTap: () async {
final confirmed = await showDialog<bool>( final confirmed = await showConfirmAlert(
context: context, 'publicationSiteDeleteConfirm'.tr(),
builder: (context) => AlertDialog( 'deleteSite'.tr(),
title: Text('deleteSite'.tr()), isDanger: true,
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()),
),
],
),
); );
if (confirmed == true) { if (confirmed == true) {
try { try {

View File

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

View File

@@ -2,6 +2,7 @@ import 'package:flutter/material.dart';
import 'package:flutter/services.dart'; import 'package:flutter/services.dart';
import 'package:easy_localization/easy_localization.dart'; import 'package:easy_localization/easy_localization.dart';
import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:gap/gap.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:island/models/custom_app_secret.dart'; import 'package:island/models/custom_app_secret.dart';
import 'package:island/pods/network.dart'; import 'package:island/pods/network.dart';
@@ -53,37 +54,36 @@ class AppSecretsScreen extends HookConsumerWidget {
showModalBottomSheet( showModalBottomSheet(
context: context, context: context,
isScrollControlled: true, isScrollControlled: true,
builder: builder: (context) => SheetScaffold(
(context) => SheetScaffold( titleText: 'newSecretGenerated'.tr(),
titleText: 'newSecretGenerated'.tr(), child: Padding(
child: Padding( padding: const EdgeInsets.all(20.0),
padding: const EdgeInsets.all(20.0), child: Column(
child: Column( crossAxisAlignment: CrossAxisAlignment.stretch,
crossAxisAlignment: CrossAxisAlignment.stretch, mainAxisSize: MainAxisSize.min,
mainAxisSize: MainAxisSize.min, children: [
children: [ Text('copySecretHint'.tr()),
Text('copySecretHint'.tr()), const SizedBox(height: 16),
const SizedBox(height: 16), Container(
Container( padding: const EdgeInsets.all(12),
padding: const EdgeInsets.all(12), decoration: BoxDecoration(
decoration: BoxDecoration( color: Theme.of(context).colorScheme.surfaceContainer,
color: Theme.of(context).colorScheme.surfaceContainer, borderRadius: BorderRadius.circular(8),
borderRadius: BorderRadius.circular(8), ),
), child: SelectableText(newSecret),
child: SelectableText(newSecret),
),
const SizedBox(height: 20),
FilledButton.icon(
onPressed: () {
Clipboard.setData(ClipboardData(text: newSecret));
},
icon: const Icon(Symbols.copy_all),
label: Text('copy'.tr()),
),
],
), ),
), const SizedBox(height: 20),
FilledButton.icon(
onPressed: () {
Clipboard.setData(ClipboardData(text: newSecret));
},
icon: const Icon(Symbols.copy_all),
label: Text('copy'.tr()),
),
],
), ),
),
),
).whenComplete(() { ).whenComplete(() {
ref.invalidate( ref.invalidate(
customAppSecretsProvider(publisherName, projectId, appId), customAppSecretsProvider(publisherName, projectId, appId),
@@ -114,22 +114,38 @@ class AppSecretsScreen extends HookConsumerWidget {
controller: descriptionController, controller: descriptionController,
decoration: InputDecoration( decoration: InputDecoration(
labelText: 'description'.tr(), labelText: 'description'.tr(),
border: OutlineInputBorder(
borderRadius: const BorderRadius.all(
Radius.circular(12),
),
),
), ),
autofocus: true, autofocus: true,
), ),
const SizedBox(height: 20), const Gap(16),
TextFormField( TextFormField(
controller: expiresInController, controller: expiresInController,
decoration: InputDecoration( decoration: InputDecoration(
labelText: 'expiresIn'.tr(), labelText: 'expiresIn'.tr(),
border: OutlineInputBorder(
borderRadius: const BorderRadius.all(
Radius.circular(12),
),
),
), ),
keyboardType: TextInputType.number, keyboardType: TextInputType.number,
), ),
const SizedBox(height: 20), const Gap(16),
SwitchListTile( Card(
title: Text('isOidc'.tr()), margin: EdgeInsets.zero,
value: isOidc.value, child: SwitchListTile(
onChanged: (value) => isOidc.value = value, shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(8),
),
title: Text('isOidc'.tr()),
value: isOidc.value,
onChanged: (value) => isOidc.value = value,
),
), ),
const SizedBox(height: 20), const SizedBox(height: 20),
FilledButton.icon( FilledButton.icon(
@@ -175,14 +191,9 @@ class AppSecretsScreen extends HookConsumerWidget {
return secrets.when( return secrets.when(
data: (data) { data: (data) {
return RefreshIndicator( return RefreshIndicator(
onRefresh: onRefresh: () => ref.refresh(
() => ref.refresh( customAppSecretsProvider(publisherName, projectId, appId).future,
customAppSecretsProvider( ),
publisherName,
projectId,
appId,
).future,
),
child: Column( child: Column(
children: [ children: [
ListTile( ListTile(
@@ -240,14 +251,12 @@ class AppSecretsScreen extends HookConsumerWidget {
); );
}, },
loading: () => const Center(child: CircularProgressIndicator()), loading: () => const Center(child: CircularProgressIndicator()),
error: error: (err, stack) => ResponseErrorWidget(
(err, stack) => ResponseErrorWidget( error: err,
error: err, onRetry: () => ref.invalidate(
onRetry: customAppSecretsProvider(publisherName, projectId, appId),
() => ref.invalidate( ),
customAppSecretsProvider(publisherName, projectId, appId), ),
),
),
); );
} }
} }

View File

@@ -76,15 +76,14 @@ class CustomAppsScreen extends HookConsumerWidget {
showModalBottomSheet( showModalBottomSheet(
context: context, context: context,
isScrollControlled: true, isScrollControlled: true,
builder: builder: (context) => SheetScaffold(
(context) => SheetScaffold( titleText: 'createCustomApp'.tr(),
titleText: 'createCustomApp'.tr(), child: NewCustomAppScreen(
child: NewCustomAppScreen( publisherName: publisherName,
publisherName: publisherName, projectId: projectId,
projectId: projectId, isModal: true,
isModal: true, ),
), ),
),
); );
}, },
icon: const Icon(Symbols.add), icon: const Icon(Symbols.add),
@@ -95,10 +94,8 @@ class CustomAppsScreen extends HookConsumerWidget {
); );
} }
return ExtendedRefreshIndicator( return ExtendedRefreshIndicator(
onRefresh: onRefresh: () =>
() => ref.refresh( ref.refresh(customAppsProvider(publisherName, projectId).future),
customAppsProvider(publisherName, projectId).future,
),
child: Column( child: Column(
children: [ children: [
const Gap(8), const Gap(8),
@@ -110,15 +107,14 @@ class CustomAppsScreen extends HookConsumerWidget {
showModalBottomSheet( showModalBottomSheet(
context: context, context: context,
isScrollControlled: true, isScrollControlled: true,
builder: builder: (context) => SheetScaffold(
(context) => SheetScaffold( titleText: 'createCustomApp'.tr(),
titleText: 'createCustomApp'.tr(), child: NewCustomAppScreen(
child: NewCustomAppScreen( publisherName: publisherName,
publisherName: publisherName, projectId: projectId,
projectId: projectId, isModal: true,
isModal: true, ),
), ),
),
); );
}, },
icon: const Icon(Symbols.add), icon: const Icon(Symbols.add),
@@ -146,31 +142,20 @@ class CustomAppsScreen extends HookConsumerWidget {
}, },
child: Column( child: Column(
children: [ children: [
SizedBox( if (app.background != null)
height: 150, AspectRatio(
child: Stack( aspectRatio: 16 / 7,
fit: StackFit.expand, child: CloudFileWidget(
children: [ item: app.background!,
if (app.background != null) fit: BoxFit.cover,
CloudFileWidget( ).clipRRect(topLeft: 8, topRight: 8),
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( ListTile(
title: Text(app.name), title: Text(app.name),
leading: ProfilePictureWidget(
fileId: app.picture?.id,
fallbackIcon: Symbols.apps,
),
subtitle: Text( subtitle: Text(
app.slug, app.slug,
style: GoogleFonts.robotoMono(fontSize: 12), style: GoogleFonts.robotoMono(fontSize: 12),
@@ -180,52 +165,48 @@ class CustomAppsScreen extends HookConsumerWidget {
right: 12, right: 12,
), ),
trailing: PopupMenuButton( trailing: PopupMenuButton(
itemBuilder: itemBuilder: (context) => [
(context) => [ PopupMenuItem(
PopupMenuItem( value: 'edit',
value: 'edit', child: Row(
child: Row( children: [
children: [ const Icon(Symbols.edit),
const Icon(Symbols.edit), const SizedBox(width: 12),
const SizedBox(width: 12), Text('edit').tr(),
Text('edit').tr(), ],
], ),
),
PopupMenuItem(
value: 'delete',
child: Row(
children: [
const Icon(
Symbols.delete,
color: Colors.red,
), ),
), const SizedBox(width: 12),
PopupMenuItem( Text(
value: 'delete', 'delete',
child: Row( style: TextStyle(color: Colors.red),
children: [ ).tr(),
const Icon( ],
Symbols.delete, ),
color: Colors.red, ),
), ],
const SizedBox(width: 12),
Text(
'delete',
style: TextStyle(
color: Colors.red,
),
).tr(),
],
),
),
],
onSelected: (value) { onSelected: (value) {
if (value == 'edit') { if (value == 'edit') {
showModalBottomSheet( showModalBottomSheet(
context: context, context: context,
isScrollControlled: true, isScrollControlled: true,
builder: builder: (context) => SheetScaffold(
(context) => SheetScaffold( titleText: 'editCustomApp'.tr(),
titleText: 'editCustomApp'.tr(), child: EditAppScreen(
child: EditAppScreen( publisherName: publisherName,
publisherName: publisherName, projectId: projectId,
projectId: projectId, id: app.id,
id: app.id, isModal: true,
isModal: true, ),
), ),
),
); );
} else if (value == 'delete') { } else if (value == 'delete') {
showConfirmAlert( showConfirmAlert(
@@ -264,14 +245,11 @@ class CustomAppsScreen extends HookConsumerWidget {
); );
}, },
loading: () => const Center(child: CircularProgressIndicator()), loading: () => const Center(child: CircularProgressIndicator()),
error: error: (err, stack) => ResponseErrorWidget(
(err, stack) => ResponseErrorWidget( error: err,
error: err, onRetry: () =>
onRetry: ref.invalidate(customAppsProvider(publisherName, projectId)),
() => ref.invalidate( ),
customAppsProvider(publisherName, projectId),
),
),
); );
} }
} }

View File

@@ -36,19 +36,18 @@ class BotDetailScreen extends HookConsumerWidget {
actions: [ actions: [
IconButton( IconButton(
icon: const Icon(Symbols.edit), icon: const Icon(Symbols.edit),
onPressed: onPressed: botData.value == null
botData.value == null ? null
? null : () {
: () { context.pushNamed(
context.pushNamed( 'developerBotEdit',
'developerBotEdit', pathParameters: {
pathParameters: { 'name': publisherName,
'name': publisherName, 'projectId': projectId,
'projectId': projectId, 'id': botId,
'id': botId, },
}, );
); },
},
), ),
], ],
bottom: TabBar( bottom: TabBar(
@@ -84,24 +83,33 @@ class BotDetailScreen extends HookConsumerWidget {
controller: tabController, controller: tabController,
physics: const NeverScrollableScrollPhysics(), physics: const NeverScrollableScrollPhysics(),
children: [ children: [
_BotOverview(bot: bot), Align(
BotKeysScreen( alignment: Alignment.topCenter,
publisherName: publisherName, child: ConstrainedBox(
projectId: projectId, constraints: const BoxConstraints(maxWidth: 640),
botId: botId, 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()), loading: () => const Center(child: CircularProgressIndicator()),
error: error: (err, stack) => ResponseErrorWidget(
(err, stack) => ResponseErrorWidget( error: err,
error: err, onRetry: () =>
onRetry: ref.invalidate(botProvider(publisherName, projectId, botId)),
() => ref.invalidate( ),
botProvider(publisherName, projectId, botId),
),
),
), ),
); );
} }
@@ -124,13 +132,12 @@ class _BotOverview extends StatelessWidget {
children: [ children: [
Container( Container(
color: Theme.of(context).colorScheme.surfaceContainer, color: Theme.of(context).colorScheme.surfaceContainer,
child: child: bot.account.profile.background != null
bot.account.profile.background != null ? CloudFileWidget(
? CloudFileWidget( item: bot.account.profile.background!,
item: bot.account.profile.background!, fit: BoxFit.cover,
fit: BoxFit.cover, )
) : const SizedBox.shrink(),
: const SizedBox.shrink(),
), ),
Positioned( Positioned(
left: 20, left: 20,

View File

@@ -53,37 +53,36 @@ class BotKeysScreen extends HookConsumerWidget {
showModalBottomSheet( showModalBottomSheet(
context: context, context: context,
isScrollControlled: true, isScrollControlled: true,
builder: builder: (context) => SheetScaffold(
(context) => SheetScaffold( titleText: 'newKeyGenerated'.tr(),
titleText: 'newKeyGenerated'.tr(), child: Padding(
child: Padding( padding: const EdgeInsets.all(20.0),
padding: const EdgeInsets.all(20.0), child: Column(
child: Column( crossAxisAlignment: CrossAxisAlignment.stretch,
crossAxisAlignment: CrossAxisAlignment.stretch, mainAxisSize: MainAxisSize.min,
mainAxisSize: MainAxisSize.min, children: [
children: [ Text('copyKeyHint'.tr()),
Text('copyKeyHint'.tr()), const SizedBox(height: 16),
const SizedBox(height: 16), Container(
Container( padding: const EdgeInsets.all(12),
padding: const EdgeInsets.all(12), decoration: BoxDecoration(
decoration: BoxDecoration( color: Theme.of(context).colorScheme.surfaceContainer,
color: Theme.of(context).colorScheme.surfaceContainer, borderRadius: BorderRadius.circular(8),
borderRadius: BorderRadius.circular(8), ),
), child: SelectableText(token),
child: SelectableText(token),
),
const SizedBox(height: 20),
FilledButton.icon(
onPressed: () {
Clipboard.setData(ClipboardData(text: token));
},
icon: const Icon(Symbols.copy_all),
label: Text('copy'.tr()),
),
],
), ),
), const SizedBox(height: 20),
FilledButton.icon(
onPressed: () {
Clipboard.setData(ClipboardData(text: token));
},
icon: const Icon(Symbols.copy_all),
label: Text('copy'.tr()),
),
],
), ),
),
),
).whenComplete(() { ).whenComplete(() {
ref.invalidate(botKeysProvider(publisherName, projectId, botId)); ref.invalidate(botKeysProvider(publisherName, projectId, botId));
}); });
@@ -94,45 +93,50 @@ class BotKeysScreen extends HookConsumerWidget {
showModalBottomSheet( showModalBottomSheet(
context: context, context: context,
isScrollControlled: true, isScrollControlled: true,
builder: builder: (context) => SheetScaffold(
(context) => SheetScaffold( heightFactor: 0.7,
titleText: 'newBotKey'.tr(), titleText: 'newBotKey'.tr(),
child: Padding( child: Padding(
padding: const EdgeInsets.all(20.0), padding: const EdgeInsets.all(20.0),
child: Column( child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch, crossAxisAlignment: CrossAxisAlignment.stretch,
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
children: [ children: [
TextFormField( TextFormField(
controller: keyNameController, controller: keyNameController,
decoration: InputDecoration(labelText: 'keyName'.tr()), decoration: InputDecoration(
autofocus: true, labelText: 'keyName'.tr(),
border: const OutlineInputBorder(
borderRadius: BorderRadius.all(Radius.circular(12)),
), ),
const SizedBox(height: 20), ),
FilledButton.icon( autofocus: true,
onPressed: () async {
if (keyNameController.text.isEmpty) return;
final keyName = keyNameController.text;
Navigator.pop(context); // Close the sheet
try {
final client = ref.read(apiClientProvider);
final resp = await client.post(
'/develop/developers/$publisherName/projects/$projectId/bots/$botId/keys',
data: {'label': keyName},
);
final newApiKey = SnAccountApiKey.fromJson(resp.data);
showNewKeySheet(newApiKey);
} catch (e) {
showErrorAlert(e.toString());
}
},
icon: const Icon(Symbols.add),
label: Text('create'.tr()),
),
],
), ),
), const SizedBox(height: 20),
FilledButton.icon(
onPressed: () async {
if (keyNameController.text.isEmpty) return;
final keyName = keyNameController.text;
Navigator.pop(context); // Close the sheet
try {
final client = ref.read(apiClientProvider);
final resp = await client.post(
'/develop/developers/$publisherName/projects/$projectId/bots/$botId/keys',
data: {'label': keyName},
);
final newApiKey = SnAccountApiKey.fromJson(resp.data);
showNewKeySheet(newApiKey);
} catch (e) {
showErrorAlert(e.toString());
}
},
icon: const Icon(Symbols.add),
label: Text('create'.tr()),
),
],
), ),
),
),
); );
} }
@@ -189,92 +193,79 @@ class BotKeysScreen extends HookConsumerWidget {
ListTile( ListTile(
leading: const Icon(Symbols.add), leading: const Icon(Symbols.add),
title: Text('newBotKey'.tr()), title: Text('newBotKey'.tr()),
trailing: const Icon(Symbols.chevron_right),
onTap: createKey, onTap: createKey,
), ),
const Divider(height: 1), const Divider(height: 1),
Expanded( Expanded(
child: child: data.isEmpty
data.isEmpty ? Center(child: Text('noBotKeys'.tr()))
? Center(child: Text('noBotKeys'.tr())) : RefreshIndicator(
: RefreshIndicator( onRefresh: () => ref.refresh(
onRefresh: botKeysProvider(publisherName, projectId, botId).future,
() => ref.refresh( ),
botKeysProvider( child: ListView.builder(
publisherName, padding: EdgeInsets.zero,
projectId, itemCount: data.length,
botId, itemBuilder: (context, index) {
).future, final apiKey = data[index];
return ListTile(
title: Text(apiKey.label),
subtitle: Text(apiKey.createdAt.formatSystem()),
contentPadding: EdgeInsets.only(
left: 16,
right: 12,
), ),
child: ListView.builder( trailing: PopupMenuButton(
padding: EdgeInsets.zero, itemBuilder: (context) => [
itemCount: data.length, PopupMenuItem(
itemBuilder: (context, index) { value: 'rotate',
final apiKey = data[index]; child: Row(
return ListTile( children: [
title: Text(apiKey.label), const Icon(Symbols.refresh),
subtitle: Text(apiKey.createdAt.formatSystem()), const Gap(12),
contentPadding: EdgeInsets.only( Text('rotateKey'.tr()),
left: 16, ],
right: 12, ),
), ),
trailing: PopupMenuButton( PopupMenuItem(
itemBuilder: value: 'revoke',
(context) => [ child: Row(
PopupMenuItem( children: [
value: 'rotate', const Icon(
child: Row( Symbols.delete,
children: [ color: Colors.red,
const Icon(Symbols.refresh),
const Gap(12),
Text('rotateKey'.tr()),
],
),
), ),
PopupMenuItem( const Gap(12),
value: 'revoke', Text(
child: Row( 'revoke'.tr(),
children: [ style: TextStyle(color: Colors.red),
const Icon(
Symbols.delete,
color: Colors.red,
),
const Gap(12),
Text(
'revoke'.tr(),
style: TextStyle(
color: Colors.red,
),
),
],
),
), ),
], ],
onSelected: (value) { ),
if (value == 'rotate') { ),
rotateKey(apiKey.id); ],
} else if (value == 'revoke') { onSelected: (value) {
revokeKey(apiKey.id); if (value == 'rotate') {
} rotateKey(apiKey.id);
}, } else if (value == 'revoke') {
), revokeKey(apiKey.id);
); }
}, },
), ),
);
},
), ),
),
), ),
], ],
); );
}, },
loading: () => const Center(child: CircularProgressIndicator()), loading: () => const Center(child: CircularProgressIndicator()),
error: error: (err, stack) => ResponseErrorWidget(
(err, stack) => ResponseErrorWidget( error: err,
error: err, onRetry: () =>
onRetry: ref.invalidate(botKeysProvider(publisherName, projectId, botId)),
() => ref.invalidate( ),
botKeysProvider(publisherName, projectId, botId),
),
),
); );
} }
} }

View File

@@ -54,15 +54,14 @@ class BotsScreen extends HookConsumerWidget {
showModalBottomSheet( showModalBottomSheet(
context: context, context: context,
isScrollControlled: true, isScrollControlled: true,
builder: builder: (context) => SheetScaffold(
(context) => SheetScaffold( titleText: 'createBot'.tr(),
titleText: 'createBot'.tr(), child: NewBotScreen(
child: NewBotScreen( publisherName: publisherName,
publisherName: publisherName, projectId: projectId,
projectId: projectId, isModal: true,
isModal: true, ),
), ),
),
); );
}, },
icon: const Icon(Symbols.add), icon: const Icon(Symbols.add),
@@ -73,8 +72,8 @@ class BotsScreen extends HookConsumerWidget {
); );
} }
return ExtendedRefreshIndicator( return ExtendedRefreshIndicator(
onRefresh: onRefresh: () =>
() => ref.refresh(botsProvider(publisherName, projectId).future), ref.refresh(botsProvider(publisherName, projectId).future),
child: Column( child: Column(
children: [ children: [
const Gap(8), const Gap(8),
@@ -86,15 +85,14 @@ class BotsScreen extends HookConsumerWidget {
showModalBottomSheet( showModalBottomSheet(
context: context, context: context,
isScrollControlled: true, isScrollControlled: true,
builder: builder: (context) => SheetScaffold(
(context) => SheetScaffold( titleText: 'createBot'.tr(),
titleText: 'createBot'.tr(), child: NewBotScreen(
child: NewBotScreen( publisherName: publisherName,
publisherName: publisherName, projectId: projectId,
projectId: projectId, isModal: true,
isModal: true, ),
), ),
),
); );
}, },
icon: const Icon(Symbols.add), icon: const Icon(Symbols.add),
@@ -108,23 +106,30 @@ class BotsScreen extends HookConsumerWidget {
itemBuilder: (context, index) { itemBuilder: (context, index) {
final bot = data[index]; final bot = data[index];
return Card( return Card(
child: ListTile( child: Column(
shape: const RoundedRectangleBorder( children: [
borderRadius: BorderRadius.all(Radius.circular(8.0)), if (bot.account.profile.background != null)
), AspectRatio(
leading: CircleAvatar( aspectRatio: 16 / 7,
child: child: CloudFileWidget(
bot.account.profile.picture != null item: bot.account.profile.background!,
? ProfilePictureWidget( fit: BoxFit.cover,
file: bot.account.profile.picture!, ).clipRRect(topLeft: 8, topRight: 8),
) ),
: const Icon(Symbols.smart_toy), ListTile(
), shape: const RoundedRectangleBorder(
title: Text(bot.account.nick), borderRadius: BorderRadius.all(
subtitle: Text(bot.account.name), Radius.circular(8.0),
trailing: PopupMenuButton( ),
itemBuilder: ),
(context) => [ 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) => [
PopupMenuItem( PopupMenuItem(
value: 'edit', value: 'edit',
child: Row( child: Row(
@@ -152,13 +157,12 @@ class BotsScreen extends HookConsumerWidget {
), ),
), ),
], ],
onSelected: (value) { onSelected: (value) {
if (value == 'edit') { if (value == 'edit') {
showModalBottomSheet( showModalBottomSheet(
context: context, context: context,
isScrollControlled: true, isScrollControlled: true,
builder: builder: (context) => SheetScaffold(
(context) => SheetScaffold(
titleText: 'editBot'.tr(), titleText: 'editBot'.tr(),
child: EditBotScreen( child: EditBotScreen(
publisherName: publisherName, publisherName: publisherName,
@@ -167,36 +171,40 @@ class BotsScreen extends HookConsumerWidget {
isModal: true, isModal: true,
), ),
), ),
);
} else if (value == 'delete') {
showConfirmAlert(
'deleteBotHint'.tr(),
'deleteBot'.tr(),
isDanger: true,
).then((confirm) {
if (confirm) {
final client = ref.read(apiClientProvider);
client.delete(
'/develop/developers/$publisherName/projects/$projectId/bots/${bot.id}',
);
ref.invalidate(
botsProvider(publisherName, projectId),
); );
} else if (value == 'delete') {
showConfirmAlert(
'deleteBotHint'.tr(),
'deleteBot'.tr(),
isDanger: true,
).then((confirm) {
if (confirm) {
final client = ref.read(
apiClientProvider,
);
client.delete(
'/develop/developers/$publisherName/projects/$projectId/bots/${bot.id}',
);
ref.invalidate(
botsProvider(publisherName, projectId),
);
}
});
} }
}); },
} ),
}, onTap: () {
), context.pushNamed(
onTap: () { 'developerBotDetail',
context.pushNamed( pathParameters: {
'developerBotDetail', 'name': publisherName,
pathParameters: { 'projectId': projectId,
'name': publisherName, 'botId': bot.id,
'projectId': projectId, },
'botId': bot.id, );
}, },
); ),
}, ],
), ),
); );
}, },
@@ -207,12 +215,10 @@ class BotsScreen extends HookConsumerWidget {
); );
}, },
loading: () => const Center(child: CircularProgressIndicator()), loading: () => const Center(child: CircularProgressIndicator()),
error: error: (err, stack) => ResponseErrorWidget(
(err, stack) => ResponseErrorWidget( error: err,
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 @override
Widget build(BuildContext context, WidgetRef ref) { Widget build(BuildContext context, WidgetRef ref) {
final tabController = useTabController(initialLength: 2); 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); final isWide = isWideScreen(context);
@@ -38,14 +48,13 @@ class ProjectDetailView extends HookConsumerWidget {
child: NavigationRail( child: NavigationRail(
extended: isWiderScreen(context), extended: isWiderScreen(context),
scrollable: true, scrollable: true,
labelType: labelType: isWiderScreen(context)
isWiderScreen(context) ? null
? null : NavigationRailLabelType.selected,
: NavigationRailLabelType.selected,
backgroundColor: Colors.transparent, backgroundColor: Colors.transparent,
selectedIndex: tabController.index, selectedIndex: currentDest.value,
onDestinationSelected: onDestinationSelected: (index) =>
(index) => tabController.animateTo(index), tabController.animateTo(index),
destinations: [ destinations: [
NavigationRailDestination( NavigationRailDestination(
icon: Icon(Icons.apps), icon: Icon(Icons.apps),

View File

@@ -1,4 +1,7 @@
import 'dart:io';
import 'package:easy_localization/easy_localization.dart'; import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/foundation.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';
import 'package:go_router/go_router.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/compose.dart';
import 'package:island/screens/posts/post_detail.dart'; import 'package:island/screens/posts/post_detail.dart';
import 'package:island/services/compose_storage_db.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/content/sheet.dart';
import 'package:island/widgets/post/compose_card.dart'; import 'package:island/widgets/post/compose_card.dart';
import 'package:island/widgets/post/compose_shared.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( return SheetScaffold(
heightFactor: isTablet ? 0.95 : 0.8,
titleText: 'postCompose'.tr(), titleText: 'postCompose'.tr(),
actions: actions, actions: actions,
child: PostComposeCard( child: PostComposeCard(

View File

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

View File

@@ -19,64 +19,50 @@ class SiteActionMenu extends HookConsumerWidget {
@override @override
Widget build(BuildContext context, WidgetRef ref) { Widget build(BuildContext context, WidgetRef ref) {
return PopupMenuButton<String>( return PopupMenuButton<String>(
itemBuilder: itemBuilder: (context) => [
(context) => [ PopupMenuItem(
PopupMenuItem( value: 'edit',
value: 'edit', child: Row(
child: Row( children: [
children: [ Icon(
Icon( Symbols.edit,
Symbols.edit, color: Theme.of(context).colorScheme.onSurface,
color: Theme.of(context).colorScheme.onSurface,
),
const Gap(16),
Text('edit'.tr()),
],
), ),
), const Gap(16),
const PopupMenuDivider(), Text('edit'.tr()),
PopupMenuItem( ],
value: 'delete', ),
child: Row( ),
children: [ const PopupMenuDivider(),
const Icon(Symbols.delete, color: Colors.red), PopupMenuItem(
const Gap(16), value: 'delete',
Text('delete'.tr()).textColor(Colors.red), child: Row(
], children: [
), const Icon(Symbols.delete, color: Colors.red),
), const Gap(16),
], Text('delete'.tr()).textColor(Colors.red),
],
),
),
],
onSelected: (value) async { onSelected: (value) async {
switch (value) { switch (value) {
case 'edit': case 'edit':
showModalBottomSheet( showModalBottomSheet(
context: context, context: context,
isScrollControlled: true, isScrollControlled: true,
builder: builder: (context) =>
(context) => SiteForm(pubName: pubName, siteSlug: site.slug), SiteForm(pubName: pubName, siteSlug: site.slug),
).then((_) { ).then((_) {
// Refresh site data after potential edit // Refresh site data after potential edit
ref.invalidate(publicationSiteDetailProvider(pubName, site.slug)); ref.invalidate(publicationSiteDetailProvider(pubName, site.slug));
}); });
break; break;
case 'delete': case 'delete':
final confirmed = await showDialog<bool>( final confirmed = await showConfirmAlert(
context: context, 'publicationSiteDeleteConfirm'.tr(),
builder: 'deleteSite'.tr(),
(context) => AlertDialog( isDanger: true,
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()),
),
],
),
); );
if (confirmed == true) { if (confirmed == true) {

View File

@@ -1917,6 +1917,54 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "3.1.0+3" 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: petitparser:
dependency: transitive dependency: transitive
description: 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 # 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 # 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. # 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: environment:
sdk: ^3.8.0 sdk: ^3.8.0
@@ -171,6 +171,7 @@ dependencies:
http_parser: ^4.1.2 http_parser: ^4.1.2
flutter_code_editor: ^0.3.5 flutter_code_editor: ^0.3.5
skeletonizer: ^2.1.1 skeletonizer: ^2.1.1
permission_handler: ^12.0.1
dev_dependencies: dev_dependencies:
flutter_test: 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_libs_windows_video/media_kit_libs_windows_video_plugin_c_api.h>
#include <media_kit_video/media_kit_video_plugin_c_api.h> #include <media_kit_video/media_kit_video_plugin_c_api.h>
#include <pasteboard/pasteboard_plugin.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 <protocol_handler_windows/protocol_handler_windows_plugin_c_api.h>
#include <record_windows/record_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> #include <screen_retriever_windows/screen_retriever_windows_plugin_c_api.h>
@@ -73,6 +74,8 @@ void RegisterPlugins(flutter::PluginRegistry* registry) {
registry->GetRegistrarForPlugin("MediaKitVideoPluginCApi")); registry->GetRegistrarForPlugin("MediaKitVideoPluginCApi"));
PasteboardPluginRegisterWithRegistrar( PasteboardPluginRegisterWithRegistrar(
registry->GetRegistrarForPlugin("PasteboardPlugin")); registry->GetRegistrarForPlugin("PasteboardPlugin"));
PermissionHandlerWindowsPluginRegisterWithRegistrar(
registry->GetRegistrarForPlugin("PermissionHandlerWindowsPlugin"));
ProtocolHandlerWindowsPluginCApiRegisterWithRegistrar( ProtocolHandlerWindowsPluginCApiRegisterWithRegistrar(
registry->GetRegistrarForPlugin("ProtocolHandlerWindowsPluginCApi")); registry->GetRegistrarForPlugin("ProtocolHandlerWindowsPluginCApi"));
RecordWindowsPluginCApiRegisterWithRegistrar( RecordWindowsPluginCApiRegisterWithRegistrar(

View File

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