creek
The AI Image Editor of 2030git clone https://github.com/maydayv7/creek
commit 49f6880575e305ba8d2783079825ab34790fb5d2 parent 7714f87e7dcbc15cdf26860c0e4d51ef3bddaa2d Author: Nilotpal Gupta <nilotpalgupta0701@gmail.com> Date: Mon, 24 Nov 2025 18:15:41 +0530 barebone of everything related to ui boards pins downloading
Diffstat:
| M | android/app/build.gradle.kts | | | 8 | ++++---- |
| M | android/app/src/main/AndroidManifest.xml | | | 15 | ++++++++++++++- |
| M | android/build.gradle.kts | | | 12 | ++++++++++++ |
| A | lib/data/database.dart | | | 49 | +++++++++++++++++++++++++++++++++++++++++++++++++ |
| A | lib/data/models/board_image_board.dart | | | 9 | +++++++++ |
| A | lib/data/models/board_model.dart | | | 12 | ++++++++++++ |
| A | lib/data/models/image_model.dart | | | 12 | ++++++++++++ |
| A | lib/data/repos/board_image_repo.dart | | | 26 | ++++++++++++++++++++++++++ |
| A | lib/data/repos/board_repo.dart | | | 18 | ++++++++++++++++++ |
| A | lib/data/repos/image_repo.dart | | | 19 | +++++++++++++++++++ |
| M | lib/main.dart | | | 150 | ++++++++++++++++++++++++++++--------------------------------------------------- |
| A | lib/services/download_service.dart | | | 92 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
| A | lib/services/image_service.dart | | | 29 | +++++++++++++++++++++++++++++ |
| A | lib/ui/pages/board_detail_page.dart | | | 89 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
| A | lib/ui/pages/board_page.dart | | | 191 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
| A | lib/ui/pages/home_page.dart | | | 160 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
| A | lib/ui/pages/image_picker_page.dart | | | 34 | ++++++++++++++++++++++++++++++++++ |
| A | lib/ui/pages/share_handler_page.dart | | | 167 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
| A | lib/ui/widgets/save_to_board_sheet.dart | | | 73 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
| M | linux/flutter/generated_plugin_registrant.cc | | | 4 | ++++ |
| M | linux/flutter/generated_plugins.cmake | | | 1 | + |
| M | macos/Flutter/GeneratedPluginRegistrant.swift | | | 6 | ++++++ |
| M | pubspec.lock | | | 337 | ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++- |
| M | pubspec.yaml | | | 8 | ++++++++ |
| M | test/widget_test.dart | | | 5 | ++--- |
| M | windows/flutter/generated_plugin_registrant.cc | | | 3 | +++ |
| M | windows/flutter/generated_plugins.cmake | | | 1 | + |
27 files changed, 1422 insertions(+), 108 deletions(-)
diff --git a/android/app/build.gradle.kts b/android/app/build.gradle.kts @@ -8,15 +8,15 @@ plugins { android { namespace = "com.example.adobe" compileSdk = flutter.compileSdkVersion - ndkVersion = flutter.ndkVersion + ndkVersion = "27.0.12077973" compileOptions { - sourceCompatibility = JavaVersion.VERSION_11 - targetCompatibility = JavaVersion.VERSION_11 + sourceCompatibility = JavaVersion.VERSION_17 + targetCompatibility = JavaVersion.VERSION_17 } kotlinOptions { - jvmTarget = JavaVersion.VERSION_11.toString() + jvmTarget = JavaVersion.VERSION_17.toString() } defaultConfig { diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml @@ -1,4 +1,5 @@ <manifest xmlns:android="http://schemas.android.com/apk/res/android"> + <uses-permission android:name="android.permission.INTERNET"/> <application android:label="adobe" android:name="${applicationName}" @@ -6,7 +7,7 @@ <activity android:name=".MainActivity" android:exported="true" - android:launchMode="singleTop" + android:launchMode="singleTask" android:taskAffinity="" android:theme="@style/LaunchTheme" android:configChanges="orientation|keyboardHidden|keyboard|screenSize|smallestScreenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode" @@ -24,6 +25,18 @@ <action android:name="android.intent.action.MAIN"/> <category android:name="android.intent.category.LAUNCHER"/> </intent-filter> + + <intent-filter> + <action android:name="android.intent.action.SEND" /> + <category android:name="android.intent.category.DEFAULT" /> + <data android:mimeType="text/*"/> + </intent-filter> + + <intent-filter> + <action android:name="android.intent.action.SEND" /> + <category android:name="android.intent.category.DEFAULT" /> + <data android:mimeType="image/*"/> + </intent-filter> </activity> <!-- Don't delete the meta-data below. This is used by the Flutter tool to generate GeneratedPluginRegistrant.java --> diff --git a/android/build.gradle.kts b/android/build.gradle.kts @@ -19,3 +19,14 @@ subprojects { tasks.register<Delete>("clean") { delete(rootProject.layout.buildDirectory) } + +subprojects { + pluginManager.withPlugin("com.android.library") { + val android = extensions.findByName("android") + if (android != null) { + val compileOptions = android.javaClass.getMethod("getCompileOptions").invoke(android) + compileOptions.javaClass.getMethod("setSourceCompatibility", JavaVersion::class.java).invoke(compileOptions, JavaVersion.VERSION_17) + compileOptions.javaClass.getMethod("setTargetCompatibility", JavaVersion::class.java).invoke(compileOptions, JavaVersion.VERSION_17) + } + } +} +\ No newline at end of file diff --git a/lib/data/database.dart b/lib/data/database.dart @@ -0,0 +1,49 @@ +// lib/data/database.dart + +import 'package:sqflite/sqflite.dart'; +import 'package:path/path.dart'; + +class AppDatabase { + static Database? _db; + + static Future<Database> get db async { + if (_db != null) return _db!; + _db = await _init(); + return _db!; + } + + static Future<Database> _init() async { + final path = join(await getDatabasesPath(), 'pinterest.db'); + + return await openDatabase( + path, + version: 1, + onCreate: (db, version) async { + await db.execute(''' + CREATE TABLE images ( + id TEXT PRIMARY KEY, + filePath TEXT, + createdAt TEXT + ) + '''); + + await db.execute(''' + CREATE TABLE boards ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + name TEXT, + createdAt TEXT + ) + '''); + + await db.execute(''' + CREATE TABLE board_images ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + board_id INTEGER, + image_id TEXT, + createdAt TEXT + ) + '''); + }, + ); + } +} diff --git a/lib/data/models/board_image_board.dart b/lib/data/models/board_image_board.dart @@ -0,0 +1,9 @@ +// lib/data/models/board_image_board.dart + +class BoardImage { + final int id; + final int boardId; + final String imageId; + + BoardImage({required this.id, required this.boardId, required this.imageId}); +} diff --git a/lib/data/models/board_model.dart b/lib/data/models/board_model.dart @@ -0,0 +1,12 @@ +// lib/data/models/board_model.dart + +class Board { + final int id; + final String name; + + Board({required this.id, required this.name}); + + factory Board.fromMap(Map<String, dynamic> m) { + return Board(id: m['id'], name: m['name']); + } +} diff --git a/lib/data/models/image_model.dart b/lib/data/models/image_model.dart @@ -0,0 +1,12 @@ +// lib/data/models/image_model.dart + +class ImageModel { + final String id; + final String filePath; + + ImageModel({required this.id, required this.filePath}); + + factory ImageModel.fromMap(Map<String, dynamic> m) { + return ImageModel(id: m['id'], filePath: m['filePath']); + } +} diff --git a/lib/data/repos/board_image_repo.dart b/lib/data/repos/board_image_repo.dart @@ -0,0 +1,26 @@ +// lib/data/repos/board_image_repo.dart + +import 'package:adobe/data/database.dart'; + +class BoardImageRepository { + Future<void> saveToBoard(int boardId, String imageId) async { + final db = await AppDatabase.db; + + await db.insert("board_images", { + "board_id": boardId, + "image_id": imageId, + "createdAt": DateTime.now().toString() + }); + } + + Future<List<Map<String, dynamic>>> getImagesOfBoard(int boardId) async { + final db = await AppDatabase.db; + + return await db.rawQuery(''' + SELECT images.* + FROM images + JOIN board_images ON images.id = board_images.image_id + WHERE board_images.board_id = ? + ''', [boardId]); + } +} diff --git a/lib/data/repos/board_repo.dart b/lib/data/repos/board_repo.dart @@ -0,0 +1,18 @@ +// lib/data/repos/board_repo.dart + +import 'package:adobe/data/database.dart'; + +class BoardRepository { + Future<int> createBoard(String name) async { + final db = await AppDatabase.db; + return await db.insert("boards", { + "name": name, + "createdAt": DateTime.now().toString() + }); + } + + Future<List<Map<String, dynamic>>> getBoards() async { + final db = await AppDatabase.db; + return await db.query("boards"); + } +} diff --git a/lib/data/repos/image_repo.dart b/lib/data/repos/image_repo.dart @@ -0,0 +1,19 @@ +// lib/data/repos/image_repo.dart + +import 'package:adobe/data/database.dart'; + +class ImageRepository { + Future<void> insertImage(String id, String filePath) async { + final db = await AppDatabase.db; + await db.insert("images", { + "id": id, + "filePath": filePath, + "createdAt": DateTime.now().toString() + }); + } + + Future<List<Map<String, dynamic>>> getAllImages() async { + final db = await AppDatabase.db; + return await db.query("images"); + } +} diff --git a/lib/main.dart b/lib/main.dart @@ -1,122 +1,75 @@ +// lib/main.dart + +import 'dart:async'; import 'package:flutter/material.dart'; +import 'package:receive_sharing_intent/receive_sharing_intent.dart'; +import 'package:adobe/ui/pages/home_page.dart'; +import 'package:adobe/ui/pages/share_handler_page.dart'; void main() { runApp(const MyApp()); } -class MyApp extends StatelessWidget { +class MyApp extends StatefulWidget { const MyApp({super.key}); - // This widget is the root of your application. @override - Widget build(BuildContext context) { - return MaterialApp( - title: 'Flutter Demo', - theme: ThemeData( - // This is the theme of your application. - // - // TRY THIS: Try running your application with "flutter run". You'll see - // the application has a purple toolbar. Then, without quitting the app, - // try changing the seedColor in the colorScheme below to Colors.green - // and then invoke "hot reload" (save your changes or press the "hot - // reload" button in a Flutter-supported IDE, or press "r" if you used - // the command line to start the app). - // - // Notice that the counter didn't reset back to zero; the application - // state is not lost during the reload. To reset the state, use hot - // restart instead. - // - // This works for code too, not just values: Most code changes can be - // tested with just a hot reload. - colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple), - ), - home: const MyHomePage(title: 'Flutter Demo Home Page'), - ); - } + State<MyApp> createState() => _MyAppState(); } -class MyHomePage extends StatefulWidget { - const MyHomePage({super.key, required this.title}); +class _MyAppState extends State<MyApp> { + late StreamSubscription _intentSub; + final _navigatorKey = GlobalKey<NavigatorState>(); - // This widget is the home page of your application. It is stateful, meaning - // that it has a State object (defined below) that contains fields that affect - // how it looks. + @override + void initState() { + super.initState(); - // This class is the configuration for the state. It holds the values (in this - // case the title) provided by the parent (in this case the App widget) and - // used by the build method of the State. Fields in a Widget subclass are - // always marked "final". + // 1. LISTEN: App is running in memory + _intentSub = ReceiveSharingIntent.instance.getMediaStream().listen((List<SharedMediaFile> value) { + if (value.isNotEmpty) { + // The URL or Text is inside the 'path' property + _navigateToSharePage(value.first.path); + } + }, onError: (err) { + debugPrint("Share Error: $err"); + }); - final String title; + // 2. LISTEN: App is closed and opened via Share + ReceiveSharingIntent.instance.getInitialMedia().then((List<SharedMediaFile> value) { + if (value.isNotEmpty) { + _navigateToSharePage(value.first.path); + + // Optional: Clear the intent so it doesn't re-trigger on reload + ReceiveSharingIntent.instance.reset(); + } + }); + } @override - State<MyHomePage> createState() => _MyHomePageState(); -} - -class _MyHomePageState extends State<MyHomePage> { - int _counter = 0; + void dispose() { + _intentSub.cancel(); + super.dispose(); + } - void _incrementCounter() { - setState(() { - // This call to setState tells the Flutter framework that something has - // changed in this State, which causes it to rerun the build method below - // so that the display can reflect the updated values. If we changed - // _counter without calling setState(), then the build method would not be - // called again, and so nothing would appear to happen. - _counter++; + void _navigateToSharePage(String sharedText) { + // Navigate to the Share Handler Page + // Use a microtask to ensure the navigator is ready + Future.delayed(Duration.zero, () { + _navigatorKey.currentState?.push( + MaterialPageRoute( + builder: (_) => ShareHandlerPage(sharedText: sharedText), + ), + ); }); } @override Widget build(BuildContext context) { - // This method is rerun every time setState is called, for instance as done - // by the _incrementCounter method above. - // - // The Flutter framework has been optimized to make rerunning build methods - // fast, so that you can just rebuild anything that needs updating rather - // than having to individually change instances of widgets. - return Scaffold( - appBar: AppBar( - // TRY THIS: Try changing the color here to a specific color (to - // Colors.amber, perhaps?) and trigger a hot reload to see the AppBar - // change color while the other colors stay the same. - backgroundColor: Theme.of(context).colorScheme.inversePrimary, - // Here we take the value from the MyHomePage object that was created by - // the App.build method, and use it to set our appbar title. - title: Text(widget.title), - ), - body: Center( - // Center is a layout widget. It takes a single child and positions it - // in the middle of the parent. - child: Column( - // Column is also a layout widget. It takes a list of children and - // arranges them vertically. By default, it sizes itself to fit its - // children horizontally, and tries to be as tall as its parent. - // - // Column has various properties to control how it sizes itself and - // how it positions its children. Here we use mainAxisAlignment to - // center the children vertically; the main axis here is the vertical - // axis because Columns are vertical (the cross axis would be - // horizontal). - // - // TRY THIS: Invoke "debug painting" (choose the "Toggle Debug Paint" - // action in the IDE, or press "p" in the console), to see the - // wireframe for each widget. - mainAxisAlignment: MainAxisAlignment.center, - children: <Widget>[ - const Text('You have pushed the button this many times:'), - Text( - '$_counter', - style: Theme.of(context).textTheme.headlineMedium, - ), - ], - ), - ), - floatingActionButton: FloatingActionButton( - onPressed: _incrementCounter, - tooltip: 'Increment', - child: const Icon(Icons.add), - ), // This trailing comma makes auto-formatting nicer for build methods. + return MaterialApp( + navigatorKey: _navigatorKey, + debugShowCheckedModeBanner: false, + home: const HomePage(), ); } -} +} +\ No newline at end of file diff --git a/lib/services/download_service.dart b/lib/services/download_service.dart @@ -0,0 +1,91 @@ +import 'dart:io'; +import 'package:flutter/material.dart'; +import 'package:http/http.dart' as http; +import 'package:path_provider/path_provider.dart'; +import 'package:uuid/uuid.dart'; +import 'package:adobe/data/repos/image_repo.dart'; +import 'package:html/parser.dart' as parser; // Import the parser + +class DownloadService { + final _repo = ImageRepository(); + final _uuid = const Uuid(); + + Future<String?> downloadAndSaveImage(String url) async { + debugPrint("📥 START PROCESS: $url"); + + try { + // 1. Initial Request + var response = await http.get( + Uri.parse(url), + headers: { + 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36', + }, + ); + + if (response.statusCode != 200) return null; + + // 2. Check: Is it an Image or a Website? + var contentType = response.headers['content-type']; + debugPrint("🔹 Type: $contentType"); + + // CASE A: It is a WEBSITE (like Wikipedia, Pinterest, etc.) + if (contentType != null && !contentType.startsWith('image/')) { + debugPrint("📄 It's a website. Looking for the real image inside..."); + + // Parse the HTML text + final document = parser.parse(response.body); + + // Try to find the "Open Graph" image (Standard used by Wiki, Pinterest, Insta) + String? realImageUrl; + + final metaTags = document.getElementsByTagName('meta'); + for (var meta in metaTags) { + if (meta.attributes['property'] == 'og:image') { + realImageUrl = meta.attributes['content']; + break; + } + } + + // If found, we must download THIS new URL + if (realImageUrl != null) { + debugPrint("🎯 Found Real Image: $realImageUrl"); + // Recursively call this function with the REAL link + return downloadAndSaveImage(realImageUrl); + } else { + debugPrint("❌ Could not find an image inside this page."); + return null; + } + } + + // CASE B: It is a REAL IMAGE (content-type is image/png, image/jpeg, etc.) + // Proceed to save it normally. + + final dir = await getApplicationDocumentsDirectory(); + final imagesDir = Directory('${dir.path}/images'); + if (!await imagesDir.exists()) await imagesDir.create(recursive: true); + + // Guess extension + String extension = 'png'; + if (contentType != null) { + if (contentType.contains('jpg')) extension = 'jpg'; + if (contentType.contains('gif')) extension = 'gif'; + if (contentType.contains('webp')) extension = 'webp'; + } + + final String imageId = _uuid.v4(); + final String filePath = '${imagesDir.path}/$imageId.$extension'; + + final file = File(filePath); + await file.writeAsBytes(response.bodyBytes); + + await _repo.insertImage(imageId, filePath); + debugPrint("✅ Saved Successfully!"); + + return imageId; + + } catch (e) { + debugPrint("❌ Error: $e"); + return null; + } + } +} +\ No newline at end of file diff --git a/lib/services/image_service.dart b/lib/services/image_service.dart @@ -0,0 +1,29 @@ +// lib/services/image_service.dart + +import 'dart:io'; +import 'package:path_provider/path_provider.dart'; +import 'package:uuid/uuid.dart'; +import '../data/repos/image_repo.dart'; + +class ImageService { + final _repo = ImageRepository(); + + Future<String> saveImage(File file) async { + final dir = await getApplicationDocumentsDirectory(); + final folder = Directory("${dir.path}/images"); + + if (!await folder.exists()) { + await folder.create(); + } + + final id = const Uuid().v4(); + final ext = file.path.split('.').last; + + final newPath = "${folder.path}/$id.$ext"; + final savedFile = await file.copy(newPath); + + await _repo.insertImage(id, savedFile.path); + + return id; + } +} diff --git a/lib/ui/pages/board_detail_page.dart b/lib/ui/pages/board_detail_page.dart @@ -0,0 +1,88 @@ +// lib/ui/pages/board_detail_page.dart + +import 'dart:io'; +import 'package:flutter/material.dart'; +import 'package:adobe/data/models/board_model.dart'; +import 'package:adobe/data/models/image_model.dart'; +import 'package:adobe/data/repos/board_image_repo.dart'; + +class BoardDetailPage extends StatefulWidget { + final Board board; + + const BoardDetailPage({super.key, required this.board}); + + @override + State<BoardDetailPage> createState() => _BoardDetailPageState(); +} + +class _BoardDetailPageState extends State<BoardDetailPage> { + final _boardImageRepo = BoardImageRepository(); + late Future<List<ImageModel>> _imagesFuture; + + @override + void initState() { + super.initState(); + _imagesFuture = _fetchImages(); + } + + Future<List<ImageModel>> _fetchImages() async { + final rawData = await _boardImageRepo.getImagesOfBoard(widget.board.id); + return rawData.map((e) => ImageModel.fromMap(e)).toList(); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar(title: Text(widget.board.name)), + body: FutureBuilder<List<ImageModel>>( + future: _imagesFuture, + builder: (context, snapshot) { + if (snapshot.connectionState == ConnectionState.waiting) { + return const Center(child: CircularProgressIndicator()); + } + + if (!snapshot.hasData || snapshot.data!.isEmpty) { + return const Center(child: Text("No images saved to this board yet.")); + } + + final images = snapshot.data!; + + return GridView.builder( + padding: const EdgeInsets.all(8), + gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount( + crossAxisCount: 2, + crossAxisSpacing: 8, + mainAxisSpacing: 8, + childAspectRatio: 0.8, // Taller for images + ), + itemCount: images.length, + itemBuilder: (context, index) { + final img = images[index]; + final file = File(img.filePath); + + return ClipRRect( + borderRadius: BorderRadius.circular(12), + child: file.existsSync() + ? Image.file( + file, + fit: BoxFit.cover, + errorBuilder: (context, error, stackTrace) { + return Container( + color: Colors.grey[300], + alignment: Alignment.center, + child: const Icon(Icons.broken_image, color: Colors.grey), + ); + }, + ) + : Container( + color: Colors.grey[300], + child: const Icon(Icons.error, color: Colors.red), + ), + ); + }, + ); + }, + ), + ); + } +} +\ No newline at end of file diff --git a/lib/ui/pages/board_page.dart b/lib/ui/pages/board_page.dart @@ -0,0 +1,191 @@ +// lib/ui/pages/board_page.dart + +import 'dart:io'; +import 'package:flutter/material.dart'; + +import '../../data/repos/board_repo.dart'; +import '../../data/repos/board_image_repo.dart'; +import '../../data/models/board_model.dart'; +import '../../data/models/image_model.dart'; + +class BoardListPage extends StatefulWidget { + const BoardListPage({super.key}); + + @override + State<BoardListPage> createState() => _BoardListPageState(); +} + +class _BoardListPageState extends State<BoardListPage> { + final boardRepo = BoardRepository(); + List<Board> boards = []; + + @override + void initState() { + super.initState(); + loadBoards(); + } + + Future<void> loadBoards() async { + final result = await boardRepo.getBoards(); + boards = result.map((e) => Board.fromMap(e)).toList(); + if (mounted) setState(() {}); + } + + Future<void> createBoardDialog() async { + final nameController = TextEditingController(); + if (!mounted) return; + + await showDialog( + context: context, + builder: (c) => AlertDialog( + title: Text("Create New Board"), + content: TextField( + controller: nameController, + decoration: InputDecoration(hintText: "Board name"), + ), + actions: [ + TextButton( + onPressed: () { + Navigator.pop(c); + }, + child: Text("Cancel"), + ), + ElevatedButton( + onPressed: () async { + if (nameController.text.isNotEmpty) { + await boardRepo.createBoard(nameController.text); + if (!c.mounted) return; + Navigator.pop(c); + await loadBoards(); + } + }, + child: Text("Create"), + ) + ], + ), + ); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + title: Text("Boards"), + actions: [ + IconButton( + icon: Icon(Icons.add), + onPressed: createBoardDialog, + ) + ], + ), + + body: boards.isEmpty + ? Center(child: Text("No boards yet")) + : GridView.builder( + padding: EdgeInsets.all(12), + gridDelegate: SliverGridDelegateWithFixedCrossAxisCount( + crossAxisCount: 2, + childAspectRatio: 1, + crossAxisSpacing: 10, + mainAxisSpacing: 10, + ), + itemCount: boards.length, + itemBuilder: (c, i) { + final board = boards[i]; + + return GestureDetector( + onTap: () { + Navigator.push( + context, + MaterialPageRoute( + builder: (_) => BoardDetailPage(board: board), + ), + ); + }, + child: Container( + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(12), + color: Colors.grey.shade300, + ), + child: Center( + child: Text( + board.name, + style: TextStyle(fontSize: 18), + ), + ), + ), + ); + }, + ), + ); + } +} + +// --------------------------------------------------------- +// BOARD DETAIL PAGE +// --------------------------------------------------------- + +class BoardDetailPage extends StatefulWidget { + final Board board; + + const BoardDetailPage({super.key, required this.board}); + + @override + State<BoardDetailPage> createState() => _BoardDetailPageState(); +} + +class _BoardDetailPageState extends State<BoardDetailPage> { + final boardImageRepo = BoardImageRepository(); + List<ImageModel> images = []; + + @override + void initState() { + super.initState(); + loadImages(); + } + + Future<void> loadImages() async { + final result = await boardImageRepo.getImagesOfBoard(widget.board.id); + + images = result + .map((e) => ImageModel( + id: e['id'], + filePath: e['filePath'], + )) + .toList(); + + if (mounted) setState(() {}); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + title: Text(widget.board.name), + ), + body: images.isEmpty + ? Center(child: Text("No images in this board yet")) + : GridView.builder( + padding: EdgeInsets.all(12), + itemCount: images.length, + gridDelegate: SliverGridDelegateWithFixedCrossAxisCount( + crossAxisCount: 2, + mainAxisSpacing: 10, + crossAxisSpacing: 10, + childAspectRatio: 1, + ), + itemBuilder: (context, index) { + final img = images[index]; + + return ClipRRect( + borderRadius: BorderRadius.circular(10), + child: Image.file( + File(img.filePath), + fit: BoxFit.cover, + ), + ); + }, + ), + ); + } +} diff --git a/lib/ui/pages/home_page.dart b/lib/ui/pages/home_page.dart @@ -0,0 +1,159 @@ +// lib/ui/pages/home_page.dart + +import 'package:flutter/material.dart'; +import 'package:adobe/data/repos/board_repo.dart'; +import 'package:adobe/data/models/board_model.dart'; +import 'package:adobe/ui/pages/board_detail_page.dart'; + +class HomePage extends StatefulWidget { + const HomePage({super.key}); + + @override + State<HomePage> createState() => _HomePageState(); +} + +class _HomePageState extends State<HomePage> { + final _boardRepo = BoardRepository(); + late Future<List<Board>> _boardsFuture; + + @override + void initState() { + super.initState(); + _refreshBoards(); + } + + void _refreshBoards() { + setState(() { + _boardsFuture = _boardRepo.getBoards().then((data) { + return data.map((e) => Board.fromMap(e)).toList(); + }); + }); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + title: const Text("My Boards"), + centerTitle: true, + ), + floatingActionButton: FloatingActionButton( + onPressed: _showCreateBoardDialog, + child: const Icon(Icons.add), + ), + // Pull to Refresh allows you to update the list after sharing an image + body: RefreshIndicator( + onRefresh: () async => _refreshBoards(), + child: FutureBuilder<List<Board>>( + future: _boardsFuture, + builder: (context, snapshot) { + if (snapshot.connectionState == ConnectionState.waiting) { + return const Center(child: CircularProgressIndicator()); + } + + if (!snapshot.hasData || snapshot.data!.isEmpty) { + return ListView( + children: const [ + SizedBox(height: 200), + Center(child: Text("No boards yet. Create one!")), + ], + ); + } + + final boards = snapshot.data!; + + return GridView.builder( + padding: const EdgeInsets.all(12), + itemCount: boards.length, + gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount( + crossAxisCount: 2, + crossAxisSpacing: 12, + mainAxisSpacing: 12, + childAspectRatio: 1.2, // Rectangular cards + ), + itemBuilder: (context, index) { + final board = boards[index]; + return _buildBoardCard(board); + }, + ); + }, + ), + ), + ); + } + + Widget _buildBoardCard(Board board) { + return GestureDetector( + onTap: () { + // Navigate to the details of this board + Navigator.push( + context, + MaterialPageRoute( + builder: (_) => BoardDetailPage(board: board), + ), + ).then((_) => _refreshBoards()); // Refresh when coming back + }, + child: Container( + decoration: BoxDecoration( + color: Colors.grey[200], + borderRadius: BorderRadius.circular(16), + boxShadow: [ + BoxShadow( + color: Colors.black.withValues(alpha: 0.05), + blurRadius: 5, + offset: const Offset(0, 4), + ) + ], + ), + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + const Icon(Icons.dashboard, size: 40, color: Colors.grey), + const SizedBox(height: 8), + Text( + board.name, + style: const TextStyle( + fontSize: 18, + fontWeight: FontWeight.bold, + ), + textAlign: TextAlign.center, + ), + const SizedBox(height: 4), + const Text( + "Tap to view", + style: TextStyle(fontSize: 12, color: Colors.grey), + ), + ], + ), + ), + ); + } + + void _showCreateBoardDialog() { + final controller = TextEditingController(); + showDialog( + context: context, + builder: (c) => AlertDialog( + title: const Text("New Board"), + content: TextField( + controller: controller, + decoration: const InputDecoration(hintText: "Board Name"), + autofocus: true, + ), + actions: [ + TextButton(onPressed: () => Navigator.pop(c), child: const Text("Cancel")), + ElevatedButton( + onPressed: () async { + if (controller.text.isNotEmpty) { + await _boardRepo.createBoard(controller.text); + if (c.mounted) Navigator.pop(c); + _refreshBoards(); + } + }, + child: const Text("Create"), + ) + ], + ), + ); + } +} +\ No newline at end of file diff --git a/lib/ui/pages/image_picker_page.dart b/lib/ui/pages/image_picker_page.dart @@ -0,0 +1,34 @@ +// lib/ui/pages/image_picker_page.dart + +import 'dart:io'; +import 'package:flutter/material.dart'; +import 'package:image_picker/image_picker.dart'; +import '../../services/image_service.dart'; + +class ImagePickerPage extends StatelessWidget { + ImagePickerPage({super.key}); + final picker = ImagePicker(); + final service = ImageService(); + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar(title: Text("Pick Image")), + body: Center( + child: ElevatedButton( + onPressed: () async { + final picked = await picker.pickImage(source: ImageSource.gallery); + + if (picked != null) { + await service.saveImage(File(picked.path)); + } + + if (!context.mounted) return; + Navigator.pop(context); + }, + child: Text("Pick From Gallery"), + ), + ), + ); + } +} diff --git a/lib/ui/pages/share_handler_page.dart b/lib/ui/pages/share_handler_page.dart @@ -0,0 +1,166 @@ +// lib/ui/pages/share_handler_page.dart + +import 'package:flutter/material.dart'; +import 'package:adobe/services/download_service.dart'; +import 'package:adobe/data/repos/board_repo.dart'; +import 'package:adobe/data/repos/board_image_repo.dart'; + +class ShareHandlerPage extends StatefulWidget { + final String sharedText; // The URL passed from Android + + const ShareHandlerPage({super.key, required this.sharedText}); + + @override + State<ShareHandlerPage> createState() => _ShareHandlerPageState(); +} + +class _ShareHandlerPageState extends State<ShareHandlerPage> { + final _downloadService = DownloadService(); + final _boardRepo = BoardRepository(); + final _boardImageRepo = BoardImageRepository(); + + String? _downloadedImageId; + bool _isDownloading = true; + bool _hasError = false; + List<Map<String, dynamic>> _boards = []; + + @override + void initState() { + super.initState(); + _startProcess(); + } + + void _startProcess() async { + // 1. Start loading boards immediately + _boards = await _boardRepo.getBoards(); + + // 2. Parse URL (Simple regex to find a URL in the shared text) + final urlRegExp = RegExp(r'(https?://\S+)'); + final match = urlRegExp.firstMatch(widget.sharedText); + + if (match != null) { + final url = match.group(0)!; + // 3. Download in background + final id = await _downloadService.downloadAndSaveImage(url); + + if (mounted) { + setState(() { + _downloadedImageId = id; + _isDownloading = false; + if (id == null) _hasError = true; + }); + } + } else { + if (mounted) setState(() => _hasError = true); + } + } + + Future<void> _saveToBoard(int boardId) async { + if (_downloadedImageId == null) return; + + await _boardImageRepo.saveToBoard(boardId, _downloadedImageId!); + + if (!mounted) return; + + ScaffoldMessenger.of(context).showSnackBar( + SnackBar(content: Text("Saved to board!")), + ); + + // Close the app or go back to main screen + Navigator.popUntil(context, (route) => route.isFirst); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar(title: Text("Save to Board")), + body: _buildBody(), + ); + } + + Widget _buildBody() { + if (_hasError) { + return Center(child: Text("Could not download image from link.")); + } + + // While downloading, show loader but ALSO show boards (disabled or overlay) + // Or just show loader as per your request + if (_isDownloading) { + return Center( + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + CircularProgressIndicator(), + SizedBox(height: 16), + Text("Downloading image..."), + ], + ), + ); + } + + // Once downloaded, allow selection + return Column( + children: [ + Padding( + padding: const EdgeInsets.all(16.0), + child: Text( + "Select a board", + style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold), + ), + ), + Expanded( + child: ListView.builder( + itemCount: _boards.length + 1, // +1 for "Create Board" + itemBuilder: (context, index) { + // "Create New Board" Option + if (index == 0) { + return ListTile( + leading: Icon(Icons.add_box, color: Colors.red), + title: Text("Create New Board"), + onTap: () { + // Call your create board dialog logic here + _showCreateBoardDialog(); + }, + ); + } + + final board = _boards[index - 1]; + return ListTile( + leading: Icon(Icons.dashboard), + title: Text(board['name']), + onTap: () => _saveToBoard(board['id']), + ); + }, + ), + ), + ], + ); + } + + void _showCreateBoardDialog() { + final controller = TextEditingController(); + showDialog( + context: context, + builder: (c) => AlertDialog( + title: Text("New Board"), + content: TextField(controller: controller, decoration: InputDecoration(hintText: "Name")), + actions: [ + TextButton(onPressed: () => Navigator.pop(c), child: Text("Cancel")), + ElevatedButton( + onPressed: () async { + if (controller.text.isNotEmpty) { + // Create board + final newId = await _boardRepo.createBoard(controller.text); + if(!c.mounted) return; + Navigator.pop(c); // Close dialog + // Save image to new board + _saveToBoard(newId); + } + }, + child: Text("Create"), + ) + ], + ), + ); + } +} +\ No newline at end of file diff --git a/lib/ui/widgets/save_to_board_sheet.dart b/lib/ui/widgets/save_to_board_sheet.dart @@ -0,0 +1,73 @@ +// lib/ui/widgets/save_to_board_sheet.dart + +import 'package:flutter/material.dart'; +import '../../data/repos/board_repo.dart'; +import '../../data/repos/board_image_repo.dart'; + +class SaveToBoardSheet extends StatefulWidget { + final String imageId; + + const SaveToBoardSheet({super.key, required this.imageId}); + + @override + State<SaveToBoardSheet> createState() => _SaveToBoardSheetState(); +} + +class _SaveToBoardSheetState extends State<SaveToBoardSheet> { + final boardRepo = BoardRepository(); + final boardImgRepo = BoardImageRepository(); + + List<Map<String, dynamic>> boards = []; + + @override + void initState() { + loadBoards(); + super.initState(); + } + + Future<void> loadBoards() async { + boards = await boardRepo.getBoards(); + if (mounted) setState(() {}); + } + + @override + Widget build(BuildContext context) { + return Container( + padding: EdgeInsets.all(16), + height: 300, + child: Column( + children: [ + Text("Save to Board", style: TextStyle(fontSize: 20)), + ElevatedButton( + onPressed: () async { + await boardRepo.createBoard("New Board"); + await loadBoards(); + }, + child: Text("Create New Board"), + ), + SizedBox(height: 10), + Expanded( + child: ListView.builder( + itemCount: boards.length, + itemBuilder: (c, i) { + final board = boards[i]; + + return ListTile( + title: Text(board['name']), + onTap: () async { + await boardImgRepo.saveToBoard( + board['id'], + widget.imageId, + ); + if (!context.mounted) return; + Navigator.pop(context); + }, + ); + }, + ), + ) + ], + ), + ); + } +} diff --git a/linux/flutter/generated_plugin_registrant.cc b/linux/flutter/generated_plugin_registrant.cc @@ -6,6 +6,10 @@ #include "generated_plugin_registrant.h" +#include <file_selector_linux/file_selector_plugin.h> void fl_register_plugins(FlPluginRegistry* registry) { + g_autoptr(FlPluginRegistrar) file_selector_linux_registrar = + fl_plugin_registry_get_registrar_for_plugin(registry, "FileSelectorPlugin"); + file_selector_plugin_register_with_registrar(file_selector_linux_registrar); } diff --git a/linux/flutter/generated_plugins.cmake b/linux/flutter/generated_plugins.cmake @@ -3,6 +3,7 @@ # list(APPEND FLUTTER_PLUGIN_LIST + file_selector_linux ) list(APPEND FLUTTER_FFI_PLUGIN_LIST diff --git a/macos/Flutter/GeneratedPluginRegistrant.swift b/macos/Flutter/GeneratedPluginRegistrant.swift @@ -5,6 +5,12 @@ import FlutterMacOS import Foundation +import file_selector_macos +import path_provider_foundation +import sqflite_darwin func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { + FileSelectorPlugin.register(with: registry.registrar(forPlugin: "FileSelectorPlugin")) + PathProviderPlugin.register(with: registry.registrar(forPlugin: "PathProviderPlugin")) + SqflitePlugin.register(with: registry.registrar(forPlugin: "SqflitePlugin")) } diff --git a/pubspec.lock b/pubspec.lock @@ -41,6 +41,30 @@ packages: url: "https://pub.dev" source: hosted version: "1.19.1" + cross_file: + dependency: transitive + description: + name: cross_file + sha256: "942a4791cd385a68ccb3b32c71c427aba508a1bb949b86dff2adbe4049f16239" + url: "https://pub.dev" + source: hosted + version: "0.3.5" + crypto: + dependency: transitive + description: + name: crypto + sha256: c8ea0233063ba03258fbcf2ca4d6dadfefe14f02fab57702265467a19f27fadf + url: "https://pub.dev" + source: hosted + version: "3.0.7" + csslib: + dependency: transitive + description: + name: csslib + sha256: "09bad715f418841f976c77db72d5398dc1253c21fb9c0c7f0b0b985860b2d58e" + url: "https://pub.dev" + source: hosted + version: "1.0.2" cupertino_icons: dependency: "direct main" description: @@ -57,6 +81,54 @@ packages: url: "https://pub.dev" source: hosted version: "1.3.2" + ffi: + dependency: transitive + description: + name: ffi + sha256: "289279317b4b16eb2bb7e271abccd4bf84ec9bdcbe999e278a94b804f5630418" + url: "https://pub.dev" + source: hosted + version: "2.1.4" + file_selector_linux: + dependency: transitive + description: + name: file_selector_linux + sha256: "54cbbd957e1156d29548c7d9b9ec0c0ebb6de0a90452198683a7d23aed617a33" + url: "https://pub.dev" + source: hosted + version: "0.9.3+2" + file_selector_macos: + dependency: transitive + description: + name: file_selector_macos + sha256: "19124ff4a3d8864fdc62072b6a2ef6c222d55a3404fe14893a3c02744907b60c" + url: "https://pub.dev" + source: hosted + version: "0.9.4+4" + file_selector_platform_interface: + dependency: transitive + description: + name: file_selector_platform_interface + sha256: "35e0bd61ebcdb91a3505813b055b09b79dfdc7d0aee9c09a7ba59ae4bb13dc85" + url: "https://pub.dev" + source: hosted + version: "2.7.0" + file_selector_windows: + dependency: transitive + description: + name: file_selector_windows + sha256: "320fcfb6f33caa90f0b58380489fc5ac05d99ee94b61aa96ec2bff0ba81d3c2b" + url: "https://pub.dev" + source: hosted + version: "0.9.3+4" + fixnum: + dependency: transitive + description: + name: fixnum + sha256: b6dc7065e46c974bc7c5f143080a6764ec7a4be6da1285ececdc37be96de53be + url: "https://pub.dev" + source: hosted + version: "1.1.1" flutter: dependency: "direct main" description: flutter @@ -70,11 +142,112 @@ packages: url: "https://pub.dev" source: hosted version: "5.0.0" + flutter_plugin_android_lifecycle: + dependency: transitive + description: + name: flutter_plugin_android_lifecycle + sha256: c2fe1001710127dfa7da89977a08d591398370d099aacdaa6d44da7eb14b8476 + url: "https://pub.dev" + source: hosted + version: "2.0.31" flutter_test: dependency: "direct dev" description: flutter source: sdk version: "0.0.0" + flutter_web_plugins: + dependency: transitive + description: flutter + source: sdk + version: "0.0.0" + html: + dependency: "direct main" + description: + name: html + sha256: "6d1264f2dffa1b1101c25a91dff0dc2daee4c18e87cd8538729773c073dbf602" + url: "https://pub.dev" + source: hosted + version: "0.15.6" + http: + dependency: "direct main" + description: + name: http + sha256: "87721a4a50b19c7f1d49001e51409bddc46303966ce89a65af4f4e6004896412" + url: "https://pub.dev" + source: hosted + version: "1.6.0" + http_parser: + dependency: transitive + description: + name: http_parser + sha256: "178d74305e7866013777bab2c3d8726205dc5a4dd935297175b19a23a2e66571" + url: "https://pub.dev" + source: hosted + version: "4.1.2" + image_picker: + dependency: "direct main" + description: + name: image_picker + sha256: "784210112be18ea55f69d7076e2c656a4e24949fa9e76429fe53af0c0f4fa320" + url: "https://pub.dev" + source: hosted + version: "1.2.1" + image_picker_android: + dependency: transitive + description: + name: image_picker_android + sha256: "28f3987ca0ec702d346eae1d90eda59603a2101b52f1e234ded62cff1d5cfa6e" + url: "https://pub.dev" + source: hosted + version: "0.8.13+1" + image_picker_for_web: + dependency: transitive + description: + name: image_picker_for_web + sha256: "40c2a6a0da15556dc0f8e38a3246064a971a9f512386c3339b89f76db87269b6" + url: "https://pub.dev" + source: hosted + version: "3.1.0" + image_picker_ios: + dependency: transitive + description: + name: image_picker_ios + sha256: eb06fe30bab4c4497bad449b66448f50edcc695f1c59408e78aa3a8059eb8f0e + url: "https://pub.dev" + source: hosted + version: "0.8.13" + image_picker_linux: + dependency: transitive + description: + name: image_picker_linux + sha256: "1f81c5f2046b9ab724f85523e4af65be1d47b038160a8c8deed909762c308ed4" + url: "https://pub.dev" + source: hosted + version: "0.2.2" + image_picker_macos: + dependency: transitive + description: + name: image_picker_macos + sha256: d58cd9d67793d52beefd6585b12050af0a7663c0c2a6ece0fb110a35d6955e04 + url: "https://pub.dev" + source: hosted + version: "0.2.2" + image_picker_platform_interface: + dependency: transitive + description: + name: image_picker_platform_interface + sha256: "567e056716333a1647c64bb6bd873cff7622233a5c3f694be28a583d4715690c" + url: "https://pub.dev" + source: hosted + version: "2.11.1" + image_picker_windows: + dependency: transitive + description: + name: image_picker_windows + sha256: d248c86554a72b5495a31c56f060cf73a41c7ff541689327b1a7dbccc33adfae + url: "https://pub.dev" + source: hosted + version: "0.2.2" leak_tracker: dependency: transitive description: @@ -131,14 +304,94 @@ packages: url: "https://pub.dev" source: hosted version: "1.16.0" - path: + mime: dependency: transitive description: + name: mime + sha256: "41a20518f0cb1256669420fdba0cd90d21561e560ac240f26ef8322e45bb7ed6" + url: "https://pub.dev" + source: hosted + version: "2.0.0" + path: + dependency: "direct main" + description: name: path sha256: "75cca69d1490965be98c73ceaea117e8a04dd21217b37b292c9ddbec0d955bc5" url: "https://pub.dev" source: hosted version: "1.9.1" + path_provider: + dependency: "direct main" + description: + name: path_provider + sha256: "50c5dd5b6e1aaf6fb3a78b33f6aa3afca52bf903a8a5298f53101fdaee55bbcd" + url: "https://pub.dev" + source: hosted + version: "2.1.5" + path_provider_android: + dependency: transitive + description: + name: path_provider_android + sha256: "3b4c1fc3aa55ddc9cd4aa6759984330d5c8e66aa7702a6223c61540dc6380c37" + url: "https://pub.dev" + source: hosted + version: "2.2.19" + path_provider_foundation: + dependency: transitive + description: + name: path_provider_foundation + sha256: "16eef174aacb07e09c351502740fa6254c165757638eba1e9116b0a781201bbd" + url: "https://pub.dev" + source: hosted + version: "2.4.2" + path_provider_linux: + dependency: transitive + description: + name: path_provider_linux + sha256: f7a1fe3a634fe7734c8d3f2766ad746ae2a2884abe22e241a8b301bf5cac3279 + url: "https://pub.dev" + source: hosted + version: "2.2.1" + path_provider_platform_interface: + dependency: transitive + description: + name: path_provider_platform_interface + sha256: "88f5779f72ba699763fa3a3b06aa4bf6de76c8e5de842cf6f29e2e06476c2334" + url: "https://pub.dev" + source: hosted + version: "2.1.2" + path_provider_windows: + dependency: transitive + description: + name: path_provider_windows + sha256: bd6f00dbd873bfb70d0761682da2b3a2c2fccc2b9e84c495821639601d81afe7 + url: "https://pub.dev" + source: hosted + version: "2.3.0" + platform: + dependency: transitive + description: + name: platform + sha256: "5d6b1b0036a5f331ebc77c850ebc8506cbc1e9416c27e59b439f917a902a4984" + url: "https://pub.dev" + source: hosted + version: "3.1.6" + plugin_platform_interface: + dependency: transitive + description: + name: plugin_platform_interface + sha256: "4820fbfdb9478b1ebae27888254d445073732dae3d6ea81f0b7e06d5dedc3f02" + url: "https://pub.dev" + source: hosted + version: "2.1.8" + receive_sharing_intent: + dependency: "direct main" + description: + name: receive_sharing_intent + sha256: ec76056e4d258ad708e76d85591d933678625318e411564dcb9059048ca3a593 + url: "https://pub.dev" + source: hosted + version: "1.8.1" sky_engine: dependency: transitive description: flutter @@ -152,6 +405,46 @@ packages: url: "https://pub.dev" source: hosted version: "1.10.1" + sqflite: + dependency: "direct main" + description: + name: sqflite + sha256: e2297b1da52f127bc7a3da11439985d9b536f75070f3325e62ada69a5c585d03 + url: "https://pub.dev" + source: hosted + version: "2.4.2" + sqflite_android: + dependency: transitive + description: + name: sqflite_android + sha256: "2b3070c5fa881839f8b402ee4a39c1b4d561704d4ebbbcfb808a119bc2a1701b" + url: "https://pub.dev" + source: hosted + version: "2.4.1" + sqflite_common: + dependency: transitive + description: + name: sqflite_common + sha256: "84731e8bfd8303a3389903e01fb2141b6e59b5973cacbb0929021df08dddbe8b" + url: "https://pub.dev" + source: hosted + version: "2.5.5" + sqflite_darwin: + dependency: transitive + description: + name: sqflite_darwin + sha256: "279832e5cde3fe99e8571879498c9211f3ca6391b0d818df4e17d9fff5c6ccb3" + url: "https://pub.dev" + source: hosted + version: "2.4.2" + sqflite_platform_interface: + dependency: transitive + description: + name: sqflite_platform_interface + sha256: "8dd4515c7bdcae0a785b0062859336de775e8c65db81ae33dd5445f35be61920" + url: "https://pub.dev" + source: hosted + version: "2.4.0" stack_trace: dependency: transitive description: @@ -176,6 +469,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.4.1" + synchronized: + dependency: transitive + description: + name: synchronized + sha256: "0669c70faae6270521ee4f05bffd2919892d42d1276e6c495be80174b6bc0ef6" + url: "https://pub.dev" + source: hosted + version: "3.3.1" term_glyph: dependency: transitive description: @@ -192,6 +493,22 @@ packages: url: "https://pub.dev" source: hosted version: "0.7.4" + typed_data: + dependency: transitive + description: + name: typed_data + sha256: f9049c039ebfeb4cf7a7104a675823cd72dba8297f264b6637062516699fa006 + url: "https://pub.dev" + source: hosted + version: "1.4.0" + uuid: + dependency: "direct main" + description: + name: uuid + sha256: a11b666489b1954e01d992f3d601b1804a33937b5a8fe677bd26b8a9f96f96e8 + url: "https://pub.dev" + source: hosted + version: "4.5.2" vector_math: dependency: transitive description: @@ -208,6 +525,22 @@ packages: url: "https://pub.dev" source: hosted version: "14.3.1" + web: + dependency: transitive + description: + name: web + sha256: "868d88a33d8a87b18ffc05f9f030ba328ffefba92d6c127917a2ba740f9cfe4a" + url: "https://pub.dev" + source: hosted + version: "1.1.1" + xdg_directories: + dependency: transitive + description: + name: xdg_directories + sha256: "7a3f37b05d989967cdddcbb571f1ea834867ae2faa29725fd085180e0883aa15" + url: "https://pub.dev" + source: hosted + version: "1.1.0" sdks: dart: ">=3.7.2 <4.0.0" - flutter: ">=3.18.0-18.0.pre.54" + flutter: ">=3.29.0" diff --git a/pubspec.yaml b/pubspec.yaml @@ -34,6 +34,14 @@ dependencies: # The following adds the Cupertino Icons font to your application. # Use with the CupertinoIcons class for iOS style icons. cupertino_icons: ^1.0.8 + sqflite: ^2.4.2 + path_provider: ^2.1.5 + path: ^1.9.1 + image_picker: ^1.2.1 + uuid: ^4.5.2 + receive_sharing_intent: ^1.8.1 + http: ^1.6.0 + html: ^0.15.6 dev_dependencies: flutter_test: diff --git a/test/widget_test.dart b/test/widget_test.dart @@ -5,15 +5,14 @@ // 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:adobe/ui/pages/home_page.dart'; import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; -import 'package:adobe/main.dart'; - void main() { testWidgets('Counter increments smoke test', (WidgetTester tester) async { // Build our app and trigger a frame. - await tester.pumpWidget(const MyApp()); + await tester.pumpWidget(MaterialApp(home: HomePage())); // Verify that our counter starts at 0. expect(find.text('0'), findsOneWidget); diff --git a/windows/flutter/generated_plugin_registrant.cc b/windows/flutter/generated_plugin_registrant.cc @@ -6,6 +6,9 @@ #include "generated_plugin_registrant.h" +#include <file_selector_windows/file_selector_windows.h> void RegisterPlugins(flutter::PluginRegistry* registry) { + FileSelectorWindowsRegisterWithRegistrar( + registry->GetRegistrarForPlugin("FileSelectorWindows")); } diff --git a/windows/flutter/generated_plugins.cmake b/windows/flutter/generated_plugins.cmake @@ -3,6 +3,7 @@ # list(APPEND FLUTTER_PLUGIN_LIST + file_selector_windows ) list(APPEND FLUTTER_FFI_PLUGIN_LIST