From 44ca620a88b9c466a827b630582ca65f171a1188 Mon Sep 17 00:00:00 2001 From: Javed Hussein Date: Fri, 22 Apr 2022 18:38:08 -0400 Subject: [PATCH 01/16] feat: add file preview to info panel PE-1368 --- .../fs_entry_preview_cubit.dart | 72 +++++++ .../fs_entry_preview_state.dart | 23 +++ .../components/fs_entry_side_sheet.dart | 179 ++++++++++++------ lib/pages/drive_detail/drive_detail_page.dart | 3 + pubspec.lock | 42 ++++ pubspec.yaml | 1 + 6 files changed, 264 insertions(+), 56 deletions(-) create mode 100644 lib/blocs/fs_entry_preview/fs_entry_preview_cubit.dart create mode 100644 lib/blocs/fs_entry_preview/fs_entry_preview_state.dart diff --git a/lib/blocs/fs_entry_preview/fs_entry_preview_cubit.dart b/lib/blocs/fs_entry_preview/fs_entry_preview_cubit.dart new file mode 100644 index 0000000000..2f673291f0 --- /dev/null +++ b/lib/blocs/fs_entry_preview/fs_entry_preview_cubit.dart @@ -0,0 +1,72 @@ +import 'dart:async'; + +import 'package:ardrive/blocs/drive_detail/selected_item.dart'; +import 'package:ardrive/models/models.dart'; +import 'package:ardrive/services/config/app_config.dart'; +import 'package:bloc/bloc.dart'; +import 'package:equatable/equatable.dart'; + +part 'fs_entry_preview_state.dart'; + +class FsEntryPreviewCubit extends Cubit { + final String driveId; + final SelectedItem? maybeSelectedItem; + + final DriveDao _driveDao; + final AppConfig _config; + + StreamSubscription? _entrySubscription; + + final previewMaxFileSize = 1024 * 1024 * 10; + final allowedPreviewContentTypes = []; + + FsEntryPreviewCubit({ + required this.driveId, + this.maybeSelectedItem, + required DriveDao driveDao, + required AppConfig config, + }) : _driveDao = driveDao, + _config = config, + super(FsEntryPreviewInitial()) { + final selectedItem = maybeSelectedItem; + if (selectedItem != null) { + switch (selectedItem.runtimeType) { + case SelectedFile: + _entrySubscription = _driveDao + .fileById(driveId: driveId, fileId: selectedItem.id) + .watchSingle() + .listen((file) { + if (file.size <= previewMaxFileSize) { + emit( + FsEntryPreviewSuccess( + previewUrl: + '${_config.defaultArweaveGatewayUrl}/${file.dataTxId}', + ), + ); + } else { + emit(FsEntryPreviewUnavailable()); + } + }); + break; + + default: + } + } else { + emit(FsEntryPreviewUnavailable()); + } + } + + @override + void onError(Object error, StackTrace stackTrace) { + emit(FsEntryPreviewFailure()); + super.onError(error, stackTrace); + + print('Failed to load entity activity: $error $stackTrace'); + } + + @override + Future close() { + _entrySubscription?.cancel(); + return super.close(); + } +} diff --git a/lib/blocs/fs_entry_preview/fs_entry_preview_state.dart b/lib/blocs/fs_entry_preview/fs_entry_preview_state.dart new file mode 100644 index 0000000000..e6857ad1ae --- /dev/null +++ b/lib/blocs/fs_entry_preview/fs_entry_preview_state.dart @@ -0,0 +1,23 @@ +part of 'fs_entry_preview_cubit.dart'; + +abstract class FsEntryPreviewState extends Equatable { + const FsEntryPreviewState(); + + @override + List get props => []; +} + +class FsEntryPreviewUnavailable extends FsEntryPreviewState {} + +class FsEntryPreviewInitial extends FsEntryPreviewState {} + +class FsEntryPreviewSuccess extends FsEntryPreviewState { + final String previewUrl; + + FsEntryPreviewSuccess({required this.previewUrl}); + + @override + List get props => [previewUrl]; +} + +class FsEntryPreviewFailure extends FsEntryPreviewState {} diff --git a/lib/pages/drive_detail/components/fs_entry_side_sheet.dart b/lib/pages/drive_detail/components/fs_entry_side_sheet.dart index 43f7da25cb..6c8d01bb31 100644 --- a/lib/pages/drive_detail/components/fs_entry_side_sheet.dart +++ b/lib/pages/drive_detail/components/fs_entry_side_sheet.dart @@ -1,6 +1,6 @@ part of '../drive_detail_page.dart'; -class FsEntrySideSheet extends StatelessWidget { +class FsEntrySideSheet extends StatefulWidget { final String driveId; final Privacy drivePrivacy; final SelectedItem? maybeSelectedItem; @@ -10,70 +10,135 @@ class FsEntrySideSheet extends StatelessWidget { this.maybeSelectedItem, }); + @override + State createState() => _FsEntrySideSheetState(); +} + +class _FsEntrySideSheetState extends State { + Map datas = {}; + var tabCount = 2; + bool hasPreview = false; @override Widget build(BuildContext context) => Drawer( elevation: 1, - child: BlocProvider( + child: MultiBlocProvider( // Specify a key to ensure a new cubit is provided when the folder/file id changes. key: ValueKey( - driveId + - '${maybeSelectedItem?.id ?? Random().nextInt(1000).toString()}', + widget.driveId + + '${widget.maybeSelectedItem?.id ?? Random().nextInt(1000).toString()}', ), - create: (context) => FsEntryInfoCubit( - driveId: driveId, - maybeSelectedItem: maybeSelectedItem, - driveDao: context.read(), - ), - child: DefaultTabController( - length: 2, - child: BlocBuilder( - builder: (context, state) => state is FsEntryInfoSuccess - ? Column( - mainAxisSize: MainAxisSize.min, - crossAxisAlignment: CrossAxisAlignment.stretch, - children: [ - const SizedBox(height: 8), - ListTile( - title: Text(state.name), - trailing: IconButton( - icon: const Icon(Icons.close), - onPressed: () => context - .read() - .toggleSelectedItemDetails(), - ), - ), - TabBar( - tabs: [ - Tab( - text: appLocalizationsOf(context) - .itemDetailsEmphasized), - Tab( - text: appLocalizationsOf(context) - .itemActivityEmphasized) - ], - ), - Expanded( - child: TabBarView( + providers: [ + BlocProvider( + create: (context) => FsEntryInfoCubit( + driveId: widget.driveId, + maybeSelectedItem: widget.maybeSelectedItem, + driveDao: context.read(), + ), + ), + BlocProvider( + create: (context) => FsEntryPreviewCubit( + driveId: widget.driveId, + maybeSelectedItem: widget.maybeSelectedItem, + driveDao: context.read(), + config: context.read()), + ) + ], + child: BlocListener( + listener: (context, previewState) { + if (previewState is FsEntryPreviewSuccess) { + setState(() { + tabCount = 3; + hasPreview = true; + }); + } else { + setState(() { + tabCount = 2; + hasPreview = false; + }); + } + }, + child: DefaultTabController( + length: tabCount, + child: BlocBuilder( + builder: (context, previewState) { + return BlocBuilder( + builder: (context, state) => state is FsEntryInfoSuccess + ? Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.stretch, children: [ - Column( - crossAxisAlignment: CrossAxisAlignment.stretch, - children: [ - _buildInfoTable(context, state), - _buildTxTable(context, state), + const SizedBox(height: 8), + ListTile( + title: Text(state.name), + trailing: IconButton( + icon: const Icon(Icons.close), + onPressed: () => context + .read() + .toggleSelectedItemDetails(), + ), + ), + TabBar( + tabs: [ + if (hasPreview) + Tab( + text: 'Preview', + ), + Tab( + text: appLocalizationsOf(context) + .itemDetailsEmphasized), + Tab( + text: appLocalizationsOf(context) + .itemActivityEmphasized), ], ), - _buildActivityTab(context, state), + Expanded( + child: TabBarView( + children: [ + if (hasPreview && + previewState is FsEntryPreviewSuccess) + _buildPreviewTab(context, previewState), + Column( + crossAxisAlignment: + CrossAxisAlignment.stretch, + children: [ + _buildInfoTable(context, state), + _buildTxTable(context, state), + ], + ), + _buildActivityTab(context, state), + ], + ), + ) ], - ), - ) - ], - ) - : const SizedBox(), + ) + : const SizedBox(), + ); + }, + ), ), ), ), ); + Widget _buildPreviewTab(BuildContext context, FsEntryPreviewSuccess state) => + Container( + child: LinkPreview( + enableAnimation: true, + onPreviewDataFetched: (data) { + setState(() { + datas = { + ...datas, + state.previewUrl: data, + }; + }); + }, + previewData: datas[state.previewUrl], + text: state.previewUrl, + hideImage: false, + width: 700, + ), + ); + Widget _buildInfoTable(BuildContext context, FsEntryInfoSuccess state) => DataTable( // Hide the data table header. @@ -208,12 +273,13 @@ class FsEntrySideSheet extends StatelessWidget { ]), ], ); + Widget _buildTxTable(BuildContext context, FsEntryInfoSuccess infoState) => BlocProvider( create: (context) => FsEntryActivityCubit( - driveId: driveId, + driveId: widget.driveId, driveDao: context.read(), - maybeSelectedItem: maybeSelectedItem, + maybeSelectedItem: widget.maybeSelectedItem, ), child: BlocBuilder( builder: (context, state) { @@ -328,8 +394,8 @@ class FsEntrySideSheet extends StatelessWidget { padding: const EdgeInsets.only(top: 16), child: BlocProvider( create: (context) => FsEntryActivityCubit( - driveId: driveId, - maybeSelectedItem: maybeSelectedItem, + driveId: widget.driveId, + maybeSelectedItem: widget.maybeSelectedItem, driveDao: context.read(), ), child: BlocBuilder( @@ -392,7 +458,7 @@ class FsEntrySideSheet extends StatelessWidget { final previewOrDownloadButton = InkWell( onTap: () { downloadOrPreviewRevision( - drivePrivacy: drivePrivacy, + drivePrivacy: widget.drivePrivacy, context: context, revision: revision, ); @@ -401,7 +467,8 @@ class FsEntrySideSheet extends StatelessWidget { padding: const EdgeInsets.symmetric(vertical: 4), child: Row( mainAxisAlignment: MainAxisAlignment.start, - children: drivePrivacy == DrivePrivacy.private + children: widget.drivePrivacy == + DrivePrivacy.private ? [ Text( appLocalizationsOf(context).download), diff --git a/lib/pages/drive_detail/drive_detail_page.dart b/lib/pages/drive_detail/drive_detail_page.dart index 4779356b6e..5783333f1c 100644 --- a/lib/pages/drive_detail/drive_detail_page.dart +++ b/lib/pages/drive_detail/drive_detail_page.dart @@ -2,6 +2,7 @@ import 'dart:math'; import 'package:ardrive/blocs/blocs.dart'; import 'package:ardrive/blocs/drive_detail/selected_item.dart'; +import 'package:ardrive/blocs/fs_entry_preview/fs_entry_preview_cubit.dart'; import 'package:ardrive/components/components.dart'; import 'package:ardrive/components/csv_export_dialog.dart'; import 'package:ardrive/components/drive_rename_form.dart'; @@ -12,6 +13,7 @@ import 'package:ardrive/l11n/l11n.dart'; import 'package:ardrive/models/models.dart'; import 'package:ardrive/pages/congestion_warning_wrapper.dart'; import 'package:ardrive/pages/drive_detail/components/drive_file_drop_zone.dart'; +import 'package:ardrive/services/config/app_config.dart'; import 'package:ardrive/theme/theme.dart'; import 'package:ardrive/utils/num_to_string_parsers.dart'; import 'package:filesize/filesize.dart'; @@ -20,6 +22,7 @@ import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_gen/gen_l10n/app_localizations.dart'; +import 'package:flutter_link_previewer/flutter_link_previewer.dart'; import 'package:intersperse/intersperse.dart'; import 'package:moor/moor.dart' show OrderingMode; import 'package:responsive_builder/responsive_builder.dart'; diff --git a/pubspec.lock b/pubspec.lock index 9ee48b672f..72db26e77d 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -227,6 +227,13 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "2.0.2" + csslib: + dependency: transitive + description: + name: csslib + url: "https://pub.dartlang.org" + source: hosted + version: "0.17.1" csv: dependency: "direct main" description: @@ -337,6 +344,13 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "8.0.1" + flutter_chat_types: + dependency: transitive + description: + name: flutter_chat_types + url: "https://pub.dartlang.org" + source: hosted + version: "3.1.4" flutter_driver: dependency: "direct dev" description: flutter @@ -363,6 +377,20 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "3.0.7" + flutter_link_previewer: + dependency: "direct main" + description: + name: flutter_link_previewer + url: "https://pub.dartlang.org" + source: hosted + version: "2.5.2" + flutter_linkify: + dependency: transitive + description: + name: flutter_linkify + url: "https://pub.dartlang.org" + source: hosted + version: "5.0.2" flutter_localizations: dependency: "direct main" description: flutter @@ -460,6 +488,13 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "2.1.0" + html: + dependency: transitive + description: + name: html + url: "https://pub.dartlang.org" + source: hosted + version: "0.15.0" http: dependency: transitive description: @@ -537,6 +572,13 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "0.1.0" + linkify: + dependency: transitive + description: + name: linkify + url: "https://pub.dartlang.org" + source: hosted + version: "4.1.0" logging: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index f61c362bfc..d8bc0252ed 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -56,6 +56,7 @@ dependencies: collection: ^1.15.0-nullsafety.4 csv: 5.0.0 stash_memory: 4.0.1 + flutter_link_previewer: ^2.5.2 dev_dependencies: flutter_test: From 36dd022dc53aabd39f71d7fff33e7b0ba108dcd8 Mon Sep 17 00:00:00 2001 From: Javed Hussein Date: Mon, 25 Apr 2022 10:04:33 -0400 Subject: [PATCH 02/16] refactor: add more preview states PE-1368 --- .../fs_entry_preview_cubit.dart | 24 +++-- .../fs_entry_preview_state.dart | 36 +++++++ .../components/fs_entry_preview_widget.dart | 54 ++++++++++ .../components/fs_entry_side_sheet.dart | 23 +--- lib/pages/drive_detail/drive_detail_page.dart | 4 +- macos/Flutter/GeneratedPluginRegistrant.swift | 2 + pubspec.lock | 102 +++++++++++++++++- pubspec.yaml | 3 + 8 files changed, 219 insertions(+), 29 deletions(-) create mode 100644 lib/pages/drive_detail/components/fs_entry_preview_widget.dart diff --git a/lib/blocs/fs_entry_preview/fs_entry_preview_cubit.dart b/lib/blocs/fs_entry_preview/fs_entry_preview_cubit.dart index 2f673291f0..b05648fdb3 100644 --- a/lib/blocs/fs_entry_preview/fs_entry_preview_cubit.dart +++ b/lib/blocs/fs_entry_preview/fs_entry_preview_cubit.dart @@ -37,12 +37,24 @@ class FsEntryPreviewCubit extends Cubit { .watchSingle() .listen((file) { if (file.size <= previewMaxFileSize) { - emit( - FsEntryPreviewSuccess( - previewUrl: - '${_config.defaultArweaveGatewayUrl}/${file.dataTxId}', - ), - ); + final contentType = file.dataContentType!.split('/').first; + final previewUrl = + '${_config.defaultArweaveGatewayUrl}/${file.dataTxId}'; + switch (contentType) { + case 'image': + emit(FsEntryPreviewImage(previewUrl: previewUrl)); + break; + case 'audio': + emit(FsEntryPreviewAudio(previewUrl: previewUrl)); + break; + case 'video': + emit(FsEntryPreviewVideo(previewUrl: previewUrl)); + break; + case 'text': + emit(FsEntryPreviewText(previewUrl: previewUrl)); + break; + default: + } } else { emit(FsEntryPreviewUnavailable()); } diff --git a/lib/blocs/fs_entry_preview/fs_entry_preview_state.dart b/lib/blocs/fs_entry_preview/fs_entry_preview_state.dart index e6857ad1ae..506701d927 100644 --- a/lib/blocs/fs_entry_preview/fs_entry_preview_state.dart +++ b/lib/blocs/fs_entry_preview/fs_entry_preview_state.dart @@ -20,4 +20,40 @@ class FsEntryPreviewSuccess extends FsEntryPreviewState { List get props => [previewUrl]; } +class FsEntryPreviewImage extends FsEntryPreviewSuccess { + FsEntryPreviewImage({ + required String previewUrl, + }) : super(previewUrl: previewUrl); + + @override + List get props => [previewUrl]; +} + +class FsEntryPreviewAudio extends FsEntryPreviewSuccess { + FsEntryPreviewAudio({ + required String previewUrl, + }) : super(previewUrl: previewUrl); + + @override + List get props => [previewUrl]; +} + +class FsEntryPreviewVideo extends FsEntryPreviewSuccess { + FsEntryPreviewVideo({ + required String previewUrl, + }) : super(previewUrl: previewUrl); + + @override + List get props => [previewUrl]; +} + +class FsEntryPreviewText extends FsEntryPreviewSuccess { + FsEntryPreviewText({ + required String previewUrl, + }) : super(previewUrl: previewUrl); + + @override + List get props => [previewUrl]; +} + class FsEntryPreviewFailure extends FsEntryPreviewState {} diff --git a/lib/pages/drive_detail/components/fs_entry_preview_widget.dart b/lib/pages/drive_detail/components/fs_entry_preview_widget.dart new file mode 100644 index 0000000000..bcba13b0a2 --- /dev/null +++ b/lib/pages/drive_detail/components/fs_entry_preview_widget.dart @@ -0,0 +1,54 @@ +part of '../drive_detail_page.dart'; + +class FsEntryPreviewWidget extends StatefulWidget { + const FsEntryPreviewWidget({ + Key? key, + required this.state, + }) : super(key: key); + + final FsEntryPreviewSuccess state; + + @override + State createState() => _FsEntryPreviewWidgetState(); +} + +class _FsEntryPreviewWidgetState extends State { + VideoPlayerController? videoPlayerController; + ChewieController? chewieController; + + @override + void dispose() { + if (videoPlayerController != null) { + videoPlayerController?.dispose(); + chewieController?.dispose(); + } + super.dispose(); + } + + @override + Widget build(BuildContext context) { + switch (widget.state.runtimeType) { + case FsEntryPreviewImage: + return Image( + loadingBuilder: (context, child, loadingProgress) => + CircularProgressIndicator(), + image: NetworkImage(widget.state.previewUrl), + ); + case FsEntryPreviewVideo: + videoPlayerController = + VideoPlayerController.network(widget.state.previewUrl) + ..initialize(); + + chewieController = ChewieController( + videoPlayerController: videoPlayerController!, + autoPlay: true, + looping: true, + ); + return Chewie( + controller: chewieController!, + ); + default: + return Container(); + } + } +} diff --git a/lib/pages/drive_detail/components/fs_entry_side_sheet.dart b/lib/pages/drive_detail/components/fs_entry_side_sheet.dart index 6c8d01bb31..a4aee0bfc3 100644 --- a/lib/pages/drive_detail/components/fs_entry_side_sheet.dart +++ b/lib/pages/drive_detail/components/fs_entry_side_sheet.dart @@ -96,7 +96,7 @@ class _FsEntrySideSheetState extends State { children: [ if (hasPreview && previewState is FsEntryPreviewSuccess) - _buildPreviewTab(context, previewState), + FsEntryPreviewWidget( state: previewState), Column( crossAxisAlignment: CrossAxisAlignment.stretch, @@ -120,25 +120,6 @@ class _FsEntrySideSheetState extends State { ), ); - Widget _buildPreviewTab(BuildContext context, FsEntryPreviewSuccess state) => - Container( - child: LinkPreview( - enableAnimation: true, - onPreviewDataFetched: (data) { - setState(() { - datas = { - ...datas, - state.previewUrl: data, - }; - }); - }, - previewData: datas[state.previewUrl], - text: state.previewUrl, - hideImage: false, - width: 700, - ), - ); - Widget _buildInfoTable(BuildContext context, FsEntryInfoSuccess state) => DataTable( // Hide the data table header. @@ -577,6 +558,8 @@ class _FsEntrySideSheetState extends State { ); } + + void downloadOrPreviewRevision({ required String drivePrivacy, required BuildContext context, diff --git a/lib/pages/drive_detail/drive_detail_page.dart b/lib/pages/drive_detail/drive_detail_page.dart index 5783333f1c..733f7f7cdb 100644 --- a/lib/pages/drive_detail/drive_detail_page.dart +++ b/lib/pages/drive_detail/drive_detail_page.dart @@ -16,18 +16,19 @@ import 'package:ardrive/pages/drive_detail/components/drive_file_drop_zone.dart' import 'package:ardrive/services/config/app_config.dart'; import 'package:ardrive/theme/theme.dart'; import 'package:ardrive/utils/num_to_string_parsers.dart'; +import 'package:chewie/chewie.dart'; import 'package:filesize/filesize.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_gen/gen_l10n/app_localizations.dart'; -import 'package:flutter_link_previewer/flutter_link_previewer.dart'; import 'package:intersperse/intersperse.dart'; import 'package:moor/moor.dart' show OrderingMode; import 'package:responsive_builder/responsive_builder.dart'; import 'package:timeago/timeago.dart'; import 'package:url_launcher/url_launcher.dart'; +import 'package:video_player/video_player.dart'; import '../../../utils/app_localizations_wrapper.dart'; import 'components/custom_paginated_data_table.dart'; @@ -38,6 +39,7 @@ part 'components/drive_detail_data_list.dart'; part 'components/drive_detail_data_table.dart'; part 'components/drive_detail_data_table_source.dart'; part 'components/drive_detail_folder_empty_card.dart'; +part 'components/fs_entry_preview_widget.dart'; part 'components/fs_entry_side_sheet.dart'; class DriveDetailPage extends StatelessWidget { diff --git a/macos/Flutter/GeneratedPluginRegistrant.swift b/macos/Flutter/GeneratedPluginRegistrant.swift index 56009adc2a..7c2cd708a2 100644 --- a/macos/Flutter/GeneratedPluginRegistrant.swift +++ b/macos/Flutter/GeneratedPluginRegistrant.swift @@ -9,10 +9,12 @@ import file_selector_macos import package_info_plus_macos import path_provider_macos import url_launcher_macos +import wakelock_macos func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { FileSelectorPlugin.register(with: registry.registrar(forPlugin: "FileSelectorPlugin")) FLTPackageInfoPlusPlugin.register(with: registry.registrar(forPlugin: "FLTPackageInfoPlusPlugin")) PathProviderPlugin.register(with: registry.registrar(forPlugin: "PathProviderPlugin")) UrlLauncherPlugin.register(with: registry.registrar(forPlugin: "UrlLauncherPlugin")) + WakelockMacosPlugin.register(with: registry.registrar(forPlugin: "WakelockMacosPlugin")) } diff --git a/pubspec.lock b/pubspec.lock index 72db26e77d..d36a959f5e 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -164,6 +164,13 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "2.0.1" + chewie: + dependency: "direct main" + description: + name: chewie + url: "https://pub.dartlang.org" + source: hosted + version: "1.3.2" cli_util: dependency: transitive description: @@ -241,6 +248,13 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "5.0.0" + cupertino_icons: + dependency: transitive + description: + name: cupertino_icons + url: "https://pub.dartlang.org" + source: hosted + version: "1.0.4" dart_style: dependency: transitive description: @@ -753,7 +767,7 @@ packages: name: plugin_platform_interface url: "https://pub.dartlang.org" source: hosted - version: "2.0.2" + version: "2.1.2" pointycastle: dependency: transitive description: @@ -859,6 +873,13 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "1.0.1" + shimmer: + dependency: "direct main" + description: + name: shimmer + url: "https://pub.dartlang.org" + source: hosted + version: "2.0.0" sky_engine: dependency: transitive description: flutter @@ -1060,6 +1081,48 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "2.1.1" + very_good_analysis: + dependency: transitive + description: + name: very_good_analysis + url: "https://pub.dartlang.org" + source: hosted + version: "2.4.0" + video_player: + dependency: "direct main" + description: + name: video_player + url: "https://pub.dartlang.org" + source: hosted + version: "2.3.0" + video_player_android: + dependency: transitive + description: + name: video_player_android + url: "https://pub.dartlang.org" + source: hosted + version: "2.3.2" + video_player_avfoundation: + dependency: transitive + description: + name: video_player_avfoundation + url: "https://pub.dartlang.org" + source: hosted + version: "2.3.2" + video_player_platform_interface: + dependency: transitive + description: + name: video_player_platform_interface + url: "https://pub.dartlang.org" + source: hosted + version: "5.1.2" + video_player_web: + dependency: transitive + description: + name: video_player_web + url: "https://pub.dartlang.org" + source: hosted + version: "2.0.8" vm_service: dependency: transitive description: @@ -1067,6 +1130,41 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "7.3.0" + wakelock: + dependency: transitive + description: + name: wakelock + url: "https://pub.dartlang.org" + source: hosted + version: "0.6.1+2" + wakelock_macos: + dependency: transitive + description: + name: wakelock_macos + url: "https://pub.dartlang.org" + source: hosted + version: "0.4.0" + wakelock_platform_interface: + dependency: transitive + description: + name: wakelock_platform_interface + url: "https://pub.dartlang.org" + source: hosted + version: "0.3.0" + wakelock_web: + dependency: transitive + description: + name: wakelock_web + url: "https://pub.dartlang.org" + source: hosted + version: "0.4.0" + wakelock_windows: + dependency: transitive + description: + name: wakelock_windows + url: "https://pub.dartlang.org" + source: hosted + version: "0.2.0" watcher: dependency: transitive description: @@ -1118,4 +1216,4 @@ packages: version: "3.1.0" sdks: dart: ">=2.15.0 <3.0.0" - flutter: ">=2.2.0" + flutter: ">=2.8.0" diff --git a/pubspec.yaml b/pubspec.yaml index d8bc0252ed..b127b3ef91 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -57,6 +57,9 @@ dependencies: csv: 5.0.0 stash_memory: 4.0.1 flutter_link_previewer: ^2.5.2 + shimmer: ^2.0.0 + chewie: ^1.3.2 + video_player: ^2.3.0 dev_dependencies: flutter_test: From 9efea3e67153f8e26460786f6f769894080fbe59 Mon Sep 17 00:00:00 2001 From: Javed Hussein Date: Mon, 25 Apr 2022 11:45:07 -0400 Subject: [PATCH 03/16] refactor: use ExtendedImage package for image preview PE-1368 --- .../components/fs_entry_preview_widget.dart | 8 +++---- lib/pages/drive_detail/drive_detail_page.dart | 1 + pubspec.lock | 21 +++++++++++++++++++ pubspec.yaml | 1 + 4 files changed, 27 insertions(+), 4 deletions(-) diff --git a/lib/pages/drive_detail/components/fs_entry_preview_widget.dart b/lib/pages/drive_detail/components/fs_entry_preview_widget.dart index bcba13b0a2..9f36a7256f 100644 --- a/lib/pages/drive_detail/components/fs_entry_preview_widget.dart +++ b/lib/pages/drive_detail/components/fs_entry_preview_widget.dart @@ -29,10 +29,10 @@ class _FsEntryPreviewWidgetState extends State { Widget build(BuildContext context) { switch (widget.state.runtimeType) { case FsEntryPreviewImage: - return Image( - loadingBuilder: (context, child, loadingProgress) => - CircularProgressIndicator(), - image: NetworkImage(widget.state.previewUrl), + return ExtendedImage.network( + widget.state.previewUrl, + fit: BoxFit.fitWidth, + cache: true, ); case FsEntryPreviewVideo: videoPlayerController = diff --git a/lib/pages/drive_detail/drive_detail_page.dart b/lib/pages/drive_detail/drive_detail_page.dart index 733f7f7cdb..2dbac4b04c 100644 --- a/lib/pages/drive_detail/drive_detail_page.dart +++ b/lib/pages/drive_detail/drive_detail_page.dart @@ -17,6 +17,7 @@ import 'package:ardrive/services/config/app_config.dart'; import 'package:ardrive/theme/theme.dart'; import 'package:ardrive/utils/num_to_string_parsers.dart'; import 'package:chewie/chewie.dart'; +import 'package:extended_image/extended_image.dart'; import 'package:filesize/filesize.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; diff --git a/pubspec.lock b/pubspec.lock index d36a959f5e..e96c3cc160 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -283,6 +283,20 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "2.2.2" + extended_image: + dependency: "direct main" + description: + name: extended_image + url: "https://pub.dartlang.org" + source: hosted + version: "6.0.2+1" + extended_image_library: + dependency: transitive + description: + name: extended_image_library + url: "https://pub.dartlang.org" + source: hosted + version: "3.1.4" fake_async: dependency: transitive description: @@ -523,6 +537,13 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "1.5.1" + http_client_helper: + dependency: transitive + description: + name: http_client_helper + url: "https://pub.dartlang.org" + source: hosted + version: "2.0.2" http_multi_server: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index b127b3ef91..1629a48550 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -60,6 +60,7 @@ dependencies: shimmer: ^2.0.0 chewie: ^1.3.2 video_player: ^2.3.0 + extended_image: ^6.0.2+1 dev_dependencies: flutter_test: From dadff2f2020022a49e6a61a5777c4071c40d68ed Mon Sep 17 00:00:00 2001 From: Javed Hussein Date: Mon, 25 Apr 2022 11:52:48 -0400 Subject: [PATCH 04/16] fix: handle cases where contentType is null PE-1368 --- lib/blocs/fs_entry_preview/fs_entry_preview_cubit.dart | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/lib/blocs/fs_entry_preview/fs_entry_preview_cubit.dart b/lib/blocs/fs_entry_preview/fs_entry_preview_cubit.dart index b05648fdb3..e96d3842ff 100644 --- a/lib/blocs/fs_entry_preview/fs_entry_preview_cubit.dart +++ b/lib/blocs/fs_entry_preview/fs_entry_preview_cubit.dart @@ -5,6 +5,7 @@ import 'package:ardrive/models/models.dart'; import 'package:ardrive/services/config/app_config.dart'; import 'package:bloc/bloc.dart'; import 'package:equatable/equatable.dart'; +import 'package:mime/mime.dart'; part 'fs_entry_preview_state.dart'; @@ -37,10 +38,12 @@ class FsEntryPreviewCubit extends Cubit { .watchSingle() .listen((file) { if (file.size <= previewMaxFileSize) { - final contentType = file.dataContentType!.split('/').first; + final contentType = + file.dataContentType ?? lookupMimeType(file.name); + final previewType = contentType?.split('/').first; final previewUrl = '${_config.defaultArweaveGatewayUrl}/${file.dataTxId}'; - switch (contentType) { + switch (previewType) { case 'image': emit(FsEntryPreviewImage(previewUrl: previewUrl)); break; From 3bee105c93e0e30f6b7c8a71b00dd8dc759048ba Mon Sep 17 00:00:00 2001 From: Javed Hussein Date: Wed, 27 Apr 2022 09:25:45 -0400 Subject: [PATCH 05/16] feat: add private image previews PE-1368 --- .../fs_entry_preview_cubit.dart | 101 +++++++++++-- .../fs_entry_preview_state.dart | 15 ++ .../components/fs_entry_preview_widget.dart | 33 ++--- .../components/fs_entry_side_sheet.dart | 138 ++++++++--------- lib/pages/drive_detail/drive_detail_page.dart | 5 +- macos/Flutter/GeneratedPluginRegistrant.swift | 2 - pubspec.lock | 140 ------------------ pubspec.yaml | 4 - 8 files changed, 176 insertions(+), 262 deletions(-) diff --git a/lib/blocs/fs_entry_preview/fs_entry_preview_cubit.dart b/lib/blocs/fs_entry_preview/fs_entry_preview_cubit.dart index e96d3842ff..1fdbe30034 100644 --- a/lib/blocs/fs_entry_preview/fs_entry_preview_cubit.dart +++ b/lib/blocs/fs_entry_preview/fs_entry_preview_cubit.dart @@ -1,11 +1,17 @@ import 'dart:async'; import 'package:ardrive/blocs/drive_detail/selected_item.dart'; +import 'package:ardrive/blocs/profile/profile_cubit.dart'; +import 'package:ardrive/entities/entities.dart'; import 'package:ardrive/models/models.dart'; import 'package:ardrive/services/config/app_config.dart'; +import 'package:ardrive/services/services.dart'; import 'package:bloc/bloc.dart'; +import 'package:cryptography/cryptography.dart'; import 'package:equatable/equatable.dart'; +import 'package:http/http.dart' as http; import 'package:mime/mime.dart'; +import 'package:moor/moor.dart'; part 'fs_entry_preview_state.dart'; @@ -15,10 +21,12 @@ class FsEntryPreviewCubit extends Cubit { final DriveDao _driveDao; final AppConfig _config; + final ArweaveService _arweave; + final ProfileCubit _profileCubit; StreamSubscription? _entrySubscription; - final previewMaxFileSize = 1024 * 1024 * 10; + final previewMaxFileSize = 1024 * 1024 * 100; final allowedPreviewContentTypes = []; FsEntryPreviewCubit({ @@ -26,9 +34,17 @@ class FsEntryPreviewCubit extends Cubit { this.maybeSelectedItem, required DriveDao driveDao, required AppConfig config, + required ArweaveService arweave, + required ProfileCubit profileCubit, }) : _driveDao = driveDao, _config = config, + _arweave = arweave, + _profileCubit = profileCubit, super(FsEntryPreviewInitial()) { + preview(); + } + + Future preview() async { final selectedItem = maybeSelectedItem; if (selectedItem != null) { switch (selectedItem.runtimeType) { @@ -45,18 +61,23 @@ class FsEntryPreviewCubit extends Cubit { '${_config.defaultArweaveGatewayUrl}/${file.dataTxId}'; switch (previewType) { case 'image': - emit(FsEntryPreviewImage(previewUrl: previewUrl)); - break; - case 'audio': - emit(FsEntryPreviewAudio(previewUrl: previewUrl)); - break; - case 'video': - emit(FsEntryPreviewVideo(previewUrl: previewUrl)); - break; - case 'text': - emit(FsEntryPreviewText(previewUrl: previewUrl)); + emitImagePreview(file.id, previewUrl); + break; + + ///TODO Enable more previews in the future after dealing + /// with state and widget disposal + // case 'audio': + // emit(FsEntryPreviewAudio(previewUrl: previewUrl)); + // break; + // case 'video': + // emit(FsEntryPreviewVideo(previewUrl: previewUrl)); + // break; + // case 'text': + // emit(FsEntryPreviewText(previewUrl: previewUrl)); + // break; default: + emit(FsEntryPreviewUnavailable()); } } else { emit(FsEntryPreviewUnavailable()); @@ -71,6 +92,64 @@ class FsEntryPreviewCubit extends Cubit { } } + Future emitImagePreview(String fileId, String dataUrl) async { + try { + final drive = await _driveDao.driveById(driveId: driveId).getSingle(); + final file = await _driveDao + .fileById(driveId: driveId, fileId: fileId) + .getSingle(); + + late Uint8List dataBytes; + + switch (drive.privacy) { + case DrivePrivacy.public: + emit(FsEntryPreviewImage(previewUrl: dataUrl)); + break; + case DrivePrivacy.private: + emit(FsEntryPreviewLoading()); + final profile = _profileCubit.state; + SecretKey? driveKey; + + if (profile is ProfileLoggedIn) { + driveKey = await _driveDao.getDriveKey( + drive.id, + profile.cipherKey, + ); + } else { + driveKey = await _driveDao.getDriveKeyFromMemory(driveId); + } + + if (driveKey == null) { + throw StateError('Drive Key not found'); + } + + final fileKey = await _driveDao.getFileKey(fileId, driveKey); + final dataTx = await (_arweave.getTransactionDetails(file.dataTxId)); + + if (dataTx != null) { + final dataRes = await http.get(Uri.parse(dataUrl)); + dataBytes = await decryptTransactionData( + dataTx, + dataRes.bodyBytes, + fileKey, + ); + } + emit( + FsEntryPreviewPrivateImage( + imageBytes: dataBytes, + previewUrl: dataUrl, + ), + ); + break; + + default: + emit(FsEntryPreviewFailure()); + } + } catch (err) { + addError(err); + } + } + @override void onError(Object error, StackTrace stackTrace) { emit(FsEntryPreviewFailure()); diff --git a/lib/blocs/fs_entry_preview/fs_entry_preview_state.dart b/lib/blocs/fs_entry_preview/fs_entry_preview_state.dart index 506701d927..a2c43e7844 100644 --- a/lib/blocs/fs_entry_preview/fs_entry_preview_state.dart +++ b/lib/blocs/fs_entry_preview/fs_entry_preview_state.dart @@ -20,6 +20,10 @@ class FsEntryPreviewSuccess extends FsEntryPreviewState { List get props => [previewUrl]; } +class FsEntryPreviewLoading extends FsEntryPreviewSuccess { + FsEntryPreviewLoading() : super(previewUrl: ''); +} + class FsEntryPreviewImage extends FsEntryPreviewSuccess { FsEntryPreviewImage({ required String previewUrl, @@ -29,6 +33,17 @@ class FsEntryPreviewImage extends FsEntryPreviewSuccess { List get props => [previewUrl]; } +class FsEntryPreviewPrivateImage extends FsEntryPreviewImage { + final Uint8List imageBytes; + FsEntryPreviewPrivateImage({ + required this.imageBytes, + required String previewUrl, + }) : super(previewUrl: previewUrl); + + @override + List get props => [imageBytes, previewUrl]; +} + class FsEntryPreviewAudio extends FsEntryPreviewSuccess { FsEntryPreviewAudio({ required String previewUrl, diff --git a/lib/pages/drive_detail/components/fs_entry_preview_widget.dart b/lib/pages/drive_detail/components/fs_entry_preview_widget.dart index 9f36a7256f..f38120164e 100644 --- a/lib/pages/drive_detail/components/fs_entry_preview_widget.dart +++ b/lib/pages/drive_detail/components/fs_entry_preview_widget.dart @@ -13,40 +13,27 @@ class FsEntryPreviewWidget extends StatefulWidget { } class _FsEntryPreviewWidgetState extends State { - VideoPlayerController? videoPlayerController; - ChewieController? chewieController; - - @override - void dispose() { - if (videoPlayerController != null) { - videoPlayerController?.dispose(); - chewieController?.dispose(); - } - super.dispose(); - } - @override Widget build(BuildContext context) { switch (widget.state.runtimeType) { + case FsEntryPreviewLoading: + return Center( + child: SizedBox( + height: 24, width: 24, child: CircularProgressIndicator())); + case FsEntryPreviewImage: return ExtendedImage.network( widget.state.previewUrl, fit: BoxFit.fitWidth, cache: true, ); - case FsEntryPreviewVideo: - videoPlayerController = - VideoPlayerController.network(widget.state.previewUrl) - ..initialize(); - chewieController = ChewieController( - videoPlayerController: videoPlayerController!, - autoPlay: true, - looping: true, - ); - return Chewie( - controller: chewieController!, + case FsEntryPreviewPrivateImage: + return ExtendedImage.memory( + (widget.state as FsEntryPreviewPrivateImage).imageBytes, + fit: BoxFit.fitWidth, ); + default: return Container(); } diff --git a/lib/pages/drive_detail/components/fs_entry_side_sheet.dart b/lib/pages/drive_detail/components/fs_entry_side_sheet.dart index a4aee0bfc3..aaa0f27d1b 100644 --- a/lib/pages/drive_detail/components/fs_entry_side_sheet.dart +++ b/lib/pages/drive_detail/components/fs_entry_side_sheet.dart @@ -15,18 +15,16 @@ class FsEntrySideSheet extends StatefulWidget { } class _FsEntrySideSheetState extends State { - Map datas = {}; - var tabCount = 2; - bool hasPreview = false; @override Widget build(BuildContext context) => Drawer( elevation: 1, child: MultiBlocProvider( // Specify a key to ensure a new cubit is provided when the folder/file id changes. - key: ValueKey( - widget.driveId + - '${widget.maybeSelectedItem?.id ?? Random().nextInt(1000).toString()}', - ), + key: widget.maybeSelectedItem?.id != null + ? ValueKey( + widget.driveId + '${widget.maybeSelectedItem?.id}', + ) + : UniqueKey(), providers: [ BlocProvider( create: (context) => FsEntryInfoCubit( @@ -40,82 +38,68 @@ class _FsEntrySideSheetState extends State { driveId: widget.driveId, maybeSelectedItem: widget.maybeSelectedItem, driveDao: context.read(), + profileCubit: context.read(), + arweave: context.read(), config: context.read()), ) ], - child: BlocListener( - listener: (context, previewState) { - if (previewState is FsEntryPreviewSuccess) { - setState(() { - tabCount = 3; - hasPreview = true; - }); - } else { - setState(() { - tabCount = 2; - hasPreview = false; - }); - } - }, - child: DefaultTabController( - length: tabCount, - child: BlocBuilder( - builder: (context, previewState) { - return BlocBuilder( - builder: (context, state) => state is FsEntryInfoSuccess - ? Column( - mainAxisSize: MainAxisSize.min, - crossAxisAlignment: CrossAxisAlignment.stretch, - children: [ - const SizedBox(height: 8), - ListTile( - title: Text(state.name), - trailing: IconButton( - icon: const Icon(Icons.close), - onPressed: () => context - .read() - .toggleSelectedItemDetails(), - ), + child: BlocBuilder( + builder: (context, previewState) { + return DefaultTabController( + length: previewState is FsEntryPreviewSuccess ? 3 : 2, + child: BlocBuilder( + builder: (context, state) => state is FsEntryInfoSuccess + ? Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + const SizedBox(height: 8), + ListTile( + title: Text(state.name), + trailing: IconButton( + icon: const Icon(Icons.close), + onPressed: () => context + .read() + .toggleSelectedItemDetails(), ), - TabBar( - tabs: [ - if (hasPreview) - Tab( - text: 'Preview', - ), - Tab( - text: appLocalizationsOf(context) - .itemDetailsEmphasized), + ), + TabBar( + tabs: [ + if (previewState is FsEntryPreviewSuccess) Tab( - text: appLocalizationsOf(context) - .itemActivityEmphasized), + text: 'Preview', + ), + Tab( + text: appLocalizationsOf(context) + .itemDetailsEmphasized), + Tab( + text: appLocalizationsOf(context) + .itemActivityEmphasized), + ], + ), + Expanded( + child: TabBarView( + children: [ + if (previewState is FsEntryPreviewSuccess) + FsEntryPreviewWidget(state: previewState), + Column( + crossAxisAlignment: + CrossAxisAlignment.stretch, + children: [ + _buildInfoTable(context, state), + _buildTxTable(context, state), + ], + ), + _buildActivityTab(context, state), ], ), - Expanded( - child: TabBarView( - children: [ - if (hasPreview && - previewState is FsEntryPreviewSuccess) - FsEntryPreviewWidget( state: previewState), - Column( - crossAxisAlignment: - CrossAxisAlignment.stretch, - children: [ - _buildInfoTable(context, state), - _buildTxTable(context, state), - ], - ), - _buildActivityTab(context, state), - ], - ), - ) - ], - ) - : const SizedBox(), - ); - }, - ), - ), + ) + ], + ) + : const SizedBox(), + ), + ); + }, ), ), ); @@ -558,8 +542,6 @@ class _FsEntrySideSheetState extends State { ); } - - void downloadOrPreviewRevision({ required String drivePrivacy, required BuildContext context, diff --git a/lib/pages/drive_detail/drive_detail_page.dart b/lib/pages/drive_detail/drive_detail_page.dart index 2dbac4b04c..16edf6bbe7 100644 --- a/lib/pages/drive_detail/drive_detail_page.dart +++ b/lib/pages/drive_detail/drive_detail_page.dart @@ -1,5 +1,3 @@ -import 'dart:math'; - import 'package:ardrive/blocs/blocs.dart'; import 'package:ardrive/blocs/drive_detail/selected_item.dart'; import 'package:ardrive/blocs/fs_entry_preview/fs_entry_preview_cubit.dart'; @@ -13,10 +11,10 @@ import 'package:ardrive/l11n/l11n.dart'; import 'package:ardrive/models/models.dart'; import 'package:ardrive/pages/congestion_warning_wrapper.dart'; import 'package:ardrive/pages/drive_detail/components/drive_file_drop_zone.dart'; +import 'package:ardrive/services/arweave/arweave.dart'; import 'package:ardrive/services/config/app_config.dart'; import 'package:ardrive/theme/theme.dart'; import 'package:ardrive/utils/num_to_string_parsers.dart'; -import 'package:chewie/chewie.dart'; import 'package:extended_image/extended_image.dart'; import 'package:filesize/filesize.dart'; import 'package:flutter/foundation.dart'; @@ -29,7 +27,6 @@ import 'package:moor/moor.dart' show OrderingMode; import 'package:responsive_builder/responsive_builder.dart'; import 'package:timeago/timeago.dart'; import 'package:url_launcher/url_launcher.dart'; -import 'package:video_player/video_player.dart'; import '../../../utils/app_localizations_wrapper.dart'; import 'components/custom_paginated_data_table.dart'; diff --git a/macos/Flutter/GeneratedPluginRegistrant.swift b/macos/Flutter/GeneratedPluginRegistrant.swift index 7c2cd708a2..56009adc2a 100644 --- a/macos/Flutter/GeneratedPluginRegistrant.swift +++ b/macos/Flutter/GeneratedPluginRegistrant.swift @@ -9,12 +9,10 @@ import file_selector_macos import package_info_plus_macos import path_provider_macos import url_launcher_macos -import wakelock_macos func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { FileSelectorPlugin.register(with: registry.registrar(forPlugin: "FileSelectorPlugin")) FLTPackageInfoPlusPlugin.register(with: registry.registrar(forPlugin: "FLTPackageInfoPlusPlugin")) PathProviderPlugin.register(with: registry.registrar(forPlugin: "PathProviderPlugin")) UrlLauncherPlugin.register(with: registry.registrar(forPlugin: "UrlLauncherPlugin")) - WakelockMacosPlugin.register(with: registry.registrar(forPlugin: "WakelockMacosPlugin")) } diff --git a/pubspec.lock b/pubspec.lock index e96c3cc160..76f54a81f9 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -164,13 +164,6 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "2.0.1" - chewie: - dependency: "direct main" - description: - name: chewie - url: "https://pub.dartlang.org" - source: hosted - version: "1.3.2" cli_util: dependency: transitive description: @@ -234,13 +227,6 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "2.0.2" - csslib: - dependency: transitive - description: - name: csslib - url: "https://pub.dartlang.org" - source: hosted - version: "0.17.1" csv: dependency: "direct main" description: @@ -248,13 +234,6 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "5.0.0" - cupertino_icons: - dependency: transitive - description: - name: cupertino_icons - url: "https://pub.dartlang.org" - source: hosted - version: "1.0.4" dart_style: dependency: transitive description: @@ -372,13 +351,6 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "8.0.1" - flutter_chat_types: - dependency: transitive - description: - name: flutter_chat_types - url: "https://pub.dartlang.org" - source: hosted - version: "3.1.4" flutter_driver: dependency: "direct dev" description: flutter @@ -405,20 +377,6 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "3.0.7" - flutter_link_previewer: - dependency: "direct main" - description: - name: flutter_link_previewer - url: "https://pub.dartlang.org" - source: hosted - version: "2.5.2" - flutter_linkify: - dependency: transitive - description: - name: flutter_linkify - url: "https://pub.dartlang.org" - source: hosted - version: "5.0.2" flutter_localizations: dependency: "direct main" description: flutter @@ -516,13 +474,6 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "2.1.0" - html: - dependency: transitive - description: - name: html - url: "https://pub.dartlang.org" - source: hosted - version: "0.15.0" http: dependency: transitive description: @@ -607,13 +558,6 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "0.1.0" - linkify: - dependency: transitive - description: - name: linkify - url: "https://pub.dartlang.org" - source: hosted - version: "4.1.0" logging: dependency: transitive description: @@ -894,13 +838,6 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "1.0.1" - shimmer: - dependency: "direct main" - description: - name: shimmer - url: "https://pub.dartlang.org" - source: hosted - version: "2.0.0" sky_engine: dependency: transitive description: flutter @@ -1102,48 +1039,6 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "2.1.1" - very_good_analysis: - dependency: transitive - description: - name: very_good_analysis - url: "https://pub.dartlang.org" - source: hosted - version: "2.4.0" - video_player: - dependency: "direct main" - description: - name: video_player - url: "https://pub.dartlang.org" - source: hosted - version: "2.3.0" - video_player_android: - dependency: transitive - description: - name: video_player_android - url: "https://pub.dartlang.org" - source: hosted - version: "2.3.2" - video_player_avfoundation: - dependency: transitive - description: - name: video_player_avfoundation - url: "https://pub.dartlang.org" - source: hosted - version: "2.3.2" - video_player_platform_interface: - dependency: transitive - description: - name: video_player_platform_interface - url: "https://pub.dartlang.org" - source: hosted - version: "5.1.2" - video_player_web: - dependency: transitive - description: - name: video_player_web - url: "https://pub.dartlang.org" - source: hosted - version: "2.0.8" vm_service: dependency: transitive description: @@ -1151,41 +1046,6 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "7.3.0" - wakelock: - dependency: transitive - description: - name: wakelock - url: "https://pub.dartlang.org" - source: hosted - version: "0.6.1+2" - wakelock_macos: - dependency: transitive - description: - name: wakelock_macos - url: "https://pub.dartlang.org" - source: hosted - version: "0.4.0" - wakelock_platform_interface: - dependency: transitive - description: - name: wakelock_platform_interface - url: "https://pub.dartlang.org" - source: hosted - version: "0.3.0" - wakelock_web: - dependency: transitive - description: - name: wakelock_web - url: "https://pub.dartlang.org" - source: hosted - version: "0.4.0" - wakelock_windows: - dependency: transitive - description: - name: wakelock_windows - url: "https://pub.dartlang.org" - source: hosted - version: "0.2.0" watcher: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index 1629a48550..89fdaf122e 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -56,10 +56,6 @@ dependencies: collection: ^1.15.0-nullsafety.4 csv: 5.0.0 stash_memory: 4.0.1 - flutter_link_previewer: ^2.5.2 - shimmer: ^2.0.0 - chewie: ^1.3.2 - video_player: ^2.3.0 extended_image: ^6.0.2+1 dev_dependencies: From 88541db89e8180f0cd93953d4b89364f81bce1f2 Mon Sep 17 00:00:00 2001 From: Javed Hussein Date: Wed, 27 Apr 2022 10:26:20 -0400 Subject: [PATCH 06/16] refactor: refactor states OE-1368 --- .../fs_entry_preview_cubit.dart | 59 +++++++++++-------- .../fs_entry_preview_state.dart | 12 +--- lib/models/daos/drive_dao/drive_dao.dart | 14 +++++ .../components/fs_entry_preview_widget.dart | 19 +++--- .../components/fs_entry_side_sheet.dart | 13 ++-- lib/pages/drive_detail/drive_detail_page.dart | 1 - pubspec.lock | 23 +------- pubspec.yaml | 1 - 8 files changed, 65 insertions(+), 77 deletions(-) diff --git a/lib/blocs/fs_entry_preview/fs_entry_preview_cubit.dart b/lib/blocs/fs_entry_preview/fs_entry_preview_cubit.dart index 1fdbe30034..ecbba8c517 100644 --- a/lib/blocs/fs_entry_preview/fs_entry_preview_cubit.dart +++ b/lib/blocs/fs_entry_preview/fs_entry_preview_cubit.dart @@ -4,7 +4,6 @@ import 'package:ardrive/blocs/drive_detail/selected_item.dart'; import 'package:ardrive/blocs/profile/profile_cubit.dart'; import 'package:ardrive/entities/entities.dart'; import 'package:ardrive/models/models.dart'; -import 'package:ardrive/services/config/app_config.dart'; import 'package:ardrive/services/services.dart'; import 'package:bloc/bloc.dart'; import 'package:cryptography/cryptography.dart'; @@ -61,12 +60,12 @@ class FsEntryPreviewCubit extends Cubit { '${_config.defaultArweaveGatewayUrl}/${file.dataTxId}'; switch (previewType) { case 'image': - emitImagePreview(file.id, previewUrl); - + emitImagePreview(file, previewUrl); break; - ///TODO Enable more previews in the future after dealing + /// Enable more previews in the future after dealing /// with state and widget disposal + // case 'audio': // emit(FsEntryPreviewAudio(previewUrl: previewUrl)); // break; @@ -92,21 +91,37 @@ class FsEntryPreviewCubit extends Cubit { } } - Future emitImagePreview(String fileId, String dataUrl) async { + Future emitImagePreview(FileEntry file, String dataUrl) async { try { - final drive = await _driveDao.driveById(driveId: driveId).getSingle(); - final file = await _driveDao - .fileById(driveId: driveId, fileId: fileId) - .getSingle(); + emit(FsEntryPreviewLoading()); + + final dataTx = await _arweave.getTransactionDetails(file.dataTxId); + if (dataTx == null) { + emit(FsEntryPreviewFailure()); + return; + } late Uint8List dataBytes; + final cachedBytes = await _driveDao.getPreviewDataFromMemory(dataTx.id); + if (cachedBytes == null) { + final dataRes = await http.get(Uri.parse(dataUrl)); + dataBytes = dataRes.bodyBytes; + await _driveDao.putPreviewDataInMemory( + dataTxId: dataTx.id, + bytes: dataBytes, + ); + } else { + dataBytes = cachedBytes; + } + final drive = await _driveDao.driveById(driveId: driveId).getSingle(); switch (drive.privacy) { case DrivePrivacy.public: - emit(FsEntryPreviewImage(previewUrl: dataUrl)); + emit( + FsEntryPreviewImage(imageBytes: dataBytes, previewUrl: dataUrl), + ); break; case DrivePrivacy.private: - emit(FsEntryPreviewLoading()); final profile = _profileCubit.state; SecretKey? driveKey; @@ -123,22 +138,14 @@ class FsEntryPreviewCubit extends Cubit { throw StateError('Drive Key not found'); } - final fileKey = await _driveDao.getFileKey(fileId, driveKey); - final dataTx = await (_arweave.getTransactionDetails(file.dataTxId)); - - if (dataTx != null) { - final dataRes = await http.get(Uri.parse(dataUrl)); - dataBytes = await decryptTransactionData( - dataTx, - dataRes.bodyBytes, - fileKey, - ); - } + final fileKey = await _driveDao.getFileKey(file.id, driveKey); + final decodedBytes = await decryptTransactionData( + dataTx, + dataBytes, + fileKey, + ); emit( - FsEntryPreviewPrivateImage( - imageBytes: dataBytes, - previewUrl: dataUrl, - ), + FsEntryPreviewImage(imageBytes: decodedBytes, previewUrl: dataUrl), ); break; diff --git a/lib/blocs/fs_entry_preview/fs_entry_preview_state.dart b/lib/blocs/fs_entry_preview/fs_entry_preview_state.dart index a2c43e7844..65470e0bb7 100644 --- a/lib/blocs/fs_entry_preview/fs_entry_preview_state.dart +++ b/lib/blocs/fs_entry_preview/fs_entry_preview_state.dart @@ -25,17 +25,9 @@ class FsEntryPreviewLoading extends FsEntryPreviewSuccess { } class FsEntryPreviewImage extends FsEntryPreviewSuccess { - FsEntryPreviewImage({ - required String previewUrl, - }) : super(previewUrl: previewUrl); - - @override - List get props => [previewUrl]; -} - -class FsEntryPreviewPrivateImage extends FsEntryPreviewImage { final Uint8List imageBytes; - FsEntryPreviewPrivateImage({ + + FsEntryPreviewImage({ required this.imageBytes, required String previewUrl, }) : super(previewUrl: previewUrl); diff --git a/lib/models/daos/drive_dao/drive_dao.dart b/lib/models/daos/drive_dao/drive_dao.dart index 32488d19ce..2d0081edb5 100644 --- a/lib/models/daos/drive_dao/drive_dao.dart +++ b/lib/models/daos/drive_dao/drive_dao.dart @@ -27,12 +27,15 @@ class DriveDao extends DatabaseAccessor with _$DriveDaoMixin { late Vault _driveKeyVault; + late Vault _previewVault; + DriveDao(Database db) : super(db) { // Creates a store final store = newMemoryVaultStore(); // Creates a vault from the previously created store _driveKeyVault = store.vault(name: 'driveKeyVault'); + _previewVault = store.vault(name: 'previewVault'); } Future deleteSharedPrivateDrives(String? owner) async { @@ -67,6 +70,17 @@ class DriveDao extends DatabaseAccessor with _$DriveDaoMixin { return await _driveKeyVault.put(driveID, driveKey); } + Future getPreviewDataFromMemory(TxID dataTxId) async { + return await _previewVault.get(dataTxId); + } + + Future putPreviewDataInMemory({ + required TxID dataTxId, + required Uint8List bytes, + }) async { + return await _previewVault.put(dataTxId, bytes); + } + /// Creates a drive with its accompanying root folder. Future createDrive({ required String name, diff --git a/lib/pages/drive_detail/components/fs_entry_preview_widget.dart b/lib/pages/drive_detail/components/fs_entry_preview_widget.dart index f38120164e..8da765d084 100644 --- a/lib/pages/drive_detail/components/fs_entry_preview_widget.dart +++ b/lib/pages/drive_detail/components/fs_entry_preview_widget.dart @@ -18,19 +18,16 @@ class _FsEntryPreviewWidgetState extends State { switch (widget.state.runtimeType) { case FsEntryPreviewLoading: return Center( - child: SizedBox( - height: 24, width: 24, child: CircularProgressIndicator())); - - case FsEntryPreviewImage: - return ExtendedImage.network( - widget.state.previewUrl, - fit: BoxFit.fitWidth, - cache: true, + child: SizedBox( + height: 24, + width: 24, + child: CircularProgressIndicator(), + ), ); - case FsEntryPreviewPrivateImage: - return ExtendedImage.memory( - (widget.state as FsEntryPreviewPrivateImage).imageBytes, + case FsEntryPreviewImage: + return Image.memory( + (widget.state as FsEntryPreviewImage).imageBytes, fit: BoxFit.fitWidth, ); diff --git a/lib/pages/drive_detail/components/fs_entry_side_sheet.dart b/lib/pages/drive_detail/components/fs_entry_side_sheet.dart index aaa0f27d1b..c22ca52c44 100644 --- a/lib/pages/drive_detail/components/fs_entry_side_sheet.dart +++ b/lib/pages/drive_detail/components/fs_entry_side_sheet.dart @@ -35,12 +35,13 @@ class _FsEntrySideSheetState extends State { ), BlocProvider( create: (context) => FsEntryPreviewCubit( - driveId: widget.driveId, - maybeSelectedItem: widget.maybeSelectedItem, - driveDao: context.read(), - profileCubit: context.read(), - arweave: context.read(), - config: context.read()), + driveId: widget.driveId, + maybeSelectedItem: widget.maybeSelectedItem, + driveDao: context.read(), + profileCubit: context.read(), + arweave: context.read(), + config: context.read(), + ), ) ], child: BlocBuilder( diff --git a/lib/pages/drive_detail/drive_detail_page.dart b/lib/pages/drive_detail/drive_detail_page.dart index 16edf6bbe7..d1f625bb6d 100644 --- a/lib/pages/drive_detail/drive_detail_page.dart +++ b/lib/pages/drive_detail/drive_detail_page.dart @@ -15,7 +15,6 @@ import 'package:ardrive/services/arweave/arweave.dart'; import 'package:ardrive/services/config/app_config.dart'; import 'package:ardrive/theme/theme.dart'; import 'package:ardrive/utils/num_to_string_parsers.dart'; -import 'package:extended_image/extended_image.dart'; import 'package:filesize/filesize.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; diff --git a/pubspec.lock b/pubspec.lock index 76f54a81f9..59ea3af24f 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -262,20 +262,6 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "2.2.2" - extended_image: - dependency: "direct main" - description: - name: extended_image - url: "https://pub.dartlang.org" - source: hosted - version: "6.0.2+1" - extended_image_library: - dependency: transitive - description: - name: extended_image_library - url: "https://pub.dartlang.org" - source: hosted - version: "3.1.4" fake_async: dependency: transitive description: @@ -488,13 +474,6 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "1.5.1" - http_client_helper: - dependency: transitive - description: - name: http_client_helper - url: "https://pub.dartlang.org" - source: hosted - version: "2.0.2" http_multi_server: dependency: transitive description: @@ -1097,4 +1076,4 @@ packages: version: "3.1.0" sdks: dart: ">=2.15.0 <3.0.0" - flutter: ">=2.8.0" + flutter: ">=2.2.0" diff --git a/pubspec.yaml b/pubspec.yaml index 89fdaf122e..f61c362bfc 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -56,7 +56,6 @@ dependencies: collection: ^1.15.0-nullsafety.4 csv: 5.0.0 stash_memory: 4.0.1 - extended_image: ^6.0.2+1 dev_dependencies: flutter_test: From 41e7e06a7c68c2744b4c6596034809a92788234b Mon Sep 17 00:00:00 2001 From: Javed Hussein Date: Wed, 27 Apr 2022 10:30:44 -0400 Subject: [PATCH 07/16] refactor: add string to translation file PE-1368 --- lib/l10n/app_en.arb | 4 ++++ lib/pages/drive_detail/components/fs_entry_side_sheet.dart | 3 ++- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/lib/l10n/app_en.arb b/lib/l10n/app_en.arb index 2c27e97f42..9e4d419aa2 100644 --- a/lib/l10n/app_en.arb +++ b/lib/l10n/app_en.arb @@ -323,6 +323,10 @@ "@uploadDragAndDrop": { "description": "The action of uploading a file" }, + "itemPreviewEmphasized": "PREVIEW", + "@itemPreviewEmphasized": { + "description": "Item previews" + }, "itemDetailsEmphasized": "DETAILS", "@itemDetailsEmphasized": { "description": "Item details such as dates, transaction IDs and entity IDs. Emphasized with upper case" diff --git a/lib/pages/drive_detail/components/fs_entry_side_sheet.dart b/lib/pages/drive_detail/components/fs_entry_side_sheet.dart index c22ca52c44..21e4e09293 100644 --- a/lib/pages/drive_detail/components/fs_entry_side_sheet.dart +++ b/lib/pages/drive_detail/components/fs_entry_side_sheet.dart @@ -68,7 +68,8 @@ class _FsEntrySideSheetState extends State { tabs: [ if (previewState is FsEntryPreviewSuccess) Tab( - text: 'Preview', + text: appLocalizationsOf(context) + .itemPreviewEmphasized, ), Tab( text: appLocalizationsOf(context) From df633bfcbde0c9f0e93dfec084564869e858725c Mon Sep 17 00:00:00 2001 From: Mati Date: Wed, 27 Apr 2022 13:12:32 -0300 Subject: [PATCH 08/16] feat(internationalization es): adds keys for Spanish PE-1368 --- lib/l10n/app_es.arb | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/lib/l10n/app_es.arb b/lib/l10n/app_es.arb index 02de5f1fc3..495bb7232c 100644 --- a/lib/l10n/app_es.arb +++ b/lib/l10n/app_es.arb @@ -323,6 +323,10 @@ "@uploadDragAndDrop": { "description": "The action of uploading a file" }, + "itemPreviewEmphasized": "VISTA PREVIA", + "@itemPreviewEmphasized": { + "description": "Item previews" + }, "itemDetailsEmphasized": "DETALLES", "@itemDetailsEmphasized": { "description": "Item details such as dates, transaction IDs and entity IDs. Emphasized with upper case" From 4e1ab25dd79ef34d1801eb933e3115a208ab1e31 Mon Sep 17 00:00:00 2001 From: Mati Date: Wed, 27 Apr 2022 14:54:23 -0300 Subject: [PATCH 09/16] feat(internationalization es): minor fix PE-1368 --- lib/l10n/app_en.arb | 2 +- lib/l10n/app_es.arb | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/l10n/app_en.arb b/lib/l10n/app_en.arb index 9e4d419aa2..eea7e1abc2 100644 --- a/lib/l10n/app_en.arb +++ b/lib/l10n/app_en.arb @@ -325,7 +325,7 @@ }, "itemPreviewEmphasized": "PREVIEW", "@itemPreviewEmphasized": { - "description": "Item previews" + "description": "Item preview" }, "itemDetailsEmphasized": "DETAILS", "@itemDetailsEmphasized": { diff --git a/lib/l10n/app_es.arb b/lib/l10n/app_es.arb index 495bb7232c..acb94fa582 100644 --- a/lib/l10n/app_es.arb +++ b/lib/l10n/app_es.arb @@ -325,7 +325,7 @@ }, "itemPreviewEmphasized": "VISTA PREVIA", "@itemPreviewEmphasized": { - "description": "Item previews" + "description": "Item preview" }, "itemDetailsEmphasized": "DETALLES", "@itemDetailsEmphasized": { From 90e77fc927ff78d9e04880b422aa3a2872377974 Mon Sep 17 00:00:00 2001 From: Javed Hussein Date: Fri, 29 Apr 2022 09:07:56 -0400 Subject: [PATCH 10/16] refactor: update package to allow working with flutter 2.10 PE-1368 --- android/app/src/main/AndroidManifest.xml | 2 +- pubspec.lock | 50 +++++++++++++++--------- pubspec.yaml | 2 +- 3 files changed, 34 insertions(+), 20 deletions(-) diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml index 24f2ba3ac6..c28c2e9555 100644 --- a/android/app/src/main/AndroidManifest.xml +++ b/android/app/src/main/AndroidManifest.xml @@ -6,7 +6,7 @@ additional functionality it is fine to subclass or reimplement FlutterApplication and put your custom class here. --> Date: Fri, 29 Apr 2022 09:08:28 -0400 Subject: [PATCH 11/16] refactor: update switch case PE-1368 --- .../fs_entry_preview_cubit.dart | 71 +++++++++---------- 1 file changed, 33 insertions(+), 38 deletions(-) diff --git a/lib/blocs/fs_entry_preview/fs_entry_preview_cubit.dart b/lib/blocs/fs_entry_preview/fs_entry_preview_cubit.dart index ecbba8c517..d69bb45a8d 100644 --- a/lib/blocs/fs_entry_preview/fs_entry_preview_cubit.dart +++ b/lib/blocs/fs_entry_preview/fs_entry_preview_cubit.dart @@ -46,45 +46,40 @@ class FsEntryPreviewCubit extends Cubit { Future preview() async { final selectedItem = maybeSelectedItem; if (selectedItem != null) { - switch (selectedItem.runtimeType) { - case SelectedFile: - _entrySubscription = _driveDao - .fileById(driveId: driveId, fileId: selectedItem.id) - .watchSingle() - .listen((file) { - if (file.size <= previewMaxFileSize) { - final contentType = - file.dataContentType ?? lookupMimeType(file.name); - final previewType = contentType?.split('/').first; - final previewUrl = - '${_config.defaultArweaveGatewayUrl}/${file.dataTxId}'; - switch (previewType) { - case 'image': - emitImagePreview(file, previewUrl); - break; - - /// Enable more previews in the future after dealing - /// with state and widget disposal - - // case 'audio': - // emit(FsEntryPreviewAudio(previewUrl: previewUrl)); - // break; - // case 'video': - // emit(FsEntryPreviewVideo(previewUrl: previewUrl)); - // break; - // case 'text': - // emit(FsEntryPreviewText(previewUrl: previewUrl)); - // break; - default: - emit(FsEntryPreviewUnavailable()); - } - } else { - emit(FsEntryPreviewUnavailable()); + if (selectedItem.runtimeType == SelectedFile) { + _entrySubscription = _driveDao + .fileById(driveId: driveId, fileId: selectedItem.id) + .watchSingle() + .listen((file) { + if (file.size <= previewMaxFileSize) { + final contentType = + file.dataContentType ?? lookupMimeType(file.name); + final previewType = contentType?.split('/').first; + final previewUrl = + '${_config.defaultArweaveGatewayUrl}/${file.dataTxId}'; + switch (previewType) { + case 'image': + emitImagePreview(file, previewUrl); + break; + + /// Enable more previews in the future after dealing + /// with state and widget disposal + + // case 'audio': + // emit(FsEntryPreviewAudio(previewUrl: previewUrl)); + // break; + // case 'video': + // emit(FsEntryPreviewVideo(previewUrl: previewUrl)); + // break; + // case 'text': + // emit(FsEntryPreviewText(previewUrl: previewUrl)); + // break; + + default: + emit(FsEntryPreviewUnavailable()); } - }); - break; - - default: + } + }); } } else { emit(FsEntryPreviewUnavailable()); From 8ece17a24d7bad4f4f3a947d864aa59e64272106 Mon Sep 17 00:00:00 2001 From: thiagocarvalhodev Date: Mon, 9 May 2022 18:18:26 -0300 Subject: [PATCH 12/16] fix: Add image type validation in image preview PE-1460 --- .../fs_entry_preview_cubit.dart | 22 +++++++++++++++++++ lib/utils/constants.dart | 8 +++++++ 2 files changed, 30 insertions(+) create mode 100644 lib/utils/constants.dart diff --git a/lib/blocs/fs_entry_preview/fs_entry_preview_cubit.dart b/lib/blocs/fs_entry_preview/fs_entry_preview_cubit.dart index d69bb45a8d..01befb898c 100644 --- a/lib/blocs/fs_entry_preview/fs_entry_preview_cubit.dart +++ b/lib/blocs/fs_entry_preview/fs_entry_preview_cubit.dart @@ -5,6 +5,7 @@ import 'package:ardrive/blocs/profile/profile_cubit.dart'; import 'package:ardrive/entities/entities.dart'; import 'package:ardrive/models/models.dart'; import 'package:ardrive/services/services.dart'; +import 'package:ardrive/utils/constants.dart'; import 'package:bloc/bloc.dart'; import 'package:cryptography/cryptography.dart'; import 'package:equatable/equatable.dart'; @@ -54,9 +55,16 @@ class FsEntryPreviewCubit extends Cubit { if (file.size <= previewMaxFileSize) { final contentType = file.dataContentType ?? lookupMimeType(file.name); + final fileExtension = contentType?.split('/').last; final previewType = contentType?.split('/').first; final previewUrl = '${_config.defaultArweaveGatewayUrl}/${file.dataTxId}'; + + if (!_supportedExtension(previewType, fileExtension)) { + emit(FsEntryPreviewUnavailable()); + return; + } + switch (previewType) { case 'image': emitImagePreview(file, previewUrl); @@ -160,6 +168,20 @@ class FsEntryPreviewCubit extends Cubit { print('Failed to load entity activity: $error $stackTrace'); } + bool _supportedExtension(String? previewType, String? extension) { + if (previewType == null || extension == null) { + return false; + } + + switch (previewType) { + case 'image': + return supportedImageTypesInFilePreview + .any((element) => element.contains(extension)); + default: + return false; + } + } + @override Future close() { _entrySubscription?.cancel(); diff --git a/lib/utils/constants.dart b/lib/utils/constants.dart new file mode 100644 index 0000000000..0e8d3762b1 --- /dev/null +++ b/lib/utils/constants.dart @@ -0,0 +1,8 @@ +const List supportedImageTypesInFilePreview = [ + 'image/jpeg', + 'image/png', + 'image/gif', + 'image/webp', + 'image/bmp', + 'image/vnd.wap.wbmp', +]; From 5962c80245cbcabc23eb455194d335b0474dd144 Mon Sep 17 00:00:00 2001 From: thiagocarvalhodev Date: Tue, 10 May 2022 10:59:26 -0300 Subject: [PATCH 13/16] refactor: Rename extension parameter to fileExtension PE-1460 --- lib/blocs/fs_entry_preview/fs_entry_preview_cubit.dart | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/lib/blocs/fs_entry_preview/fs_entry_preview_cubit.dart b/lib/blocs/fs_entry_preview/fs_entry_preview_cubit.dart index 01befb898c..407c3124d9 100644 --- a/lib/blocs/fs_entry_preview/fs_entry_preview_cubit.dart +++ b/lib/blocs/fs_entry_preview/fs_entry_preview_cubit.dart @@ -59,7 +59,6 @@ class FsEntryPreviewCubit extends Cubit { final previewType = contentType?.split('/').first; final previewUrl = '${_config.defaultArweaveGatewayUrl}/${file.dataTxId}'; - if (!_supportedExtension(previewType, fileExtension)) { emit(FsEntryPreviewUnavailable()); return; @@ -168,15 +167,15 @@ class FsEntryPreviewCubit extends Cubit { print('Failed to load entity activity: $error $stackTrace'); } - bool _supportedExtension(String? previewType, String? extension) { - if (previewType == null || extension == null) { + bool _supportedExtension(String? previewType, String? fileExtension) { + if (previewType == null || fileExtension == null) { return false; } switch (previewType) { case 'image': return supportedImageTypesInFilePreview - .any((element) => element.contains(extension)); + .any((element) => element.contains(fileExtension)); default: return false; } From 167efaf366b9f2c6d177f4b25bc78044a4587f80 Mon Sep 17 00:00:00 2001 From: thiagocarvalhodev Date: Tue, 10 May 2022 15:31:42 -0300 Subject: [PATCH 14/16] fix(image preview): Align image preview on top PE-1484 --- lib/pages/drive_detail/components/fs_entry_side_sheet.dart | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/lib/pages/drive_detail/components/fs_entry_side_sheet.dart b/lib/pages/drive_detail/components/fs_entry_side_sheet.dart index 87be499973..4c3254dfbe 100644 --- a/lib/pages/drive_detail/components/fs_entry_side_sheet.dart +++ b/lib/pages/drive_detail/components/fs_entry_side_sheet.dart @@ -83,7 +83,10 @@ class _FsEntrySideSheetState extends State { child: TabBarView( children: [ if (previewState is FsEntryPreviewSuccess) - FsEntryPreviewWidget(state: previewState), + Align( + alignment: Alignment.topCenter, + child: FsEntryPreviewWidget( + state: previewState)), Column( crossAxisAlignment: CrossAxisAlignment.stretch, From 1550c5ba923a9c22988ecf2bf11f82ada4528404 Mon Sep 17 00:00:00 2001 From: thiagocarvalhodev Date: Wed, 11 May 2022 15:40:13 -0300 Subject: [PATCH 15/16] chore(bump version): Bump version from 1.15.0 to 1.16.0 for next releases --- pubspec.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pubspec.yaml b/pubspec.yaml index 6ecbaa8f18..cac6504f10 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -13,7 +13,7 @@ publish_to: 'none' # In iOS, build-name is used as CFBundleShortVersionString while build-number used as CFBundleVersion. # Read more about iOS versioning at # https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html -version: 1.15.0 +version: 1.16.0 environment: sdk: '>=2.13.0 <3.0.0' From 30d8d8bfb07c0286e08fc01fdd6bb5f5e77d9eb1 Mon Sep 17 00:00:00 2001 From: thiagocarvalhodev Date: Wed, 11 May 2022 16:09:47 -0300 Subject: [PATCH 16/16] fix(show drive with no funds) Add validaiton of permission and no funds PE-1503 --- lib/components/app_drawer/app_drawer.dart | 34 +++++++++++------------ 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/lib/components/app_drawer/app_drawer.dart b/lib/components/app_drawer/app_drawer.dart index 387cff300c..5a8f84b47e 100644 --- a/lib/components/app_drawer/app_drawer.dart +++ b/lib/components/app_drawer/app_drawer.dart @@ -273,9 +273,9 @@ class AppDrawer extends StatelessWidget { context: context, isEnabled: state.hasWritePermissions && hasMinBalance, itemTitle: appLocalizationsOf(context).newFolder, - message: hasMinBalance - ? null - : appLocalizationsOf(context).insufficientFundsForCreateAFolder, + message: state.hasWritePermissions && !hasMinBalance + ? appLocalizationsOf(context).insufficientFundsForCreateAFolder + : null, value: (context) => promptToCreateFolder( context, driveId: state.currentDrive.id, @@ -289,9 +289,9 @@ class AppDrawer extends StatelessWidget { return _buildMenuItemTile( context: context, isEnabled: state.hasWritePermissions && hasMinBalance, - message: hasMinBalance - ? null - : appLocalizationsOf(context).insufficientFundsForUploadFiles, + message: state.hasWritePermissions && !hasMinBalance + ? appLocalizationsOf(context).insufficientFundsForUploadFiles + : null, itemTitle: appLocalizationsOf(context).uploadFiles, value: (context) => promptToUpload( context, @@ -308,9 +308,9 @@ class AppDrawer extends StatelessWidget { context: context, isEnabled: state.hasWritePermissions && hasMinBalance, itemTitle: appLocalizationsOf(context).uploadFolder, - message: hasMinBalance - ? null - : appLocalizationsOf(context).insufficientFundsForUploadFolders, + message: state.hasWritePermissions && !hasMinBalance + ? appLocalizationsOf(context).insufficientFundsForUploadFolders + : null, value: (context) => promptToUpload( context, driveId: state.currentDrive.id, @@ -329,8 +329,8 @@ class AppDrawer extends StatelessWidget { ); } - PopupMenuEntry _buildCreateDrive(BuildContext context, - DrivesLoadSuccess drivesState, bool hasMinBalance) { + PopupMenuEntry _buildCreateDrive( + BuildContext context, DrivesLoadSuccess drivesState, bool hasMinBalance) { return _buildMenuItemTile( context: context, isEnabled: drivesState.canCreateNewDrive && hasMinBalance, @@ -359,15 +359,15 @@ class AppDrawer extends StatelessWidget { ); } - PopupMenuEntry _buildCreateManifestItem(BuildContext context, - DriveDetailLoadSuccess state, bool hasMinBalance) { + PopupMenuEntry _buildCreateManifestItem( + BuildContext context, DriveDetailLoadSuccess state, bool hasMinBalance) { return _buildMenuItemTile( context: context, - isEnabled: !state.driveIsEmpty, + isEnabled: !state.driveIsEmpty && hasMinBalance, itemTitle: appLocalizationsOf(context).createManifest, - message: hasMinBalance - ? null - : appLocalizationsOf(context).insufficientFundsForCreateAManifest, + message: !state.driveIsEmpty && !hasMinBalance + ? appLocalizationsOf(context).insufficientFundsForCreateAManifest + : null, value: (context) => promptToCreateManifest(context, drive: state.currentDrive), );