From faaf3c5a5815e53121a94f8106fd65aae028f90b Mon Sep 17 00:00:00 2001 From: phuoc Date: Wed, 6 Dec 2023 17:13:59 +0700 Subject: [PATCH 01/13] show merch if product is not empty Signed-off-by: phuoc --- .../workflows/android-release-appcenter.yaml | 1 + .../bmvn_build_appcenter_android.yaml | 1 + .../workflows/bmvn_build_appcenter_ios.yaml | 1 + .github/workflows/ios-release-appstore.yaml | 1 + lib/common/environment.dart | 3 + lib/common/injector.dart | 6 ++ lib/gateway/merchandise_api.dart | 13 ++++ lib/gateway/merchandise_api.g.dart | 76 +++++++++++++++++++ .../postcard_detail_bloc.dart | 31 +++++++- .../postcard_detail_page.dart | 3 +- .../postcard_detail_state.dart | 4 + 11 files changed, 137 insertions(+), 3 deletions(-) create mode 100644 lib/gateway/merchandise_api.dart create mode 100644 lib/gateway/merchandise_api.g.dart diff --git a/.github/workflows/android-release-appcenter.yaml b/.github/workflows/android-release-appcenter.yaml index fa5f9c68c..0d051cc8a 100644 --- a/.github/workflows/android-release-appcenter.yaml +++ b/.github/workflows/android-release-appcenter.yaml @@ -76,6 +76,7 @@ jobs: echo TZKT_MAINNET_URL=${{ secrets.TZKT_MAINNET_URL }} >> .env ${{ github.event.inputs.testnet == 'true' }} && echo POSTCARD_CONTRACT_ADDRESS=${{ secrets.POSTCARD_CONTRACT_ADDRESS_TESTNET }} >> .env || echo POSTCARD_CONTRACT_ADDRESS=${{ secrets.POSTCARD_CONTRACT_ADDRESS_MAINNET }} >> .env ${{ github.event.inputs.testnet == 'true' }} && echo AUTONOMY_MERCHANDISE_BASE_URL=${{ secrets.AUTONOMY_MERCHANDISE_BASE_URL_TESTNET }} >> .env || echo AUTONOMY_MERCHANDISE_BASE_URL=${{ secrets.AUTONOMY_MERCHANDISE_BASE_URL_MAINNET }} >> .env + ${{ github.event.inputs.testnet == 'true' }} && echo AUTONOMY_MERCHANDISE_API_URL=${{ secrets.AUTONOMY_MERCHANDISE_API_URL_TESTNET }} >> .env || echo AUTONOMY_MERCHANDISE_API_URL=${{ secrets.AUTONOMY_MERCHANDISE_API_URL_MAINNET }} >> .env ${{ github.event.inputs.testnet == 'true' }} && echo PAY_TO_MINT_BASE_URL=${{ secrets.PAY_TO_MINT_BASE_URL_TESTNET }} >> .env || echo PAY_TO_MINT_BASE_URL=${{ secrets.PAY_TO_MINT_BASE_URL_MAINNET }} >> .env ${{ github.event.inputs.testnet == 'true' }} && echo AUTONOMY_PUBDOC_URL=${{ secrets.AUTONOMY_PUBDOC_URL_DEV }} >> .env || echo AUTONOMY_PUBDOC_URL=${{ secrets.AUTONOMY_PUBDOC_URL_PRD }} >> .env diff --git a/.github/workflows/bmvn_build_appcenter_android.yaml b/.github/workflows/bmvn_build_appcenter_android.yaml index 1726448d3..c190c43de 100644 --- a/.github/workflows/bmvn_build_appcenter_android.yaml +++ b/.github/workflows/bmvn_build_appcenter_android.yaml @@ -95,6 +95,7 @@ jobs: echo TZKT_MAINNET_URL=${{ secrets.TZKT_MAINNET_URL }} >> .env ${{ inputs.testnet == true }} && echo POSTCARD_CONTRACT_ADDRESS=${{ secrets.POSTCARD_CONTRACT_ADDRESS_TESTNET }} >> .env || echo POSTCARD_CONTRACT_ADDRESS=${{ secrets.POSTCARD_CONTRACT_ADDRESS_MAINNET }} >> .env ${{ inputs.testnet == true }} && echo AUTONOMY_MERCHANDISE_BASE_URL=${{ secrets.AUTONOMY_MERCHANDISE_BASE_URL_TESTNET }} >> .env || echo AUTONOMY_MERCHANDISE_BASE_URL=${{ secrets.AUTONOMY_MERCHANDISE_BASE_URL_MAINNET }} >> .env + ${{ inputs.testnet == true }} && echo AUTONOMY_MERCHANDISE_API_URL=${{ secrets.AUTONOMY_MERCHANDISE_API_URL_TESTNET }} >> .env || echo AUTONOMY_MERCHANDISE_API_URL=${{ secrets.AUTONOMY_MERCHANDISE_API_URL_MAINNET }} >> .env ${{ inputs.testnet == true }} && echo PAY_TO_MINT_BASE_URL=${{ secrets.PAY_TO_MINT_BASE_URL_TESTNET }} >> .env || echo PAY_TO_MINT_BASE_URL=${{ secrets.PAY_TO_MINT_BASE_URL_MAINNET }} >> .env ${{ inputs.testnet == true }} && echo AUTONOMY_PUBDOC_URL=${{ secrets.AUTONOMY_PUBDOC_URL_DEV }} >> .env || echo AUTONOMY_PUBDOC_URL=${{ secrets.AUTONOMY_PUBDOC_URL_PRD }} >> .env diff --git a/.github/workflows/bmvn_build_appcenter_ios.yaml b/.github/workflows/bmvn_build_appcenter_ios.yaml index 59c90acf4..aa92b54be 100644 --- a/.github/workflows/bmvn_build_appcenter_ios.yaml +++ b/.github/workflows/bmvn_build_appcenter_ios.yaml @@ -102,6 +102,7 @@ jobs: echo TZKT_MAINNET_URL=${{ secrets.TZKT_MAINNET_URL }} >> .env ${{ inputs.testnet == true }} && echo POSTCARD_CONTRACT_ADDRESS=${{ secrets.POSTCARD_CONTRACT_ADDRESS_TESTNET }} >> .env || echo POSTCARD_CONTRACT_ADDRESS=${{ secrets.POSTCARD_CONTRACT_ADDRESS_MAINNET }} >> .env ${{ inputs.testnet == true }} && echo AUTONOMY_MERCHANDISE_BASE_URL=${{ secrets.AUTONOMY_MERCHANDISE_BASE_URL_TESTNET }} >> .env || echo AUTONOMY_MERCHANDISE_BASE_URL=${{ secrets.AUTONOMY_MERCHANDISE_BASE_URL_MAINNET }} >> .env + ${{ inputs.testnet == true }} && echo AUTONOMY_MERCHANDISE_API_URL=${{ secrets.AUTONOMY_MERCHANDISE_API_URL_TESTNET }} >> .env || echo AUTONOMY_MERCHANDISE_API_URL=${{ secrets.AUTONOMY_MERCHANDISE_API_URL_MAINNET }} >> .env ${{ inputs.testnet == true }} && echo PAY_TO_MINT_BASE_URL=${{ secrets.PAY_TO_MINT_BASE_URL_TESTNET }} >> .env || echo PAY_TO_MINT_BASE_URL=${{ secrets.PAY_TO_MINT_BASE_URL_MAINNET }} >> .env ${{ inputs.testnet == true }} && echo AUTONOMY_PUBDOC_URL=${{ secrets.AUTONOMY_PUBDOC_URL_DEV }} >> .env || echo AUTONOMY_PUBDOC_URL=${{ secrets.AUTONOMY_PUBDOC_URL_PRD }} >> .env diff --git a/.github/workflows/ios-release-appstore.yaml b/.github/workflows/ios-release-appstore.yaml index 7f1053344..03a0f49fa 100644 --- a/.github/workflows/ios-release-appstore.yaml +++ b/.github/workflows/ios-release-appstore.yaml @@ -87,6 +87,7 @@ jobs: echo TZKT_MAINNET_URL=${{ secrets.TZKT_MAINNET_URL }} >> .env ${{ github.event.inputs.testnet == 'true' }} && echo POSTCARD_CONTRACT_ADDRESS=${{ secrets.POSTCARD_CONTRACT_ADDRESS_TESTNET }} >> .env || echo POSTCARD_CONTRACT_ADDRESS=${{ secrets.POSTCARD_CONTRACT_ADDRESS_MAINNET }} >> .env ${{ github.event.inputs.testnet == 'true' }} && echo AUTONOMY_MERCHANDISE_BASE_URL=${{ secrets.AUTONOMY_MERCHANDISE_BASE_URL_TESTNET }} >> .env || echo AUTONOMY_MERCHANDISE_BASE_URL=${{ secrets.AUTONOMY_MERCHANDISE_BASE_URL_MAINNET }} >> .env + ${{ github.event.inputs.testnet == 'true' }} && echo AUTONOMY_MERCHANDISE_API_URL=${{ secrets.AUTONOMY_MERCHANDISE_API_URL_TESTNET }} >> .env || echo AUTONOMY_MERCHANDISE_API_URL=${{ secrets.AUTONOMY_MERCHANDISE_API_URL_MAINNET }} >> .env ${{ github.event.inputs.testnet == 'true' }} && echo PAY_TO_MINT_BASE_URL=${{ secrets.PAY_TO_MINT_BASE_URL_TESTNET }} >> .env || echo PAY_TO_MINT_BASE_URL=${{ secrets.PAY_TO_MINT_BASE_URL_MAINNET }} >> .env ${{ github.event.inputs.testnet == 'true' }} && echo AUTONOMY_PUBDOC_URL=${{ secrets.AUTONOMY_PUBDOC_URL_DEV }} >> .env || echo AUTONOMY_PUBDOC_URL=${{ secrets.AUTONOMY_PUBDOC_URL_PRD }} >> .env diff --git a/lib/common/environment.dart b/lib/common/environment.dart index e2e027c66..4f6b1a206 100644 --- a/lib/common/environment.dart +++ b/lib/common/environment.dart @@ -160,6 +160,9 @@ class Environment { static String get merchandiseBaseUrl => dotenv.env['AUTONOMY_MERCHANDISE_BASE_URL'] ?? ''; + static String get merchandiseApiUrl => + dotenv.env['AUTONOMY_MERCHANDISE_API_URL'] ?? ''; + static String get payToMintBaseUrl => dotenv.env['PAY_TO_MINT_BASE_URL'] ?? ''; diff --git a/lib/common/injector.dart b/lib/common/injector.dart index 2e8e68695..5a4aefb2f 100644 --- a/lib/common/injector.dart +++ b/lib/common/injector.dart @@ -5,6 +5,8 @@ // that can be found in the LICENSE file. // +// ignore_for_file: cascade_invocations + import 'dart:math'; import 'package:autonomy_flutter/common/environment.dart'; @@ -21,6 +23,7 @@ import 'package:autonomy_flutter/gateway/customer_support_api.dart'; import 'package:autonomy_flutter/gateway/etherchain_api.dart'; import 'package:autonomy_flutter/gateway/feralfile_api.dart'; import 'package:autonomy_flutter/gateway/iap_api.dart'; +import 'package:autonomy_flutter/gateway/merchandise_api.dart'; import 'package:autonomy_flutter/gateway/postcard_api.dart'; import 'package:autonomy_flutter/gateway/pubdoc_api.dart'; import 'package:autonomy_flutter/gateway/tzkt_api.dart'; @@ -294,6 +297,9 @@ Future setup() async { receiveTimeout: const Duration(seconds: 30))), baseUrl: Environment.auClaimAPIURL)); + injector.registerLazySingleton( + () => MerchandiseApi(dio, baseUrl: Environment.merchandiseApiUrl)); + final indexerClient = IndexerClient(Environment.indexerURL); injector.registerLazySingleton( () => IndexerService(indexerClient)); diff --git a/lib/gateway/merchandise_api.dart b/lib/gateway/merchandise_api.dart new file mode 100644 index 000000000..c8bf5900c --- /dev/null +++ b/lib/gateway/merchandise_api.dart @@ -0,0 +1,13 @@ +import 'package:dio/dio.dart'; +import 'package:retrofit/retrofit.dart'; + +part 'merchandise_api.g.dart'; + +@RestApi(baseUrl: '') +abstract class MerchandiseApi { + factory MerchandiseApi(Dio dio, {String baseUrl}) = _MerchandiseApi; + + @GET('/v1/products') + Future getProducts( + @Query('index_id') String indexId); +} diff --git a/lib/gateway/merchandise_api.g.dart b/lib/gateway/merchandise_api.g.dart new file mode 100644 index 000000000..da01190b6 --- /dev/null +++ b/lib/gateway/merchandise_api.g.dart @@ -0,0 +1,76 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'merchandise_api.dart'; + +// ************************************************************************** +// RetrofitGenerator +// ************************************************************************** + +// ignore_for_file: unnecessary_brace_in_string_interps,no_leading_underscores_for_local_identifiers + +class _MerchandiseApi implements MerchandiseApi { + _MerchandiseApi( + this._dio, { + this.baseUrl, + }); + + final Dio _dio; + + String? baseUrl; + + @override + Future getProducts(String indexId) async { + const _extra = {}; + final queryParameters = {r'index_id': indexId}; + final _headers = {}; + final Map? _data = null; + final _result = await _dio.fetch(_setStreamType(Options( + method: 'GET', + headers: _headers, + extra: _extra, + ) + .compose( + _dio.options, + '/v1/products', + queryParameters: queryParameters, + data: _data, + ) + .copyWith( + baseUrl: _combineBaseUrls( + _dio.options.baseUrl, + baseUrl, + )))); + final value = _result.data; + return value; + } + + RequestOptions _setStreamType(RequestOptions requestOptions) { + if (T != dynamic && + !(requestOptions.responseType == ResponseType.bytes || + requestOptions.responseType == ResponseType.stream)) { + if (T == String) { + requestOptions.responseType = ResponseType.plain; + } else { + requestOptions.responseType = ResponseType.json; + } + } + return requestOptions; + } + + String _combineBaseUrls( + String dioBaseUrl, + String? baseUrl, + ) { + if (baseUrl == null || baseUrl.trim().isEmpty) { + return dioBaseUrl; + } + + final url = Uri.parse(baseUrl); + + if (url.isAbsolute) { + return url.toString(); + } + + return Uri.parse(dioBaseUrl).resolveUri(url).toString(); + } +} diff --git a/lib/screen/interactive_postcard/postcard_detail_bloc.dart b/lib/screen/interactive_postcard/postcard_detail_bloc.dart index 18e590b0a..1601443d6 100644 --- a/lib/screen/interactive_postcard/postcard_detail_bloc.dart +++ b/lib/screen/interactive_postcard/postcard_detail_bloc.dart @@ -9,6 +9,7 @@ import 'dart:async'; import 'package:autonomy_flutter/au_bloc.dart'; import 'package:autonomy_flutter/common/injector.dart'; +import 'package:autonomy_flutter/gateway/merchandise_api.dart'; import 'package:autonomy_flutter/model/pair.dart'; import 'package:autonomy_flutter/screen/detail/artwork_detail_page.dart'; import 'package:autonomy_flutter/screen/interactive_postcard/leaderboard/postcard_leaderboard.dart'; @@ -78,6 +79,11 @@ class PostcardDetailBloc imagePath: paths.first, metadataPath: paths.second, )); + + final hasMerch = await _hasMerchProduct(assetToken.first.id); + if (hasMerch != state.showMerch) { + emit(state.copyWith(showMerch: hasMerch)); + } } return; } else { @@ -96,15 +102,23 @@ class PostcardDetailBloc assetToken?.setAssetPrompt(tempsPrompt); } final paths = getUpdatingPath(assetToken); - emit(state.copyWith( + emit( + state.copyWith( assetToken: assetToken, imagePath: paths.first, - metadataPath: paths.second)); + metadataPath: paths.second, + ), + ); final provenances = await _provenanceDao.findProvenanceByTokenID(event.identity.id); emit(state.copyWith(provenances: provenances)); + final hasMerch = await _hasMerchProduct(assetToken?.id); + if (hasMerch != state.showMerch) { + emit(state.copyWith(showMerch: hasMerch)); + } + if (assetToken != null && assetToken.asset != null && (assetToken.mimeType?.isEmpty ?? true)) { @@ -198,4 +212,17 @@ class PostcardDetailBloc } return Pair(imagePath, metadataPath); } + + Future _hasMerchProduct(String? indexId) async { + if (indexId == null) { + return false; + } + try { + final merchApi = injector(); + final products = await merchApi.getProducts(indexId); + return products.isNotEmpty; + } catch (e) { + return false; + } + } } diff --git a/lib/screen/interactive_postcard/postcard_detail_page.dart b/lib/screen/interactive_postcard/postcard_detail_page.dart index 62cf7a316..2abd14178 100644 --- a/lib/screen/interactive_postcard/postcard_detail_page.dart +++ b/lib/screen/interactive_postcard/postcard_detail_page.dart @@ -564,7 +564,8 @@ class ClaimedPostcardDetailPageState extends State ConfigKey.enable) && (_remoteConfig.getBool(ConfigGroup.merchandise, ConfigKey.allowViewOnly) || - !isViewOnly)) ...[ + !isViewOnly) && + state.showMerch == true) ...[ _postcardPhysical(context, asset), const SizedBox( height: 20, diff --git a/lib/screen/interactive_postcard/postcard_detail_state.dart b/lib/screen/interactive_postcard/postcard_detail_state.dart index e5b10bbf6..9353e9a45 100644 --- a/lib/screen/interactive_postcard/postcard_detail_state.dart +++ b/lib/screen/interactive_postcard/postcard_detail_state.dart @@ -17,6 +17,7 @@ class PostcardDetailState { String? metadataPath; PostcardLeaderboard? leaderboard; bool isFetchingLeaderboard; + bool? showMerch; PostcardDetailState({ required this.provenances, @@ -25,6 +26,7 @@ class PostcardDetailState { this.metadataPath, this.leaderboard, this.isFetchingLeaderboard = false, + this.showMerch, }); ArtworkDetailState toArtworkDetailState() => ArtworkDetailState( @@ -39,6 +41,7 @@ class PostcardDetailState { String? metadataPath, PostcardLeaderboard? leaderboard, bool? isFetchingLeaderboard, + bool? showMerch, }) => PostcardDetailState( assetToken: assetToken ?? this.assetToken, @@ -48,5 +51,6 @@ class PostcardDetailState { leaderboard: leaderboard ?? this.leaderboard, isFetchingLeaderboard: isFetchingLeaderboard ?? this.isFetchingLeaderboard, + showMerch: showMerch ?? this.showMerch, ); } From cdeb8cc43016d5d1bd412baab9553f9c97e7abd1 Mon Sep 17 00:00:00 2001 From: phuoc Date: Thu, 7 Dec 2023 10:07:08 +0700 Subject: [PATCH 02/13] refactor remote config, viewonly Signed-off-by: phuoc --- lib/screen/app_router.dart | 46 ++++------- .../preview_detail/preview_detail_widget.dart | 2 +- .../postcard_detail_bloc.dart | 77 +++++++++++++------ .../postcard_detail_page.dart | 31 +++----- .../postcard_detail_state.dart | 4 + 5 files changed, 85 insertions(+), 75 deletions(-) diff --git a/lib/screen/app_router.dart b/lib/screen/app_router.dart index 961a1752b..1e304bdd6 100644 --- a/lib/screen/app_router.dart +++ b/lib/screen/app_router.dart @@ -220,6 +220,17 @@ class AppRouter { injector(), ); final identityBloc = IdentityBloc(injector(), injector()); + final postcardDetailBloc = PostcardDetailBloc( + injector(), + injector(), + injector(), + injector(), + injector(), + injector(), + injector(), + injector(), + injector(), + ); switch (settings.name) { case viewPlayListPage: @@ -382,15 +393,7 @@ class AppRouter { child: MultiBlocProvider( providers: [ BlocProvider(create: (_) => identityBloc), - BlocProvider( - create: (_) => PostcardDetailBloc( - injector(), - injector(), - injector(), - injector(), - injector(), - injector(), - )), + BlocProvider(create: (_) => postcardDetailBloc), ], child: StampPreview( payload: settings.arguments! as StampPreviewPayload), @@ -587,9 +590,7 @@ class AppRouter { injector(), ), ), - BlocProvider( - create: (_) => PostcardDetailBloc(injector(), injector(), - injector(), injector(), injector(), injector())), + BlocProvider(create: (_) => postcardDetailBloc), ], child: ArtworkPreviewPage( payload: settings.arguments! as ArtworkDetailPayload, @@ -683,15 +684,7 @@ class AppRouter { BlocProvider.value(value: accountsBloc), BlocProvider(create: (_) => identityBloc), BlocProvider(create: (_) => TravelInfoBloc()), - BlocProvider( - create: (_) => PostcardDetailBloc( - injector(), - injector(), - injector(), - injector(), - injector(), - injector(), - )), + BlocProvider(create: (_) => postcardDetailBloc), ], child: ClaimedPostcardDetailPage( key: payload.key, payload: payload))); @@ -1132,16 +1125,7 @@ class AppRouter { child: MultiBlocProvider( providers: [ BlocProvider.value(value: accountsBloc), - BlocProvider( - create: (_) => PostcardDetailBloc( - injector(), - injector(), - injector(), - injector(), - injector(), - injector(), - ), - ), + BlocProvider(create: (_) => postcardDetailBloc), ], child: PostcardLeaderboardPage( payload: settings.arguments! as PostcardLeaderboardPagePayload, diff --git a/lib/screen/detail/preview_detail/preview_detail_widget.dart b/lib/screen/detail/preview_detail/preview_detail_widget.dart index ead02618c..0edb2217a 100644 --- a/lib/screen/detail/preview_detail/preview_detail_widget.dart +++ b/lib/screen/detail/preview_detail/preview_detail_widget.dart @@ -194,7 +194,7 @@ class PostcardPreviewWidget extends StatefulWidget { class _PostcardPreviewWidgetState extends State with WidgetsBindingObserver, RouteAware { - final bloc = PostcardDetailBloc( + final bloc = PostcardDetailBloc(injector(), injector(), injector(), injector(), injector(), injector(), injector(), injector(), injector()); @override diff --git a/lib/screen/interactive_postcard/postcard_detail_bloc.dart b/lib/screen/interactive_postcard/postcard_detail_bloc.dart index 1601443d6..58d451345 100644 --- a/lib/screen/interactive_postcard/postcard_detail_bloc.dart +++ b/lib/screen/interactive_postcard/postcard_detail_bloc.dart @@ -8,7 +8,6 @@ import 'dart:async'; import 'package:autonomy_flutter/au_bloc.dart'; -import 'package:autonomy_flutter/common/injector.dart'; import 'package:autonomy_flutter/gateway/merchandise_api.dart'; import 'package:autonomy_flutter/model/pair.dart'; import 'package:autonomy_flutter/screen/detail/artwork_detail_page.dart'; @@ -16,6 +15,7 @@ import 'package:autonomy_flutter/screen/interactive_postcard/leaderboard/postcar import 'package:autonomy_flutter/screen/interactive_postcard/postcard_detail_state.dart'; import 'package:autonomy_flutter/service/configuration_service.dart'; import 'package:autonomy_flutter/service/postcard_service.dart'; +import 'package:autonomy_flutter/service/remote_config_service.dart'; import 'package:autonomy_flutter/util/asset_token_ext.dart'; import 'package:autonomy_flutter/util/constants.dart'; import 'package:autonomy_flutter/util/distance_formater.dart'; @@ -32,8 +32,10 @@ abstract class PostcardDetailEvent {} class PostcardDetailGetInfoEvent extends PostcardDetailEvent { final ArtworkIdentity identity; final bool useIndexer; + final bool isFromLeaderboard; - PostcardDetailGetInfoEvent(this.identity, {this.useIndexer = false}); + PostcardDetailGetInfoEvent(this.identity, + {this.useIndexer = false, this.isFromLeaderboard = false}); } class FetchLeaderboardEvent extends PostcardDetailEvent {} @@ -48,6 +50,9 @@ class PostcardDetailBloc final IndexerService _indexerService; final PostcardService _postcardService; final ConfigurationService _configurationService; + final TokensService _tokenService; + final MerchandiseApi _merchandiseApi; + final RemoteConfigService _remoteConfig; PostcardDetailBloc( this._assetTokenDao, @@ -56,9 +61,12 @@ class PostcardDetailBloc this._indexerService, this._postcardService, this._configurationService, - ) : super(PostcardDetailState(provenances: [])) { + this._tokenService, + this._merchandiseApi, + this._remoteConfig, + ) : super(PostcardDetailState(provenances: [], isViewOnly: true)) { on((event, emit) async { - if (event.useIndexer) { + if (event.useIndexer || event.isFromLeaderboard) { final request = QueryListTokensRequest( owners: [event.identity.owner], ); @@ -73,22 +81,26 @@ class PostcardDetailBloc assetToken.first.setAssetPrompt(tempsPrompt); } final paths = getUpdatingPath(assetToken.first); + + final isViewOnly = + await _isViewOnly(assetToken.first, event.isFromLeaderboard); emit(state.copyWith( assetToken: assetToken.first, provenances: assetToken.first.provenance, imagePath: paths.first, metadataPath: paths.second, + isViewOnly: isViewOnly, )); - final hasMerch = await _hasMerchProduct(assetToken.first.id); + final hasMerch = + await _showMerchProduct(assetToken.first, isViewOnly); if (hasMerch != state.showMerch) { emit(state.copyWith(showMerch: hasMerch)); } } return; } else { - final tokenService = injector(); - unawaited(tokenService.reindexAddresses([event.identity.owner])); + unawaited(_tokenService.reindexAddresses([event.identity.owner])); final assetToken = await _assetTokenDao.findAssetTokenByIdAndOwner( event.identity.id, event.identity.owner); if (assetToken == null) { @@ -102,19 +114,25 @@ class PostcardDetailBloc assetToken?.setAssetPrompt(tempsPrompt); } final paths = getUpdatingPath(assetToken); + final isViewOnly = + await _isViewOnly(assetToken, event.isFromLeaderboard); emit( state.copyWith( - assetToken: assetToken, - imagePath: paths.first, - metadataPath: paths.second, - ), + assetToken: assetToken, + imagePath: paths.first, + metadataPath: paths.second, + isViewOnly: isViewOnly), ); - final provenances = - await _provenanceDao.findProvenanceByTokenID(event.identity.id); - emit(state.copyWith(provenances: provenances)); + final showProvenances = + _remoteConfig.getBool(ConfigGroup.viewDetail, ConfigKey.provenance); + if (showProvenances) { + final provenances = + await _provenanceDao.findProvenanceByTokenID(event.identity.id); + emit(state.copyWith(provenances: provenances)); + } - final hasMerch = await _hasMerchProduct(assetToken?.id); + final hasMerch = await _showMerchProduct(assetToken, isViewOnly); if (hasMerch != state.showMerch) { emit(state.copyWith(showMerch: hasMerch)); } @@ -181,9 +199,8 @@ class PostcardDetailBloc String? imagePath; String? metadataPath; if (asset != null) { - final postcardService = injector(); final stampingPostcard = - postcardService.getStampingPostcardWithPath(asset.stampingPostcard!); + _postcardService.getStampingPostcardWithPath(asset.stampingPostcard!); final processingStampPostcard = asset.processingStampPostcard; final isStamped = asset.isStamped; if (!isStamped) { @@ -200,7 +217,7 @@ class PostcardDetailBloc } } else { if (stampingPostcard != null) { - unawaited(postcardService + unawaited(_postcardService .updateStampingPostcard([stampingPostcard], isRemove: true)); } if (processingStampPostcard != null) { @@ -213,13 +230,29 @@ class PostcardDetailBloc return Pair(imagePath, metadataPath); } - Future _hasMerchProduct(String? indexId) async { - if (indexId == null) { + Future _isViewOnly(AssetToken? asset, bool isFromLeaderboard) async { + if (asset == null) { + return true; + } + return (await asset.isViewOnly()) || isFromLeaderboard; + } + + Future _showMerchProduct(AssetToken? asset, bool isViewOnly) async { + if (asset == null) { + return false; + } + final isShowConfig = (asset.isCompleted || + !_remoteConfig.getBool( + ConfigGroup.merchandise, ConfigKey.mustCompleted)) && + _remoteConfig.getBool(ConfigGroup.merchandise, ConfigKey.enable) && + (_remoteConfig.getBool( + ConfigGroup.merchandise, ConfigKey.allowViewOnly) || + !isViewOnly); + if (!isShowConfig) { return false; } try { - final merchApi = injector(); - final products = await merchApi.getProducts(indexId); + final products = await _merchandiseApi.getProducts(asset.id); return products.isNotEmpty; } catch (e) { return false; diff --git a/lib/screen/interactive_postcard/postcard_detail_page.dart b/lib/screen/interactive_postcard/postcard_detail_page.dart index 2abd14178..cf6efa040 100644 --- a/lib/screen/interactive_postcard/postcard_detail_page.dart +++ b/lib/screen/interactive_postcard/postcard_detail_page.dart @@ -127,8 +127,8 @@ class ClaimedPostcardDetailPageState extends State context.read().add( PostcardDetailGetInfoEvent( widget.payload.identities[widget.payload.currentIndex], - useIndexer: widget.payload.isFromLeaderboard || - widget.payload.useIndexer), + useIndexer: widget.payload.isFromLeaderboard, + isFromLeaderboard: widget.payload.useIndexer), ); context.read().add(FetchLeaderboardEvent()); context.read().add(FetchAllAddressesEvent()); @@ -322,8 +322,8 @@ class ClaimedPostcardDetailPageState extends State return; } final assetToken = state.assetToken; + if (assetToken != null) { - final viewOnly = isViewOnly || (await assetToken.isViewOnly()); if (!mounted) { return; } @@ -340,10 +340,10 @@ class ClaimedPostcardDetailPageState extends State } setState(() { currentAsset = state.assetToken; - isViewOnly = viewOnly; + isViewOnly = state.isViewOnly; isSending = state.assetToken?.isSending ?? false; }); - if (viewOnly) { + if (isViewOnly) { return; } if (withSharing) { @@ -431,8 +431,8 @@ class ClaimedPostcardDetailPageState extends State child: Semantics( label: 'artworkDotIcon', child: IconButton( - onPressed: () => unawaited( - _showArtworkOptionsDialog(context, asset)), + onPressed: () => unawaited(_showArtworkOptionsDialog( + context, asset, state.isViewOnly)), constraints: const BoxConstraints( maxWidth: 44, maxHeight: 44, @@ -556,16 +556,7 @@ class ClaimedPostcardDetailPageState extends State height: 20, ), ], - if ((asset.isCompleted || - !_remoteConfig.getBool( - ConfigGroup.merchandise, - ConfigKey.mustCompleted)) && - _remoteConfig.getBool(ConfigGroup.merchandise, - ConfigKey.enable) && - (_remoteConfig.getBool(ConfigGroup.merchandise, - ConfigKey.allowViewOnly) || - !isViewOnly) && - state.showMerch == true) ...[ + if (state.showMerch == true) ...[ _postcardPhysical(context, asset), const SizedBox( height: 20, @@ -890,8 +881,7 @@ class ClaimedPostcardDetailPageState extends State ), ), const SizedBox(height: 20), - ] else if (_remoteConfig.getBool( - ConfigGroup.viewDetail, ConfigKey.provenance)) ...[ + ] else ...[ if (provenances.isNotEmpty) PostcardContainer( child: _provenanceView(context, provenances)) @@ -927,9 +917,8 @@ class ClaimedPostcardDetailPageState extends State ); Future _showArtworkOptionsDialog( - BuildContext context, AssetToken asset) async { + BuildContext context, AssetToken asset, bool isViewOnly) async { final theme = Theme.of(context); - final isViewOnly = await asset.isViewOnly(); if (!mounted) { return; } diff --git a/lib/screen/interactive_postcard/postcard_detail_state.dart b/lib/screen/interactive_postcard/postcard_detail_state.dart index 9353e9a45..5cdd9cacf 100644 --- a/lib/screen/interactive_postcard/postcard_detail_state.dart +++ b/lib/screen/interactive_postcard/postcard_detail_state.dart @@ -18,9 +18,11 @@ class PostcardDetailState { PostcardLeaderboard? leaderboard; bool isFetchingLeaderboard; bool? showMerch; + bool isViewOnly; PostcardDetailState({ required this.provenances, + required this.isViewOnly, this.assetToken, this.imagePath, this.metadataPath, @@ -42,6 +44,7 @@ class PostcardDetailState { PostcardLeaderboard? leaderboard, bool? isFetchingLeaderboard, bool? showMerch, + bool? isViewOnly, }) => PostcardDetailState( assetToken: assetToken ?? this.assetToken, @@ -52,5 +55,6 @@ class PostcardDetailState { isFetchingLeaderboard: isFetchingLeaderboard ?? this.isFetchingLeaderboard, showMerch: showMerch ?? this.showMerch, + isViewOnly: isViewOnly ?? this.isViewOnly, ); } From de50fb8d6cd8254d1aab9656dec6dee931eaee84 Mon Sep 17 00:00:00 2001 From: phuoc Date: Thu, 7 Dec 2023 11:55:19 +0700 Subject: [PATCH 03/13] refactor is not owner concept Signed-off-by: phuoc --- .../postcard_detail_bloc.dart | 57 ++++++++++--------- .../postcard_detail_page.dart | 25 ++++---- .../postcard_detail_state.dart | 8 ++- 3 files changed, 48 insertions(+), 42 deletions(-) diff --git a/lib/screen/interactive_postcard/postcard_detail_bloc.dart b/lib/screen/interactive_postcard/postcard_detail_bloc.dart index 58d451345..af0076ad8 100644 --- a/lib/screen/interactive_postcard/postcard_detail_bloc.dart +++ b/lib/screen/interactive_postcard/postcard_detail_bloc.dart @@ -32,10 +32,8 @@ abstract class PostcardDetailEvent {} class PostcardDetailGetInfoEvent extends PostcardDetailEvent { final ArtworkIdentity identity; final bool useIndexer; - final bool isFromLeaderboard; - PostcardDetailGetInfoEvent(this.identity, - {this.useIndexer = false, this.isFromLeaderboard = false}); + PostcardDetailGetInfoEvent(this.identity, {this.useIndexer = false}); } class FetchLeaderboardEvent extends PostcardDetailEvent {} @@ -64,9 +62,9 @@ class PostcardDetailBloc this._tokenService, this._merchandiseApi, this._remoteConfig, - ) : super(PostcardDetailState(provenances: [], isViewOnly: true)) { + ) : super(PostcardDetailState(provenances: [])) { on((event, emit) async { - if (event.useIndexer || event.isFromLeaderboard) { + if (event.useIndexer) { final request = QueryListTokensRequest( owners: [event.identity.owner], ); @@ -82,21 +80,14 @@ class PostcardDetailBloc } final paths = getUpdatingPath(assetToken.first); - final isViewOnly = - await _isViewOnly(assetToken.first, event.isFromLeaderboard); emit(state.copyWith( assetToken: assetToken.first, provenances: assetToken.first.provenance, imagePath: paths.first, metadataPath: paths.second, - isViewOnly: isViewOnly, + showMerch: false, + enableMerch: false, )); - - final hasMerch = - await _showMerchProduct(assetToken.first, isViewOnly); - if (hasMerch != state.showMerch) { - emit(state.copyWith(showMerch: hasMerch)); - } } return; } else { @@ -114,8 +105,7 @@ class PostcardDetailBloc assetToken?.setAssetPrompt(tempsPrompt); } final paths = getUpdatingPath(assetToken); - final isViewOnly = - await _isViewOnly(assetToken, event.isFromLeaderboard); + final isViewOnly = await _isViewOnly(assetToken); emit( state.copyWith( assetToken: assetToken, @@ -132,9 +122,12 @@ class PostcardDetailBloc emit(state.copyWith(provenances: provenances)); } - final hasMerch = await _showMerchProduct(assetToken, isViewOnly); - if (hasMerch != state.showMerch) { - emit(state.copyWith(showMerch: hasMerch)); + final showMerch = + await _showMerchProduct(assetToken, isViewOnly ?? true); + if (showMerch != state.showMerch) { + emit(state.copyWith( + showMerch: showMerch, + enableMerch: showMerch && _enableMerch(assetToken))); } if (assetToken != null && @@ -230,24 +223,22 @@ class PostcardDetailBloc return Pair(imagePath, metadataPath); } - Future _isViewOnly(AssetToken? asset, bool isFromLeaderboard) async { + Future _isViewOnly(AssetToken? asset) async { if (asset == null) { - return true; + return null; } - return (await asset.isViewOnly()) || isFromLeaderboard; + return await asset.isViewOnly(); } Future _showMerchProduct(AssetToken? asset, bool isViewOnly) async { if (asset == null) { return false; } - final isShowConfig = (asset.isCompleted || - !_remoteConfig.getBool( - ConfigGroup.merchandise, ConfigKey.mustCompleted)) && + final isShowConfig = _remoteConfig.getBool(ConfigGroup.merchandise, ConfigKey.enable) && - (_remoteConfig.getBool( - ConfigGroup.merchandise, ConfigKey.allowViewOnly) || - !isViewOnly); + (_remoteConfig.getBool( + ConfigGroup.merchandise, ConfigKey.allowViewOnly) || + !isViewOnly); if (!isShowConfig) { return false; } @@ -255,7 +246,17 @@ class PostcardDetailBloc final products = await _merchandiseApi.getProducts(asset.id); return products.isNotEmpty; } catch (e) { + return true; + } + } + + bool _enableMerch(AssetToken? asset) { + if (asset == null) { return false; } + final isEnable = asset.isCompleted || + !_remoteConfig.getBool( + ConfigGroup.merchandise, ConfigKey.mustCompleted); + return isEnable; } } diff --git a/lib/screen/interactive_postcard/postcard_detail_page.dart b/lib/screen/interactive_postcard/postcard_detail_page.dart index cf6efa040..c4f44334a 100644 --- a/lib/screen/interactive_postcard/postcard_detail_page.dart +++ b/lib/screen/interactive_postcard/postcard_detail_page.dart @@ -98,7 +98,7 @@ class ClaimedPostcardDetailPageState extends State with AfterLayoutMixin { late ScrollController _scrollController; late bool withSharing; - late bool isViewOnly; + late bool isNotOwner; late bool isSending; late bool alreadyShowPopup; late bool isProcessingStampPostcard; @@ -118,7 +118,7 @@ class ClaimedPostcardDetailPageState extends State @override void initState() { _scrollController = ScrollController(); - isViewOnly = widget.payload.isFromLeaderboard; + isNotOwner = widget.payload.isFromLeaderboard; isSending = false; alreadyShowPopup = false; isProcessingStampPostcard = false; @@ -127,8 +127,8 @@ class ClaimedPostcardDetailPageState extends State context.read().add( PostcardDetailGetInfoEvent( widget.payload.identities[widget.payload.currentIndex], - useIndexer: widget.payload.isFromLeaderboard, - isFromLeaderboard: widget.payload.useIndexer), + useIndexer: widget.payload.useIndexer || + widget.payload.isFromLeaderboard), ); context.read().add(FetchLeaderboardEvent()); context.read().add(FetchAllAddressesEvent()); @@ -304,7 +304,8 @@ class ClaimedPostcardDetailPageState extends State if (previous.assetToken?.isCompleted != true && current.assetToken?.isCompleted == true && current.assetToken?.isAlreadyShowYouDidIt == false && - !isViewOnly) { + !isNotOwner && + current.isViewOnly == false) { unawaited(_youDidIt(context, current.assetToken!)); } return true; @@ -340,10 +341,10 @@ class ClaimedPostcardDetailPageState extends State } setState(() { currentAsset = state.assetToken; - isViewOnly = state.isViewOnly; + isNotOwner = isNotOwner || (state.isViewOnly ?? true); isSending = state.assetToken?.isSending ?? false; }); - if (isViewOnly) { + if (isNotOwner) { return; } if (withSharing) { @@ -431,8 +432,8 @@ class ClaimedPostcardDetailPageState extends State child: Semantics( label: 'artworkDotIcon', child: IconButton( - onPressed: () => unawaited(_showArtworkOptionsDialog( - context, asset, state.isViewOnly)), + onPressed: () => unawaited( + _showArtworkOptionsDialog(context, asset)), constraints: const BoxConstraints( maxWidth: 44, maxHeight: 44, @@ -623,7 +624,7 @@ class ClaimedPostcardDetailPageState extends State 'place_15_stamps'.tr(), style: theme.textTheme.moMASans400Black12, ); - if (asset.isCompleted || isViewOnly || !asset.isLastOwner) { + if (asset.isCompleted || isNotOwner || !asset.isLastOwner) { return const SizedBox(); } if (isProcessingStampPostcard || @@ -917,7 +918,7 @@ class ClaimedPostcardDetailPageState extends State ); Future _showArtworkOptionsDialog( - BuildContext context, AssetToken asset, bool isViewOnly) async { + BuildContext context, AssetToken asset) async { final theme = Theme.of(context); if (!mounted) { return; @@ -961,7 +962,7 @@ class ClaimedPostcardDetailPageState extends State Navigator.of(context).pop(); }, ), - if (!isViewOnly) ...[ + if (!isNotOwner) ...[ if (_remoteConfig.getBool( ConfigGroup.feature, ConfigKey.downloadStamp)) OptionItem( diff --git a/lib/screen/interactive_postcard/postcard_detail_state.dart b/lib/screen/interactive_postcard/postcard_detail_state.dart index 5cdd9cacf..07e5c4669 100644 --- a/lib/screen/interactive_postcard/postcard_detail_state.dart +++ b/lib/screen/interactive_postcard/postcard_detail_state.dart @@ -18,17 +18,19 @@ class PostcardDetailState { PostcardLeaderboard? leaderboard; bool isFetchingLeaderboard; bool? showMerch; - bool isViewOnly; + bool? enableMerch; + bool? isViewOnly; PostcardDetailState({ required this.provenances, - required this.isViewOnly, + this.isViewOnly, this.assetToken, this.imagePath, this.metadataPath, this.leaderboard, this.isFetchingLeaderboard = false, this.showMerch, + this.enableMerch, }); ArtworkDetailState toArtworkDetailState() => ArtworkDetailState( @@ -45,6 +47,7 @@ class PostcardDetailState { bool? isFetchingLeaderboard, bool? showMerch, bool? isViewOnly, + bool? enableMerch, }) => PostcardDetailState( assetToken: assetToken ?? this.assetToken, @@ -56,5 +59,6 @@ class PostcardDetailState { isFetchingLeaderboard ?? this.isFetchingLeaderboard, showMerch: showMerch ?? this.showMerch, isViewOnly: isViewOnly ?? this.isViewOnly, + enableMerch: enableMerch ?? this.enableMerch, ); } From 864808977db7add18e969706bbd9061074602cd1 Mon Sep 17 00:00:00 2001 From: phuoc Date: Thu, 7 Dec 2023 11:58:59 +0700 Subject: [PATCH 04/13] fix, refactor Signed-off-by: phuoc --- .../interactive_postcard/postcard_detail_bloc.dart | 11 ++--------- 1 file changed, 2 insertions(+), 9 deletions(-) diff --git a/lib/screen/interactive_postcard/postcard_detail_bloc.dart b/lib/screen/interactive_postcard/postcard_detail_bloc.dart index af0076ad8..e049aa572 100644 --- a/lib/screen/interactive_postcard/postcard_detail_bloc.dart +++ b/lib/screen/interactive_postcard/postcard_detail_bloc.dart @@ -105,7 +105,7 @@ class PostcardDetailBloc assetToken?.setAssetPrompt(tempsPrompt); } final paths = getUpdatingPath(assetToken); - final isViewOnly = await _isViewOnly(assetToken); + final isViewOnly = await assetToken?.isViewOnly(); emit( state.copyWith( assetToken: assetToken, @@ -223,13 +223,6 @@ class PostcardDetailBloc return Pair(imagePath, metadataPath); } - Future _isViewOnly(AssetToken? asset) async { - if (asset == null) { - return null; - } - return await asset.isViewOnly(); - } - Future _showMerchProduct(AssetToken? asset, bool isViewOnly) async { if (asset == null) { return false; @@ -246,7 +239,7 @@ class PostcardDetailBloc final products = await _merchandiseApi.getProducts(asset.id); return products.isNotEmpty; } catch (e) { - return true; + return false; } } From 7e5cf592e2fea79a62a0afa239a1033739ab27ed Mon Sep 17 00:00:00 2001 From: phuoc Date: Thu, 7 Dec 2023 13:52:22 +0700 Subject: [PATCH 05/13] merch button Signed-off-by: phuoc --- .../postcard_detail_page.dart | 66 +++++++++---------- lib/view/postcard_button.dart | 11 +++- 2 files changed, 38 insertions(+), 39 deletions(-) diff --git a/lib/screen/interactive_postcard/postcard_detail_page.dart b/lib/screen/interactive_postcard/postcard_detail_page.dart index c4f44334a..e49ba6033 100644 --- a/lib/screen/interactive_postcard/postcard_detail_page.dart +++ b/lib/screen/interactive_postcard/postcard_detail_page.dart @@ -517,9 +517,7 @@ class ClaimedPostcardDetailPageState extends State }, ), ), - const SizedBox( - height: 15, - ), + const SizedBox(height: 15), Hero( tag: 'detail_${asset.id}', child: Stack( @@ -547,47 +545,34 @@ class ClaimedPostcardDetailPageState extends State ], ), ), - const SizedBox( - height: 20, - ), + const SizedBox(height: 20), if (_remoteConfig.getBool(ConfigGroup.viewDetail, ConfigKey.actionButton)) ...[ _postcardAction(context, asset), - const SizedBox( - height: 20, - ), + const SizedBox(height: 20), ], if (state.showMerch == true) ...[ - _postcardPhysical(context, asset), - const SizedBox( - height: 20, - ), + _postcardPhysical( + context, asset, state.enableMerch ?? false), + const SizedBox(height: 20), ], _postcardInfo(context, asset), - const SizedBox( - height: 20, - ), + const SizedBox(height: 20), if (_remoteConfig.getBool(ConfigGroup.viewDetail, ConfigKey.leaderBoard)) ...[ _postcardLeaderboard( context, state.leaderboard, asset), - const SizedBox( - height: 20, - ), + const SizedBox(height: 20), ], if (_remoteConfig.getBool(ConfigGroup.viewDetail, ConfigKey.aboutMoma)) ...[ _aboutTheProject(context), - const SizedBox( - height: 20, - ), + const SizedBox(height: 20), ], if (_remoteConfig.getBool(ConfigGroup.viewDetail, ConfigKey.glossary)) ...[ _web3Glossary(context, asset), - const SizedBox( - height: 20, - ), + const SizedBox(height: 20), ], _artworkInfo(context, asset, state.provenances, artistNames, owners), @@ -620,10 +605,6 @@ class ClaimedPostcardDetailPageState extends State Widget _postcardAction(final BuildContext context, final AssetToken asset) { final theme = Theme.of(context); - final place15StampsText = Text( - 'place_15_stamps'.tr(), - style: theme.textTheme.moMASans400Black12, - ); if (asset.isCompleted || isNotOwner || !asset.isLastOwner) { return const SizedBox(); } @@ -676,10 +657,6 @@ class ClaimedPostcardDetailPageState extends State style: theme.textTheme.moMASans400Black12, ), ), - Padding( - padding: const EdgeInsets.only(left: 16, right: 15, top: 10), - child: place15StampsText, - ), ]; if (!asset.isFinal) { if (!isSending) { @@ -730,12 +707,19 @@ class ClaimedPostcardDetailPageState extends State return const SizedBox(); } - Widget _postcardPhysical(BuildContext context, AssetToken assetToken) => + Widget _postcardPhysical( + BuildContext context, AssetToken assetToken, bool isEnable) => Column( + crossAxisAlignment: CrossAxisAlignment.start, children: [ PostcardButton( - text: 'unlock_physical_objects'.tr(), - color: POSTCARD_PINK_BUTTON_COLOR, + text: 'buy_merch'.tr(), + enabled: isEnable, + icon: SvgPicture.asset( + isEnable + ? 'assets/images/lock_icon.svg' + : 'assets/images/unlock_icon.svg', + ), onTap: () async { final indexId = assetToken.id; final jwtToken = @@ -753,6 +737,16 @@ class ClaimedPostcardDetailPageState extends State isPlainUI: true, localStorageItems: {'token': jwtToken})); }, ), + if (!isEnable) ...[ + const SizedBox(height: 10), + Padding( + padding: const EdgeInsets.only(left: 16, right: 15), + child: Text( + 'must_complete_to_unlock'.tr(), + style: Theme.of(context).textTheme.moMASans400Black12, + ), + ), + ] ], ); diff --git a/lib/view/postcard_button.dart b/lib/view/postcard_button.dart index 9d9d32d6b..eb64267ed 100644 --- a/lib/view/postcard_button.dart +++ b/lib/view/postcard_button.dart @@ -16,6 +16,7 @@ class PostcardButton extends StatelessWidget { final Color? disabledTextColor; final double? fontSize; final TextStyle? textStyle; + final Widget? icon; const PostcardButton({ super.key, @@ -30,6 +31,7 @@ class PostcardButton extends StatelessWidget { this.disabledTextColor, this.fontSize, this.textStyle, + this.icon, }); @override @@ -69,9 +71,12 @@ class PostcardButton extends StatelessWidget { backgroundColor: theme.colorScheme.surface, strokeWidth: 2, ), - ) - else - const SizedBox(), + ), + if (icon != null) + Padding( + padding: const EdgeInsets.only(right: 8), + child: icon, + ), Text( text ?? '', style: (textStyle ?? theme.textTheme.moMASans700Black18) From 98fd1f0dc64095fc727010bf5bda91d6c7d6bb40 Mon Sep 17 00:00:00 2001 From: phuoc Date: Thu, 7 Dec 2023 13:58:56 +0700 Subject: [PATCH 06/13] lint Signed-off-by: phuoc --- lib/gateway/merchandise_api.dart | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/lib/gateway/merchandise_api.dart b/lib/gateway/merchandise_api.dart index c8bf5900c..4d469b541 100644 --- a/lib/gateway/merchandise_api.dart +++ b/lib/gateway/merchandise_api.dart @@ -8,6 +8,5 @@ abstract class MerchandiseApi { factory MerchandiseApi(Dio dio, {String baseUrl}) = _MerchandiseApi; @GET('/v1/products') - Future getProducts( - @Query('index_id') String indexId); + Future getProducts(@Query('index_id') String indexId); } From 67f9552cd365a4c0bb851c2e9d4fbc1f907b7fac Mon Sep 17 00:00:00 2001 From: phuoc Date: Tue, 12 Dec 2023 14:45:05 +0700 Subject: [PATCH 07/13] show show merch when incomplete Signed-off-by: phuoc --- lib/screen/interactive_postcard/postcard_detail_bloc.dart | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/lib/screen/interactive_postcard/postcard_detail_bloc.dart b/lib/screen/interactive_postcard/postcard_detail_bloc.dart index e049aa572..53957968b 100644 --- a/lib/screen/interactive_postcard/postcard_detail_bloc.dart +++ b/lib/screen/interactive_postcard/postcard_detail_bloc.dart @@ -122,12 +122,11 @@ class PostcardDetailBloc emit(state.copyWith(provenances: provenances)); } - final showMerch = + final showMerch = !_enableMerch(assetToken) || await _showMerchProduct(assetToken, isViewOnly ?? true); if (showMerch != state.showMerch) { emit(state.copyWith( - showMerch: showMerch, - enableMerch: showMerch && _enableMerch(assetToken))); + showMerch: showMerch, enableMerch: _enableMerch(assetToken))); } if (assetToken != null && From 3c38f14253c3dcda7734923f3fae7698f0f5fc8c Mon Sep 17 00:00:00 2001 From: phuoc Date: Tue, 12 Dec 2023 15:25:36 +0700 Subject: [PATCH 08/13] change way to checkout product Signed-off-by: phuoc --- lib/common/environment.dart | 3 - lib/common/injector.dart | 4 - lib/gateway/merchandise_api.dart | 12 --- lib/gateway/merchandise_api.g.dart | 76 ------------------- lib/gateway/postcard_api.dart | 3 + lib/gateway/postcard_api.g.dart | 26 +++++++ lib/screen/app_router.dart | 1 - .../preview_detail/preview_detail_widget.dart | 2 +- .../postcard_detail_bloc.dart | 10 +-- lib/service/postcard_service.dart | 9 +++ .../services/postcard_service_test.mocks.dart | 9 +++ 11 files changed, 52 insertions(+), 103 deletions(-) delete mode 100644 lib/gateway/merchandise_api.dart delete mode 100644 lib/gateway/merchandise_api.g.dart diff --git a/lib/common/environment.dart b/lib/common/environment.dart index 4f6b1a206..e2e027c66 100644 --- a/lib/common/environment.dart +++ b/lib/common/environment.dart @@ -160,9 +160,6 @@ class Environment { static String get merchandiseBaseUrl => dotenv.env['AUTONOMY_MERCHANDISE_BASE_URL'] ?? ''; - static String get merchandiseApiUrl => - dotenv.env['AUTONOMY_MERCHANDISE_API_URL'] ?? ''; - static String get payToMintBaseUrl => dotenv.env['PAY_TO_MINT_BASE_URL'] ?? ''; diff --git a/lib/common/injector.dart b/lib/common/injector.dart index 5a4aefb2f..84476e51a 100644 --- a/lib/common/injector.dart +++ b/lib/common/injector.dart @@ -23,7 +23,6 @@ import 'package:autonomy_flutter/gateway/customer_support_api.dart'; import 'package:autonomy_flutter/gateway/etherchain_api.dart'; import 'package:autonomy_flutter/gateway/feralfile_api.dart'; import 'package:autonomy_flutter/gateway/iap_api.dart'; -import 'package:autonomy_flutter/gateway/merchandise_api.dart'; import 'package:autonomy_flutter/gateway/postcard_api.dart'; import 'package:autonomy_flutter/gateway/pubdoc_api.dart'; import 'package:autonomy_flutter/gateway/tzkt_api.dart'; @@ -297,9 +296,6 @@ Future setup() async { receiveTimeout: const Duration(seconds: 30))), baseUrl: Environment.auClaimAPIURL)); - injector.registerLazySingleton( - () => MerchandiseApi(dio, baseUrl: Environment.merchandiseApiUrl)); - final indexerClient = IndexerClient(Environment.indexerURL); injector.registerLazySingleton( () => IndexerService(indexerClient)); diff --git a/lib/gateway/merchandise_api.dart b/lib/gateway/merchandise_api.dart deleted file mode 100644 index 4d469b541..000000000 --- a/lib/gateway/merchandise_api.dart +++ /dev/null @@ -1,12 +0,0 @@ -import 'package:dio/dio.dart'; -import 'package:retrofit/retrofit.dart'; - -part 'merchandise_api.g.dart'; - -@RestApi(baseUrl: '') -abstract class MerchandiseApi { - factory MerchandiseApi(Dio dio, {String baseUrl}) = _MerchandiseApi; - - @GET('/v1/products') - Future getProducts(@Query('index_id') String indexId); -} diff --git a/lib/gateway/merchandise_api.g.dart b/lib/gateway/merchandise_api.g.dart deleted file mode 100644 index da01190b6..000000000 --- a/lib/gateway/merchandise_api.g.dart +++ /dev/null @@ -1,76 +0,0 @@ -// GENERATED CODE - DO NOT MODIFY BY HAND - -part of 'merchandise_api.dart'; - -// ************************************************************************** -// RetrofitGenerator -// ************************************************************************** - -// ignore_for_file: unnecessary_brace_in_string_interps,no_leading_underscores_for_local_identifiers - -class _MerchandiseApi implements MerchandiseApi { - _MerchandiseApi( - this._dio, { - this.baseUrl, - }); - - final Dio _dio; - - String? baseUrl; - - @override - Future getProducts(String indexId) async { - const _extra = {}; - final queryParameters = {r'index_id': indexId}; - final _headers = {}; - final Map? _data = null; - final _result = await _dio.fetch(_setStreamType(Options( - method: 'GET', - headers: _headers, - extra: _extra, - ) - .compose( - _dio.options, - '/v1/products', - queryParameters: queryParameters, - data: _data, - ) - .copyWith( - baseUrl: _combineBaseUrls( - _dio.options.baseUrl, - baseUrl, - )))); - final value = _result.data; - return value; - } - - RequestOptions _setStreamType(RequestOptions requestOptions) { - if (T != dynamic && - !(requestOptions.responseType == ResponseType.bytes || - requestOptions.responseType == ResponseType.stream)) { - if (T == String) { - requestOptions.responseType = ResponseType.plain; - } else { - requestOptions.responseType = ResponseType.json; - } - } - return requestOptions; - } - - String _combineBaseUrls( - String dioBaseUrl, - String? baseUrl, - ) { - if (baseUrl == null || baseUrl.trim().isEmpty) { - return dioBaseUrl; - } - - final url = Uri.parse(baseUrl); - - if (url.isAbsolute) { - return url.toString(); - } - - return Uri.parse(dioBaseUrl).resolveUri(url).toString(); - } -} diff --git a/lib/gateway/postcard_api.dart b/lib/gateway/postcard_api.dart index 8899a1d5c..0493742a0 100644 --- a/lib/gateway/postcard_api.dart +++ b/lib/gateway/postcard_api.dart @@ -31,6 +31,9 @@ abstract class PostcardApi { @GET('/v1/postcard/claim/{share_code}') Future claimShareCode(@Path('share_code') String shareCode); + @GET('/v1/postcard/{token_id}/merchandise_enabled') + Future getMerchandiseEnable(@Path('token_id') String tokenId); + @MultiPart() @POST('/v1/postcard/{token_id}/stamp') Future updatePostcard({ diff --git a/lib/gateway/postcard_api.g.dart b/lib/gateway/postcard_api.g.dart index 5ab77f45a..101aa0899 100644 --- a/lib/gateway/postcard_api.g.dart +++ b/lib/gateway/postcard_api.g.dart @@ -158,6 +158,32 @@ class _PostcardApi implements PostcardApi { return value; } + @override + Future getMerchandiseEnable(String tokenId) async { + const _extra = {}; + final queryParameters = {}; + final _headers = {}; + final Map? _data = null; + final _result = await _dio.fetch(_setStreamType(Options( + method: 'GET', + headers: _headers, + extra: _extra, + ) + .compose( + _dio.options, + '/v1/postcard/${tokenId}/merchandise_enabled', + queryParameters: queryParameters, + data: _data, + ) + .copyWith( + baseUrl: _combineBaseUrls( + _dio.options.baseUrl, + baseUrl, + )))); + final value = _result.data!; + return value; + } + @override Future updatePostcard({ required String tokenId, diff --git a/lib/screen/app_router.dart b/lib/screen/app_router.dart index 1e304bdd6..10d5def54 100644 --- a/lib/screen/app_router.dart +++ b/lib/screen/app_router.dart @@ -229,7 +229,6 @@ class AppRouter { injector(), injector(), injector(), - injector(), ); switch (settings.name) { diff --git a/lib/screen/detail/preview_detail/preview_detail_widget.dart b/lib/screen/detail/preview_detail/preview_detail_widget.dart index 0edb2217a..f10f43552 100644 --- a/lib/screen/detail/preview_detail/preview_detail_widget.dart +++ b/lib/screen/detail/preview_detail/preview_detail_widget.dart @@ -195,7 +195,7 @@ class PostcardPreviewWidget extends StatefulWidget { class _PostcardPreviewWidgetState extends State with WidgetsBindingObserver, RouteAware { final bloc = PostcardDetailBloc(injector(), injector(), injector(), - injector(), injector(), injector(), injector(), injector(), injector()); + injector(), injector(), injector(), injector(), injector()); @override void initState() { diff --git a/lib/screen/interactive_postcard/postcard_detail_bloc.dart b/lib/screen/interactive_postcard/postcard_detail_bloc.dart index 53957968b..feed1d689 100644 --- a/lib/screen/interactive_postcard/postcard_detail_bloc.dart +++ b/lib/screen/interactive_postcard/postcard_detail_bloc.dart @@ -8,7 +8,6 @@ import 'dart:async'; import 'package:autonomy_flutter/au_bloc.dart'; -import 'package:autonomy_flutter/gateway/merchandise_api.dart'; import 'package:autonomy_flutter/model/pair.dart'; import 'package:autonomy_flutter/screen/detail/artwork_detail_page.dart'; import 'package:autonomy_flutter/screen/interactive_postcard/leaderboard/postcard_leaderboard.dart'; @@ -49,7 +48,6 @@ class PostcardDetailBloc final PostcardService _postcardService; final ConfigurationService _configurationService; final TokensService _tokenService; - final MerchandiseApi _merchandiseApi; final RemoteConfigService _remoteConfig; PostcardDetailBloc( @@ -60,7 +58,6 @@ class PostcardDetailBloc this._postcardService, this._configurationService, this._tokenService, - this._merchandiseApi, this._remoteConfig, ) : super(PostcardDetailState(provenances: [])) { on((event, emit) async { @@ -122,7 +119,7 @@ class PostcardDetailBloc emit(state.copyWith(provenances: provenances)); } - final showMerch = !_enableMerch(assetToken) || + final showMerch = await _showMerchProduct(assetToken, isViewOnly ?? true); if (showMerch != state.showMerch) { emit(state.copyWith( @@ -235,8 +232,9 @@ class PostcardDetailBloc return false; } try { - final products = await _merchandiseApi.getProducts(asset.id); - return products.isNotEmpty; + final enableMerch = + await _postcardService.isMerchandiseEnable(asset.tokenId ?? ''); + return enableMerch; } catch (e) { return false; } diff --git a/lib/service/postcard_service.dart b/lib/service/postcard_service.dart index 3d5e1a1e9..92cb8636b 100644 --- a/lib/service/postcard_service.dart +++ b/lib/service/postcard_service.dart @@ -92,6 +92,8 @@ abstract class PostcardService { required int stampIndex, }); + Future isMerchandiseEnable(String tokenId); + Future downloadPostcard(String tokenId); String getTokenId(String id); @@ -381,6 +383,13 @@ class PostcardServiceImpl extends PostcardService { await sharePostcard(asset); } + @override + Future isMerchandiseEnable(String tokenId) async { + final response = await _postcardApi.getMerchandiseEnable(tokenId); + final result = jsonDecode(response)['enabled'] as bool; + return result; + } + @override Future fetchPostcardLeaderboard( {required String unit, required int size, required int offset}) async { diff --git a/test/services/postcard_service_test.mocks.dart b/test/services/postcard_service_test.mocks.dart index b364ddd79..aa6a6ce6d 100644 --- a/test/services/postcard_service_test.mocks.dart +++ b/test/services/postcard_service_test.mocks.dart @@ -296,6 +296,15 @@ class MockPostcardApi extends _i1.Mock implements _i4.PostcardApi { returnValue: _i15.Future.value(), ) as _i15.Future); @override + _i15.Future getMerchandiseEnable(String? tokenId) => + (super.noSuchMethod( + Invocation.method( + #getMerchandiseEnable, + [tokenId], + ), + returnValue: _i15.Future.value(''), + ) as _i15.Future); + @override _i15.Future updatePostcard({ required String? tokenId, required _i16.File? data, From c2445f4f1c8ea99697ed81fe49e7326254157c55 Mon Sep 17 00:00:00 2001 From: phuoc Date: Wed, 13 Dec 2023 11:07:31 +0700 Subject: [PATCH 09/13] switch lock icon Signed-off-by: phuoc --- lib/screen/interactive_postcard/postcard_detail_page.dart | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/screen/interactive_postcard/postcard_detail_page.dart b/lib/screen/interactive_postcard/postcard_detail_page.dart index e49ba6033..f36554c38 100644 --- a/lib/screen/interactive_postcard/postcard_detail_page.dart +++ b/lib/screen/interactive_postcard/postcard_detail_page.dart @@ -717,8 +717,8 @@ class ClaimedPostcardDetailPageState extends State enabled: isEnable, icon: SvgPicture.asset( isEnable - ? 'assets/images/lock_icon.svg' - : 'assets/images/unlock_icon.svg', + ? 'assets/images/unlock_icon.svg' + : 'assets/images/lock_icon.svg', ), onTap: () async { final indexId = assetToken.id; From 3446fc2c73b17ffdfe45e69e9eb96dfaed02fb2f Mon Sep 17 00:00:00 2001 From: phuoc Date: Wed, 13 Dec 2023 16:52:51 +0700 Subject: [PATCH 10/13] fix add prompt to metadata Signed-off-by: phuoc --- .../claim_empty_postcard_bloc.dart | 9 +-------- lib/service/postcard_service.dart | 19 +++++++++++++------ 2 files changed, 14 insertions(+), 14 deletions(-) diff --git a/lib/screen/interactive_postcard/claim_empty_postcard/claim_empty_postcard_bloc.dart b/lib/screen/interactive_postcard/claim_empty_postcard/claim_empty_postcard_bloc.dart index 367fe7d1e..2b26ecdb3 100644 --- a/lib/screen/interactive_postcard/claim_empty_postcard/claim_empty_postcard_bloc.dart +++ b/lib/screen/interactive_postcard/claim_empty_postcard/claim_empty_postcard_bloc.dart @@ -5,7 +5,6 @@ import 'package:autonomy_flutter/common/environment.dart'; import 'package:autonomy_flutter/common/injector.dart'; import 'package:autonomy_flutter/model/postcard_claim.dart'; import 'package:autonomy_flutter/model/postcard_metadata.dart'; -import 'package:autonomy_flutter/model/prompt.dart'; import 'package:autonomy_flutter/screen/app_router.dart'; import 'package:autonomy_flutter/service/account_service.dart'; import 'package:autonomy_flutter/service/configuration_service.dart'; @@ -35,13 +34,7 @@ class ClaimEmptyPostCardBloc on((event, emit) async { final indexId = event.claimRequest.tokenId; final tokenId = 'tez-${Environment.postcardContractAddress}-$indexId'; - late final Prompt? prompt; - try { - final prompts = await _postcardService.getPrompts(indexId); - prompt = prompts.isNotEmpty ? prompts.first : null; - } catch (e) { - prompt = null; - } + final prompt = await _postcardService.getPrompt(indexId); final postcardMetadata = PostcardMetadata( prompt: prompt, locationInformation: [], diff --git a/lib/service/postcard_service.dart b/lib/service/postcard_service.dart index 92cb8636b..cf37ba6f6 100644 --- a/lib/service/postcard_service.dart +++ b/lib/service/postcard_service.dart @@ -112,7 +112,7 @@ abstract class PostcardService { Future finalizeStamp(AssetToken asset, String imagePath, String metadataPath, Location location); - Future> getPrompts(String tokenId); + Future getPrompt(String tokenId); } class PostcardServiceImpl extends PostcardService { @@ -538,7 +538,9 @@ class PostcardServiceImpl extends PostcardService { 'postcardId': result.tokenID, })); final tokenID = 'tez-${result.contractAddress}-${result.tokenID}'; + final prompt = await getPrompt(result.tokenID ?? ''); final postcardMetadata = PostcardMetadata( + prompt: prompt, locationInformation: [], ); final token = AssetToken( @@ -550,7 +552,7 @@ class PostcardServiceImpl extends PostcardService { title: requestPostcardResponse.name, previewURL: requestPostcardResponse.previewURL, source: 'postcard', - artworkMetadata: jsonEncode(postcardMetadata.toJson()), + artworkMetadata: jsonEncode(postcardMetadata), medium: 'software', ), blockchain: 'tezos', @@ -688,10 +690,15 @@ class PostcardServiceImpl extends PostcardService { } @override - Future> getPrompts(String tokenId) async { - final prompts = await _postcardApi.getPrompts(tokenId); - log.info('[POSTCARD] getPrompts: $prompts'); - return prompts; + Future getPrompt(String tokenId) async { + try { + final prompts = await _postcardApi.getPrompts(tokenId); + log.info('[POSTCARD] getPrompts: $prompts'); + final prompt = prompts.isNotEmpty ? prompts.first : null; + return prompt; + } catch (e) { + return null; + } } } From ce1bed0c967833a8630ab5d50eb76c98f8f799ac Mon Sep 17 00:00:00 2001 From: phuoc Date: Wed, 13 Dec 2023 16:56:53 +0700 Subject: [PATCH 11/13] resolve conflict Signed-off-by: phuoc --- lib/screen/interactive_postcard/postcard_detail_page.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/screen/interactive_postcard/postcard_detail_page.dart b/lib/screen/interactive_postcard/postcard_detail_page.dart index c293018f5..2e64f7fe8 100644 --- a/lib/screen/interactive_postcard/postcard_detail_page.dart +++ b/lib/screen/interactive_postcard/postcard_detail_page.dart @@ -452,7 +452,7 @@ class ClaimedPostcardDetailPageState extends State } if (assetToken.isShareExpired && - (assetToken.isLastOwner && !isViewOnly)) { + (assetToken.isLastOwner && !isNotOwner)) { if (!mounted) { return; } From d21d5a91bc41016ceb07cfc30e548119ae65a977 Mon Sep 17 00:00:00 2001 From: phuoc Date: Wed, 13 Dec 2023 17:47:16 +0700 Subject: [PATCH 12/13] onboarding pading, style Signed-off-by: phuoc --- .../interactive_postcard/postcard_explain.dart | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/lib/screen/interactive_postcard/postcard_explain.dart b/lib/screen/interactive_postcard/postcard_explain.dart index 21c4d1930..0c0435b8d 100644 --- a/lib/screen/interactive_postcard/postcard_explain.dart +++ b/lib/screen/interactive_postcard/postcard_explain.dart @@ -104,7 +104,7 @@ class _PostcardExplainState extends State { padding: const EdgeInsets.only(bottom: 32), child: Column( children: [ - const SizedBox(height: 25), + const SizedBox(height: 40), Padding( padding: const EdgeInsets.only(left: 15, right: 15), child: Row( @@ -138,7 +138,7 @@ class _PostcardExplainState extends State { ], ), ), - const SizedBox(height: 80), + const SizedBox(height: 25), Expanded( child: _viewClaimPage ? Padding( @@ -157,7 +157,7 @@ class _PostcardExplainState extends State { }); }, itemBuilder: (context, index) => Padding( - padding: padding, + padding: padding.copyWith(top: 40), child: pages[index], ), itemCount: swiperSize, @@ -223,12 +223,12 @@ class _PostcardExplainState extends State { children: [ Text( 'moma_project_invite'.tr(), - style: theme.textTheme.moMASans400Black18, + style: theme.textTheme.moMASans400Black14, ), - const SizedBox(height: 8), + const SizedBox(height: 20), Text( 'with_15_blank_stamps'.tr(), - style: theme.textTheme.moMASans400Black18, + style: theme.textTheme.moMASans400Black14, ), const SizedBox(height: 40), Text.rich( From 27fa6f933358ec946fb3f4de6b39920c877ead51 Mon Sep 17 00:00:00 2001 From: phuoc Date: Thu, 14 Dec 2023 09:12:17 +0700 Subject: [PATCH 13/13] refactor: mpve to ext Signed-off-by: phuoc --- .../interactive_postcard/postcard_detail_bloc.dart | 13 ++----------- lib/util/asset_token_ext.dart | 8 ++++++++ 2 files changed, 10 insertions(+), 11 deletions(-) diff --git a/lib/screen/interactive_postcard/postcard_detail_bloc.dart b/lib/screen/interactive_postcard/postcard_detail_bloc.dart index feed1d689..83212fcf5 100644 --- a/lib/screen/interactive_postcard/postcard_detail_bloc.dart +++ b/lib/screen/interactive_postcard/postcard_detail_bloc.dart @@ -123,7 +123,8 @@ class PostcardDetailBloc await _showMerchProduct(assetToken, isViewOnly ?? true); if (showMerch != state.showMerch) { emit(state.copyWith( - showMerch: showMerch, enableMerch: _enableMerch(assetToken))); + showMerch: showMerch, + enableMerch: assetToken?.enabledMerch ?? false)); } if (assetToken != null && @@ -239,14 +240,4 @@ class PostcardDetailBloc return false; } } - - bool _enableMerch(AssetToken? asset) { - if (asset == null) { - return false; - } - final isEnable = asset.isCompleted || - !_remoteConfig.getBool( - ConfigGroup.merchandise, ConfigKey.mustCompleted); - return isEnable; - } } diff --git a/lib/util/asset_token_ext.dart b/lib/util/asset_token_ext.dart index 7894b3002..c05a997d2 100644 --- a/lib/util/asset_token_ext.dart +++ b/lib/util/asset_token_ext.dart @@ -15,6 +15,7 @@ import 'package:autonomy_flutter/screen/detail/artwork_detail_page.dart'; import 'package:autonomy_flutter/screen/interactive_postcard/stamp_preview.dart'; import 'package:autonomy_flutter/service/configuration_service.dart'; import 'package:autonomy_flutter/service/postcard_service.dart'; +import 'package:autonomy_flutter/service/remote_config_service.dart'; import 'package:autonomy_flutter/util/constants.dart'; import 'package:autonomy_flutter/util/feralfile_extension.dart'; import 'package:autonomy_flutter/util/log.dart'; @@ -768,4 +769,11 @@ extension PostcardExtension on AssetToken { return sharedPostcards.any((element) => element.owner == owner && element.tokenID == id && element.isExpired); } + + bool get enabledMerch { + final remoteConfig = injector(); + final isEnable = isCompleted || + !remoteConfig.getBool(ConfigGroup.merchandise, ConfigKey.mustCompleted); + return isEnable; + } }