diff --git a/lib/bootstrap.dart b/lib/bootstrap.dart index a8195ea..a102004 100644 --- a/lib/bootstrap.dart +++ b/lib/bootstrap.dart @@ -12,7 +12,7 @@ import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:flutter_native_splash/flutter_native_splash.dart'; import 'package:get_it/get_it.dart'; -import 'package:launch_review_service/launch_review_service.dart'; +import 'package:in_app_review_service/in_app_review_service.dart'; import 'package:package_info_plus/package_info_plus.dart'; import 'package:path/path.dart'; import 'package:path_provider/path_provider.dart'; @@ -132,11 +132,31 @@ Future setup() async { repository: getIt(), ), ) + ..registerLazySingleton( + () => ObjectBoxMergeStatisticsService( + service: getIt(), + ), + ) + ..registerLazySingleton( + () => MergeStatisticsRepositoryImpl( + localService: getIt(), + ), + ) + ..registerLazySingleton( + () => GetMergeStatisticsUseCase( + repository: getIt(), + ), + ) + ..registerLazySingleton( + () => SaveMergeStatisticsUseCase( + repository: getIt(), + ), + ) ..registerLazySingleton( () => packageInfo, ) - ..registerLazySingleton( - () => LaunchReviewService(androidAppId: getIt().packageName), + ..registerLazySingleton( + InAppReviewService.new, ) ..registerLazySingleton( () => const UrlLauncherService(), diff --git a/lib/src/core/gen/objectbox-model.json b/lib/src/core/gen/objectbox-model.json index d97ebd0..8d0c4be 100644 --- a/lib/src/core/gen/objectbox-model.json +++ b/lib/src/core/gen/objectbox-model.json @@ -60,9 +60,33 @@ } ], "relations": [] + }, + { + "id": "3:6752277543447190155", + "lastPropertyId": "5:7060629709470016846", + "name": "LocalMergeStatistics", + "properties": [ + { + "id": "1:5740094858668497488", + "name": "id", + "type": 6, + "flags": 1 + }, + { + "id": "4:7986270554451540890", + "name": "successMergeCount", + "type": 6 + }, + { + "id": "5:7060629709470016846", + "name": "failedMergeCount", + "type": 6 + } + ], + "relations": [] } ], - "lastEntityId": "2:4520518054235479906", + "lastEntityId": "3:6752277543447190155", "lastIndexId": "0:0", "lastRelationId": "0:0", "lastSequenceId": "0:0", @@ -70,7 +94,10 @@ "modelVersionParserMinimum": 5, "retiredEntityUids": [], "retiredIndexUids": [], - "retiredPropertyUids": [], + "retiredPropertyUids": [ + 3379488580577270059, + 4991885907385683044 + ], "retiredRelationUids": [], "version": 1 } \ No newline at end of file diff --git a/lib/src/core/gen/objectbox.g.dart b/lib/src/core/gen/objectbox.g.dart index 6dbc30a..9a884d6 100644 --- a/lib/src/core/gen/objectbox.g.dart +++ b/lib/src/core/gen/objectbox.g.dart @@ -17,6 +17,7 @@ import 'package:objectbox_flutter_libs/objectbox_flutter_libs.dart'; import '../../../src/app/data/models/local_theme_configuration.dart'; import '../../../src/features/merge/data/models/local_merge_settings.dart'; +import '../../../src/features/merge/data/models/local_merge_statistics.dart'; export 'package:objectbox/objectbox.dart'; // so that callers only have to import this file @@ -78,6 +79,30 @@ final _entities = [ flags: 0) ], relations: [], + backlinks: []), + obx_int.ModelEntity( + id: const obx_int.IdUid(3, 6752277543447190155), + name: 'LocalMergeStatistics', + lastPropertyId: const obx_int.IdUid(5, 7060629709470016846), + flags: 0, + properties: [ + obx_int.ModelProperty( + id: const obx_int.IdUid(1, 5740094858668497488), + name: 'id', + type: 6, + flags: 1), + obx_int.ModelProperty( + id: const obx_int.IdUid(4, 7986270554451540890), + name: 'successMergeCount', + type: 6, + flags: 0), + obx_int.ModelProperty( + id: const obx_int.IdUid(5, 7060629709470016846), + name: 'failedMergeCount', + type: 6, + flags: 0) + ], + relations: [], backlinks: []) ]; @@ -116,13 +141,13 @@ Future openStore( obx_int.ModelDefinition getObjectBoxModel() { final model = obx_int.ModelInfo( entities: _entities, - lastEntityId: const obx_int.IdUid(2, 4520518054235479906), + lastEntityId: const obx_int.IdUid(3, 6752277543447190155), lastIndexId: const obx_int.IdUid(0, 0), lastRelationId: const obx_int.IdUid(0, 0), lastSequenceId: const obx_int.IdUid(0, 0), retiredEntityUids: const [], retiredIndexUids: const [], - retiredPropertyUids: const [], + retiredPropertyUids: const [3379488580577270059, 4991885907385683044], retiredRelationUids: const [], modelVersion: 5, modelVersionParserMinimum: 5, @@ -197,6 +222,35 @@ obx_int.ModelDefinition getObjectBoxModel() { ..mainColor = const fb.StringReader(asciiOptimization: true) .vTableGet(buffer, rootOffset, 8, ''); + return object; + }), + LocalMergeStatistics: obx_int.EntityDefinition( + model: _entities[2], + toOneRelations: (LocalMergeStatistics object) => [], + toManyRelations: (LocalMergeStatistics object) => {}, + getId: (LocalMergeStatistics object) => object.id, + setId: (LocalMergeStatistics object, int id) { + object.id = id; + }, + objectToFB: (LocalMergeStatistics object, fb.Builder fbb) { + fbb.startTable(6); + fbb.addInt64(0, object.id); + fbb.addInt64(3, object.successMergeCount); + fbb.addInt64(4, object.failedMergeCount); + fbb.finish(fbb.endTable()); + return object.id; + }, + objectFromFB: (obx.Store store, ByteData fbData) { + final buffer = fb.BufferContext(fbData); + final rootOffset = buffer.derefObject(0); + + final object = LocalMergeStatistics() + ..id = const fb.Int64Reader().vTableGet(buffer, rootOffset, 4, 0) + ..successMergeCount = + const fb.Int64Reader().vTableGet(buffer, rootOffset, 10, 0) + ..failedMergeCount = + const fb.Int64Reader().vTableGet(buffer, rootOffset, 12, 0); + return object; }) }; @@ -241,3 +295,20 @@ class LocalThemeConfiguration_ { static final mainColor = obx.QueryStringProperty( _entities[1].properties[2]); } + +/// [LocalMergeStatistics] entity fields to define ObjectBox queries. +class LocalMergeStatistics_ { + /// see [LocalMergeStatistics.id] + static final id = obx.QueryIntegerProperty( + _entities[2].properties[0]); + + /// see [LocalMergeStatistics.successMergeCount] + static final successMergeCount = + obx.QueryIntegerProperty( + _entities[2].properties[1]); + + /// see [LocalMergeStatistics.failedMergeCount] + static final failedMergeCount = + obx.QueryIntegerProperty( + _entities[2].properties[2]); +} diff --git a/lib/src/core/l10n/app_localizations.dart b/lib/src/core/l10n/app_localizations.dart index 18d7224..231c8d1 100644 --- a/lib/src/core/l10n/app_localizations.dart +++ b/lib/src/core/l10n/app_localizations.dart @@ -212,6 +212,12 @@ abstract class AppLocalizations { /// **'Could not open privacy policy! Please check your internet connection and try again.'** String get couldNotOpenPrivacyPolicyMessage; + /// No description provided for @couldNotOpenStoreListingMessage. + /// + /// In en, this message translates to: + /// **'Could not open store listing! Please check your internet connection and try again.'** + String get couldNotOpenStoreListingMessage; + /// No description provided for @couldNotOpenTermsAndConditionsMessage. /// /// In en, this message translates to: @@ -236,12 +242,6 @@ abstract class AppLocalizations { /// **'Could not launch email service! Please check your internet connection and try again.'** String get couldNotLaunchEmailServiceMessage; - /// No description provided for @couldNotLaunchReviewServiceMessage. - /// - /// In en, this message translates to: - /// **'Could not launch review service! Please check your internet connection and try again.'** - String get couldNotLaunchReviewServiceMessage; - /// No description provided for @couldNotSeekVideoPositionMessage. /// /// In en, this message translates to: diff --git a/lib/src/core/l10n/app_localizations_en.dart b/lib/src/core/l10n/app_localizations_en.dart index 3bffa1b..75525ff 100644 --- a/lib/src/core/l10n/app_localizations_en.dart +++ b/lib/src/core/l10n/app_localizations_en.dart @@ -71,6 +71,10 @@ class AppLocalizationsEn extends AppLocalizations { String get couldNotOpenPrivacyPolicyMessage => 'Could not open privacy policy! Please check your internet connection and try again.'; + @override + String get couldNotOpenStoreListingMessage => + 'Could not open store listing! Please check your internet connection and try again.'; + @override String get couldNotOpenTermsAndConditionsMessage => 'Could not open terms and conditions! Please check your internet connection and try again.'; @@ -87,10 +91,6 @@ class AppLocalizationsEn extends AppLocalizations { String get couldNotLaunchEmailServiceMessage => 'Could not launch email service! Please check your internet connection and try again.'; - @override - String get couldNotLaunchReviewServiceMessage => - 'Could not launch review service! Please check your internet connection and try again.'; - @override String get couldNotSeekVideoPositionMessage => 'Could not seek video position! Please try again later.'; diff --git a/lib/src/core/l10n/arb/app_en.arb b/lib/src/core/l10n/arb/app_en.arb index 98ce329..a26939f 100644 --- a/lib/src/core/l10n/arb/app_en.arb +++ b/lib/src/core/l10n/arb/app_en.arb @@ -20,11 +20,11 @@ "couldNotLoadVideosMessage": "Could not load videos! Please confirm that video files are valid.", "couldNotOpenAssetPickerMessage": "Could not open the asset picker! Please grant the necessary permissions.", "couldNotOpenPrivacyPolicyMessage": "Could not open privacy policy! Please check your internet connection and try again.", + "couldNotOpenStoreListingMessage": "Could not open store listing! Please check your internet connection and try again.", "couldNotOpenTermsAndConditionsMessage": "Could not open terms and conditions! Please check your internet connection and try again.", "couldNotPauseVideoMessage": "Could not pause video! Please try again later.", "couldNotPlayVideoMessage": "Could not play video! Please confirm that video files are valid.", "couldNotLaunchEmailServiceMessage": "Could not launch email service! Please check your internet connection and try again.", - "couldNotLaunchReviewServiceMessage": "Could not launch review service! Please check your internet connection and try again.", "couldNotSeekVideoPositionMessage": "Could not seek video position! Please try again later.", "couldNotSetVideoSpeedMessage": "Could not set video playback speed! Please try again later.", "cyan": "Cyan", diff --git a/lib/src/features/merge/data/data_sources/data_sources.dart b/lib/src/features/merge/data/data_sources/data_sources.dart index adc5598..f018035 100644 --- a/lib/src/features/merge/data/data_sources/data_sources.dart +++ b/lib/src/features/merge/data/data_sources/data_sources.dart @@ -3,3 +3,4 @@ // in the LICENSE file. export 'merge_settings_service/merge_settings_service.dart'; +export 'merge_statistics_service/merge_statistics_service.dart'; diff --git a/lib/src/features/merge/data/data_sources/merge_settings_service/local_merge_settings_service.dart b/lib/src/features/merge/data/data_sources/merge_settings_service/local_merge_settings_service.dart index ce4980a..c67bf32 100644 --- a/lib/src/features/merge/data/data_sources/merge_settings_service/local_merge_settings_service.dart +++ b/lib/src/features/merge/data/data_sources/merge_settings_service/local_merge_settings_service.dart @@ -8,8 +8,7 @@ import 'package:vmerge/src/features/merge/merge.dart'; /// An interface that inherits from [MergeSettingsService] and defines the /// requirements for implementations that use a local service. /// -/// The [LocalMergeSettings] type is the model -/// type. +/// The [LocalMergeSettings] type is the model type. /// {@endtemplate} abstract interface class LocalMergeSettingsService implements MergeSettingsService { diff --git a/lib/src/features/merge/data/data_sources/merge_settings_service/object_box_merge_settings_service.dart b/lib/src/features/merge/data/data_sources/merge_settings_service/object_box_merge_settings_service.dart index 59abb14..16e060e 100644 --- a/lib/src/features/merge/data/data_sources/merge_settings_service/object_box_merge_settings_service.dart +++ b/lib/src/features/merge/data/data_sources/merge_settings_service/object_box_merge_settings_service.dart @@ -22,6 +22,12 @@ final class ObjectBoxMergeSettingsService implements LocalMergeSettingsService { final box = _service.store.box(); final query = box.query().build(); final entities = query.find(); + + if (entities.isEmpty) { + query.close(); + return saveMergeSettings(LocalMergeSettings()); + } + final settings = entities.first; query.close(); @@ -30,7 +36,9 @@ final class ObjectBoxMergeSettingsService implements LocalMergeSettingsService { /// Saves the [LocalMergeSettings]. @override - Future saveMergeSettings(LocalMergeSettings settings) async { + Future saveMergeSettings( + LocalMergeSettings settings, + ) async { final box = _service.store.box(); final query = box.query().build(); final entities = query.find(); @@ -39,6 +47,6 @@ final class ObjectBoxMergeSettingsService implements LocalMergeSettingsService { if (oldSettings != null) settings.id = oldSettings.id; - box.put(settings); + return box.putAndGetAsync(settings); } } diff --git a/lib/src/features/merge/data/data_sources/merge_statistics_service/local_merge_statistics_service.dart b/lib/src/features/merge/data/data_sources/merge_statistics_service/local_merge_statistics_service.dart new file mode 100644 index 0000000..206dc4f --- /dev/null +++ b/lib/src/features/merge/data/data_sources/merge_statistics_service/local_merge_statistics_service.dart @@ -0,0 +1,17 @@ +// Copyright 2024 BBK Development. All rights reserved. +// Use of this source code is governed by a GPL-style license that can be found +// in the LICENSE file. + +import 'package:vmerge/src/features/merge/merge.dart'; + +/// {@template local_merge_statistics_service} +/// An interface that inherits from [MergeStatisticsService] and defines the +/// requirements for implementations that use a local service. +/// +/// The [LocalMergeStatistics] type is the model type. +/// {@endtemplate} +abstract interface class LocalMergeStatisticsService + implements MergeStatisticsService { + /// {@macro local_merge_statistics_service} + const LocalMergeStatisticsService(); +} diff --git a/lib/src/features/merge/data/data_sources/merge_statistics_service/merge_statistics_service.dart b/lib/src/features/merge/data/data_sources/merge_statistics_service/merge_statistics_service.dart new file mode 100644 index 0000000..bd62d7c --- /dev/null +++ b/lib/src/features/merge/data/data_sources/merge_statistics_service/merge_statistics_service.dart @@ -0,0 +1,21 @@ +// Copyright 2024 BBK Development. All rights reserved. +// Use of this source code is governed by a GPL-style license that can be found +// in the LICENSE file. + +export 'local_merge_statistics_service.dart'; +export 'object_box_merge_statistics_service.dart'; + +/// {@template merge_statistics_service} +/// An interface that defines the requirements for implementations that provide +/// merge statistics. +/// +/// The [M] type is the model type. +/// {@endtemplate} +abstract interface class MergeStatisticsService { + /// {@macro merge_statistics_service} + const MergeStatisticsService(); + + Future getMergeStatistics(); + + Future saveMergeStatistics(M statistics); +} diff --git a/lib/src/features/merge/data/data_sources/merge_statistics_service/object_box_merge_statistics_service.dart b/lib/src/features/merge/data/data_sources/merge_statistics_service/object_box_merge_statistics_service.dart new file mode 100644 index 0000000..45a656e --- /dev/null +++ b/lib/src/features/merge/data/data_sources/merge_statistics_service/object_box_merge_statistics_service.dart @@ -0,0 +1,49 @@ +// Copyright 2024 BBK Development. All rights reserved. +// Use of this source code is governed by a GPL-style license that can be found +// in the LICENSE file. + +import 'package:vmerge/bootstrap.dart'; +import 'package:vmerge/src/features/merge/merge.dart'; + +final class ObjectBoxMergeStatisticsService + implements LocalMergeStatisticsService { + /// {@macro object_box_merge_statistics_service} + const ObjectBoxMergeStatisticsService({required ObjectBoxService service}) + : _service = service; + + final ObjectBoxService _service; + + /// Gets the [LocalMergeStatistics]. + @override + Future getMergeStatistics() async { + final box = _service.store.box(); + final query = box.query().build(); + final entities = query.find(); + + if (entities.isEmpty) { + query.close(); + return saveMergeStatistics(LocalMergeStatistics()); + } + + final statistics = entities.first; + query.close(); + + return statistics; + } + + /// Saves the [LocalMergeStatistics]. + @override + Future saveMergeStatistics( + LocalMergeStatistics statistics, + ) async { + final box = _service.store.box(); + final query = box.query().build(); + final entities = query.find(); + final oldStatistics = entities.firstOrNull; + query.close(); + + if (oldStatistics != null) statistics.id = oldStatistics.id; + + return box.putAndGetAsync(statistics); + } +} diff --git a/lib/src/features/merge/data/models/local_merge_settings.dart b/lib/src/features/merge/data/models/local_merge_settings.dart index b8c0528..380342e 100644 --- a/lib/src/features/merge/data/models/local_merge_settings.dart +++ b/lib/src/features/merge/data/models/local_merge_settings.dart @@ -7,14 +7,19 @@ import 'package:vmerge/src/features/merge/merge.dart'; @Entity() final class LocalMergeSettings implements DataModel { - LocalMergeSettings(); + LocalMergeSettings() + : id = 0, + isSoundOn = true, + playbackSpeed = PlaybackSpeed.one.toString(), + videoResolution = VideoResolution.original.toString(), + videoAspectRatio = VideoAspectRatio.firstVideo.toString(); LocalMergeSettings.fromArgs({ required this.isSoundOn, required this.playbackSpeed, required this.videoResolution, required this.videoAspectRatio, - }); + }) : id = 0; LocalMergeSettings.fromEntity(MergeSettings entity) : this.fromArgs( @@ -24,11 +29,11 @@ final class LocalMergeSettings implements DataModel { videoAspectRatio: entity.videoAspectRatio.toString(), ); - int id = 0; - bool isSoundOn = false; - String playbackSpeed = ''; - String videoResolution = ''; - String videoAspectRatio = ''; + int id; + bool isSoundOn; + String playbackSpeed; + String videoResolution; + String videoAspectRatio; @override MergeSettings toEntity() { diff --git a/lib/src/features/merge/data/models/local_merge_statistics.dart b/lib/src/features/merge/data/models/local_merge_statistics.dart new file mode 100644 index 0000000..cbc27ae --- /dev/null +++ b/lib/src/features/merge/data/models/local_merge_statistics.dart @@ -0,0 +1,37 @@ +// Copyright 2024 BBK Development. All rights reserved. +// Use of this source code is governed by a GPL-style license that can be found +// in the LICENSE file. + +import 'package:vmerge/src/core/core.dart'; +import 'package:vmerge/src/features/merge/domain/domain.dart'; + +@Entity() +final class LocalMergeStatistics implements DataModel { + LocalMergeStatistics() + : id = 0, + successMergeCount = 0, + failedMergeCount = 0; + + LocalMergeStatistics.fromArgs({ + required this.successMergeCount, + required this.failedMergeCount, + }) : id = 0; + + LocalMergeStatistics.fromEntity(MergeStatistics entity) + : this.fromArgs( + successMergeCount: entity.successMergeCount, + failedMergeCount: entity.failedMergeCount, + ); + + int id; + int successMergeCount; + int failedMergeCount; + + @override + MergeStatistics toEntity() { + return MergeStatistics( + successMergeCount: successMergeCount, + failedMergeCount: failedMergeCount, + ); + } +} diff --git a/lib/src/features/merge/data/models/models.dart b/lib/src/features/merge/data/models/models.dart index 9b481e7..59642d7 100644 --- a/lib/src/features/merge/data/models/models.dart +++ b/lib/src/features/merge/data/models/models.dart @@ -3,3 +3,4 @@ // in the LICENSE file. export 'local_merge_settings.dart'; +export 'local_merge_statistics.dart'; diff --git a/lib/src/features/merge/data/repositories/merge_statistics_repository_impl.dart b/lib/src/features/merge/data/repositories/merge_statistics_repository_impl.dart new file mode 100644 index 0000000..aed185e --- /dev/null +++ b/lib/src/features/merge/data/repositories/merge_statistics_repository_impl.dart @@ -0,0 +1,52 @@ +// Copyright 2024 BBK Development. All rights reserved. +// Use of this source code is governed by a GPL-style license that can be found +// in the LICENSE file. + +import 'package:vmerge/src/core/core.dart'; +import 'package:vmerge/src/features/merge/merge.dart'; + +final class MergeStatisticsRepositoryImpl implements MergeStatisticsRepository { + const MergeStatisticsRepositoryImpl({ + required LocalMergeStatisticsService localService, + }) : _localService = localService; + + final LocalMergeStatisticsService _localService; + + @override + Future> getMergeStatistics() async { + try { + final localMergeStatistics = await _localService.getMergeStatistics(); + final mergeStatistics = localMergeStatistics.toEntity(); + return DataSuccess(mergeStatistics); + } catch (error, stackTrace) { + return DataFailure( + Failure( + 'Could not get merge statistics!', + error: error, + name: '$MergeStatisticsRepositoryImpl', + stackTrace: stackTrace, + ), + ); + } + } + + @override + Future> saveMergeStatistics( + MergeStatistics statistics, + ) async { + try { + final localMergeStatistics = LocalMergeStatistics.fromEntity(statistics); + await _localService.saveMergeStatistics(localMergeStatistics); + return const DataSuccess(null); + } catch (error, stackTrace) { + return DataFailure( + Failure( + 'Could not save merge statistics!', + error: error, + name: '$MergeStatisticsRepositoryImpl', + stackTrace: stackTrace, + ), + ); + } + } +} diff --git a/lib/src/features/merge/data/repositories/repositories.dart b/lib/src/features/merge/data/repositories/repositories.dart index 4f9e7ea..175d2cf 100644 --- a/lib/src/features/merge/data/repositories/repositories.dart +++ b/lib/src/features/merge/data/repositories/repositories.dart @@ -3,3 +3,4 @@ // in the LICENSE file. export 'merge_settings_repository_impl.dart'; +export 'merge_statistics_repository_impl.dart'; diff --git a/lib/src/features/merge/domain/entities/entities.dart b/lib/src/features/merge/domain/entities/entities.dart index a96a31e..758e296 100644 --- a/lib/src/features/merge/domain/entities/entities.dart +++ b/lib/src/features/merge/domain/entities/entities.dart @@ -3,4 +3,5 @@ // in the LICENSE file. export 'merge_settings.dart'; +export 'merge_statistics.dart'; export 'video_metadata.dart'; diff --git a/lib/src/features/merge/domain/entities/merge_settings.dart b/lib/src/features/merge/domain/entities/merge_settings.dart index 62b84b8..e908cd9 100644 --- a/lib/src/features/merge/domain/entities/merge_settings.dart +++ b/lib/src/features/merge/domain/entities/merge_settings.dart @@ -32,6 +32,20 @@ final class MergeSettings extends DomainEntity { videoResolution, videoAspectRatio, ]; + + MergeSettings copyWith({ + bool? isAudioOn, + PlaybackSpeed? playbackSpeed, + VideoResolution? videoResolution, + VideoAspectRatio? videoAspectRatio, + }) { + return MergeSettings( + isAudioOn: isAudioOn ?? this.isAudioOn, + playbackSpeed: playbackSpeed ?? this.playbackSpeed, + videoResolution: videoResolution ?? this.videoResolution, + videoAspectRatio: videoAspectRatio ?? this.videoAspectRatio, + ); + } } enum PlaybackSpeed { diff --git a/lib/src/features/merge/domain/entities/merge_statistics.dart b/lib/src/features/merge/domain/entities/merge_statistics.dart new file mode 100644 index 0000000..449c5a3 --- /dev/null +++ b/lib/src/features/merge/domain/entities/merge_statistics.dart @@ -0,0 +1,34 @@ +// Copyright 2024 BBK Development. All rights reserved. +// Use of this source code is governed by a GPL-style license that can be found +// in the LICENSE file. + +import 'package:vmerge/src/core/core.dart'; + +final class MergeStatistics extends DomainEntity { + const MergeStatistics({ + required this.successMergeCount, + required this.failedMergeCount, + }); + + final int successMergeCount; + final int failedMergeCount; + + @override + String toString() { + return 'MergeStatistics(successMergeCount: $successMergeCount, ' + 'failedMergeCount: $failedMergeCount)'; + } + + @override + List get props => [ + successMergeCount, + failedMergeCount, + ]; + + MergeStatistics copyWith({int? successMergeCount, int? failedMergeCount}) { + return MergeStatistics( + successMergeCount: successMergeCount ?? this.successMergeCount, + failedMergeCount: failedMergeCount ?? this.failedMergeCount, + ); + } +} diff --git a/lib/src/features/merge/domain/repositories/merge_statistics_repository.dart b/lib/src/features/merge/domain/repositories/merge_statistics_repository.dart new file mode 100644 index 0000000..5afdc17 --- /dev/null +++ b/lib/src/features/merge/domain/repositories/merge_statistics_repository.dart @@ -0,0 +1,14 @@ +// Copyright 2024 BBK Development. All rights reserved. +// Use of this source code is governed by a GPL-style license that can be found +// in the LICENSE file. + +import 'package:vmerge/src/core/core.dart'; +import 'package:vmerge/src/features/merge/merge.dart'; + +abstract interface class MergeStatisticsRepository { + const MergeStatisticsRepository(); + + Future> getMergeStatistics(); + + Future> saveMergeStatistics(MergeStatistics statistics); +} diff --git a/lib/src/features/merge/domain/repositories/repositories.dart b/lib/src/features/merge/domain/repositories/repositories.dart index d6a3b62..1985eb6 100644 --- a/lib/src/features/merge/domain/repositories/repositories.dart +++ b/lib/src/features/merge/domain/repositories/repositories.dart @@ -3,3 +3,4 @@ // in the LICENSE file. export 'merge_settings_repository.dart'; +export 'merge_statistics_repository.dart'; diff --git a/lib/src/features/merge/domain/use_cases/get_merge_statistics_use_case.dart b/lib/src/features/merge/domain/use_cases/get_merge_statistics_use_case.dart new file mode 100644 index 0000000..ccee7c8 --- /dev/null +++ b/lib/src/features/merge/domain/use_cases/get_merge_statistics_use_case.dart @@ -0,0 +1,20 @@ +// Copyright 2024 BBK Development. All rights reserved. +// Use of this source code is governed by a GPL-style license that can be found +// in the LICENSE file. + +import 'package:vmerge/src/core/core.dart'; +import 'package:vmerge/src/features/merge/merge.dart'; + +final class GetMergeStatisticsUseCase + implements UseCase, void> { + const GetMergeStatisticsUseCase({ + required MergeStatisticsRepository repository, + }) : _repository = repository; + + final MergeStatisticsRepository _repository; + + @override + Future> call({void params}) { + return _repository.getMergeStatistics(); + } +} diff --git a/lib/src/features/merge/domain/use_cases/save_merge_statistics_use_case.dart b/lib/src/features/merge/domain/use_cases/save_merge_statistics_use_case.dart new file mode 100644 index 0000000..6124156 --- /dev/null +++ b/lib/src/features/merge/domain/use_cases/save_merge_statistics_use_case.dart @@ -0,0 +1,20 @@ +// Copyright 2024 BBK Development. All rights reserved. +// Use of this source code is governed by a GPL-style license that can be found +// in the LICENSE file. + +import 'package:vmerge/src/core/core.dart'; +import 'package:vmerge/src/features/merge/merge.dart'; + +final class SaveMergeStatisticsUseCase + implements UseCase, MergeStatistics> { + const SaveMergeStatisticsUseCase({ + required MergeStatisticsRepository repository, + }) : _repository = repository; + + final MergeStatisticsRepository _repository; + + @override + Future> call({required MergeStatistics params}) { + return _repository.saveMergeStatistics(params); + } +} diff --git a/lib/src/features/merge/domain/use_cases/use_cases.dart b/lib/src/features/merge/domain/use_cases/use_cases.dart index 60aba21..777686f 100644 --- a/lib/src/features/merge/domain/use_cases/use_cases.dart +++ b/lib/src/features/merge/domain/use_cases/use_cases.dart @@ -3,4 +3,6 @@ // in the LICENSE file. export 'get_merge_settings_use_case.dart'; +export 'get_merge_statistics_use_case.dart'; export 'save_merge_settings_use_case.dart'; +export 'save_merge_statistics_use_case.dart'; diff --git a/lib/src/features/merge/presentation/cubits/save_bottom_sheet_cubit/save_bottom_sheet_cubit.dart b/lib/src/features/merge/presentation/cubits/save_bottom_sheet_cubit/save_bottom_sheet_cubit.dart index cdbb3c9..09645e8 100644 --- a/lib/src/features/merge/presentation/cubits/save_bottom_sheet_cubit/save_bottom_sheet_cubit.dart +++ b/lib/src/features/merge/presentation/cubits/save_bottom_sheet_cubit/save_bottom_sheet_cubit.dart @@ -3,13 +3,16 @@ // in the LICENSE file. import 'dart:async'; +import 'dart:developer'; import 'dart:io'; import 'package:bloc/bloc.dart'; import 'package:ffmpeg_service/ffmpeg_service.dart'; import 'package:image_gallery_saver/image_gallery_saver.dart'; +import 'package:in_app_review_service/in_app_review_service.dart'; import 'package:path/path.dart' as path; import 'package:path_provider/path_provider.dart'; +import 'package:vmerge/src/core/core.dart'; import 'package:vmerge/src/features/merge/merge.dart'; import 'package:wakelock_service/wakelock_service.dart'; @@ -17,8 +20,14 @@ final class SaveBottomSheetCubit extends Cubit { SaveBottomSheetCubit({ required FFmpegService ffmpegService, required WakelockService wakelockService, + required InAppReviewService inAppReviewService, + required GetMergeStatisticsUseCase getMergeStatisticsUseCase, + required SaveMergeStatisticsUseCase saveMergeStatisticsUseCase, }) : _ffmpegService = ffmpegService, _wakelockService = wakelockService, + _getMergeStatisticsUseCase = getMergeStatisticsUseCase, + _saveMergeStatisticsUseCase = saveMergeStatisticsUseCase, + _inAppReviewService = inAppReviewService, super( const SaveBottomSheetInitial( videoMetadatas: [], @@ -27,6 +36,9 @@ final class SaveBottomSheetCubit extends Cubit { final FFmpegService _ffmpegService; final WakelockService _wakelockService; + final InAppReviewService _inAppReviewService; + final GetMergeStatisticsUseCase _getMergeStatisticsUseCase; + final SaveMergeStatisticsUseCase _saveMergeStatisticsUseCase; void init(List videoMetadatas) { emit(SaveBottomSheetInitial(videoMetadatas: videoMetadatas)); @@ -39,6 +51,8 @@ final class SaveBottomSheetCubit extends Cubit { int? outputHeight, bool? forceFirstAspectRatio, }) async { + final statistics = await _getMergeStatistics(); + final Directory appDocsDir; try { appDocsDir = await getApplicationDocumentsDirectory(); @@ -50,6 +64,13 @@ final class SaveBottomSheetCubit extends Cubit { stackTrace: stackTrace, ), ); + unawaited( + _saveMergeStatistics( + statistics?.copyWith( + failedMergeCount: statistics.failedMergeCount + 1, + ), + ), + ); return; } @@ -81,6 +102,13 @@ final class SaveBottomSheetCubit extends Cubit { stackTrace: stackTrace, ), ); + unawaited( + _saveMergeStatistics( + statistics?.copyWith( + failedMergeCount: statistics.failedMergeCount + 1, + ), + ), + ); unawaited(_wakelockService.disable()); return; } @@ -107,6 +135,13 @@ final class SaveBottomSheetCubit extends Cubit { stackTrace: stackTrace, ), ); + unawaited( + _saveMergeStatistics( + statistics?.copyWith( + failedMergeCount: statistics.failedMergeCount + 1, + ), + ), + ); return; } on FFmpegServiceInsufficientVideosException catch (error, stackTrace) { emit( @@ -117,6 +152,13 @@ final class SaveBottomSheetCubit extends Cubit { stackTrace: stackTrace, ), ); + unawaited( + _saveMergeStatistics( + statistics?.copyWith( + failedMergeCount: statistics.failedMergeCount + 1, + ), + ), + ); return; } on FFmpegServiceMergeException catch (error, stackTrace) { emit( @@ -126,6 +168,13 @@ final class SaveBottomSheetCubit extends Cubit { stackTrace: stackTrace, ), ); + unawaited( + _saveMergeStatistics( + statistics?.copyWith( + failedMergeCount: statistics.failedMergeCount + 1, + ), + ), + ); return; } finally { unawaited(_wakelockService.disable()); @@ -145,9 +194,21 @@ final class SaveBottomSheetCubit extends Cubit { stackTrace: stackTrace, ), ); + unawaited( + _saveMergeStatistics( + statistics?.copyWith( + failedMergeCount: statistics.failedMergeCount + 1, + ), + ), + ); return; } + final newStatistics = statistics?.copyWith( + successMergeCount: statistics.successMergeCount + 1, + ); + unawaited(_saveMergeStatistics(newStatistics)); + unawaited(_requestReview(newStatistics)); emit(const SaveBottomSheetSuccess()); } @@ -157,6 +218,65 @@ final class SaveBottomSheetCubit extends Cubit { emit(const SaveBottomSheetCancelled()); await _ffmpegService.cancelMerge(); } + + Future _getMergeStatistics() async { + final dataState = await _getMergeStatisticsUseCase(); + + switch (dataState) { + case DataSuccess(): + return dataState.data; + case DataFailure(): + log( + dataState.message, + name: dataState.name, + error: dataState.error, + stackTrace: dataState.stackTrace, + ); + return null; + } + } + + Future _saveMergeStatistics(MergeStatistics? statistics) async { + if (statistics == null) return; + + final dataState = await _saveMergeStatisticsUseCase(params: statistics); + + switch (dataState) { + case DataSuccess(): + break; + case DataFailure(): + log( + dataState.message, + name: dataState.name, + error: dataState.error, + stackTrace: dataState.stackTrace, + ); + } + } + + Future _requestReview(MergeStatistics? statistics) async { + if (statistics == null) return; + + final totalMergeCount = + statistics.successMergeCount + statistics.failedMergeCount; + // If the user has not merged at least 2 videos, don't request review. + if (totalMergeCount < 2) return; + + final successRate = statistics.successMergeCount / totalMergeCount; + // If the success rate is less than 90%, don't request review. + if (successRate < 0.9) return; + + try { + await _inAppReviewService.requestReview(); + } on RequestReviewException catch (error, stackTrace) { + log( + 'Could not request review!', + name: '$SaveBottomSheetCubit', + error: error, + stackTrace: stackTrace, + ); + } + } } /// The minimum duration a status should be displayed for. diff --git a/lib/src/features/merge/presentation/cubits/settings_bottom_sheet_cubit/settings_bottom_sheet_cubit.dart b/lib/src/features/merge/presentation/cubits/settings_bottom_sheet_cubit/settings_bottom_sheet_cubit.dart index 59b4b4c..65e9fd8 100644 --- a/lib/src/features/merge/presentation/cubits/settings_bottom_sheet_cubit/settings_bottom_sheet_cubit.dart +++ b/lib/src/features/merge/presentation/cubits/settings_bottom_sheet_cubit/settings_bottom_sheet_cubit.dart @@ -14,39 +14,26 @@ final class SettingsBottomSheetCubit extends Cubit { required SaveMergeSettingsUseCase saveMergeSettingsUseCase, }) : _getMergeSettingsUseCase = getMergeSettingsUseCase, _saveMergeSettingsUseCase = saveMergeSettingsUseCase, - super( - SettingsBottomSheetLoaded( - isAudioOn: _defaultMergeSettings.isAudioOn, - playbackSpeed: _defaultMergeSettings.playbackSpeed, - videoResolution: _defaultMergeSettings.videoResolution, - videoAspectRatio: _defaultMergeSettings.videoAspectRatio, - ), - ); + super(const SettingsBottomSheetInitial()); final GetMergeSettingsUseCase _getMergeSettingsUseCase; final SaveMergeSettingsUseCase _saveMergeSettingsUseCase; - static const _defaultMergeSettings = MergeSettings( - isAudioOn: true, - playbackSpeed: PlaybackSpeed.one, - videoResolution: VideoResolution.original, - videoAspectRatio: VideoAspectRatio.firstVideo, - ); - Future init() async { final settings = await _getMergeSettings(); emit( SettingsBottomSheetLoaded( - isAudioOn: settings.isAudioOn, - playbackSpeed: settings.playbackSpeed, - videoResolution: settings.videoResolution, - videoAspectRatio: settings.videoAspectRatio, + isAudioOn: settings?.isAudioOn ?? true, + playbackSpeed: settings?.playbackSpeed ?? PlaybackSpeed.one, + videoResolution: settings?.videoResolution ?? VideoResolution.original, + videoAspectRatio: + settings?.videoAspectRatio ?? VideoAspectRatio.firstVideo, ), ); } - Future _getMergeSettings() async { + Future _getMergeSettings() async { final dataState = await _getMergeSettingsUseCase(); switch (dataState) { @@ -59,8 +46,7 @@ final class SettingsBottomSheetCubit extends Cubit { error: dataState.error, stackTrace: dataState.stackTrace, ); - await _saveMergeSettings(_defaultMergeSettings); - return _defaultMergeSettings; + return null; } } diff --git a/lib/src/features/merge/presentation/pages/merge_page.dart b/lib/src/features/merge/presentation/pages/merge_page.dart index 0f32df0..ae1ff4b 100644 --- a/lib/src/features/merge/presentation/pages/merge_page.dart +++ b/lib/src/features/merge/presentation/pages/merge_page.dart @@ -11,6 +11,7 @@ import 'package:android_intent_plus/flag.dart'; import 'package:ffmpeg_service/ffmpeg_service.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:in_app_review_service/in_app_review_service.dart'; import 'package:modal_bottom_sheet/modal_bottom_sheet.dart'; import 'package:video_player/video_player.dart'; import 'package:vmerge/bootstrap.dart'; @@ -51,6 +52,9 @@ class MergePage extends StatelessWidget { create: (_) => SaveBottomSheetCubit( ffmpegService: getIt(), wakelockService: getIt(), + inAppReviewService: getIt(), + getMergeStatisticsUseCase: getIt(), + saveMergeStatisticsUseCase: getIt(), ), ), ], diff --git a/lib/src/features/more/presentation/pages/more_page.dart b/lib/src/features/more/presentation/pages/more_page.dart index ad5bdd8..0425735 100644 --- a/lib/src/features/more/presentation/pages/more_page.dart +++ b/lib/src/features/more/presentation/pages/more_page.dart @@ -5,7 +5,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_svg/flutter_svg.dart'; -import 'package:launch_review_service/launch_review_service.dart'; +import 'package:in_app_review_service/in_app_review_service.dart'; import 'package:modal_bottom_sheet/modal_bottom_sheet.dart'; import 'package:package_info_plus/package_info_plus.dart'; import 'package:url_launcher_service/url_launcher_service.dart'; diff --git a/lib/src/features/more/presentation/widgets/more_page_option.dart b/lib/src/features/more/presentation/widgets/more_page_option.dart index b26decc..6ef61a1 100644 --- a/lib/src/features/more/presentation/widgets/more_page_option.dart +++ b/lib/src/features/more/presentation/widgets/more_page_option.dart @@ -60,11 +60,11 @@ class _MorePageOption extends StatelessWidget { }, ); case MorePageOption.rateUs: - getIt() - .launch() - .onError((error, stackTrace) { + getIt() + .openStoreListing() + .onError((error, stackTrace) { context.read().caught( - message: l10n.couldNotLaunchReviewServiceMessage, + message: l10n.couldNotOpenStoreListingMessage, error: error, stackTrace: stackTrace, ); diff --git a/pubspec.lock b/pubspec.lock index e4d8755..fc6c81f 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -45,10 +45,10 @@ packages: dependency: transitive description: name: args - sha256: eef6c46b622e0494a36c5a12d10d77fb4e855501a91c1b9ef9339326e58f0596 + sha256: "7cf60b9f0cc88203c5a190b4cd62a99feea42759a7fa695010eb5de1c0b2252a" url: "https://pub.dev" source: hosted - version: "2.4.2" + version: "2.5.0" async: dependency: transitive description: @@ -354,18 +354,18 @@ packages: dependency: transitive description: name: flutter_gen_core - sha256: b9894396b2a790cc2d6eb3ed86e5e113aaed993765b21d4b981c9da4476e0f52 + sha256: d8e828ad015a8511624491b78ad8e3f86edb7993528b1613aefbb4ad95947795 url: "https://pub.dev" source: hosted - version: "5.5.0+1" + version: "5.6.0" flutter_gen_runner: dependency: "direct dev" description: name: flutter_gen_runner - sha256: b4c4c54e4dd89022f5e405fe96f16781be2dfbeabe8a70ccdf73b7af1302c655 + sha256: "931b03f77c164df0a4815aac0efc619a6ac8ec4cada55025119fca4894dada90" url: "https://pub.dev" source: hosted - version: "5.5.0+1" + version: "5.6.0" flutter_lints: dependency: "direct dev" description: @@ -383,10 +383,10 @@ packages: dependency: "direct main" description: name: flutter_native_splash - sha256: edf39bcf4d74aca1eb2c1e43c3e445fd9f494013df7f0da752fefe72020eedc0 + sha256: aa06fec78de2190f3db4319dd60fdc8d12b2626e93ef9828633928c2dcaea840 url: "https://pub.dev" source: hosted - version: "2.4.0" + version: "2.4.1" flutter_svg: dependency: "direct main" description: @@ -489,10 +489,10 @@ packages: dependency: transitive description: name: image - sha256: "4c68bfd5ae83e700b5204c1e74451e7bf3cf750e6843c6e158289cf56bda018e" + sha256: "2237616a36c0d69aef7549ab439b833fb7f9fb9fc861af2cc9ac3eedddd69ca8" url: "https://pub.dev" source: hosted - version: "4.1.7" + version: "4.2.0" image_gallery_saver: dependency: "direct main" description: @@ -509,6 +509,31 @@ packages: url: "https://pub.dev" source: hosted version: "2.1.3" + in_app_review: + dependency: transitive + description: + name: in_app_review + sha256: "99869244d09adc76af16bf8fd731dd13cef58ecafd5917847589c49f378cbb30" + url: "https://pub.dev" + source: hosted + version: "2.0.9" + in_app_review_platform_interface: + dependency: transitive + description: + name: in_app_review_platform_interface + sha256: fed2c755f2125caa9ae10495a3c163aa7fab5af3585a9c62ef4a6920c5b45f10 + url: "https://pub.dev" + source: hosted + version: "2.0.5" + in_app_review_service: + dependency: "direct main" + description: + path: "." + ref: "v1.0.1" + resolved-ref: ad4c30c7f6ed404bc70c54decae94fc41db471a9 + url: "https://github.com/BBKDevelopment/In-App-Review-Service.git" + source: git + version: "1.0.1" intl: dependency: "direct main" description: @@ -541,23 +566,6 @@ packages: url: "https://pub.dev" source: hosted version: "4.8.1" - launch_review: - dependency: transitive - description: - name: launch_review - sha256: "04cdaf752033cefd53bc0fa9c22105801ef53791a93d8b6cdd00fcb3c1c1604b" - url: "https://pub.dev" - source: hosted - version: "3.0.1" - launch_review_service: - dependency: "direct main" - description: - path: "." - ref: "v1.0.0" - resolved-ref: a85533d94d4308ee3f50268db225c60ea2080bac - url: "https://github.com/BBKDevelopment/Launch-Review-Service.git" - source: git - version: "1.0.0" leak_tracker: dependency: transitive description: @@ -1064,18 +1072,18 @@ packages: dependency: "direct dev" description: name: very_good_analysis - sha256: "9ae7f3a3bd5764fb021b335ca28a34f040cd0ab6eec00a1b213b445dae58a4b8" + sha256: "1fb637c0022034b1f19ea2acb42a3603cbd8314a470646a59a2fb01f5f3a8629" url: "https://pub.dev" source: hosted - version: "5.1.0" + version: "6.0.0" video_player: dependency: "direct main" description: name: video_player - sha256: aced48e701e24c02b0b7f881a8819e4937794e46b5a5821005e2bf3b40a324cc + sha256: e30df0d226c4ef82e2c150ebf6834b3522cf3f654d8e2f9419d376cdc071425d url: "https://pub.dev" source: hosted - version: "2.8.7" + version: "2.9.1" video_player_android: dependency: transitive description: @@ -1178,10 +1186,10 @@ packages: dependency: "direct main" description: name: wechat_assets_picker - sha256: "6a1193beb2ce170edcf4091d849d2bc811eb2a36a8007fa63e6bfbaf3ac99625" + sha256: f4b3eb0662f9a9f0453a591f056f5f63244586b772b62b79730a54e501b02671 url: "https://pub.dev" source: hosted - version: "9.0.4" + version: "9.1.0" wechat_picker_library: dependency: transitive description: @@ -1224,4 +1232,4 @@ packages: version: "3.1.2" sdks: dart: ">=3.4.0 <4.0.0" - flutter: ">=3.19.0" + flutter: ">=3.22.0" diff --git a/pubspec.yaml b/pubspec.yaml index 53ed366..e5614b7 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -22,15 +22,15 @@ dependencies: flutter_bloc: ^8.1.5 flutter_localizations: sdk: flutter - flutter_native_splash: ^2.4.0 + flutter_native_splash: ^2.4.1 flutter_svg: ^2.0.10+1 get_it: ^7.7.0 image_gallery_saver: ^2.0.3 - intl: 0.19.0 - launch_review_service: + in_app_review_service: git: - url: https://github.com/BBKDevelopment/Launch-Review-Service.git - ref: v1.0.0 + url: https://github.com/BBKDevelopment/In-App-Review-Service.git + ref: v1.0.1 + intl: 0.19.0 modal_bottom_sheet: ^3.0.0 objectbox: ^2.5.1 objectbox_flutter_libs: ^2.5.1 @@ -41,7 +41,7 @@ dependencies: git: url: https://github.com/BBKDevelopment/Url-Launcher-Service.git ref: v1.0.0 - video_player: ^2.8.7 + video_player: ^2.9.1 video_player_service: git: url: https://github.com/BBKDevelopment/Video-Player-Service.git @@ -50,16 +50,16 @@ dependencies: git: url: https://github.com/BBKDevelopment/Wakelock-Service.git ref: v1.1.0 - wechat_assets_picker: ^9.0.4 + wechat_assets_picker: ^9.1.0 dev_dependencies: build_runner: ^2.4.11 - flutter_gen_runner: ^5.5.0+1 + flutter_gen_runner: ^5.6.0 flutter_lints: ^4.0.0 flutter_test: sdk: flutter objectbox_generator: ^2.5.1 - very_good_analysis: ^5.1.0 + very_good_analysis: ^6.0.0 flutter_gen: output: lib/src/core/gen/