mirror of
https://github.com/AppFlowy-IO/AppFlowy.git
synced 2024-08-30 18:12:39 +00:00
feat: complete UIUX for AI and limits
This commit is contained in:
parent
61419e305c
commit
e9715abe90
@ -106,6 +106,7 @@ class SettingsBillingBloc
|
||||
addSubscription: (plan) async {
|
||||
final result =
|
||||
await _userService.createSubscription(workspaceId, plan);
|
||||
|
||||
result.fold(
|
||||
(link) => afLaunchUrlString(link.paymentLink),
|
||||
(f) => Log.error(f.msg, f),
|
||||
|
@ -28,8 +28,10 @@ class SettingsPlanBloc extends Bloc<SettingsPlanEvent, SettingsPlanState> {
|
||||
|
||||
on<SettingsPlanEvent>((event, emit) async {
|
||||
await event.when(
|
||||
started: (withSuccessfulUpgrade) async {
|
||||
emit(const SettingsPlanState.loading());
|
||||
started: (withSuccessfulUpgrade, shouldLoad) async {
|
||||
if (shouldLoad) {
|
||||
emit(const SettingsPlanState.loading());
|
||||
}
|
||||
|
||||
final snapshots = await Future.wait([
|
||||
_service.getWorkspaceUsage(),
|
||||
@ -109,7 +111,12 @@ class SettingsPlanBloc extends Bloc<SettingsPlanEvent, SettingsPlanState> {
|
||||
return;
|
||||
}
|
||||
|
||||
add(SettingsPlanEvent.started(withSuccessfulUpgrade: plan));
|
||||
add(
|
||||
SettingsPlanEvent.started(
|
||||
withSuccessfulUpgrade: plan,
|
||||
shouldLoad: false,
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
});
|
||||
@ -142,6 +149,7 @@ class SettingsPlanBloc extends Bloc<SettingsPlanEvent, SettingsPlanState> {
|
||||
class SettingsPlanEvent with _$SettingsPlanEvent {
|
||||
const factory SettingsPlanEvent.started({
|
||||
@Default(null) SubscriptionPlanPB? withSuccessfulUpgrade,
|
||||
@Default(true) bool shouldLoad,
|
||||
}) = _Started;
|
||||
|
||||
const factory SettingsPlanEvent.addSubscription(SubscriptionPlanPB plan) =
|
||||
|
@ -367,8 +367,9 @@ class _PlanUsageSummary extends StatelessWidget {
|
||||
],
|
||||
),
|
||||
const VSpace(16),
|
||||
Column(
|
||||
SeparatedColumn(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
separatorBuilder: () => const VSpace(4),
|
||||
children: [
|
||||
if (subscriptionInfo.plan == WorkspacePlanPB.FreePlan) ...[
|
||||
_ToggleMore(
|
||||
@ -383,10 +384,40 @@ class _PlanUsageSummary extends StatelessWidget {
|
||||
SubscriptionPlanPB.Pro,
|
||||
),
|
||||
);
|
||||
await Future.delayed(
|
||||
const Duration(seconds: 2),
|
||||
() {},
|
||||
);
|
||||
await Future.delayed(const Duration(seconds: 2), () {});
|
||||
},
|
||||
),
|
||||
],
|
||||
if (!subscriptionInfo.hasAIMax) ...[
|
||||
_ToggleMore(
|
||||
value: false,
|
||||
label: LocaleKeys.settings_planPage_planUsage_aiMaxToggle.tr(),
|
||||
badgeLabel:
|
||||
LocaleKeys.settings_planPage_planUsage_aiMaxBadge.tr(),
|
||||
onTap: () async {
|
||||
context.read<SettingsPlanBloc>().add(
|
||||
const SettingsPlanEvent.addSubscription(
|
||||
SubscriptionPlanPB.AiMax,
|
||||
),
|
||||
);
|
||||
await Future.delayed(const Duration(seconds: 2), () {});
|
||||
},
|
||||
),
|
||||
],
|
||||
if (!subscriptionInfo.hasAIOnDevice) ...[
|
||||
_ToggleMore(
|
||||
value: false,
|
||||
label: LocaleKeys.settings_planPage_planUsage_aiOnDeviceToggle
|
||||
.tr(),
|
||||
badgeLabel:
|
||||
LocaleKeys.settings_planPage_planUsage_aiOnDeviceBadge.tr(),
|
||||
onTap: () async {
|
||||
context.read<SettingsPlanBloc>().add(
|
||||
const SettingsPlanEvent.addSubscription(
|
||||
SubscriptionPlanPB.AiLocal,
|
||||
),
|
||||
);
|
||||
await Future.delayed(const Duration(seconds: 2), () {});
|
||||
},
|
||||
),
|
||||
],
|
||||
@ -417,8 +448,6 @@ class _UsageBox extends StatelessWidget {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final isLM = Theme.of(context).isLightMode;
|
||||
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
@ -429,33 +458,22 @@ class _UsageBox extends StatelessWidget {
|
||||
),
|
||||
if (unlimited) ...[
|
||||
const VSpace(4),
|
||||
DecoratedBox(
|
||||
decoration: BoxDecoration(
|
||||
border: Border.all(
|
||||
color: isLM ? const Color(0xFFE8E2EE) : const Color(0xFF9C00FB),
|
||||
),
|
||||
borderRadius: BorderRadius.circular(20),
|
||||
),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
vertical: 4,
|
||||
horizontal: 8,
|
||||
),
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
const FlowySvg(
|
||||
FlowySvgs.check_circle_outlined_s,
|
||||
color: Color(0xFF9C00FB),
|
||||
),
|
||||
const HSpace(4),
|
||||
FlowyText(
|
||||
unlimitedLabel,
|
||||
fontWeight: FontWeight.w500,
|
||||
fontSize: 11,
|
||||
),
|
||||
],
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: 4),
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
const FlowySvg(
|
||||
FlowySvgs.check_circle_outlined_s,
|
||||
color: Color(0xFF9C00FB),
|
||||
),
|
||||
const HSpace(4),
|
||||
FlowyText(
|
||||
unlimitedLabel,
|
||||
fontWeight: FontWeight.w500,
|
||||
fontSize: 11,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
] else ...[
|
||||
@ -569,7 +587,9 @@ class _PlanProgressIndicator extends StatelessWidget {
|
||||
widthFactor: progress,
|
||||
child: Container(
|
||||
decoration: BoxDecoration(
|
||||
color: theme.colorScheme.primary,
|
||||
color: progress >= 1
|
||||
? theme.colorScheme.error
|
||||
: theme.colorScheme.primary,
|
||||
),
|
||||
),
|
||||
),
|
||||
@ -613,6 +633,8 @@ class _AddOnBox extends StatelessWidget {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final isLM = Theme.of(context).isLightMode;
|
||||
|
||||
return Container(
|
||||
height: 220,
|
||||
padding: const EdgeInsets.symmetric(
|
||||
@ -621,11 +643,9 @@ class _AddOnBox extends StatelessWidget {
|
||||
),
|
||||
decoration: BoxDecoration(
|
||||
border: Border.all(
|
||||
color: isActive ? const Color(0xFF9C00FB) : const Color(0xFFBDBDBD),
|
||||
color: isActive ? const Color(0xFFBDBDBD) : const Color(0xFF9C00FB),
|
||||
),
|
||||
color: isActive
|
||||
? const Color(0xFFF7F8FC).withOpacity(0.05)
|
||||
: Colors.transparent,
|
||||
color: const Color(0xFFF7F8FC).withOpacity(0.05),
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
),
|
||||
child: Column(
|
||||
@ -673,22 +693,36 @@ class _AddOnBox extends StatelessWidget {
|
||||
Expanded(
|
||||
child: FlowyTextButton(
|
||||
buttonText,
|
||||
heading: isActive
|
||||
? const FlowySvg(
|
||||
FlowySvgs.check_circle_outlined_s,
|
||||
color: Color(0xFF9C00FB),
|
||||
)
|
||||
: null,
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
padding:
|
||||
const EdgeInsets.symmetric(horizontal: 16, vertical: 7),
|
||||
fillColor:
|
||||
isActive ? const Color(0xFFE8E2EE) : Colors.transparent,
|
||||
fillColor: isActive
|
||||
? const Color(0xFFE8E2EE)
|
||||
: isLM
|
||||
? Colors.transparent
|
||||
: const Color(0xFF5C3699),
|
||||
constraints: const BoxConstraints(minWidth: 115),
|
||||
radius: Corners.s16Border,
|
||||
hoverColor: isActive
|
||||
? const Color(0xFFE8E2EE)
|
||||
: const Color(0xFF5C3699),
|
||||
fontColor: const Color(0xFF5C3699),
|
||||
: isLM
|
||||
? const Color(0xFF5C3699)
|
||||
: const Color(0xFF4d3472),
|
||||
fontColor:
|
||||
isLM || isActive ? const Color(0xFF5C3699) : Colors.white,
|
||||
fontHoverColor:
|
||||
isActive ? const Color(0xFF5C3699) : Colors.white,
|
||||
borderColor: isActive
|
||||
? const Color(0xFFE8E2EE)
|
||||
: const Color(0xFF5C3699),
|
||||
: isLM
|
||||
? const Color(0xFF5C3699)
|
||||
: const Color(0xFF4d3472),
|
||||
fontSize: 12,
|
||||
onPressed: isActive
|
||||
? null
|
||||
|
@ -120,7 +120,10 @@ class SettingsDialog extends StatelessWidget {
|
||||
return const AIFeatureOnlySupportedWhenUsingAppFlowyCloud();
|
||||
}
|
||||
case SettingsPage.member:
|
||||
return WorkspaceMembersPage(userProfile: user);
|
||||
return WorkspaceMembersPage(
|
||||
userProfile: user,
|
||||
workspaceId: workspaceId,
|
||||
);
|
||||
case SettingsPage.plan:
|
||||
return SettingsPlanView(workspaceId: workspaceId, user: user);
|
||||
case SettingsPage.billing:
|
||||
|
@ -1,3 +1,6 @@
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:appflowy/core/helpers/url_launcher.dart';
|
||||
import 'package:appflowy/user/application/user_service.dart';
|
||||
import 'package:appflowy_backend/dispatch/dispatch.dart';
|
||||
import 'package:appflowy_backend/log.dart';
|
||||
@ -24,13 +27,14 @@ class WorkspaceMemberBloc
|
||||
extends Bloc<WorkspaceMemberEvent, WorkspaceMemberState> {
|
||||
WorkspaceMemberBloc({
|
||||
required this.userProfile,
|
||||
String? workspaceId,
|
||||
this.workspace,
|
||||
}) : _userBackendService = UserBackendService(userId: userProfile.id),
|
||||
super(WorkspaceMemberState.initial()) {
|
||||
on<WorkspaceMemberEvent>((event, emit) async {
|
||||
await event.when(
|
||||
initial: () async {
|
||||
await _setCurrentWorkspaceId();
|
||||
await _setCurrentWorkspaceId(workspaceId);
|
||||
|
||||
final result = await _userBackendService.getWorkspaceMembers(
|
||||
_workspaceId,
|
||||
@ -135,9 +139,7 @@ class WorkspaceMemberBloc
|
||||
(s) => state.members.map((e) {
|
||||
if (e.email == email) {
|
||||
e.freeze();
|
||||
return e.rebuild((p0) {
|
||||
p0.role = role;
|
||||
});
|
||||
return e.rebuild((p0) => p0.role = role);
|
||||
}
|
||||
return e;
|
||||
}).toList(),
|
||||
@ -153,7 +155,26 @@ class WorkspaceMemberBloc
|
||||
),
|
||||
);
|
||||
},
|
||||
upgradePlan: () {},
|
||||
updateSubscriptionInfo: (info) async =>
|
||||
emit(state.copyWith(subscriptionInfo: info)),
|
||||
upgradePlan: () async {
|
||||
final plan = state.subscriptionInfo?.plan;
|
||||
if (plan == null) {
|
||||
return Log.error('Failed to upgrade plan: plan is null');
|
||||
}
|
||||
|
||||
if (plan == WorkspacePlanPB.FreePlan) {
|
||||
final checkoutLink = await _userBackendService.createSubscription(
|
||||
_workspaceId,
|
||||
SubscriptionPlanPB.Pro,
|
||||
);
|
||||
|
||||
checkoutLink.fold(
|
||||
(pl) => afLaunchUrlString(pl.paymentLink),
|
||||
(f) => Log.error('Failed to create subscription: ${f.msg}', f),
|
||||
);
|
||||
}
|
||||
},
|
||||
);
|
||||
});
|
||||
}
|
||||
@ -179,9 +200,11 @@ class WorkspaceMemberBloc
|
||||
return role;
|
||||
}
|
||||
|
||||
Future<void> _setCurrentWorkspaceId() async {
|
||||
Future<void> _setCurrentWorkspaceId(String? workspaceId) async {
|
||||
if (workspace != null) {
|
||||
_workspaceId = workspace!.workspaceId;
|
||||
} else if (workspaceId != null && workspaceId.isNotEmpty) {
|
||||
_workspaceId = workspaceId;
|
||||
} else {
|
||||
final currentWorkspace = await FolderEventReadCurrentWorkspace().send();
|
||||
currentWorkspace.fold((s) {
|
||||
@ -192,6 +215,20 @@ class WorkspaceMemberBloc
|
||||
_workspaceId = '';
|
||||
});
|
||||
}
|
||||
|
||||
unawaited(_fetchWorkspaceSubscriptionInfo());
|
||||
}
|
||||
|
||||
// We fetch workspace subscription info lazily as it's not needed in the first
|
||||
// render of the page.
|
||||
Future<void> _fetchWorkspaceSubscriptionInfo() async {
|
||||
final result =
|
||||
await UserBackendService.getWorkspaceSubscriptionInfo(_workspaceId);
|
||||
|
||||
result.fold(
|
||||
(info) => add(WorkspaceMemberEvent.updateSubscriptionInfo(info)),
|
||||
(f) => Log.error('Failed to fetch subscription info: ${f.msg}', f),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@ -210,6 +247,9 @@ class WorkspaceMemberEvent with _$WorkspaceMemberEvent {
|
||||
String email,
|
||||
AFRolePB role,
|
||||
) = UpdateWorkspaceMember;
|
||||
const factory WorkspaceMemberEvent.updateSubscriptionInfo(
|
||||
WorkspaceSubscriptionInfoPB subscriptionInfo,
|
||||
) = UpdateSubscriptionInfo;
|
||||
|
||||
const factory WorkspaceMemberEvent.upgradePlan() = UpgradePlan;
|
||||
}
|
||||
@ -244,6 +284,7 @@ class WorkspaceMemberState with _$WorkspaceMemberState {
|
||||
@Default(AFRolePB.Guest) AFRolePB myRole,
|
||||
@Default(null) WorkspaceMemberActionResult? actionResult,
|
||||
@Default(true) bool isLoading,
|
||||
@Default(null) WorkspaceSubscriptionInfoPB? subscriptionInfo,
|
||||
}) = _WorkspaceMemberState;
|
||||
|
||||
factory WorkspaceMemberState.initial() => const WorkspaceMemberState();
|
||||
@ -258,6 +299,7 @@ class WorkspaceMemberState with _$WorkspaceMemberState {
|
||||
return other is WorkspaceMemberState &&
|
||||
other.members == members &&
|
||||
other.myRole == myRole &&
|
||||
other.subscriptionInfo == subscriptionInfo &&
|
||||
identical(other.actionResult, actionResult);
|
||||
}
|
||||
}
|
||||
|
@ -1,11 +1,12 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'package:appflowy/core/helpers/url_launcher.dart';
|
||||
import 'package:appflowy/generated/flowy_svgs.g.dart';
|
||||
import 'package:appflowy/generated/locale_keys.g.dart';
|
||||
import 'package:appflowy/shared/af_role_pb_extension.dart';
|
||||
import 'package:appflowy/util/theme_extension.dart';
|
||||
import 'package:appflowy/workspace/presentation/home/toast.dart';
|
||||
import 'package:appflowy/workspace/presentation/settings/shared/settings_body.dart';
|
||||
import 'package:appflowy/workspace/presentation/settings/shared/settings_category_spacer.dart';
|
||||
import 'package:appflowy/workspace/presentation/settings/widgets/members/workspace_member_bloc.dart';
|
||||
import 'package:appflowy/workspace/presentation/widgets/dialogs.dart';
|
||||
import 'package:appflowy/workspace/presentation/widgets/pop_up_action.dart';
|
||||
@ -13,7 +14,7 @@ import 'package:appflowy_backend/protobuf/flowy-error/code.pbenum.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-user/protobuf.dart';
|
||||
import 'package:appflowy_popover/appflowy_popover.dart';
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flowy_infra/size.dart';
|
||||
import 'package:flowy_infra/theme_extension.dart';
|
||||
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
|
||||
import 'package:flowy_infra_ui/widget/flowy_tooltip.dart';
|
||||
import 'package:flowy_infra_ui/widget/rounded_button.dart';
|
||||
@ -21,9 +22,14 @@ import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:string_validator/string_validator.dart';
|
||||
|
||||
class WorkspaceMembersPage extends StatelessWidget {
|
||||
const WorkspaceMembersPage({super.key, required this.userProfile});
|
||||
const WorkspaceMembersPage({
|
||||
super.key,
|
||||
required this.userProfile,
|
||||
required this.workspaceId,
|
||||
});
|
||||
|
||||
final UserProfilePB userProfile;
|
||||
final String workspaceId;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
@ -34,13 +40,16 @@ class WorkspaceMembersPage extends StatelessWidget {
|
||||
builder: (context, state) {
|
||||
return SettingsBody(
|
||||
title: LocaleKeys.settings_appearance_members_title.tr(),
|
||||
autoSeparate: false,
|
||||
children: [
|
||||
if (state.actionResult != null)
|
||||
_showMemberLimitWarning(
|
||||
context,
|
||||
state.actionResult!,
|
||||
),
|
||||
if (state.myRole.canInvite) const _InviteMember(),
|
||||
if (state.actionResult != null) ...[
|
||||
_showMemberLimitWarning(context, state),
|
||||
const VSpace(16),
|
||||
],
|
||||
if (state.myRole.canInvite) ...[
|
||||
const _InviteMember(),
|
||||
const SettingsCategorySpacer(),
|
||||
],
|
||||
if (state.members.isNotEmpty)
|
||||
_MemberList(
|
||||
members: state.members,
|
||||
@ -56,78 +65,98 @@ class WorkspaceMembersPage extends StatelessWidget {
|
||||
|
||||
Widget _showMemberLimitWarning(
|
||||
BuildContext context,
|
||||
WorkspaceMemberActionResult result,
|
||||
WorkspaceMemberState state,
|
||||
) {
|
||||
final isLM = Theme.of(context).isLightMode;
|
||||
// We promise that state.actionResult != null before calling
|
||||
// this method
|
||||
final actionResult = state.actionResult!.result;
|
||||
final actionType = state.actionResult!.actionType;
|
||||
|
||||
final actionType = result.actionType;
|
||||
final actionResult = result.result;
|
||||
debugPrint("Plan: ${state.subscriptionInfo?.plan}");
|
||||
|
||||
if (actionType == WorkspaceMemberActionType.invite &&
|
||||
actionResult.isFailure) {
|
||||
final error = actionResult.getFailure().code;
|
||||
if (error == ErrorCode.WorkspaceMemberLimitExceeded) {
|
||||
return DecoratedBox(
|
||||
decoration: BoxDecoration(
|
||||
color: isLM
|
||||
? const Color(0xFFFFF4E5)
|
||||
: const Color.fromARGB(255, 255, 200, 125),
|
||||
borderRadius: Corners.s8Border,
|
||||
),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
vertical: 6,
|
||||
horizontal: 16,
|
||||
return Row(
|
||||
children: [
|
||||
const FlowySvg(
|
||||
FlowySvgs.warning_s,
|
||||
blendMode: BlendMode.dst,
|
||||
size: Size.square(20),
|
||||
),
|
||||
child: Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
FlowySvg(
|
||||
FlowySvgs.warning_m,
|
||||
color: isLM
|
||||
? const Color(0xFFEF6C00)
|
||||
: const Color.fromARGB(255, 160, 75, 0),
|
||||
),
|
||||
const HSpace(12),
|
||||
Expanded(
|
||||
child: FlowyText(
|
||||
LocaleKeys.settings_appearance_members_memberLimitExceeded
|
||||
.tr(),
|
||||
color: const Color(0xFF663C00),
|
||||
maxLines: 3,
|
||||
fontSize: 14,
|
||||
),
|
||||
),
|
||||
MouseRegion(
|
||||
cursor: SystemMouseCursors.click,
|
||||
child: GestureDetector(
|
||||
onTap: () {},
|
||||
child: DecoratedBox(
|
||||
decoration: BoxDecoration(
|
||||
border: Border.all(
|
||||
color: const Color(0xFF5F2120),
|
||||
),
|
||||
borderRadius: Corners.s4Border,
|
||||
),
|
||||
child: const Padding(
|
||||
padding: EdgeInsets.symmetric(
|
||||
horizontal: 10,
|
||||
vertical: 5,
|
||||
),
|
||||
child: FlowyText(
|
||||
// TODO(Mathias): Localization
|
||||
'UPGRADE',
|
||||
fontSize: 13,
|
||||
fontWeight: FontWeight.w500,
|
||||
color: Color(0xFF663C00),
|
||||
const HSpace(12),
|
||||
Expanded(
|
||||
child: RichText(
|
||||
text: TextSpan(
|
||||
children: [
|
||||
if (state.subscriptionInfo?.plan ==
|
||||
WorkspacePlanPB.ProPlan) ...[
|
||||
TextSpan(
|
||||
text: LocaleKeys
|
||||
.settings_appearance_members_memberLimitExceededPro
|
||||
.tr(),
|
||||
style: TextStyle(
|
||||
fontSize: 14,
|
||||
fontWeight: FontWeight.w400,
|
||||
color: AFThemeExtension.of(context).strongText,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
WidgetSpan(
|
||||
child: MouseRegion(
|
||||
cursor: SystemMouseCursors.click,
|
||||
child: GestureDetector(
|
||||
// Hardcoded support email, in the future we might
|
||||
// want to add this to an environment variable
|
||||
onTap: () async => afLaunchUrlString(
|
||||
'mailto:support@appflowy.io',
|
||||
),
|
||||
child: FlowyText(
|
||||
LocaleKeys
|
||||
.settings_appearance_members_memberLimitExceededProContact
|
||||
.tr(),
|
||||
fontSize: 14,
|
||||
fontWeight: FontWeight.w400,
|
||||
color: Theme.of(context).colorScheme.primary,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
] else ...[
|
||||
TextSpan(
|
||||
text: LocaleKeys
|
||||
.settings_appearance_members_memberLimitExceeded
|
||||
.tr(),
|
||||
style: TextStyle(
|
||||
fontSize: 14,
|
||||
fontWeight: FontWeight.w400,
|
||||
color: AFThemeExtension.of(context).strongText,
|
||||
),
|
||||
),
|
||||
WidgetSpan(
|
||||
child: MouseRegion(
|
||||
cursor: SystemMouseCursors.click,
|
||||
child: GestureDetector(
|
||||
onTap: () => context
|
||||
.read<WorkspaceMemberBloc>()
|
||||
.add(const WorkspaceMemberEvent.upgradePlan()),
|
||||
child: FlowyText(
|
||||
LocaleKeys
|
||||
.settings_appearance_members_memberLimitExceededUpgrade
|
||||
.tr(),
|
||||
fontSize: 14,
|
||||
fontWeight: FontWeight.w400,
|
||||
color: Theme.of(context).colorScheme.primary,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
5
frontend/resources/flowy_icons/16x/warning.svg
Normal file
5
frontend/resources/flowy_icons/16x/warning.svg
Normal file
@ -0,0 +1,5 @@
|
||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<circle cx="8.00033" cy="7.99984" r="7.33333" fill="#FF811A"/>
|
||||
<path d="M7.99967 4.6665C7.63148 4.6665 7.33301 4.96498 7.33301 5.33317V8.6665C7.33301 9.03469 7.63148 9.33317 7.99967 9.33317C8.36786 9.33317 8.66634 9.03469 8.66634 8.6665V5.33317C8.66634 4.96498 8.36786 4.6665 7.99967 4.6665Z" fill="white"/>
|
||||
<path d="M7.99967 11.3332C8.36786 11.3332 8.66634 11.0347 8.66634 10.6665C8.66634 10.2983 8.36786 9.99984 7.99967 9.99984C7.63148 9.99984 7.33301 10.2983 7.33301 10.6665C7.33301 11.0347 7.63148 11.3332 7.99967 11.3332Z" fill="white"/>
|
||||
</svg>
|
After Width: | Height: | Size: 646 B |
@ -1,5 +0,0 @@
|
||||
<svg width="22" height="22" viewBox="0 0 22 22" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M11.0003 5.949L17.9028 17.8748H4.09783L11.0003 5.949ZM11.0003 2.2915L0.916992 19.7082H21.0837L11.0003 2.2915Z" fill="#EF6C00"/>
|
||||
<path d="M11.917 15.1248H10.0837V16.9582H11.917V15.1248Z" fill="#EF6C00"/>
|
||||
<path d="M11.917 9.62484H10.0837V14.2082H11.917V9.62484Z" fill="#EF6C00"/>
|
||||
</svg>
|
Before Width: | Height: | Size: 390 B |
@ -646,7 +646,11 @@
|
||||
"aiResponseUsage": "{} of {}",
|
||||
"unlimitedAILabel": "Unlimited responses",
|
||||
"proBadge": "Pro",
|
||||
"aiMaxBadge": "AI Max",
|
||||
"aiOnDeviceBadge": "AI On-device",
|
||||
"memberProToggle": "More members & unlimited AI",
|
||||
"aiMaxToggle": "Unlimited AI responses",
|
||||
"aiOnDeviceToggle": "On-device AI for ultimate privacy",
|
||||
"aiCredit": {
|
||||
"title": "Add @:appName AI Credit",
|
||||
"price": "5$",
|
||||
@ -717,15 +721,15 @@
|
||||
"renewLabel": "Renew",
|
||||
"aiMax": {
|
||||
"label": "AI Max",
|
||||
"description": "US$8 /user per month billed annually or US$10 billed monthly",
|
||||
"description": "Unlock unlimited AI and advanced models",
|
||||
"activeDescription": "Next invoice due on {}",
|
||||
"canceledDescription": "AI Max will be removed on {}"
|
||||
"canceledDescription": "AI Max will be available until {}"
|
||||
},
|
||||
"aiOnDevice": {
|
||||
"label": "AI On-device",
|
||||
"description": "US$8 /user per month billed annually or US$10 billed monthly",
|
||||
"description": "Unlock unlimited AI offline on your device",
|
||||
"activeDescription": "Next invoice due on {}",
|
||||
"canceledDescription": "AI On-device will be removed on {}"
|
||||
"canceledDescription": "AI On-device will be available until {}"
|
||||
}
|
||||
}
|
||||
},
|
||||
@ -951,7 +955,10 @@
|
||||
"one": "{} member",
|
||||
"other": "{} members"
|
||||
},
|
||||
"memberLimitExceeded": "Member limit reached. Upgrade to invite additional members.",
|
||||
"memberLimitExceeded": "Member limit reached, to invite more members, please ",
|
||||
"memberLimitExceededUpgrade": "upgrade",
|
||||
"memberLimitExceededPro": "Member limit reached, if you require more members contact ",
|
||||
"memberLimitExceededProContact": "support@appflowy.io",
|
||||
"failedToAddMember": "Failed to add member",
|
||||
"addMemberSuccess": "Member added successfully",
|
||||
"removeMember": "Remove Member",
|
||||
|
Loading…
Reference in New Issue
Block a user