Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Migrate to Quicknode for ipfs upload #2337

Merged
merged 6 commits into from
Apr 17, 2024
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion easel/android/app/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ apply plugin: 'com.google.firebase.crashlytics'
apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle"

android {
compileSdkVersion 33
compileSdkVersion 34

compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
Expand Down
4 changes: 2 additions & 2 deletions easel/android/build.gradle
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
buildscript {
ext.kotlin_version = '1.6.10'
ext.kotlin_version = '1.9.20'
repositories {
google()
mavenCentral()
Expand All @@ -26,6 +26,6 @@ subprojects {
project.evaluationDependsOn(':app')
}

task clean(type: Delete) {
tasks.register("clean", Delete) {
delete rootProject.buildDir
}
40 changes: 27 additions & 13 deletions easel/lib/easel_provider.dart
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import 'dart:async';
import 'dart:io';

import 'package:dartz/dartz.dart';
import 'package:easel_flutter/main.dart';
import 'package:easel_flutter/models/denom.dart';
import 'package:easel_flutter/models/nft.dart';
Expand All @@ -12,10 +13,12 @@ import 'package:easel_flutter/models/upload_progress.dart';
import 'package:easel_flutter/repository/repository.dart';
import 'package:easel_flutter/screens/creator_hub/creator_hub_view_model.dart';
import 'package:easel_flutter/services/third_party_services/audio_player_helper.dart';
import 'package:easel_flutter/services/third_party_services/quick_node.dart';
import 'package:easel_flutter/services/third_party_services/video_player_helper.dart';
import 'package:easel_flutter/utils/constants.dart';
import 'package:easel_flutter/utils/enums.dart';
import 'package:easel_flutter/utils/extension_util.dart';
import 'package:easel_flutter/utils/failure/failure.dart';
import 'package:easel_flutter/utils/file_utils_helper.dart';
import 'package:easel_flutter/widgets/audio_widget.dart';
import 'package:easel_flutter/widgets/loading_with_progress.dart';
Expand Down Expand Up @@ -239,9 +242,7 @@ class EaselProvider extends ChangeNotifier {
royaltyController.text = royalties ?? "";
priceController.text = price ?? "";
noOfEditionController.text = edition ?? "";
_selectedDenom = denom != ""
? Denom.availableDenoms.firstWhere((element) => element.symbol == denom)
: Denom.availableDenoms.first;
_selectedDenom = denom != "" ? Denom.availableDenoms.firstWhere((element) => element.symbol == denom) : Denom.availableDenoms.first;
isFreeDrop = freeDrop!;
notifyListeners();
}
Expand Down Expand Up @@ -680,8 +681,7 @@ class EaselProvider extends ChangeNotifier {
currentUsername = sdkResponse.data!.username;
stripeAccountExists = sdkResponse.data!.stripeExists;

supportedDenomList =
Denom.availableDenoms.where((Denom e) => sdkResponse.data!.supportedCoins.contains(e.symbol)).toList();
supportedDenomList = Denom.availableDenoms.where((Denom e) => sdkResponse.data!.supportedCoins.contains(e.symbol)).toList();

if (supportedDenomList.isNotEmpty && selectedDenom.symbol.isEmpty) {
_selectedDenom = supportedDenomList.first;
Expand Down Expand Up @@ -777,8 +777,7 @@ class EaselProvider extends ChangeNotifier {
}
}

bool isThumbnailPresent() =>
nftFormat.format == NFTTypes.audio || nftFormat.format == NFTTypes.video || nftFormat.format == NFTTypes.pdf;
bool isThumbnailPresent() => nftFormat.format == NFTTypes.audio || nftFormat.format == NFTTypes.video || nftFormat.format == NFTTypes.pdf;

Future<bool> saveNftLocally(UploadStep step) async {
final scaffoldMessengerOptionalState = navigatorKey.getState();
Expand Down Expand Up @@ -814,12 +813,27 @@ class EaselProvider extends ChangeNotifier {
uploadThumbnailResponse = uploadResponse.getOrElse(() => StorageResponseModel.initial());
}

final response = await repository.uploadFile(
file: _file!,
onUploadProgressCallback: (value) {
_uploadProgressController.sink.add(value);
},
);
final whereToUpload = QuickNode.listOfQuickNodeAllowedExtension().contains(fileExtension.toLowerCase());
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The name doesn't justifies what it is doing. Maybe you should use shouldUploadToQuickNode

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done


Either<Failure, StorageResponseModel> response;

if (!whereToUpload) {
response = await repository.uploadFile(
file: _file!,
onUploadProgressCallback: (value) {
_uploadProgressController.sink.add(value);
},
);
} else {
response = await repository.uploadFileUsingQuickNode(
uploadIPFSInput: UploadIPFSInput(
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does quick node doesn't provides any support for the upload progress? Kindly check line 824.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yes it is supported working on it

fileName: fileName,
filePath: file?.path ?? '',
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If the file doesn't have a path then it shouldn't be uploaded.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

contentType: QuickNode.getContentType(fileExtension),
),
);
}

if (response.isLeft()) {
loading.dismiss();
LocaleKeys.something_wrong_while_uploading.tr().show();
Expand Down
3 changes: 1 addition & 2 deletions easel/lib/main.dart
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ bool isTablet = false;

Future<void> main() async {
WidgetsFlutterBinding.ensureInitialized();

await Firebase.initializeApp();
di.init();
final firebaseCrashlytics = GetIt.I.get<FirebaseCrashlytics>();
Expand Down Expand Up @@ -109,7 +109,6 @@ class MyApp extends StatelessWidget {
}
}


bool _getIsCurrentDeviceTablet() {
final MediaQueryData mediaQuery = MediaQueryData.fromView(PlatformDispatcher.instance.implicitView!);
return mediaQuery.size.shortestSide >= tabletMinWidth;
Expand Down
22 changes: 22 additions & 0 deletions easel/lib/models/storage_response_model.dart
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import 'package:easel_flutter/services/third_party_services/quick_node.dart';

class StorageResponseModel {
bool? ok;
Value? value;
Expand All @@ -21,6 +23,26 @@ class StorageResponseModel {
factory StorageResponseModel.initial() {
return StorageResponseModel();
}

factory StorageResponseModel.fromQuickNode({required UploadIPFSOutput uploadIPFSOutput}) {
return StorageResponseModel(
ok: true,
value: Value(
cid: uploadIPFSOutput.pin?.cid,
created: uploadIPFSOutput.created,
size: int.parse(
uploadIPFSOutput.info?.size ?? '0',
),
pin: Pin(
cid: uploadIPFSOutput.pin?.cid,
created: uploadIPFSOutput.created,
size: int.parse(
uploadIPFSOutput.info?.size ?? '0',
),
status: uploadIPFSOutput.status,
)),
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
)),
),),

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

);
}
}

class Value {
Expand Down
20 changes: 20 additions & 0 deletions easel/lib/repository/repository.dart
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import 'package:easel_flutter/services/datasources/local_datasource.dart';
import 'package:easel_flutter/services/datasources/remote_datasource.dart';
import 'package:easel_flutter/services/third_party_services/crashlytics_helper.dart';
import 'package:easel_flutter/services/third_party_services/network_info.dart';
import 'package:easel_flutter/services/third_party_services/quick_node.dart';
import 'package:easel_flutter/utils/extension_util.dart';
import 'package:easel_flutter/utils/failure/failure.dart';
import 'package:easel_flutter/utils/file_utils_helper.dart';
Expand Down Expand Up @@ -111,6 +112,11 @@ abstract class Repository {
/// Output : [ApiResponse] the ApiResponse which can contain [success] or [error] response
Future<Either<Failure, StorageResponseModel>> uploadFile({required File file, required OnUploadProgressCallback onUploadProgressCallback});

/// This method is used uploading provided file to the server using [QuickNode]
/// Input : [UploadIPFSInput] which needs to be uploaded
/// Output : [ApiResponse] the ApiResponse which can contain [success] or [error] response
Future<Either<Failure, StorageResponseModel>> uploadFileUsingQuickNode({required UploadIPFSInput uploadIPFSInput});

/// This method will get the drafts List from the local database
/// Output: [List] returns that contains a number of [NFT]
Future<Either<Failure, List<NFT>>> getNfts();
Expand Down Expand Up @@ -317,7 +323,21 @@ class RepositoryImp implements Repository {

try {
final storageResponseModel = await remoteDataSource.uploadFile(file: file, uploadProgressCallback: onUploadProgressCallback);
return Right(storageResponseModel);
} on Exception catch (_) {
crashlyticsHelper.recordFatalError(error: _.toString());
return Left(CacheFailure(LocaleKeys.update_failed.tr()));
}
}

@override
Future<Either<Failure, StorageResponseModel>> uploadFileUsingQuickNode({required UploadIPFSInput uploadIPFSInput}) async {
if (!await networkInfo.isConnected) {
return Left(NoInternetFailure(LocaleKeys.no_internet.tr()));
}

try {
final storageResponseModel = await remoteDataSource.uploadFileUsingQuickNode(uploadIPFSInput: uploadIPFSInput);
return Right(storageResponseModel);
} on Exception catch (_) {
crashlyticsHelper.recordFatalError(error: _.toString());
Expand Down
3 changes: 3 additions & 0 deletions easel/lib/screens/choose_format_screen.dart
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,9 @@ class _ChooseFormatScreenState extends State<ChooseFormatScreen> {
return;
}




Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You can revert these spaces.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

if (!provider.nftFormat.extensions.contains(result.extension)) {
final fileName = result.fileName.replaceAll(".${result.extension}", "");
errorText.value = LocaleKeys.could_not_uploaded.tr(
Expand Down
40 changes: 9 additions & 31 deletions easel/lib/screens/publish_screen.dart
Original file line number Diff line number Diff line change
Expand Up @@ -72,12 +72,7 @@ class _PublishScreenState extends State<PublishScreen> {
body: Consumer<EaselProvider>(builder: (_, easelProvider, __) {
return Stack(
children: [
Positioned(
left: 0,
right: 0,
top: 0,
bottom: 0,
child: SizedBox(width: double.infinity, child: buildPreviewWidget(easelProvider))),
Positioned(left: 0, right: 0, top: 0, bottom: 0, child: SizedBox(width: double.infinity, child: buildPreviewWidget(easelProvider))),
Positioned(
left: 10.w,
top: 30.h,
Expand Down Expand Up @@ -187,10 +182,7 @@ class _OwnerBottomDrawerState extends State<OwnerBottomDrawer> {
collapseStatus: viewModel.collapsed,
onCollapsed: (context) => DecoratedBox(
decoration: const BoxDecoration(
gradient: LinearGradient(
begin: Alignment.topCenter,
end: Alignment.bottomCenter,
colors: [EaselAppTheme.kTransparent, EaselAppTheme.kBlack]),
gradient: LinearGradient(begin: Alignment.topCenter, end: Alignment.bottomCenter, colors: [EaselAppTheme.kTransparent, EaselAppTheme.kBlack]),
),
child: Padding(
padding: EdgeInsets.only(left: 16.w, right: 16.w, top: 8.h, bottom: 16.h),
Expand Down Expand Up @@ -236,11 +228,7 @@ class _OwnerBottomDrawerState extends State<OwnerBottomDrawer> {
builder: (_, value, __) {
switch (value) {
case ButtonState.loading:
return SizedBox(
height: 20.h,
width: 15.h,
child: CircularProgressIndicator(
strokeWidth: 2.w, color: EaselAppTheme.kWhite));
return SizedBox(height: 20.h, width: 15.h, child: CircularProgressIndicator(strokeWidth: 2.w, color: EaselAppTheme.kWhite));
case ButtonState.paused:
return InkWell(
onTap: () {
Expand Down Expand Up @@ -282,8 +270,7 @@ class _OwnerBottomDrawerState extends State<OwnerBottomDrawer> {
bufferedBarColor: EaselAppTheme.kLightGrey,
buffered: value.buffered,
total: value.total,
timeLabelTextStyle: TextStyle(
color: EaselAppTheme.kWhite, fontWeight: FontWeight.w800, fontSize: 9.sp),
timeLabelTextStyle: TextStyle(color: EaselAppTheme.kWhite, fontWeight: FontWeight.w800, fontSize: 9.sp),
thumbRadius: 10.h,
timeLabelPadding: 3.h,
onSeek: (position) {
Expand Down Expand Up @@ -316,9 +303,7 @@ class _OwnerBottomDrawerState extends State<OwnerBottomDrawer> {
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
_title(
nft: widget.nft,
owner: widget.nft.type == NftType.TYPE_RECIPE.name ? "you".tr() : widget.nft.creator),
_title(nft: widget.nft, owner: widget.nft.type == NftType.TYPE_RECIPE.name ? "you".tr() : widget.nft.creator),
SizedBox(
height: 30.h,
),
Expand Down Expand Up @@ -387,16 +372,14 @@ class _OwnerBottomDrawerState extends State<OwnerBottomDrawer> {
subtitle: "${widget.nft.tradePercentage}%",
),
SizedBox(height: 5.h),
buildRow(
title: LocaleKeys.content_identifier.tr(), subtitle: widget.nft.cid, canCopy: true),
buildRow(title: LocaleKeys.content_identifier.tr(), subtitle: widget.nft.cid, canCopy: true),
SizedBox(height: 5.h),
CidOrIpfs(
viewCid: (context) {
return const SizedBox.shrink();
},
viewIpfs: (context) {
return buildRow(
title: LocaleKeys.asset_uri.tr(), subtitle: LocaleKeys.view.tr(), viewIPFS: true);
return buildRow(title: LocaleKeys.asset_uri.tr(), subtitle: LocaleKeys.view.tr(), viewIPFS: true);
},
type: widget.nft.assetType,
),
Expand Down Expand Up @@ -522,10 +505,7 @@ class _OwnerBottomDrawerState extends State<OwnerBottomDrawer> {
builder: (_, value, __) {
switch (value) {
case ButtonState.loading:
return SizedBox(
height: 22.h,
width: 22.h,
child: CircularProgressIndicator(strokeWidth: 2.w, color: EaselAppTheme.kWhite));
return SizedBox(height: 22.h, width: 22.h, child: CircularProgressIndicator(strokeWidth: 2.w, color: EaselAppTheme.kWhite));
case ButtonState.paused:
return InkWell(
onTap: () {
Expand Down Expand Up @@ -751,9 +731,7 @@ class BuildPublishBottomSheet extends StatelessWidget {
final WidgetBuilder onOpened;
final bool collapseStatus;

const BuildPublishBottomSheet(
{Key? key, required this.onCollapsed, required this.onOpened, required this.collapseStatus})
: super(key: key);
const BuildPublishBottomSheet({Key? key, required this.onCollapsed, required this.onOpened, required this.collapseStatus}) : super(key: key);

@override
Widget build(BuildContext context) {
Expand Down
21 changes: 18 additions & 3 deletions easel/lib/services/datasources/remote_datasource.dart
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import 'package:easel_flutter/easel_provider.dart';
import 'package:easel_flutter/models/storage_response_model.dart';
import 'package:easel_flutter/models/upload_progress.dart';
import 'package:easel_flutter/services/third_party_services/analytics_helper.dart';
import 'package:easel_flutter/services/third_party_services/quick_node.dart';
import 'package:easy_localization/easy_localization.dart';
import 'package:pylons_sdk/low_level.dart';

Expand All @@ -21,19 +22,33 @@ abstract class RemoteDataSource {
/// Output : [Future<List<Recipe>>] which will be a Future list of Recipes against the given [cookbookID]
Future<List<Recipe>> getRecipesByCookbookID(String cookBookID);



/// This method is used to log user journey in the easel app.
/// Input: [screenName] the screen name in the easel.
/// Output: [bool] tells whether the user journey is recorded or not
Future<bool> logUserJourney({required String screenName});

/// This method is used uploading provided file to the server using [QuickNode]
/// Input : [UploadIPFSInput] which needs to be uploaded
/// Output : [Future<ApiResponse<StorageResponseModel>>] the ApiResponse which can contain [success] or [error] response
Future<StorageResponseModel> uploadFileUsingQuickNode({required UploadIPFSInput uploadIPFSInput});
}

class RemoteDataSourceImpl implements RemoteDataSource {
final Dio httpClient;
final AnalyticsHelper analyticsHelper;
final QuickNode quickNode;

RemoteDataSourceImpl({required this.httpClient, required this.analyticsHelper});
RemoteDataSourceImpl({
required this.httpClient,
required this.analyticsHelper,
required this.quickNode,
});

@override
Future<StorageResponseModel> uploadFileUsingQuickNode({required UploadIPFSInput uploadIPFSInput}) async {
final response = await quickNode.uploadNewObjectToIPFS(uploadIPFSInput);
return response;
}
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
@override
Future<StorageResponseModel> uploadFileUsingQuickNode({required UploadIPFSInput uploadIPFSInput}) async {
final response = await quickNode.uploadNewObjectToIPFS(uploadIPFSInput);
return response;
}
@override
Future<StorageResponseModel> uploadFileUsingQuickNode({required UploadIPFSInput uploadIPFSInput}) =>
quickNode.uploadNewObjectToIPFS(uploadIPFSInput);


@override
Future<StorageResponseModel> uploadFile({required OnUploadProgressCallback uploadProgressCallback, required File file}) async {
Expand Down
Loading
Loading