@@ -41,6 +41,8 @@ import 'package:fula_files/features/tags/widgets/tag_selector_dialog.dart';
4141import 'package:fula_files/features/tags/widgets/tag_chip.dart' ;
4242import 'package:open_filex/open_filex.dart' ;
4343import 'package:share_plus/share_plus.dart' ;
44+ import 'package:url_launcher/url_launcher.dart' ;
45+ import 'package:fula_files/core/services/ipfs_public_service.dart' ;
4446import 'package:fula_files/shared/utils/error_messages.dart' ;
4547import 'package:fula_files/features/sharing/providers/collaboration_provider.dart' ;
4648import 'package:fula_files/core/models/collaboration_group.dart' ;
@@ -2105,6 +2107,17 @@ class _FileBrowserScreenState extends ConsumerState<FileBrowserScreen> {
21052107 ],
21062108 );
21072109 }),
2110+ // Share Publicly via IPFS (files only, no cloud sync required)
2111+ if (! file.isDirectory)
2112+ ListTile (
2113+ leading: const Icon (LucideIcons .globe, color: Colors .teal),
2114+ title: const Text ('Share Publicly' ),
2115+ subtitle: const Text ('Pin to IPFS (unencrypted)' ),
2116+ onTap: () {
2117+ Navigator .pop (ctx);
2118+ _sharePubliclyViaIpfs (file);
2119+ },
2120+ ),
21082121 const Divider (height: 1 ),
21092122 // Archive actions - only for archive files
21102123 if (! file.isDirectory && ArchiveService .instance.isArchive (file.path))
@@ -2647,6 +2660,146 @@ class _FileBrowserScreenState extends ConsumerState<FileBrowserScreen> {
26472660 // ============================================================================
26482661
26492662 /// Show info message when share is disabled
2663+ Future <void > _sharePubliclyViaIpfs (LocalFile file) async {
2664+ final confirmed = await showDialog <bool >(
2665+ context: context,
2666+ builder: (ctx) => AlertDialog (
2667+ title: const Text ('Share Publicly?' ),
2668+ content: Column (
2669+ mainAxisSize: MainAxisSize .min,
2670+ crossAxisAlignment: CrossAxisAlignment .start,
2671+ children: [
2672+ const Text (
2673+ 'This will upload the file to IPFS without any encryption. '
2674+ 'Anyone with the link will be able to access it.' ,
2675+ ),
2676+ const SizedBox (height: 16 ),
2677+ Text ('File: ${file .name }' , style: const TextStyle (fontWeight: FontWeight .bold)),
2678+ Text ('Size: ${file .sizeFormatted }' ),
2679+ ],
2680+ ),
2681+ actions: [
2682+ TextButton (
2683+ onPressed: () => Navigator .pop (ctx, false ),
2684+ child: const Text ('Cancel' ),
2685+ ),
2686+ FilledButton (
2687+ style: FilledButton .styleFrom (backgroundColor: Colors .teal),
2688+ onPressed: () => Navigator .pop (ctx, true ),
2689+ child: const Text ('Share Publicly' ),
2690+ ),
2691+ ],
2692+ ),
2693+ );
2694+
2695+ if (confirmed != true || ! mounted) return ;
2696+
2697+ ScaffoldMessenger .of (context).showSnackBar (
2698+ SnackBar (
2699+ content: Row (
2700+ children: [
2701+ const SizedBox (
2702+ width: 20 , height: 20 ,
2703+ child: CircularProgressIndicator (strokeWidth: 2 , color: Colors .white),
2704+ ),
2705+ const SizedBox (width: 16 ),
2706+ Expanded (child: Text ('Uploading ${file .name } to IPFS...' )),
2707+ ],
2708+ ),
2709+ duration: const Duration (seconds: 60 ),
2710+ ),
2711+ );
2712+
2713+ try {
2714+ final result = await IpfsPublicService .instance.pinFile (file.path, file.name);
2715+
2716+ if (! mounted) return ;
2717+ ScaffoldMessenger .of (context).clearSnackBars ();
2718+ _showPublicIpfsLinkDialog (result.gatewayUrl, file.name);
2719+ } catch (e, st) {
2720+ debugPrint ('Share publicly failed: $e \n $st ' );
2721+ if (! mounted) return ;
2722+ ScaffoldMessenger .of (context).clearSnackBars ();
2723+ // Show the actual exception message — our service throws clear messages
2724+ final msg = e is Exception
2725+ ? e.toString ().replaceFirst ('Exception: ' , '' )
2726+ : ErrorMessages .forPublicShare (e);
2727+ ScaffoldMessenger .of (context).showSnackBar (
2728+ SnackBar (
2729+ content: Text (msg),
2730+ backgroundColor: Colors .red,
2731+ duration: const Duration (seconds: 8 ),
2732+ showCloseIcon: true ,
2733+ closeIconColor: Colors .white,
2734+ ),
2735+ );
2736+ }
2737+ }
2738+
2739+ void _showPublicIpfsLinkDialog (String gatewayUrl, String fileName) {
2740+ showDialog (
2741+ context: context,
2742+ builder: (ctx) => AlertDialog (
2743+ title: Row (
2744+ children: const [
2745+ Icon (LucideIcons .checkCircle, color: Colors .green, size: 24 ),
2746+ SizedBox (width: 8 ),
2747+ Text ('Shared Publicly' ),
2748+ ],
2749+ ),
2750+ content: Column (
2751+ mainAxisSize: MainAxisSize .min,
2752+ crossAxisAlignment: CrossAxisAlignment .start,
2753+ children: [
2754+ Text ('$fileName is now publicly accessible via IPFS.' ),
2755+ const SizedBox (height: 8 ),
2756+ const Text (
2757+ 'Anyone with this link can access the file:' ,
2758+ style: TextStyle (fontSize: 12 , color: Colors .grey),
2759+ ),
2760+ const SizedBox (height: 12 ),
2761+ Container (
2762+ width: double .infinity,
2763+ padding: const EdgeInsets .all (12 ),
2764+ decoration: BoxDecoration (
2765+ color: Theme .of (ctx).colorScheme.surfaceContainerHighest,
2766+ borderRadius: BorderRadius .circular (8 ),
2767+ ),
2768+ child: SelectableText (
2769+ gatewayUrl,
2770+ style: const TextStyle (fontFamily: 'monospace' , fontSize: 11 ),
2771+ ),
2772+ ),
2773+ ],
2774+ ),
2775+ actions: [
2776+ TextButton (
2777+ onPressed: () => Navigator .pop (ctx),
2778+ child: const Text ('Close' ),
2779+ ),
2780+ OutlinedButton .icon (
2781+ icon: const Icon (LucideIcons .externalLink, size: 16 ),
2782+ label: const Text ('Open' ),
2783+ onPressed: () {
2784+ launchUrl (Uri .parse (gatewayUrl), mode: LaunchMode .externalApplication);
2785+ },
2786+ ),
2787+ FilledButton .icon (
2788+ icon: const Icon (LucideIcons .copy, size: 16 ),
2789+ label: const Text ('Copy URL' ),
2790+ onPressed: () {
2791+ Clipboard .setData (ClipboardData (text: gatewayUrl));
2792+ Navigator .pop (ctx);
2793+ ScaffoldMessenger .of (context).showSnackBar (
2794+ const SnackBar (content: Text ('IPFS URL copied to clipboard' )),
2795+ );
2796+ },
2797+ ),
2798+ ],
2799+ ),
2800+ );
2801+ }
2802+
26502803 void _showShareDisabledInfo (bool isLoggedIn) {
26512804 if (! mounted) return ;
26522805
0 commit comments