diff --git a/.fvmrc b/.fvmrc new file mode 100644 index 0000000..0fdcb48 --- /dev/null +++ b/.fvmrc @@ -0,0 +1,3 @@ +{ + "flutter": "3.27.1" +} \ No newline at end of file diff --git a/.github/workflows/build_web.yml b/.github/workflows/build_web.yml new file mode 100644 index 0000000..fba3084 --- /dev/null +++ b/.github/workflows/build_web.yml @@ -0,0 +1,56 @@ +name: Build & Deploy Web + +on: + workflow_dispatch: + push: + branches: + - main + +# Required for GitHub Pages deployment +permissions: + contents: read + pages: write + id-token: write + +# Only allow one concurrent deployment +concurrency: + group: pages + cancel-in-progress: true + +jobs: + build: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - name: Setup Flutter + uses: subosito/flutter-action@v2 + with: + flutter-version: '3.27.1' + + - name: Install dependencies + run: flutter pub get + + - name: Add web platform support + run: flutter create --platforms=web . + + - name: Build web + run: | + flutter build web --release \ + --base-href "/${{ github.event.repository.name }}/" + + - name: Upload Pages artifact + uses: actions/upload-pages-artifact@v3 + with: + path: build/web + + deploy: + needs: build + runs-on: ubuntu-latest + environment: + name: github-pages + url: ${{ steps.deployment.outputs.page_url }} + steps: + - name: Deploy to GitHub Pages + id: deployment + uses: actions/deploy-pages@v4 diff --git a/.gitignore b/.gitignore index e2a8cf1..50110c9 100644 --- a/.gitignore +++ b/.gitignore @@ -138,4 +138,7 @@ app.*.symbols !/dev/ci/**/Gemfile.lock !.vscode/settings.json *.noindex** -Logs/ \ No newline at end of file +Logs/ + +# FVM Version Cache +.fvm/ \ No newline at end of file diff --git a/.metadata b/.metadata new file mode 100644 index 0000000..9a613f0 --- /dev/null +++ b/.metadata @@ -0,0 +1,30 @@ +# This file tracks properties of this Flutter project. +# Used by Flutter tool to assess capabilities and perform upgrades etc. +# +# This file should be version controlled and should not be manually edited. + +version: + revision: "17025dd88227cd9532c33fa78f5250d548d87e9a" + channel: "stable" + +project_type: app + +# Tracks metadata for the flutter migrate command +migration: + platforms: + - platform: root + create_revision: 17025dd88227cd9532c33fa78f5250d548d87e9a + base_revision: 17025dd88227cd9532c33fa78f5250d548d87e9a + - platform: web + create_revision: 17025dd88227cd9532c33fa78f5250d548d87e9a + base_revision: 17025dd88227cd9532c33fa78f5250d548d87e9a + + # User provided section + + # List of Local paths (relative to this file) that should be + # ignored by the migrate tool. + # + # Files that are not part of the templates will be ignored by default. + unmanaged_files: + - 'lib/main.dart' + - 'ios/Runner.xcodeproj/project.pbxproj' diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..b01430d --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,3 @@ +{ + "dart.flutterSdkPath": ".fvm/versions/3.27.1" +} \ No newline at end of file diff --git a/README.md b/README.md index 6d7eae8..a56b75a 100644 --- a/README.md +++ b/README.md @@ -43,6 +43,7 @@ The app is compatible with both Android and iOS. | - | - | | **Build APK** | ![CI](https://github.com/andreped/IronFlow/workflows/Build%20APK/badge.svg) | | **Build IPA** | ![CI](https://github.com/andreped/IronFlow/workflows/Build%20IPA/badge.svg) | +| **Build Web** | ![CI](https://github.com/andreped/IronFlow/workflows/Build%20%26%20Deploy%20Web/badge.svg) | | **Create Release** | ![CI](https://github.com/andreped/IronFlow/workflows/Create%20Release/badge.svg) | | **Integration tests** | ![CI](https://github.com/andreped/IronFlow/workflows/Integration%20Tests/badge.svg) | | **Linting** | ![CI](https://github.com/andreped/IronFlow/workflows/Linting/badge.svg) | @@ -57,6 +58,53 @@ To run integration tests, run the command: maestro test .maestro/integration_tests.yml ``` +## [Development](https://github.com/andreped/IronFlow#Development) + +This project uses [fvm](https://fvm.app) to pin the Flutter version. Install it first: + +```bash +brew tap leoafarias/fvm +brew install fvm +``` + +Then install the pinned Flutter version and fetch dependencies: + +```bash +fvm install +fvm flutter pub get +``` + +### Run + +```bash +# Mobile (Android/iOS emulator or device) +fvm flutter run + +# Web (opens Chrome) +fvm flutter run -d chrome +``` + +### Build + +```bash +# Android +fvm flutter build apk --release + +# Web +fvm flutter build web --release +``` + +The web build output is in `build/web/`. Serve it locally with: + +```bash +npx serve build/web +``` + +### Deploy web to GitHub Pages + +Push to `main` or trigger the **Build & Deploy Web** workflow manually from the Actions tab. +The app will be live at `https://.github.io/IronFlow/`. + ## [Getting Started](https://github.com/andreped/IronFlow#Getting-Started) A cross-platform mobile app was developed to test the produced solutions. Installers for both diff --git a/lib/core/database.dart b/lib/core/database.dart index cd473ec..fbf7c78 100644 --- a/lib/core/database.dart +++ b/lib/core/database.dart @@ -1,5 +1,6 @@ import 'package:file_picker/file_picker.dart'; import 'dart:io'; +import 'package:flutter/foundation.dart' show kIsWeb; import 'package:sqflite/sqflite.dart'; import 'package:path/path.dart'; import '../common/constants.dart'; @@ -572,6 +573,11 @@ class DatabaseHelper { } Future backupDatabase(BuildContext context) async { + if (kIsWeb) { + await _showDialog( + context, 'Not Supported', 'Database backup is not available on web.'); + return; + } try { // set backupPath for each OS, android and iOS String backupPath; @@ -597,6 +603,11 @@ class DatabaseHelper { } Future restoreDatabase(BuildContext context) async { + if (kIsWeb) { + await _showDialog(context, 'Not Supported', + 'Database restore is not available on web.'); + return; + } try { String filePath; if (Platform.isIOS) { diff --git a/lib/core/database_init.dart b/lib/core/database_init.dart new file mode 100644 index 0000000..c7ad1ee --- /dev/null +++ b/lib/core/database_init.dart @@ -0,0 +1,2 @@ +export 'database_init_stub.dart' + if (dart.library.html) 'database_init_web.dart'; diff --git a/lib/core/database_init_stub.dart b/lib/core/database_init_stub.dart new file mode 100644 index 0000000..49351af --- /dev/null +++ b/lib/core/database_init_stub.dart @@ -0,0 +1,3 @@ +Future initializeDatabaseFactory() async { + // No-op on native platforms — sqflite uses its built-in factory. +} diff --git a/lib/core/database_init_web.dart b/lib/core/database_init_web.dart new file mode 100644 index 0000000..9e9dff3 --- /dev/null +++ b/lib/core/database_init_web.dart @@ -0,0 +1,6 @@ +import 'package:sqflite/sqflite.dart'; +import 'package:sqflite_common_ffi_web/sqflite_ffi_web.dart'; + +Future initializeDatabaseFactory() async { + databaseFactory = databaseFactoryFfiWeb; +} diff --git a/lib/main.dart b/lib/main.dart index 2fb6bda..73ec65b 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -3,8 +3,11 @@ import 'package:shared_preferences/shared_preferences.dart'; import 'components/scroll/custom_scroll_behavior.dart'; import 'tabs/home_tab.dart'; import 'core/theme/app_themes.dart'; +import 'core/database_init.dart'; -void main() { +void main() async { + WidgetsFlutterBinding.ensureInitialized(); + await initializeDatabaseFactory(); runApp(const MyApp()); } diff --git a/pubspec.yaml b/pubspec.yaml index c27d45a..9f2599b 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -19,6 +19,7 @@ dependencies: # storage sqflite: ^2.0.0+4 + sqflite_common_ffi_web: ^0.4.3 path: ^1.8.0 path_provider: ^2.1.5 file_picker: ^8.1.7 diff --git a/test/widget_test.dart b/test/widget_test.dart new file mode 100644 index 0000000..c349e25 --- /dev/null +++ b/test/widget_test.dart @@ -0,0 +1,30 @@ +// This is a basic Flutter widget test. +// +// To perform an interaction with a widget in your test, use the WidgetTester +// utility in the flutter_test package. For example, you can send tap and scroll +// gestures. You can also use WidgetTester to find child widgets in the widget +// tree, read text, and verify that the values of widget properties are correct. + +import 'package:flutter/material.dart'; +import 'package:flutter_test/flutter_test.dart'; + +import 'package:ironflow/main.dart'; + +void main() { + testWidgets('Counter increments smoke test', (WidgetTester tester) async { + // Build our app and trigger a frame. + await tester.pumpWidget(const MyApp()); + + // Verify that our counter starts at 0. + expect(find.text('0'), findsOneWidget); + expect(find.text('1'), findsNothing); + + // Tap the '+' icon and trigger a frame. + await tester.tap(find.byIcon(Icons.add)); + await tester.pump(); + + // Verify that our counter has incremented. + expect(find.text('0'), findsNothing); + expect(find.text('1'), findsOneWidget); + }); +} diff --git a/web/_headers b/web/_headers new file mode 100644 index 0000000..f8a3474 --- /dev/null +++ b/web/_headers @@ -0,0 +1,2 @@ +/* + X-Frame-Options: ALLOWALL diff --git a/web/favicon.png b/web/favicon.png new file mode 100644 index 0000000..8aaa46a Binary files /dev/null and b/web/favicon.png differ diff --git a/web/icons/Icon-192.png b/web/icons/Icon-192.png new file mode 100644 index 0000000..b749bfe Binary files /dev/null and b/web/icons/Icon-192.png differ diff --git a/web/icons/Icon-512.png b/web/icons/Icon-512.png new file mode 100644 index 0000000..88cfd48 Binary files /dev/null and b/web/icons/Icon-512.png differ diff --git a/web/icons/Icon-maskable-192.png b/web/icons/Icon-maskable-192.png new file mode 100644 index 0000000..eb9b4d7 Binary files /dev/null and b/web/icons/Icon-maskable-192.png differ diff --git a/web/icons/Icon-maskable-512.png b/web/icons/Icon-maskable-512.png new file mode 100644 index 0000000..d69c566 Binary files /dev/null and b/web/icons/Icon-maskable-512.png differ diff --git a/web/index.html b/web/index.html new file mode 100644 index 0000000..7fe9b95 --- /dev/null +++ b/web/index.html @@ -0,0 +1,38 @@ + + + + + + + + + + + + + + + + + + + + ironflow + + + + + + diff --git a/web/manifest.json b/web/manifest.json new file mode 100644 index 0000000..98db90a --- /dev/null +++ b/web/manifest.json @@ -0,0 +1,35 @@ +{ + "name": "ironflow", + "short_name": "ironflow", + "start_url": ".", + "display": "standalone", + "background_color": "#0175C2", + "theme_color": "#0175C2", + "description": "A new Flutter project.", + "orientation": "portrait-primary", + "prefer_related_applications": false, + "icons": [ + { + "src": "icons/Icon-192.png", + "sizes": "192x192", + "type": "image/png" + }, + { + "src": "icons/Icon-512.png", + "sizes": "512x512", + "type": "image/png" + }, + { + "src": "icons/Icon-maskable-192.png", + "sizes": "192x192", + "type": "image/png", + "purpose": "maskable" + }, + { + "src": "icons/Icon-maskable-512.png", + "sizes": "512x512", + "type": "image/png", + "purpose": "maskable" + } + ] +}