neoagent 3.2.1-beta.8 → 3.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/flutter_app/lib/main_app_shell.dart +178 -34
- package/flutter_app/lib/main_chat.dart +542 -80
- package/flutter_app/lib/main_controller.dart +59 -4
- package/flutter_app/lib/main_models.dart +171 -22
- package/flutter_app/lib/main_operations.dart +0 -49
- package/flutter_app/lib/main_settings.dart +338 -0
- package/flutter_app/lib/src/backend_client.dart +19 -0
- package/package.json +1 -1
- package/server/db/database.js +2 -2
- package/server/http/routes.js +1 -0
- package/server/public/.last_build_id +1 -1
- package/server/public/assets/fonts/MaterialIcons-Regular.otf +0 -0
- package/server/public/flutter_bootstrap.js +1 -1
- package/server/public/main.dart.js +85079 -83904
- package/server/routes/behavior.js +80 -0
- package/server/routes/settings.js +4 -0
- package/server/services/agents/manager.js +1 -1
- package/server/services/ai/history.js +1 -1
- package/server/services/ai/loop/agent_engine_core.js +131 -18
- package/server/services/ai/loop/blank_recovery.js +5 -4
- package/server/services/ai/loop/completion_judge.js +280 -18
- package/server/services/ai/loop/conversation_loop.js +92 -34
- package/server/services/ai/loop/messaging_delivery.js +47 -0
- package/server/services/ai/loop/progress_classification.js +2 -0
- package/server/services/ai/loopPolicy.js +24 -2
- package/server/services/ai/messagingFallback.js +17 -17
- package/server/services/ai/model_failure_cache.js +7 -0
- package/server/services/ai/systemPrompt.js +31 -122
- package/server/services/ai/taskAnalysis.js +101 -12
- package/server/services/ai/toolEvidence.js +198 -0
- package/server/services/ai/tools.js +60 -19
- package/server/services/behavior/config.js +251 -0
- package/server/services/behavior/defaults.js +68 -0
- package/server/services/behavior/delivery.js +176 -0
- package/server/services/behavior/index.js +43 -0
- package/server/services/behavior/model_client.js +35 -0
- package/server/services/behavior/modules/agent_identity.js +37 -0
- package/server/services/behavior/modules/channel_style.js +28 -0
- package/server/services/behavior/modules/index.js +29 -0
- package/server/services/behavior/modules/norms.js +101 -0
- package/server/services/behavior/modules/persona.js +172 -0
- package/server/services/behavior/modules/persona_prompt.js +238 -0
- package/server/services/behavior/modules/social_memory.js +86 -0
- package/server/services/behavior/modules/social_observability.js +94 -0
- package/server/services/behavior/modules/social_signals.js +41 -0
- package/server/services/behavior/modules/theory_of_mind.js +118 -0
- package/server/services/behavior/modules/turn_taking.js +238 -0
- package/server/services/behavior/pipeline.js +341 -0
- package/server/services/behavior/registry.js +78 -0
- package/server/services/behavior/signals.js +107 -0
- package/server/services/behavior/state.js +99 -0
- package/server/services/behavior/system_prompt.js +75 -0
- package/server/services/memory/manager.js +14 -33
- package/server/services/messaging/access_policy.js +269 -74
- package/server/services/messaging/automation.js +158 -27
- package/server/services/messaging/discord.js +11 -1
- package/server/services/messaging/formatting_guides.js +5 -4
- package/server/services/messaging/http_platforms.js +73 -28
- package/server/services/messaging/inbound_queue.js +74 -13
- package/server/services/messaging/inbound_store.js +33 -0
- package/server/services/messaging/manager.js +57 -5
- package/server/services/messaging/telegram.js +10 -1
- package/server/services/messaging/whatsapp.js +11 -0
- package/server/services/social_reach/channels/social_video.js +1 -1
- package/server/services/social_video/captions.js +2 -2
- package/server/services/social_video/service.js +194 -29
- package/server/services/voice/message.js +1 -1
- package/server/services/voice/runtime.js +2 -2
- package/server/utils/logger.js +19 -0
- package/server/services/ai/terminal_reply.js +0 -57
|
@@ -3032,6 +3032,71 @@ Future<void> _showMessagingAccessPolicyDialog(
|
|
|
3032
3032
|
return result;
|
|
3033
3033
|
}
|
|
3034
3034
|
|
|
3035
|
+
List<MessagingAccessRule> sharedSpaces() {
|
|
3036
|
+
final spaces = <MessagingAccessRule>[
|
|
3037
|
+
...policy.sharedSpaceRules,
|
|
3038
|
+
...policy.sharedMemberRules
|
|
3039
|
+
.where(
|
|
3040
|
+
(rule) =>
|
|
3041
|
+
(rule.spaceScope ?? '').isNotEmpty &&
|
|
3042
|
+
(rule.spaceValue ?? '').isNotEmpty,
|
|
3043
|
+
)
|
|
3044
|
+
.map(
|
|
3045
|
+
(rule) => MessagingAccessRule(
|
|
3046
|
+
scope: rule.spaceScope!,
|
|
3047
|
+
value: rule.spaceValue!,
|
|
3048
|
+
label: rule.spaceLabel,
|
|
3049
|
+
),
|
|
3050
|
+
),
|
|
3051
|
+
...policy.sharedParticipationRules.map(
|
|
3052
|
+
(rule) => MessagingAccessRule(
|
|
3053
|
+
scope: rule.scope,
|
|
3054
|
+
value: rule.value,
|
|
3055
|
+
label: rule.label,
|
|
3056
|
+
),
|
|
3057
|
+
),
|
|
3058
|
+
...catalog.suggestedTargets
|
|
3059
|
+
.where((target) => target.bucket == 'sharedSpaceRules')
|
|
3060
|
+
.map((target) => target.asRule),
|
|
3061
|
+
...catalog.discoveredTargets
|
|
3062
|
+
.where((target) => target.bucket == 'sharedSpaceRules')
|
|
3063
|
+
.map((target) => target.asRule),
|
|
3064
|
+
];
|
|
3065
|
+
return dedupeRules(spaces);
|
|
3066
|
+
}
|
|
3067
|
+
|
|
3068
|
+
bool allowsUntagged(MessagingAccessRule space) {
|
|
3069
|
+
for (final rule in policy.sharedParticipationRules) {
|
|
3070
|
+
if (rule.scope == space.scope && rule.value == space.value) {
|
|
3071
|
+
return rule.allowUntagged;
|
|
3072
|
+
}
|
|
3073
|
+
}
|
|
3074
|
+
return policy.defaultAllowUntaggedInShared;
|
|
3075
|
+
}
|
|
3076
|
+
|
|
3077
|
+
void setAllowsUntagged(
|
|
3078
|
+
MessagingAccessRule space,
|
|
3079
|
+
bool allow,
|
|
3080
|
+
void Function(void Function()) setLocalState,
|
|
3081
|
+
) {
|
|
3082
|
+
setLocalState(() {
|
|
3083
|
+
final rules = policy.sharedParticipationRules
|
|
3084
|
+
.where(
|
|
3085
|
+
(rule) => !(rule.scope == space.scope && rule.value == space.value),
|
|
3086
|
+
)
|
|
3087
|
+
.toList();
|
|
3088
|
+
rules.add(
|
|
3089
|
+
MessagingSharedParticipationRule(
|
|
3090
|
+
scope: space.scope,
|
|
3091
|
+
value: space.value,
|
|
3092
|
+
label: space.label,
|
|
3093
|
+
allowUntagged: allow,
|
|
3094
|
+
),
|
|
3095
|
+
);
|
|
3096
|
+
policy = policy.copyWith(sharedParticipationRules: rules);
|
|
3097
|
+
});
|
|
3098
|
+
}
|
|
3099
|
+
|
|
3035
3100
|
void addRule(
|
|
3036
3101
|
_MessagingRuleSelection selection,
|
|
3037
3102
|
void Function(void Function()) setLocalState,
|
|
@@ -3051,6 +3116,9 @@ Future<void> _showMessagingAccessPolicyDialog(
|
|
|
3051
3116
|
break;
|
|
3052
3117
|
case 'sharedActorRules':
|
|
3053
3118
|
policy = policy.copyWith(
|
|
3119
|
+
directPolicy: policy.directPolicy == 'disabled'
|
|
3120
|
+
? 'allowlist'
|
|
3121
|
+
: policy.directPolicy,
|
|
3054
3122
|
sharedPolicy: policy.sharedPolicy == 'disabled'
|
|
3055
3123
|
? 'allowlist'
|
|
3056
3124
|
: policy.sharedPolicy,
|
|
@@ -3060,6 +3128,17 @@ Future<void> _showMessagingAccessPolicyDialog(
|
|
|
3060
3128
|
]),
|
|
3061
3129
|
);
|
|
3062
3130
|
break;
|
|
3131
|
+
case 'sharedMemberRules':
|
|
3132
|
+
policy = policy.copyWith(
|
|
3133
|
+
sharedPolicy: policy.sharedPolicy == 'disabled'
|
|
3134
|
+
? 'allowlist'
|
|
3135
|
+
: policy.sharedPolicy,
|
|
3136
|
+
sharedMemberRules: dedupeRules(<MessagingAccessRule>[
|
|
3137
|
+
...policy.sharedMemberRules,
|
|
3138
|
+
selection.rule,
|
|
3139
|
+
]),
|
|
3140
|
+
);
|
|
3141
|
+
break;
|
|
3063
3142
|
default:
|
|
3064
3143
|
policy = policy.copyWith(
|
|
3065
3144
|
sharedPolicy: policy.sharedPolicy == 'disabled'
|
|
@@ -3095,6 +3174,13 @@ Future<void> _showMessagingAccessPolicyDialog(
|
|
|
3095
3174
|
.toList(growable: false),
|
|
3096
3175
|
);
|
|
3097
3176
|
break;
|
|
3177
|
+
case 'sharedMemberRules':
|
|
3178
|
+
policy = policy.copyWith(
|
|
3179
|
+
sharedMemberRules: policy.sharedMemberRules
|
|
3180
|
+
.where((item) => item.id != rule.id)
|
|
3181
|
+
.toList(growable: false),
|
|
3182
|
+
);
|
|
3183
|
+
break;
|
|
3098
3184
|
default:
|
|
3099
3185
|
policy = policy.copyWith(
|
|
3100
3186
|
sharedSpaceRules: policy.sharedSpaceRules
|
|
@@ -3111,14 +3197,20 @@ Future<void> _showMessagingAccessPolicyDialog(
|
|
|
3111
3197
|
return StatefulBuilder(
|
|
3112
3198
|
builder: (context, setLocalState) {
|
|
3113
3199
|
final capabilities = catalog.capabilities;
|
|
3200
|
+
final participationSpaces = sharedSpaces();
|
|
3201
|
+
final taggedOnlyCount = participationSpaces
|
|
3202
|
+
.where((space) => !allowsUntagged(space))
|
|
3203
|
+
.length;
|
|
3114
3204
|
final summaryText = [
|
|
3115
3205
|
'DMs ${policy.directPolicy}',
|
|
3116
3206
|
if (capabilities.supportsSharedPolicy)
|
|
3117
3207
|
'shared ${policy.sharedPolicy}',
|
|
3118
|
-
if (capabilities.
|
|
3119
|
-
policy.
|
|
3120
|
-
? '
|
|
3121
|
-
:
|
|
3208
|
+
if (capabilities.supportsUntaggedGroupToggle)
|
|
3209
|
+
!policy.defaultAllowUntaggedInShared
|
|
3210
|
+
? 'untagged off by default'
|
|
3211
|
+
: taggedOnlyCount == 0
|
|
3212
|
+
? 'social intelligence enabled'
|
|
3213
|
+
: '$taggedOnlyCount tagged-only',
|
|
3122
3214
|
if (policy.totalRuleCount > 0) '${policy.totalRuleCount} rules',
|
|
3123
3215
|
].join(' • ');
|
|
3124
3216
|
|
|
@@ -3128,7 +3220,45 @@ Future<void> _showMessagingAccessPolicyDialog(
|
|
|
3128
3220
|
horizontal: 24,
|
|
3129
3221
|
vertical: 18,
|
|
3130
3222
|
),
|
|
3131
|
-
|
|
3223
|
+
shape: RoundedRectangleBorder(
|
|
3224
|
+
borderRadius: BorderRadius.circular(24),
|
|
3225
|
+
),
|
|
3226
|
+
title: Row(
|
|
3227
|
+
children: <Widget>[
|
|
3228
|
+
Container(
|
|
3229
|
+
width: 42,
|
|
3230
|
+
height: 42,
|
|
3231
|
+
decoration: BoxDecoration(
|
|
3232
|
+
color: platform.accent.withValues(alpha: 0.12),
|
|
3233
|
+
borderRadius: BorderRadius.circular(13),
|
|
3234
|
+
),
|
|
3235
|
+
child: Icon(
|
|
3236
|
+
Icons.admin_panel_settings_outlined,
|
|
3237
|
+
color: platform.accent,
|
|
3238
|
+
),
|
|
3239
|
+
),
|
|
3240
|
+
const SizedBox(width: 12),
|
|
3241
|
+
Expanded(
|
|
3242
|
+
child: Column(
|
|
3243
|
+
crossAxisAlignment: CrossAxisAlignment.start,
|
|
3244
|
+
children: <Widget>[
|
|
3245
|
+
Text(
|
|
3246
|
+
'${platform.label} access',
|
|
3247
|
+
style: TextStyle(fontWeight: FontWeight.w800),
|
|
3248
|
+
),
|
|
3249
|
+
Text(
|
|
3250
|
+
'People, groups, and response behavior',
|
|
3251
|
+
style: TextStyle(
|
|
3252
|
+
color: _textSecondary,
|
|
3253
|
+
fontSize: 13,
|
|
3254
|
+
fontWeight: FontWeight.w400,
|
|
3255
|
+
),
|
|
3256
|
+
),
|
|
3257
|
+
],
|
|
3258
|
+
),
|
|
3259
|
+
),
|
|
3260
|
+
],
|
|
3261
|
+
),
|
|
3132
3262
|
content: SizedBox(
|
|
3133
3263
|
width: 760,
|
|
3134
3264
|
child: SingleChildScrollView(
|
|
@@ -3146,7 +3276,10 @@ Future<void> _showMessagingAccessPolicyDialog(
|
|
|
3146
3276
|
const SizedBox(height: 18),
|
|
3147
3277
|
if (capabilities.supportsDirectPolicy)
|
|
3148
3278
|
_AccessModeField(
|
|
3279
|
+
icon: Icons.chat_bubble_outline_rounded,
|
|
3149
3280
|
label: 'Direct messages',
|
|
3281
|
+
description:
|
|
3282
|
+
'Control who can reach the agent one-to-one.',
|
|
3150
3283
|
value: policy.directPolicy,
|
|
3151
3284
|
onChanged: (value) => setLocalState(() {
|
|
3152
3285
|
policy = policy.copyWith(directPolicy: value);
|
|
@@ -3155,28 +3288,24 @@ Future<void> _showMessagingAccessPolicyDialog(
|
|
|
3155
3288
|
if (capabilities.supportsSharedPolicy) ...<Widget>[
|
|
3156
3289
|
const SizedBox(height: 12),
|
|
3157
3290
|
_AccessModeField(
|
|
3291
|
+
icon: Icons.groups_2_outlined,
|
|
3158
3292
|
label: 'Shared spaces',
|
|
3293
|
+
description:
|
|
3294
|
+
'Control access in groups, channels, and rooms.',
|
|
3159
3295
|
value: policy.sharedPolicy,
|
|
3160
3296
|
onChanged: (value) => setLocalState(() {
|
|
3161
3297
|
policy = policy.copyWith(sharedPolicy: value);
|
|
3162
3298
|
}),
|
|
3163
3299
|
),
|
|
3164
3300
|
],
|
|
3165
|
-
if (capabilities.
|
|
3166
|
-
const SizedBox(height:
|
|
3167
|
-
|
|
3168
|
-
|
|
3169
|
-
|
|
3170
|
-
|
|
3171
|
-
|
|
3172
|
-
|
|
3173
|
-
),
|
|
3174
|
-
value: policy.requireMentionInShared,
|
|
3175
|
-
onChanged: (value) => setLocalState(() {
|
|
3176
|
-
policy = policy.copyWith(
|
|
3177
|
-
requireMentionInShared: value,
|
|
3178
|
-
);
|
|
3179
|
-
}),
|
|
3301
|
+
if (capabilities.supportsUntaggedGroupToggle) ...<Widget>[
|
|
3302
|
+
const SizedBox(height: 16),
|
|
3303
|
+
_GroupParticipationSection(
|
|
3304
|
+
spaces: participationSpaces,
|
|
3305
|
+
supportsMentionGate: capabilities.supportsMentionGate,
|
|
3306
|
+
allowsUntagged: allowsUntagged,
|
|
3307
|
+
onChanged: (space, value) =>
|
|
3308
|
+
setAllowsUntagged(space, value, setLocalState),
|
|
3180
3309
|
),
|
|
3181
3310
|
],
|
|
3182
3311
|
const SizedBox(height: 14),
|
|
@@ -3195,7 +3324,7 @@ Future<void> _showMessagingAccessPolicyDialog(
|
|
|
3195
3324
|
}
|
|
3196
3325
|
},
|
|
3197
3326
|
icon: Icon(Icons.add_rounded),
|
|
3198
|
-
label: Text('Add
|
|
3327
|
+
label: Text('Add access'),
|
|
3199
3328
|
),
|
|
3200
3329
|
const SizedBox(width: 10),
|
|
3201
3330
|
OutlinedButton.icon(
|
|
@@ -3207,14 +3336,15 @@ Future<void> _showMessagingAccessPolicyDialog(
|
|
|
3207
3336
|
});
|
|
3208
3337
|
},
|
|
3209
3338
|
icon: Icon(Icons.travel_explore_rounded),
|
|
3210
|
-
label: Text('Refresh
|
|
3339
|
+
label: Text('Refresh groups & people'),
|
|
3211
3340
|
),
|
|
3212
3341
|
],
|
|
3213
3342
|
),
|
|
3214
3343
|
const SizedBox(height: 18),
|
|
3215
3344
|
_AccessRuleSection(
|
|
3216
|
-
title: 'Direct
|
|
3217
|
-
subtitle:
|
|
3345
|
+
title: 'Direct-only rules',
|
|
3346
|
+
subtitle:
|
|
3347
|
+
'Specific one-to-one chats that do not grant group access.',
|
|
3218
3348
|
rules: policy.directRules,
|
|
3219
3349
|
emptyLabel: 'No direct sender rules yet.',
|
|
3220
3350
|
onRemove: (rule) =>
|
|
@@ -3223,9 +3353,9 @@ Future<void> _showMessagingAccessPolicyDialog(
|
|
|
3223
3353
|
if (capabilities.supportsSharedPolicy) ...<Widget>[
|
|
3224
3354
|
const SizedBox(height: 16),
|
|
3225
3355
|
_AccessRuleSection(
|
|
3226
|
-
title: '
|
|
3356
|
+
title: 'Everyone in a shared space',
|
|
3227
3357
|
subtitle:
|
|
3228
|
-
'
|
|
3358
|
+
'Allow every sender in a selected channel, group, room, or server.',
|
|
3229
3359
|
rules: policy.sharedSpaceRules,
|
|
3230
3360
|
emptyLabel: 'No shared-space rules yet.',
|
|
3231
3361
|
onRemove: (rule) =>
|
|
@@ -3233,14 +3363,28 @@ Future<void> _showMessagingAccessPolicyDialog(
|
|
|
3233
3363
|
),
|
|
3234
3364
|
const SizedBox(height: 16),
|
|
3235
3365
|
_AccessRuleSection(
|
|
3236
|
-
title: '
|
|
3366
|
+
title: 'Senders everywhere',
|
|
3237
3367
|
subtitle:
|
|
3238
|
-
'
|
|
3368
|
+
'Allow these people in DMs and shared spaces. Role rules apply only in shared spaces.',
|
|
3239
3369
|
rules: policy.sharedActorRules,
|
|
3240
3370
|
emptyLabel: 'No shared-actor rules yet.',
|
|
3241
3371
|
onRemove: (rule) =>
|
|
3242
3372
|
removeRule('sharedActorRules', rule, setLocalState),
|
|
3243
3373
|
),
|
|
3374
|
+
const SizedBox(height: 16),
|
|
3375
|
+
_AccessRuleSection(
|
|
3376
|
+
title: 'Senders in one shared space',
|
|
3377
|
+
subtitle:
|
|
3378
|
+
'Allow a sender only in the selected group, channel, or room.',
|
|
3379
|
+
rules: policy.sharedMemberRules,
|
|
3380
|
+
emptyLabel: 'No group-specific sender rules yet.',
|
|
3381
|
+
onRemove: (rule) => removeRule(
|
|
3382
|
+
'sharedMemberRules',
|
|
3383
|
+
rule,
|
|
3384
|
+
setLocalState,
|
|
3385
|
+
),
|
|
3386
|
+
showSpace: true,
|
|
3387
|
+
),
|
|
3244
3388
|
],
|
|
3245
3389
|
],
|
|
3246
3390
|
),
|
|
@@ -3258,7 +3402,7 @@ Future<void> _showMessagingAccessPolicyDialog(
|
|
|
3258
3402
|
Navigator.of(dialogContext).pop();
|
|
3259
3403
|
}
|
|
3260
3404
|
},
|
|
3261
|
-
child: Text('Save
|
|
3405
|
+
child: Text('Save changes'),
|
|
3262
3406
|
),
|
|
3263
3407
|
],
|
|
3264
3408
|
);
|
|
@@ -3270,32 +3414,175 @@ Future<void> _showMessagingAccessPolicyDialog(
|
|
|
3270
3414
|
|
|
3271
3415
|
class _AccessModeField extends StatelessWidget {
|
|
3272
3416
|
const _AccessModeField({
|
|
3417
|
+
required this.icon,
|
|
3273
3418
|
required this.label,
|
|
3419
|
+
required this.description,
|
|
3274
3420
|
required this.value,
|
|
3275
3421
|
required this.onChanged,
|
|
3276
3422
|
});
|
|
3277
3423
|
|
|
3424
|
+
final IconData icon;
|
|
3278
3425
|
final String label;
|
|
3426
|
+
final String description;
|
|
3279
3427
|
final String value;
|
|
3280
3428
|
final ValueChanged<String> onChanged;
|
|
3281
3429
|
|
|
3282
3430
|
@override
|
|
3283
3431
|
Widget build(BuildContext context) {
|
|
3284
|
-
return
|
|
3285
|
-
|
|
3286
|
-
|
|
3287
|
-
|
|
3288
|
-
|
|
3289
|
-
|
|
3290
|
-
|
|
3291
|
-
|
|
3292
|
-
|
|
3293
|
-
|
|
3294
|
-
|
|
3295
|
-
|
|
3296
|
-
|
|
3297
|
-
|
|
3298
|
-
|
|
3432
|
+
return Container(
|
|
3433
|
+
width: double.infinity,
|
|
3434
|
+
padding: const EdgeInsets.all(14),
|
|
3435
|
+
decoration: BoxDecoration(
|
|
3436
|
+
color: _bgCard,
|
|
3437
|
+
borderRadius: BorderRadius.circular(16),
|
|
3438
|
+
border: Border.all(color: _borderLight),
|
|
3439
|
+
),
|
|
3440
|
+
child: Row(
|
|
3441
|
+
crossAxisAlignment: CrossAxisAlignment.start,
|
|
3442
|
+
children: <Widget>[
|
|
3443
|
+
Icon(icon, color: _accent),
|
|
3444
|
+
const SizedBox(width: 12),
|
|
3445
|
+
Expanded(
|
|
3446
|
+
child: Column(
|
|
3447
|
+
crossAxisAlignment: CrossAxisAlignment.start,
|
|
3448
|
+
children: <Widget>[
|
|
3449
|
+
Text(label, style: TextStyle(fontWeight: FontWeight.w700)),
|
|
3450
|
+
const SizedBox(height: 3),
|
|
3451
|
+
Text(description, style: TextStyle(color: _textSecondary)),
|
|
3452
|
+
const SizedBox(height: 12),
|
|
3453
|
+
Wrap(
|
|
3454
|
+
spacing: 8,
|
|
3455
|
+
runSpacing: 8,
|
|
3456
|
+
children: <Widget>[
|
|
3457
|
+
ChoiceChip(
|
|
3458
|
+
avatar: Icon(Icons.rule_rounded, size: 18),
|
|
3459
|
+
label: Text('Allowlist'),
|
|
3460
|
+
selected: value == 'allowlist',
|
|
3461
|
+
onSelected: (_) => onChanged('allowlist'),
|
|
3462
|
+
),
|
|
3463
|
+
ChoiceChip(
|
|
3464
|
+
avatar: Icon(Icons.public_rounded, size: 18),
|
|
3465
|
+
label: Text('Open'),
|
|
3466
|
+
selected: value == 'open',
|
|
3467
|
+
onSelected: (_) => onChanged('open'),
|
|
3468
|
+
),
|
|
3469
|
+
ChoiceChip(
|
|
3470
|
+
avatar: Icon(Icons.block_rounded, size: 18),
|
|
3471
|
+
label: Text('Off'),
|
|
3472
|
+
selected: value == 'disabled',
|
|
3473
|
+
onSelected: (_) => onChanged('disabled'),
|
|
3474
|
+
),
|
|
3475
|
+
],
|
|
3476
|
+
),
|
|
3477
|
+
],
|
|
3478
|
+
),
|
|
3479
|
+
),
|
|
3480
|
+
],
|
|
3481
|
+
),
|
|
3482
|
+
);
|
|
3483
|
+
}
|
|
3484
|
+
}
|
|
3485
|
+
|
|
3486
|
+
class _GroupParticipationSection extends StatelessWidget {
|
|
3487
|
+
const _GroupParticipationSection({
|
|
3488
|
+
required this.spaces,
|
|
3489
|
+
required this.supportsMentionGate,
|
|
3490
|
+
required this.allowsUntagged,
|
|
3491
|
+
required this.onChanged,
|
|
3492
|
+
});
|
|
3493
|
+
|
|
3494
|
+
final List<MessagingAccessRule> spaces;
|
|
3495
|
+
final bool supportsMentionGate;
|
|
3496
|
+
final bool Function(MessagingAccessRule) allowsUntagged;
|
|
3497
|
+
final void Function(MessagingAccessRule, bool) onChanged;
|
|
3498
|
+
|
|
3499
|
+
@override
|
|
3500
|
+
Widget build(BuildContext context) {
|
|
3501
|
+
return Container(
|
|
3502
|
+
width: double.infinity,
|
|
3503
|
+
padding: const EdgeInsets.all(14),
|
|
3504
|
+
decoration: BoxDecoration(
|
|
3505
|
+
color: _bgCard,
|
|
3506
|
+
borderRadius: BorderRadius.circular(16),
|
|
3507
|
+
border: Border.all(color: _borderLight),
|
|
3508
|
+
),
|
|
3509
|
+
child: Column(
|
|
3510
|
+
crossAxisAlignment: CrossAxisAlignment.start,
|
|
3511
|
+
children: <Widget>[
|
|
3512
|
+
Row(
|
|
3513
|
+
children: <Widget>[
|
|
3514
|
+
Icon(Icons.alternate_email_rounded, color: _accent),
|
|
3515
|
+
const SizedBox(width: 10),
|
|
3516
|
+
Expanded(
|
|
3517
|
+
child: Column(
|
|
3518
|
+
crossAxisAlignment: CrossAxisAlignment.start,
|
|
3519
|
+
children: <Widget>[
|
|
3520
|
+
Text(
|
|
3521
|
+
'Untagged group messages',
|
|
3522
|
+
style: TextStyle(fontWeight: FontWeight.w700),
|
|
3523
|
+
),
|
|
3524
|
+
const SizedBox(height: 3),
|
|
3525
|
+
Text(
|
|
3526
|
+
supportsMentionGate
|
|
3527
|
+
? 'Tags and replies always get a response. Choose which groups may also use social intelligence for untagged messages.'
|
|
3528
|
+
: 'Choose which shared spaces may use social intelligence for untagged messages. This bridge may not identify tags separately.',
|
|
3529
|
+
style: TextStyle(color: _textSecondary, height: 1.35),
|
|
3530
|
+
),
|
|
3531
|
+
],
|
|
3532
|
+
),
|
|
3533
|
+
),
|
|
3534
|
+
],
|
|
3535
|
+
),
|
|
3536
|
+
const SizedBox(height: 12),
|
|
3537
|
+
if (spaces.isEmpty)
|
|
3538
|
+
Container(
|
|
3539
|
+
width: double.infinity,
|
|
3540
|
+
padding: const EdgeInsets.all(12),
|
|
3541
|
+
decoration: BoxDecoration(
|
|
3542
|
+
color: _bgSecondary,
|
|
3543
|
+
borderRadius: BorderRadius.circular(12),
|
|
3544
|
+
),
|
|
3545
|
+
child: Text(
|
|
3546
|
+
'No groups discovered yet. Refresh discovery after the agent has seen a group message.',
|
|
3547
|
+
style: TextStyle(color: _textMuted),
|
|
3548
|
+
),
|
|
3549
|
+
)
|
|
3550
|
+
else
|
|
3551
|
+
...spaces.map((space) {
|
|
3552
|
+
final enabled = allowsUntagged(space);
|
|
3553
|
+
return Container(
|
|
3554
|
+
margin: const EdgeInsets.only(top: 8),
|
|
3555
|
+
decoration: BoxDecoration(
|
|
3556
|
+
color: _bgSecondary,
|
|
3557
|
+
borderRadius: BorderRadius.circular(12),
|
|
3558
|
+
),
|
|
3559
|
+
child: SwitchListTile(
|
|
3560
|
+
contentPadding: const EdgeInsets.symmetric(
|
|
3561
|
+
horizontal: 12,
|
|
3562
|
+
vertical: 2,
|
|
3563
|
+
),
|
|
3564
|
+
secondary: Icon(
|
|
3565
|
+
enabled
|
|
3566
|
+
? Icons.psychology_alt_outlined
|
|
3567
|
+
: Icons.notifications_off_outlined,
|
|
3568
|
+
color: enabled ? _accent : _textMuted,
|
|
3569
|
+
),
|
|
3570
|
+
title: Text(
|
|
3571
|
+
space.displayLabel,
|
|
3572
|
+
style: TextStyle(fontWeight: FontWeight.w600),
|
|
3573
|
+
),
|
|
3574
|
+
subtitle: Text(
|
|
3575
|
+
enabled
|
|
3576
|
+
? 'Untagged messages use social intelligence'
|
|
3577
|
+
: 'Untagged messages are ignored completely',
|
|
3578
|
+
style: TextStyle(color: _textSecondary),
|
|
3579
|
+
),
|
|
3580
|
+
value: enabled,
|
|
3581
|
+
onChanged: (value) => onChanged(space, value),
|
|
3582
|
+
),
|
|
3583
|
+
);
|
|
3584
|
+
}),
|
|
3585
|
+
],
|
|
3299
3586
|
),
|
|
3300
3587
|
);
|
|
3301
3588
|
}
|
|
@@ -3308,6 +3595,7 @@ class _AccessRuleSection extends StatelessWidget {
|
|
|
3308
3595
|
required this.rules,
|
|
3309
3596
|
required this.emptyLabel,
|
|
3310
3597
|
required this.onRemove,
|
|
3598
|
+
this.showSpace = false,
|
|
3311
3599
|
});
|
|
3312
3600
|
|
|
3313
3601
|
final String title;
|
|
@@ -3315,6 +3603,7 @@ class _AccessRuleSection extends StatelessWidget {
|
|
|
3315
3603
|
final List<MessagingAccessRule> rules;
|
|
3316
3604
|
final String emptyLabel;
|
|
3317
3605
|
final ValueChanged<MessagingAccessRule> onRemove;
|
|
3606
|
+
final bool showSpace;
|
|
3318
3607
|
|
|
3319
3608
|
@override
|
|
3320
3609
|
Widget build(BuildContext context) {
|
|
@@ -3341,8 +3630,14 @@ class _AccessRuleSection extends StatelessWidget {
|
|
|
3341
3630
|
runSpacing: 8,
|
|
3342
3631
|
children: rules
|
|
3343
3632
|
.map((rule) {
|
|
3633
|
+
final spaceSuffix =
|
|
3634
|
+
showSpace && rule.spaceDisplayLabel.isNotEmpty
|
|
3635
|
+
? ' in ${rule.spaceDisplayLabel}'
|
|
3636
|
+
: '';
|
|
3344
3637
|
return Chip(
|
|
3345
|
-
label: Text(
|
|
3638
|
+
label: Text(
|
|
3639
|
+
'${rule.scopeLabel}: ${rule.displayLabel}$spaceSuffix',
|
|
3640
|
+
),
|
|
3346
3641
|
deleteIcon: Icon(Icons.close_rounded, size: 18),
|
|
3347
3642
|
onDeleted: () => onRemove(rule),
|
|
3348
3643
|
);
|
|
@@ -3387,30 +3682,47 @@ class _MessagingAccessRulePickerSheet extends StatefulWidget {
|
|
|
3387
3682
|
class _MessagingAccessRulePickerSheetState
|
|
3388
3683
|
extends State<_MessagingAccessRulePickerSheet> {
|
|
3389
3684
|
late final TextEditingController _queryController;
|
|
3685
|
+
late final TextEditingController _valueController;
|
|
3686
|
+
late final TextEditingController _spaceValueController;
|
|
3390
3687
|
late String _selectedBucket;
|
|
3391
3688
|
late String _selectedScope;
|
|
3689
|
+
late String _selectedSpaceScope;
|
|
3392
3690
|
|
|
3393
3691
|
@override
|
|
3394
3692
|
void initState() {
|
|
3395
3693
|
super.initState();
|
|
3396
3694
|
_queryController = TextEditingController();
|
|
3397
|
-
|
|
3695
|
+
_valueController = TextEditingController();
|
|
3696
|
+
_spaceValueController = TextEditingController();
|
|
3697
|
+
_selectedBucket =
|
|
3698
|
+
widget.catalog.capabilities.sharedActorRuleScopes.isNotEmpty
|
|
3699
|
+
? 'sharedActorRules'
|
|
3700
|
+
: _directOnlyScopes().isNotEmpty
|
|
3398
3701
|
? 'directRules'
|
|
3399
3702
|
: (widget.catalog.capabilities.sharedSpaceRuleScopes.isNotEmpty
|
|
3400
3703
|
? 'sharedSpaceRules'
|
|
3401
3704
|
: 'sharedActorRules');
|
|
3402
|
-
_selectedScope =
|
|
3403
|
-
|
|
3705
|
+
_selectedScope =
|
|
3706
|
+
widget.catalog.capabilities.sharedActorRuleScopes.isNotEmpty
|
|
3707
|
+
? widget.catalog.capabilities.sharedActorRuleScopes.first
|
|
3708
|
+
: _directOnlyScopes().isNotEmpty
|
|
3709
|
+
? _directOnlyScopes().first
|
|
3404
3710
|
: (widget.catalog.capabilities.sharedSpaceRuleScopes.isNotEmpty
|
|
3405
3711
|
? widget.catalog.capabilities.sharedSpaceRuleScopes.first
|
|
3406
3712
|
: (widget.catalog.capabilities.sharedActorRuleScopes.isNotEmpty
|
|
3407
3713
|
? widget.catalog.capabilities.sharedActorRuleScopes.first
|
|
3408
3714
|
: 'chat'));
|
|
3715
|
+
_selectedSpaceScope =
|
|
3716
|
+
widget.catalog.capabilities.sharedSpaceRuleScopes.isNotEmpty
|
|
3717
|
+
? widget.catalog.capabilities.sharedSpaceRuleScopes.first
|
|
3718
|
+
: 'chat';
|
|
3409
3719
|
}
|
|
3410
3720
|
|
|
3411
3721
|
@override
|
|
3412
3722
|
void dispose() {
|
|
3413
3723
|
_queryController.dispose();
|
|
3724
|
+
_valueController.dispose();
|
|
3725
|
+
_spaceValueController.dispose();
|
|
3414
3726
|
super.dispose();
|
|
3415
3727
|
}
|
|
3416
3728
|
|
|
@@ -3423,14 +3735,23 @@ class _MessagingAccessRulePickerSheetState
|
|
|
3423
3735
|
List<String> _scopesForBucket() {
|
|
3424
3736
|
switch (_selectedBucket) {
|
|
3425
3737
|
case 'directRules':
|
|
3426
|
-
return
|
|
3738
|
+
return _directOnlyScopes();
|
|
3427
3739
|
case 'sharedActorRules':
|
|
3740
|
+
case 'sharedMemberRules':
|
|
3428
3741
|
return widget.catalog.capabilities.sharedActorRuleScopes;
|
|
3429
3742
|
default:
|
|
3430
3743
|
return widget.catalog.capabilities.sharedSpaceRuleScopes;
|
|
3431
3744
|
}
|
|
3432
3745
|
}
|
|
3433
3746
|
|
|
3747
|
+
List<String> _directOnlyScopes() {
|
|
3748
|
+
final sharedActorScopes = widget.catalog.capabilities.sharedActorRuleScopes
|
|
3749
|
+
.toSet();
|
|
3750
|
+
return widget.catalog.capabilities.directRuleScopes
|
|
3751
|
+
.where((scope) => !sharedActorScopes.contains(scope))
|
|
3752
|
+
.toList(growable: false);
|
|
3753
|
+
}
|
|
3754
|
+
|
|
3434
3755
|
void _syncSelectedScope() {
|
|
3435
3756
|
final availableScopes = _scopesForBucket();
|
|
3436
3757
|
if (availableScopes.isEmpty || availableScopes.contains(_selectedScope)) {
|
|
@@ -3439,24 +3760,59 @@ class _MessagingAccessRulePickerSheetState
|
|
|
3439
3760
|
_selectedScope = availableScopes.first;
|
|
3440
3761
|
}
|
|
3441
3762
|
|
|
3763
|
+
void _submitManualRule(BuildContext context) {
|
|
3764
|
+
final value = _valueController.text.trim();
|
|
3765
|
+
if (value.isEmpty) return;
|
|
3766
|
+
final isMemberRule = _selectedBucket == 'sharedMemberRules';
|
|
3767
|
+
final spaceValue = _spaceValueController.text.trim();
|
|
3768
|
+
if (isMemberRule && spaceValue.isEmpty) return;
|
|
3769
|
+
Navigator.of(context).pop(
|
|
3770
|
+
_MessagingRuleSelection(
|
|
3771
|
+
bucket: _selectedBucket,
|
|
3772
|
+
rule: MessagingAccessRule(
|
|
3773
|
+
scope: _selectedScope,
|
|
3774
|
+
value: value,
|
|
3775
|
+
spaceScope: isMemberRule ? _selectedSpaceScope : null,
|
|
3776
|
+
spaceValue: isMemberRule ? spaceValue : null,
|
|
3777
|
+
),
|
|
3778
|
+
),
|
|
3779
|
+
);
|
|
3780
|
+
}
|
|
3781
|
+
|
|
3442
3782
|
@override
|
|
3443
3783
|
Widget build(BuildContext context) {
|
|
3444
3784
|
final availableScopes = _scopesForBucket();
|
|
3445
3785
|
final query = _queryController.text.trim().toLowerCase();
|
|
3446
|
-
final
|
|
3447
|
-
|
|
3448
|
-
|
|
3449
|
-
|
|
3450
|
-
|
|
3451
|
-
|
|
3452
|
-
|
|
3453
|
-
|
|
3454
|
-
|
|
3455
|
-
|
|
3456
|
-
|
|
3457
|
-
|
|
3458
|
-
|
|
3459
|
-
|
|
3786
|
+
final allTargets = <MessagingAccessTarget>[
|
|
3787
|
+
...widget.catalog.suggestedTargets,
|
|
3788
|
+
...widget.catalog.discoveredTargets,
|
|
3789
|
+
];
|
|
3790
|
+
bool matchesQuery(MessagingAccessTarget target) {
|
|
3791
|
+
if (query.isEmpty) return true;
|
|
3792
|
+
final haystack =
|
|
3793
|
+
'${target.label} ${target.subtitle} ${target.scope} ${target.value}'
|
|
3794
|
+
.toLowerCase();
|
|
3795
|
+
return haystack.contains(query);
|
|
3796
|
+
}
|
|
3797
|
+
|
|
3798
|
+
final targets = allTargets
|
|
3799
|
+
.where((target) {
|
|
3800
|
+
if (target.bucket != _selectedBucket) return false;
|
|
3801
|
+
return matchesQuery(target);
|
|
3802
|
+
})
|
|
3803
|
+
.toList(growable: false);
|
|
3804
|
+
final memberActorTargets = allTargets
|
|
3805
|
+
.where(
|
|
3806
|
+
(target) =>
|
|
3807
|
+
target.bucket == 'sharedActorRules' && matchesQuery(target),
|
|
3808
|
+
)
|
|
3809
|
+
.toList(growable: false);
|
|
3810
|
+
final memberSpaceTargets = allTargets
|
|
3811
|
+
.where(
|
|
3812
|
+
(target) =>
|
|
3813
|
+
target.bucket == 'sharedSpaceRules' && matchesQuery(target),
|
|
3814
|
+
)
|
|
3815
|
+
.toList(growable: false);
|
|
3460
3816
|
|
|
3461
3817
|
return Padding(
|
|
3462
3818
|
padding: EdgeInsets.only(
|
|
@@ -3484,9 +3840,9 @@ class _MessagingAccessRulePickerSheetState
|
|
|
3484
3840
|
spacing: 8,
|
|
3485
3841
|
runSpacing: 8,
|
|
3486
3842
|
children: <Widget>[
|
|
3487
|
-
if (
|
|
3843
|
+
if (_directOnlyScopes().isNotEmpty)
|
|
3488
3844
|
ChoiceChip(
|
|
3489
|
-
label: Text('Direct'),
|
|
3845
|
+
label: Text('Direct only'),
|
|
3490
3846
|
selected: _selectedBucket == 'directRules',
|
|
3491
3847
|
onSelected: (_) => setState(() {
|
|
3492
3848
|
_selectedBucket = 'directRules';
|
|
@@ -3499,7 +3855,7 @@ class _MessagingAccessRulePickerSheetState
|
|
|
3499
3855
|
.sharedSpaceRuleScopes
|
|
3500
3856
|
.isNotEmpty)
|
|
3501
3857
|
ChoiceChip(
|
|
3502
|
-
label: Text('
|
|
3858
|
+
label: Text('Everyone in space'),
|
|
3503
3859
|
selected: _selectedBucket == 'sharedSpaceRules',
|
|
3504
3860
|
onSelected: (_) => setState(() {
|
|
3505
3861
|
_selectedBucket = 'sharedSpaceRules';
|
|
@@ -3512,13 +3868,31 @@ class _MessagingAccessRulePickerSheetState
|
|
|
3512
3868
|
.sharedActorRuleScopes
|
|
3513
3869
|
.isNotEmpty)
|
|
3514
3870
|
ChoiceChip(
|
|
3515
|
-
label: Text('
|
|
3871
|
+
label: Text('Sender everywhere'),
|
|
3516
3872
|
selected: _selectedBucket == 'sharedActorRules',
|
|
3517
3873
|
onSelected: (_) => setState(() {
|
|
3518
3874
|
_selectedBucket = 'sharedActorRules';
|
|
3519
3875
|
_syncSelectedScope();
|
|
3520
3876
|
}),
|
|
3521
3877
|
),
|
|
3878
|
+
if (widget
|
|
3879
|
+
.catalog
|
|
3880
|
+
.capabilities
|
|
3881
|
+
.sharedActorRuleScopes
|
|
3882
|
+
.isNotEmpty &&
|
|
3883
|
+
widget
|
|
3884
|
+
.catalog
|
|
3885
|
+
.capabilities
|
|
3886
|
+
.sharedSpaceRuleScopes
|
|
3887
|
+
.isNotEmpty)
|
|
3888
|
+
ChoiceChip(
|
|
3889
|
+
label: Text('Sender in one space'),
|
|
3890
|
+
selected: _selectedBucket == 'sharedMemberRules',
|
|
3891
|
+
onSelected: (_) => setState(() {
|
|
3892
|
+
_selectedBucket = 'sharedMemberRules';
|
|
3893
|
+
_syncSelectedScope();
|
|
3894
|
+
}),
|
|
3895
|
+
),
|
|
3522
3896
|
],
|
|
3523
3897
|
),
|
|
3524
3898
|
const SizedBox(height: 12),
|
|
@@ -3557,6 +3931,59 @@ class _MessagingAccessRulePickerSheetState
|
|
|
3557
3931
|
}),
|
|
3558
3932
|
const Divider(height: 24),
|
|
3559
3933
|
],
|
|
3934
|
+
if (_selectedBucket == 'sharedMemberRules' &&
|
|
3935
|
+
(memberActorTargets.isNotEmpty ||
|
|
3936
|
+
memberSpaceTargets.isNotEmpty)) ...<Widget>[
|
|
3937
|
+
if (memberActorTargets.isNotEmpty) ...<Widget>[
|
|
3938
|
+
Text(
|
|
3939
|
+
'Choose a discovered sender',
|
|
3940
|
+
style: TextStyle(fontWeight: FontWeight.w700),
|
|
3941
|
+
),
|
|
3942
|
+
const SizedBox(height: 8),
|
|
3943
|
+
Wrap(
|
|
3944
|
+
spacing: 8,
|
|
3945
|
+
runSpacing: 8,
|
|
3946
|
+
children: memberActorTargets
|
|
3947
|
+
.take(10)
|
|
3948
|
+
.map((target) {
|
|
3949
|
+
return ActionChip(
|
|
3950
|
+
label: Text(target.label),
|
|
3951
|
+
onPressed: () => setState(() {
|
|
3952
|
+
_selectedScope = target.scope;
|
|
3953
|
+
_valueController.text = target.value;
|
|
3954
|
+
}),
|
|
3955
|
+
);
|
|
3956
|
+
})
|
|
3957
|
+
.toList(growable: false),
|
|
3958
|
+
),
|
|
3959
|
+
const SizedBox(height: 14),
|
|
3960
|
+
],
|
|
3961
|
+
if (memberSpaceTargets.isNotEmpty) ...<Widget>[
|
|
3962
|
+
Text(
|
|
3963
|
+
'Choose a discovered shared space',
|
|
3964
|
+
style: TextStyle(fontWeight: FontWeight.w700),
|
|
3965
|
+
),
|
|
3966
|
+
const SizedBox(height: 8),
|
|
3967
|
+
Wrap(
|
|
3968
|
+
spacing: 8,
|
|
3969
|
+
runSpacing: 8,
|
|
3970
|
+
children: memberSpaceTargets
|
|
3971
|
+
.take(10)
|
|
3972
|
+
.map((target) {
|
|
3973
|
+
return ActionChip(
|
|
3974
|
+
label: Text(target.label),
|
|
3975
|
+
onPressed: () => setState(() {
|
|
3976
|
+
_selectedSpaceScope = target.scope;
|
|
3977
|
+
_spaceValueController.text = target.value;
|
|
3978
|
+
}),
|
|
3979
|
+
);
|
|
3980
|
+
})
|
|
3981
|
+
.toList(growable: false),
|
|
3982
|
+
),
|
|
3983
|
+
const SizedBox(height: 14),
|
|
3984
|
+
],
|
|
3985
|
+
const Divider(height: 10),
|
|
3986
|
+
],
|
|
3560
3987
|
Text('Manual entry', style: TextStyle(fontWeight: FontWeight.w700)),
|
|
3561
3988
|
const SizedBox(height: 8),
|
|
3562
3989
|
if (availableScopes.isNotEmpty)
|
|
@@ -3584,23 +4011,58 @@ class _MessagingAccessRulePickerSheetState
|
|
|
3584
4011
|
),
|
|
3585
4012
|
const SizedBox(height: 12),
|
|
3586
4013
|
TextField(
|
|
4014
|
+
controller: _valueController,
|
|
3587
4015
|
decoration: InputDecoration(
|
|
3588
|
-
labelText:
|
|
4016
|
+
labelText: _selectedBucket == 'sharedMemberRules'
|
|
4017
|
+
? 'Sender ID / value'
|
|
4018
|
+
: 'ID / value',
|
|
3589
4019
|
helperText: widget.catalog.capabilities.manualEntryHint,
|
|
3590
4020
|
),
|
|
3591
|
-
onSubmitted:
|
|
3592
|
-
|
|
3593
|
-
|
|
3594
|
-
|
|
3595
|
-
|
|
3596
|
-
|
|
3597
|
-
|
|
3598
|
-
|
|
3599
|
-
|
|
3600
|
-
|
|
4021
|
+
onSubmitted: _selectedBucket == 'sharedMemberRules'
|
|
4022
|
+
? null
|
|
4023
|
+
: (_) => _submitManualRule(context),
|
|
4024
|
+
),
|
|
4025
|
+
if (_selectedBucket == 'sharedMemberRules') ...<Widget>[
|
|
4026
|
+
const SizedBox(height: 12),
|
|
4027
|
+
InputDecorator(
|
|
4028
|
+
decoration: InputDecoration(labelText: 'Shared-space scope'),
|
|
4029
|
+
child: DropdownButtonHideUnderline(
|
|
4030
|
+
child: DropdownButton<String>(
|
|
4031
|
+
value: _selectedSpaceScope,
|
|
4032
|
+
isExpanded: true,
|
|
4033
|
+
items: widget.catalog.capabilities.sharedSpaceRuleScopes
|
|
4034
|
+
.map(
|
|
4035
|
+
(scope) => DropdownMenuItem<String>(
|
|
4036
|
+
value: scope,
|
|
4037
|
+
child: Text(scope.replaceAll('_', ' ')),
|
|
4038
|
+
),
|
|
4039
|
+
)
|
|
4040
|
+
.toList(growable: false),
|
|
4041
|
+
onChanged: (value) {
|
|
4042
|
+
if (value != null) {
|
|
4043
|
+
setState(() => _selectedSpaceScope = value);
|
|
4044
|
+
}
|
|
4045
|
+
},
|
|
3601
4046
|
),
|
|
3602
|
-
)
|
|
3603
|
-
|
|
4047
|
+
),
|
|
4048
|
+
),
|
|
4049
|
+
const SizedBox(height: 12),
|
|
4050
|
+
TextField(
|
|
4051
|
+
controller: _spaceValueController,
|
|
4052
|
+
decoration: InputDecoration(
|
|
4053
|
+
labelText: 'Group / channel / room ID',
|
|
4054
|
+
),
|
|
4055
|
+
onSubmitted: (_) => _submitManualRule(context),
|
|
4056
|
+
),
|
|
4057
|
+
],
|
|
4058
|
+
const SizedBox(height: 14),
|
|
4059
|
+
Align(
|
|
4060
|
+
alignment: Alignment.centerRight,
|
|
4061
|
+
child: FilledButton.icon(
|
|
4062
|
+
onPressed: () => _submitManualRule(context),
|
|
4063
|
+
icon: Icon(Icons.add_rounded),
|
|
4064
|
+
label: Text('Add rule'),
|
|
4065
|
+
),
|
|
3604
4066
|
),
|
|
3605
4067
|
],
|
|
3606
4068
|
),
|