Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f407905057 | ||
|
|
8f8fd43bd1 | ||
|
|
170e09bae9 | ||
|
|
8aa6dd8821 | ||
|
|
1516bca2d0 | ||
|
|
d1ab82d32a | ||
|
|
ed18fe32b4 | ||
|
|
de1577128e | ||
|
|
4e75dbe16a | ||
|
|
3c2d65a025 | ||
|
|
92af6103b0 | ||
|
|
c34d9c0e1d | ||
|
|
e870481ef9 | ||
|
|
c1f6a0efea | ||
|
|
5c19c08cb3 | ||
|
|
210ea0c2ac | ||
|
|
ab70dccc8e | ||
|
|
eb22d86f3e | ||
|
|
a7d5c389b0 | ||
|
|
71a6ba660f |
No files matched your search
@@ -26,7 +26,7 @@ CARGO_MAKE_EXTEND_WORKSPACE_MAKEFILE = true
|
||||
CARGO_MAKE_CRATE_FS_NAME = "dart_ffi"
|
||||
CARGO_MAKE_CRATE_NAME = "dart-ffi"
|
||||
LIB_NAME = "dart_ffi"
|
||||
APPFLOWY_VERSION = "0.8.8"
|
||||
APPFLOWY_VERSION = "0.8.9"
|
||||
FLUTTER_DESKTOP_FEATURES = "dart"
|
||||
PRODUCT_NAME = "AppFlowy"
|
||||
MACOSX_DEPLOYMENT_TARGET = "11.0"
|
||||
|
||||
@@ -4,6 +4,7 @@ analyzer:
|
||||
exclude:
|
||||
- "**/*.g.dart"
|
||||
- "**/*.freezed.dart"
|
||||
- "packages/**/*.dart"
|
||||
|
||||
linter:
|
||||
rules:
|
||||
|
||||
@@ -15,7 +15,6 @@ void main() {
|
||||
cloudType: AuthenticatorType.appflowyCloudSelfHost,
|
||||
);
|
||||
|
||||
await tester.tapContinousAnotherWay();
|
||||
await tester.tapAnonymousSignInButton();
|
||||
await tester.expectToSeeHomePageWithGetStartedPage();
|
||||
|
||||
|
||||
@@ -15,6 +15,7 @@ void main() {
|
||||
await tester.initializeAppFlowy();
|
||||
await tester.tapAnonymousSignInButton();
|
||||
|
||||
// create a database and add a linked database view
|
||||
await tester.createNewPageWithNameUnderParent(layout: ViewLayoutPB.Grid);
|
||||
await tester.tapCreateLinkedDatabaseViewButton(DatabaseLayoutPB.Grid);
|
||||
|
||||
@@ -29,6 +30,11 @@ void main() {
|
||||
await tester.tapHidePropertyButton();
|
||||
tester.noFieldWithName('New field 1');
|
||||
|
||||
// create another field, New field 1 to be hidden still
|
||||
await tester.tapNewPropertyButton();
|
||||
await tester.dismissFieldEditor();
|
||||
tester.noFieldWithName('New field 1');
|
||||
|
||||
// go back to inline database view, expect field to be shown
|
||||
await tester.tapTabBarLinkedViewByViewName('Untitled');
|
||||
tester.findFieldWithName('New field 1');
|
||||
@@ -60,5 +66,40 @@ void main() {
|
||||
await tester.tapDatabaseSortButton();
|
||||
await tester.tapCreateSortByFieldType(FieldType.RichText, "New field 1");
|
||||
});
|
||||
|
||||
testWidgets('field cell width', (tester) async {
|
||||
await tester.initializeAppFlowy();
|
||||
await tester.tapAnonymousSignInButton();
|
||||
|
||||
// create a database and add a linked database view
|
||||
await tester.createNewPageWithNameUnderParent(layout: ViewLayoutPB.Grid);
|
||||
await tester.tapCreateLinkedDatabaseViewButton(DatabaseLayoutPB.Grid);
|
||||
|
||||
// create a field
|
||||
await tester.scrollToRight(find.byType(GridPage));
|
||||
await tester.tapNewPropertyButton();
|
||||
await tester.renameField('New field 1');
|
||||
await tester.dismissFieldEditor();
|
||||
|
||||
// check the width of the field
|
||||
expect(tester.getFieldWidth('New field 1'), 150);
|
||||
|
||||
// change the width of the field
|
||||
await tester.changeFieldWidth('New field 1', 200);
|
||||
expect(tester.getFieldWidth('New field 1'), 205);
|
||||
|
||||
// create another field, New field 1 to be same width
|
||||
await tester.tapNewPropertyButton();
|
||||
await tester.dismissFieldEditor();
|
||||
expect(tester.getFieldWidth('New field 1'), 205);
|
||||
|
||||
// go back to inline database view, expect New field 1 to be 150px
|
||||
await tester.tapTabBarLinkedViewByViewName('Untitled');
|
||||
expect(tester.getFieldWidth('New field 1'), 150);
|
||||
|
||||
// go back to linked database view, expect New field 1 to be 205px
|
||||
await tester.tapTabBarLinkedViewByViewName('Grid');
|
||||
expect(tester.getFieldWidth('New field 1'), 205);
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
import 'package:appflowy_editor/appflowy_editor.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:integration_test/integration_test.dart';
|
||||
|
||||
@@ -47,5 +48,41 @@ void main() {
|
||||
|
||||
expect(editorState.selection!.start.offset, 0);
|
||||
});
|
||||
|
||||
testWidgets('select and delete text', (tester) async {
|
||||
await tester.initializeAppFlowy();
|
||||
await tester.tapAnonymousSignInButton();
|
||||
|
||||
/// create a new document
|
||||
await tester.createNewPageWithNameUnderParent();
|
||||
|
||||
/// input text
|
||||
final editor = tester.editor;
|
||||
final editorState = editor.getCurrentEditorState();
|
||||
|
||||
const inputText = 'Test for text selection and deletion';
|
||||
final texts = inputText.split(' ');
|
||||
await editor.tapLineOfEditorAt(0);
|
||||
await tester.ime.insertText(inputText);
|
||||
|
||||
/// selecte and delete
|
||||
int index = 0;
|
||||
while (texts.isNotEmpty) {
|
||||
final text = texts.removeAt(0);
|
||||
await tester.editor.updateSelection(
|
||||
Selection(
|
||||
start: Position(path: [0], offset: index),
|
||||
end: Position(path: [0], offset: index + text.length),
|
||||
),
|
||||
);
|
||||
await tester.simulateKeyEvent(LogicalKeyboardKey.delete);
|
||||
index++;
|
||||
}
|
||||
|
||||
/// excpete the text value is correct
|
||||
final node = editorState.getNodeAtPath([0])!;
|
||||
final nodeText = node.delta?.toPlainText() ?? '';
|
||||
expect(nodeText, ' ' * (index - 1));
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -942,6 +942,31 @@ extension AppFlowyDatabaseTest on WidgetTester {
|
||||
await pumpAndSettle(const Duration(milliseconds: 200));
|
||||
}
|
||||
|
||||
Future<void> changeFieldWidth(String fieldName, double width) async {
|
||||
final field = find.byWidgetPredicate(
|
||||
(widget) => widget is GridFieldCell && widget.fieldInfo.name == fieldName,
|
||||
);
|
||||
await hoverOnWidget(
|
||||
field,
|
||||
onHover: () async {
|
||||
final dragHandle = find.descendant(
|
||||
of: field,
|
||||
matching: find.byType(DragToExpandLine),
|
||||
);
|
||||
await drag(dragHandle, Offset(width - getSize(field).width, 0));
|
||||
await pumpAndSettle(const Duration(milliseconds: 200));
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
double getFieldWidth(String fieldName) {
|
||||
final field = find.byWidgetPredicate(
|
||||
(widget) => widget is GridFieldCell && widget.fieldInfo.name == fieldName,
|
||||
);
|
||||
|
||||
return getSize(field).width;
|
||||
}
|
||||
|
||||
Future<void> findDateEditor(dynamic matcher) async {
|
||||
final finder = find.byType(DateCellEditor);
|
||||
expect(finder, matcher);
|
||||
|
||||
@@ -181,37 +181,37 @@ EXTERNAL SOURCES:
|
||||
:path: ".symlinks/plugins/webview_flutter_wkwebview/darwin"
|
||||
|
||||
SPEC CHECKSUMS:
|
||||
app_links: e7a6750a915a9e161c58d91bc610e8cd1d4d0ad0
|
||||
appflowy_backend: 144c20d8bfb298c4e10fa3fa6701a9f41bf98b88
|
||||
connectivity_plus: bf0076dd84a130856aa636df1c71ccaff908fa1d
|
||||
device_info_plus: 97af1d7e84681a90d0693e63169a5d50e0839a0d
|
||||
app_links: 3da4c36b46cac3bf24eb897f1a6ce80bda109874
|
||||
appflowy_backend: 78f6a053f756e6bc29bcc5a2106cbe77b756e97a
|
||||
connectivity_plus: 481668c94744c30c53b8895afb39159d1e619bdf
|
||||
device_info_plus: 71ffc6ab7634ade6267c7a93088ed7e4f74e5896
|
||||
DKImagePickerController: b512c28220a2b8ac7419f21c491fc8534b7601ac
|
||||
DKPhotoGallery: fdfad5125a9fdda9cc57df834d49df790dbb4179
|
||||
file_picker: 09aa5ec1ab24135ccd7a1621c46c84134bfd6655
|
||||
flowy_infra_ui: 0455e1fa8c51885aa1437848e361e99419f34ebc
|
||||
file_picker: 9b3292d7c8bc68c8a7bf8eb78f730e49c8efc517
|
||||
flowy_infra_ui: 931b73a18b54a392ab6152eebe29a63a30751f53
|
||||
Flutter: e0871f40cf51350855a761d2e70bf5af5b9b5de7
|
||||
fluttertoast: e9a18c7be5413da53898f660530c56f35edfba9c
|
||||
image_picker_ios: c560581cceedb403a6ff17f2f816d7fea1421fc1
|
||||
integration_test: 252f60fa39af5e17c3aa9899d35d908a0721b573
|
||||
irondash_engine_context: 3458bf979b90d616ffb8ae03a150bafe2e860cc9
|
||||
keyboard_height_plugin: 43fa8bba20fd5c4fdeed5076466b8b9d43cc6b86
|
||||
open_filex: 6e26e659846ec990262224a12ef1c528bb4edbe4
|
||||
package_info_plus: c0502532a26c7662a62a356cebe2692ec5fe4ec4
|
||||
path_provider_foundation: 2b6b4c569c0fb62ec74538f866245ac84301af46
|
||||
permission_handler_apple: 9878588469a2b0d0fc1e048d9f43605f92e6cec2
|
||||
fluttertoast: 76fea30fcf04176325f6864c87306927bd7d2038
|
||||
image_picker_ios: 7fe1ff8e34c1790d6fff70a32484959f563a928a
|
||||
integration_test: 4a889634ef21a45d28d50d622cf412dc6d9f586e
|
||||
irondash_engine_context: 8e58ca8e0212ee9d1c7dc6a42121849986c88486
|
||||
keyboard_height_plugin: ef70a8181b29f27670e9e2450814ca6b6dc05b05
|
||||
open_filex: 432f3cd11432da3e39f47fcc0df2b1603854eff1
|
||||
package_info_plus: af8e2ca6888548050f16fa2f1938db7b5a5df499
|
||||
path_provider_foundation: 080d55be775b7414fd5a5ef3ac137b97b097e564
|
||||
permission_handler_apple: 4ed2196e43d0651e8ff7ca3483a069d469701f2d
|
||||
ReachabilitySwift: 985039c6f7b23a1da463388634119492ff86c825
|
||||
saver_gallery: 76172dc4bf6b40e66d694948ada9ff402304dd87
|
||||
saver_gallery: af2d0c762dafda254e0ad025ef0dabd6506cd490
|
||||
SDWebImage: b9a731e1d6307f44ca703b3976d18c24ca561e84
|
||||
Sentry: 1fe34e9c2cbba1e347623610d26db121dcb569f1
|
||||
sentry_flutter: a39c2a2d67d5e5b9cb0b94a4985c76dd5b3fc737
|
||||
share_plus: 8b6f8b3447e494cca5317c8c3073de39b3600d1f
|
||||
shared_preferences_foundation: fcdcbc04712aee1108ac7fda236f363274528f78
|
||||
sqflite_darwin: 5a7236e3b501866c1c9befc6771dfd73ffb8702d
|
||||
super_native_extensions: 4916b3c627a9c7fffdc48a23a9eca0b1ac228fa7
|
||||
sentry_flutter: e24b397f9a61fa5bbefd8279c3b2242ca86faa90
|
||||
share_plus: 50da8cb520a8f0f65671c6c6a99b3617ed10a58a
|
||||
shared_preferences_foundation: 9e1978ff2562383bd5676f64ec4e9aa8fa06a6f7
|
||||
sqflite_darwin: 20b2a3a3b70e43edae938624ce550a3cbf66a3d0
|
||||
super_native_extensions: b763c02dc3a8fd078389f410bf15149179020cb4
|
||||
SwiftyGif: 6c3eafd0ce693cad58bb63d2b2fb9bacb8552780
|
||||
Toast: 91b396c56ee72a5790816f40d3a94dd357abc196
|
||||
url_launcher_ios: 5334b05cef931de560670eeae103fd3e431ac3fe
|
||||
webview_flutter_wkwebview: 0982481e3d9c78fd5c6f62a002fcd24fc791f1e4
|
||||
url_launcher_ios: 694010445543906933d732453a59da0a173ae33d
|
||||
webview_flutter_wkwebview: 44d4dee7d7056d5ad185d25b38404436d56c547c
|
||||
|
||||
PODFILE CHECKSUM: d0d9b4ff572d8695c38eb3f9b490f55cdfc57eca
|
||||
|
||||
|
||||
@@ -25,14 +25,14 @@ class AboutSettingGroup extends StatelessWidget {
|
||||
trailing: const Icon(
|
||||
Icons.chevron_right,
|
||||
),
|
||||
onTap: () => afLaunchUrlString('https://appflowy.io/privacy'),
|
||||
onTap: () => afLaunchUrlString('https://appflowy.com/privacy'),
|
||||
),
|
||||
MobileSettingItem(
|
||||
name: LocaleKeys.settings_mobile_termsAndConditions.tr(),
|
||||
trailing: const Icon(
|
||||
Icons.chevron_right,
|
||||
),
|
||||
onTap: () => afLaunchUrlString('https://appflowy.io/terms'),
|
||||
onTap: () => afLaunchUrlString('https://appflowy.com/terms'),
|
||||
),
|
||||
if (kDebugMode)
|
||||
MobileSettingItem(
|
||||
|
||||
@@ -282,11 +282,12 @@ class _InviteMemberPageState extends State<_InviteMemberPage> {
|
||||
void _inviteMember(BuildContext context) {
|
||||
final email = emailController.text;
|
||||
if (!isEmail(email)) {
|
||||
return showToastNotification(
|
||||
showToastNotification(
|
||||
context,
|
||||
type: ToastificationType.error,
|
||||
message: LocaleKeys.settings_appearance_members_emailInvalidError.tr(),
|
||||
);
|
||||
return;
|
||||
}
|
||||
context
|
||||
.read<WorkspaceMemberBloc>()
|
||||
|
||||
@@ -411,23 +411,28 @@ class FieldController {
|
||||
/// Listen for field setting changes in the backend.
|
||||
void _listenOnFieldSettingsChanged() {
|
||||
FieldInfo? updateFieldSettings(FieldSettingsPB updatedFieldSettings) {
|
||||
final List<FieldInfo> newFields = fieldInfos;
|
||||
var updatedField = newFields.firstOrNull;
|
||||
final newFields = [...fieldInfos];
|
||||
|
||||
if (updatedField == null) {
|
||||
if (newFields.isEmpty) {
|
||||
return null;
|
||||
}
|
||||
|
||||
final index = newFields
|
||||
.indexWhere((field) => field.id == updatedFieldSettings.fieldId);
|
||||
|
||||
if (index != -1) {
|
||||
newFields[index] =
|
||||
newFields[index].copyWith(fieldSettings: updatedFieldSettings);
|
||||
updatedField = newFields[index];
|
||||
_fieldNotifier.fieldInfos = newFields;
|
||||
_fieldSettings
|
||||
..removeWhere(
|
||||
(field) => field.fieldId == updatedFieldSettings.fieldId,
|
||||
)
|
||||
..add(updatedFieldSettings);
|
||||
return newFields[index];
|
||||
}
|
||||
|
||||
_fieldNotifier.fieldInfos = newFields;
|
||||
return updatedField;
|
||||
return null;
|
||||
}
|
||||
|
||||
_fieldSettingsListener.start(
|
||||
|
||||
@@ -108,7 +108,7 @@ class _GridFieldCellState extends State<GridFieldCell> {
|
||||
top: 0,
|
||||
bottom: 0,
|
||||
right: 0,
|
||||
child: _DragToExpandLine(),
|
||||
child: DragToExpandLine(),
|
||||
);
|
||||
|
||||
return _GridHeaderCellContainer(
|
||||
@@ -158,8 +158,11 @@ class _GridHeaderCellContainer extends StatelessWidget {
|
||||
}
|
||||
}
|
||||
|
||||
class _DragToExpandLine extends StatelessWidget {
|
||||
const _DragToExpandLine();
|
||||
@visibleForTesting
|
||||
class DragToExpandLine extends StatelessWidget {
|
||||
const DragToExpandLine({
|
||||
super.key,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
|
||||
@@ -3,6 +3,7 @@ import 'dart:async';
|
||||
import 'package:appflowy/ai/ai.dart';
|
||||
import 'package:appflowy/workspace/application/view/view_service.dart';
|
||||
import 'package:appflowy_backend/dispatch/dispatch.dart';
|
||||
import 'package:appflowy_backend/log.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-ai/protobuf.dart';
|
||||
import 'package:appflowy_editor/appflowy_editor.dart';
|
||||
import 'package:appflowy_result/appflowy_result.dart';
|
||||
@@ -15,6 +16,11 @@ import 'ai_writer_block_operations.dart';
|
||||
import 'ai_writer_entities.dart';
|
||||
import 'ai_writer_node_extension.dart';
|
||||
|
||||
/// Enable the debug log for the AiWriterCubit.
|
||||
///
|
||||
/// This is useful for debugging the AI writer cubit.
|
||||
const _aiWriterCubitDebugLog = false;
|
||||
|
||||
class AiWriterCubit extends Cubit<AiWriterState> {
|
||||
AiWriterCubit({
|
||||
required this.documentId,
|
||||
@@ -95,6 +101,10 @@ class AiWriterCubit extends Cubit<AiWriterState> {
|
||||
final command = node.aiWriterCommand;
|
||||
final (run, prompt) = await _addSelectionTextToRecords(command);
|
||||
|
||||
_aiWriterCubitLog(
|
||||
'command: $command, run: $run, prompt: $prompt',
|
||||
);
|
||||
|
||||
if (!run) {
|
||||
await exit();
|
||||
return;
|
||||
@@ -211,20 +221,26 @@ class AiWriterCubit extends Cubit<AiWriterState> {
|
||||
return;
|
||||
}
|
||||
|
||||
// Accept
|
||||
//
|
||||
// If the user clicks accept, we need to replace the selection with the AI's response
|
||||
if (action case SuggestionAction.accept) {
|
||||
await _textRobot.persist();
|
||||
await formatSelection(
|
||||
editorState,
|
||||
selection,
|
||||
ApplySuggestionFormatType.clear,
|
||||
// trim the markdown text to avoid extra new lines
|
||||
final trimmedMarkdownText = _textRobot.markdownText.trim();
|
||||
|
||||
_aiWriterCubitLog(
|
||||
'trigger accept action, markdown text: $trimmedMarkdownText',
|
||||
);
|
||||
final nodes = editorState.getNodesInSelection(selection);
|
||||
final transaction = editorState.transaction..deleteNodes(nodes);
|
||||
await editorState.apply(
|
||||
transaction,
|
||||
withUpdateSelection: false,
|
||||
|
||||
await _textRobot.deleteAINodes();
|
||||
|
||||
await _textRobot.replace(
|
||||
selection: selection,
|
||||
markdownText: trimmedMarkdownText,
|
||||
);
|
||||
|
||||
await exit(withDiscard: false, withUnformat: false);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -276,17 +292,24 @@ class AiWriterCubit extends Cubit<AiWriterState> {
|
||||
AiWriterCommand command,
|
||||
) async {
|
||||
final node = aiWriterNode;
|
||||
|
||||
// check the node is registered
|
||||
if (node == null) {
|
||||
return (false, '');
|
||||
}
|
||||
|
||||
// check the selection is valid
|
||||
final selection = node.aiWriterSelection?.normalized;
|
||||
if (selection == null) {
|
||||
return (false, '');
|
||||
}
|
||||
|
||||
// if the command is continue writing, we don't need to get the selection text
|
||||
if (command == AiWriterCommand.continueWriting) {
|
||||
return (true, '');
|
||||
}
|
||||
|
||||
// if the selection is collapsed, we don't need to get the selection text
|
||||
if (selection.isCollapsed) {
|
||||
return (true, '');
|
||||
}
|
||||
@@ -297,6 +320,7 @@ class AiWriterCubit extends Cubit<AiWriterState> {
|
||||
records.add(
|
||||
AiWriterRecord.user(content: selectionText, format: null),
|
||||
);
|
||||
|
||||
return (true, '');
|
||||
} else {
|
||||
return (true, selectionText);
|
||||
@@ -540,6 +564,10 @@ class AiWriterCubit extends Cubit<AiWriterState> {
|
||||
attributes: ApplySuggestionFormatType.replace.attributes,
|
||||
);
|
||||
onAppendToDocument?.call();
|
||||
|
||||
_aiWriterCubitLog(
|
||||
'received message: $text',
|
||||
);
|
||||
},
|
||||
processAssistMessage: (text) async {
|
||||
if (state case final GeneratingAiWriterState generatingState) {
|
||||
@@ -551,6 +579,10 @@ class AiWriterCubit extends Cubit<AiWriterState> {
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
_aiWriterCubitLog(
|
||||
'received assist message: $text',
|
||||
);
|
||||
},
|
||||
onEnd: () async {
|
||||
if (state case final GeneratingAiWriterState generatingState) {
|
||||
@@ -567,6 +599,10 @@ class AiWriterCubit extends Cubit<AiWriterState> {
|
||||
records.add(
|
||||
AiWriterRecord.ai(content: _textRobot.markdownText),
|
||||
);
|
||||
|
||||
_aiWriterCubitLog(
|
||||
'returned response: ${_textRobot.markdownText}',
|
||||
);
|
||||
}
|
||||
},
|
||||
onError: (error) async {
|
||||
@@ -658,6 +694,12 @@ class AiWriterCubit extends Cubit<AiWriterState> {
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
void _aiWriterCubitLog(String message) {
|
||||
if (_aiWriterCubitDebugLog) {
|
||||
Log.debug('[AiWriterCubit] $message');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
mixin RegisteredAiWriter {
|
||||
|
||||
@@ -57,11 +57,30 @@ extension AiWriterNodeExtension on EditorState {
|
||||
slicedNodes.add(copiedNode);
|
||||
}
|
||||
|
||||
for (final (i, node) in slicedNodes.indexed) {
|
||||
final childNodesShouldBeDeleted = <Node>[];
|
||||
for (final child in node.children) {
|
||||
if (!child.path.inSelection(selection)) {
|
||||
childNodesShouldBeDeleted.add(child);
|
||||
}
|
||||
}
|
||||
for (final child in childNodesShouldBeDeleted) {
|
||||
slicedNodes[i] = node.copyWith(
|
||||
children: node.children.where((e) => e.id != child.id).toList(),
|
||||
type: selection.startIndex != 0 ? ParagraphBlockKeys.type : node.type,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// use \n\n as line break to improve the ai response
|
||||
// using \n will cause the ai response treat the text as a single line
|
||||
final markdown = await customDocumentToMarkdown(
|
||||
Document.blank()..insert([0], slicedNodes),
|
||||
lineBreak: '\n\n',
|
||||
);
|
||||
|
||||
return markdown;
|
||||
// trim the last \n if it exists
|
||||
return markdown.trimRight();
|
||||
}
|
||||
|
||||
List<String> getPlainTextInSelection(Selection? selection) {
|
||||
|
||||
@@ -110,10 +110,13 @@ class MarkdownTextRobot {
|
||||
}
|
||||
|
||||
/// Persist the text into the document
|
||||
Future<void> persist({String? markdownText}) async {
|
||||
Future<void> persist({
|
||||
String? markdownText,
|
||||
}) async {
|
||||
if (markdownText != null) {
|
||||
_markdownText = markdownText;
|
||||
}
|
||||
|
||||
await _lock.synchronized(() async {
|
||||
await _refresh(inMemoryUpdate: false);
|
||||
});
|
||||
@@ -124,6 +127,34 @@ class MarkdownTextRobot {
|
||||
}
|
||||
}
|
||||
|
||||
/// Replace the selected content with the AI's response
|
||||
Future<void> replace({
|
||||
required Selection selection,
|
||||
required String markdownText,
|
||||
}) async {
|
||||
if (selection.isSingle) {
|
||||
await _replaceInSameLine(
|
||||
selection: selection,
|
||||
markdownText: markdownText,
|
||||
);
|
||||
} else {
|
||||
await _replaceInMultiLines(
|
||||
selection: selection,
|
||||
markdownText: markdownText,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// Delete the temporary inserted AI nodes
|
||||
Future<void> deleteAINodes() async {
|
||||
final nodes = getInsertedNodes();
|
||||
final transaction = editorState.transaction..deleteNodes(nodes);
|
||||
await editorState.apply(
|
||||
transaction,
|
||||
options: const ApplyOptions(recordUndo: false),
|
||||
);
|
||||
}
|
||||
|
||||
/// Discard the inserted content
|
||||
Future<void> discard() async {
|
||||
final start = _insertPosition;
|
||||
@@ -282,6 +313,194 @@ class MarkdownTextRobot {
|
||||
children: children,
|
||||
);
|
||||
}
|
||||
|
||||
/// If the selected content is in the same line,
|
||||
/// keep the selected node and replace the delta.
|
||||
Future<void> _replaceInSameLine({
|
||||
required Selection selection,
|
||||
required String markdownText,
|
||||
}) async {
|
||||
if (markdownText.isEmpty) {
|
||||
assert(false, 'Expected non-empty markdown text');
|
||||
Log.error('Expected non-empty markdown text');
|
||||
return;
|
||||
}
|
||||
|
||||
selection = selection.normalized;
|
||||
|
||||
// If the selection is not a single node, do nothing.
|
||||
if (!selection.isSingle) {
|
||||
assert(false, 'Expected single node selection');
|
||||
Log.error('Expected single node selection');
|
||||
return;
|
||||
}
|
||||
|
||||
final startIndex = selection.startIndex;
|
||||
final endIndex = selection.endIndex;
|
||||
final length = endIndex - startIndex;
|
||||
|
||||
// Get the selected node.
|
||||
final node = editorState.getNodeAtPath(selection.start.path);
|
||||
final delta = node?.delta;
|
||||
if (node == null || delta == null) {
|
||||
assert(false, 'Expected non-null node and delta');
|
||||
Log.error('Expected non-null node and delta');
|
||||
return;
|
||||
}
|
||||
|
||||
// Convert the markdown text to delta.
|
||||
// Question: Why we need to convert the markdown to document first?
|
||||
// Answer: Because the markdown text may contain the list item,
|
||||
// if we convert the markdown to delta directly, the list item will be
|
||||
// treated as a normal text node, and the delta will be incorrect.
|
||||
// For example, the markdown text is:
|
||||
// ```
|
||||
// 1. item1
|
||||
// ```
|
||||
// if we convert the markdown to delta directly, the delta will be:
|
||||
// ```
|
||||
// [
|
||||
// {
|
||||
// "insert": "1. item1"
|
||||
// }
|
||||
// ]
|
||||
// ```
|
||||
// if we convert the markdown to document first, the document will be:
|
||||
// ```
|
||||
// [
|
||||
// {
|
||||
// "type": "numbered_list",
|
||||
// "children": [
|
||||
// {
|
||||
// "insert": "item1"
|
||||
// }
|
||||
// ]
|
||||
// }
|
||||
// ]
|
||||
final document = customMarkdownToDocument(markdownText);
|
||||
final nodes = document.root.children;
|
||||
final decoder = DeltaMarkdownDecoder();
|
||||
final markdownDelta =
|
||||
nodes.firstOrNull?.delta ?? decoder.convert(markdownText);
|
||||
|
||||
if (markdownDelta.isEmpty) {
|
||||
assert(false, 'Expected non-empty markdown delta');
|
||||
Log.error('Expected non-empty markdown delta');
|
||||
return;
|
||||
}
|
||||
|
||||
// Replace the delta of the selected node.
|
||||
final transaction = editorState.transaction;
|
||||
|
||||
// it means the user selected the entire sentence, we just replace the node
|
||||
if (startIndex == 0 && length == node.delta?.length) {
|
||||
transaction
|
||||
..insertNodes(node.path.next, nodes)
|
||||
..deleteNode(node);
|
||||
} else {
|
||||
// it means the user selected a part of the sentence, we need to delete the
|
||||
// selected part and insert the new delta.
|
||||
transaction
|
||||
..deleteText(node, startIndex, length)
|
||||
..insertTextDelta(node, startIndex, markdownDelta);
|
||||
|
||||
// Add the remaining nodes to the document.
|
||||
final remainingNodes = nodes.skip(1);
|
||||
if (remainingNodes.isNotEmpty) {
|
||||
transaction.insertNodes(
|
||||
node.path.next,
|
||||
remainingNodes,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
await editorState.apply(transaction);
|
||||
}
|
||||
|
||||
/// If the selected content is in multiple lines
|
||||
Future<void> _replaceInMultiLines({
|
||||
required Selection selection,
|
||||
required String markdownText,
|
||||
}) async {
|
||||
selection = selection.normalized;
|
||||
|
||||
// If the selection is a single node, do nothing.
|
||||
if (selection.isSingle) {
|
||||
assert(false, 'Expected multi-line selection');
|
||||
Log.error('Expected multi-line selection');
|
||||
return;
|
||||
}
|
||||
|
||||
final markdownNodes = customMarkdownToDocument(
|
||||
markdownText,
|
||||
tableWidth: 250.0,
|
||||
).root.children;
|
||||
|
||||
// Get the selected nodes.
|
||||
final nodes = editorState.getNodesInSelection(selection);
|
||||
|
||||
// Note: Don't change its order, otherwise the delta will be incorrect.
|
||||
// step 1. merge the first selected node and the first node from the ai response
|
||||
// step 2. merge the last selected node and the last node from the ai response
|
||||
// step 3. insert the middle nodes from the ai response
|
||||
// step 4. delete the middle nodes
|
||||
final transaction = editorState.transaction;
|
||||
|
||||
// step 1
|
||||
final firstNode = nodes.firstOrNull;
|
||||
final delta = firstNode?.delta;
|
||||
final firstMarkdownNode = markdownNodes.firstOrNull;
|
||||
final firstMarkdownDelta = firstMarkdownNode?.delta;
|
||||
if (firstNode != null &&
|
||||
delta != null &&
|
||||
firstMarkdownNode != null &&
|
||||
firstMarkdownDelta != null) {
|
||||
final startIndex = selection.startIndex;
|
||||
final length = delta.length - startIndex;
|
||||
|
||||
transaction
|
||||
..deleteText(firstNode, startIndex, length)
|
||||
..insertTextDelta(firstNode, startIndex, firstMarkdownDelta);
|
||||
}
|
||||
|
||||
// step 2
|
||||
final lastNode = nodes.lastOrNull;
|
||||
final lastDelta = lastNode?.delta;
|
||||
final lastMarkdownNode = markdownNodes.lastOrNull;
|
||||
final lastMarkdownDelta = lastMarkdownNode?.delta;
|
||||
if (lastNode != null &&
|
||||
lastDelta != null &&
|
||||
lastMarkdownNode != null &&
|
||||
lastMarkdownDelta != null) {
|
||||
final endIndex = selection.endIndex;
|
||||
|
||||
transaction.deleteText(lastNode, 0, endIndex);
|
||||
|
||||
// if the last node is same as the first node, it means we have replaced the
|
||||
// selected text in the first node.
|
||||
if (lastMarkdownNode.id != firstMarkdownNode?.id) {
|
||||
transaction.insertTextDelta(lastNode, 0, lastMarkdownDelta);
|
||||
}
|
||||
}
|
||||
|
||||
// step 3
|
||||
final insertedPath = selection.start.path.nextNPath(1);
|
||||
if (markdownNodes.length > 2) {
|
||||
transaction.insertNodes(
|
||||
insertedPath,
|
||||
markdownNodes.skip(1).take(markdownNodes.length - 2).toList(),
|
||||
);
|
||||
}
|
||||
|
||||
// step 4
|
||||
final length = nodes.length - 2;
|
||||
if (length > 0) {
|
||||
final middleNodes = nodes.skip(1).take(length).toList();
|
||||
transaction.deleteNodes(middleNodes);
|
||||
}
|
||||
|
||||
await editorState.apply(transaction);
|
||||
}
|
||||
}
|
||||
|
||||
class AINodeExternalValues extends NodeExternalValues {
|
||||
|
||||
@@ -104,10 +104,11 @@ Future<void> downloadMediaFile(
|
||||
await afLaunchUrlString(file.url);
|
||||
} else {
|
||||
if (userProfile == null) {
|
||||
return showToastNotification(
|
||||
showToastNotification(
|
||||
context,
|
||||
message: LocaleKeys.grid_media_downloadFailedToken.tr(),
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
final uri = Uri.parse(file.url);
|
||||
|
||||
@@ -568,7 +568,6 @@ class EditorStyleCustomizer {
|
||||
if (style == null) {
|
||||
return null;
|
||||
}
|
||||
final fontSize = style.fontSize ?? 14.0;
|
||||
final isLight = Theme.of(context).isLightMode;
|
||||
final textColor = isLight ? Color(0xFF007296) : Color(0xFF49CFF4);
|
||||
final underlineColor = isLight ? Color(0x33005A7A) : Color(0x3349CFF4);
|
||||
@@ -578,17 +577,10 @@ class EditorStyleCustomizer {
|
||||
decoration: TextDecoration.lineThrough,
|
||||
),
|
||||
AiWriterBlockKeys.suggestionReplacement => style.copyWith(
|
||||
color: Colors.transparent,
|
||||
color: textColor,
|
||||
decoration: TextDecoration.underline,
|
||||
decorationColor: underlineColor,
|
||||
decorationThickness: 1.0,
|
||||
// hack: https://jtmuller5.medium.com/the-ultimate-guide-to-underlining-text-in-flutter-57936f5c79bb
|
||||
shadows: [
|
||||
Shadow(
|
||||
color: textColor,
|
||||
offset: Offset(0, -fontSize * 0.2),
|
||||
),
|
||||
],
|
||||
),
|
||||
_ => style,
|
||||
};
|
||||
|
||||
@@ -38,10 +38,6 @@ Future<bool> emojiCommandHandler(
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!selection.isCollapsed) {
|
||||
await editorState.deleteSelection(selection);
|
||||
}
|
||||
|
||||
final node = editorState.getNodeAtPath(selection.end.path);
|
||||
final delta = node?.delta;
|
||||
if (node == null ||
|
||||
@@ -58,6 +54,8 @@ Future<bool> emojiCommandHandler(
|
||||
if (previousCharacter != _emojiCharacter) return false;
|
||||
if (!context.mounted) return false;
|
||||
|
||||
if (!selection.isCollapsed) return false;
|
||||
|
||||
await editorState.insertTextAtPosition(
|
||||
character,
|
||||
position: selection.start,
|
||||
|
||||
@@ -27,6 +27,7 @@ Future<String> customDocumentToMarkdown(
|
||||
Document document, {
|
||||
String path = '',
|
||||
AsyncValueSetter<Archive>? onArchive,
|
||||
String lineBreak = '',
|
||||
}) async {
|
||||
final List<Future<ArchiveFile>> fileFutures = [];
|
||||
|
||||
@@ -41,6 +42,7 @@ Future<String> customDocumentToMarkdown(
|
||||
try {
|
||||
markdown = documentToMarkdown(
|
||||
document,
|
||||
lineBreak: lineBreak,
|
||||
customParsers: [
|
||||
const MathEquationNodeParser(),
|
||||
const CalloutNodeParser(),
|
||||
|
||||
@@ -19,6 +19,7 @@ import 'package:appflowy/workspace/application/view/view_ext.dart';
|
||||
import 'package:appflowy/workspace/presentation/command_palette/command_palette.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-folder/view.pb.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-user/protobuf.dart';
|
||||
import 'package:appflowy_ui/appflowy_ui.dart';
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flowy_infra/theme.dart';
|
||||
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
|
||||
@@ -230,14 +231,19 @@ class _ApplicationWidgetState extends State<ApplicationWidget> {
|
||||
data: MediaQuery.of(context).copyWith(
|
||||
textScaler: TextScaler.linear(state.textScaleFactor),
|
||||
),
|
||||
child: overlayManagerBuilder(
|
||||
context,
|
||||
!UniversalPlatform.isMobile && FeatureFlag.search.isOn
|
||||
? CommandPalette(
|
||||
notifier: _commandPaletteNotifier,
|
||||
child: child,
|
||||
)
|
||||
: child,
|
||||
child: AppFlowyTheme(
|
||||
data: Theme.of(context).brightness == Brightness.light
|
||||
? AppFlowyThemeData.light()
|
||||
: AppFlowyThemeData.dark(),
|
||||
child: overlayManagerBuilder(
|
||||
context,
|
||||
!UniversalPlatform.isMobile && FeatureFlag.search.isOn
|
||||
? CommandPalette(
|
||||
notifier: _commandPaletteNotifier,
|
||||
child: child,
|
||||
)
|
||||
: child,
|
||||
),
|
||||
),
|
||||
),
|
||||
debugShowCheckedModeBanner: false,
|
||||
|
||||
@@ -8,10 +8,15 @@ import 'package:appflowy/startup/startup.dart';
|
||||
class WindowSizeManager {
|
||||
static const double minWindowHeight = 640.0;
|
||||
static const double minWindowWidth = 640.0;
|
||||
|
||||
// Preventing failed assertion due to Texture Descriptor Validation
|
||||
static const double maxWindowHeight = 8192.0;
|
||||
static const double maxWindowWidth = 8192.0;
|
||||
|
||||
// Default windows size
|
||||
static const double defaultWindowHeight = 960.0;
|
||||
static const double defaultWindowWidth = 1280.0;
|
||||
|
||||
static const double maxScaleFactor = 2.0;
|
||||
static const double minScaleFactor = 0.5;
|
||||
|
||||
@@ -36,8 +41,8 @@ class WindowSizeManager {
|
||||
Future<Size> getSize() async {
|
||||
final defaultWindowSize = jsonEncode(
|
||||
{
|
||||
WindowSizeManager.height: minWindowHeight,
|
||||
WindowSizeManager.width: minWindowWidth,
|
||||
WindowSizeManager.height: defaultWindowHeight,
|
||||
WindowSizeManager.width: defaultWindowWidth,
|
||||
},
|
||||
);
|
||||
final windowSize = await getIt<KeyValueStorage>().get(KVKeys.windowSize);
|
||||
|
||||
@@ -83,6 +83,13 @@ class AppFlowyCloudDeepLink {
|
||||
void unsubscribeDeepLinkLoadingState(VoidCallback listener) =>
|
||||
_stateNotifier?.removeListener(listener);
|
||||
|
||||
Future<void> passGotrueTokenResponse(
|
||||
GotrueTokenResponsePB gotrueTokenResponse,
|
||||
) async {
|
||||
final uri = _buildDeepLinkUri(gotrueTokenResponse);
|
||||
await _handleUri(uri);
|
||||
}
|
||||
|
||||
Future<void> _handleUri(
|
||||
Uri? uri,
|
||||
) async {
|
||||
@@ -173,6 +180,57 @@ class AppFlowyCloudDeepLink {
|
||||
bool _isPaymentSuccessUri(Uri uri) {
|
||||
return uri.host == 'payment-success';
|
||||
}
|
||||
|
||||
Uri? _buildDeepLinkUri(GotrueTokenResponsePB gotrueTokenResponse) {
|
||||
final params = <String, String>{};
|
||||
|
||||
if (gotrueTokenResponse.hasAccessToken() &&
|
||||
gotrueTokenResponse.accessToken.isNotEmpty) {
|
||||
params['access_token'] = gotrueTokenResponse.accessToken;
|
||||
}
|
||||
|
||||
if (gotrueTokenResponse.hasExpiresAt()) {
|
||||
params['expires_at'] = gotrueTokenResponse.expiresAt.toString();
|
||||
}
|
||||
|
||||
if (gotrueTokenResponse.hasExpiresIn()) {
|
||||
params['expires_in'] = gotrueTokenResponse.expiresIn.toString();
|
||||
}
|
||||
|
||||
if (gotrueTokenResponse.hasProviderRefreshToken() &&
|
||||
gotrueTokenResponse.providerRefreshToken.isNotEmpty) {
|
||||
params['provider_refresh_token'] =
|
||||
gotrueTokenResponse.providerRefreshToken;
|
||||
}
|
||||
|
||||
if (gotrueTokenResponse.hasProviderAccessToken() &&
|
||||
gotrueTokenResponse.providerAccessToken.isNotEmpty) {
|
||||
params['provider_token'] = gotrueTokenResponse.providerAccessToken;
|
||||
}
|
||||
|
||||
if (gotrueTokenResponse.hasRefreshToken() &&
|
||||
gotrueTokenResponse.refreshToken.isNotEmpty) {
|
||||
params['refresh_token'] = gotrueTokenResponse.refreshToken;
|
||||
}
|
||||
|
||||
if (gotrueTokenResponse.hasTokenType() &&
|
||||
gotrueTokenResponse.tokenType.isNotEmpty) {
|
||||
params['token_type'] = gotrueTokenResponse.tokenType;
|
||||
}
|
||||
|
||||
if (params.isEmpty) {
|
||||
return null;
|
||||
}
|
||||
|
||||
final fragment = params.entries
|
||||
.map(
|
||||
(e) =>
|
||||
'${Uri.encodeComponent(e.key)}=${Uri.encodeComponent(e.value)}',
|
||||
)
|
||||
.join('&');
|
||||
|
||||
return Uri.parse('appflowy-flutter://login-callback#$fragment');
|
||||
}
|
||||
}
|
||||
|
||||
class InitAppFlowyCloudTask extends LaunchTask {
|
||||
|
||||
@@ -32,12 +32,17 @@ class AppFlowyCloudAuthService implements AuthService {
|
||||
}
|
||||
|
||||
@override
|
||||
Future<FlowyResult<UserProfilePB, FlowyError>> signInWithEmailPassword({
|
||||
Future<FlowyResult<GotrueTokenResponsePB, FlowyError>>
|
||||
signInWithEmailPassword({
|
||||
required String email,
|
||||
required String password,
|
||||
Map<String, String> params = const {},
|
||||
}) async {
|
||||
throw UnimplementedError();
|
||||
return _backendAuthService.signInWithEmailPassword(
|
||||
email: email,
|
||||
password: password,
|
||||
params: params,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
@@ -106,6 +111,17 @@ class AppFlowyCloudAuthService implements AuthService {
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<FlowyResult<GotrueTokenResponsePB, FlowyError>> signInWithPasscode({
|
||||
required String email,
|
||||
required String passcode,
|
||||
}) async {
|
||||
return _backendAuthService.signInWithPasscode(
|
||||
email: email,
|
||||
passcode: passcode,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<FlowyResult<UserProfilePB, FlowyError>> getUser() async {
|
||||
return UserBackendService.getCurrentUserProfile();
|
||||
|
||||
@@ -33,7 +33,8 @@ class AppFlowyCloudMockAuthService implements AuthService {
|
||||
}
|
||||
|
||||
@override
|
||||
Future<FlowyResult<UserProfilePB, FlowyError>> signInWithEmailPassword({
|
||||
Future<FlowyResult<GotrueTokenResponsePB, FlowyError>>
|
||||
signInWithEmailPassword({
|
||||
required String email,
|
||||
required String password,
|
||||
Map<String, String> params = const {},
|
||||
@@ -106,4 +107,12 @@ class AppFlowyCloudMockAuthService implements AuthService {
|
||||
Future<FlowyResult<UserProfilePB, FlowyError>> getUser() async {
|
||||
return UserBackendService.getCurrentUserProfile();
|
||||
}
|
||||
|
||||
@override
|
||||
Future<FlowyResult<GotrueTokenResponsePB, FlowyError>> signInWithPasscode({
|
||||
required String email,
|
||||
required String passcode,
|
||||
}) async {
|
||||
throw UnimplementedError();
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
import 'package:appflowy_backend/protobuf/flowy-error/errors.pb.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-user/user_profile.pbserver.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-user/protobuf.dart';
|
||||
import 'package:appflowy_result/appflowy_result.dart';
|
||||
|
||||
class AuthServiceMapKeys {
|
||||
@@ -23,7 +23,8 @@ abstract class AuthService {
|
||||
///
|
||||
/// Returns [UserProfilePB] if the user is authenticated, otherwise returns [FlowyError].
|
||||
|
||||
Future<FlowyResult<UserProfilePB, FlowyError>> signInWithEmailPassword({
|
||||
Future<FlowyResult<GotrueTokenResponsePB, FlowyError>>
|
||||
signInWithEmailPassword({
|
||||
required String email,
|
||||
required String password,
|
||||
Map<String, String> params,
|
||||
@@ -75,6 +76,17 @@ abstract class AuthService {
|
||||
Map<String, String> params,
|
||||
});
|
||||
|
||||
/// Authenticates a user with a passcode sent to their email.
|
||||
///
|
||||
/// - `email`: The email address of the user.
|
||||
/// - `passcode`: The passcode of the user.
|
||||
///
|
||||
/// Returns [UserProfilePB] if the user is authenticated, otherwise returns [FlowyError].
|
||||
Future<FlowyResult<GotrueTokenResponsePB, FlowyError>> signInWithPasscode({
|
||||
required String email,
|
||||
required String passcode,
|
||||
});
|
||||
|
||||
/// Signs out the currently authenticated user.
|
||||
Future<void> signOut();
|
||||
|
||||
|
||||
@@ -19,7 +19,8 @@ class BackendAuthService implements AuthService {
|
||||
final AuthenticatorPB authType;
|
||||
|
||||
@override
|
||||
Future<FlowyResult<UserProfilePB, FlowyError>> signInWithEmailPassword({
|
||||
Future<FlowyResult<GotrueTokenResponsePB, FlowyError>>
|
||||
signInWithEmailPassword({
|
||||
required String email,
|
||||
required String password,
|
||||
Map<String, String> params = const {},
|
||||
@@ -29,8 +30,7 @@ class BackendAuthService implements AuthService {
|
||||
..password = password
|
||||
..authType = authType
|
||||
..deviceId = await getDeviceId();
|
||||
final response = UserEventSignInWithEmailPassword(request).send();
|
||||
return response.then((value) => value);
|
||||
return UserEventSignInWithEmailPassword(request).send();
|
||||
}
|
||||
|
||||
@override
|
||||
@@ -107,4 +107,12 @@ class BackendAuthService implements AuthService {
|
||||
// No need to pass the redirect URL.
|
||||
return UserBackendService.signInWithMagicLink(email, '');
|
||||
}
|
||||
|
||||
@override
|
||||
Future<FlowyResult<GotrueTokenResponsePB, FlowyError>> signInWithPasscode({
|
||||
required String email,
|
||||
required String passcode,
|
||||
}) async {
|
||||
return UserBackendService.signInWithPasscode(email, passcode);
|
||||
}
|
||||
}
|
||||
@@ -30,12 +30,26 @@ class SignInBloc extends Bloc<SignInEvent, SignInState> {
|
||||
on<SignInEvent>(
|
||||
(event, emit) async {
|
||||
await event.when(
|
||||
signedInWithUserEmailAndPassword: () async => _onSignIn(emit),
|
||||
signedInWithOAuth: (platform) async =>
|
||||
_onSignInWithOAuth(emit, platform),
|
||||
signedInAsGuest: () async => _onSignInAsGuest(emit),
|
||||
signedWithMagicLink: (email) async =>
|
||||
_onSignInWithMagicLink(emit, email),
|
||||
signInWithEmailAndPassword: (email, password) async =>
|
||||
_onSignInWithEmailAndPassword(
|
||||
emit,
|
||||
email: email,
|
||||
password: password,
|
||||
),
|
||||
signInWithOAuth: (platform) async => _onSignInWithOAuth(
|
||||
emit,
|
||||
platform: platform,
|
||||
),
|
||||
signInAsGuest: () async => _onSignInAsGuest(emit),
|
||||
signInWithMagicLink: (email) async => _onSignInWithMagicLink(
|
||||
emit,
|
||||
email: email,
|
||||
),
|
||||
signInWithPasscode: (email, passcode) async => _onSignInWithPasscode(
|
||||
emit,
|
||||
email: email,
|
||||
passcode: passcode,
|
||||
),
|
||||
deepLinkStateChange: (result) => _onDeepLinkStateChange(emit, result),
|
||||
cancel: () {
|
||||
emit(
|
||||
@@ -119,26 +133,34 @@ class SignInBloc extends Bloc<SignInEvent, SignInState> {
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _onSignIn(Emitter<SignInState> emit) async {
|
||||
Future<void> _onSignInWithEmailAndPassword(
|
||||
Emitter<SignInState> emit, {
|
||||
required String email,
|
||||
required String password,
|
||||
}) async {
|
||||
final result = await authService.signInWithEmailPassword(
|
||||
email: state.email ?? '',
|
||||
password: state.password ?? '',
|
||||
email: email,
|
||||
password: password,
|
||||
);
|
||||
emit(
|
||||
result.fold(
|
||||
(userProfile) => state.copyWith(
|
||||
isSubmitting: false,
|
||||
successOrFail: FlowyResult.success(userProfile),
|
||||
),
|
||||
(gotrueTokenResponse) {
|
||||
getIt<AppFlowyCloudDeepLink>().passGotrueTokenResponse(
|
||||
gotrueTokenResponse,
|
||||
);
|
||||
return state.copyWith(
|
||||
isSubmitting: false,
|
||||
);
|
||||
},
|
||||
(error) => _stateFromCode(error),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> _onSignInWithOAuth(
|
||||
Emitter<SignInState> emit,
|
||||
String platform,
|
||||
) async {
|
||||
Emitter<SignInState> emit, {
|
||||
required String platform,
|
||||
}) async {
|
||||
emit(
|
||||
state.copyWith(
|
||||
isSubmitting: true,
|
||||
@@ -161,9 +183,9 @@ class SignInBloc extends Bloc<SignInEvent, SignInState> {
|
||||
}
|
||||
|
||||
Future<void> _onSignInWithMagicLink(
|
||||
Emitter<SignInState> emit,
|
||||
String email,
|
||||
) async {
|
||||
Emitter<SignInState> emit, {
|
||||
required String email,
|
||||
}) async {
|
||||
emit(
|
||||
state.copyWith(
|
||||
isSubmitting: true,
|
||||
@@ -183,6 +205,40 @@ class SignInBloc extends Bloc<SignInEvent, SignInState> {
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> _onSignInWithPasscode(
|
||||
Emitter<SignInState> emit, {
|
||||
required String email,
|
||||
required String passcode,
|
||||
}) async {
|
||||
emit(
|
||||
state.copyWith(
|
||||
isSubmitting: true,
|
||||
emailError: null,
|
||||
passwordError: null,
|
||||
successOrFail: null,
|
||||
),
|
||||
);
|
||||
|
||||
final result = await authService.signInWithPasscode(
|
||||
email: email,
|
||||
passcode: passcode,
|
||||
);
|
||||
|
||||
emit(
|
||||
result.fold(
|
||||
(gotrueTokenResponse) {
|
||||
getIt<AppFlowyCloudDeepLink>().passGotrueTokenResponse(
|
||||
gotrueTokenResponse,
|
||||
);
|
||||
return state.copyWith(
|
||||
isSubmitting: false,
|
||||
);
|
||||
},
|
||||
(error) => _stateFromCode(error),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> _onSignInAsGuest(
|
||||
Emitter<SignInState> emit,
|
||||
) async {
|
||||
@@ -224,10 +280,17 @@ class SignInBloc extends Bloc<SignInEvent, SignInState> {
|
||||
emailError: null,
|
||||
);
|
||||
case ErrorCode.UserUnauthorized:
|
||||
final errorMsg = error.msg;
|
||||
String msg = LocaleKeys.signIn_generalError.tr();
|
||||
if (errorMsg.contains('rate limit')) {
|
||||
msg = LocaleKeys.signIn_limitRateError.tr();
|
||||
} else if (errorMsg.contains('invalid')) {
|
||||
msg = LocaleKeys.signIn_tokenHasExpiredOrInvalid.tr();
|
||||
}
|
||||
return state.copyWith(
|
||||
isSubmitting: false,
|
||||
successOrFail: FlowyResult.failure(
|
||||
FlowyError(msg: LocaleKeys.signIn_limitRateError.tr()),
|
||||
FlowyError(msg: msg),
|
||||
),
|
||||
);
|
||||
default:
|
||||
@@ -243,19 +306,35 @@ class SignInBloc extends Bloc<SignInEvent, SignInState> {
|
||||
|
||||
@freezed
|
||||
class SignInEvent with _$SignInEvent {
|
||||
const factory SignInEvent.signedInWithUserEmailAndPassword() =
|
||||
SignedInWithUserEmailAndPassword;
|
||||
const factory SignInEvent.signedInWithOAuth(String platform) =
|
||||
SignedInWithOAuth;
|
||||
const factory SignInEvent.signedInAsGuest() = SignedInAsGuest;
|
||||
const factory SignInEvent.signedWithMagicLink(String email) =
|
||||
SignedWithMagicLink;
|
||||
const factory SignInEvent.emailChanged(String email) = EmailChanged;
|
||||
const factory SignInEvent.passwordChanged(String password) = PasswordChanged;
|
||||
// Sign in methods
|
||||
const factory SignInEvent.signInWithEmailAndPassword({
|
||||
required String email,
|
||||
required String password,
|
||||
}) = SignInWithEmailAndPassword;
|
||||
const factory SignInEvent.signInWithOAuth({
|
||||
required String platform,
|
||||
}) = SignInWithOAuth;
|
||||
const factory SignInEvent.signInAsGuest() = SignInAsGuest;
|
||||
const factory SignInEvent.signInWithMagicLink({
|
||||
required String email,
|
||||
}) = SignInWithMagicLink;
|
||||
const factory SignInEvent.signInWithPasscode({
|
||||
required String email,
|
||||
required String passcode,
|
||||
}) = SignInWithPasscode;
|
||||
|
||||
// Event handlers
|
||||
const factory SignInEvent.emailChanged({
|
||||
required String email,
|
||||
}) = EmailChanged;
|
||||
const factory SignInEvent.passwordChanged({
|
||||
required String password,
|
||||
}) = PasswordChanged;
|
||||
const factory SignInEvent.deepLinkStateChange(DeepLinkResult result) =
|
||||
DeepLinkStateChange;
|
||||
const factory SignInEvent.cancel() = _Cancel;
|
||||
const factory SignInEvent.switchLoginType(LoginType type) = _SwitchLoginType;
|
||||
|
||||
const factory SignInEvent.cancel() = Cancel;
|
||||
const factory SignInEvent.switchLoginType(LoginType type) = SwitchLoginType;
|
||||
}
|
||||
|
||||
// we support sign in directly without sign up, but we want to allow the users to sign up if they want to
|
||||
|
||||
@@ -86,6 +86,15 @@ class UserBackendService implements IUserBackendService {
|
||||
return UserEventMagicLinkSignIn(payload).send();
|
||||
}
|
||||
|
||||
static Future<FlowyResult<GotrueTokenResponsePB, FlowyError>>
|
||||
signInWithPasscode(
|
||||
String email,
|
||||
String passcode,
|
||||
) async {
|
||||
final payload = PasscodeSignInPB(email: email, passcode: passcode);
|
||||
return UserEventPasscodeSignIn(payload).send();
|
||||
}
|
||||
|
||||
static Future<FlowyResult<void, FlowyError>> signOut() {
|
||||
return UserEventSignOut().send();
|
||||
}
|
||||
|
||||
@@ -1,11 +1,13 @@
|
||||
import 'package:appflowy/core/frameless_window.dart';
|
||||
import 'package:appflowy/env/cloud_env.dart';
|
||||
import 'package:appflowy/generated/flowy_svgs.g.dart';
|
||||
import 'package:appflowy/generated/locale_keys.g.dart';
|
||||
import 'package:appflowy/shared/settings/show_settings.dart';
|
||||
import 'package:appflowy/shared/window_title_bar.dart';
|
||||
import 'package:appflowy/user/application/sign_in_bloc.dart';
|
||||
import 'package:appflowy/user/presentation/screens/sign_in_screen/widgets/widgets.dart';
|
||||
import 'package:appflowy/user/presentation/widgets/widgets.dart';
|
||||
import 'package:appflowy_ui/appflowy_ui.dart';
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
@@ -19,6 +21,8 @@ class DesktopSignInScreen extends StatelessWidget {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final theme = AppFlowyTheme.of(context);
|
||||
|
||||
const indicatorMinHeight = 4.0;
|
||||
return BlocBuilder<SignInBloc, SignInState>(
|
||||
builder: (context, state) {
|
||||
@@ -29,25 +33,23 @@ class DesktopSignInScreen extends StatelessWidget {
|
||||
children: [
|
||||
const Spacer(),
|
||||
|
||||
const VSpace(20),
|
||||
|
||||
// logo and title
|
||||
FlowyLogoTitle(
|
||||
title: LocaleKeys.welcomeText.tr(),
|
||||
logoSize: const Size(60, 60),
|
||||
logoSize: Size.square(36),
|
||||
),
|
||||
const VSpace(20),
|
||||
VSpace(theme.spacing.xxl),
|
||||
|
||||
// magic link sign in
|
||||
const SignInWithMagicLinkButtons(),
|
||||
const VSpace(20),
|
||||
// continue with email and password
|
||||
const ContinueWithEmailAndPassword(),
|
||||
VSpace(theme.spacing.xxl),
|
||||
|
||||
// third-party sign in.
|
||||
if (isAuthEnabled) ...[
|
||||
const _OrDivider(),
|
||||
const VSpace(20),
|
||||
VSpace(theme.spacing.xxl),
|
||||
const ThirdPartySignInButtons(),
|
||||
const VSpace(20),
|
||||
VSpace(theme.spacing.xxl),
|
||||
],
|
||||
|
||||
// sign in agreement
|
||||
@@ -69,7 +71,7 @@ class DesktopSignInScreen extends StatelessWidget {
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
DesktopSignInSettingsButton(),
|
||||
HSpace(42),
|
||||
HSpace(20),
|
||||
SignInAnonymousButtonV2(),
|
||||
],
|
||||
),
|
||||
@@ -99,18 +101,24 @@ class DesktopSignInSettingsButton extends StatelessWidget {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return FlowyButton(
|
||||
useIntrinsicWidth: true,
|
||||
text: FlowyText(
|
||||
LocaleKeys.signIn_settings.tr(),
|
||||
textAlign: TextAlign.center,
|
||||
fontSize: 12.0,
|
||||
// fontWeight: FontWeight.w500,
|
||||
color: Colors.grey,
|
||||
decoration: TextDecoration.underline,
|
||||
final theme = AppFlowyTheme.of(context);
|
||||
return AFGhostIconTextButton(
|
||||
text: LocaleKeys.signIn_settings.tr(),
|
||||
textColor: (context, isHovering, disabled) {
|
||||
return theme.textColorScheme.secondary;
|
||||
},
|
||||
size: AFButtonSize.s,
|
||||
padding: EdgeInsets.symmetric(
|
||||
horizontal: theme.spacing.m,
|
||||
vertical: theme.spacing.xs,
|
||||
),
|
||||
onTap: () {
|
||||
showSimpleSettingsDialog(context);
|
||||
onTap: () => showSimpleSettingsDialog(context),
|
||||
iconBuilder: (context, isHovering, disabled) {
|
||||
return FlowySvg(
|
||||
FlowySvgs.settings_s,
|
||||
size: Size.square(20),
|
||||
color: theme.textColorScheme.secondary,
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
@@ -121,14 +129,30 @@ class _OrDivider extends StatelessWidget {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final theme = AppFlowyTheme.of(context);
|
||||
return Row(
|
||||
children: [
|
||||
const Flexible(child: Divider(thickness: 1)),
|
||||
Flexible(
|
||||
child: Divider(
|
||||
thickness: 1,
|
||||
color: theme.borderColorScheme.greyTertiary,
|
||||
),
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 10),
|
||||
child: FlowyText.regular(LocaleKeys.signIn_or.tr()),
|
||||
child: Text(
|
||||
LocaleKeys.signIn_or.tr(),
|
||||
style: theme.textStyle.body.standard(
|
||||
color: theme.textColorScheme.secondary,
|
||||
),
|
||||
),
|
||||
),
|
||||
Flexible(
|
||||
child: Divider(
|
||||
thickness: 1,
|
||||
color: theme.borderColorScheme.greyTertiary,
|
||||
),
|
||||
),
|
||||
const Flexible(child: Divider(thickness: 1)),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
@@ -7,6 +7,7 @@ import 'package:appflowy/mobile/presentation/setting/launch_settings_page.dart';
|
||||
import 'package:appflowy/user/application/sign_in_bloc.dart';
|
||||
import 'package:appflowy/user/presentation/screens/sign_in_screen/widgets/anonymous_sign_in_button.dart';
|
||||
import 'package:appflowy/user/presentation/screens/sign_in_screen/widgets/widgets.dart';
|
||||
import 'package:appflowy_ui/appflowy_ui.dart';
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
@@ -37,7 +38,7 @@ class MobileSignInScreen extends StatelessWidget {
|
||||
const VSpace(spacing * 2),
|
||||
isLocalAuthEnabled
|
||||
? const SignInAnonymousButtonV3()
|
||||
: const SignInWithMagicLinkButtons(),
|
||||
: const ContinueWithEmailAndPassword(),
|
||||
const VSpace(spacing),
|
||||
if (isAuthEnabled) _buildThirdPartySignInButtons(colorScheme),
|
||||
const VSpace(spacing * 1.5),
|
||||
@@ -103,21 +104,28 @@ class MobileSignInScreen extends StatelessWidget {
|
||||
}
|
||||
|
||||
Widget _buildSettingsButton(BuildContext context) {
|
||||
final theme = AppFlowyTheme.of(context);
|
||||
|
||||
return Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
FlowyButton(
|
||||
useIntrinsicWidth: true,
|
||||
text: FlowyText(
|
||||
LocaleKeys.signIn_settings.tr(),
|
||||
textAlign: TextAlign.center,
|
||||
fontSize: 12.0,
|
||||
// fontWeight: FontWeight.w500,
|
||||
color: Colors.grey,
|
||||
decoration: TextDecoration.underline,
|
||||
AFGhostIconTextButton(
|
||||
text: LocaleKeys.signIn_settings.tr(),
|
||||
textColor: (context, isHovering, disabled) {
|
||||
return theme.textColorScheme.secondary;
|
||||
},
|
||||
size: AFButtonSize.s,
|
||||
padding: EdgeInsets.symmetric(
|
||||
horizontal: theme.spacing.m,
|
||||
vertical: theme.spacing.xs,
|
||||
),
|
||||
onTap: () {
|
||||
context.push(MobileLaunchSettingsPage.routeName);
|
||||
onTap: () => context.push(MobileLaunchSettingsPage.routeName),
|
||||
iconBuilder: (context, isHovering, disabled) {
|
||||
return FlowySvg(
|
||||
FlowySvgs.settings_s,
|
||||
size: Size.square(20),
|
||||
color: theme.textColorScheme.secondary,
|
||||
);
|
||||
},
|
||||
),
|
||||
const HSpace(24),
|
||||
|
||||
@@ -34,7 +34,7 @@ class SignInAnonymousButtonV3 extends StatelessWidget {
|
||||
? () {
|
||||
context
|
||||
.read<SignInBloc>()
|
||||
.add(const SignInEvent.signedInAsGuest());
|
||||
.add(const SignInEvent.signInAsGuest());
|
||||
}
|
||||
: () {
|
||||
final bloc = context.read<AnonUserBloc>();
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
import 'package:appflowy_ui/appflowy_ui.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class AnonymousSignInButton extends StatelessWidget {
|
||||
const AnonymousSignInButton({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return AFGhostButton.normal(
|
||||
onTap: () {},
|
||||
builder: (context, isHovering, disabled) {
|
||||
return const Placeholder();
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
import 'package:appflowy/generated/locale_keys.g.dart';
|
||||
import 'package:appflowy_ui/appflowy_ui.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
|
||||
class ContinueWithEmail extends StatelessWidget {
|
||||
const ContinueWithEmail({
|
||||
super.key,
|
||||
required this.onTap,
|
||||
});
|
||||
|
||||
final VoidCallback onTap;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return AFFilledTextButton.primary(
|
||||
text: LocaleKeys.signIn_continueWithEmail.tr(),
|
||||
size: AFButtonSize.l,
|
||||
alignment: Alignment.center,
|
||||
onTap: onTap,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,134 @@
|
||||
import 'package:appflowy/generated/locale_keys.g.dart';
|
||||
import 'package:appflowy/user/application/sign_in_bloc.dart';
|
||||
import 'package:appflowy/user/presentation/screens/sign_in_screen/widgets/continue_with/continue_with_email.dart';
|
||||
import 'package:appflowy/user/presentation/screens/sign_in_screen/widgets/continue_with/continue_with_magic_link_or_passcode_page.dart';
|
||||
import 'package:appflowy/workspace/presentation/widgets/dialogs.dart';
|
||||
import 'package:appflowy_ui/appflowy_ui.dart';
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:string_validator/string_validator.dart';
|
||||
import 'package:universal_platform/universal_platform.dart';
|
||||
|
||||
class ContinueWithEmailAndPassword extends StatefulWidget {
|
||||
const ContinueWithEmailAndPassword({super.key});
|
||||
|
||||
@override
|
||||
State<ContinueWithEmailAndPassword> createState() =>
|
||||
_ContinueWithEmailAndPasswordState();
|
||||
}
|
||||
|
||||
class _ContinueWithEmailAndPasswordState
|
||||
extends State<ContinueWithEmailAndPassword> {
|
||||
final controller = TextEditingController();
|
||||
final focusNode = FocusNode();
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
controller.dispose();
|
||||
focusNode.dispose();
|
||||
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final theme = AppFlowyTheme.of(context);
|
||||
|
||||
return Column(
|
||||
children: [
|
||||
SizedBox(
|
||||
height: UniversalPlatform.isMobile ? 38.0 : 40.0,
|
||||
child: AFTextField(
|
||||
controller: controller,
|
||||
hintText: LocaleKeys.signIn_pleaseInputYourEmail.tr(),
|
||||
radius: 10,
|
||||
onSubmitted: (value) => _pushContinueWithMagicLinkOrPasscodePage(
|
||||
context,
|
||||
value,
|
||||
),
|
||||
),
|
||||
),
|
||||
VSpace(theme.spacing.l),
|
||||
ContinueWithEmail(
|
||||
onTap: () => _pushContinueWithMagicLinkOrPasscodePage(
|
||||
context,
|
||||
controller.text,
|
||||
),
|
||||
),
|
||||
// Hide password sign in until we implement the reset password / forgot password
|
||||
// VSpace(theme.spacing.l),
|
||||
// ContinueWithPassword(
|
||||
// onTap: () => _pushContinueWithPasswordPage(
|
||||
// context,
|
||||
// controller.text,
|
||||
// ),
|
||||
// ),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
void _pushContinueWithMagicLinkOrPasscodePage(
|
||||
BuildContext context,
|
||||
String email,
|
||||
) {
|
||||
if (!isEmail(email)) {
|
||||
showToastNotification(
|
||||
context,
|
||||
message: LocaleKeys.signIn_invalidEmail.tr(),
|
||||
type: ToastificationType.error,
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
final signInBloc = context.read<SignInBloc>();
|
||||
|
||||
signInBloc.add(SignInEvent.signInWithMagicLink(email: email));
|
||||
|
||||
// push the a continue with magic link or passcode screen
|
||||
Navigator.push(
|
||||
context,
|
||||
MaterialPageRoute(
|
||||
builder: (context) => BlocProvider.value(
|
||||
value: signInBloc,
|
||||
child: ContinueWithMagicLinkOrPasscodePage(
|
||||
email: email,
|
||||
backToLogin: () => Navigator.pop(context),
|
||||
onEnterPasscode: (passcode) => signInBloc.add(
|
||||
SignInEvent.signInWithPasscode(
|
||||
email: email,
|
||||
passcode: passcode,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
// void _pushContinueWithPasswordPage(
|
||||
// BuildContext context,
|
||||
// String email,
|
||||
// ) {
|
||||
// final signInBloc = context.read<SignInBloc>();
|
||||
// Navigator.push(
|
||||
// context,
|
||||
// MaterialPageRoute(
|
||||
// builder: (context) => ContinueWithPasswordPage(
|
||||
// email: email,
|
||||
// backToLogin: () => Navigator.pop(context),
|
||||
// onEnterPassword: (password) => signInBloc.add(
|
||||
// SignInEvent.signInWithEmailAndPassword(
|
||||
// email: email,
|
||||
// password: password,
|
||||
// ),
|
||||
// ),
|
||||
// onForgotPassword: () {
|
||||
// // todo: implement forgot password
|
||||
// },
|
||||
// ),
|
||||
// ),
|
||||
// );
|
||||
// }
|
||||
}
|
||||
@@ -0,0 +1,188 @@
|
||||
import 'package:appflowy/generated/locale_keys.g.dart';
|
||||
import 'package:appflowy/user/application/sign_in_bloc.dart';
|
||||
import 'package:appflowy/user/presentation/screens/sign_in_screen/widgets/logo/logo.dart';
|
||||
import 'package:appflowy/workspace/presentation/widgets/dialogs.dart';
|
||||
import 'package:appflowy_ui/appflowy_ui.dart';
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flowy_infra_ui/widget/spacing.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
|
||||
class ContinueWithMagicLinkOrPasscodePage extends StatefulWidget {
|
||||
const ContinueWithMagicLinkOrPasscodePage({
|
||||
super.key,
|
||||
required this.backToLogin,
|
||||
required this.email,
|
||||
required this.onEnterPasscode,
|
||||
});
|
||||
|
||||
final String email;
|
||||
final VoidCallback backToLogin;
|
||||
final ValueChanged<String> onEnterPasscode;
|
||||
|
||||
@override
|
||||
State<ContinueWithMagicLinkOrPasscodePage> createState() =>
|
||||
_ContinueWithMagicLinkOrPasscodePageState();
|
||||
}
|
||||
|
||||
class _ContinueWithMagicLinkOrPasscodePageState
|
||||
extends State<ContinueWithMagicLinkOrPasscodePage> {
|
||||
final passcodeController = TextEditingController();
|
||||
|
||||
bool isEnteringPasscode = false;
|
||||
|
||||
ToastificationItem? toastificationItem;
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
passcodeController.dispose();
|
||||
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return BlocListener<SignInBloc, SignInState>(
|
||||
listener: (context, state) {
|
||||
if (state.isSubmitting) {
|
||||
_showLoadingDialog();
|
||||
} else {
|
||||
_dismissLoadingDialog();
|
||||
}
|
||||
},
|
||||
child: Scaffold(
|
||||
body: Center(
|
||||
child: SizedBox(
|
||||
width: 320,
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
// Logo, title and description
|
||||
..._buildLogoTitleAndDescription(),
|
||||
|
||||
// Enter code manually
|
||||
..._buildEnterCodeManually(),
|
||||
|
||||
// Back to login
|
||||
..._buildBackToLogin(),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
List<Widget> _buildEnterCodeManually() {
|
||||
// todo: ask designer to provide the spacing
|
||||
final spacing = VSpace(20);
|
||||
|
||||
if (!isEnteringPasscode) {
|
||||
return [
|
||||
AFFilledTextButton.primary(
|
||||
text: LocaleKeys.signIn_enterCodeManually.tr(),
|
||||
onTap: () => setState(() => isEnteringPasscode = true),
|
||||
size: AFButtonSize.l,
|
||||
alignment: Alignment.center,
|
||||
),
|
||||
spacing,
|
||||
];
|
||||
}
|
||||
|
||||
return [
|
||||
// Enter code manually
|
||||
SizedBox(
|
||||
height: 40,
|
||||
child: AFTextField(
|
||||
controller: passcodeController,
|
||||
hintText: LocaleKeys.signIn_enterCode.tr(),
|
||||
keyboardType: TextInputType.number,
|
||||
radius: 10,
|
||||
autoFocus: true,
|
||||
onSubmitted: widget.onEnterPasscode,
|
||||
),
|
||||
),
|
||||
// todo: ask designer to provide the spacing
|
||||
VSpace(12),
|
||||
|
||||
// continue to login
|
||||
AFFilledTextButton.primary(
|
||||
text: 'Continue to sign in',
|
||||
onTap: () => widget.onEnterPasscode(passcodeController.text),
|
||||
size: AFButtonSize.l,
|
||||
alignment: Alignment.center,
|
||||
),
|
||||
|
||||
spacing,
|
||||
];
|
||||
}
|
||||
|
||||
List<Widget> _buildBackToLogin() {
|
||||
return [
|
||||
AFGhostTextButton(
|
||||
text: LocaleKeys.signIn_backToLogin.tr(),
|
||||
size: AFButtonSize.s,
|
||||
onTap: widget.backToLogin,
|
||||
textColor: (context, isHovering, disabled) {
|
||||
final theme = AppFlowyTheme.of(context);
|
||||
if (isHovering) {
|
||||
return theme.fillColorScheme.themeThickHover;
|
||||
}
|
||||
return theme.textColorScheme.theme;
|
||||
},
|
||||
),
|
||||
];
|
||||
}
|
||||
|
||||
List<Widget> _buildLogoTitleAndDescription() {
|
||||
final theme = AppFlowyTheme.of(context);
|
||||
final spacing = VSpace(theme.spacing.xxl);
|
||||
return [
|
||||
// logo
|
||||
const AFLogo(),
|
||||
spacing,
|
||||
|
||||
// title
|
||||
Text(
|
||||
LocaleKeys.signIn_checkYourEmail.tr(),
|
||||
style: theme.textStyle.heading.h3(
|
||||
color: theme.textColorScheme.primary,
|
||||
),
|
||||
),
|
||||
spacing,
|
||||
|
||||
// description
|
||||
Text(
|
||||
LocaleKeys.signIn_temporaryVerificationSent.tr(),
|
||||
style: theme.textStyle.body.standard(
|
||||
color: theme.textColorScheme.primary,
|
||||
),
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
Text(
|
||||
widget.email,
|
||||
style: theme.textStyle.body.enhanced(
|
||||
color: theme.textColorScheme.primary,
|
||||
),
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
spacing,
|
||||
];
|
||||
}
|
||||
|
||||
void _showLoadingDialog() {
|
||||
_dismissLoadingDialog();
|
||||
|
||||
toastificationItem = showToastNotification(
|
||||
context,
|
||||
message: LocaleKeys.signIn_signingIn.tr(),
|
||||
);
|
||||
}
|
||||
|
||||
void _dismissLoadingDialog() {
|
||||
final toastificationItem = this.toastificationItem;
|
||||
if (toastificationItem != null) {
|
||||
toastification.dismiss(toastificationItem);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
import 'package:appflowy_ui/appflowy_ui.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class ContinueWithPassword extends StatelessWidget {
|
||||
const ContinueWithPassword({
|
||||
super.key,
|
||||
required this.onTap,
|
||||
});
|
||||
|
||||
final VoidCallback onTap;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return AFOutlinedTextButton.normal(
|
||||
text: 'Continue with password',
|
||||
size: AFButtonSize.l,
|
||||
alignment: Alignment.center,
|
||||
onTap: onTap,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,147 @@
|
||||
import 'package:appflowy/user/presentation/screens/sign_in_screen/widgets/logo/logo.dart';
|
||||
import 'package:appflowy_ui/appflowy_ui.dart';
|
||||
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
|
||||
import 'package:flowy_infra_ui/widget/spacing.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class ContinueWithPasswordPage extends StatefulWidget {
|
||||
const ContinueWithPasswordPage({
|
||||
super.key,
|
||||
required this.backToLogin,
|
||||
required this.email,
|
||||
required this.onEnterPassword,
|
||||
required this.onForgotPassword,
|
||||
});
|
||||
|
||||
final String email;
|
||||
final VoidCallback backToLogin;
|
||||
final ValueChanged<String> onEnterPassword;
|
||||
final VoidCallback onForgotPassword;
|
||||
|
||||
@override
|
||||
State<ContinueWithPasswordPage> createState() =>
|
||||
_ContinueWithPasswordPageState();
|
||||
}
|
||||
|
||||
class _ContinueWithPasswordPageState extends State<ContinueWithPasswordPage> {
|
||||
final passwordController = TextEditingController();
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
passwordController.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
body: Center(
|
||||
child: SizedBox(
|
||||
width: 320,
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
// Logo and title
|
||||
..._buildLogoAndTitle(),
|
||||
|
||||
// Password input and buttons
|
||||
..._buildPasswordSection(),
|
||||
|
||||
// Back to login
|
||||
..._buildBackToLogin(),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
List<Widget> _buildLogoAndTitle() {
|
||||
final theme = AppFlowyTheme.of(context);
|
||||
final spacing = VSpace(theme.spacing.xxl);
|
||||
return [
|
||||
// logo
|
||||
const AFLogo(),
|
||||
spacing,
|
||||
|
||||
// title
|
||||
Text(
|
||||
'Enter password',
|
||||
style: theme.textStyle.heading.h3(
|
||||
color: theme.textColorScheme.primary,
|
||||
),
|
||||
),
|
||||
spacing,
|
||||
|
||||
// email display
|
||||
RichText(
|
||||
text: TextSpan(
|
||||
children: [
|
||||
TextSpan(
|
||||
text: 'Login as ',
|
||||
style: theme.textStyle.body.standard(
|
||||
color: theme.textColorScheme.primary,
|
||||
),
|
||||
),
|
||||
TextSpan(
|
||||
text: widget.email,
|
||||
style: theme.textStyle.body.enhanced(
|
||||
color: theme.textColorScheme.primary,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
spacing,
|
||||
];
|
||||
}
|
||||
|
||||
List<Widget> _buildPasswordSection() {
|
||||
return [
|
||||
// Password input
|
||||
AFTextField(
|
||||
controller: passwordController,
|
||||
hintText: 'Enter password',
|
||||
autoFocus: true,
|
||||
onSubmitted: widget.onEnterPassword,
|
||||
),
|
||||
// todo: ask designer to provide the spacing
|
||||
VSpace(12),
|
||||
|
||||
// todo: forgot password is not implemented yet
|
||||
// Forgot password button
|
||||
// AFGhostTextButton(
|
||||
// text: 'Forget password?',
|
||||
// size: AFButtonSize.s,
|
||||
// onTap: widget.onForgotPassword,
|
||||
// textColor: (context, isHovering, disabled) {
|
||||
// return theme.textColorScheme.theme;
|
||||
// },
|
||||
// ),
|
||||
VSpace(12),
|
||||
|
||||
// Continue button
|
||||
AFFilledTextButton.primary(
|
||||
text: 'Continue',
|
||||
onTap: () => widget.onEnterPassword(passwordController.text),
|
||||
size: AFButtonSize.l,
|
||||
alignment: Alignment.center,
|
||||
),
|
||||
VSpace(20),
|
||||
];
|
||||
}
|
||||
|
||||
List<Widget> _buildBackToLogin() {
|
||||
return [
|
||||
AFGhostTextButton(
|
||||
text: 'Back to Login',
|
||||
size: AFButtonSize.s,
|
||||
onTap: widget.backToLogin,
|
||||
textColor: (context, isHovering, disabled) {
|
||||
final theme = AppFlowyTheme.of(context);
|
||||
return theme.textColorScheme.theme;
|
||||
},
|
||||
),
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
import 'package:appflowy/generated/flowy_svgs.g.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class AFLogo extends StatelessWidget {
|
||||
const AFLogo({
|
||||
super.key,
|
||||
this.size = const Size.square(36),
|
||||
});
|
||||
|
||||
final Size size;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return FlowySvg(
|
||||
FlowySvgs.flowy_logo_xl,
|
||||
blendMode: null,
|
||||
size: size,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -64,14 +64,17 @@ class _SignInWithMagicLinkButtonsState
|
||||
|
||||
void _sendMagicLink(BuildContext context, String email) {
|
||||
if (!isEmail(email)) {
|
||||
return showToastNotification(
|
||||
showToastNotification(
|
||||
context,
|
||||
message: LocaleKeys.signIn_invalidEmail.tr(),
|
||||
type: ToastificationType.error,
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
context.read<SignInBloc>().add(SignInEvent.signedWithMagicLink(email));
|
||||
context
|
||||
.read<SignInBloc>()
|
||||
.add(SignInEvent.signInWithMagicLink(email: email));
|
||||
|
||||
showConfirmDialog(
|
||||
context: context,
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import 'package:appflowy/core/helpers/url_launcher.dart';
|
||||
import 'package:appflowy/env/cloud_env.dart';
|
||||
import 'package:appflowy/generated/locale_keys.g.dart';
|
||||
import 'package:appflowy_ui/appflowy_ui.dart';
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flutter/gestures.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
@@ -12,41 +13,40 @@ class SignInAgreement extends StatelessWidget {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final theme = AppFlowyTheme.of(context);
|
||||
final textStyle = theme.textStyle.caption.standard(
|
||||
color: theme.textColorScheme.secondary,
|
||||
);
|
||||
final underlinedTextStyle = theme.textStyle.caption.underline(
|
||||
color: theme.textColorScheme.secondary,
|
||||
);
|
||||
return RichText(
|
||||
textAlign: TextAlign.center,
|
||||
text: TextSpan(
|
||||
children: [
|
||||
TextSpan(
|
||||
text: isLocalAuthEnabled
|
||||
? '${LocaleKeys.web_signInLocalAgreement.tr()} '
|
||||
: '${LocaleKeys.web_signInAgreement.tr()} ',
|
||||
style: const TextStyle(color: Colors.grey, fontSize: 12),
|
||||
? '${LocaleKeys.web_signInLocalAgreement.tr()} \n'
|
||||
: '${LocaleKeys.web_signInAgreement.tr()} \n',
|
||||
style: textStyle,
|
||||
),
|
||||
TextSpan(
|
||||
text: '${LocaleKeys.web_termOfUse.tr()} ',
|
||||
style: const TextStyle(
|
||||
color: Colors.grey,
|
||||
fontSize: 12,
|
||||
decoration: TextDecoration.underline,
|
||||
),
|
||||
style: underlinedTextStyle,
|
||||
mouseCursor: SystemMouseCursors.click,
|
||||
recognizer: TapGestureRecognizer()
|
||||
..onTap = () => afLaunchUrlString('https://appflowy.io/terms'),
|
||||
..onTap = () => afLaunchUrlString('https://appflowy.com/terms'),
|
||||
),
|
||||
TextSpan(
|
||||
text: '${LocaleKeys.web_and.tr()} ',
|
||||
style: const TextStyle(color: Colors.grey, fontSize: 12),
|
||||
style: textStyle,
|
||||
),
|
||||
TextSpan(
|
||||
text: LocaleKeys.web_privacyPolicy.tr(),
|
||||
style: const TextStyle(
|
||||
color: Colors.grey,
|
||||
fontSize: 12,
|
||||
decoration: TextDecoration.underline,
|
||||
),
|
||||
style: underlinedTextStyle,
|
||||
mouseCursor: SystemMouseCursors.click,
|
||||
recognizer: TapGestureRecognizer()
|
||||
..onTap = () => afLaunchUrlString('https://appflowy.io/privacy'),
|
||||
..onTap = () => afLaunchUrlString('https://appflowy.com/privacy'),
|
||||
),
|
||||
],
|
||||
),
|
||||
|
||||
@@ -1,91 +1,14 @@
|
||||
import 'package:appflowy/env/cloud_env.dart';
|
||||
import 'package:appflowy/generated/flowy_svgs.g.dart';
|
||||
import 'package:appflowy/generated/locale_keys.g.dart';
|
||||
import 'package:appflowy/startup/startup.dart';
|
||||
import 'package:appflowy/user/application/anon_user_bloc.dart';
|
||||
import 'package:appflowy/user/application/sign_in_bloc.dart';
|
||||
import 'package:appflowy_ui/appflowy_ui.dart';
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flowy_infra/size.dart';
|
||||
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:universal_platform/universal_platform.dart';
|
||||
|
||||
/// Used in DesktopSignInScreen and MobileSignInScreen
|
||||
class SignInAnonymousButton extends StatelessWidget {
|
||||
const SignInAnonymousButton({
|
||||
super.key,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final isMobile = UniversalPlatform.isMobile;
|
||||
|
||||
return BlocBuilder<SignInBloc, SignInState>(
|
||||
builder: (context, signInState) {
|
||||
return BlocProvider(
|
||||
create: (context) => AnonUserBloc()
|
||||
..add(
|
||||
const AnonUserEvent.initial(),
|
||||
),
|
||||
child: BlocListener<AnonUserBloc, AnonUserState>(
|
||||
listener: (context, state) async {
|
||||
if (state.openedAnonUser != null) {
|
||||
await runAppFlowy();
|
||||
}
|
||||
},
|
||||
child: BlocBuilder<AnonUserBloc, AnonUserState>(
|
||||
builder: (context, state) {
|
||||
final text = state.anonUsers.isEmpty
|
||||
? LocaleKeys.signIn_loginStartWithAnonymous.tr()
|
||||
: LocaleKeys.signIn_continueAnonymousUser.tr();
|
||||
final onTap = state.anonUsers.isEmpty
|
||||
? () {
|
||||
context
|
||||
.read<SignInBloc>()
|
||||
.add(const SignInEvent.signedInAsGuest());
|
||||
}
|
||||
: () {
|
||||
final bloc = context.read<AnonUserBloc>();
|
||||
final user = bloc.state.anonUsers.first;
|
||||
bloc.add(AnonUserEvent.openAnonUser(user));
|
||||
};
|
||||
// SignInAnonymousButton in mobile
|
||||
if (isMobile) {
|
||||
return ElevatedButton(
|
||||
style: ElevatedButton.styleFrom(
|
||||
minimumSize: const Size(double.infinity, 56),
|
||||
),
|
||||
onPressed: onTap,
|
||||
child: FlowyText(
|
||||
LocaleKeys.signIn_loginStartWithAnonymous.tr(),
|
||||
fontSize: 14,
|
||||
color: Theme.of(context).colorScheme.onPrimary,
|
||||
fontWeight: FontWeight.w500,
|
||||
),
|
||||
);
|
||||
}
|
||||
// SignInAnonymousButton in desktop
|
||||
return SizedBox(
|
||||
height: 48,
|
||||
child: FlowyButton(
|
||||
isSelected: true,
|
||||
disable: signInState.isSubmitting,
|
||||
text: FlowyText.medium(
|
||||
text,
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
radius: Corners.s6Border,
|
||||
onTap: onTap,
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class SignInAnonymousButtonV2 extends StatelessWidget {
|
||||
const SignInAnonymousButtonV2({
|
||||
@@ -109,27 +32,35 @@ class SignInAnonymousButtonV2 extends StatelessWidget {
|
||||
},
|
||||
child: BlocBuilder<AnonUserBloc, AnonUserState>(
|
||||
builder: (context, state) {
|
||||
final text = LocaleKeys.signIn_anonymous.tr();
|
||||
final theme = AppFlowyTheme.of(context);
|
||||
final onTap = state.anonUsers.isEmpty
|
||||
? () {
|
||||
context
|
||||
.read<SignInBloc>()
|
||||
.add(const SignInEvent.signedInAsGuest());
|
||||
.add(const SignInEvent.signInAsGuest());
|
||||
}
|
||||
: () {
|
||||
final bloc = context.read<AnonUserBloc>();
|
||||
final user = bloc.state.anonUsers.first;
|
||||
bloc.add(AnonUserEvent.openAnonUser(user));
|
||||
};
|
||||
return FlowyButton(
|
||||
useIntrinsicWidth: true,
|
||||
onTap: onTap,
|
||||
text: FlowyText(
|
||||
text,
|
||||
color: Colors.grey,
|
||||
decoration: TextDecoration.underline,
|
||||
fontSize: 12,
|
||||
return AFGhostIconTextButton(
|
||||
text: LocaleKeys.signIn_anonymousMode.tr(),
|
||||
textColor: (context, isHovering, disabled) {
|
||||
return theme.textColorScheme.secondary;
|
||||
},
|
||||
padding: EdgeInsets.symmetric(
|
||||
horizontal: theme.spacing.m,
|
||||
vertical: theme.spacing.xs,
|
||||
),
|
||||
size: AFButtonSize.s,
|
||||
onTap: onTap,
|
||||
iconBuilder: (context, isHovering, disabled) {
|
||||
return FlowySvg(
|
||||
FlowySvgs.anonymous_mode_m,
|
||||
color: theme.textColorScheme.secondary,
|
||||
);
|
||||
},
|
||||
);
|
||||
},
|
||||
),
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import 'package:appflowy/generated/flowy_svgs.g.dart';
|
||||
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
|
||||
import 'package:appflowy_ui/appflowy_ui.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class MobileLogoutButton extends StatelessWidget {
|
||||
@@ -18,50 +18,19 @@ class MobileLogoutButton extends StatelessWidget {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final style = Theme.of(context);
|
||||
return GestureDetector(
|
||||
return AFOutlinedIconTextButton.normal(
|
||||
text: text,
|
||||
onTap: onPressed,
|
||||
child: Container(
|
||||
height: 38,
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: const BorderRadius.all(
|
||||
Radius.circular(4),
|
||||
),
|
||||
border: Border.all(
|
||||
color: textColor ?? style.colorScheme.outline,
|
||||
width: 0.5,
|
||||
),
|
||||
),
|
||||
alignment: Alignment.center,
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
if (icon != null) ...[
|
||||
SizedBox(
|
||||
// The icon could be in different height as original aspect ratio, we use a fixed sizebox to wrap it to make sure they all occupy the same space.
|
||||
width: 30,
|
||||
height: 30,
|
||||
child: Center(
|
||||
child: SizedBox(
|
||||
width: 24,
|
||||
child: FlowySvg(
|
||||
icon!,
|
||||
blendMode: null,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
const HSpace(8),
|
||||
],
|
||||
FlowyText(
|
||||
text,
|
||||
fontSize: 14.0,
|
||||
fontWeight: FontWeight.w400,
|
||||
color: textColor,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
size: AFButtonSize.l,
|
||||
iconBuilder: (context, isHovering, disabled) {
|
||||
if (icon == null) {
|
||||
return const SizedBox.shrink();
|
||||
}
|
||||
return FlowySvg(
|
||||
icon!,
|
||||
size: Size.square(18),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,10 +1,7 @@
|
||||
import 'package:appflowy/generated/flowy_svgs.g.dart';
|
||||
import 'package:appflowy/generated/locale_keys.g.dart';
|
||||
import 'package:appflowy/mobile/presentation/base/animated_gesture.dart';
|
||||
import 'package:appflowy/user/presentation/widgets/widgets.dart';
|
||||
import 'package:appflowy_ui/appflowy_ui.dart';
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flowy_infra/size.dart';
|
||||
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
enum ThirdPartySignInButtonType {
|
||||
@@ -102,118 +99,55 @@ class MobileThirdPartySignInButton extends StatelessWidget {
|
||||
super.key,
|
||||
this.height = 38,
|
||||
this.fontSize = 14.0,
|
||||
required this.onPressed,
|
||||
required this.onTap,
|
||||
required this.type,
|
||||
});
|
||||
|
||||
final VoidCallback onPressed;
|
||||
final VoidCallback onTap;
|
||||
final double height;
|
||||
final double fontSize;
|
||||
final ThirdPartySignInButtonType type;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final style = Theme.of(context);
|
||||
|
||||
return AnimatedGestureDetector(
|
||||
scaleFactor: 1.0,
|
||||
onTapUp: onPressed,
|
||||
child: Container(
|
||||
height: height,
|
||||
decoration: BoxDecoration(
|
||||
color: type.backgroundColor(context),
|
||||
borderRadius: const BorderRadius.all(
|
||||
Radius.circular(4),
|
||||
),
|
||||
border: Border.all(
|
||||
color: style.colorScheme.outline,
|
||||
width: 0.5,
|
||||
),
|
||||
),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
if (type != ThirdPartySignInButtonType.anonymous)
|
||||
FlowySvg(
|
||||
type.icon,
|
||||
size: Size.square(fontSize),
|
||||
blendMode: type.blendMode,
|
||||
color: type.textColor(context),
|
||||
),
|
||||
const HSpace(8.0),
|
||||
FlowyText(
|
||||
type.labelText,
|
||||
fontSize: fontSize,
|
||||
color: type.textColor(context),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
return AFOutlinedIconTextButton.normal(
|
||||
text: type.labelText,
|
||||
onTap: onTap,
|
||||
size: AFButtonSize.l,
|
||||
iconBuilder: (context, isHovering, disabled) {
|
||||
return FlowySvg(
|
||||
type.icon,
|
||||
size: Size.square(16),
|
||||
blendMode: type.blendMode,
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class DesktopSignInButton extends StatelessWidget {
|
||||
const DesktopSignInButton({
|
||||
class DesktopThirdPartySignInButton extends StatelessWidget {
|
||||
const DesktopThirdPartySignInButton({
|
||||
super.key,
|
||||
required this.type,
|
||||
required this.onPressed,
|
||||
required this.onTap,
|
||||
});
|
||||
|
||||
final ThirdPartySignInButtonType type;
|
||||
final VoidCallback onPressed;
|
||||
final VoidCallback onTap;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final style = Theme.of(context);
|
||||
// In desktop, the width of button is limited by [AuthFormContainer]
|
||||
return SizedBox(
|
||||
height: 48,
|
||||
width: AuthFormContainer.width,
|
||||
child: OutlinedButton.icon(
|
||||
// In order to align all the labels vertically in a relatively centered position to the button, we use a fixed width container to wrap the icon(align to the right), then use another container to align the label to left.
|
||||
icon: Container(
|
||||
width: AuthFormContainer.width / 4,
|
||||
alignment: Alignment.centerRight,
|
||||
child: SizedBox(
|
||||
// Some icons are not square, so we just use a fixed width here.
|
||||
width: 24,
|
||||
child: FlowySvg(
|
||||
type.icon,
|
||||
blendMode: type.blendMode,
|
||||
),
|
||||
),
|
||||
),
|
||||
label: Container(
|
||||
padding: const EdgeInsets.only(left: 8),
|
||||
alignment: Alignment.centerLeft,
|
||||
child: FlowyText(
|
||||
type.labelText,
|
||||
fontSize: 14,
|
||||
),
|
||||
),
|
||||
style: ButtonStyle(
|
||||
overlayColor: WidgetStateProperty.resolveWith<Color?>(
|
||||
(states) {
|
||||
if (states.contains(WidgetState.hovered)) {
|
||||
return style.colorScheme.onSecondaryContainer;
|
||||
}
|
||||
return null;
|
||||
},
|
||||
),
|
||||
shape: WidgetStateProperty.all(
|
||||
const RoundedRectangleBorder(
|
||||
borderRadius: Corners.s6Border,
|
||||
),
|
||||
),
|
||||
side: WidgetStateProperty.all(
|
||||
BorderSide(
|
||||
color: style.dividerColor,
|
||||
),
|
||||
),
|
||||
),
|
||||
onPressed: onPressed,
|
||||
),
|
||||
return AFOutlinedIconTextButton.normal(
|
||||
text: type.labelText,
|
||||
onTap: onTap,
|
||||
size: AFButtonSize.l,
|
||||
iconBuilder: (context, isHovering, disabled) {
|
||||
return FlowySvg(
|
||||
type.icon,
|
||||
size: Size.square(18),
|
||||
blendMode: type.blendMode,
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,8 +1,7 @@
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:appflowy/generated/locale_keys.g.dart';
|
||||
import 'package:appflowy/user/application/sign_in_bloc.dart';
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:appflowy_ui/appflowy_ui.dart';
|
||||
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
@@ -40,7 +39,7 @@ class ThirdPartySignInButtons extends StatelessWidget {
|
||||
|
||||
void _signIn(BuildContext context, String provider) {
|
||||
context.read<SignInBloc>().add(
|
||||
SignInEvent.signedInWithOAuth(provider),
|
||||
SignInEvent.signInWithOAuth(platform: provider),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -58,23 +57,22 @@ class _DesktopThirdPartySignIn extends StatefulWidget {
|
||||
}
|
||||
|
||||
class _DesktopThirdPartySignInState extends State<_DesktopThirdPartySignIn> {
|
||||
static const padding = 12.0;
|
||||
|
||||
bool isExpanded = false;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final theme = AppFlowyTheme.of(context);
|
||||
return Column(
|
||||
children: [
|
||||
DesktopSignInButton(
|
||||
DesktopThirdPartySignInButton(
|
||||
key: signInWithGoogleButtonKey,
|
||||
type: ThirdPartySignInButtonType.google,
|
||||
onPressed: () => widget.onSignIn(ThirdPartySignInButtonType.google),
|
||||
onTap: () => widget.onSignIn(ThirdPartySignInButtonType.google),
|
||||
),
|
||||
const VSpace(padding),
|
||||
DesktopSignInButton(
|
||||
VSpace(theme.spacing.l),
|
||||
DesktopThirdPartySignInButton(
|
||||
type: ThirdPartySignInButtonType.apple,
|
||||
onPressed: () => widget.onSignIn(ThirdPartySignInButtonType.apple),
|
||||
onTap: () => widget.onSignIn(ThirdPartySignInButtonType.apple),
|
||||
),
|
||||
...isExpanded ? _buildExpandedButtons() : _buildCollapsedButtons(),
|
||||
],
|
||||
@@ -82,38 +80,38 @@ class _DesktopThirdPartySignInState extends State<_DesktopThirdPartySignIn> {
|
||||
}
|
||||
|
||||
List<Widget> _buildExpandedButtons() {
|
||||
final theme = AppFlowyTheme.of(context);
|
||||
return [
|
||||
const VSpace(padding * 1.5),
|
||||
DesktopSignInButton(
|
||||
VSpace(theme.spacing.l),
|
||||
DesktopThirdPartySignInButton(
|
||||
type: ThirdPartySignInButtonType.github,
|
||||
onPressed: () => widget.onSignIn(ThirdPartySignInButtonType.github),
|
||||
onTap: () => widget.onSignIn(ThirdPartySignInButtonType.github),
|
||||
),
|
||||
const VSpace(padding),
|
||||
DesktopSignInButton(
|
||||
VSpace(theme.spacing.l),
|
||||
DesktopThirdPartySignInButton(
|
||||
type: ThirdPartySignInButtonType.discord,
|
||||
onPressed: () => widget.onSignIn(ThirdPartySignInButtonType.discord),
|
||||
onTap: () => widget.onSignIn(ThirdPartySignInButtonType.discord),
|
||||
),
|
||||
];
|
||||
}
|
||||
|
||||
List<Widget> _buildCollapsedButtons() {
|
||||
final theme = AppFlowyTheme.of(context);
|
||||
return [
|
||||
const VSpace(padding),
|
||||
MouseRegion(
|
||||
cursor: SystemMouseCursors.click,
|
||||
child: GestureDetector(
|
||||
onTap: () {
|
||||
setState(() {
|
||||
isExpanded = !isExpanded;
|
||||
});
|
||||
},
|
||||
child: FlowyText(
|
||||
LocaleKeys.signIn_continueAnotherWay.tr(),
|
||||
color: Theme.of(context).colorScheme.onSurface,
|
||||
decoration: TextDecoration.underline,
|
||||
fontSize: 14,
|
||||
),
|
||||
),
|
||||
VSpace(theme.spacing.l),
|
||||
AFGhostTextButton(
|
||||
text: 'More options',
|
||||
textColor: (context, isHovering, disabled) {
|
||||
if (isHovering) {
|
||||
return theme.fillColorScheme.themeThickHover;
|
||||
}
|
||||
return theme.textColorScheme.theme;
|
||||
},
|
||||
onTap: () {
|
||||
setState(() {
|
||||
isExpanded = !isExpanded;
|
||||
});
|
||||
},
|
||||
),
|
||||
];
|
||||
}
|
||||
@@ -153,14 +151,14 @@ class _MobileThirdPartySignInState extends State<_MobileThirdPartySignIn> {
|
||||
if (Platform.isIOS) ...[
|
||||
MobileThirdPartySignInButton(
|
||||
type: ThirdPartySignInButtonType.apple,
|
||||
onPressed: () => widget.onSignIn(ThirdPartySignInButtonType.apple),
|
||||
onTap: () => widget.onSignIn(ThirdPartySignInButtonType.apple),
|
||||
),
|
||||
const VSpace(padding),
|
||||
],
|
||||
MobileThirdPartySignInButton(
|
||||
key: signInWithGoogleButtonKey,
|
||||
type: ThirdPartySignInButtonType.google,
|
||||
onPressed: () => widget.onSignIn(ThirdPartySignInButtonType.google),
|
||||
onTap: () => widget.onSignIn(ThirdPartySignInButtonType.google),
|
||||
),
|
||||
...isExpanded ? _buildExpandedButtons() : _buildCollapsedButtons(),
|
||||
],
|
||||
@@ -172,31 +170,33 @@ class _MobileThirdPartySignInState extends State<_MobileThirdPartySignIn> {
|
||||
const VSpace(padding),
|
||||
MobileThirdPartySignInButton(
|
||||
type: ThirdPartySignInButtonType.github,
|
||||
onPressed: () => widget.onSignIn(ThirdPartySignInButtonType.github),
|
||||
onTap: () => widget.onSignIn(ThirdPartySignInButtonType.github),
|
||||
),
|
||||
const VSpace(padding),
|
||||
MobileThirdPartySignInButton(
|
||||
type: ThirdPartySignInButtonType.discord,
|
||||
onPressed: () => widget.onSignIn(ThirdPartySignInButtonType.discord),
|
||||
onTap: () => widget.onSignIn(ThirdPartySignInButtonType.discord),
|
||||
),
|
||||
];
|
||||
}
|
||||
|
||||
List<Widget> _buildCollapsedButtons() {
|
||||
final theme = AppFlowyTheme.of(context);
|
||||
return [
|
||||
const VSpace(padding * 2),
|
||||
GestureDetector(
|
||||
AFGhostTextButton(
|
||||
text: 'More options',
|
||||
textColor: (context, isHovering, disabled) {
|
||||
if (isHovering) {
|
||||
return theme.fillColorScheme.themeThickHover;
|
||||
}
|
||||
return theme.textColorScheme.theme;
|
||||
},
|
||||
onTap: () {
|
||||
setState(() {
|
||||
isExpanded = !isExpanded;
|
||||
});
|
||||
},
|
||||
child: FlowyText(
|
||||
LocaleKeys.signIn_continueAnotherWay.tr(),
|
||||
color: Theme.of(context).colorScheme.onSurface,
|
||||
decoration: TextDecoration.underline,
|
||||
fontSize: 14,
|
||||
),
|
||||
),
|
||||
];
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
export 'magic_link_sign_in_buttons.dart';
|
||||
export 'continue_with/continue_with_email_and_password.dart';
|
||||
export 'sign_in_agreement.dart';
|
||||
export 'sign_in_anonymous_button.dart';
|
||||
export 'sign_in_or_logout_button.dart';
|
||||
export 'third_party_sign_in_button.dart';
|
||||
export 'third_party_sign_in_button/third_party_sign_in_button.dart';
|
||||
// export 'switch_sign_in_sign_up_button.dart';
|
||||
export 'third_party_sign_in_buttons.dart';
|
||||
export 'sign_in_agreement.dart';
|
||||
export 'third_party_sign_in_button/third_party_sign_in_buttons.dart';
|
||||
@@ -8,7 +8,7 @@ class AuthFormContainer extends StatelessWidget {
|
||||
|
||||
final List<Widget> children;
|
||||
|
||||
static const double width = 340;
|
||||
static const double width = 320;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
import 'package:appflowy/generated/flowy_svgs.g.dart';
|
||||
import 'package:flowy_infra/size.dart';
|
||||
import 'package:appflowy/user/presentation/screens/sign_in_screen/widgets/logo/logo.dart';
|
||||
import 'package:appflowy_ui/appflowy_ui.dart';
|
||||
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:google_fonts/google_fonts.dart';
|
||||
|
||||
class FlowyLogoTitle extends StatelessWidget {
|
||||
const FlowyLogoTitle({
|
||||
@@ -16,24 +15,19 @@ class FlowyLogoTitle extends StatelessWidget {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final theme = AppFlowyTheme.of(context);
|
||||
|
||||
return SizedBox(
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
SizedBox.fromSize(
|
||||
size: logoSize,
|
||||
child: const FlowySvg(
|
||||
FlowySvgs.flowy_logo_xl,
|
||||
blendMode: null,
|
||||
),
|
||||
),
|
||||
AFLogo(size: logoSize),
|
||||
const VSpace(20),
|
||||
FlowyText.regular(
|
||||
Text(
|
||||
title,
|
||||
fontSize: FontSizes.s24,
|
||||
fontFamily:
|
||||
GoogleFonts.poppins(fontWeight: FontWeight.w500).fontFamily,
|
||||
color: Theme.of(context).colorScheme.tertiary,
|
||||
style: theme.textStyle.heading.h3(
|
||||
color: theme.textColorScheme.primary,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
|
||||
@@ -4,6 +4,7 @@ import 'package:appflowy/workspace/application/settings/appearance/base_appearan
|
||||
import 'package:flowy_infra/size.dart';
|
||||
import 'package:flowy_infra/theme.dart';
|
||||
import 'package:flowy_infra/theme_extension.dart';
|
||||
import 'package:flowy_infra/theme_extension_v2.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class MobileAppearance extends BaseAppearance {
|
||||
@@ -29,6 +30,7 @@ class MobileAppearance extends BaseAppearance {
|
||||
);
|
||||
|
||||
final codeFontStyle = getFontStyle(fontFamily: codeFontFamily);
|
||||
final isLight = brightness == Brightness.light;
|
||||
|
||||
final theme = brightness == Brightness.light
|
||||
? appTheme.lightTheme
|
||||
@@ -283,6 +285,11 @@ class MobileAppearance extends BaseAppearance {
|
||||
toolbarHoverColor: theme.toolbarHoverColor,
|
||||
),
|
||||
ToolbarColorExtension.fromBrightness(brightness),
|
||||
isLight
|
||||
? lightAFThemeV2
|
||||
: darkAFThemeV2.copyWith(
|
||||
icon_primary: theme.icon,
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
@@ -188,6 +188,9 @@ class SidebarPlanBloc extends Bloc<SidebarPlanEvent, SidebarPlanState> {
|
||||
UserEventGetWorkspaceUsage(payload).send().then((result) {
|
||||
result.onSuccess(
|
||||
(usage) {
|
||||
if (isClosed) {
|
||||
return;
|
||||
}
|
||||
add(SidebarPlanEvent.updateWorkspaceUsage(usage));
|
||||
},
|
||||
);
|
||||
|
||||
@@ -4,7 +4,7 @@ import 'package:appflowy/generated/locale_keys.g.dart';
|
||||
import 'package:appflowy/startup/startup.dart';
|
||||
import 'package:appflowy/user/application/auth/auth_service.dart';
|
||||
import 'package:appflowy/user/application/prelude.dart';
|
||||
import 'package:appflowy/user/presentation/screens/sign_in_screen/widgets/magic_link_sign_in_buttons.dart';
|
||||
import 'package:appflowy/user/presentation/screens/sign_in_screen/widgets/continue_with/continue_with_email_and_password.dart';
|
||||
import 'package:appflowy/util/navigator_context_extension.dart';
|
||||
import 'package:appflowy/workspace/presentation/settings/widgets/setting_third_party_login.dart';
|
||||
import 'package:appflowy/workspace/presentation/widgets/dialogs.dart';
|
||||
@@ -111,7 +111,7 @@ class _SignInDialogContent extends StatelessWidget {
|
||||
const _DialogHeader(),
|
||||
const _DialogTitle(),
|
||||
const VSpace(16),
|
||||
const SignInWithMagicLinkButtons(),
|
||||
const ContinueWithEmailAndPassword(),
|
||||
if (isAuthEnabled) ...[
|
||||
const VSpace(20),
|
||||
const _OrDivider(),
|
||||
|
||||
@@ -140,10 +140,8 @@ class LocalAISettingPanel extends StatelessWidget {
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
const LocalAIStatusIndicator(),
|
||||
if (state.showSettings) ...[
|
||||
const VSpace(10),
|
||||
OllamaSettingPage(),
|
||||
],
|
||||
const VSpace(10),
|
||||
OllamaSettingPage(),
|
||||
],
|
||||
);
|
||||
},
|
||||
|
||||
@@ -288,6 +288,10 @@ class _LackOfResource extends StatelessWidget {
|
||||
text: LocaleKeys.settings_aiPage_keys_modelsMissing.tr(),
|
||||
style: textStyle,
|
||||
),
|
||||
TextSpan(
|
||||
text: modelNames.join(', '),
|
||||
style: textStyle,
|
||||
),
|
||||
TextSpan(
|
||||
text: ' ',
|
||||
style: textStyle,
|
||||
|
||||
@@ -71,7 +71,7 @@ class AppFlowyCloudViewSetting extends StatelessWidget {
|
||||
const VSpace(8),
|
||||
const AppFlowyCloudEnableSync(),
|
||||
const VSpace(6),
|
||||
const AppFlowyCloudSyncLogEnabled(),
|
||||
// const AppFlowyCloudSyncLogEnabled(),
|
||||
const VSpace(12),
|
||||
RestartButton(
|
||||
onClick: () {
|
||||
@@ -130,7 +130,7 @@ class CustomAppFlowyCloudView extends StatelessWidget {
|
||||
final List<Widget> children = [];
|
||||
children.addAll([
|
||||
const AppFlowyCloudEnableSync(),
|
||||
const AppFlowyCloudSyncLogEnabled(),
|
||||
// const AppFlowyCloudSyncLogEnabled(),
|
||||
const VSpace(40),
|
||||
]);
|
||||
|
||||
|
||||
@@ -363,7 +363,7 @@ class OkCancelButton extends StatelessWidget {
|
||||
}
|
||||
}
|
||||
|
||||
void showToastNotification(
|
||||
ToastificationItem showToastNotification(
|
||||
BuildContext context, {
|
||||
String? message,
|
||||
TextSpan? richMessage,
|
||||
@@ -376,7 +376,7 @@ void showToastNotification(
|
||||
(message == null) != (richMessage == null),
|
||||
"Exactly one of message or richMessage must be non-null.",
|
||||
);
|
||||
toastification.showCustom(
|
||||
return toastification.showCustom(
|
||||
alignment: Alignment.bottomCenter,
|
||||
autoCloseDuration: const Duration(milliseconds: 3000),
|
||||
callbacks: callbacks ?? const ToastificationCallbacks(),
|
||||
|
||||
@@ -0,0 +1,31 @@
|
||||
# Miscellaneous
|
||||
*.class
|
||||
*.log
|
||||
*.pyc
|
||||
*.swp
|
||||
.DS_Store
|
||||
.atom/
|
||||
.buildlog/
|
||||
.history
|
||||
.svn/
|
||||
migrate_working_dir/
|
||||
|
||||
# IntelliJ related
|
||||
*.iml
|
||||
*.ipr
|
||||
*.iws
|
||||
.idea/
|
||||
|
||||
# The .vscode folder contains launch configuration and tasks you configure in
|
||||
# VS Code which you may wish to be included in version control, so this line
|
||||
# is commented out by default.
|
||||
.vscode/
|
||||
|
||||
# Flutter/Dart/Pub related
|
||||
# Libraries should not include pubspec.lock, per https://dart.dev/guides/libraries/private-files#pubspeclock.
|
||||
/pubspec.lock
|
||||
**/doc/api/
|
||||
.dart_tool/
|
||||
.flutter-plugins
|
||||
.flutter-plugins-dependencies
|
||||
build/
|
||||
@@ -0,0 +1,10 @@
|
||||
# This file tracks properties of this Flutter project.
|
||||
# Used by Flutter tool to assess capabilities and perform upgrades etc.
|
||||
#
|
||||
# This file should be version controlled and should not be manually edited.
|
||||
|
||||
version:
|
||||
revision: "d8a9f9a52e5af486f80d932e838ee93861ffd863"
|
||||
channel: "[user-branch]"
|
||||
|
||||
project_type: package
|
||||
@@ -0,0 +1,3 @@
|
||||
## 0.0.1
|
||||
|
||||
* TODO: Describe initial release.
|
||||
@@ -0,0 +1 @@
|
||||
TODO: Add your license here.
|
||||
@@ -0,0 +1,39 @@
|
||||
# AppFlowy UI
|
||||
|
||||
AppFlowy UI is a Flutter package that provides a collection of reusable UI components following the AppFlowy design system. These components are designed to be consistent, accessible, and easy to use.
|
||||
|
||||
## Features
|
||||
|
||||
- **Design System Components**: Buttons, text fields, and more UI components that follow the AppFlowy design system
|
||||
- **Theming**: Consistent theming across all components with light and dark mode support
|
||||
|
||||
## Installation
|
||||
|
||||
Add the following to your `pubspec.yaml` file:
|
||||
|
||||
```yaml
|
||||
dependencies:
|
||||
appflowy_ui: ^1.0.0
|
||||
```
|
||||
|
||||
## Supported components
|
||||
|
||||
- [x] Button
|
||||
- [x] TextField
|
||||
- [ ] Avatar
|
||||
- [ ] Checkbox
|
||||
- [ ] Grid
|
||||
- [ ] Link
|
||||
- [ ] Loading & Progress Indicator
|
||||
- [ ] Menu
|
||||
- [ ] Message Box
|
||||
- [ ] Navigation Bar
|
||||
- [ ] Popover
|
||||
- [ ] Scroll Bar
|
||||
- [ ] Tab Bar
|
||||
- [ ] Toggle
|
||||
- [ ] Tooltip
|
||||
|
||||
## Reference
|
||||
|
||||
Figma: https://www.figma.com/design/aphWa2OgkqyIragpatdk7a/Design-System
|
||||
@@ -0,0 +1,29 @@
|
||||
include: package:flutter_lints/flutter.yaml
|
||||
|
||||
linter:
|
||||
rules:
|
||||
- require_trailing_commas
|
||||
|
||||
- prefer_collection_literals
|
||||
- prefer_final_fields
|
||||
- prefer_final_in_for_each
|
||||
- prefer_final_locals
|
||||
|
||||
- sized_box_for_whitespace
|
||||
- use_decorated_box
|
||||
|
||||
- unnecessary_parenthesis
|
||||
- unnecessary_await_in_return
|
||||
- unnecessary_raw_strings
|
||||
|
||||
- avoid_unnecessary_containers
|
||||
- avoid_redundant_argument_values
|
||||
- avoid_unused_constructor_parameters
|
||||
|
||||
- always_declare_return_types
|
||||
|
||||
- sort_constructors_first
|
||||
- unawaited_futures
|
||||
|
||||
errors:
|
||||
invalid_annotation_target: ignore
|
||||
@@ -0,0 +1,45 @@
|
||||
# Miscellaneous
|
||||
*.class
|
||||
*.log
|
||||
*.pyc
|
||||
*.swp
|
||||
.DS_Store
|
||||
.atom/
|
||||
.build/
|
||||
.buildlog/
|
||||
.history
|
||||
.svn/
|
||||
.swiftpm/
|
||||
migrate_working_dir/
|
||||
|
||||
# IntelliJ related
|
||||
*.iml
|
||||
*.ipr
|
||||
*.iws
|
||||
.idea/
|
||||
|
||||
# The .vscode folder contains launch configuration and tasks you configure in
|
||||
# VS Code which you may wish to be included in version control, so this line
|
||||
# is commented out by default.
|
||||
#.vscode/
|
||||
|
||||
# Flutter/Dart/Pub related
|
||||
**/doc/api/
|
||||
**/ios/Flutter/.last_build_id
|
||||
.dart_tool/
|
||||
.flutter-plugins
|
||||
.flutter-plugins-dependencies
|
||||
.pub-cache/
|
||||
.pub/
|
||||
/build/
|
||||
|
||||
# Symbolication related
|
||||
app.*.symbols
|
||||
|
||||
# Obfuscation related
|
||||
app.*.map.json
|
||||
|
||||
# Android Studio will place build artifacts here
|
||||
/android/app/debug
|
||||
/android/app/profile
|
||||
/android/app/release
|
||||
@@ -0,0 +1,30 @@
|
||||
# This file tracks properties of this Flutter project.
|
||||
# Used by Flutter tool to assess capabilities and perform upgrades etc.
|
||||
#
|
||||
# This file should be version controlled and should not be manually edited.
|
||||
|
||||
version:
|
||||
revision: "d8a9f9a52e5af486f80d932e838ee93861ffd863"
|
||||
channel: "[user-branch]"
|
||||
|
||||
project_type: app
|
||||
|
||||
# Tracks metadata for the flutter migrate command
|
||||
migration:
|
||||
platforms:
|
||||
- platform: root
|
||||
create_revision: d8a9f9a52e5af486f80d932e838ee93861ffd863
|
||||
base_revision: d8a9f9a52e5af486f80d932e838ee93861ffd863
|
||||
- platform: macos
|
||||
create_revision: d8a9f9a52e5af486f80d932e838ee93861ffd863
|
||||
base_revision: d8a9f9a52e5af486f80d932e838ee93861ffd863
|
||||
|
||||
# User provided section
|
||||
|
||||
# List of Local paths (relative to this file) that should be
|
||||
# ignored by the migrate tool.
|
||||
#
|
||||
# Files that are not part of the templates will be ignored by default.
|
||||
unmanaged_files:
|
||||
- 'lib/main.dart'
|
||||
- 'ios/Runner.xcodeproj/project.pbxproj'
|
||||
@@ -0,0 +1,41 @@
|
||||
# AppFlowy UI Example
|
||||
|
||||
This example demonstrates how to use the `appflowy_ui` package in a Flutter application.
|
||||
|
||||
## Getting Started
|
||||
|
||||
To run this example:
|
||||
|
||||
1. Ensure you have Flutter installed and set up on your machine
|
||||
2. Clone this repository
|
||||
3. Navigate to the example directory:
|
||||
```bash
|
||||
cd example
|
||||
```
|
||||
4. Get the dependencies:
|
||||
```bash
|
||||
flutter pub get
|
||||
```
|
||||
5. Run the example:
|
||||
```bash
|
||||
flutter run
|
||||
```
|
||||
|
||||
## Features Demonstrated
|
||||
|
||||
- Basic app structure using AppFlowy UI components
|
||||
- Material 3 design integration
|
||||
- Responsive layout
|
||||
|
||||
## Project Structure
|
||||
|
||||
- `lib/main.dart`: The main application file
|
||||
- `pubspec.yaml`: Project dependencies and configuration
|
||||
|
||||
## Additional Resources
|
||||
|
||||
For more information about the AppFlowy UI package, please refer to:
|
||||
|
||||
- The main package documentation
|
||||
- [AppFlowy Website](https://appflowy.io)
|
||||
- [AppFlowy GitHub Repository](https://github.com/AppFlowy-IO/AppFlowy)
|
||||
@@ -0,0 +1,28 @@
|
||||
# This file configures the analyzer, which statically analyzes Dart code to
|
||||
# check for errors, warnings, and lints.
|
||||
#
|
||||
# The issues identified by the analyzer are surfaced in the UI of Dart-enabled
|
||||
# IDEs (https://dart.dev/tools#ides-and-editors). The analyzer can also be
|
||||
# invoked from the command line by running `flutter analyze`.
|
||||
|
||||
# The following line activates a set of recommended lints for Flutter apps,
|
||||
# packages, and plugins designed to encourage good coding practices.
|
||||
include: package:flutter_lints/flutter.yaml
|
||||
|
||||
linter:
|
||||
# The lint rules applied to this project can be customized in the
|
||||
# section below to disable rules from the `package:flutter_lints/flutter.yaml`
|
||||
# included above or to enable additional rules. A list of all available lints
|
||||
# and their documentation is published at https://dart.dev/lints.
|
||||
#
|
||||
# Instead of disabling a lint rule for the entire project in the
|
||||
# section below, it can also be suppressed for a single line of code
|
||||
# or a specific dart file by using the `// ignore: name_of_lint` and
|
||||
# `// ignore_for_file: name_of_lint` syntax on the line or in the file
|
||||
# producing the lint.
|
||||
rules:
|
||||
# avoid_print: false # Uncomment to disable the `avoid_print` rule
|
||||
# prefer_single_quotes: true # Uncomment to enable the `prefer_single_quotes` rule
|
||||
|
||||
# Additional information about this file can be found at
|
||||
# https://dart.dev/guides/language/analysis-options
|
||||
@@ -0,0 +1,109 @@
|
||||
import 'package:appflowy_ui/appflowy_ui.dart';
|
||||
import 'package:appflowy_ui_example/src/buttons/buttons_page.dart';
|
||||
import 'package:appflowy_ui_example/src/textfield/textfield_page.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
enum ThemeMode {
|
||||
light,
|
||||
dark,
|
||||
}
|
||||
|
||||
final themeMode = ValueNotifier(ThemeMode.light);
|
||||
|
||||
void main() {
|
||||
runApp(
|
||||
const MyApp(),
|
||||
);
|
||||
}
|
||||
|
||||
class MyApp extends StatelessWidget {
|
||||
const MyApp({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return ValueListenableBuilder(
|
||||
valueListenable: themeMode,
|
||||
builder: (context, themeMode, child) {
|
||||
return AppFlowyTheme(
|
||||
data: themeMode == ThemeMode.light
|
||||
? AppFlowyThemeData.light()
|
||||
: AppFlowyThemeData.dark(),
|
||||
child: MaterialApp(
|
||||
debugShowCheckedModeBanner: false,
|
||||
title: 'AppFlowy UI Example',
|
||||
theme: themeMode == ThemeMode.light
|
||||
? ThemeData.light()
|
||||
: ThemeData.dark(),
|
||||
home: const MyHomePage(
|
||||
title: 'AppFlowy UI',
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class MyHomePage extends StatefulWidget {
|
||||
const MyHomePage({
|
||||
super.key,
|
||||
required this.title,
|
||||
});
|
||||
|
||||
final String title;
|
||||
|
||||
@override
|
||||
State<MyHomePage> createState() => _MyHomePageState();
|
||||
}
|
||||
|
||||
class _MyHomePageState extends State<MyHomePage> {
|
||||
final tabs = [
|
||||
Tab(text: 'Button'),
|
||||
Tab(text: 'TextField'),
|
||||
];
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final theme = AppFlowyTheme.of(context);
|
||||
return DefaultTabController(
|
||||
length: tabs.length,
|
||||
child: Scaffold(
|
||||
appBar: AppBar(
|
||||
backgroundColor: Theme.of(context).colorScheme.inversePrimary,
|
||||
title: Text(
|
||||
widget.title,
|
||||
style: theme.textStyle.title.enhanced(
|
||||
color: theme.textColorScheme.primary,
|
||||
),
|
||||
),
|
||||
actions: [
|
||||
IconButton(
|
||||
icon: Icon(
|
||||
theme.brightness == Brightness.light
|
||||
? Icons.dark_mode
|
||||
: Icons.light_mode,
|
||||
),
|
||||
onPressed: _toggleTheme,
|
||||
tooltip: 'Toggle theme',
|
||||
),
|
||||
],
|
||||
),
|
||||
body: TabBarView(
|
||||
children: [
|
||||
ButtonsPage(),
|
||||
TextFieldPage(),
|
||||
],
|
||||
),
|
||||
bottomNavigationBar: TabBar(
|
||||
tabs: tabs,
|
||||
),
|
||||
floatingActionButton: null,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
void _toggleTheme() {
|
||||
themeMode.value =
|
||||
themeMode.value == ThemeMode.light ? ThemeMode.dark : ThemeMode.light;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,287 @@
|
||||
import 'package:appflowy_ui/appflowy_ui.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class ButtonsPage extends StatelessWidget {
|
||||
const ButtonsPage({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return SingleChildScrollView(
|
||||
padding: const EdgeInsets.all(16.0),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
_buildSection(
|
||||
'Filled Text Buttons',
|
||||
[
|
||||
AFFilledTextButton.primary(
|
||||
text: 'Primary Button',
|
||||
onTap: () {},
|
||||
),
|
||||
const SizedBox(width: 16),
|
||||
AFFilledTextButton.destructive(
|
||||
text: 'Destructive Button',
|
||||
onTap: () {},
|
||||
),
|
||||
const SizedBox(width: 16),
|
||||
AFFilledTextButton.disabled(
|
||||
text: 'Disabled Button',
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 32),
|
||||
_buildSection(
|
||||
'Filled Icon Text Buttons',
|
||||
[
|
||||
AFFilledButton.primary(
|
||||
onTap: () {},
|
||||
builder: (context, isHovering, disabled) => Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Icon(
|
||||
Icons.add,
|
||||
size: 20,
|
||||
color: AppFlowyTheme.of(context).textColorScheme.onFill,
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
Text(
|
||||
'Primary Button',
|
||||
style: TextStyle(
|
||||
color: AppFlowyTheme.of(context).textColorScheme.onFill,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 16),
|
||||
AFFilledButton.destructive(
|
||||
onTap: () {},
|
||||
builder: (context, isHovering, disabled) => Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Icon(
|
||||
Icons.delete,
|
||||
size: 20,
|
||||
color: AppFlowyTheme.of(context).textColorScheme.onFill,
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
Text(
|
||||
'Destructive Button',
|
||||
style: TextStyle(
|
||||
color: AppFlowyTheme.of(context).textColorScheme.onFill,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 16),
|
||||
AFFilledButton.disabled(
|
||||
builder: (context, isHovering, disabled) => Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Icon(
|
||||
Icons.block,
|
||||
size: 20,
|
||||
color: AppFlowyTheme.of(context).textColorScheme.tertiary,
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
Text(
|
||||
'Disabled Button',
|
||||
style: TextStyle(
|
||||
color:
|
||||
AppFlowyTheme.of(context).textColorScheme.tertiary,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 32),
|
||||
_buildSection(
|
||||
'Outlined Text Buttons',
|
||||
[
|
||||
AFOutlinedTextButton.normal(
|
||||
text: 'Normal Button',
|
||||
onTap: () {},
|
||||
),
|
||||
const SizedBox(width: 16),
|
||||
AFOutlinedTextButton.destructive(
|
||||
text: 'Destructive Button',
|
||||
onTap: () {},
|
||||
),
|
||||
const SizedBox(width: 16),
|
||||
AFOutlinedTextButton.disabled(
|
||||
text: 'Disabled Button',
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 32),
|
||||
_buildSection(
|
||||
'Outlined Icon Text Buttons',
|
||||
[
|
||||
AFOutlinedButton.normal(
|
||||
onTap: () {},
|
||||
builder: (context, isHovering, disabled) => Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Icon(
|
||||
Icons.add,
|
||||
size: 20,
|
||||
color: AppFlowyTheme.of(context).textColorScheme.primary,
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
Text(
|
||||
'Normal Button',
|
||||
style: TextStyle(
|
||||
color:
|
||||
AppFlowyTheme.of(context).textColorScheme.primary,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 16),
|
||||
AFOutlinedButton.destructive(
|
||||
onTap: () {},
|
||||
builder: (context, isHovering, disabled) => Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Icon(
|
||||
Icons.delete,
|
||||
size: 20,
|
||||
color: AppFlowyTheme.of(context).textColorScheme.error,
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
Text(
|
||||
'Destructive Button',
|
||||
style: TextStyle(
|
||||
color: AppFlowyTheme.of(context).textColorScheme.error,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 16),
|
||||
AFOutlinedButton.disabled(
|
||||
builder: (context, isHovering, disabled) => Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Icon(
|
||||
Icons.block,
|
||||
size: 20,
|
||||
color: AppFlowyTheme.of(context).textColorScheme.tertiary,
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
Text(
|
||||
'Disabled Button',
|
||||
style: TextStyle(
|
||||
color:
|
||||
AppFlowyTheme.of(context).textColorScheme.tertiary,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 32),
|
||||
_buildSection(
|
||||
'Ghost Buttons',
|
||||
[
|
||||
AFGhostTextButton.primary(
|
||||
text: 'Primary Button',
|
||||
onTap: () {},
|
||||
),
|
||||
const SizedBox(width: 16),
|
||||
AFGhostTextButton.disabled(
|
||||
text: 'Disabled Button',
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 32),
|
||||
_buildSection(
|
||||
'Button with alignment',
|
||||
[
|
||||
SizedBox(
|
||||
width: 200,
|
||||
child: AFFilledTextButton.primary(
|
||||
text: 'Left Button',
|
||||
onTap: () {},
|
||||
alignment: Alignment.centerLeft,
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 16),
|
||||
SizedBox(
|
||||
width: 200,
|
||||
child: AFFilledTextButton.primary(
|
||||
text: 'Center Button',
|
||||
onTap: () {},
|
||||
alignment: Alignment.center,
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 16),
|
||||
SizedBox(
|
||||
width: 200,
|
||||
child: AFFilledTextButton.primary(
|
||||
text: 'Right Button',
|
||||
onTap: () {},
|
||||
alignment: Alignment.centerRight,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 32),
|
||||
_buildSection(
|
||||
'Button Sizes',
|
||||
[
|
||||
AFFilledTextButton.primary(
|
||||
text: 'Small Button',
|
||||
onTap: () {},
|
||||
size: AFButtonSize.s,
|
||||
),
|
||||
const SizedBox(width: 16),
|
||||
AFFilledTextButton.primary(
|
||||
text: 'Medium Button',
|
||||
onTap: () {},
|
||||
),
|
||||
const SizedBox(width: 16),
|
||||
AFFilledTextButton.primary(
|
||||
text: 'Large Button',
|
||||
onTap: () {},
|
||||
size: AFButtonSize.l,
|
||||
),
|
||||
const SizedBox(width: 16),
|
||||
AFFilledTextButton.primary(
|
||||
text: 'Extra Large Button',
|
||||
onTap: () {},
|
||||
size: AFButtonSize.xl,
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildSection(String title, List<Widget> children) {
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
title,
|
||||
style: const TextStyle(
|
||||
fontSize: 20,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
Wrap(
|
||||
spacing: 8,
|
||||
runSpacing: 8,
|
||||
children: children,
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,77 @@
|
||||
import 'package:appflowy_ui/appflowy_ui.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class TextFieldPage extends StatelessWidget {
|
||||
const TextFieldPage({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return SingleChildScrollView(
|
||||
padding: const EdgeInsets.all(16.0),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
_buildSection(
|
||||
'TextField with hint text',
|
||||
[
|
||||
AFTextField(
|
||||
hintText: 'Please enter your name',
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 32),
|
||||
_buildSection(
|
||||
'TextField with initial text',
|
||||
[
|
||||
AFTextField(
|
||||
initialText: 'https://appflowy.com',
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 32),
|
||||
_buildSection(
|
||||
'TextField with validator ',
|
||||
[
|
||||
AFTextField(
|
||||
validator: (controller) {
|
||||
if (controller.text.isEmpty) {
|
||||
return (true, 'This field is required');
|
||||
}
|
||||
|
||||
final emailRegex =
|
||||
RegExp(r'^[\w-\.]+@([\w-]+\.)+[\w-]{2,4}$');
|
||||
if (!emailRegex.hasMatch(controller.text)) {
|
||||
return (true, 'Please enter a valid email address');
|
||||
}
|
||||
|
||||
return (false, '');
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildSection(String title, List<Widget> children) {
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
title,
|
||||
style: const TextStyle(
|
||||
fontSize: 20,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
Wrap(
|
||||
spacing: 8,
|
||||
runSpacing: 8,
|
||||
children: children,
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
# Flutter-related
|
||||
**/Flutter/ephemeral/
|
||||
**/Pods/
|
||||
|
||||
# Xcode-related
|
||||
**/dgph
|
||||
**/xcuserdata/
|
||||
@@ -0,0 +1 @@
|
||||
#include "ephemeral/Flutter-Generated.xcconfig"
|
||||
@@ -0,0 +1 @@
|
||||
#include "ephemeral/Flutter-Generated.xcconfig"
|
||||
@@ -0,0 +1,705 @@
|
||||
// !$*UTF8*$!
|
||||
{
|
||||
archiveVersion = 1;
|
||||
classes = {
|
||||
};
|
||||
objectVersion = 54;
|
||||
objects = {
|
||||
|
||||
/* Begin PBXAggregateTarget section */
|
||||
33CC111A2044C6BA0003C045 /* Flutter Assemble */ = {
|
||||
isa = PBXAggregateTarget;
|
||||
buildConfigurationList = 33CC111B2044C6BA0003C045 /* Build configuration list for PBXAggregateTarget "Flutter Assemble" */;
|
||||
buildPhases = (
|
||||
33CC111E2044C6BF0003C045 /* ShellScript */,
|
||||
);
|
||||
dependencies = (
|
||||
);
|
||||
name = "Flutter Assemble";
|
||||
productName = FLX;
|
||||
};
|
||||
/* End PBXAggregateTarget section */
|
||||
|
||||
/* Begin PBXBuildFile section */
|
||||
331C80D8294CF71000263BE5 /* RunnerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 331C80D7294CF71000263BE5 /* RunnerTests.swift */; };
|
||||
335BBD1B22A9A15E00E9071D /* GeneratedPluginRegistrant.swift in Sources */ = {isa = PBXBuildFile; fileRef = 335BBD1A22A9A15E00E9071D /* GeneratedPluginRegistrant.swift */; };
|
||||
33CC10F12044A3C60003C045 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 33CC10F02044A3C60003C045 /* AppDelegate.swift */; };
|
||||
33CC10F32044A3C60003C045 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 33CC10F22044A3C60003C045 /* Assets.xcassets */; };
|
||||
33CC10F62044A3C60003C045 /* MainMenu.xib in Resources */ = {isa = PBXBuildFile; fileRef = 33CC10F42044A3C60003C045 /* MainMenu.xib */; };
|
||||
33CC11132044BFA00003C045 /* MainFlutterWindow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 33CC11122044BFA00003C045 /* MainFlutterWindow.swift */; };
|
||||
/* End PBXBuildFile section */
|
||||
|
||||
/* Begin PBXContainerItemProxy section */
|
||||
331C80D9294CF71000263BE5 /* PBXContainerItemProxy */ = {
|
||||
isa = PBXContainerItemProxy;
|
||||
containerPortal = 33CC10E52044A3C60003C045 /* Project object */;
|
||||
proxyType = 1;
|
||||
remoteGlobalIDString = 33CC10EC2044A3C60003C045;
|
||||
remoteInfo = Runner;
|
||||
};
|
||||
33CC111F2044C79F0003C045 /* PBXContainerItemProxy */ = {
|
||||
isa = PBXContainerItemProxy;
|
||||
containerPortal = 33CC10E52044A3C60003C045 /* Project object */;
|
||||
proxyType = 1;
|
||||
remoteGlobalIDString = 33CC111A2044C6BA0003C045;
|
||||
remoteInfo = FLX;
|
||||
};
|
||||
/* End PBXContainerItemProxy section */
|
||||
|
||||
/* Begin PBXCopyFilesBuildPhase section */
|
||||
33CC110E2044A8840003C045 /* Bundle Framework */ = {
|
||||
isa = PBXCopyFilesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
dstPath = "";
|
||||
dstSubfolderSpec = 10;
|
||||
files = (
|
||||
);
|
||||
name = "Bundle Framework";
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
/* End PBXCopyFilesBuildPhase section */
|
||||
|
||||
/* Begin PBXFileReference section */
|
||||
331C80D5294CF71000263BE5 /* RunnerTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = RunnerTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
331C80D7294CF71000263BE5 /* RunnerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RunnerTests.swift; sourceTree = "<group>"; };
|
||||
333000ED22D3DE5D00554162 /* Warnings.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Warnings.xcconfig; sourceTree = "<group>"; };
|
||||
335BBD1A22A9A15E00E9071D /* GeneratedPluginRegistrant.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GeneratedPluginRegistrant.swift; sourceTree = "<group>"; };
|
||||
33CC10ED2044A3C60003C045 /* appflowy_ui_example.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "appflowy_ui_example.app"; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
33CC10F02044A3C60003C045 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; };
|
||||
33CC10F22044A3C60003C045 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; name = Assets.xcassets; path = Runner/Assets.xcassets; sourceTree = "<group>"; };
|
||||
33CC10F52044A3C60003C045 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/MainMenu.xib; sourceTree = "<group>"; };
|
||||
33CC10F72044A3C60003C045 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; name = Info.plist; path = Runner/Info.plist; sourceTree = "<group>"; };
|
||||
33CC11122044BFA00003C045 /* MainFlutterWindow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MainFlutterWindow.swift; sourceTree = "<group>"; };
|
||||
33CEB47222A05771004F2AC0 /* Flutter-Debug.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = "Flutter-Debug.xcconfig"; sourceTree = "<group>"; };
|
||||
33CEB47422A05771004F2AC0 /* Flutter-Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = "Flutter-Release.xcconfig"; sourceTree = "<group>"; };
|
||||
33CEB47722A0578A004F2AC0 /* Flutter-Generated.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = "Flutter-Generated.xcconfig"; path = "ephemeral/Flutter-Generated.xcconfig"; sourceTree = "<group>"; };
|
||||
33E51913231747F40026EE4D /* DebugProfile.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = DebugProfile.entitlements; sourceTree = "<group>"; };
|
||||
33E51914231749380026EE4D /* Release.entitlements */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.entitlements; path = Release.entitlements; sourceTree = "<group>"; };
|
||||
33E5194F232828860026EE4D /* AppInfo.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = AppInfo.xcconfig; sourceTree = "<group>"; };
|
||||
7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Release.xcconfig; sourceTree = "<group>"; };
|
||||
9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; path = Debug.xcconfig; sourceTree = "<group>"; };
|
||||
/* End PBXFileReference section */
|
||||
|
||||
/* Begin PBXFrameworksBuildPhase section */
|
||||
331C80D2294CF70F00263BE5 /* Frameworks */ = {
|
||||
isa = PBXFrameworksBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
33CC10EA2044A3C60003C045 /* Frameworks */ = {
|
||||
isa = PBXFrameworksBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
/* End PBXFrameworksBuildPhase section */
|
||||
|
||||
/* Begin PBXGroup section */
|
||||
331C80D6294CF71000263BE5 /* RunnerTests */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
331C80D7294CF71000263BE5 /* RunnerTests.swift */,
|
||||
);
|
||||
path = RunnerTests;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
33BA886A226E78AF003329D5 /* Configs */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
33E5194F232828860026EE4D /* AppInfo.xcconfig */,
|
||||
9740EEB21CF90195004384FC /* Debug.xcconfig */,
|
||||
7AFA3C8E1D35360C0083082E /* Release.xcconfig */,
|
||||
333000ED22D3DE5D00554162 /* Warnings.xcconfig */,
|
||||
);
|
||||
path = Configs;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
33CC10E42044A3C60003C045 = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
33FAB671232836740065AC1E /* Runner */,
|
||||
33CEB47122A05771004F2AC0 /* Flutter */,
|
||||
331C80D6294CF71000263BE5 /* RunnerTests */,
|
||||
33CC10EE2044A3C60003C045 /* Products */,
|
||||
D73912EC22F37F3D000D13A0 /* Frameworks */,
|
||||
);
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
33CC10EE2044A3C60003C045 /* Products */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
33CC10ED2044A3C60003C045 /* appflowy_ui_example.app */,
|
||||
331C80D5294CF71000263BE5 /* RunnerTests.xctest */,
|
||||
);
|
||||
name = Products;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
33CC11242044D66E0003C045 /* Resources */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
33CC10F22044A3C60003C045 /* Assets.xcassets */,
|
||||
33CC10F42044A3C60003C045 /* MainMenu.xib */,
|
||||
33CC10F72044A3C60003C045 /* Info.plist */,
|
||||
);
|
||||
name = Resources;
|
||||
path = ..;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
33CEB47122A05771004F2AC0 /* Flutter */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
335BBD1A22A9A15E00E9071D /* GeneratedPluginRegistrant.swift */,
|
||||
33CEB47222A05771004F2AC0 /* Flutter-Debug.xcconfig */,
|
||||
33CEB47422A05771004F2AC0 /* Flutter-Release.xcconfig */,
|
||||
33CEB47722A0578A004F2AC0 /* Flutter-Generated.xcconfig */,
|
||||
);
|
||||
path = Flutter;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
33FAB671232836740065AC1E /* Runner */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
33CC10F02044A3C60003C045 /* AppDelegate.swift */,
|
||||
33CC11122044BFA00003C045 /* MainFlutterWindow.swift */,
|
||||
33E51913231747F40026EE4D /* DebugProfile.entitlements */,
|
||||
33E51914231749380026EE4D /* Release.entitlements */,
|
||||
33CC11242044D66E0003C045 /* Resources */,
|
||||
33BA886A226E78AF003329D5 /* Configs */,
|
||||
);
|
||||
path = Runner;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
D73912EC22F37F3D000D13A0 /* Frameworks */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
);
|
||||
name = Frameworks;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
/* End PBXGroup section */
|
||||
|
||||
/* Begin PBXNativeTarget section */
|
||||
331C80D4294CF70F00263BE5 /* RunnerTests */ = {
|
||||
isa = PBXNativeTarget;
|
||||
buildConfigurationList = 331C80DE294CF71000263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */;
|
||||
buildPhases = (
|
||||
331C80D1294CF70F00263BE5 /* Sources */,
|
||||
331C80D2294CF70F00263BE5 /* Frameworks */,
|
||||
331C80D3294CF70F00263BE5 /* Resources */,
|
||||
);
|
||||
buildRules = (
|
||||
);
|
||||
dependencies = (
|
||||
331C80DA294CF71000263BE5 /* PBXTargetDependency */,
|
||||
);
|
||||
name = RunnerTests;
|
||||
productName = RunnerTests;
|
||||
productReference = 331C80D5294CF71000263BE5 /* RunnerTests.xctest */;
|
||||
productType = "com.apple.product-type.bundle.unit-test";
|
||||
};
|
||||
33CC10EC2044A3C60003C045 /* Runner */ = {
|
||||
isa = PBXNativeTarget;
|
||||
buildConfigurationList = 33CC10FB2044A3C60003C045 /* Build configuration list for PBXNativeTarget "Runner" */;
|
||||
buildPhases = (
|
||||
33CC10E92044A3C60003C045 /* Sources */,
|
||||
33CC10EA2044A3C60003C045 /* Frameworks */,
|
||||
33CC10EB2044A3C60003C045 /* Resources */,
|
||||
33CC110E2044A8840003C045 /* Bundle Framework */,
|
||||
3399D490228B24CF009A79C7 /* ShellScript */,
|
||||
);
|
||||
buildRules = (
|
||||
);
|
||||
dependencies = (
|
||||
33CC11202044C79F0003C045 /* PBXTargetDependency */,
|
||||
);
|
||||
name = Runner;
|
||||
productName = Runner;
|
||||
productReference = 33CC10ED2044A3C60003C045 /* appflowy_ui_example.app */;
|
||||
productType = "com.apple.product-type.application";
|
||||
};
|
||||
/* End PBXNativeTarget section */
|
||||
|
||||
/* Begin PBXProject section */
|
||||
33CC10E52044A3C60003C045 /* Project object */ = {
|
||||
isa = PBXProject;
|
||||
attributes = {
|
||||
BuildIndependentTargetsInParallel = YES;
|
||||
LastSwiftUpdateCheck = 0920;
|
||||
LastUpgradeCheck = 1510;
|
||||
ORGANIZATIONNAME = "";
|
||||
TargetAttributes = {
|
||||
331C80D4294CF70F00263BE5 = {
|
||||
CreatedOnToolsVersion = 14.0;
|
||||
TestTargetID = 33CC10EC2044A3C60003C045;
|
||||
};
|
||||
33CC10EC2044A3C60003C045 = {
|
||||
CreatedOnToolsVersion = 9.2;
|
||||
LastSwiftMigration = 1100;
|
||||
ProvisioningStyle = Automatic;
|
||||
SystemCapabilities = {
|
||||
com.apple.Sandbox = {
|
||||
enabled = 1;
|
||||
};
|
||||
};
|
||||
};
|
||||
33CC111A2044C6BA0003C045 = {
|
||||
CreatedOnToolsVersion = 9.2;
|
||||
ProvisioningStyle = Manual;
|
||||
};
|
||||
};
|
||||
};
|
||||
buildConfigurationList = 33CC10E82044A3C60003C045 /* Build configuration list for PBXProject "Runner" */;
|
||||
compatibilityVersion = "Xcode 9.3";
|
||||
developmentRegion = en;
|
||||
hasScannedForEncodings = 0;
|
||||
knownRegions = (
|
||||
en,
|
||||
Base,
|
||||
);
|
||||
mainGroup = 33CC10E42044A3C60003C045;
|
||||
productRefGroup = 33CC10EE2044A3C60003C045 /* Products */;
|
||||
projectDirPath = "";
|
||||
projectRoot = "";
|
||||
targets = (
|
||||
33CC10EC2044A3C60003C045 /* Runner */,
|
||||
331C80D4294CF70F00263BE5 /* RunnerTests */,
|
||||
33CC111A2044C6BA0003C045 /* Flutter Assemble */,
|
||||
);
|
||||
};
|
||||
/* End PBXProject section */
|
||||
|
||||
/* Begin PBXResourcesBuildPhase section */
|
||||
331C80D3294CF70F00263BE5 /* Resources */ = {
|
||||
isa = PBXResourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
33CC10EB2044A3C60003C045 /* Resources */ = {
|
||||
isa = PBXResourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
33CC10F32044A3C60003C045 /* Assets.xcassets in Resources */,
|
||||
33CC10F62044A3C60003C045 /* MainMenu.xib in Resources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
/* End PBXResourcesBuildPhase section */
|
||||
|
||||
/* Begin PBXShellScriptBuildPhase section */
|
||||
3399D490228B24CF009A79C7 /* ShellScript */ = {
|
||||
isa = PBXShellScriptBuildPhase;
|
||||
alwaysOutOfDate = 1;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
);
|
||||
inputFileListPaths = (
|
||||
);
|
||||
inputPaths = (
|
||||
);
|
||||
outputFileListPaths = (
|
||||
);
|
||||
outputPaths = (
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
shellPath = /bin/sh;
|
||||
shellScript = "echo \"$PRODUCT_NAME.app\" > \"$PROJECT_DIR\"/Flutter/ephemeral/.app_filename && \"$FLUTTER_ROOT\"/packages/flutter_tools/bin/macos_assemble.sh embed\n";
|
||||
};
|
||||
33CC111E2044C6BF0003C045 /* ShellScript */ = {
|
||||
isa = PBXShellScriptBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
);
|
||||
inputFileListPaths = (
|
||||
Flutter/ephemeral/FlutterInputs.xcfilelist,
|
||||
);
|
||||
inputPaths = (
|
||||
Flutter/ephemeral/tripwire,
|
||||
);
|
||||
outputFileListPaths = (
|
||||
Flutter/ephemeral/FlutterOutputs.xcfilelist,
|
||||
);
|
||||
outputPaths = (
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
shellPath = /bin/sh;
|
||||
shellScript = "\"$FLUTTER_ROOT\"/packages/flutter_tools/bin/macos_assemble.sh && touch Flutter/ephemeral/tripwire";
|
||||
};
|
||||
/* End PBXShellScriptBuildPhase section */
|
||||
|
||||
/* Begin PBXSourcesBuildPhase section */
|
||||
331C80D1294CF70F00263BE5 /* Sources */ = {
|
||||
isa = PBXSourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
331C80D8294CF71000263BE5 /* RunnerTests.swift in Sources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
33CC10E92044A3C60003C045 /* Sources */ = {
|
||||
isa = PBXSourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
33CC11132044BFA00003C045 /* MainFlutterWindow.swift in Sources */,
|
||||
33CC10F12044A3C60003C045 /* AppDelegate.swift in Sources */,
|
||||
335BBD1B22A9A15E00E9071D /* GeneratedPluginRegistrant.swift in Sources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
/* End PBXSourcesBuildPhase section */
|
||||
|
||||
/* Begin PBXTargetDependency section */
|
||||
331C80DA294CF71000263BE5 /* PBXTargetDependency */ = {
|
||||
isa = PBXTargetDependency;
|
||||
target = 33CC10EC2044A3C60003C045 /* Runner */;
|
||||
targetProxy = 331C80D9294CF71000263BE5 /* PBXContainerItemProxy */;
|
||||
};
|
||||
33CC11202044C79F0003C045 /* PBXTargetDependency */ = {
|
||||
isa = PBXTargetDependency;
|
||||
target = 33CC111A2044C6BA0003C045 /* Flutter Assemble */;
|
||||
targetProxy = 33CC111F2044C79F0003C045 /* PBXContainerItemProxy */;
|
||||
};
|
||||
/* End PBXTargetDependency section */
|
||||
|
||||
/* Begin PBXVariantGroup section */
|
||||
33CC10F42044A3C60003C045 /* MainMenu.xib */ = {
|
||||
isa = PBXVariantGroup;
|
||||
children = (
|
||||
33CC10F52044A3C60003C045 /* Base */,
|
||||
);
|
||||
name = MainMenu.xib;
|
||||
path = Runner;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
/* End PBXVariantGroup section */
|
||||
|
||||
/* Begin XCBuildConfiguration section */
|
||||
331C80DB294CF71000263BE5 /* Debug */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
BUNDLE_LOADER = "$(TEST_HOST)";
|
||||
CURRENT_PROJECT_VERSION = 1;
|
||||
GENERATE_INFOPLIST_FILE = YES;
|
||||
MARKETING_VERSION = 1.0;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.example.appflowyUiExample.RunnerTests;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SWIFT_VERSION = 5.0;
|
||||
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/appflowy_ui_example.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/appflowy_ui_example";
|
||||
};
|
||||
name = Debug;
|
||||
};
|
||||
331C80DC294CF71000263BE5 /* Release */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
BUNDLE_LOADER = "$(TEST_HOST)";
|
||||
CURRENT_PROJECT_VERSION = 1;
|
||||
GENERATE_INFOPLIST_FILE = YES;
|
||||
MARKETING_VERSION = 1.0;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.example.appflowyUiExample.RunnerTests;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SWIFT_VERSION = 5.0;
|
||||
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/appflowy_ui_example.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/appflowy_ui_example";
|
||||
};
|
||||
name = Release;
|
||||
};
|
||||
331C80DD294CF71000263BE5 /* Profile */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
BUNDLE_LOADER = "$(TEST_HOST)";
|
||||
CURRENT_PROJECT_VERSION = 1;
|
||||
GENERATE_INFOPLIST_FILE = YES;
|
||||
MARKETING_VERSION = 1.0;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.example.appflowyUiExample.RunnerTests;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SWIFT_VERSION = 5.0;
|
||||
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/appflowy_ui_example.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/appflowy_ui_example";
|
||||
};
|
||||
name = Profile;
|
||||
};
|
||||
338D0CE9231458BD00FA5F75 /* Profile */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */;
|
||||
buildSettings = {
|
||||
ALWAYS_SEARCH_USER_PATHS = NO;
|
||||
ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
|
||||
CLANG_ANALYZER_NONNULL = YES;
|
||||
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
|
||||
CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
|
||||
CLANG_CXX_LIBRARY = "libc++";
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CLANG_ENABLE_OBJC_ARC = YES;
|
||||
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
|
||||
CLANG_WARN_BOOL_CONVERSION = YES;
|
||||
CLANG_WARN_CONSTANT_CONVERSION = YES;
|
||||
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
|
||||
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
|
||||
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
|
||||
CLANG_WARN_EMPTY_BODY = YES;
|
||||
CLANG_WARN_ENUM_CONVERSION = YES;
|
||||
CLANG_WARN_INFINITE_RECURSION = YES;
|
||||
CLANG_WARN_INT_CONVERSION = YES;
|
||||
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
|
||||
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
|
||||
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
|
||||
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
|
||||
CLANG_WARN_SUSPICIOUS_MOVE = YES;
|
||||
CODE_SIGN_IDENTITY = "-";
|
||||
COPY_PHASE_STRIP = NO;
|
||||
DEAD_CODE_STRIPPING = YES;
|
||||
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
|
||||
ENABLE_NS_ASSERTIONS = NO;
|
||||
ENABLE_STRICT_OBJC_MSGSEND = YES;
|
||||
ENABLE_USER_SCRIPT_SANDBOXING = NO;
|
||||
GCC_C_LANGUAGE_STANDARD = gnu11;
|
||||
GCC_NO_COMMON_BLOCKS = YES;
|
||||
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
|
||||
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
|
||||
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
|
||||
GCC_WARN_UNUSED_FUNCTION = YES;
|
||||
GCC_WARN_UNUSED_VARIABLE = YES;
|
||||
MACOSX_DEPLOYMENT_TARGET = 10.14;
|
||||
MTL_ENABLE_DEBUG_INFO = NO;
|
||||
SDKROOT = macosx;
|
||||
SWIFT_COMPILATION_MODE = wholemodule;
|
||||
SWIFT_OPTIMIZATION_LEVEL = "-O";
|
||||
};
|
||||
name = Profile;
|
||||
};
|
||||
338D0CEA231458BD00FA5F75 /* Profile */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
baseConfigurationReference = 33E5194F232828860026EE4D /* AppInfo.xcconfig */;
|
||||
buildSettings = {
|
||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CODE_SIGN_ENTITLEMENTS = Runner/DebugProfile.entitlements;
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
COMBINE_HIDPI_IMAGES = YES;
|
||||
INFOPLIST_FILE = Runner/Info.plist;
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"@executable_path/../Frameworks",
|
||||
);
|
||||
PROVISIONING_PROFILE_SPECIFIER = "";
|
||||
SWIFT_VERSION = 5.0;
|
||||
};
|
||||
name = Profile;
|
||||
};
|
||||
338D0CEB231458BD00FA5F75 /* Profile */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
CODE_SIGN_STYLE = Manual;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
};
|
||||
name = Profile;
|
||||
};
|
||||
33CC10F92044A3C60003C045 /* Debug */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */;
|
||||
buildSettings = {
|
||||
ALWAYS_SEARCH_USER_PATHS = NO;
|
||||
ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
|
||||
CLANG_ANALYZER_NONNULL = YES;
|
||||
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
|
||||
CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
|
||||
CLANG_CXX_LIBRARY = "libc++";
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CLANG_ENABLE_OBJC_ARC = YES;
|
||||
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
|
||||
CLANG_WARN_BOOL_CONVERSION = YES;
|
||||
CLANG_WARN_CONSTANT_CONVERSION = YES;
|
||||
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
|
||||
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
|
||||
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
|
||||
CLANG_WARN_EMPTY_BODY = YES;
|
||||
CLANG_WARN_ENUM_CONVERSION = YES;
|
||||
CLANG_WARN_INFINITE_RECURSION = YES;
|
||||
CLANG_WARN_INT_CONVERSION = YES;
|
||||
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
|
||||
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
|
||||
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
|
||||
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
|
||||
CLANG_WARN_SUSPICIOUS_MOVE = YES;
|
||||
CODE_SIGN_IDENTITY = "-";
|
||||
COPY_PHASE_STRIP = NO;
|
||||
DEAD_CODE_STRIPPING = YES;
|
||||
DEBUG_INFORMATION_FORMAT = dwarf;
|
||||
ENABLE_STRICT_OBJC_MSGSEND = YES;
|
||||
ENABLE_TESTABILITY = YES;
|
||||
ENABLE_USER_SCRIPT_SANDBOXING = NO;
|
||||
GCC_C_LANGUAGE_STANDARD = gnu11;
|
||||
GCC_DYNAMIC_NO_PIC = NO;
|
||||
GCC_NO_COMMON_BLOCKS = YES;
|
||||
GCC_OPTIMIZATION_LEVEL = 0;
|
||||
GCC_PREPROCESSOR_DEFINITIONS = (
|
||||
"DEBUG=1",
|
||||
"$(inherited)",
|
||||
);
|
||||
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
|
||||
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
|
||||
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
|
||||
GCC_WARN_UNUSED_FUNCTION = YES;
|
||||
GCC_WARN_UNUSED_VARIABLE = YES;
|
||||
MACOSX_DEPLOYMENT_TARGET = 10.14;
|
||||
MTL_ENABLE_DEBUG_INFO = YES;
|
||||
ONLY_ACTIVE_ARCH = YES;
|
||||
SDKROOT = macosx;
|
||||
SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;
|
||||
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
|
||||
};
|
||||
name = Debug;
|
||||
};
|
||||
33CC10FA2044A3C60003C045 /* Release */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */;
|
||||
buildSettings = {
|
||||
ALWAYS_SEARCH_USER_PATHS = NO;
|
||||
ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
|
||||
CLANG_ANALYZER_NONNULL = YES;
|
||||
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
|
||||
CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
|
||||
CLANG_CXX_LIBRARY = "libc++";
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CLANG_ENABLE_OBJC_ARC = YES;
|
||||
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
|
||||
CLANG_WARN_BOOL_CONVERSION = YES;
|
||||
CLANG_WARN_CONSTANT_CONVERSION = YES;
|
||||
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
|
||||
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
|
||||
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
|
||||
CLANG_WARN_EMPTY_BODY = YES;
|
||||
CLANG_WARN_ENUM_CONVERSION = YES;
|
||||
CLANG_WARN_INFINITE_RECURSION = YES;
|
||||
CLANG_WARN_INT_CONVERSION = YES;
|
||||
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
|
||||
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
|
||||
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
|
||||
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
|
||||
CLANG_WARN_SUSPICIOUS_MOVE = YES;
|
||||
CODE_SIGN_IDENTITY = "-";
|
||||
COPY_PHASE_STRIP = NO;
|
||||
DEAD_CODE_STRIPPING = YES;
|
||||
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
|
||||
ENABLE_NS_ASSERTIONS = NO;
|
||||
ENABLE_STRICT_OBJC_MSGSEND = YES;
|
||||
ENABLE_USER_SCRIPT_SANDBOXING = NO;
|
||||
GCC_C_LANGUAGE_STANDARD = gnu11;
|
||||
GCC_NO_COMMON_BLOCKS = YES;
|
||||
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
|
||||
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
|
||||
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
|
||||
GCC_WARN_UNUSED_FUNCTION = YES;
|
||||
GCC_WARN_UNUSED_VARIABLE = YES;
|
||||
MACOSX_DEPLOYMENT_TARGET = 10.14;
|
||||
MTL_ENABLE_DEBUG_INFO = NO;
|
||||
SDKROOT = macosx;
|
||||
SWIFT_COMPILATION_MODE = wholemodule;
|
||||
SWIFT_OPTIMIZATION_LEVEL = "-O";
|
||||
};
|
||||
name = Release;
|
||||
};
|
||||
33CC10FC2044A3C60003C045 /* Debug */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
baseConfigurationReference = 33E5194F232828860026EE4D /* AppInfo.xcconfig */;
|
||||
buildSettings = {
|
||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CODE_SIGN_ENTITLEMENTS = Runner/DebugProfile.entitlements;
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
COMBINE_HIDPI_IMAGES = YES;
|
||||
INFOPLIST_FILE = Runner/Info.plist;
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"@executable_path/../Frameworks",
|
||||
);
|
||||
PROVISIONING_PROFILE_SPECIFIER = "";
|
||||
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
|
||||
SWIFT_VERSION = 5.0;
|
||||
};
|
||||
name = Debug;
|
||||
};
|
||||
33CC10FD2044A3C60003C045 /* Release */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
baseConfigurationReference = 33E5194F232828860026EE4D /* AppInfo.xcconfig */;
|
||||
buildSettings = {
|
||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CODE_SIGN_ENTITLEMENTS = Runner/Release.entitlements;
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
COMBINE_HIDPI_IMAGES = YES;
|
||||
INFOPLIST_FILE = Runner/Info.plist;
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"@executable_path/../Frameworks",
|
||||
);
|
||||
PROVISIONING_PROFILE_SPECIFIER = "";
|
||||
SWIFT_VERSION = 5.0;
|
||||
};
|
||||
name = Release;
|
||||
};
|
||||
33CC111C2044C6BA0003C045 /* Debug */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
CODE_SIGN_STYLE = Manual;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
};
|
||||
name = Debug;
|
||||
};
|
||||
33CC111D2044C6BA0003C045 /* Release */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
};
|
||||
name = Release;
|
||||
};
|
||||
/* End XCBuildConfiguration section */
|
||||
|
||||
/* Begin XCConfigurationList section */
|
||||
331C80DE294CF71000263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */ = {
|
||||
isa = XCConfigurationList;
|
||||
buildConfigurations = (
|
||||
331C80DB294CF71000263BE5 /* Debug */,
|
||||
331C80DC294CF71000263BE5 /* Release */,
|
||||
331C80DD294CF71000263BE5 /* Profile */,
|
||||
);
|
||||
defaultConfigurationIsVisible = 0;
|
||||
defaultConfigurationName = Release;
|
||||
};
|
||||
33CC10E82044A3C60003C045 /* Build configuration list for PBXProject "Runner" */ = {
|
||||
isa = XCConfigurationList;
|
||||
buildConfigurations = (
|
||||
33CC10F92044A3C60003C045 /* Debug */,
|
||||
33CC10FA2044A3C60003C045 /* Release */,
|
||||
338D0CE9231458BD00FA5F75 /* Profile */,
|
||||
);
|
||||
defaultConfigurationIsVisible = 0;
|
||||
defaultConfigurationName = Release;
|
||||
};
|
||||
33CC10FB2044A3C60003C045 /* Build configuration list for PBXNativeTarget "Runner" */ = {
|
||||
isa = XCConfigurationList;
|
||||
buildConfigurations = (
|
||||
33CC10FC2044A3C60003C045 /* Debug */,
|
||||
33CC10FD2044A3C60003C045 /* Release */,
|
||||
338D0CEA231458BD00FA5F75 /* Profile */,
|
||||
);
|
||||
defaultConfigurationIsVisible = 0;
|
||||
defaultConfigurationName = Release;
|
||||
};
|
||||
33CC111B2044C6BA0003C045 /* Build configuration list for PBXAggregateTarget "Flutter Assemble" */ = {
|
||||
isa = XCConfigurationList;
|
||||
buildConfigurations = (
|
||||
33CC111C2044C6BA0003C045 /* Debug */,
|
||||
33CC111D2044C6BA0003C045 /* Release */,
|
||||
338D0CEB231458BD00FA5F75 /* Profile */,
|
||||
);
|
||||
defaultConfigurationIsVisible = 0;
|
||||
defaultConfigurationName = Release;
|
||||
};
|
||||
/* End XCConfigurationList section */
|
||||
};
|
||||
rootObject = 33CC10E52044A3C60003C045 /* Project object */;
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>IDEDidComputeMac32BitWarning</key>
|
||||
<true/>
|
||||
</dict>
|
||||
</plist>
|
||||
@@ -0,0 +1,98 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Scheme
|
||||
LastUpgradeVersion = "1510"
|
||||
version = "1.3">
|
||||
<BuildAction
|
||||
parallelizeBuildables = "YES"
|
||||
buildImplicitDependencies = "YES">
|
||||
<BuildActionEntries>
|
||||
<BuildActionEntry
|
||||
buildForTesting = "YES"
|
||||
buildForRunning = "YES"
|
||||
buildForProfiling = "YES"
|
||||
buildForArchiving = "YES"
|
||||
buildForAnalyzing = "YES">
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "33CC10EC2044A3C60003C045"
|
||||
BuildableName = "appflowy_ui_example.app"
|
||||
BlueprintName = "Runner"
|
||||
ReferencedContainer = "container:Runner.xcodeproj">
|
||||
</BuildableReference>
|
||||
</BuildActionEntry>
|
||||
</BuildActionEntries>
|
||||
</BuildAction>
|
||||
<TestAction
|
||||
buildConfiguration = "Debug"
|
||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||
shouldUseLaunchSchemeArgsEnv = "YES">
|
||||
<MacroExpansion>
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "33CC10EC2044A3C60003C045"
|
||||
BuildableName = "appflowy_ui_example.app"
|
||||
BlueprintName = "Runner"
|
||||
ReferencedContainer = "container:Runner.xcodeproj">
|
||||
</BuildableReference>
|
||||
</MacroExpansion>
|
||||
<Testables>
|
||||
<TestableReference
|
||||
skipped = "NO"
|
||||
parallelizable = "YES">
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "331C80D4294CF70F00263BE5"
|
||||
BuildableName = "RunnerTests.xctest"
|
||||
BlueprintName = "RunnerTests"
|
||||
ReferencedContainer = "container:Runner.xcodeproj">
|
||||
</BuildableReference>
|
||||
</TestableReference>
|
||||
</Testables>
|
||||
</TestAction>
|
||||
<LaunchAction
|
||||
buildConfiguration = "Debug"
|
||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||
launchStyle = "0"
|
||||
useCustomWorkingDirectory = "NO"
|
||||
ignoresPersistentStateOnLaunch = "NO"
|
||||
debugDocumentVersioning = "YES"
|
||||
debugServiceExtension = "internal"
|
||||
allowLocationSimulation = "YES">
|
||||
<BuildableProductRunnable
|
||||
runnableDebuggingMode = "0">
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "33CC10EC2044A3C60003C045"
|
||||
BuildableName = "appflowy_ui_example.app"
|
||||
BlueprintName = "Runner"
|
||||
ReferencedContainer = "container:Runner.xcodeproj">
|
||||
</BuildableReference>
|
||||
</BuildableProductRunnable>
|
||||
</LaunchAction>
|
||||
<ProfileAction
|
||||
buildConfiguration = "Profile"
|
||||
shouldUseLaunchSchemeArgsEnv = "YES"
|
||||
savedToolIdentifier = ""
|
||||
useCustomWorkingDirectory = "NO"
|
||||
debugDocumentVersioning = "YES">
|
||||
<BuildableProductRunnable
|
||||
runnableDebuggingMode = "0">
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "33CC10EC2044A3C60003C045"
|
||||
BuildableName = "appflowy_ui_example.app"
|
||||
BlueprintName = "Runner"
|
||||
ReferencedContainer = "container:Runner.xcodeproj">
|
||||
</BuildableReference>
|
||||
</BuildableProductRunnable>
|
||||
</ProfileAction>
|
||||
<AnalyzeAction
|
||||
buildConfiguration = "Debug">
|
||||
</AnalyzeAction>
|
||||
<ArchiveAction
|
||||
buildConfiguration = "Release"
|
||||
revealArchiveInOrganizer = "YES">
|
||||
</ArchiveAction>
|
||||
</Scheme>
|
||||
@@ -0,0 +1,7 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Workspace
|
||||
version = "1.0">
|
||||
<FileRef
|
||||
location = "group:Runner.xcodeproj">
|
||||
</FileRef>
|
||||
</Workspace>
|
||||
@@ -0,0 +1,8 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>IDEDidComputeMac32BitWarning</key>
|
||||
<true/>
|
||||
</dict>
|
||||
</plist>
|
||||
@@ -0,0 +1,13 @@
|
||||
import Cocoa
|
||||
import FlutterMacOS
|
||||
|
||||
@main
|
||||
class AppDelegate: FlutterAppDelegate {
|
||||
override func applicationShouldTerminateAfterLastWindowClosed(_ sender: NSApplication) -> Bool {
|
||||
return true
|
||||
}
|
||||
|
||||
override func applicationSupportsSecureRestorableState(_ app: NSApplication) -> Bool {
|
||||
return true
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"size" : "16x16",
|
||||
"idiom" : "mac",
|
||||
"filename" : "app_icon_16.png",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"size" : "16x16",
|
||||
"idiom" : "mac",
|
||||
"filename" : "app_icon_32.png",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"size" : "32x32",
|
||||
"idiom" : "mac",
|
||||
"filename" : "app_icon_32.png",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"size" : "32x32",
|
||||
"idiom" : "mac",
|
||||
"filename" : "app_icon_64.png",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"size" : "128x128",
|
||||
"idiom" : "mac",
|
||||
"filename" : "app_icon_128.png",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"size" : "128x128",
|
||||
"idiom" : "mac",
|
||||
"filename" : "app_icon_256.png",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"size" : "256x256",
|
||||
"idiom" : "mac",
|
||||
"filename" : "app_icon_256.png",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"size" : "256x256",
|
||||
"idiom" : "mac",
|
||||
"filename" : "app_icon_512.png",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"size" : "512x512",
|
||||
"idiom" : "mac",
|
||||
"filename" : "app_icon_512.png",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"size" : "512x512",
|
||||
"idiom" : "mac",
|
||||
"filename" : "app_icon_1024.png",
|
||||
"scale" : "2x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"version" : 1,
|
||||
"author" : "xcode"
|
||||
}
|
||||
}
|
||||
|
After Width: | Height: | Size: 101 KiB |
|
After Width: | Height: | Size: 5.5 KiB |
|
After Width: | Height: | Size: 520 B |
|
After Width: | Height: | Size: 14 KiB |
|
After Width: | Height: | Size: 1.0 KiB |
|
After Width: | Height: | Size: 36 KiB |
|
After Width: | Height: | Size: 2.2 KiB |
@@ -0,0 +1,343 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="14490.70" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES" customObjectInstantitationMethod="direct">
|
||||
<dependencies>
|
||||
<deployment identifier="macosx"/>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="14490.70"/>
|
||||
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
|
||||
</dependencies>
|
||||
<objects>
|
||||
<customObject id="-2" userLabel="File's Owner" customClass="NSApplication">
|
||||
<connections>
|
||||
<outlet property="delegate" destination="Voe-Tx-rLC" id="GzC-gU-4Uq"/>
|
||||
</connections>
|
||||
</customObject>
|
||||
<customObject id="-1" userLabel="First Responder" customClass="FirstResponder"/>
|
||||
<customObject id="-3" userLabel="Application" customClass="NSObject"/>
|
||||
<customObject id="Voe-Tx-rLC" customClass="AppDelegate" customModule="Runner" customModuleProvider="target">
|
||||
<connections>
|
||||
<outlet property="applicationMenu" destination="uQy-DD-JDr" id="XBo-yE-nKs"/>
|
||||
<outlet property="mainFlutterWindow" destination="QvC-M9-y7g" id="gIp-Ho-8D9"/>
|
||||
</connections>
|
||||
</customObject>
|
||||
<customObject id="YLy-65-1bz" customClass="NSFontManager"/>
|
||||
<menu title="Main Menu" systemMenu="main" id="AYu-sK-qS6">
|
||||
<items>
|
||||
<menuItem title="APP_NAME" id="1Xt-HY-uBw">
|
||||
<modifierMask key="keyEquivalentModifierMask"/>
|
||||
<menu key="submenu" title="APP_NAME" systemMenu="apple" id="uQy-DD-JDr">
|
||||
<items>
|
||||
<menuItem title="About APP_NAME" id="5kV-Vb-QxS">
|
||||
<modifierMask key="keyEquivalentModifierMask"/>
|
||||
<connections>
|
||||
<action selector="orderFrontStandardAboutPanel:" target="-1" id="Exp-CZ-Vem"/>
|
||||
</connections>
|
||||
</menuItem>
|
||||
<menuItem isSeparatorItem="YES" id="VOq-y0-SEH"/>
|
||||
<menuItem title="Preferences…" keyEquivalent="," id="BOF-NM-1cW"/>
|
||||
<menuItem isSeparatorItem="YES" id="wFC-TO-SCJ"/>
|
||||
<menuItem title="Services" id="NMo-om-nkz">
|
||||
<modifierMask key="keyEquivalentModifierMask"/>
|
||||
<menu key="submenu" title="Services" systemMenu="services" id="hz9-B4-Xy5"/>
|
||||
</menuItem>
|
||||
<menuItem isSeparatorItem="YES" id="4je-JR-u6R"/>
|
||||
<menuItem title="Hide APP_NAME" keyEquivalent="h" id="Olw-nP-bQN">
|
||||
<connections>
|
||||
<action selector="hide:" target="-1" id="PnN-Uc-m68"/>
|
||||
</connections>
|
||||
</menuItem>
|
||||
<menuItem title="Hide Others" keyEquivalent="h" id="Vdr-fp-XzO">
|
||||
<modifierMask key="keyEquivalentModifierMask" option="YES" command="YES"/>
|
||||
<connections>
|
||||
<action selector="hideOtherApplications:" target="-1" id="VT4-aY-XCT"/>
|
||||
</connections>
|
||||
</menuItem>
|
||||
<menuItem title="Show All" id="Kd2-mp-pUS">
|
||||
<modifierMask key="keyEquivalentModifierMask"/>
|
||||
<connections>
|
||||
<action selector="unhideAllApplications:" target="-1" id="Dhg-Le-xox"/>
|
||||
</connections>
|
||||
</menuItem>
|
||||
<menuItem isSeparatorItem="YES" id="kCx-OE-vgT"/>
|
||||
<menuItem title="Quit APP_NAME" keyEquivalent="q" id="4sb-4s-VLi">
|
||||
<connections>
|
||||
<action selector="terminate:" target="-1" id="Te7-pn-YzF"/>
|
||||
</connections>
|
||||
</menuItem>
|
||||
</items>
|
||||
</menu>
|
||||
</menuItem>
|
||||
<menuItem title="Edit" id="5QF-Oa-p0T">
|
||||
<modifierMask key="keyEquivalentModifierMask"/>
|
||||
<menu key="submenu" title="Edit" id="W48-6f-4Dl">
|
||||
<items>
|
||||
<menuItem title="Undo" keyEquivalent="z" id="dRJ-4n-Yzg">
|
||||
<connections>
|
||||
<action selector="undo:" target="-1" id="M6e-cu-g7V"/>
|
||||
</connections>
|
||||
</menuItem>
|
||||
<menuItem title="Redo" keyEquivalent="Z" id="6dh-zS-Vam">
|
||||
<connections>
|
||||
<action selector="redo:" target="-1" id="oIA-Rs-6OD"/>
|
||||
</connections>
|
||||
</menuItem>
|
||||
<menuItem isSeparatorItem="YES" id="WRV-NI-Exz"/>
|
||||
<menuItem title="Cut" keyEquivalent="x" id="uRl-iY-unG">
|
||||
<connections>
|
||||
<action selector="cut:" target="-1" id="YJe-68-I9s"/>
|
||||
</connections>
|
||||
</menuItem>
|
||||
<menuItem title="Copy" keyEquivalent="c" id="x3v-GG-iWU">
|
||||
<connections>
|
||||
<action selector="copy:" target="-1" id="G1f-GL-Joy"/>
|
||||
</connections>
|
||||
</menuItem>
|
||||
<menuItem title="Paste" keyEquivalent="v" id="gVA-U4-sdL">
|
||||
<connections>
|
||||
<action selector="paste:" target="-1" id="UvS-8e-Qdg"/>
|
||||
</connections>
|
||||
</menuItem>
|
||||
<menuItem title="Paste and Match Style" keyEquivalent="V" id="WeT-3V-zwk">
|
||||
<modifierMask key="keyEquivalentModifierMask" option="YES" command="YES"/>
|
||||
<connections>
|
||||
<action selector="pasteAsPlainText:" target="-1" id="cEh-KX-wJQ"/>
|
||||
</connections>
|
||||
</menuItem>
|
||||
<menuItem title="Delete" id="pa3-QI-u2k">
|
||||
<modifierMask key="keyEquivalentModifierMask"/>
|
||||
<connections>
|
||||
<action selector="delete:" target="-1" id="0Mk-Ml-PaM"/>
|
||||
</connections>
|
||||
</menuItem>
|
||||
<menuItem title="Select All" keyEquivalent="a" id="Ruw-6m-B2m">
|
||||
<connections>
|
||||
<action selector="selectAll:" target="-1" id="VNm-Mi-diN"/>
|
||||
</connections>
|
||||
</menuItem>
|
||||
<menuItem isSeparatorItem="YES" id="uyl-h8-XO2"/>
|
||||
<menuItem title="Find" id="4EN-yA-p0u">
|
||||
<modifierMask key="keyEquivalentModifierMask"/>
|
||||
<menu key="submenu" title="Find" id="1b7-l0-nxx">
|
||||
<items>
|
||||
<menuItem title="Find…" tag="1" keyEquivalent="f" id="Xz5-n4-O0W">
|
||||
<connections>
|
||||
<action selector="performFindPanelAction:" target="-1" id="cD7-Qs-BN4"/>
|
||||
</connections>
|
||||
</menuItem>
|
||||
<menuItem title="Find and Replace…" tag="12" keyEquivalent="f" id="YEy-JH-Tfz">
|
||||
<modifierMask key="keyEquivalentModifierMask" option="YES" command="YES"/>
|
||||
<connections>
|
||||
<action selector="performFindPanelAction:" target="-1" id="WD3-Gg-5AJ"/>
|
||||
</connections>
|
||||
</menuItem>
|
||||
<menuItem title="Find Next" tag="2" keyEquivalent="g" id="q09-fT-Sye">
|
||||
<connections>
|
||||
<action selector="performFindPanelAction:" target="-1" id="NDo-RZ-v9R"/>
|
||||
</connections>
|
||||
</menuItem>
|
||||
<menuItem title="Find Previous" tag="3" keyEquivalent="G" id="OwM-mh-QMV">
|
||||
<connections>
|
||||
<action selector="performFindPanelAction:" target="-1" id="HOh-sY-3ay"/>
|
||||
</connections>
|
||||
</menuItem>
|
||||
<menuItem title="Use Selection for Find" tag="7" keyEquivalent="e" id="buJ-ug-pKt">
|
||||
<connections>
|
||||
<action selector="performFindPanelAction:" target="-1" id="U76-nv-p5D"/>
|
||||
</connections>
|
||||
</menuItem>
|
||||
<menuItem title="Jump to Selection" keyEquivalent="j" id="S0p-oC-mLd">
|
||||
<connections>
|
||||
<action selector="centerSelectionInVisibleArea:" target="-1" id="IOG-6D-g5B"/>
|
||||
</connections>
|
||||
</menuItem>
|
||||
</items>
|
||||
</menu>
|
||||
</menuItem>
|
||||
<menuItem title="Spelling and Grammar" id="Dv1-io-Yv7">
|
||||
<modifierMask key="keyEquivalentModifierMask"/>
|
||||
<menu key="submenu" title="Spelling" id="3IN-sU-3Bg">
|
||||
<items>
|
||||
<menuItem title="Show Spelling and Grammar" keyEquivalent=":" id="HFo-cy-zxI">
|
||||
<connections>
|
||||
<action selector="showGuessPanel:" target="-1" id="vFj-Ks-hy3"/>
|
||||
</connections>
|
||||
</menuItem>
|
||||
<menuItem title="Check Document Now" keyEquivalent=";" id="hz2-CU-CR7">
|
||||
<connections>
|
||||
<action selector="checkSpelling:" target="-1" id="fz7-VC-reM"/>
|
||||
</connections>
|
||||
</menuItem>
|
||||
<menuItem isSeparatorItem="YES" id="bNw-od-mp5"/>
|
||||
<menuItem title="Check Spelling While Typing" id="rbD-Rh-wIN">
|
||||
<modifierMask key="keyEquivalentModifierMask"/>
|
||||
<connections>
|
||||
<action selector="toggleContinuousSpellChecking:" target="-1" id="7w6-Qz-0kB"/>
|
||||
</connections>
|
||||
</menuItem>
|
||||
<menuItem title="Check Grammar With Spelling" id="mK6-2p-4JG">
|
||||
<modifierMask key="keyEquivalentModifierMask"/>
|
||||
<connections>
|
||||
<action selector="toggleGrammarChecking:" target="-1" id="muD-Qn-j4w"/>
|
||||
</connections>
|
||||
</menuItem>
|
||||
<menuItem title="Correct Spelling Automatically" id="78Y-hA-62v">
|
||||
<modifierMask key="keyEquivalentModifierMask"/>
|
||||
<connections>
|
||||
<action selector="toggleAutomaticSpellingCorrection:" target="-1" id="2lM-Qi-WAP"/>
|
||||
</connections>
|
||||
</menuItem>
|
||||
</items>
|
||||
</menu>
|
||||
</menuItem>
|
||||
<menuItem title="Substitutions" id="9ic-FL-obx">
|
||||
<modifierMask key="keyEquivalentModifierMask"/>
|
||||
<menu key="submenu" title="Substitutions" id="FeM-D8-WVr">
|
||||
<items>
|
||||
<menuItem title="Show Substitutions" id="z6F-FW-3nz">
|
||||
<modifierMask key="keyEquivalentModifierMask"/>
|
||||
<connections>
|
||||
<action selector="orderFrontSubstitutionsPanel:" target="-1" id="oku-mr-iSq"/>
|
||||
</connections>
|
||||
</menuItem>
|
||||
<menuItem isSeparatorItem="YES" id="gPx-C9-uUO"/>
|
||||
<menuItem title="Smart Copy/Paste" id="9yt-4B-nSM">
|
||||
<modifierMask key="keyEquivalentModifierMask"/>
|
||||
<connections>
|
||||
<action selector="toggleSmartInsertDelete:" target="-1" id="3IJ-Se-DZD"/>
|
||||
</connections>
|
||||
</menuItem>
|
||||
<menuItem title="Smart Quotes" id="hQb-2v-fYv">
|
||||
<modifierMask key="keyEquivalentModifierMask"/>
|
||||
<connections>
|
||||
<action selector="toggleAutomaticQuoteSubstitution:" target="-1" id="ptq-xd-QOA"/>
|
||||
</connections>
|
||||
</menuItem>
|
||||
<menuItem title="Smart Dashes" id="rgM-f4-ycn">
|
||||
<modifierMask key="keyEquivalentModifierMask"/>
|
||||
<connections>
|
||||
<action selector="toggleAutomaticDashSubstitution:" target="-1" id="oCt-pO-9gS"/>
|
||||
</connections>
|
||||
</menuItem>
|
||||
<menuItem title="Smart Links" id="cwL-P1-jid">
|
||||
<modifierMask key="keyEquivalentModifierMask"/>
|
||||
<connections>
|
||||
<action selector="toggleAutomaticLinkDetection:" target="-1" id="Gip-E3-Fov"/>
|
||||
</connections>
|
||||
</menuItem>
|
||||
<menuItem title="Data Detectors" id="tRr-pd-1PS">
|
||||
<modifierMask key="keyEquivalentModifierMask"/>
|
||||
<connections>
|
||||
<action selector="toggleAutomaticDataDetection:" target="-1" id="R1I-Nq-Kbl"/>
|
||||
</connections>
|
||||
</menuItem>
|
||||
<menuItem title="Text Replacement" id="HFQ-gK-NFA">
|
||||
<modifierMask key="keyEquivalentModifierMask"/>
|
||||
<connections>
|
||||
<action selector="toggleAutomaticTextReplacement:" target="-1" id="DvP-Fe-Py6"/>
|
||||
</connections>
|
||||
</menuItem>
|
||||
</items>
|
||||
</menu>
|
||||
</menuItem>
|
||||
<menuItem title="Transformations" id="2oI-Rn-ZJC">
|
||||
<modifierMask key="keyEquivalentModifierMask"/>
|
||||
<menu key="submenu" title="Transformations" id="c8a-y6-VQd">
|
||||
<items>
|
||||
<menuItem title="Make Upper Case" id="vmV-6d-7jI">
|
||||
<modifierMask key="keyEquivalentModifierMask"/>
|
||||
<connections>
|
||||
<action selector="uppercaseWord:" target="-1" id="sPh-Tk-edu"/>
|
||||
</connections>
|
||||
</menuItem>
|
||||
<menuItem title="Make Lower Case" id="d9M-CD-aMd">
|
||||
<modifierMask key="keyEquivalentModifierMask"/>
|
||||
<connections>
|
||||
<action selector="lowercaseWord:" target="-1" id="iUZ-b5-hil"/>
|
||||
</connections>
|
||||
</menuItem>
|
||||
<menuItem title="Capitalize" id="UEZ-Bs-lqG">
|
||||
<modifierMask key="keyEquivalentModifierMask"/>
|
||||
<connections>
|
||||
<action selector="capitalizeWord:" target="-1" id="26H-TL-nsh"/>
|
||||
</connections>
|
||||
</menuItem>
|
||||
</items>
|
||||
</menu>
|
||||
</menuItem>
|
||||
<menuItem title="Speech" id="xrE-MZ-jX0">
|
||||
<modifierMask key="keyEquivalentModifierMask"/>
|
||||
<menu key="submenu" title="Speech" id="3rS-ZA-NoH">
|
||||
<items>
|
||||
<menuItem title="Start Speaking" id="Ynk-f8-cLZ">
|
||||
<modifierMask key="keyEquivalentModifierMask"/>
|
||||
<connections>
|
||||
<action selector="startSpeaking:" target="-1" id="654-Ng-kyl"/>
|
||||
</connections>
|
||||
</menuItem>
|
||||
<menuItem title="Stop Speaking" id="Oyz-dy-DGm">
|
||||
<modifierMask key="keyEquivalentModifierMask"/>
|
||||
<connections>
|
||||
<action selector="stopSpeaking:" target="-1" id="dX8-6p-jy9"/>
|
||||
</connections>
|
||||
</menuItem>
|
||||
</items>
|
||||
</menu>
|
||||
</menuItem>
|
||||
</items>
|
||||
</menu>
|
||||
</menuItem>
|
||||
<menuItem title="View" id="H8h-7b-M4v">
|
||||
<modifierMask key="keyEquivalentModifierMask"/>
|
||||
<menu key="submenu" title="View" id="HyV-fh-RgO">
|
||||
<items>
|
||||
<menuItem title="Enter Full Screen" keyEquivalent="f" id="4J7-dP-txa">
|
||||
<modifierMask key="keyEquivalentModifierMask" control="YES" command="YES"/>
|
||||
<connections>
|
||||
<action selector="toggleFullScreen:" target="-1" id="dU3-MA-1Rq"/>
|
||||
</connections>
|
||||
</menuItem>
|
||||
</items>
|
||||
</menu>
|
||||
</menuItem>
|
||||
<menuItem title="Window" id="aUF-d1-5bR">
|
||||
<modifierMask key="keyEquivalentModifierMask"/>
|
||||
<menu key="submenu" title="Window" systemMenu="window" id="Td7-aD-5lo">
|
||||
<items>
|
||||
<menuItem title="Minimize" keyEquivalent="m" id="OY7-WF-poV">
|
||||
<connections>
|
||||
<action selector="performMiniaturize:" target="-1" id="VwT-WD-YPe"/>
|
||||
</connections>
|
||||
</menuItem>
|
||||
<menuItem title="Zoom" id="R4o-n2-Eq4">
|
||||
<modifierMask key="keyEquivalentModifierMask"/>
|
||||
<connections>
|
||||
<action selector="performZoom:" target="-1" id="DIl-cC-cCs"/>
|
||||
</connections>
|
||||
</menuItem>
|
||||
<menuItem isSeparatorItem="YES" id="eu3-7i-yIM"/>
|
||||
<menuItem title="Bring All to Front" id="LE2-aR-0XJ">
|
||||
<modifierMask key="keyEquivalentModifierMask"/>
|
||||
<connections>
|
||||
<action selector="arrangeInFront:" target="-1" id="DRN-fu-gQh"/>
|
||||
</connections>
|
||||
</menuItem>
|
||||
</items>
|
||||
</menu>
|
||||
</menuItem>
|
||||
<menuItem title="Help" id="EPT-qC-fAb">
|
||||
<modifierMask key="keyEquivalentModifierMask"/>
|
||||
<menu key="submenu" title="Help" systemMenu="help" id="rJ0-wn-3NY"/>
|
||||
</menuItem>
|
||||
</items>
|
||||
<point key="canvasLocation" x="142" y="-258"/>
|
||||
</menu>
|
||||
<window title="APP_NAME" allowsToolTipsWhenApplicationIsInactive="NO" autorecalculatesKeyViewLoop="NO" releasedWhenClosed="NO" animationBehavior="default" id="QvC-M9-y7g" customClass="MainFlutterWindow" customModule="Runner" customModuleProvider="target">
|
||||
<windowStyleMask key="styleMask" titled="YES" closable="YES" miniaturizable="YES" resizable="YES"/>
|
||||
<rect key="contentRect" x="335" y="390" width="800" height="600"/>
|
||||
<rect key="screenRect" x="0.0" y="0.0" width="2560" height="1577"/>
|
||||
<view key="contentView" wantsLayer="YES" id="EiT-Mj-1SZ">
|
||||
<rect key="frame" x="0.0" y="0.0" width="800" height="600"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
</view>
|
||||
</window>
|
||||
</objects>
|
||||
</document>
|
||||
@@ -0,0 +1,14 @@
|
||||
// Application-level settings for the Runner target.
|
||||
//
|
||||
// This may be replaced with something auto-generated from metadata (e.g., pubspec.yaml) in the
|
||||
// future. If not, the values below would default to using the project name when this becomes a
|
||||
// 'flutter create' template.
|
||||
|
||||
// The application's name. By default this is also the title of the Flutter window.
|
||||
PRODUCT_NAME = appflowy_ui_example
|
||||
|
||||
// The application's bundle identifier
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.example.appflowyUiExample
|
||||
|
||||
// The copyright displayed in application information
|
||||
PRODUCT_COPYRIGHT = Copyright © 2025 com.example. All rights reserved.
|
||||
@@ -0,0 +1,2 @@
|
||||
#include "../../Flutter/Flutter-Debug.xcconfig"
|
||||
#include "Warnings.xcconfig"
|
||||
@@ -0,0 +1,2 @@
|
||||
#include "../../Flutter/Flutter-Release.xcconfig"
|
||||
#include "Warnings.xcconfig"
|
||||
@@ -0,0 +1,13 @@
|
||||
WARNING_CFLAGS = -Wall -Wconditional-uninitialized -Wnullable-to-nonnull-conversion -Wmissing-method-return-type -Woverlength-strings
|
||||
GCC_WARN_UNDECLARED_SELECTOR = YES
|
||||
CLANG_UNDEFINED_BEHAVIOR_SANITIZER_NULLABILITY = YES
|
||||
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE
|
||||
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES
|
||||
CLANG_WARN_PRAGMA_PACK = YES
|
||||
CLANG_WARN_STRICT_PROTOTYPES = YES
|
||||
CLANG_WARN_COMMA = YES
|
||||
GCC_WARN_STRICT_SELECTOR_MATCH = YES
|
||||
CLANG_WARN_OBJC_REPEATED_USE_OF_WEAK = YES
|
||||
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES
|
||||
GCC_WARN_SHADOW = YES
|
||||
CLANG_WARN_UNREACHABLE_CODE = YES
|
||||
@@ -0,0 +1,12 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>com.apple.security.app-sandbox</key>
|
||||
<true/>
|
||||
<key>com.apple.security.cs.allow-jit</key>
|
||||
<true/>
|
||||
<key>com.apple.security.network.server</key>
|
||||
<true/>
|
||||
</dict>
|
||||
</plist>
|
||||
@@ -0,0 +1,32 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>CFBundleDevelopmentRegion</key>
|
||||
<string>$(DEVELOPMENT_LANGUAGE)</string>
|
||||
<key>CFBundleExecutable</key>
|
||||
<string>$(EXECUTABLE_NAME)</string>
|
||||
<key>CFBundleIconFile</key>
|
||||
<string></string>
|
||||
<key>CFBundleIdentifier</key>
|
||||
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
|
||||
<key>CFBundleInfoDictionaryVersion</key>
|
||||
<string>6.0</string>
|
||||
<key>CFBundleName</key>
|
||||
<string>$(PRODUCT_NAME)</string>
|
||||
<key>CFBundlePackageType</key>
|
||||
<string>APPL</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>$(FLUTTER_BUILD_NAME)</string>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>$(FLUTTER_BUILD_NUMBER)</string>
|
||||
<key>LSMinimumSystemVersion</key>
|
||||
<string>$(MACOSX_DEPLOYMENT_TARGET)</string>
|
||||
<key>NSHumanReadableCopyright</key>
|
||||
<string>$(PRODUCT_COPYRIGHT)</string>
|
||||
<key>NSMainNibFile</key>
|
||||
<string>MainMenu</string>
|
||||
<key>NSPrincipalClass</key>
|
||||
<string>NSApplication</string>
|
||||
</dict>
|
||||
</plist>
|
||||
@@ -0,0 +1,15 @@
|
||||
import Cocoa
|
||||
import FlutterMacOS
|
||||
|
||||
class MainFlutterWindow: NSWindow {
|
||||
override func awakeFromNib() {
|
||||
let flutterViewController = FlutterViewController()
|
||||
let windowFrame = self.frame
|
||||
self.contentViewController = flutterViewController
|
||||
self.setFrame(windowFrame, display: true)
|
||||
|
||||
RegisterGeneratedPlugins(registry: flutterViewController)
|
||||
|
||||
super.awakeFromNib()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>com.apple.security.app-sandbox</key>
|
||||
<true/>
|
||||
</dict>
|
||||
</plist>
|
||||
@@ -0,0 +1,12 @@
|
||||
import Cocoa
|
||||
import FlutterMacOS
|
||||
import XCTest
|
||||
|
||||
class RunnerTests: XCTestCase {
|
||||
|
||||
func testExample() {
|
||||
// If you add code to the Runner application, consider adding tests here.
|
||||
// See https://developer.apple.com/documentation/xctest for more information about using XCTest.
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
name: appflowy_ui_example
|
||||
description: "Example app showcasing AppFlowy UI components and widgets"
|
||||
publish_to: "none"
|
||||
|
||||
version: 1.0.0+1
|
||||
|
||||
environment:
|
||||
flutter: ">=3.27.4"
|
||||
sdk: ">=3.3.0 <4.0.0"
|
||||
|
||||
dependencies:
|
||||
flutter:
|
||||
sdk: flutter
|
||||
appflowy_ui:
|
||||
path: ../
|
||||
cupertino_icons: ^1.0.6
|
||||
|
||||
dev_dependencies:
|
||||
flutter_test:
|
||||
sdk: flutter
|
||||
flutter_lints: ^5.0.0
|
||||
|
||||
flutter:
|
||||
uses-material-design: true
|
||||
@@ -0,0 +1,30 @@
|
||||
// This is a basic Flutter widget test.
|
||||
//
|
||||
// To perform an interaction with a widget in your test, use the WidgetTester
|
||||
// utility in the flutter_test package. For example, you can send tap and scroll
|
||||
// gestures. You can also use WidgetTester to find child widgets in the widget
|
||||
// tree, read text, and verify that the values of widget properties are correct.
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
|
||||
import 'package:appflowy_ui_example/main.dart';
|
||||
|
||||
void main() {
|
||||
testWidgets('Counter increments smoke test', (WidgetTester tester) async {
|
||||
// Build our app and trigger a frame.
|
||||
await tester.pumpWidget(const MyApp());
|
||||
|
||||
// Verify that our counter starts at 0.
|
||||
expect(find.text('0'), findsOneWidget);
|
||||
expect(find.text('1'), findsNothing);
|
||||
|
||||
// Tap the '+' icon and trigger a frame.
|
||||
await tester.tap(find.byIcon(Icons.add));
|
||||
await tester.pump();
|
||||
|
||||
// Verify that our counter has incremented.
|
||||
expect(find.text('0'), findsNothing);
|
||||
expect(find.text('1'), findsOneWidget);
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
export 'src/component/component.dart';
|
||||
export 'src/theme/theme.dart';
|
||||
@@ -0,0 +1,54 @@
|
||||
import 'package:appflowy_ui/src/theme/appflowy_theme.dart';
|
||||
import 'package:flutter/widgets.dart';
|
||||
|
||||
enum AFButtonSize {
|
||||
s,
|
||||
m,
|
||||
l,
|
||||
xl;
|
||||
|
||||
TextStyle buildTextStyle(BuildContext context) {
|
||||
final theme = AppFlowyTheme.of(context);
|
||||
|
||||
return switch (this) {
|
||||
AFButtonSize.s => theme.textStyle.body.enhanced(),
|
||||
AFButtonSize.m => theme.textStyle.body.enhanced(),
|
||||
AFButtonSize.l => theme.textStyle.body.enhanced(),
|
||||
AFButtonSize.xl => theme.textStyle.title.enhanced(),
|
||||
};
|
||||
}
|
||||
|
||||
EdgeInsetsGeometry buildPadding(BuildContext context) {
|
||||
final theme = AppFlowyTheme.of(context);
|
||||
|
||||
return switch (this) {
|
||||
AFButtonSize.s => EdgeInsets.symmetric(
|
||||
horizontal: theme.spacing.l,
|
||||
vertical: theme.spacing.xs,
|
||||
),
|
||||
AFButtonSize.m => EdgeInsets.symmetric(
|
||||
horizontal: theme.spacing.l,
|
||||
vertical: theme.spacing.s,
|
||||
),
|
||||
AFButtonSize.l => EdgeInsets.symmetric(
|
||||
horizontal: theme.spacing.xl,
|
||||
vertical: 10, // why?
|
||||
),
|
||||
AFButtonSize.xl => EdgeInsets.symmetric(
|
||||
horizontal: theme.spacing.xl,
|
||||
vertical: 14, // why?
|
||||
),
|
||||
};
|
||||
}
|
||||
|
||||
double buildBorderRadius(BuildContext context) {
|
||||
final theme = AppFlowyTheme.of(context);
|
||||
|
||||
return switch (this) {
|
||||
AFButtonSize.s => theme.borderRadius.m,
|
||||
AFButtonSize.m => theme.borderRadius.m,
|
||||
AFButtonSize.l => 10, // why?
|
||||
AFButtonSize.xl => theme.borderRadius.xl,
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,79 @@
|
||||
import 'package:appflowy_ui/src/theme/appflowy_theme.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
typedef AFBaseButtonColorBuilder = Color Function(
|
||||
BuildContext context,
|
||||
bool isHovering,
|
||||
bool disabled,
|
||||
);
|
||||
|
||||
class AFBaseButton extends StatefulWidget {
|
||||
const AFBaseButton({
|
||||
super.key,
|
||||
required this.onTap,
|
||||
required this.builder,
|
||||
required this.padding,
|
||||
required this.borderRadius,
|
||||
this.borderColor,
|
||||
this.backgroundColor,
|
||||
this.disabled = false,
|
||||
});
|
||||
|
||||
final VoidCallback? onTap;
|
||||
|
||||
final AFBaseButtonColorBuilder? borderColor;
|
||||
final AFBaseButtonColorBuilder? backgroundColor;
|
||||
|
||||
final EdgeInsetsGeometry padding;
|
||||
final double borderRadius;
|
||||
final bool disabled;
|
||||
|
||||
final Widget Function(BuildContext context, bool isHovering, bool disabled)
|
||||
builder;
|
||||
|
||||
@override
|
||||
State<AFBaseButton> createState() => _AFBaseButtonState();
|
||||
}
|
||||
|
||||
class _AFBaseButtonState extends State<AFBaseButton> {
|
||||
bool isHovering = false;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final Color borderColor = _buildBorderColor(context);
|
||||
final Color backgroundColor = _buildBackgroundColor(context);
|
||||
|
||||
return MouseRegion(
|
||||
cursor:
|
||||
widget.disabled ? SystemMouseCursors.basic : SystemMouseCursors.click,
|
||||
onEnter: (_) => setState(() => isHovering = true),
|
||||
onExit: (_) => setState(() => isHovering = false),
|
||||
child: GestureDetector(
|
||||
onTap: widget.disabled ? null : widget.onTap,
|
||||
child: DecoratedBox(
|
||||
decoration: BoxDecoration(
|
||||
color: backgroundColor,
|
||||
border: Border.all(color: borderColor),
|
||||
borderRadius: BorderRadius.circular(widget.borderRadius),
|
||||
),
|
||||
child: Padding(
|
||||
padding: widget.padding,
|
||||
child: widget.builder(context, isHovering, widget.disabled),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Color _buildBorderColor(BuildContext context) {
|
||||
final theme = AppFlowyTheme.of(context);
|
||||
return widget.borderColor?.call(context, isHovering, widget.disabled) ??
|
||||
theme.borderColorScheme.greyTertiary;
|
||||
}
|
||||
|
||||
Color _buildBackgroundColor(BuildContext context) {
|
||||
final theme = AppFlowyTheme.of(context);
|
||||
return widget.backgroundColor?.call(context, isHovering, widget.disabled) ??
|
||||
theme.fillColorScheme.transparent;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
import 'package:appflowy_ui/appflowy_ui.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class AFBaseTextButton extends StatelessWidget {
|
||||
const AFBaseTextButton({
|
||||
super.key,
|
||||
required this.text,
|
||||
required this.onTap,
|
||||
this.disabled = false,
|
||||
this.size = AFButtonSize.m,
|
||||
this.padding,
|
||||
this.borderRadius,
|
||||
this.textColor,
|
||||
this.backgroundColor,
|
||||
this.alignment,
|
||||
});
|
||||
|
||||
/// The text of the button.
|
||||
final String text;
|
||||
|
||||
/// Whether the button is disabled.
|
||||
final bool disabled;
|
||||
|
||||
/// The callback when the button is tapped.
|
||||
final VoidCallback onTap;
|
||||
|
||||
/// The size of the button.
|
||||
final AFButtonSize size;
|
||||
|
||||
/// The padding of the button.
|
||||
final EdgeInsetsGeometry? padding;
|
||||
|
||||
/// The border radius of the button.
|
||||
final double? borderRadius;
|
||||
|
||||
/// The text color of the button.
|
||||
final AFBaseButtonColorBuilder? textColor;
|
||||
|
||||
/// The background color of the button.
|
||||
final AFBaseButtonColorBuilder? backgroundColor;
|
||||
|
||||
/// The alignment of the button.
|
||||
///
|
||||
/// If it's null, the button size will be the size of the text with padding.
|
||||
final Alignment? alignment;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
throw UnimplementedError();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
// Base button
|
||||
export 'base_button/base.dart';
|
||||
export 'base_button/base_button.dart';
|
||||
export 'base_button/base_text_button.dart';
|
||||
// Filled buttons
|
||||
export 'filled_button/filled_button.dart';
|
||||
export 'filled_button/filled_icon_text_button.dart';
|
||||
export 'filled_button/filled_text_button.dart';
|
||||
// Ghost buttons
|
||||
export 'ghost_button/ghost_button.dart';
|
||||
export 'ghost_button/ghost_icon_text_button.dart';
|
||||
export 'ghost_button/ghost_text_button.dart';
|
||||
// Outlined buttons
|
||||
export 'outlined_button/outlined_button.dart';
|
||||
export 'outlined_button/outlined_icon_text_button.dart';
|
||||
export 'outlined_button/outlined_text_button.dart';
|
||||