diff --git a/.github/workflows/manual-build.yml b/.github/workflows/manual-build.yml index 22e8f5a4..9347362d 100644 --- a/.github/workflows/manual-build.yml +++ b/.github/workflows/manual-build.yml @@ -111,11 +111,11 @@ jobs: - name: Build Linux packages (.deb, .rpm, and AppImage) run: | - # Add submodule Flutter to PATH first (so flutter_distributor uses it) + # Add submodule Flutter to PATH first (so fastforge uses it) export PATH="$(pwd)/submodules/flutter/bin:$PATH" - # Install flutter_distributor - dart pub global activate flutter_distributor + # Install fastforge + dart pub global activate fastforge export PATH="$HOME/.pub-cache/bin:$PATH" # Install tools for building .rpm and AppImage packages @@ -131,7 +131,7 @@ jobs: flutter --version # Build .deb, .rpm, and AppImage packages - flutter_distributor release --name=linux-release --jobs=release-linux-deb,release-linux-rpm,release-linux-appimage + fastforge release --name=linux-release --jobs=release-linux-deb,release-linux-rpm,release-linux-appimage - name: Upload .deb as artifact uses: actions/upload-artifact@v4 diff --git a/.github/workflows/on-release.yml b/.github/workflows/on-release.yml index 1ac75adc..72b5d2a9 100644 --- a/.github/workflows/on-release.yml +++ b/.github/workflows/on-release.yml @@ -223,11 +223,11 @@ jobs: - name: Build Linux packages (.deb, .rpm, and AppImage) run: | - # Add submodule Flutter to PATH first (so flutter_distributor uses it) + # Add submodule Flutter to PATH first (so fastforge uses it) export PATH="$(pwd)/submodules/flutter/bin:$PATH" - # Install flutter_distributor - dart pub global activate flutter_distributor + # Install fastforge + dart pub global activate fastforge export PATH="$HOME/.pub-cache/bin:$PATH" # Install tools for building .rpm and AppImage packages @@ -243,7 +243,7 @@ jobs: flutter --version # Build .deb, .rpm, and AppImage packages - flutter_distributor release --name=linux-release --jobs=release-linux-deb,release-linux-rpm,release-linux-appimage + fastforge release --name=linux-release --jobs=release-linux-deb,release-linux-rpm,release-linux-appimage - name: Upload Linux packages as Release asset uses: softprops/action-gh-release@v1 diff --git a/.gitignore b/.gitignore index 5ffe4501..d39054fa 100644 --- a/.gitignore +++ b/.gitignore @@ -79,7 +79,7 @@ package-lock.json /build-debug-file/ /tmp_build .flutter-plugins-dependencies -ios/Flutter/flutter_export_environment.sh +**/ios/Flutter/flutter_export_environment.sh macos/Flutter/ephemeral/flutter_export_environment.sh macos/Flutter/ephemeral/Flutter-Generated.xcconfig diff --git a/.metadata b/.metadata index 0505c6c9..352c9c2e 100644 --- a/.metadata +++ b/.metadata @@ -4,7 +4,7 @@ # This file should be version controlled and should not be manually edited. version: - revision: "b45fa18946ecc2d9b4009952c636ba7e2ffbb787" + revision: "8b872868494e429d94fa06dca855c306438b22c0" channel: "stable" project_type: app @@ -13,11 +13,11 @@ project_type: app migration: platforms: - platform: root - create_revision: b45fa18946ecc2d9b4009952c636ba7e2ffbb787 - base_revision: b45fa18946ecc2d9b4009952c636ba7e2ffbb787 - - platform: linux - create_revision: b45fa18946ecc2d9b4009952c636ba7e2ffbb787 - base_revision: b45fa18946ecc2d9b4009952c636ba7e2ffbb787 + create_revision: 8b872868494e429d94fa06dca855c306438b22c0 + base_revision: 8b872868494e429d94fa06dca855c306438b22c0 + - platform: macos + create_revision: 8b872868494e429d94fa06dca855c306438b22c0 + base_revision: 8b872868494e429d94fa06dca855c306438b22c0 # User provided section diff --git a/.vscode/extensions.json b/.vscode/extensions.json new file mode 100644 index 00000000..d09a7abc --- /dev/null +++ b/.vscode/extensions.json @@ -0,0 +1,6 @@ +{ + "recommendations": [ + "dart-code.dart-code", + "dart-code.flutter" + ] +} diff --git a/.vscode/launch.json b/.vscode/launch.json index 3287bb67..7e461cb2 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -7,7 +7,8 @@ { "name": "Flutter", "request": "launch", - "type": "dart" + "type": "dart", + "flutterMode": "debug" } ] } \ No newline at end of file diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 00000000..d1250ee5 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,20 @@ +{ + "search.exclude": { + "**/submodules": true, + }, + "terminal.integrated.defaultProfile.linux": "bash", + "terminal.integrated.profiles.linux": { + "bash": { + "path": "/usr/bin/flatpak-spawn", + "args": ["--host", "--env=TERM=xterm-256color", "bash"], + "icon": "terminal-bash", + "overrideName": true + } + }, + "dart.flutterSdkPath": "~/development/flutter", + "[dart]": { + "editor.defaultFormatter": "Dart-Code.dart-code", + "editor.formatOnSave": true, + "editor.formatOnType": true, + } +} diff --git a/build_linux.sh b/build_linux.sh index 35f10f76..c211e8de 100755 --- a/build_linux.sh +++ b/build_linux.sh @@ -93,18 +93,18 @@ if ! pkg-config --exists gtk+-3.0 2>/dev/null; then fi echo -e "${GREEN}✓ GTK3 development libraries found${NC}" -# Find flutter_distributor -DISTRIBUTOR_CMD="$HOME/.pub-cache/bin/flutter_distributor" +# Find fastforge +DISTRIBUTOR_CMD="$HOME/.pub-cache/bin/fastforge" if [ ! -f "$DISTRIBUTOR_CMD" ]; then - echo -e "${YELLOW}flutter_distributor not found, installing...${NC}" - $FLUTTER_CMD pub global activate flutter_distributor + echo -e "${YELLOW}fastforge not found, installing...${NC}" + $FLUTTER_CMD pub global activate fastforge if [ ! -f "$DISTRIBUTOR_CMD" ]; then - echo -e "${RED}Error: Failed to install flutter_distributor${NC}" + echo -e "${RED}Error: Failed to install fastforge${NC}" exit 1 fi fi -echo -e "${GREEN}✓ flutter_distributor ready${NC}" +echo -e "${GREEN}✓ fastforge ready${NC}" echo "" echo "===================================" diff --git a/ios/Flutter/ephemeral/flutter_lldb_helper.py b/ios/Flutter/ephemeral/flutter_lldb_helper.py deleted file mode 100644 index a88caf99..00000000 --- a/ios/Flutter/ephemeral/flutter_lldb_helper.py +++ /dev/null @@ -1,32 +0,0 @@ -# -# Generated file, do not edit. -# - -import lldb - -def handle_new_rx_page(frame: lldb.SBFrame, bp_loc, extra_args, intern_dict): - """Intercept NOTIFY_DEBUGGER_ABOUT_RX_PAGES and touch the pages.""" - base = frame.register["x0"].GetValueAsAddress() - page_len = frame.register["x1"].GetValueAsUnsigned() - - # Note: NOTIFY_DEBUGGER_ABOUT_RX_PAGES will check contents of the - # first page to see if handled it correctly. This makes diagnosing - # misconfiguration (e.g. missing breakpoint) easier. - data = bytearray(page_len) - data[0:8] = b'IHELPED!' - - error = lldb.SBError() - frame.GetThread().GetProcess().WriteMemory(base, data, error) - if not error.Success(): - print(f'Failed to write into {base}[+{page_len}]', error) - return - -def __lldb_init_module(debugger: lldb.SBDebugger, _): - target = debugger.GetDummyTarget() - # Caveat: must use BreakpointCreateByRegEx here and not - # BreakpointCreateByName. For some reasons callback function does not - # get carried over from dummy target for the later. - bp = target.BreakpointCreateByRegex("^NOTIFY_DEBUGGER_ABOUT_RX_PAGES$") - bp.SetScriptCallbackFunction('{}.handle_new_rx_page'.format(__name__)) - bp.SetAutoContinue(True) - print("-- LLDB integration loaded --") diff --git a/ios/Flutter/ephemeral/flutter_lldbinit b/ios/Flutter/ephemeral/flutter_lldbinit deleted file mode 100644 index e3ba6fbe..00000000 --- a/ios/Flutter/ephemeral/flutter_lldbinit +++ /dev/null @@ -1,5 +0,0 @@ -# -# Generated file, do not edit. -# - -command script import --relative-to-command-file flutter_lldb_helper.py diff --git a/ios/PLACEHOLDER b/ios/PLACEHOLDER deleted file mode 100644 index e69de29b..00000000 diff --git a/lib/categories/categories-grid.dart b/lib/categories/categories-grid.dart index 5b66627b..ecdae5b4 100644 --- a/lib/categories/categories-grid.dart +++ b/lib/categories/categories-grid.dart @@ -22,7 +22,6 @@ class CategoriesGrid extends StatefulWidget { } class CategoriesGridState extends State { - List orderedCategories = []; bool enableManualSorting = false; late ScrollController _scrollController; @@ -77,7 +76,7 @@ class CategoriesGridState extends State { child: Container( child: Column( children: [ - CategoryIconCircle( + CategoryIconCircle( iconEmoji: category.iconEmoji, iconDataFromDefaultIconSet: category.icon, backgroundColor: category.color, diff --git a/lib/categories/categories-list.dart b/lib/categories/categories-list.dart index 2f403eaa..8adf655f 100644 --- a/lib/categories/categories-list.dart +++ b/lib/categories/categories-list.dart @@ -55,8 +55,7 @@ class CategoriesListState extends State { iconEmoji: category.iconEmoji, iconDataFromDefaultIconSet: category.icon, backgroundColor: category.color, - overlayIcon: category.isArchived ? Icons.archive : null - ), + overlayIcon: category.isArchived ? Icons.archive : null), title: Text(category.name!, style: _biggerFont), ), ), diff --git a/lib/categories/categories-tab-page-edit.dart b/lib/categories/categories-tab-page-edit.dart index 4e4bb4fc..cd88d521 100644 --- a/lib/categories/categories-tab-page-edit.dart +++ b/lib/categories/categories-tab-page-edit.dart @@ -48,7 +48,8 @@ class TabCategoriesState extends State void _handleTabChange() { // Check if the tab index has actually changed (works for both clicks and swipes) - if (_tabController!.index != _previousTabIndex && !_tabController!.indexIsChanging) { + if (_tabController!.index != _previousTabIndex && + !_tabController!.indexIsChanging) { setState(() { _fabRotation += 3.14159; // 180 degrees rotation _previousTabIndex = _tabController!.index; @@ -426,10 +427,8 @@ class TabCategoriesState extends State await Navigator.push( context, MaterialPageRoute( - builder: (context) => EditCategoryPage( - categoryType: CategoryType.expense - ) - ), + builder: (context) => EditCategoryPage( + categoryType: CategoryType.expense)), ); await refreshCategoriesAndHighlightsTab(0); } else { @@ -437,10 +436,8 @@ class TabCategoriesState extends State await Navigator.push( context, MaterialPageRoute( - builder: (context) => EditCategoryPage( - categoryType: CategoryType.income - ) - ), + builder: (context) => EditCategoryPage( + categoryType: CategoryType.income)), ); await refreshCategoriesAndHighlightsTab(1); } diff --git a/lib/categories/categories-tab-page-view.dart b/lib/categories/categories-tab-page-view.dart index 9a34fc30..2374c247 100644 --- a/lib/categories/categories-tab-page-view.dart +++ b/lib/categories/categories-tab-page-view.dart @@ -317,9 +317,7 @@ class CategoryTabPageViewState extends State { tabs: [ Semantics( identifier: 'expenses-tab', - child: Tab( - text: "Expenses".i18n.toUpperCase() - ), + child: Tab(text: "Expenses".i18n.toUpperCase()), ), Semantics( identifier: 'income-tab', diff --git a/lib/categories/edit-category-page.dart b/lib/categories/edit-category-page.dart index 7c4d1259..db505415 100644 --- a/lib/categories/edit-category-page.dart +++ b/lib/categories/edit-category-page.dart @@ -165,8 +165,8 @@ class EditCategoryPageState extends State { // Prompt confirmation AlertDialogBuilder archiveDialog = AlertDialogBuilder(dialogMessage) - .addTrueButtonName("Yes".i18n) - .addFalseButtonName("No".i18n); + .renameTrueButtonName("Yes".i18n) + .renameFalseButtonName("No".i18n); if (!isCurrentlyArchived) { archiveDialog.addSubtitle( @@ -204,8 +204,8 @@ class EditCategoryPageState extends State { .addSubtitle( "Deleting the category you will remove all the associated records" .i18n) - .addTrueButtonName("Yes".i18n) - .addFalseButtonName("No".i18n); + .renameTrueButtonName("Yes".i18n) + .renameFalseButtonName("No".i18n); var continueDelete = await showDialog( context: context, diff --git a/lib/components/category_icon_circle.dart b/lib/components/category_icon_circle.dart index 2c10060e..d0e604b2 100644 --- a/lib/components/category_icon_circle.dart +++ b/lib/components/category_icon_circle.dart @@ -34,8 +34,7 @@ class CategoryIconCircle extends StatelessWidget { color: backgroundColor, ), child: iconEmoji != null - ? - Center( + ? Center( child: Text( iconEmoji!, // Display the emoji style: TextStyle( @@ -43,11 +42,11 @@ class CategoryIconCircle extends StatelessWidget { ), ), ) - : Icon( - iconDataFromDefaultIconSet, // Fallback to the icon - size: mainIconSize, - color: iconColor ?? Theme.of(context).colorScheme.onSurface, - ), + : Icon( + iconDataFromDefaultIconSet, // Fallback to the icon + size: mainIconSize, + color: iconColor ?? Theme.of(context).colorScheme.onSurface, + ), ); } diff --git a/lib/components/year-picker.dart b/lib/components/year-picker.dart index bf1362b4..8a49ebde 100644 --- a/lib/components/year-picker.dart +++ b/lib/components/year-picker.dart @@ -393,7 +393,7 @@ class _DatePickerDialogState extends State<_DatePickerDialog> { // been updated to 4, so we will use that here for the Date Picker, but // only if there isn't one provided in the theme. shape: const RoundedRectangleBorder( - borderRadius: BorderRadius.all(Radius.circular(4.0))), + borderRadius: BorderRadius.all(Radius.circular(4.0))), clipBehavior: Clip.antiAlias, ); } diff --git a/lib/helpers/alert-dialog-builder.dart b/lib/helpers/alert-dialog-builder.dart index bcf50ea4..62af9dc5 100644 --- a/lib/helpers/alert-dialog-builder.dart +++ b/lib/helpers/alert-dialog-builder.dart @@ -7,11 +7,15 @@ class AlertDialogBuilder { late String title; String? subtitle; + late bool trueButtonShow; + late bool falseButtonShow; late String trueButtonName; late String falseButtonName; AlertDialogBuilder(String title) { this.title = title; + this.trueButtonShow = true; + this.falseButtonShow = true; this.trueButtonName = "OK"; this.falseButtonName = "Cancel".i18n; this.subtitle = null; @@ -27,16 +31,26 @@ class AlertDialogBuilder { return this; } - AlertDialogBuilder addTrueButtonName(String trueButtonName) { + AlertDialogBuilder renameTrueButtonName(String trueButtonName) { this.trueButtonName = trueButtonName; return this; } - AlertDialogBuilder addFalseButtonName(String falseButtonName) { + AlertDialogBuilder renameFalseButtonName(String falseButtonName) { this.falseButtonName = falseButtonName; return this; } + AlertDialogBuilder hideTrueButton() { + this.trueButtonShow = false; + return this; + } + + AlertDialogBuilder hideFalseButton() { + this.falseButtonShow = false; + return this; + } + AlertDialog build(BuildContext context) { // set up the button Widget trueButton = TextButton( @@ -54,8 +68,8 @@ class AlertDialogBuilder { title: Text(title), content: (subtitle != null) ? Text(subtitle!) : null, actions: [ - falseButton, - trueButton, + if (trueButtonShow) trueButton, + if (falseButtonShow) falseButton, ], ); } diff --git a/lib/helpers/datetime-utility-functions.dart b/lib/helpers/datetime-utility-functions.dart index 5ec1b3eb..8b40164e 100644 --- a/lib/helpers/datetime-utility-functions.dart +++ b/lib/helpers/datetime-utility-functions.dart @@ -49,13 +49,17 @@ String getDateRangeStr(DateTime start, DateTime end) { } else { if (earlier.year == later.year) { // Same year: show year only once at the end - String startLocalRepr = DateFormat.MMMd(myLocale.languageCode).format(earlier); - String endLocalRepr = DateFormat.yMMMd(myLocale.languageCode).format(later); + String startLocalRepr = + DateFormat.MMMd(myLocale.languageCode).format(earlier); + String endLocalRepr = + DateFormat.yMMMd(myLocale.languageCode).format(later); return startLocalRepr + " - " + endLocalRepr; } else { // Different years: show year for both dates - String startLocalRepr = DateFormat.yMMMd(myLocale.languageCode).format(earlier); - String endLocalRepr = DateFormat.yMMMd(myLocale.languageCode).format(later); + String startLocalRepr = + DateFormat.yMMMd(myLocale.languageCode).format(earlier); + String endLocalRepr = + DateFormat.yMMMd(myLocale.languageCode).format(later); return startLocalRepr + " - " + endLocalRepr; } } @@ -86,7 +90,7 @@ String getWeekStr(DateTime dateTime) { /// - 7: Sunday /// Different locales have different week start days: /// - US, Brazil, China, Japan: Sunday (7) -/// - Arabic regions: Saturday (6) +/// - Arabic regions: Saturday (6) /// - Most European and other locales: Monday (1) int getFirstDayOfWeekIndex() { try { @@ -103,7 +107,7 @@ int getFirstDayOfWeekIndex() { // Fall back to locale-based logic String localeStr = I18n.locale.toString(); - + if (localeStr == 'en_US') return DateTime.sunday; if (localeStr.startsWith('pt_BR')) return DateTime.sunday; if (localeStr.startsWith('zh')) return DateTime.sunday; @@ -112,20 +116,23 @@ int getFirstDayOfWeekIndex() { } catch (e) { // Locale not initialized or error, use default } - + return DateTime.monday; // Default for most locales } /// Returns the last day of the week based on the first day. int _getLastDayOfWeek(int firstDayOfWeek) { - return firstDayOfWeek == DateTime.monday ? DateTime.sunday : firstDayOfWeek - 1; + return firstDayOfWeek == DateTime.monday + ? DateTime.sunday + : firstDayOfWeek - 1; } /// Calculates the number of days to offset from current day to reach target weekday. /// Handles week wraparound (e.g., going from Monday to previous Sunday). -int _calculateDaysOffset(int fromWeekday, int toWeekday, {bool forward = false}) { +int _calculateDaysOffset(int fromWeekday, int toWeekday, + {bool forward = false}) { if (forward) { - return toWeekday >= fromWeekday + return toWeekday >= fromWeekday ? toWeekday - fromWeekday : (7 - fromWeekday) + toWeekday; } else { @@ -144,7 +151,8 @@ DateTime getStartOfWeek(DateTime date) { DateTime getEndOfWeek(DateTime date) { int firstDayOfWeek = getFirstDayOfWeekIndex(); int lastDayOfWeek = _getLastDayOfWeek(firstDayOfWeek); - int daysToAdd = _calculateDaysOffset(date.weekday, lastDayOfWeek, forward: true); + int daysToAdd = + _calculateDaysOffset(date.weekday, lastDayOfWeek, forward: true); return DateTime(date.year, date.month, date.day + daysToAdd, 23, 59); } @@ -219,10 +227,10 @@ bool isFullYear(DateTime from, DateTime to) { bool isFullWeek(DateTime intervalFrom, DateTime intervalTo) { int firstDayOfWeek = getFirstDayOfWeekIndex(); int lastDayOfWeek = _getLastDayOfWeek(firstDayOfWeek); - + return intervalTo.difference(intervalFrom).inDays == 6 && - intervalFrom.weekday == firstDayOfWeek && - intervalTo.weekday == lastDayOfWeek; + intervalFrom.weekday == firstDayOfWeek && + intervalTo.weekday == lastDayOfWeek; } tz.TZDateTime createTzDateTime(DateTime utcDateTime, String timeZoneName) { diff --git a/lib/i18n/i18n_helper.dart b/lib/i18n/i18n_helper.dart index 4a7b8eba..f6f35f26 100644 --- a/lib/i18n/i18n_helper.dart +++ b/lib/i18n/i18n_helper.dart @@ -49,4 +49,4 @@ class JSONImporter extends Importer { Map _load(String source) { return Map.from(json.decode(source)); } -} \ No newline at end of file +} diff --git a/lib/main.dart b/lib/main.dart index e7bec48f..bc62099c 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -109,12 +109,15 @@ main() async { } logger.info('Timezone initialized: ${ServiceConfig.localTimezone}'); - PackageInfo packageInfo = await PackageInfo.fromPlatform(); ServiceConfig.packageName = packageInfo.packageName; ServiceConfig.version = packageInfo.version; - ServiceConfig.isPremium = packageInfo.packageName.endsWith("pro") || Platform.isLinux || Platform.isWindows || Platform.isMacOS; - logger.info('Package: ${ServiceConfig.packageName} v${ServiceConfig.version} (Premium: ${ServiceConfig.isPremium})'); + ServiceConfig.isPremium = packageInfo.packageName.endsWith("pro") || + Platform.isLinux || + Platform.isWindows || + Platform.isMacOS; + logger.info( + 'Package: ${ServiceConfig.packageName} v${ServiceConfig.version} (Premium: ${ServiceConfig.isPremium})'); ServiceConfig.sharedPreferences = await SharedPreferences.getInstance(); await MyI18n.loadTranslations(); @@ -129,7 +132,8 @@ main() async { final languageLocale = LocaleService.resolveLanguageLocale(); final currencyLocale = LocaleService.resolveCurrencyLocale(); LocaleService.setCurrencyLocale(currencyLocale); - logger.info('Locale configured: language=$languageLocale, currency=$currencyLocale'); + logger.info( + 'Locale configured: language=$languageLocale, currency=$currencyLocale'); final lightTheme = await MaterialThemeInstance.getLightTheme(); final darkTheme = await MaterialThemeInstance.getDarkTheme(); diff --git a/lib/records/components/tag_selection_dialog.dart b/lib/records/components/tag_selection_dialog.dart index 93dfd891..51134141 100644 --- a/lib/records/components/tag_selection_dialog.dart +++ b/lib/records/components/tag_selection_dialog.dart @@ -206,7 +206,9 @@ class _TagSelectionDialogState extends State elevation: 8, icon: Icon(Icons.check), label: Text( - "Add selected tags (%s)".i18n.fill([_selectedTags.length.toString()]), + "Add selected tags (%s)" + .i18n + .fill([_selectedTags.length.toString()]), style: TextStyle( fontWeight: FontWeight.w600, fontSize: 16, diff --git a/lib/records/controllers/tab_records_controller.dart b/lib/records/controllers/tab_records_controller.dart index b4669684..d3479f65 100644 --- a/lib/records/controllers/tab_records_controller.dart +++ b/lib/records/controllers/tab_records_controller.dart @@ -621,7 +621,7 @@ class TabRecordsController { Future _showNoCategoryDialog(BuildContext context) async { AlertDialogBuilder noCategoryDialog = AlertDialogBuilder( "No Category is set yet.".i18n) - .addTrueButtonName("OK") + .renameTrueButtonName("OK") .addSubtitle( "You need to set a category first. Go to Category tab and add a new category." .i18n); diff --git a/lib/records/edit-record-page.dart b/lib/records/edit-record-page.dart index f1ff1196..41ff18b6 100644 --- a/lib/records/edit-record-page.dart +++ b/lib/records/edit-record-page.dart @@ -1211,8 +1211,8 @@ class EditRecordPageState extends State { onPressed: () async { AlertDialogBuilder deleteDialog = AlertDialogBuilder("Critical action".i18n) - .addTrueButtonName("Yes".i18n) - .addFalseButtonName("No".i18n); + .renameTrueButtonName("Yes".i18n) + .renameFalseButtonName("No".i18n); if (widget.passedRecord != null) { deleteDialog = deleteDialog.addSubtitle( "Do you really want to delete this record?".i18n); diff --git a/lib/services/backup-service.dart b/lib/services/backup-service.dart index 21f1959a..487d71ff 100644 --- a/lib/services/backup-service.dart +++ b/lib/services/backup-service.dart @@ -5,6 +5,7 @@ import 'dart:typed_data'; import 'package:i18n_extension/default.i18n.dart'; import 'package:intl/intl.dart'; +import 'package:file_selector/file_selector.dart'; import 'package:package_info_plus/package_info_plus.dart'; import 'package:path_provider/path_provider.dart'; import 'package:piggybank/models/backup.dart'; @@ -29,8 +30,9 @@ import 'logger.dart'; class BackupService { static final _logger = Logger.withClass(BackupService); - static const String DEFAULT_STORAGE_DIR = - "/storage/emulated/0/Documents/oinkoin"; + static const String DEFAULT_STORAGE_DIR = Platform.isAndroid + ? '/storage/emulated/0/Documents/oinkoin' + : '${Platform.environment["HOME"]}/.oinkoin'; static const String MANDATORY_BACKUP_SUFFIX = "obackup.json"; @@ -44,16 +46,18 @@ class BackupService { /// Gets the platform-appropriate default backup directory /// Android: /storage/emulated/0/Documents/oinkoin - /// Linux/Desktop: ~/Documents/oinkoin + /// Linux: ~/.oinkoin + /// macOS: /Users//Documents + /// Windows: AppData/Local/oinkoin static Future getDefaultBackupDirectory() async { - if (Platform.isLinux || Platform.isWindows || Platform.isMacOS) { - // Desktop: use Documents directory + if (Platform.isMacOS) { + // macOS final documentsDir = await getApplicationDocumentsDirectory(); return '${documentsDir.parent.path}/Documents/oinkoin'; - } else { - // Android/iOS: use the original path + } else if (Platform.isAndroid) return DEFAULT_STORAGE_DIR; - } + else + return (await getApplicationDocumentsDirectory()).path; } /// Generates a backup file name containing the app package name, version, and current time. diff --git a/lib/services/csv-service.dart b/lib/services/csv-service.dart index 56b89c9d..16ba919e 100644 --- a/lib/services/csv-service.dart +++ b/lib/services/csv-service.dart @@ -3,7 +3,6 @@ import 'package:piggybank/models/record.dart'; import 'logger.dart'; class CSVExporter { - static final _logger = Logger.withClass(CSVExporter); /// Creates a CSV string from a list of records. diff --git a/lib/services/database/sqlite-database.dart b/lib/services/database/sqlite-database.dart index 5bbb103d..b2b6f49d 100644 --- a/lib/services/database/sqlite-database.dart +++ b/lib/services/database/sqlite-database.dart @@ -13,9 +13,9 @@ import 'package:piggybank/models/record.dart'; import 'package:piggybank/models/recurrent-record-pattern.dart'; import 'package:piggybank/services/database/database-interface.dart'; import 'package:piggybank/services/database/sqlite-migration-service.dart'; -import 'package:sqflite/sqflite.dart'; -import 'package:sqflite_common/sqflite_logger.dart'; +import 'package:piggybank/services/backup-service.dart'; import 'package:sqflite_common_ffi/sqflite_ffi.dart'; +import 'package:sqflite_common/sqflite_logger.dart'; import 'package:timezone/timezone.dart' as tz; import 'package:uuid/uuid.dart'; @@ -56,7 +56,7 @@ class SqliteDatabase implements DatabaseInterface { _logger.info('Initializing database...'); // Initialize FFI for desktop platforms (Linux, Windows, macOS) - if (Platform.isLinux || Platform.isWindows || Platform.isMacOS) { + if (Platform.isWindows || Platform.isLinux || Platform.isMacOS) { _logger.debug('Initializing sqflite FFI for desktop platform'); sqfliteFfiInit(); databaseFactory = databaseFactoryFfi; diff --git a/lib/services/logger.dart b/lib/services/logger.dart index 89e65d0b..2a379136 100644 --- a/lib/services/logger.dart +++ b/lib/services/logger.dart @@ -131,4 +131,3 @@ class _LogScreenState extends State { ); } } - diff --git a/lib/services/platform-file-service.dart b/lib/services/platform-file-service.dart index 6f5d589c..bea46202 100644 --- a/lib/services/platform-file-service.dart +++ b/lib/services/platform-file-service.dart @@ -7,7 +7,6 @@ import 'package:share_plus/share_plus.dart'; /// On mobile: Uses share_plus /// On desktop: Uses file_selector for "Save As" dialog class PlatformFileService { - /// Check if we're running on a desktop platform static bool get isDesktop { if (kIsWeb) return false; @@ -38,7 +37,8 @@ class PlatformFileService { } /// Save file using "Save As" dialog (desktop only) - static Future _saveFileAs(File sourceFile, String? suggestedName) async { + static Future _saveFileAs( + File sourceFile, String? suggestedName) async { try { final fileName = suggestedName ?? sourceFile.path.split('/').last; @@ -77,7 +77,9 @@ class PlatformFileService { ); return result.status == ShareResultStatus.success || - result.status == ShareResultStatus.unavailable; // unavailable means it worked on some platforms + result.status == + ShareResultStatus + .unavailable; // unavailable means it worked on some platforms } catch (e) { print('Error sharing file: $e'); return false; @@ -113,4 +115,3 @@ class PlatformFileService { } } } - diff --git a/lib/settings/backup-page.dart b/lib/settings/backup-page.dart index 72b6616d..beabce73 100644 --- a/lib/settings/backup-page.dart +++ b/lib/settings/backup-page.dart @@ -261,28 +261,31 @@ class BackupPageState extends State { return SingleChildScrollView( child: Column( children: [ - SettingsItem( - icon: Icon(Icons.backup, color: Colors.white), - iconBackgroundColor: Colors.orange.shade600, - title: 'Export Backup'.i18n, - subtitle: "Share the backup file".i18n, - onPressed: () async => - await createAndShareBackupFile()), - SettingsItem( - icon: Icon(Icons.dataset, color: Colors.white), - iconBackgroundColor: Colors.blueGrey.shade600, - title: 'Export Database'.i18n, - subtitle: "Share the database file".i18n, - onPressed: () async => await shareDatabase()), + if (Platform.isAndroid) + SettingsItem( + icon: Icon(Icons.backup, color: Colors.white), + iconBackgroundColor: Colors.orange.shade600, + title: 'Export Backup'.i18n, + subtitle: "Share the backup file".i18n, + onPressed: () async => + await createAndShareBackupFile()), + if (Platform.isAndroid) + SettingsItem( + icon: Icon(Icons.dataset, color: Colors.white), + iconBackgroundColor: Colors.blueGrey.shade600, + title: 'Export Database'.i18n, + subtitle: "Share the database file".i18n, + onPressed: () async => await shareDatabase()), SettingsItem( icon: Icon(Icons.save_alt, color: Colors.white), iconBackgroundColor: Colors.lightBlue.shade600, title: 'Store the Backup on disk'.i18n, onPressed: () async => await storeBackupFile(context)), - ClickableCustomizationItem( - title: "Destination folder".i18n, - subtitle: backupFolderPath, - enabled: false), + if (Platform.isAndroid) + ClickableCustomizationItem( + title: "Destination folder".i18n, + subtitle: backupFolderPath, + enabled: false), SwitchCustomizationItem( title: "Backup encryption".i18n, subtitle: diff --git a/lib/settings/backup-restore-dialogs.dart b/lib/settings/backup-restore-dialogs.dart index a44f7ca9..082e72aa 100644 --- a/lib/settings/backup-restore-dialogs.dart +++ b/lib/settings/backup-restore-dialogs.dart @@ -55,7 +55,9 @@ class BackupRestoreDialog { static Future importFromBackupFile(BuildContext context) async { try { - var hasDeletedCache = await FilePicker.platform.clearTemporaryFiles(); + bool hasDeletedCache = Platform.isAndroid + ? (await FilePicker.platform.clearTemporaryFiles() as bool) + : false; log("FilePicker has deleted cache: " + hasDeletedCache.toString()); } catch (e) { log("FilePicker.clearTemporaryFiles() not implemented on this platform"); @@ -109,7 +111,7 @@ class BackupRestoreDialog { BuildContext context, String title, String subtitle) async { AlertDialogBuilder resultDialog = AlertDialogBuilder(title) .addSubtitle(subtitle) - .addTrueButtonName("OK".i18n); + .renameTrueButtonName("OK".i18n); await showDialog( context: context, builder: (BuildContext context) { diff --git a/lib/settings/components/setting-separator.dart b/lib/settings/components/setting-separator.dart index ca4955b2..57caf3b6 100644 --- a/lib/settings/components/setting-separator.dart +++ b/lib/settings/components/setting-separator.dart @@ -17,9 +17,9 @@ class SettingSeparator extends StatelessWidget { child: Text( title, style: Theme.of(context).textTheme.titleMedium?.copyWith( - color: Theme.of(context).colorScheme.secondary, - fontWeight: FontWeight.bold, - ), + color: Theme.of(context).colorScheme.secondary, + fontWeight: FontWeight.bold, + ), ), ), const Divider(thickness: 1.5), @@ -27,4 +27,4 @@ class SettingSeparator extends StatelessWidget { ), ); } -} \ No newline at end of file +} diff --git a/lib/settings/constants/overview-time-interval.dart b/lib/settings/constants/overview-time-interval.dart index 8aa9d1be..2ff802ce 100644 --- a/lib/settings/constants/overview-time-interval.dart +++ b/lib/settings/constants/overview-time-interval.dart @@ -1 +1,6 @@ -enum OverviewTimeInterval { DisplayedRecords, FixCurrentMonth, FixCurrentYear, FixAllRecords } +enum OverviewTimeInterval { + DisplayedRecords, + FixCurrentMonth, + FixCurrentYear, + FixAllRecords +} diff --git a/lib/settings/preferences-utils.dart b/lib/settings/preferences-utils.dart index a94cca1a..69377f0f 100644 --- a/lib/settings/preferences-utils.dart +++ b/lib/settings/preferences-utils.dart @@ -16,4 +16,4 @@ class PreferencesUtils { return defaultValueOfFunction as T?; } -} \ No newline at end of file +} diff --git a/lib/settings/settings-page.dart b/lib/settings/settings-page.dart index 20086213..16af462a 100644 --- a/lib/settings/settings-page.dart +++ b/lib/settings/settings-page.dart @@ -39,8 +39,8 @@ class TabSettings extends StatelessWidget { AlertDialogBuilder premiumDialog = AlertDialogBuilder("Critical action".i18n) .addSubtitle("Do you really want to delete all the data?".i18n) - .addTrueButtonName("Yes".i18n) - .addFalseButtonName("No".i18n); + .renameTrueButtonName("Yes".i18n) + .renameFalseButtonName("No".i18n); var ok = await showDialog( context: context, builder: (BuildContext context) { @@ -51,7 +51,7 @@ class TabSettings extends StatelessWidget { AlertDialogBuilder resultDialog = AlertDialogBuilder("Data is deleted".i18n) .addSubtitle("All the data has been deleted".i18n) - .addTrueButtonName("OK"); + .hideFalseButton(); await showDialog( context: context, builder: (BuildContext context) { diff --git a/lib/tags/tags-page-view.dart b/lib/tags/tags-page-view.dart index 7b1906e1..527fa764 100644 --- a/lib/tags/tags-page-view.dart +++ b/lib/tags/tags-page-view.dart @@ -63,10 +63,9 @@ class TagsPageViewState extends State { title: Text('Delete tags'.i18n), content: Text(selectedTags.length == 1 ? 'Are you sure you want to delete this tag?'.i18n - : 'Are you sure you want to delete these %s tags?'.i18n.fill( - [selectedTags.length.toString()] - ) - ), + : 'Are you sure you want to delete these %s tags?' + .i18n + .fill([selectedTags.length.toString()])), actions: [ TextButton( onPressed: () => Navigator.of(context).pop(), diff --git a/linux/packaging/deb/make_config.yaml b/linux/packaging/deb/make_config.yaml index 47026db0..d72a3b0b 100644 --- a/linux/packaging/deb/make_config.yaml +++ b/linux/packaging/deb/make_config.yaml @@ -1,6 +1,6 @@ display_name: Oinkoin package_name: oinkoin -version: 1.1.6 +version: 1.1.10 maintainer: name: Oinkoin Team diff --git a/linux/packaging/rpm/make_config.yaml b/linux/packaging/rpm/make_config.yaml index 48587414..80ee4b04 100644 --- a/linux/packaging/rpm/make_config.yaml +++ b/linux/packaging/rpm/make_config.yaml @@ -1,6 +1,6 @@ display_name: Oinkoin package_name: oinkoin -version: 1.1.6 +version: 1.1.10 maintainer: name: Oinkoin Team diff --git a/macos/.gitignore b/macos/.gitignore new file mode 100644 index 00000000..746adbb6 --- /dev/null +++ b/macos/.gitignore @@ -0,0 +1,7 @@ +# Flutter-related +**/Flutter/ephemeral/ +**/Pods/ + +# Xcode-related +**/dgph +**/xcuserdata/ diff --git a/macos/Flutter/Flutter-Debug.xcconfig b/macos/Flutter/Flutter-Debug.xcconfig new file mode 100644 index 00000000..4b81f9b2 --- /dev/null +++ b/macos/Flutter/Flutter-Debug.xcconfig @@ -0,0 +1,2 @@ +#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig" +#include "ephemeral/Flutter-Generated.xcconfig" diff --git a/macos/Flutter/Flutter-Release.xcconfig b/macos/Flutter/Flutter-Release.xcconfig new file mode 100644 index 00000000..5caa9d15 --- /dev/null +++ b/macos/Flutter/Flutter-Release.xcconfig @@ -0,0 +1,2 @@ +#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig" +#include "ephemeral/Flutter-Generated.xcconfig" diff --git a/macos/Podfile b/macos/Podfile new file mode 100644 index 00000000..ff5ddb3b --- /dev/null +++ b/macos/Podfile @@ -0,0 +1,42 @@ +platform :osx, '10.15' + +# CocoaPods analytics sends network stats synchronously affecting flutter build latency. +ENV['COCOAPODS_DISABLE_STATS'] = 'true' + +project 'Runner', { + 'Debug' => :debug, + 'Profile' => :release, + 'Release' => :release, +} + +def flutter_root + generated_xcode_build_settings_path = File.expand_path(File.join('..', 'Flutter', 'ephemeral', 'Flutter-Generated.xcconfig'), __FILE__) + unless File.exist?(generated_xcode_build_settings_path) + raise "#{generated_xcode_build_settings_path} must exist. If you're running pod install manually, make sure \"flutter pub get\" is executed first" + end + + File.foreach(generated_xcode_build_settings_path) do |line| + matches = line.match(/FLUTTER_ROOT\=(.*)/) + return matches[1].strip if matches + end + raise "FLUTTER_ROOT not found in #{generated_xcode_build_settings_path}. Try deleting Flutter-Generated.xcconfig, then run \"flutter pub get\"" +end + +require File.expand_path(File.join('packages', 'flutter_tools', 'bin', 'podhelper'), flutter_root) + +flutter_macos_podfile_setup + +target 'Runner' do + use_frameworks! + + flutter_install_all_macos_pods File.dirname(File.realpath(__FILE__)) + target 'RunnerTests' do + inherit! :search_paths + end +end + +post_install do |installer| + installer.pods_project.targets.each do |target| + flutter_additional_macos_build_settings(target) + end +end diff --git a/macos/Podfile.lock b/macos/Podfile.lock new file mode 100644 index 00000000..c569def9 --- /dev/null +++ b/macos/Podfile.lock @@ -0,0 +1,92 @@ +PODS: + - emoji_picker_flutter (0.0.1): + - FlutterMacOS + - file_picker (0.0.1): + - FlutterMacOS + - file_selector_macos (0.0.1): + - FlutterMacOS + - flutter_timezone (0.1.0): + - FlutterMacOS + - FlutterMacOS (1.0.0) + - local_auth_darwin (0.0.1): + - Flutter + - FlutterMacOS + - package_info_plus (0.0.1): + - FlutterMacOS + - path_provider_foundation (0.0.1): + - Flutter + - FlutterMacOS + - share_plus (0.0.1): + - FlutterMacOS + - shared_preferences_foundation (0.0.1): + - Flutter + - FlutterMacOS + - sqflite_darwin (0.0.4): + - Flutter + - FlutterMacOS + - system_theme (3.2.0): + - FlutterMacOS + - url_launcher_macos (0.0.1): + - FlutterMacOS + +DEPENDENCIES: + - emoji_picker_flutter (from `Flutter/ephemeral/.symlinks/plugins/emoji_picker_flutter/macos`) + - file_picker (from `Flutter/ephemeral/.symlinks/plugins/file_picker/macos`) + - file_selector_macos (from `Flutter/ephemeral/.symlinks/plugins/file_selector_macos/macos`) + - flutter_timezone (from `Flutter/ephemeral/.symlinks/plugins/flutter_timezone/macos`) + - FlutterMacOS (from `Flutter/ephemeral`) + - local_auth_darwin (from `Flutter/ephemeral/.symlinks/plugins/local_auth_darwin/darwin`) + - package_info_plus (from `Flutter/ephemeral/.symlinks/plugins/package_info_plus/macos`) + - path_provider_foundation (from `Flutter/ephemeral/.symlinks/plugins/path_provider_foundation/darwin`) + - share_plus (from `Flutter/ephemeral/.symlinks/plugins/share_plus/macos`) + - shared_preferences_foundation (from `Flutter/ephemeral/.symlinks/plugins/shared_preferences_foundation/darwin`) + - sqflite_darwin (from `Flutter/ephemeral/.symlinks/plugins/sqflite_darwin/darwin`) + - system_theme (from `Flutter/ephemeral/.symlinks/plugins/system_theme/macos`) + - url_launcher_macos (from `Flutter/ephemeral/.symlinks/plugins/url_launcher_macos/macos`) + +EXTERNAL SOURCES: + emoji_picker_flutter: + :path: Flutter/ephemeral/.symlinks/plugins/emoji_picker_flutter/macos + file_picker: + :path: Flutter/ephemeral/.symlinks/plugins/file_picker/macos + file_selector_macos: + :path: Flutter/ephemeral/.symlinks/plugins/file_selector_macos/macos + flutter_timezone: + :path: Flutter/ephemeral/.symlinks/plugins/flutter_timezone/macos + FlutterMacOS: + :path: Flutter/ephemeral + local_auth_darwin: + :path: Flutter/ephemeral/.symlinks/plugins/local_auth_darwin/darwin + package_info_plus: + :path: Flutter/ephemeral/.symlinks/plugins/package_info_plus/macos + path_provider_foundation: + :path: Flutter/ephemeral/.symlinks/plugins/path_provider_foundation/darwin + share_plus: + :path: Flutter/ephemeral/.symlinks/plugins/share_plus/macos + shared_preferences_foundation: + :path: Flutter/ephemeral/.symlinks/plugins/shared_preferences_foundation/darwin + sqflite_darwin: + :path: Flutter/ephemeral/.symlinks/plugins/sqflite_darwin/darwin + system_theme: + :path: Flutter/ephemeral/.symlinks/plugins/system_theme/macos + url_launcher_macos: + :path: Flutter/ephemeral/.symlinks/plugins/url_launcher_macos/macos + +SPEC CHECKSUMS: + emoji_picker_flutter: 51ca408e289d84d1e460016b2a28721ec754fcf7 + file_picker: 7584aae6fa07a041af2b36a2655122d42f578c1a + file_selector_macos: 9e9e068e90ebee155097d00e89ae91edb2374db7 + flutter_timezone: d59eea86178cbd7943cd2431cc2eaa9850f935d8 + FlutterMacOS: d0db08ddef1a9af05a5ec4b724367152bb0500b1 + local_auth_darwin: c3ee6cce0a8d56be34c8ccb66ba31f7f180aaebb + package_info_plus: f0052d280d17aa382b932f399edf32507174e870 + path_provider_foundation: bb55f6dbba17d0dccd6737fe6f7f34fbd0376880 + share_plus: 510bf0af1a42cd602274b4629920c9649c52f4cc + shared_preferences_foundation: 7036424c3d8ec98dfe75ff1667cb0cd531ec82bb + sqflite_darwin: 20b2a3a3b70e43edae938624ce550a3cbf66a3d0 + system_theme: 6a419772fd834d53d9eb27accb3653b399c74426 + url_launcher_macos: f87a979182d112f911de6820aefddaf56ee9fbfd + +PODFILE CHECKSUM: 54d867c82ac51cbd61b565781b9fada492027009 + +COCOAPODS: 1.16.2 diff --git a/macos/Runner.xcodeproj/project.pbxproj b/macos/Runner.xcodeproj/project.pbxproj new file mode 100644 index 00000000..c9bf39dc --- /dev/null +++ b/macos/Runner.xcodeproj/project.pbxproj @@ -0,0 +1,791 @@ +// !$*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 */; }; + 782F78575047F81A429700B1 /* Pods_RunnerTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D30ACF996EEE54058F06B69D /* Pods_RunnerTests.framework */; }; + A33A1B4A858DC4939112D5D5 /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 8E161C60647CADB275345FDE /* Pods_Runner.framework */; }; +/* 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 = ""; }; + 333000ED22D3DE5D00554162 /* Warnings.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Warnings.xcconfig; sourceTree = ""; }; + 335BBD1A22A9A15E00E9071D /* GeneratedPluginRegistrant.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GeneratedPluginRegistrant.swift; sourceTree = ""; }; + 33CC10ED2044A3C60003C045 /* piggybank.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = piggybank.app; sourceTree = BUILT_PRODUCTS_DIR; }; + 33CC10F02044A3C60003C045 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; + 33CC10F22044A3C60003C045 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; name = Assets.xcassets; path = Runner/Assets.xcassets; sourceTree = ""; }; + 33CC10F52044A3C60003C045 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/MainMenu.xib; sourceTree = ""; }; + 33CC10F72044A3C60003C045 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; name = Info.plist; path = Runner/Info.plist; sourceTree = ""; }; + 33CC11122044BFA00003C045 /* MainFlutterWindow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MainFlutterWindow.swift; sourceTree = ""; }; + 33CEB47222A05771004F2AC0 /* Flutter-Debug.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = "Flutter-Debug.xcconfig"; sourceTree = ""; }; + 33CEB47422A05771004F2AC0 /* Flutter-Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = "Flutter-Release.xcconfig"; sourceTree = ""; }; + 33CEB47722A0578A004F2AC0 /* Flutter-Generated.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = "Flutter-Generated.xcconfig"; path = "ephemeral/Flutter-Generated.xcconfig"; sourceTree = ""; }; + 33E51913231747F40026EE4D /* DebugProfile.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = DebugProfile.entitlements; sourceTree = ""; }; + 33E51914231749380026EE4D /* Release.entitlements */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.entitlements; path = Release.entitlements; sourceTree = ""; }; + 33E5194F232828860026EE4D /* AppInfo.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = AppInfo.xcconfig; sourceTree = ""; }; + 60940BB32018ED3B0085BB4B /* Pods-RunnerTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.debug.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.debug.xcconfig"; sourceTree = ""; }; + 7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Release.xcconfig; sourceTree = ""; }; + 7CF06CD2ED2E1B2C41215CD9 /* Pods-Runner.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.profile.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.profile.xcconfig"; sourceTree = ""; }; + 8E161C60647CADB275345FDE /* Pods_Runner.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Runner.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; path = Debug.xcconfig; sourceTree = ""; }; + A98FDDE8031C3A3A3FA71E36 /* Pods-Runner.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.release.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"; sourceTree = ""; }; + B1814BB74D385F2345CDA0DB /* Pods-Runner.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.debug.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"; sourceTree = ""; }; + C33AB5261D69BA79ADC027F5 /* Pods-RunnerTests.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.profile.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.profile.xcconfig"; sourceTree = ""; }; + D30ACF996EEE54058F06B69D /* Pods_RunnerTests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_RunnerTests.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + FD652FC8A9098AC81BDD3A82 /* Pods-RunnerTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.release.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.release.xcconfig"; sourceTree = ""; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + 331C80D2294CF70F00263BE5 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 782F78575047F81A429700B1 /* Pods_RunnerTests.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 33CC10EA2044A3C60003C045 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + A33A1B4A858DC4939112D5D5 /* Pods_Runner.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 331C80D6294CF71000263BE5 /* RunnerTests */ = { + isa = PBXGroup; + children = ( + 331C80D7294CF71000263BE5 /* RunnerTests.swift */, + ); + path = RunnerTests; + sourceTree = ""; + }; + 33BA886A226E78AF003329D5 /* Configs */ = { + isa = PBXGroup; + children = ( + 33E5194F232828860026EE4D /* AppInfo.xcconfig */, + 9740EEB21CF90195004384FC /* Debug.xcconfig */, + 7AFA3C8E1D35360C0083082E /* Release.xcconfig */, + 333000ED22D3DE5D00554162 /* Warnings.xcconfig */, + ); + path = Configs; + sourceTree = ""; + }; + 33CC10E42044A3C60003C045 = { + isa = PBXGroup; + children = ( + 33FAB671232836740065AC1E /* Runner */, + 33CEB47122A05771004F2AC0 /* Flutter */, + 331C80D6294CF71000263BE5 /* RunnerTests */, + 33CC10EE2044A3C60003C045 /* Products */, + D73912EC22F37F3D000D13A0 /* Frameworks */, + D0CB489626EFCBDF3E04EC71 /* Pods */, + ); + sourceTree = ""; + }; + 33CC10EE2044A3C60003C045 /* Products */ = { + isa = PBXGroup; + children = ( + 33CC10ED2044A3C60003C045 /* piggybank.app */, + 331C80D5294CF71000263BE5 /* RunnerTests.xctest */, + ); + name = Products; + sourceTree = ""; + }; + 33CC11242044D66E0003C045 /* Resources */ = { + isa = PBXGroup; + children = ( + 33CC10F22044A3C60003C045 /* Assets.xcassets */, + 33CC10F42044A3C60003C045 /* MainMenu.xib */, + 33CC10F72044A3C60003C045 /* Info.plist */, + ); + name = Resources; + path = ..; + sourceTree = ""; + }; + 33CEB47122A05771004F2AC0 /* Flutter */ = { + isa = PBXGroup; + children = ( + 335BBD1A22A9A15E00E9071D /* GeneratedPluginRegistrant.swift */, + 33CEB47222A05771004F2AC0 /* Flutter-Debug.xcconfig */, + 33CEB47422A05771004F2AC0 /* Flutter-Release.xcconfig */, + 33CEB47722A0578A004F2AC0 /* Flutter-Generated.xcconfig */, + ); + path = Flutter; + sourceTree = ""; + }; + 33FAB671232836740065AC1E /* Runner */ = { + isa = PBXGroup; + children = ( + 33CC10F02044A3C60003C045 /* AppDelegate.swift */, + 33CC11122044BFA00003C045 /* MainFlutterWindow.swift */, + 33E51913231747F40026EE4D /* DebugProfile.entitlements */, + 33E51914231749380026EE4D /* Release.entitlements */, + 33CC11242044D66E0003C045 /* Resources */, + 33BA886A226E78AF003329D5 /* Configs */, + ); + path = Runner; + sourceTree = ""; + }; + D0CB489626EFCBDF3E04EC71 /* Pods */ = { + isa = PBXGroup; + children = ( + B1814BB74D385F2345CDA0DB /* Pods-Runner.debug.xcconfig */, + A98FDDE8031C3A3A3FA71E36 /* Pods-Runner.release.xcconfig */, + 7CF06CD2ED2E1B2C41215CD9 /* Pods-Runner.profile.xcconfig */, + 60940BB32018ED3B0085BB4B /* Pods-RunnerTests.debug.xcconfig */, + FD652FC8A9098AC81BDD3A82 /* Pods-RunnerTests.release.xcconfig */, + C33AB5261D69BA79ADC027F5 /* Pods-RunnerTests.profile.xcconfig */, + ); + path = Pods; + sourceTree = ""; + }; + D73912EC22F37F3D000D13A0 /* Frameworks */ = { + isa = PBXGroup; + children = ( + 8E161C60647CADB275345FDE /* Pods_Runner.framework */, + D30ACF996EEE54058F06B69D /* Pods_RunnerTests.framework */, + ); + name = Frameworks; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + 331C80D4294CF70F00263BE5 /* RunnerTests */ = { + isa = PBXNativeTarget; + buildConfigurationList = 331C80DE294CF71000263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */; + buildPhases = ( + 19B56F26A2F39D890E3A4F35 /* [CP] Check Pods Manifest.lock */, + 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 = ( + 1EE3F92A37BF4436ADDE32FF /* [CP] Check Pods Manifest.lock */, + 33CC10E92044A3C60003C045 /* Sources */, + 33CC10EA2044A3C60003C045 /* Frameworks */, + 33CC10EB2044A3C60003C045 /* Resources */, + 33CC110E2044A8840003C045 /* Bundle Framework */, + 3399D490228B24CF009A79C7 /* ShellScript */, + 5ECC50B98DD7046DD5F3B18B /* [CP] Embed Pods Frameworks */, + ); + buildRules = ( + ); + dependencies = ( + 33CC11202044C79F0003C045 /* PBXTargetDependency */, + ); + name = Runner; + productName = Runner; + productReference = 33CC10ED2044A3C60003C045 /* piggybank.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" */; + developmentRegion = en; + hasScannedForEncodings = 0; + knownRegions = ( + en, + Base, + ); + mainGroup = 33CC10E42044A3C60003C045; + preferredProjectObjectVersion = 77; + 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 */ + 19B56F26A2F39D890E3A4F35 /* [CP] Check Pods Manifest.lock */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + "${PODS_PODFILE_DIR_PATH}/Podfile.lock", + "${PODS_ROOT}/Manifest.lock", + ); + name = "[CP] Check Pods Manifest.lock"; + outputPaths = ( + "$(DERIVED_FILE_DIR)/Pods-RunnerTests-checkManifestLockResult.txt", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; + showEnvVarsInLog = 0; + }; + 1EE3F92A37BF4436ADDE32FF /* [CP] Check Pods Manifest.lock */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + "${PODS_PODFILE_DIR_PATH}/Podfile.lock", + "${PODS_ROOT}/Manifest.lock", + ); + name = "[CP] Check Pods Manifest.lock"; + outputPaths = ( + "$(DERIVED_FILE_DIR)/Pods-Runner-checkManifestLockResult.txt", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; + showEnvVarsInLog = 0; + }; + 3399D490228B24CF009A79C7 /* ShellScript */ = { + isa = PBXShellScriptBuildPhase; + alwaysOutOfDate = 1; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + ); + 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\n"; + }; + 5ECC50B98DD7046DD5F3B18B /* [CP] Embed Pods Frameworks */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-input-files.xcfilelist", + ); + name = "[CP] Embed Pods Frameworks"; + outputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-output-files.xcfilelist", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh\"\n"; + showEnvVarsInLog = 0; + }; +/* 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 = ""; + }; +/* End PBXVariantGroup section */ + +/* Begin XCBuildConfiguration section */ + 331C80DB294CF71000263BE5 /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 60940BB32018ED3B0085BB4B /* Pods-RunnerTests.debug.xcconfig */; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CURRENT_PROJECT_VERSION = 1; + GENERATE_INFOPLIST_FILE = YES; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = com.github.emavgl.piggybank.RunnerTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 5.0; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/piggybank.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/piggybank"; + }; + name = Debug; + }; + 331C80DC294CF71000263BE5 /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = FD652FC8A9098AC81BDD3A82 /* Pods-RunnerTests.release.xcconfig */; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CURRENT_PROJECT_VERSION = 1; + GENERATE_INFOPLIST_FILE = YES; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = com.github.emavgl.piggybank.RunnerTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 5.0; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/piggybank.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/piggybank"; + }; + name = Release; + }; + 331C80DD294CF71000263BE5 /* Profile */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = C33AB5261D69BA79ADC027F5 /* Pods-RunnerTests.profile.xcconfig */; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CURRENT_PROJECT_VERSION = 1; + GENERATE_INFOPLIST_FILE = YES; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = com.github.emavgl.piggybank.RunnerTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 5.0; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/piggybank.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/piggybank"; + }; + 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 = 14.6; + 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", + ); + MACOSX_DEPLOYMENT_TARGET = 14.6; + 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 = 14.6; + 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 = 14.6; + 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", + ); + MACOSX_DEPLOYMENT_TARGET = 14.6; + 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", + ); + MACOSX_DEPLOYMENT_TARGET = 14.6; + 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 */; +} diff --git a/macos/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/macos/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 00000000..18d98100 --- /dev/null +++ b/macos/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme new file mode 100644 index 00000000..b321cbf6 --- /dev/null +++ b/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme @@ -0,0 +1,99 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/macos/Runner.xcworkspace/contents.xcworkspacedata b/macos/Runner.xcworkspace/contents.xcworkspacedata new file mode 100644 index 00000000..21a3cc14 --- /dev/null +++ b/macos/Runner.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,10 @@ + + + + + + + diff --git a/macos/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/macos/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 00000000..18d98100 --- /dev/null +++ b/macos/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/macos/Runner/AppDelegate.swift b/macos/Runner/AppDelegate.swift new file mode 100644 index 00000000..b3c17614 --- /dev/null +++ b/macos/Runner/AppDelegate.swift @@ -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 + } +} diff --git a/macos/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json b/macos/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json new file mode 100644 index 00000000..a2ec33f1 --- /dev/null +++ b/macos/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json @@ -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" + } +} diff --git a/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png b/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png new file mode 100644 index 00000000..82b6f9d9 Binary files /dev/null and b/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png differ diff --git a/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png b/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png new file mode 100644 index 00000000..13b35eba Binary files /dev/null and b/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png differ diff --git a/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png b/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png new file mode 100644 index 00000000..0a3f5fa4 Binary files /dev/null and b/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png differ diff --git a/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png b/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png new file mode 100644 index 00000000..bdb57226 Binary files /dev/null and b/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png differ diff --git a/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32.png b/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32.png new file mode 100644 index 00000000..f083318e Binary files /dev/null and b/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32.png differ diff --git a/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png b/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png new file mode 100644 index 00000000..326c0e72 Binary files /dev/null and b/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png differ diff --git a/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png b/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png new file mode 100644 index 00000000..2f1632cf Binary files /dev/null and b/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png differ diff --git a/macos/Runner/Base.lproj/MainMenu.xib b/macos/Runner/Base.lproj/MainMenu.xib new file mode 100644 index 00000000..80e867a4 --- /dev/null +++ b/macos/Runner/Base.lproj/MainMenu.xib @@ -0,0 +1,343 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/macos/Runner/Configs/AppInfo.xcconfig b/macos/Runner/Configs/AppInfo.xcconfig new file mode 100644 index 00000000..66755315 --- /dev/null +++ b/macos/Runner/Configs/AppInfo.xcconfig @@ -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 = piggybank + +// The application's bundle identifier +PRODUCT_BUNDLE_IDENTIFIER = com.github.emavgl.piggybank + +// The copyright displayed in application information +PRODUCT_COPYRIGHT = Copyright © 2026 com.github.emavgl. All rights reserved. diff --git a/macos/Runner/Configs/Debug.xcconfig b/macos/Runner/Configs/Debug.xcconfig new file mode 100644 index 00000000..36b0fd94 --- /dev/null +++ b/macos/Runner/Configs/Debug.xcconfig @@ -0,0 +1,2 @@ +#include "../../Flutter/Flutter-Debug.xcconfig" +#include "Warnings.xcconfig" diff --git a/macos/Runner/Configs/Release.xcconfig b/macos/Runner/Configs/Release.xcconfig new file mode 100644 index 00000000..dff4f495 --- /dev/null +++ b/macos/Runner/Configs/Release.xcconfig @@ -0,0 +1,2 @@ +#include "../../Flutter/Flutter-Release.xcconfig" +#include "Warnings.xcconfig" diff --git a/macos/Runner/Configs/Warnings.xcconfig b/macos/Runner/Configs/Warnings.xcconfig new file mode 100644 index 00000000..42bcbf47 --- /dev/null +++ b/macos/Runner/Configs/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 diff --git a/macos/Runner/DebugProfile.entitlements b/macos/Runner/DebugProfile.entitlements new file mode 100644 index 00000000..d138bd5b --- /dev/null +++ b/macos/Runner/DebugProfile.entitlements @@ -0,0 +1,14 @@ + + + + + com.apple.security.app-sandbox + + com.apple.security.cs.allow-jit + + com.apple.security.network.server + + com.apple.security.files.user-selected.read-write + + + diff --git a/macos/Runner/Info.plist b/macos/Runner/Info.plist new file mode 100644 index 00000000..4789daa6 --- /dev/null +++ b/macos/Runner/Info.plist @@ -0,0 +1,32 @@ + + + + + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIconFile + + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + APPL + CFBundleShortVersionString + $(FLUTTER_BUILD_NAME) + CFBundleVersion + $(FLUTTER_BUILD_NUMBER) + LSMinimumSystemVersion + $(MACOSX_DEPLOYMENT_TARGET) + NSHumanReadableCopyright + $(PRODUCT_COPYRIGHT) + NSMainNibFile + MainMenu + NSPrincipalClass + NSApplication + + diff --git a/macos/Runner/MainFlutterWindow.swift b/macos/Runner/MainFlutterWindow.swift new file mode 100644 index 00000000..3cc05eb2 --- /dev/null +++ b/macos/Runner/MainFlutterWindow.swift @@ -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() + } +} diff --git a/macos/Runner/Release.entitlements b/macos/Runner/Release.entitlements new file mode 100644 index 00000000..19afff14 --- /dev/null +++ b/macos/Runner/Release.entitlements @@ -0,0 +1,10 @@ + + + + + com.apple.security.app-sandbox + + com.apple.security.files.user-selected.read-write + + + diff --git a/macos/RunnerTests/RunnerTests.swift b/macos/RunnerTests/RunnerTests.swift new file mode 100644 index 00000000..61f3bd1f --- /dev/null +++ b/macos/RunnerTests/RunnerTests.swift @@ -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. + } + +} diff --git a/pubspec.lock b/pubspec.lock index 200a966c..5bbfe3ff 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -173,10 +173,10 @@ packages: dependency: transitive description: name: code_builder - sha256: "11654819532ba94c34de52ff5feb52bd81cba1de00ef2ed622fd50295f9d4243" + sha256: "6a6cab2ba4680d6423f34a9b972a4c9a94ebe1b62ecec4e1a1f2cba91fd1319d" url: "https://pub.dev" source: hosted - version: "4.11.0" + version: "4.11.1" collection: dependency: transitive description: @@ -293,10 +293,10 @@ packages: dependency: transitive description: name: ffi - sha256: "289279317b4b16eb2bb7e271abccd4bf84ec9bdcbe999e278a94b804f5630418" + sha256: d07d37192dbf97461359c1518788f203b0c9102cfd2c35a716b823741219542c url: "https://pub.dev" source: hosted - version: "2.1.4" + version: "2.1.5" file: dependency: transitive description: @@ -309,10 +309,10 @@ packages: dependency: "direct main" description: name: file_picker - sha256: "7872545770c277236fd32b022767576c562ba28366204ff1a5628853cf8f2200" + sha256: d974b6ba2606371ac71dd94254beefb6fa81185bde0b59bdc1df09885da85fde url: "https://pub.dev" source: hosted - version: "10.3.7" + version: "10.3.8" file_selector: dependency: "direct main" description: @@ -1182,10 +1182,10 @@ packages: dependency: "direct main" description: name: sqflite_common_ffi - sha256: cc78dab586034b20a84904ff5a8eb63adae587baa1b383d40428e9ee491b30f6 + sha256: c59fcdc143839a77581f7a7c4de018e53682408903a0a0800b95ef2dc4033eff url: "https://pub.dev" source: hosted - version: "2.4.0+1" + version: "2.4.0+2" sqflite_darwin: dependency: transitive description: @@ -1206,10 +1206,10 @@ packages: dependency: transitive description: name: sqlite3 - sha256: "0374add4094d34fcf9d22068b6ddb4056782353ad4a5449bc5b71c5f2175f0ec" + sha256: "1d2d2afee96acccabf315e169a4d28019fa21d5217ee014840058a7fbc2008c6" url: "https://pub.dev" source: hosted - version: "3.1.1" + version: "3.1.2" stack_trace: dependency: transitive description: @@ -1254,18 +1254,18 @@ packages: dependency: "direct main" description: name: system_theme - sha256: "5f93485401689601d4636a695f99f7c70a30873ee68c1d95025d908a3386be7e" + sha256: "827cc5f94a6c3097a4c08d4f9eb11d345322f9deea5be1cb73611ff24bdf45bb" url: "https://pub.dev" source: hosted - version: "3.1.2" + version: "3.2.0" system_theme_web: dependency: transitive description: name: system_theme_web - sha256: "900c92c5c050ce58048f241ef9a17e5cd8629808325a05b473dc62a6e99bae77" + sha256: a354b25ff0788ed802b48b632187d344a841b2034f16e4f6cdaa295e4a41a8aa url: "https://pub.dev" source: hosted - version: "0.0.3" + version: "0.0.4" talker: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index 4d4c11d1..306ef399 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -69,7 +69,6 @@ dev_dependencies: build_runner: ^2.4.7 test: ^1.24.9 mockito: ^5.0.17 - sqflite_common_ffi: ^2.3.6 # For information on the generic Dart part of this file, see the # following page: https://dart.dev/tools/pub/pubspec diff --git a/submodules/flutter b/submodules/flutter index bd7a4a6b..8b872868 160000 --- a/submodules/flutter +++ b/submodules/flutter @@ -1 +1 @@ -Subproject commit bd7a4a6b5576630823ca344e3e684c53aa1a0f46 +Subproject commit 8b872868494e429d94fa06dca855c306438b22c0 diff --git a/test/backup/database_interface.mocks.dart b/test/backup/database_interface.mocks.dart index b9f78bb0..613744d4 100644 --- a/test/backup/database_interface.mocks.dart +++ b/test/backup/database_interface.mocks.dart @@ -1 +1 @@ -// TODO Implement this library. \ No newline at end of file +// TODO Implement this library. diff --git a/test/datetime_utility_functions_locale_test.dart b/test/datetime_utility_functions_locale_test.dart index 4591236c..9c449db9 100644 --- a/test/datetime_utility_functions_locale_test.dart +++ b/test/datetime_utility_functions_locale_test.dart @@ -23,7 +23,7 @@ void main() { test('should return Sunday when given a Sunday', () { final sunday = DateTime(2025, 12, 14); // Sunday final startOfWeek = getStartOfWeek(sunday); - + expect(startOfWeek.day, 14); expect(startOfWeek.weekday, DateTime.sunday); }); @@ -31,7 +31,7 @@ void main() { test('should return Sunday when given a Monday', () { final monday = DateTime(2025, 12, 15); // Monday final startOfWeek = getStartOfWeek(monday); - + expect(startOfWeek.day, 14); // Previous Sunday expect(startOfWeek.weekday, DateTime.sunday); }); @@ -39,7 +39,7 @@ void main() { test('should return Sunday when given a Saturday', () { final saturday = DateTime(2025, 12, 20); // Saturday final startOfWeek = getStartOfWeek(saturday); - + expect(startOfWeek.day, 14); // Sunday expect(startOfWeek.weekday, DateTime.sunday); }); @@ -47,7 +47,7 @@ void main() { test('should handle week crossing month boundary', () { final friday = DateTime(2025, 1, 3); // Friday final startOfWeek = getStartOfWeek(friday); - + expect(startOfWeek.year, 2024); expect(startOfWeek.month, 12); expect(startOfWeek.day, 29); // Sunday in previous month @@ -59,7 +59,7 @@ void main() { test('should return Saturday when given a Sunday', () { final sunday = DateTime(2025, 12, 14); // Sunday final endOfWeek = getEndOfWeek(sunday); - + expect(endOfWeek.day, 20); // Saturday expect(endOfWeek.weekday, DateTime.saturday); expect(endOfWeek.hour, 23); @@ -69,7 +69,7 @@ void main() { test('should return Saturday when given a Monday', () { final monday = DateTime(2025, 12, 15); // Monday final endOfWeek = getEndOfWeek(monday); - + expect(endOfWeek.day, 20); // Saturday expect(endOfWeek.weekday, DateTime.saturday); }); @@ -77,7 +77,7 @@ void main() { test('should handle week crossing month boundary', () { final monday = DateTime(2025, 12, 29); // Monday final endOfWeek = getEndOfWeek(monday); - + expect(endOfWeek.year, 2026); expect(endOfWeek.month, 1); expect(endOfWeek.day, 3); // Saturday in next month @@ -89,14 +89,14 @@ void main() { test('should return true for Sunday-Saturday week', () { final sunday = DateTime(2025, 12, 14); final saturday = DateTime(2025, 12, 20, 23, 59); - + expect(isFullWeek(sunday, saturday), true); }); test('should return false for Monday-Sunday week', () { final monday = DateTime(2025, 12, 15); final sunday = DateTime(2025, 12, 21, 23, 59); - + expect(isFullWeek(monday, sunday), false); }); }); @@ -105,10 +105,11 @@ void main() { final testDate = DateTime(2025, 12, 17); final startOfWeek = getStartOfWeek(testDate); final endOfWeek = getEndOfWeek(testDate); - - final startDay = DateTime(startOfWeek.year, startOfWeek.month, startOfWeek.day); + + final startDay = + DateTime(startOfWeek.year, startOfWeek.month, startOfWeek.day); final endDay = DateTime(endOfWeek.year, endOfWeek.month, endOfWeek.day); - + expect(endDay.difference(startDay).inDays, 6); expect(startOfWeek.weekday, DateTime.sunday); expect(endOfWeek.weekday, DateTime.saturday); @@ -125,7 +126,7 @@ void main() { test('should return Monday when given a Monday', () { final monday = DateTime(2025, 12, 15); // Monday final startOfWeek = getStartOfWeek(monday); - + expect(startOfWeek.day, 15); expect(startOfWeek.weekday, DateTime.monday); }); @@ -133,7 +134,7 @@ void main() { test('should return Monday when given a Tuesday', () { final tuesday = DateTime(2025, 12, 16); // Tuesday final startOfWeek = getStartOfWeek(tuesday); - + expect(startOfWeek.day, 15); // Monday expect(startOfWeek.weekday, DateTime.monday); }); @@ -141,7 +142,7 @@ void main() { test('should return Monday when given a Sunday', () { final sunday = DateTime(2025, 12, 21); // Sunday final startOfWeek = getStartOfWeek(sunday); - + expect(startOfWeek.day, 15); // Monday expect(startOfWeek.weekday, DateTime.monday); }); @@ -149,7 +150,7 @@ void main() { test('should handle week crossing month boundary', () { final friday = DateTime(2025, 1, 3); // Friday final startOfWeek = getStartOfWeek(friday); - + expect(startOfWeek.year, 2024); expect(startOfWeek.month, 12); expect(startOfWeek.day, 30); // Monday in previous month @@ -159,7 +160,7 @@ void main() { test('should handle week crossing year boundary', () { final thursday = DateTime(2025, 1, 2); // Thursday final startOfWeek = getStartOfWeek(thursday); - + expect(startOfWeek.year, 2024); expect(startOfWeek.month, 12); expect(startOfWeek.day, 30); // Monday in previous year @@ -171,7 +172,7 @@ void main() { test('should return Sunday when given a Monday', () { final monday = DateTime(2025, 12, 15); // Monday final endOfWeek = getEndOfWeek(monday); - + expect(endOfWeek.day, 21); // Sunday expect(endOfWeek.weekday, DateTime.sunday); expect(endOfWeek.hour, 23); @@ -181,7 +182,7 @@ void main() { test('should return Sunday when given a Wednesday', () { final wednesday = DateTime(2025, 12, 17); // Wednesday final endOfWeek = getEndOfWeek(wednesday); - + expect(endOfWeek.day, 21); // Sunday expect(endOfWeek.weekday, DateTime.sunday); }); @@ -189,7 +190,7 @@ void main() { test('should return Sunday when given a Sunday', () { final sunday = DateTime(2025, 12, 21); // Sunday final endOfWeek = getEndOfWeek(sunday); - + expect(endOfWeek.day, 21); // Same Sunday expect(endOfWeek.weekday, DateTime.sunday); }); @@ -197,7 +198,7 @@ void main() { test('should handle week crossing month boundary', () { final monday = DateTime(2025, 12, 29); // Monday final endOfWeek = getEndOfWeek(monday); - + expect(endOfWeek.year, 2026); expect(endOfWeek.month, 1); expect(endOfWeek.day, 4); // Sunday in next month @@ -207,7 +208,7 @@ void main() { test('should handle week crossing year boundary', () { final tuesday = DateTime(2025, 12, 30); // Tuesday final endOfWeek = getEndOfWeek(tuesday); - + expect(endOfWeek.year, 2026); expect(endOfWeek.month, 1); expect(endOfWeek.day, 4); // Sunday in next year @@ -219,21 +220,21 @@ void main() { test('should return true for Monday-Sunday week', () { final monday = DateTime(2025, 12, 15); final sunday = DateTime(2025, 12, 21, 23, 59); - + expect(isFullWeek(monday, sunday), true); }); test('should return false for Sunday-Saturday week', () { final sunday = DateTime(2025, 12, 14); final saturday = DateTime(2025, 12, 20, 23, 59); - + expect(isFullWeek(sunday, saturday), false); }); test('should return false for partial week', () { final tuesday = DateTime(2025, 12, 16); final friday = DateTime(2025, 12, 19, 23, 59); - + expect(isFullWeek(tuesday, friday), false); }); }); @@ -242,10 +243,11 @@ void main() { final testDate = DateTime(2025, 12, 17); final startOfWeek = getStartOfWeek(testDate); final endOfWeek = getEndOfWeek(testDate); - - final startDay = DateTime(startOfWeek.year, startOfWeek.month, startOfWeek.day); + + final startDay = + DateTime(startOfWeek.year, startOfWeek.month, startOfWeek.day); final endDay = DateTime(endOfWeek.year, endOfWeek.month, endOfWeek.day); - + expect(endDay.difference(startDay).inDays, 6); expect(startOfWeek.weekday, DateTime.monday); expect(endOfWeek.weekday, DateTime.sunday); @@ -256,12 +258,12 @@ void main() { final wednesday = DateTime(2025, 12, 17); final friday = DateTime(2025, 12, 19); final sunday = DateTime(2025, 12, 21); - + final mondayStr = getWeekStr(monday); final wednesdayStr = getWeekStr(wednesday); final fridayStr = getWeekStr(friday); final sundayStr = getWeekStr(sunday); - + expect(mondayStr, equals(wednesdayStr)); expect(mondayStr, equals(fridayStr)); expect(mondayStr, equals(sundayStr)); @@ -277,7 +279,7 @@ void main() { test('should use Monday as start of week', () { final wednesday = DateTime(2025, 12, 17); final startOfWeek = getStartOfWeek(wednesday); - + expect(startOfWeek.day, 15); expect(startOfWeek.weekday, DateTime.monday); }); @@ -285,12 +287,63 @@ void main() { test('should use Sunday as end of week', () { final wednesday = DateTime(2025, 12, 17); final endOfWeek = getEndOfWeek(wednesday); - + expect(endOfWeek.day, 21); expect(endOfWeek.weekday, DateTime.sunday); }); }); + // ===== Tests for canShift function ===== + group('canShift for CurrentWeek (locale-aware)', () { + test('should work correctly for Monday-start weeks', () { + I18n.define(Locale('en', 'GB')); + + bool canShiftBack = + canShift(-1, null, null, HomepageTimeInterval.CurrentWeek); + expect(canShiftBack, true); + + bool canShiftForward = + canShift(1, null, null, HomepageTimeInterval.CurrentWeek); + expect(canShiftForward, false); + }); + + test('should work correctly for Sunday-start weeks', () { + I18n.define(Locale('en', 'US')); + + bool canShiftBack = + canShift(-1, null, null, HomepageTimeInterval.CurrentWeek); + expect(canShiftBack, true); + + bool canShiftForward = + canShift(1, null, null, HomepageTimeInterval.CurrentWeek); + expect(canShiftForward, false); + }); + + test('should correctly validate custom week intervals (Monday-start)', () { + I18n.define(Locale('en', 'GB')); + + // Full week: Monday to Sunday + DateTime monday = DateTime(2025, 12, 15); + DateTime sunday = DateTime(2025, 12, 21, 23, 59); + + bool canShiftBack = + canShift(-1, monday, sunday, HomepageTimeInterval.CurrentWeek); + expect(canShiftBack, true); + }); + + test('should correctly validate custom week intervals (Sunday-start)', () { + I18n.define(Locale('en', 'US')); + + // Full week: Sunday to Saturday + DateTime sunday = DateTime(2025, 12, 14); + DateTime saturday = DateTime(2025, 12, 20, 23, 59); + + bool canShiftBack = + canShift(-1, sunday, saturday, HomepageTimeInterval.CurrentWeek); + expect(canShiftBack, true); + }); + }); + // ===== Tests for getDateRangeStr ===== group('getDateRangeStr', () { setUp(() { @@ -303,18 +356,20 @@ void main() { DateTime end = DateTime(2025, 11, 30, 23, 59, 59); String result = getDateRangeStr(start, end); - + expect(result.toLowerCase(), contains('november')); expect(result, contains('2025')); }); - test('should display date range when week ends on last day of month but does not start on 1st', () { + test( + 'should display date range when week ends on last day of month but does not start on 1st', + () { // Nov 24 - Nov 30 (week ending on last day, but not full month) DateTime start = DateTime(2025, 11, 24); DateTime end = DateTime(2025, 11, 30, 23, 59); - + String result = getDateRangeStr(start, end); - + // Should show date range, not just "November 2025" expect(result, contains('24')); expect(result, contains('30')); @@ -325,9 +380,9 @@ void main() { // Dec 15 - Dec 21 (regular week) DateTime start = DateTime(2025, 12, 15); DateTime end = DateTime(2025, 12, 21, 23, 59); - + String result = getDateRangeStr(start, end); - + expect(result, contains('15')); expect(result, contains('21')); expect(result, contains('-')); @@ -337,9 +392,9 @@ void main() { // Nov 30 - Dec 6 (crosses month boundary) DateTime start = DateTime(2025, 11, 30); DateTime end = DateTime(2025, 12, 6, 23, 59); - + String result = getDateRangeStr(start, end); - + expect(result, contains('30')); expect(result, contains('6')); expect(result, contains('-')); @@ -349,9 +404,9 @@ void main() { // Dec 29, 2025 - Jan 4, 2026 (crosses year boundary) DateTime start = DateTime(2025, 12, 29); DateTime end = DateTime(2026, 1, 4, 23, 59); - + String result = getDateRangeStr(start, end); - + expect(result, contains('29')); expect(result, contains('4')); expect(result, contains('2025')); @@ -363,9 +418,9 @@ void main() { // Should still work when dates are reversed DateTime start = DateTime(2025, 12, 21, 23, 59); DateTime end = DateTime(2025, 12, 15); - + String result = getDateRangeStr(start, end); - + expect(result, contains('15')); expect(result, contains('21')); expect(result, contains('-')); @@ -377,18 +432,20 @@ void main() { DateTime end = DateTime(2024, 2, 29, 23, 59, 59); String result = getDateRangeStr(start, end); - + expect(result.toLowerCase(), contains('february')); expect(result, contains('2024')); }); - test('should display date range when ending on Feb 28 in non-leap year but not starting on 1st', () { + test( + 'should display date range when ending on Feb 28 in non-leap year but not starting on 1st', + () { // Feb 22 - Feb 28, 2025 (week ending on last day of Feb in non-leap year) DateTime start = DateTime(2025, 2, 22); DateTime end = DateTime(2025, 2, 28, 23, 59); - + String result = getDateRangeStr(start, end); - + expect(result, contains('22')); expect(result, contains('28')); expect(result, contains('-')); diff --git a/test/datetime_utility_functions_test.dart b/test/datetime_utility_functions_test.dart index 03450041..baac3f93 100644 --- a/test/datetime_utility_functions_test.dart +++ b/test/datetime_utility_functions_test.dart @@ -159,7 +159,7 @@ void main() { // Sunday, December 14, 2025 final sunday = DateTime(2025, 12, 14); final startOfWeek = getStartOfWeek(sunday); - + expect(startOfWeek.year, 2025); expect(startOfWeek.month, 12); expect(startOfWeek.day, 14); @@ -170,7 +170,7 @@ void main() { // Monday, December 15, 2025 final monday = DateTime(2025, 12, 15); final startOfWeek = getStartOfWeek(monday); - + expect(startOfWeek.year, 2025); expect(startOfWeek.month, 12); expect(startOfWeek.day, 14); // Sunday @@ -181,7 +181,7 @@ void main() { // Tuesday, December 16, 2025 final tuesday = DateTime(2025, 12, 16); final startOfWeek = getStartOfWeek(tuesday); - + expect(startOfWeek.year, 2025); expect(startOfWeek.month, 12); expect(startOfWeek.day, 14); // Sunday @@ -192,7 +192,7 @@ void main() { // Wednesday, December 17, 2025 final wednesday = DateTime(2025, 12, 17); final startOfWeek = getStartOfWeek(wednesday); - + expect(startOfWeek.year, 2025); expect(startOfWeek.month, 12); expect(startOfWeek.day, 14); // Sunday @@ -203,7 +203,7 @@ void main() { // Saturday, December 20, 2025 final saturday = DateTime(2025, 12, 20); final startOfWeek = getStartOfWeek(saturday); - + expect(startOfWeek.year, 2025); expect(startOfWeek.month, 12); expect(startOfWeek.day, 14); // Sunday @@ -214,7 +214,7 @@ void main() { // Friday, January 3, 2025 final friday = DateTime(2025, 1, 3); final startOfWeek = getStartOfWeek(friday); - + expect(startOfWeek.year, 2024); expect(startOfWeek.month, 12); expect(startOfWeek.day, 29); // Sunday in previous month @@ -225,7 +225,7 @@ void main() { // Thursday, January 2, 2025 final thursday = DateTime(2025, 1, 2); final startOfWeek = getStartOfWeek(thursday); - + expect(startOfWeek.year, 2024); expect(startOfWeek.month, 12); expect(startOfWeek.day, 29); // Sunday in previous year @@ -238,7 +238,7 @@ void main() { // Monday, December 15, 2025 final monday = DateTime(2025, 12, 15); final endOfWeek = getEndOfWeek(monday); - + expect(endOfWeek.year, 2025); expect(endOfWeek.month, 12); expect(endOfWeek.day, 20); // Saturday @@ -251,7 +251,7 @@ void main() { // Wednesday, December 17, 2025 final wednesday = DateTime(2025, 12, 17); final endOfWeek = getEndOfWeek(wednesday); - + expect(endOfWeek.year, 2025); expect(endOfWeek.month, 12); expect(endOfWeek.day, 20); // Saturday @@ -264,7 +264,7 @@ void main() { // Sunday, December 14, 2025 final sunday = DateTime(2025, 12, 14); final endOfWeek = getEndOfWeek(sunday); - + expect(endOfWeek.year, 2025); expect(endOfWeek.month, 12); expect(endOfWeek.day, 20); // Saturday @@ -277,7 +277,7 @@ void main() { // Monday, December 29, 2025 final monday = DateTime(2025, 12, 29); final endOfWeek = getEndOfWeek(monday); - + expect(endOfWeek.year, 2026); expect(endOfWeek.month, 1); expect(endOfWeek.day, 3); // Saturday in next month @@ -290,7 +290,7 @@ void main() { // Tuesday, December 30, 2025 final tuesday = DateTime(2025, 12, 30); final endOfWeek = getEndOfWeek(tuesday); - + expect(endOfWeek.year, 2026); expect(endOfWeek.month, 1); expect(endOfWeek.day, 3); // Saturday in next year @@ -305,7 +305,7 @@ void main() { // Wednesday, December 17, 2025 final wednesday = DateTime(2025, 12, 17); final weekStr = getWeekStr(wednesday); - + // Basic validation - just ensure it returns a valid string expect(weekStr, isNotNull); expect(weekStr, isNotEmpty); @@ -315,7 +315,7 @@ void main() { // Thursday, January 2, 2025 (week spans 2024-2025) final thursday = DateTime(2025, 1, 2); final weekStr = getWeekStr(thursday); - + expect(weekStr, isNotNull); expect(weekStr, isNotEmpty); }); @@ -324,7 +324,7 @@ void main() { // Tuesday, December 30, 2025 (week spans 2025-2026) final tuesday = DateTime(2025, 12, 30); final weekStr = getWeekStr(tuesday); - + expect(weekStr, isNotNull); expect(weekStr, isNotEmpty); }); @@ -335,12 +335,12 @@ void main() { final monday = DateTime(2025, 12, 15); final wednesday = DateTime(2025, 12, 17); final saturday = DateTime(2025, 12, 20); - + final sundayStr = getWeekStr(sunday); final mondayStr = getWeekStr(monday); final wednesdayStr = getWeekStr(wednesday); final saturdayStr = getWeekStr(saturday); - + expect(sundayStr, equals(mondayStr)); expect(sundayStr, equals(wednesdayStr)); expect(sundayStr, equals(saturdayStr)); @@ -352,23 +352,24 @@ void main() { final testDate = DateTime(2025, 12, 17); final startOfWeek = getStartOfWeek(testDate); final endOfWeek = getEndOfWeek(testDate); - + // Calculate days difference (ignoring hours/minutes) - final startDay = DateTime(startOfWeek.year, startOfWeek.month, startOfWeek.day); + final startDay = + DateTime(startOfWeek.year, startOfWeek.month, startOfWeek.day); final endDay = DateTime(endOfWeek.year, endOfWeek.month, endOfWeek.day); final daysDifference = endDay.difference(startDay).inDays; - + expect(daysDifference, 6); }); test('start of week should always be Sunday', () { // Test various dates throughout the year final testDates = [ - DateTime(2025, 1, 15), // Wednesday - DateTime(2025, 3, 7), // Friday - DateTime(2025, 6, 20), // Friday - DateTime(2025, 9, 14), // Sunday - DateTime(2025, 12, 1), // Monday + DateTime(2025, 1, 15), // Wednesday + DateTime(2025, 3, 7), // Friday + DateTime(2025, 6, 20), // Friday + DateTime(2025, 9, 14), // Sunday + DateTime(2025, 12, 1), // Monday ]; for (var date in testDates) { @@ -381,11 +382,11 @@ void main() { test('end of week should always be Saturday', () { // Test various dates throughout the year final testDates = [ - DateTime(2025, 1, 15), // Wednesday - DateTime(2025, 3, 7), // Friday - DateTime(2025, 6, 20), // Friday - DateTime(2025, 9, 14), // Sunday - DateTime(2025, 12, 1), // Monday + DateTime(2025, 1, 15), // Wednesday + DateTime(2025, 3, 7), // Friday + DateTime(2025, 6, 20), // Friday + DateTime(2025, 9, 14), // Sunday + DateTime(2025, 12, 1), // Monday ]; for (var date in testDates) { @@ -398,7 +399,7 @@ void main() { test('end of week should always be at 23:59', () { final testDate = DateTime(2025, 6, 15, 10, 30); // With specific time final endOfWeek = getEndOfWeek(testDate); - + expect(endOfWeek.hour, 23); expect(endOfWeek.minute, 59); }); diff --git a/test/future_records_integration_test.dart b/test/future_records_integration_test.dart index 5f7a88d6..edbbef07 100644 --- a/test/future_records_integration_test.dart +++ b/test/future_records_integration_test.dart @@ -19,8 +19,11 @@ void main() { final recurrentRecordService = RecurrentRecordService(); - test('RecordsPerDay should include future records in balance calculation when enabled', () { - final category = Category("Test Category", categoryType: CategoryType.expense); + test( + 'RecordsPerDay should include future records in balance calculation when enabled', + () { + final category = + Category("Test Category", categoryType: CategoryType.expense); final today = DateTime.now(); final dateKey = DateTime(today.year, today.month, today.day); @@ -42,7 +45,8 @@ void main() { isFutureRecord: true, ); - final recordsPerDay = RecordsPerDay(dateKey, records: [pastRecord, futureRecord]); + final recordsPerDay = + RecordsPerDay(dateKey, records: [pastRecord, futureRecord]); // When future records setting is enabled, they should be included in calculations expect(recordsPerDay.expenses, -300.0); // -100 + -200 @@ -50,8 +54,11 @@ void main() { expect(recordsPerDay.balance, -300.0); }); - test('RecordsPerDay should include future income records in calculation when enabled', () { - final incomeCategory = Category("Salary", categoryType: CategoryType.income); + test( + 'RecordsPerDay should include future income records in calculation when enabled', + () { + final incomeCategory = + Category("Salary", categoryType: CategoryType.income); final today = DateTime.now(); final dateKey = DateTime(today.year, today.month, today.day); @@ -71,7 +78,8 @@ void main() { isFutureRecord: true, ); - final recordsPerDay = RecordsPerDay(dateKey, records: [pastIncome, futureIncome]); + final recordsPerDay = + RecordsPerDay(dateKey, records: [pastIncome, futureIncome]); // Should include both past and future income when future records are enabled expect(recordsPerDay.income, 1500.0); // 500 + 1000 @@ -79,8 +87,11 @@ void main() { expect(recordsPerDay.balance, 1500.0); }); - test('RecordsPerDay with only future records should include them in balance', () { - final category = Category("Future Category", categoryType: CategoryType.expense); + test( + 'RecordsPerDay with only future records should include them in balance', + () { + final category = + Category("Future Category", categoryType: CategoryType.expense); final today = DateTime.now(); final dateKey = DateTime(today.year, today.month, today.day); @@ -101,7 +112,8 @@ void main() { isFutureRecord: true, ); - final recordsPerDay = RecordsPerDay(dateKey, records: [futureRecord1, futureRecord2]); + final recordsPerDay = + RecordsPerDay(dateKey, records: [futureRecord1, futureRecord2]); // Future records are included when the setting is enabled expect(recordsPerDay.expenses, -300.0); @@ -136,8 +148,10 @@ void main() { expect(futureRecord.isFutureRecord, false); }); - test('Monthly recurrent pattern generates correct future records count', () { - final category = Category("Monthly Bill", categoryType: CategoryType.expense); + test('Monthly recurrent pattern generates correct future records count', + () { + final category = + Category("Monthly Bill", categoryType: CategoryType.expense); final startDate = DateTime(2024, 1, 1).toUtc(); final endOfYear = DateTime(2024, 12, 31, 23, 59).toUtc(); @@ -149,7 +163,8 @@ void main() { RecurrentPeriod.EveryMonth, ); - final records = recurrentRecordService.generateRecurrentRecordsFromDateTime( + final records = + recurrentRecordService.generateRecurrentRecordsFromDateTime( pattern, endOfYear, ); @@ -165,7 +180,8 @@ void main() { }); test('Weekly pattern with future view date generates correct records', () { - final category = Category("Weekly Task", categoryType: CategoryType.expense); + final category = + Category("Weekly Task", categoryType: CategoryType.expense); final startDate = DateTime(2024, 1, 1).toUtc(); // Monday final endDate = DateTime(2024, 1, 29).toUtc(); // 4 weeks later @@ -177,7 +193,8 @@ void main() { RecurrentPeriod.EveryWeek, ); - final records = recurrentRecordService.generateRecurrentRecordsFromDateTime( + final records = + recurrentRecordService.generateRecurrentRecordsFromDateTime( pattern, endDate, ); @@ -203,14 +220,23 @@ void main() { expect(futureRecord.tags, tags); }); - test('Mixed past and future records maintain their flags independently', () { + test('Mixed past and future records maintain their flags independently', + () { final category = Category("Mixed Category"); final records = [ - Record(100.0, "Past 1", category, DateTime.now().subtract(Duration(days: 2)).toUtc(), isFutureRecord: false), - Record(200.0, "Past 2", category, DateTime.now().subtract(Duration(days: 1)).toUtc(), isFutureRecord: false), - Record(300.0, "Future 1", category, DateTime.now().add(Duration(days: 1)).toUtc(), isFutureRecord: true), - Record(400.0, "Future 2", category, DateTime.now().add(Duration(days: 2)).toUtc(), isFutureRecord: true), + Record(100.0, "Past 1", category, + DateTime.now().subtract(Duration(days: 2)).toUtc(), + isFutureRecord: false), + Record(200.0, "Past 2", category, + DateTime.now().subtract(Duration(days: 1)).toUtc(), + isFutureRecord: false), + Record(300.0, "Future 1", category, + DateTime.now().add(Duration(days: 1)).toUtc(), + isFutureRecord: true), + Record(400.0, "Future 2", category, + DateTime.now().add(Duration(days: 2)).toUtc(), + isFutureRecord: true), ]; final pastRecords = records.where((r) => !r.isFutureRecord).toList(); @@ -223,4 +249,3 @@ void main() { }); }); } - diff --git a/test/future_recurrent_records_test.dart b/test/future_recurrent_records_test.dart index 32c5138d..d2fe4c93 100644 --- a/test/future_recurrent_records_test.dart +++ b/test/future_recurrent_records_test.dart @@ -24,8 +24,8 @@ void main() { // View end date is in the future (end of month) final viewEndDate = DateTime.utc(2024, 1, 31, 23, 59); - final recordPattern = RecurrentRecordPattern( - 100.0, "Daily Pattern", category1, patternStartDate, RecurrentPeriod.EveryDay); + final recordPattern = RecurrentRecordPattern(100.0, "Daily Pattern", + category1, patternStartDate, RecurrentPeriod.EveryDay); final records = recurrentRecordService .generateRecurrentRecordsFromDateTime(recordPattern, viewEndDate); @@ -42,8 +42,8 @@ void main() { // View end date is 10 days from now final viewEndDate = today.add(Duration(days: 10)); - final recordPattern = RecurrentRecordPattern( - 50.0, "Daily Future Pattern", category1, patternStartDate, RecurrentPeriod.EveryDay); + final recordPattern = RecurrentRecordPattern(50.0, "Daily Future Pattern", + category1, patternStartDate, RecurrentPeriod.EveryDay); final records = recurrentRecordService .generateRecurrentRecordsFromDateTime(recordPattern, viewEndDate); @@ -54,20 +54,21 @@ void main() { // All records should be generated (this is just generation, marking happens in service) final lastRecord = records.last; expect( - lastRecord.utcDateTime.isBefore(viewEndDate) || - lastRecord.utcDateTime.isAtSameMomentAs(viewEndDate), - true - ); + lastRecord.utcDateTime.isBefore(viewEndDate) || + lastRecord.utcDateTime.isAtSameMomentAs(viewEndDate), + true); }); - test('should split records into past and future correctly in boundary cases', () { + test( + 'should split records into past and future correctly in boundary cases', + () { // Test edge case where today's date is exactly on a recurrent record final today = DateTime.now().toUtc(); final startOfToday = DateTime(today.year, today.month, today.day).toUtc(); // Pattern starts today - final recordPattern = RecurrentRecordPattern( - 75.0, "Boundary Test", category1, startOfToday, RecurrentPeriod.EveryDay); + final recordPattern = RecurrentRecordPattern(75.0, "Boundary Test", + category1, startOfToday, RecurrentPeriod.EveryDay); // View end date is 5 days from today final viewEndDate = startOfToday.add(Duration(days: 5)); @@ -83,8 +84,8 @@ void main() { final patternStartDate = DateTime(2024, 1, 15).toUtc(); final viewEndDate = DateTime(2024, 6, 30).toUtc(); - final recordPattern = RecurrentRecordPattern( - 200.0, "Monthly Pattern", category1, patternStartDate, RecurrentPeriod.EveryMonth); + final recordPattern = RecurrentRecordPattern(200.0, "Monthly Pattern", + category1, patternStartDate, RecurrentPeriod.EveryMonth); final records = recurrentRecordService .generateRecurrentRecordsFromDateTime(recordPattern, viewEndDate); @@ -105,8 +106,8 @@ void main() { final patternStartDate = DateTime(2024, 1, 1).toUtc(); final viewEndDate = DateTime(2024, 2, 15).toUtc(); - final recordPattern = RecurrentRecordPattern( - 30.0, "Weekly Pattern", category1, patternStartDate, RecurrentPeriod.EveryWeek); + final recordPattern = RecurrentRecordPattern(30.0, "Weekly Pattern", + category1, patternStartDate, RecurrentPeriod.EveryWeek); final records = recurrentRecordService .generateRecurrentRecordsFromDateTime(recordPattern, viewEndDate); @@ -122,7 +123,9 @@ void main() { } }); - test('future records should have isFutureRecord flag set to false by default', () { + test( + 'future records should have isFutureRecord flag set to false by default', + () { // This tests the Record model default behavior final category = Category("Test Category"); final record = Record( @@ -149,12 +152,13 @@ void main() { expect(record.isFutureRecord, true); }); - test('should not generate records when viewEndDate is before pattern start', () { + test('should not generate records when viewEndDate is before pattern start', + () { final patternStartDate = DateTime(2024, 6, 1).toUtc(); final viewEndDate = DateTime(2024, 5, 31).toUtc(); - final recordPattern = RecurrentRecordPattern( - 100.0, "Future Pattern", category1, patternStartDate, RecurrentPeriod.EveryDay); + final recordPattern = RecurrentRecordPattern(100.0, "Future Pattern", + category1, patternStartDate, RecurrentPeriod.EveryDay); final records = recurrentRecordService .generateRecurrentRecordsFromDateTime(recordPattern, viewEndDate); @@ -167,7 +171,11 @@ void main() { final viewEndDate = DateTime(2024, 1, 31).toUtc(); final recordPattern = RecurrentRecordPattern( - 150.0, "Year Boundary Pattern", category1, patternStartDate, RecurrentPeriod.EveryWeek); + 150.0, + "Year Boundary Pattern", + category1, + patternStartDate, + RecurrentPeriod.EveryWeek); final records = recurrentRecordService .generateRecurrentRecordsFromDateTime(recordPattern, viewEndDate); @@ -275,4 +283,3 @@ void main() { }); }); } - diff --git a/test/helpers/test_database.dart b/test/helpers/test_database.dart index d5174db0..86e0b890 100644 --- a/test/helpers/test_database.dart +++ b/test/helpers/test_database.dart @@ -13,7 +13,7 @@ class TestDatabaseHelper { var factoryWithLogs = SqfliteDatabaseFactoryLogger(databaseFactory, options: SqfliteLoggerOptions(type: SqfliteDatabaseFactoryLoggerType.all)); - + final db = await factoryWithLogs.openDatabase( inMemoryDatabasePath, // Each call creates a new isolated in-memory database options: OpenDatabaseOptions( @@ -22,10 +22,10 @@ class TestDatabaseHelper { onUpgrade: SqliteMigrationService.onUpgrade, onDowngrade: SqliteMigrationService.onUpgrade), ); - + // Set the database for the singleton instance to use SqliteDatabase.setDatabaseForTesting(db); - + return db; } } diff --git a/test/recurrent_pattern_tags_integration_test.dart b/test/recurrent_pattern_tags_integration_test.dart index f40af899..e3e41dc5 100644 --- a/test/recurrent_pattern_tags_integration_test.dart +++ b/test/recurrent_pattern_tags_integration_test.dart @@ -73,19 +73,12 @@ void main() { DateTime.utc(2023, 1, 1), DateTime.now().toUtc()); expect( - taggedRecords - .any((element) => element['key'] == 'streaming'), - true, + taggedRecords.any((element) => element['key'] == 'streaming'), true, reason: 'Should be able to find records by streaming tag'); - expect( - taggedRecords - .any((element) => element['key'] == 'entertainment'), + expect(taggedRecords.any((element) => element['key'] == 'entertainment'), true, reason: 'Should be able to find records by entertainment tag'); - expect( - taggedRecords - .any((element) => element['key'] == 'monthly'), - true, + expect(taggedRecords.any((element) => element['key'] == 'monthly'), true, reason: 'Should be able to find records by monthly tag'); }); diff --git a/test/show_future_records_preference_test.dart b/test/show_future_records_preference_test.dart index 19e96f4c..a099e21b 100644 --- a/test/show_future_records_preference_test.dart +++ b/test/show_future_records_preference_test.dart @@ -12,8 +12,8 @@ void main() { }); test('Default value for showFutureRecords should be true', () { - final defaultValue = PreferencesDefaultValues.defaultValues[ - PreferencesKeys.showFutureRecords]; + final defaultValue = PreferencesDefaultValues + .defaultValues[PreferencesKeys.showFutureRecords]; expect(defaultValue, true); }); @@ -69,4 +69,3 @@ void main() { }); }); } - diff --git a/test/tag_management_test.dart b/test/tag_management_test.dart index dbc09b5f..cb8faa9d 100644 --- a/test/tag_management_test.dart +++ b/test/tag_management_test.dart @@ -8,7 +8,6 @@ import 'package:timezone/data/latest_all.dart' as tz; import 'helpers/test_database.dart'; void main() { - // Setup sqflite_common_ffi for flutter test setUpAll(() { TestWidgetsFlutterBinding.ensureInitialized(); diff --git a/test/test_database.dart b/test/test_database.dart index a52d3922..6aa21726 100644 --- a/test/test_database.dart +++ b/test/test_database.dart @@ -729,8 +729,8 @@ Future main() async { for (var record in allRecords) { expect(record?.tags, isNotEmpty, reason: 'Record should have tags from pattern'); - expect(record?.tags, - containsAll(['subscription', 'recurring', 'digital']), + expect( + record?.tags, containsAll(['subscription', 'recurring', 'digital']), reason: 'Record should contain all tags from the recurrent pattern'); }