Upgrade Flutter

This commit is contained in:
Hillel Coren 2023-02-26 17:27:06 +02:00
parent a9393134b8
commit ca278c046a
26 changed files with 1180 additions and 654 deletions

View File

@ -4,7 +4,7 @@
# This file should be version controlled.
version:
revision: 4f9d92fbbdf072a70a70d2179a9f87392b94104c
revision: c07f7888888435fd9df505aa2efc38d3cf65681b
channel: stable
project_type: app
@ -13,11 +13,11 @@ project_type: app
migration:
platforms:
- platform: root
create_revision: 4f9d92fbbdf072a70a70d2179a9f87392b94104c
base_revision: 4f9d92fbbdf072a70a70d2179a9f87392b94104c
- platform: macos
create_revision: 4f9d92fbbdf072a70a70d2179a9f87392b94104c
base_revision: 4f9d92fbbdf072a70a70d2179a9f87392b94104c
create_revision: c07f7888888435fd9df505aa2efc38d3cf65681b
base_revision: c07f7888888435fd9df505aa2efc38d3cf65681b
- platform: linux
create_revision: c07f7888888435fd9df505aa2efc38d3cf65681b
base_revision: c07f7888888435fd9df505aa2efc38d3cf65681b
# User provided section

View File

@ -153,9 +153,12 @@ class InvoiceNinjaAppState extends State<InvoiceNinjaApp> {
try {
authenticated = await LocalAuthentication().authenticate(
localizedReason: 'Please authenticate to access the app',
options: const AuthenticationOptions(
biometricOnly: true,
useErrorDialogs: true,
stickyAuth: false);
stickyAuth: false,
),
);
} catch (e) {
print(e);
}
@ -384,6 +387,11 @@ class InvoiceNinjaAppState extends State<InvoiceNinjaApp> {
? LockScreen(onAuthenticatePressed: _authenticate)
: InitScreen(),
locale: locale,
/*
theme: state.prefState.enableDarkMode
? ThemeData.dark(useMaterial3: true)
: ThemeData.light(useMaterial3: true),
*/
theme: state.prefState.enableDarkMode
? ThemeData(
colorScheme: ColorScheme.dark().copyWith(

View File

@ -14,7 +14,7 @@ import 'package:image_cropper/image_cropper.dart';
import 'package:image_picker/image_picker.dart';
import 'package:path_provider/path_provider.dart';
import 'package:permission_handler/permission_handler.dart';
import 'package:share/share.dart';
import 'package:share_plus/share_plus.dart';
import 'package:url_launcher/url_launcher.dart';
// Project imports:
@ -227,7 +227,7 @@ class DocumentTile extends StatelessWidget {
await File(filePath)
.writeAsBytes(response.bodyBytes);
await Share.shareFiles([filePath]);
await Share.shareXFiles([XFile(filePath)]);
}
} else if (value == localization.delete) {
confirmCallback(

View File

@ -1400,7 +1400,7 @@ void _showAbout(BuildContext context) async {
padding: const EdgeInsets.only(top: 4),
child: AppButton(
label: localization.appPlatforms.toUpperCase(),
iconData: MdiIcons.desktopMac,
iconData: MdiIcons.desktopClassic,
onPressed: () {
showDialog<AlertDialog>(
context: context,

View File

@ -22,7 +22,6 @@ import 'package:invoiceninja_flutter/ui/app/forms/date_picker.dart';
import 'package:invoiceninja_flutter/utils/formatting.dart';
import 'package:path_provider/path_provider.dart';
import 'package:printing/printing.dart';
import 'package:share/share.dart';
// Project imports:
import 'package:invoiceninja_flutter/data/models/dashboard_model.dart';
@ -40,6 +39,7 @@ import 'package:invoiceninja_flutter/utils/platforms.dart';
import 'package:invoiceninja_flutter/utils/web_stub.dart'
if (dart.library.html) 'package:invoiceninja_flutter/utils/web.dart';
import 'package:share_plus/share_plus.dart';
class ClientPdfView extends StatefulWidget {
const ClientPdfView({
@ -404,7 +404,7 @@ class _ClientPdfViewState extends State<ClientPdfView> {
showToast(
localization.fileSavedInDownloadsFolder);
} else {
await Share.shareFiles([filePath]);
await Share.shareXFiles([XFile(filePath)]);
}
}
},
@ -439,6 +439,7 @@ class _ClientPdfViewState extends State<ClientPdfView> {
callback: (_) => loadPDF(sendEmail: true));
},
),
if (supportsSchedules())
TextButton(
onPressed: () {
if (!state.isProPlan) {
@ -452,8 +453,8 @@ class _ClientPdfViewState extends State<ClientPdfView> {
section: kSettingsAccountManagement));
Navigator.of(context).pop();
},
child:
Text(localization.upgrade.toUpperCase())),
child: Text(
localization.upgrade.toUpperCase())),
]);
return;
}
@ -461,7 +462,8 @@ class _ClientPdfViewState extends State<ClientPdfView> {
createEntity(
context: context,
entity: ScheduleEntity().rebuild((b) => b
..template = ScheduleEntity.TEMPLATE_EMAIL_STATEMENT
..template =
ScheduleEntity.TEMPLATE_EMAIL_STATEMENT
..parameters.clients.add(client.id)
..parameters.showAgingTable = _showAging
..parameters.showPaymentsTable = _showPayments

View File

@ -16,7 +16,7 @@ import 'package:invoiceninja_flutter/main_app.dart';
import 'package:invoiceninja_flutter/ui/app/dialogs/error_dialog.dart';
import 'package:path_provider/path_provider.dart';
import 'package:printing/printing.dart';
import 'package:share/share.dart';
import 'package:share_plus/share_plus.dart';
import 'package:url_launcher/url_launcher.dart';
// Project imports:
@ -282,7 +282,7 @@ class _InvoicePdfViewState extends State<InvoicePdfView> {
showToast(localization
.fileSavedInDownloadsFolder);
} else {
await Share.shareFiles([filePath]);
await Share.shareXFiles([XFile(filePath)]);
}
}
}

View File

@ -21,7 +21,6 @@ import 'package:invoiceninja_flutter/utils/platforms.dart';
import 'package:memoize/memoize.dart';
import 'package:path_provider/path_provider.dart';
import 'package:redux/redux.dart';
import 'package:share/share.dart';
// Project imports:
import 'package:invoiceninja_flutter/constants.dart';
@ -51,6 +50,7 @@ import 'package:invoiceninja_flutter/utils/formatting.dart';
import 'package:invoiceninja_flutter/utils/localization.dart';
import 'package:invoiceninja_flutter/utils/money.dart';
import 'package:invoiceninja_flutter/utils/strings.dart';
import 'package:share_plus/share_plus.dart';
import 'credit_report.dart';
import 'package:invoiceninja_flutter/utils/web_stub.dart'
@ -515,7 +515,7 @@ class ReportsScreenVM {
if (isDesktopOS()) {
showToast(localization.fileSavedInDownloadsFolder);
} else {
await Share.shareFiles([filePath]);
await Share.shareXFiles([XFile(filePath)]);
}
}
});

View File

@ -152,9 +152,10 @@ class DeviceSettingsVM {
authenticated = await LocalAuthentication().authenticate(
localizedReason:
AppLocalization.of(context).authenticateToChangeSetting,
options: const AuthenticationOptions(
biometricOnly: true,
useErrorDialogs: true,
stickyAuth: false);
stickyAuth: false));
} catch (e) {
print(e);
}

View File

@ -240,6 +240,7 @@ class _SettingsListState extends State<SettingsList> {
),
*/
if (showAll) ...[
if (supportsSchedules())
SettingsListTile(
section: kSettingsSchedules,
viewModel: widget.viewModel,
@ -623,6 +624,7 @@ class SettingsSearch extends StatelessWidget {
'subscriptions',
],
],
if (supportsSchedules())
kSettingsSchedules: [
[
'schedules#2023-02-15',

View File

@ -31,6 +31,12 @@ bool supportsAppleOAuth() => kIsWeb || isApple();
// TODO remove this function
bool supportsMicrosoftOAuth() => kIsWeb;
// TODO remove this
bool supportsSchedules() {
final store = StoreProvider.of<AppState>(navigatorKey.currentContext);
return store.state.isSelfHosted;
}
bool supportsInAppPurchase() {
final store = StoreProvider.of<AppState>(navigatorKey.currentContext);
if (store.state.isSelfHosted) {

View File

@ -34,12 +34,20 @@ class _ExampleEditorState extends State<ExampleEditor> {
ScrollController _scrollController;
final _darkBackground = const Color(0xFF222222);
final _lightBackground = Colors.white;
Brightness _brightness = Brightness.light;
SuperEditorDebugVisualsConfig _debugConfig;
OverlayEntry _textFormatBarOverlayEntry;
final _textSelectionAnchor = ValueNotifier<Offset>(null);
OverlayEntry _imageFormatBarOverlayEntry;
final _imageSelectionAnchor = ValueNotifier<Offset>(null);
final _overlayController = MagnifierAndToolbarController();
@override
void initState() {
super.initState();
@ -55,8 +63,10 @@ class _ExampleEditorState extends State<ExampleEditor> {
_doc = deserializeMarkdownToDocument(markdown)
..addListener(_hideOrShowToolbar)
..addListener(_onChanged);
_docEditor = DocumentEditor(document: _doc as MutableDocument);
_composer = DocumentComposer()..addListener(_hideOrShowToolbar);
_composer = DocumentComposer();
_composer.selectionNotifier.addListener(_hideOrShowToolbar);
_docOps = CommonEditorOperations(
editor: _docEditor,
composer: _composer,
@ -169,15 +179,16 @@ class _ExampleEditorState extends State<ExampleEditor> {
void _showEditorToolbar() {
if (_textFormatBarOverlayEntry == null) {
// Create an overlay entry to build the editor toolbar.
// TODO: add an overlay to the Editor widget to avoid using the
// application overlay
_textFormatBarOverlayEntry ??= OverlayEntry(builder: (context) {
return Theme(
data: ThemeData.light(),
child: EditorToolbar(
return EditorToolbar(
anchor: _textSelectionAnchor,
editorFocusNode: _editorFocusNode,
editor: _docEditor,
composer: _composer,
closeToolbar: _hideEditorToolbar,
),
);
});
@ -199,7 +210,6 @@ class _ExampleEditorState extends State<ExampleEditor> {
_composer.selection.base, _composer.selection.extent);
final docBox =
_docLayoutKey.currentContext.findRenderObject() as RenderBox;
final overlayBoundingBox = Rect.fromPoints(
docBox.localToGlobal(docBoundingBox.topLeft),
docBox.localToGlobal(docBoundingBox.bottomRight),
@ -222,7 +232,6 @@ class _ExampleEditorState extends State<ExampleEditor> {
// and non-null implies the entry is in the overlay.
_textFormatBarOverlayEntry.remove();
_textFormatBarOverlayEntry = null;
}
// Ensure that focus returns to the editor.
//
@ -231,6 +240,7 @@ class _ExampleEditorState extends State<ExampleEditor> {
// editor. I'm not sure why.
_editorFocusNode.requestFocus();
}
}
DocumentGestureMode get _gestureMode {
switch (defaultTargetPlatform) {
@ -245,29 +255,41 @@ class _ExampleEditorState extends State<ExampleEditor> {
return DocumentGestureMode.mouse;
}
return null;
return DocumentGestureMode.mouse;
}
bool get _isMobile => _gestureMode != DocumentGestureMode.mouse;
DocumentInputSource get _inputSource {
TextInputSource get _inputSource {
switch (defaultTargetPlatform) {
case TargetPlatform.android:
case TargetPlatform.iOS:
return DocumentInputSource.ime;
case TargetPlatform.fuchsia:
case TargetPlatform.linux:
case TargetPlatform.macOS:
case TargetPlatform.windows:
return DocumentInputSource.keyboard;
return TextInputSource.ime;
// return DocumentInputSource.keyboard;
}
return null;
return TextInputSource.ime;
}
void _cut() {
_docOps.cut();
_overlayController.hideToolbar();
}
void _copy() {
_docOps.copy();
_overlayController.hideToolbar();
}
void _paste() {
_docOps.paste();
_overlayController.hideToolbar();
}
void _cut() => _docOps.cut();
void _copy() => _docOps.copy();
void _paste() => _docOps.paste();
void _selectAll() => _docOps.selectAll();
void _showImageToolbar() {
@ -277,7 +299,7 @@ class _ExampleEditorState extends State<ExampleEditor> {
return ImageFormatToolbar(
anchor: _imageSelectionAnchor,
composer: _composer,
setWidth: (dynamic nodeId, dynamic width) {
setWidth: (nodeId, width) {
final node = _doc.getNodeById(nodeId);
final currentStyles =
SingleColumnLayoutComponentStyles.fromMetadata(node);
@ -309,10 +331,8 @@ class _ExampleEditorState extends State<ExampleEditor> {
final docBox =
_docLayoutKey.currentContext.findRenderObject() as RenderBox;
final overlayBoundingBox = Rect.fromPoints(
docBox.localToGlobal(docBoundingBox.topLeft,
ancestor: context.findRenderObject()),
docBox.localToGlobal(docBoundingBox.bottomRight,
ancestor: context.findRenderObject()),
docBox.localToGlobal(docBoundingBox.topLeft),
docBox.localToGlobal(docBoundingBox.bottomRight),
);
_imageSelectionAnchor.value = overlayBoundingBox.center;
@ -332,36 +352,139 @@ class _ExampleEditorState extends State<ExampleEditor> {
// and non-null implies the entry is in the overlay.
_imageFormatBarOverlayEntry.remove();
_imageFormatBarOverlayEntry = null;
}
// Ensure that focus returns to the editor.
_editorFocusNode.requestFocus();
}
}
@override
Widget build(BuildContext context) {
return Column(
return Theme(
data: ThemeData(brightness: _brightness),
child: Builder(builder: (themedContext) {
// This builder captures the new theme
return Stack(
children: [
Column(
children: [
Expanded(
child: _buildEditor(),
child: _buildEditor(themedContext),
),
if (_isMobile) _buildMountedToolbar(),
],
),
Align(
alignment: Alignment.bottomRight,
child: _buildCornerFabs(),
),
],
);
}),
);
}
Widget _buildEditor() {
return SuperEditor(
Widget _buildCornerFabs() {
return Padding(
padding: const EdgeInsets.only(right: 16, bottom: 16),
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
_buildDebugVisualsToggle(),
const SizedBox(height: 16),
_buildLightAndDarkModeToggle(),
],
),
);
}
Widget _buildDebugVisualsToggle() {
return FloatingActionButton(
backgroundColor:
_brightness == Brightness.light ? _darkBackground : _lightBackground,
foregroundColor:
_brightness == Brightness.light ? _lightBackground : _darkBackground,
elevation: 5,
onPressed: () {
setState(() {
_debugConfig = _debugConfig != null
? null
: SuperEditorDebugVisualsConfig(
showFocus: true,
showImeConnection: true,
);
});
},
child: const Icon(
Icons.bug_report,
),
);
}
Widget _buildLightAndDarkModeToggle() {
return FloatingActionButton(
backgroundColor:
_brightness == Brightness.light ? _darkBackground : _lightBackground,
foregroundColor:
_brightness == Brightness.light ? _lightBackground : _darkBackground,
elevation: 5,
onPressed: () {
setState(() {
_brightness = _brightness == Brightness.light
? Brightness.dark
: Brightness.light;
});
},
child: _brightness == Brightness.light
? const Icon(
Icons.dark_mode,
)
: const Icon(
Icons.light_mode,
),
);
}
Widget _buildEditor(BuildContext context) {
final isLight = Theme.of(context).brightness == Brightness.light;
return ColoredBox(
color: isLight ? _lightBackground : _darkBackground,
child: SuperEditorDebugVisuals(
config: _debugConfig ?? const SuperEditorDebugVisualsConfig(),
child: SuperEditor(
editor: _docEditor,
composer: _composer,
focusNode: _editorFocusNode,
scrollController: _scrollController,
documentLayoutKey: _docLayoutKey,
documentOverlayBuilders: [
DefaultCaretOverlayBuilder(
CaretStyle()
.copyWith(color: isLight ? Colors.black : Colors.redAccent),
),
],
selectionStyle: isLight
? defaultSelectionStyle
: SelectionStyles(
selectionColor: Colors.red.withOpacity(0.3),
),
stylesheet: defaultStylesheet.copyWith(
addRulesAfter: [
if (!isLight) ..._darkModeStyles,
taskStyles,
],
),
componentBuilders: [
TaskComponentBuilder(_docEditor),
...defaultComponentBuilders,
],
gestureMode: _gestureMode,
inputSource: _inputSource,
keyboardActions: _inputSource == TextInputSource.ime
? defaultImeKeyboardActions
: defaultKeyboardActions,
androidToolbarBuilder: (_) => AndroidTextEditingFloatingToolbar(
onCutPressed: _cut,
onCopyPressed: _copy,
@ -373,6 +496,9 @@ class _ExampleEditorState extends State<ExampleEditor> {
onCopyPressed: _copy,
onPastePressed: _paste,
),
overlayController: _overlayController,
),
),
);
}
@ -398,3 +524,37 @@ class _ExampleEditorState extends State<ExampleEditor> {
);
}
}
// Makes text light, for use during dark mode styling.
final _darkModeStyles = [
StyleRule(
BlockSelector.all,
(doc, docNode) {
return <String, dynamic>{
'textStyle': const TextStyle(
color: Color(0xFFCCCCCC),
),
};
},
),
StyleRule(
const BlockSelector("header1"),
(doc, docNode) {
return <String, dynamic>{
'textStyle': const TextStyle(
color: Color(0xFF888888),
),
};
},
),
StyleRule(
const BlockSelector("header2"),
(doc, docNode) {
return <String, dynamic>{
'textStyle': const TextStyle(
color: Color(0xFF888888),
),
};
},
),
];

View File

@ -14,6 +14,7 @@ class EditorToolbar extends StatefulWidget {
const EditorToolbar({
Key key,
@required this.anchor,
@required this.editorFocusNode,
@required this.editor,
@required this.composer,
@required this.closeToolbar,
@ -26,6 +27,9 @@ class EditorToolbar extends StatefulWidget {
/// reposition itself as the [Offset] value changes.
final ValueNotifier<Offset> anchor;
/// The [FocusNode] attached to the editor to which this toolbar applies.
final FocusNode editorFocusNode;
/// The [editor] is used to alter document content, such as
/// when the user selects a different block format for a
/// text blob, e.g., paragraph, header, blockquote, or
@ -49,13 +53,13 @@ class EditorToolbar extends StatefulWidget {
class _EditorToolbarState extends State<EditorToolbar> {
bool _showUrlField = false;
FocusNode _urlFocusNode;
TextEditingController _urlController;
AttributedTextEditingController _urlController;
@override
void initState() {
super.initState();
_urlFocusNode = FocusNode();
_urlController = TextEditingController();
_urlController = SingleLineAttributedTextEditingController(_applyLink);
}
@override
@ -352,7 +356,7 @@ class _EditorToolbarState extends State<EditorToolbar> {
/// Takes the text from the [urlController] and applies it as a link
/// attribution to the currently selected text.
void _applyLink() {
final url = _urlController.text;
final url = _urlController.text.text;
final selection = widget.composer.selection;
final baseOffset = (selection.base.nodePosition as TextPosition).offset;
@ -360,7 +364,7 @@ class _EditorToolbarState extends State<EditorToolbar> {
final selectionStart = min(baseOffset, extentOffset);
final selectionEnd = max(baseOffset, extentOffset);
final selectionRange =
SpanRange(start: selectionStart, end: selectionEnd - 1);
TextRange(start: selectionStart, end: selectionEnd - 1);
final textNode =
widget.editor.document.getNodeById(selection.extent.nodeId) as TextNode;
@ -387,7 +391,7 @@ class _EditorToolbarState extends State<EditorToolbar> {
/// Given [text] and a [range] within the [text], the [range] is
/// shortened on both sides to remove any trailing whitespace and
/// the new range is returned.
SpanRange _trimTextRangeWhitespace(AttributedText text, SpanRange range) {
SpanRange _trimTextRangeWhitespace(AttributedText text, TextRange range) {
int startOffset = range.start;
int endOffset = range.end;
@ -504,6 +508,9 @@ class _EditorToolbarState extends State<EditorToolbar> {
child: Row(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
/*
// https://github.com/superlistapp/super_editor/issues/689
// https://github.com/flutter/flutter/issues/106923
// Only allow the user to select a new type of text node if
// the currently selected node can be converted.
if (_isConvertibleNode()) ...[
@ -512,7 +519,6 @@ class _EditorToolbarState extends State<EditorToolbar> {
child: DropdownButton<_TextType>(
value: _getCurrentTextType(),
items: _TextType.values
.where((element) => element != _TextType.blockquote)
.map((textType) => DropdownMenuItem<_TextType>(
value: textType,
child: Padding(
@ -522,8 +528,10 @@ class _EditorToolbarState extends State<EditorToolbar> {
))
.toList(),
icon: const Icon(Icons.arrow_drop_down),
style: const TextStyle(
color: Colors.black,
style: TextStyle(
color: Theme.of(context).brightness == Brightness.dark
? Colors.white
: Colors.black,
fontSize: 12,
),
underline: const SizedBox(),
@ -534,6 +542,7 @@ class _EditorToolbarState extends State<EditorToolbar> {
),
_buildVerticalDivider(),
],
*/
Center(
child: IconButton(
onPressed: _toggleBold,
@ -604,6 +613,15 @@ class _EditorToolbarState extends State<EditorToolbar> {
),
),
],
_buildVerticalDivider(),
Center(
child: IconButton(
onPressed: () {},
icon: const Icon(Icons.more_vert),
splashRadius: 16,
tooltip: 'More Options',
),
),
*/
],
),
@ -623,14 +641,37 @@ class _EditorToolbarState extends State<EditorToolbar> {
child: Row(
children: [
Expanded(
child: TextField(
child: FocusWithCustomParent(
focusNode: _urlFocusNode,
controller: _urlController,
decoration: const InputDecoration(
hintText: 'enter url...',
border: InputBorder.none,
parentFocusNode: widget.editorFocusNode,
// We use a SuperTextField instead of a TextField because TextField
// automatically re-parents its FocusNode, which causes #609. Flutter
// #106923 tracks the TextField issue.
child: SuperTextField(
focusNode: _urlFocusNode,
textController: _urlController,
minLines: 1,
maxLines: 1,
inputSource: TextInputSource.ime,
hintBehavior: HintBehavior.displayHintUntilTextEntered,
hintBuilder: (context) {
return Text(
'Enter a url...',
style: const TextStyle(
color: Colors.grey,
fontSize: 16,
),
);
},
textStyleBuilder: (_) {
return TextStyle(
color: Theme.of(context).brightness == Brightness.dark
? Colors.white
: Colors.black,
fontSize: 16,
);
},
),
onSubmitted: (newValue) => _applyLink(),
),
),
IconButton(
@ -840,3 +881,21 @@ class _PositionedToolbar extends StatelessWidget {
);
}
}
class SingleLineAttributedTextEditingController
extends AttributedTextEditingController {
SingleLineAttributedTextEditingController(this.onSubmit);
final VoidCallback onSubmit;
@override
void insertNewline() {
// Don't insert newline in a single-line text field.
// Invoke callback to take action on enter.
onSubmit();
// TODO: this is a hack. SuperTextField shouldn't insert newlines in a single
// line field (#697).
}
}

View File

@ -8,12 +8,13 @@ import Foundation
import in_app_purchase_storekit
import in_app_review
import package_info
import package_info_plus_macos
import path_provider_macos
import package_info_plus
import path_provider_foundation
import printing
import screen_retriever
import sentry_flutter
import shared_preferences_macos
import share_plus
import shared_preferences_foundation
import sign_in_with_apple
import smart_auth
import sqflite
@ -29,6 +30,7 @@ func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) {
PrintingPlugin.register(with: registry.registrar(forPlugin: "PrintingPlugin"))
ScreenRetrieverPlugin.register(with: registry.registrar(forPlugin: "ScreenRetrieverPlugin"))
SentryFlutterPlugin.register(with: registry.registrar(forPlugin: "SentryFlutterPlugin"))
SharePlusMacosPlugin.register(with: registry.registrar(forPlugin: "SharePlusMacosPlugin"))
SharedPreferencesPlugin.register(with: registry.registrar(forPlugin: "SharedPreferencesPlugin"))
SignInWithApplePlugin.register(with: registry.registrar(forPlugin: "SignInWithApplePlugin"))
SmartAuthPlugin.register(with: registry.registrar(forPlugin: "SmartAuthPlugin"))

View File

@ -20,16 +20,16 @@ dependencies:
sdk: flutter
flutter_localizations:
sdk: flutter
# google_sign_in: ^5.0.7
# google_sign_in: ^6.0.1
# in_app_review: ^2.0.4
# in_app_purchase: ^3.1.1
# pinput: ^2.2.11
flutter_redux: ^0.8.2
flutter_redux: ^0.10.0
redux_logging: ^0.5.0
http: ^0.13.3
path_provider: ^2.0.2
shared_preferences: ^2.0.6
material_design_icons_flutter: ^5.0.6295
material_design_icons_flutter: ^6.0.7096
built_value: ^8.1.2
built_collection: ^5.1.0
memoize: ^3.0.0
@ -37,46 +37,46 @@ dependencies:
url_launcher: ^6.0.20
share: ^2.0.4
intl: ^0.17.0
flutter_slidable: ^1.1.0
flutter_slidable: ^2.0.0
charts_flutter: ^0.12.0
#qr_flutter: ^4.0.0
qr_flutter: # https://github.com/theyakka/qr.flutter/issues/174#issuecomment-1084235757
git:
url: https://github.com/theyakka/qr.flutter.git
local_auth: ^1.1.6
sentry_flutter: ^6.5.1
image_picker: ^0.8.3+1
local_auth: ^2.1.5
sentry_flutter: ^6.20.1
image_picker: ^0.8.6+3
flutter_colorpicker: ^1.0.3
flutter_json_viewer: ^1.0.1
webview_flutter: ^3.0.4
timeago: ^3.1.0
package_info: ^2.0.2
rounded_loading_button: ^2.0.5
version: ^2.0.0
flutter_launcher_icons: ^0.9.1
rounded_loading_button: ^2.1.0
version: ^3.0.2
# flutter_launcher_icons: ^0.9.1
overflow_view: ^0.3.1
flutter_styled_toast: ^2.0.0
permission_handler: ^9.2.0
file_picker: ^4.2.3
permission_handler: ^10.2.0
file_picker: ^5.2.5
boardview: ^0.2.2
pointer_interceptor: ^0.9.0
contacts_service: ^0.6.1
contacts_service: ^0.6.3
diacritic: ^0.1.3
states_rebuilder: ^5.2.0
#super_editor: ^0.2.2
states_rebuilder: ^6.2.0
# super_editor: ^0.2.2
markdown: ^5.0.0 # REMOVE THIS
super_editor:
git:
url: https://github.com/superlistapp/super_editor.git
path: super_editor
ref: 1601fdce95ebfa34bddf80cab3e58e700b0edec3
html2md: ^1.2.5
printing: ^5.8.0
image_cropper: ^2.0.2
html2md: ^1.2.6
printing: ^5.10.1
image_cropper: ^3.0.1
msal_js: ^2.14.0
sign_in_with_apple: ^4.0.0
window_manager: ^0.2.7
# bitsdojo_window: ^0.1.2
sign_in_with_apple: ^4.3.0
window_manager: ^0.3.0
# bitsdojo_window: ^0.1.5
intl_phone_field: ^3.1.0
flutter_staggered_grid_view: ^0.6.2
# quick_actions: ^0.2.1

File diff suppressed because it is too large Load Diff

View File

@ -44,39 +44,39 @@ dependencies:
git:
url: https://github.com/theyakka/qr.flutter.git
local_auth: ^1.1.6
sentry_flutter: ^6.5.1
image_picker: ^0.8.3+1
sentry_flutter: ^6.20.1
image_picker: ^0.8.6+3
flutter_colorpicker: ^1.0.3
flutter_json_viewer: ^1.0.1
webview_flutter: ^2.0.10
timeago: ^3.1.0
package_info: ^2.0.2
rounded_loading_button: ^2.0.5
rounded_loading_button: ^2.1.0
version: ^2.0.0
flutter_launcher_icons: ^0.9.1
# flutter_launcher_icons: ^0.9.1
overflow_view: ^0.3.1
flutter_styled_toast: ^2.0.0
permission_handler: ^9.2.0
file_picker: ^4.2.3
file_picker: ^5.2.5
boardview: ^0.2.2
pointer_interceptor: ^0.9.0
contacts_service: ^0.6.1
contacts_service: ^0.6.3
diacritic: ^0.1.3
states_rebuilder: ^5.2.0
#super_editor: ^0.2.0
# super_editor: ^0.2.0
markdown: ^5.0.0 # REMOVE THIS
super_editor:
git:
url: https://github.com/superlistapp/super_editor.git
path: super_editor
ref: 1601fdce95ebfa34bddf80cab3e58e700b0edec3
html2md: ^1.2.5
printing: ^5.8.0
image_cropper: ^2.0.2
html2md: ^1.2.6
printing: ^5.10.1
image_cropper: ^3.0.1
msal_js: ^2.14.0
sign_in_with_apple: ^4.0.0
window_manager: ^0.2.5
bitsdojo_window: ^0.1.2
sign_in_with_apple: ^4.3.0
window_manager: ^0.3.0
# bitsdojo_window: ^0.1.5
intl_phone_field: ^3.1.0
flutter_staggered_grid_view: ^0.6.2
# quick_actions: ^0.2.1

View File

@ -20,7 +20,7 @@ dependencies:
sdk: flutter
flutter_localizations:
sdk: flutter
google_sign_in: ^5.0.7
google_sign_in: ^6.0.1
in_app_review: ^2.0.4
in_app_purchase: ^3.1.1
pinput: ^2.2.11
@ -29,59 +29,59 @@ dependencies:
# git:
# url: https://github.com/Tkko/Flutter_Pinput
# ref: dev
flutter_redux: ^0.8.2
flutter_redux: ^0.10.0
redux_logging: ^0.5.0
http: ^0.13.3
path_provider: ^2.0.2
shared_preferences: ^2.0.6
material_design_icons_flutter: ^5.0.6295
material_design_icons_flutter: ^6.0.7096
built_value: ^8.1.2
built_collection: ^5.1.0
memoize: ^3.0.0
cached_network_image: 3.0.0 # imageRenderMethodForWeb: ImageRenderMethodForWeb.HttpGet,
url_launcher: ^6.0.20
share: ^2.0.4
share_plus: ^6.3.1
intl: ^0.17.0
flutter_slidable: ^1.1.0
flutter_slidable: ^2.0.0
charts_flutter: ^0.12.0
#qr_flutter: ^4.0.0
qr_flutter: # https://github.com/theyakka/qr.flutter/issues/174#issuecomment-1084235757
git:
url: https://github.com/theyakka/qr.flutter.git
local_auth: ^1.1.6
sentry_flutter: ^6.9.1
image_picker: ^0.8.3+1
local_auth: ^2.1.5
sentry_flutter: ^6.20.1
image_picker: ^0.8.6+3
flutter_colorpicker: ^1.0.3
flutter_json_viewer: ^1.0.1
webview_flutter: ^3.0.4
timeago: ^3.1.0
package_info: ^2.0.2
rounded_loading_button: ^2.0.5
version: ^2.0.0
flutter_launcher_icons: ^0.9.1
rounded_loading_button: ^2.1.0
version: ^3.0.2
# flutter_launcher_icons: ^0.9.1
overflow_view: ^0.3.1
flutter_styled_toast: ^2.0.0
permission_handler: ^9.2.0
file_picker: ^4.2.3
permission_handler: ^10.2.0
file_picker: ^5.2.5
boardview: ^0.2.2
pointer_interceptor: ^0.9.0
contacts_service: ^0.6.1
contacts_service: ^0.6.3
diacritic: ^0.1.3
states_rebuilder: ^5.2.0
#super_editor: ^0.2.2
states_rebuilder: ^6.2.0
# super_editor: ^0.2.2
markdown: ^5.0.0 # REMOVE THIS
super_editor:
git:
url: https://github.com/superlistapp/super_editor.git
path: super_editor
ref: 1601fdce95ebfa34bddf80cab3e58e700b0edec3
html2md: ^1.2.5
printing: ^5.8.0
image_cropper: ^2.0.2
#ref: 1601fdce95ebfa34bddf80cab3e58e700b0edec3
html2md: ^1.2.6
printing: ^5.10.1
image_cropper: ^3.0.1
msal_js: ^2.14.0
sign_in_with_apple: ^4.0.0
window_manager: ^0.2.7
# bitsdojo_window: ^0.1.2
sign_in_with_apple: ^4.3.0
window_manager: ^0.3.0
# bitsdojo_window: ^0.1.5
intl_phone_field: ^3.1.0
flutter_staggered_grid_view: ^0.6.2
# quick_actions: ^0.2.1

View File

@ -6,15 +6,19 @@
#include "generated_plugin_registrant.h"
#include <local_auth_windows/local_auth_plugin.h>
#include <permission_handler_windows/permission_handler_windows_plugin.h>
#include <printing/printing_plugin.h>
#include <screen_retriever/screen_retriever_plugin.h>
#include <sentry_flutter/sentry_flutter_plugin.h>
#include <share_plus/share_plus_windows_plugin_c_api.h>
#include <smart_auth/smart_auth_plugin.h>
#include <url_launcher_windows/url_launcher_windows.h>
#include <window_manager/window_manager_plugin.h>
void RegisterPlugins(flutter::PluginRegistry* registry) {
LocalAuthPluginRegisterWithRegistrar(
registry->GetRegistrarForPlugin("LocalAuthPlugin"));
PermissionHandlerWindowsPluginRegisterWithRegistrar(
registry->GetRegistrarForPlugin("PermissionHandlerWindowsPlugin"));
PrintingPluginRegisterWithRegistrar(
@ -23,6 +27,8 @@ void RegisterPlugins(flutter::PluginRegistry* registry) {
registry->GetRegistrarForPlugin("ScreenRetrieverPlugin"));
SentryFlutterPluginRegisterWithRegistrar(
registry->GetRegistrarForPlugin("SentryFlutterPlugin"));
SharePlusWindowsPluginCApiRegisterWithRegistrar(
registry->GetRegistrarForPlugin("SharePlusWindowsPluginCApi"));
SmartAuthPluginRegisterWithRegistrar(
registry->GetRegistrarForPlugin("SmartAuthPlugin"));
UrlLauncherWindowsRegisterWithRegistrar(

View File

@ -3,10 +3,12 @@
#
list(APPEND FLUTTER_PLUGIN_LIST
local_auth_windows
permission_handler_windows
printing
screen_retriever
sentry_flutter
share_plus
smart_auth
url_launcher_windows
window_manager

View File

@ -20,12 +20,20 @@ add_executable(${BINARY_NAME} WIN32
# that need different build settings.
apply_standard_settings(${BINARY_NAME})
# Add preprocessor definitions for the build version.
target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION=\"${FLUTTER_VERSION}\"")
target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION_MAJOR=${FLUTTER_VERSION_MAJOR}")
target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION_MINOR=${FLUTTER_VERSION_MINOR}")
target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION_PATCH=${FLUTTER_VERSION_PATCH}")
target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION_BUILD=${FLUTTER_VERSION_BUILD}")
# Disable Windows macros that collide with C++ standard library functions.
target_compile_definitions(${BINARY_NAME} PRIVATE "NOMINMAX")
# Add dependency libraries and include directories. Add any application-specific
# dependencies here.
target_link_libraries(${BINARY_NAME} PRIVATE flutter flutter_wrapper_app)
target_link_libraries(${BINARY_NAME} PRIVATE "dwmapi.lib")
target_include_directories(${BINARY_NAME} PRIVATE "${CMAKE_SOURCE_DIR}")
# Run the Flutter tool portions of the build. This must not be removed.

View File

@ -60,14 +60,14 @@ IDI_APP_ICON ICON "resources\\app_icon.ico"
// Version
//
#ifdef FLUTTER_BUILD_NUMBER
#define VERSION_AS_NUMBER FLUTTER_BUILD_NUMBER
#if defined(FLUTTER_VERSION_MAJOR) && defined(FLUTTER_VERSION_MINOR) && defined(FLUTTER_VERSION_PATCH) && defined(FLUTTER_VERSION_BUILD)
#define VERSION_AS_NUMBER FLUTTER_VERSION_MAJOR,FLUTTER_VERSION_MINOR,FLUTTER_VERSION_PATCH,FLUTTER_VERSION_BUILD
#else
#define VERSION_AS_NUMBER 1,0,0
#define VERSION_AS_NUMBER 1,0,0,0
#endif
#ifdef FLUTTER_BUILD_NAME
#define VERSION_AS_STRING #FLUTTER_BUILD_NAME
#if defined(FLUTTER_VERSION)
#define VERSION_AS_STRING FLUTTER_VERSION
#else
#define VERSION_AS_STRING "1.0.0"
#endif
@ -93,7 +93,7 @@ BEGIN
VALUE "FileDescription", "Invoice Ninja" "\0"
VALUE "FileVersion", VERSION_AS_STRING "\0"
VALUE "InternalName", "invoiceninja" "\0"
VALUE "LegalCopyright", "Copyright (C) 2021 Invoice Ninja. All rights reserved." "\0"
VALUE "LegalCopyright", "Copyright (C) 2023 Invoice Ninja. All rights reserved." "\0"
VALUE "OriginalFilename", "invoiceninja.exe" "\0"
VALUE "ProductName", "invoiceninja" "\0"
VALUE "ProductVersion", VERSION_AS_STRING "\0"

View File

@ -26,6 +26,11 @@ bool FlutterWindow::OnCreate() {
}
RegisterPlugins(flutter_controller_->engine());
SetChildContent(flutter_controller_->view()->GetNativeWindow());
flutter_controller_->engine()->SetNextFrameCallback([&]() {
this->Show();
});
return true;
}

View File

@ -1,6 +1,3 @@
//#include <bitsdojo_window_windows/bitsdojo_window_plugin.h>
//auto bdw = bitsdojo_window_configure(BDW_CUSTOM_FRAME | BDW_HIDE_ON_STARTUP);
#include <flutter/dart_project.h>
#include <flutter/flutter_view_controller.h>
#include <windows.h>
@ -30,7 +27,7 @@ int APIENTRY wWinMain(_In_ HINSTANCE instance, _In_opt_ HINSTANCE prev,
FlutterWindow window(project);
Win32Window::Point origin(10, 10);
Win32Window::Size size(1280, 720);
if (!window.CreateAndShow(L"Invoice Ninja", origin, size)) {
if (!window.Create(L"Invoice Ninja", origin, size)) {
return EXIT_FAILURE;
}
window.SetQuitOnClose(true);

View File

@ -7,7 +7,7 @@
</application>
<compatibility xmlns="urn:schemas-microsoft-com:compatibility.v1">
<application>
<!-- Windows 10 -->
<!-- Windows 10 and Windows 11 -->
<supportedOS Id="{8e0f7a12-bfb3-4fe8-b9a5-48fd50a15a9a}"/>
<!-- Windows 8.1 -->
<supportedOS Id="{1f676c76-80e1-4239-95bb-83d0f6d0da78}"/>

View File

@ -1,13 +1,31 @@
#include "win32_window.h"
#include <dwmapi.h>
#include <flutter_windows.h>
#include "resource.h"
namespace {
/// Window attribute that enables dark mode window decorations.
///
/// Redefined in case the developer's machine has a Windows SDK older than
/// version 10.0.22000.0.
/// See: https://docs.microsoft.com/windows/win32/api/dwmapi/ne-dwmapi-dwmwindowattribute
#ifndef DWMWA_USE_IMMERSIVE_DARK_MODE
#define DWMWA_USE_IMMERSIVE_DARK_MODE 20
#endif
constexpr const wchar_t kWindowClassName[] = L"FLUTTER_RUNNER_WIN32_WINDOW";
/// Registry key for app theme preference.
///
/// A value of 0 indicates apps should use dark mode. A non-zero or missing
/// value indicates apps should use light mode.
constexpr const wchar_t kGetPreferredBrightnessRegKey[] =
L"Software\\Microsoft\\Windows\\CurrentVersion\\Themes\\Personalize";
constexpr const wchar_t kGetPreferredBrightnessRegValue[] = L"AppsUseLightTheme";
// The number of Win32Window objects that currently exist.
static int g_active_window_count = 0;
@ -31,8 +49,8 @@ void EnableFullDpiSupportIfAvailable(HWND hwnd) {
GetProcAddress(user32_module, "EnableNonClientDpiScaling"));
if (enable_non_client_dpi_scaling != nullptr) {
enable_non_client_dpi_scaling(hwnd);
FreeLibrary(user32_module);
}
FreeLibrary(user32_module);
}
} // namespace
@ -102,7 +120,7 @@ Win32Window::~Win32Window() {
Destroy();
}
bool Win32Window::CreateAndShow(const std::wstring& title,
bool Win32Window::Create(const std::wstring& title,
const Point& origin,
const Size& size) {
Destroy();
@ -117,7 +135,7 @@ bool Win32Window::CreateAndShow(const std::wstring& title,
double scale_factor = dpi / 96.0;
HWND window = CreateWindow(
window_class, title.c_str(), WS_OVERLAPPEDWINDOW | WS_VISIBLE,
window_class, title.c_str(), WS_OVERLAPPEDWINDOW,
Scale(origin.x, scale_factor), Scale(origin.y, scale_factor),
Scale(size.width, scale_factor), Scale(size.height, scale_factor),
nullptr, nullptr, GetModuleHandle(nullptr), this);
@ -126,9 +144,15 @@ bool Win32Window::CreateAndShow(const std::wstring& title,
return false;
}
UpdateTheme(window);
return OnCreate();
}
bool Win32Window::Show() {
return ShowWindow(window_handle_, SW_SHOWNORMAL);
}
// static
LRESULT CALLBACK Win32Window::WndProc(HWND const window,
UINT const message,
@ -188,6 +212,10 @@ Win32Window::MessageHandler(HWND hwnd,
SetFocus(child_content_);
}
return 0;
case WM_DWMCOLORIZATIONCOLORCHANGED:
UpdateTheme(hwnd);
return 0;
}
return DefWindowProc(window_handle_, message, wparam, lparam);
@ -243,3 +271,18 @@ bool Win32Window::OnCreate() {
void Win32Window::OnDestroy() {
// No-op; provided for subclasses.
}
void Win32Window::UpdateTheme(HWND const window) {
DWORD light_mode;
DWORD light_mode_size = sizeof(light_mode);
LSTATUS result = RegGetValue(HKEY_CURRENT_USER, kGetPreferredBrightnessRegKey,
kGetPreferredBrightnessRegValue,
RRF_RT_REG_DWORD, nullptr, &light_mode,
&light_mode_size);
if (result == ERROR_SUCCESS) {
BOOL enable_dark_mode = light_mode == 0;
DwmSetWindowAttribute(window, DWMWA_USE_IMMERSIVE_DARK_MODE,
&enable_dark_mode, sizeof(enable_dark_mode));
}
}

View File

@ -28,15 +28,16 @@ class Win32Window {
Win32Window();
virtual ~Win32Window();
// Creates and shows a win32 window with |title| and position and size using
// Creates a win32 window with |title| that is positioned and sized using
// |origin| and |size|. New windows are created on the default monitor. Window
// sizes are specified to the OS in physical pixels, hence to ensure a
// consistent size to will treat the width height passed in to this function
// as logical pixels and scale to appropriate for the default monitor. Returns
// true if the window was created successfully.
bool CreateAndShow(const std::wstring& title,
const Point& origin,
const Size& size);
// consistent size this function will scale the inputted width and height as
// as appropriate for the default monitor. The window is invisible until
// |Show| is called. Returns true if the window was created successfully.
bool Create(const std::wstring& title, const Point& origin, const Size& size);
// Show the current window. Returns true if the window was successfully shown.
bool Show();
// Release OS resources associated with window.
void Destroy();
@ -86,6 +87,9 @@ class Win32Window {
// Retrieves a class instance pointer for |window|
static Win32Window* GetThisFromHandle(HWND const window) noexcept;
// Update the window frame's theme to match the system theme.
static void UpdateTheme(HWND const window);
bool quit_on_close_ = false;
// window handle for top level window.