Skip to content

Commit

Permalink
Merge branch 'develop'
Browse files Browse the repository at this point in the history
Wolfteam committed Sep 24, 2023
2 parents b2858dc + fa3912b commit 0af1200
Showing 274 changed files with 6,034 additions and 1,717 deletions.
2 changes: 1 addition & 1 deletion .fvm/fvm_config.json
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
{
"flutterSdkVersion": "3.7.12",
"flutterSdkVersion": "3.13.1",
"flavors": {}
}
5 changes: 3 additions & 2 deletions analysis_options.yaml
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
include: package:lint/analysis_options.yaml
include: package:lint/strict.yaml

linter:
rules:
@@ -12,7 +12,8 @@ analyzer:
- '**/*.freezed.dart'
- '**/*.g.dart'
- lib/**/*.*.dart
- lib/generated/intl/*
- lib/generated/*.dart
- lib/generated/**/*.dart
- '**/mocks.mocks.dart'
- '**/shiori_icons.dart'
errors:
8 changes: 8 additions & 0 deletions android/app/build.gradle
Original file line number Diff line number Diff line change
@@ -35,6 +35,8 @@ android {
compileSdkVersion localProperties.getProperty('flutter.compileSdkVersion').toInteger()

compileOptions {
// Flag to enable support for the new language APIs
coreLibraryDesugaringEnabled true
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
@@ -55,6 +57,7 @@ android {


defaultConfig {
multiDexEnabled true
applicationId "com.miraisoft.shiori"
// You can update the following values to match your application needs.
// For more information, see: https://docs.flutter.dev/deployment/android#reviewing-the-gradle-build-configuration.
@@ -107,4 +110,9 @@ dependencies {
def appCenterSdkVersion = '4.4.5'
implementation "com.microsoft.appcenter:appcenter-analytics:${appCenterSdkVersion}"
implementation "com.microsoft.appcenter:appcenter-crashes:${appCenterSdkVersion}"

// Required by the notifications plugin
coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:1.1.5'
implementation 'androidx.window:window:1.0.0'
implementation 'androidx.window:window-java:1.0.0'
}
2 changes: 0 additions & 2 deletions android/app/proguard-rules.pro
Original file line number Diff line number Diff line change
@@ -22,8 +22,6 @@
@com.google.gson.annotations.SerializedName <fields>;
}

-keepattributes InnerClasses

# Retain generic signatures of TypeToken and its subclasses with R8 version 3.0 and higher.
-keep,allowobfuscation,allowshrinking class com.google.gson.reflect.TypeToken
-keep,allowobfuscation,allowshrinking class * extends com.google.gson.reflect.TypeToken
2 changes: 1 addition & 1 deletion android/build.gradle
Original file line number Diff line number Diff line change
@@ -26,6 +26,6 @@ subprojects {
project.evaluationDependsOn(':app')
}

task clean(type: Delete) {
tasks.register("clean", Delete) {
delete rootProject.buildDir
}
Binary file added assets/others/wish_banner_background.webp
Binary file not shown.
Binary file added assets/others/wish_banner_button.webp
Binary file not shown.
Binary file added assets/others/wish_banner_standard.webp
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file added assets/weapon_normal_skill_types/bow.webp
Binary file not shown.
Binary file added assets/weapon_normal_skill_types/catalyst.webp
Binary file not shown.
Binary file added assets/weapon_normal_skill_types/claymore.webp
Binary file not shown.
Binary file added assets/weapon_normal_skill_types/polearm.webp
Binary file not shown.
Binary file added assets/weapon_normal_skill_types/sword.webp
Binary file not shown.
82 changes: 41 additions & 41 deletions ios/Podfile.lock
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
PODS:
- AppCenter (5.0.1):
- AppCenter/Analytics (= 5.0.1)
- AppCenter/Crashes (= 5.0.1)
- AppCenter/Analytics (5.0.1):
- AppCenter (5.0.3):
- AppCenter/Analytics (= 5.0.3)
- AppCenter/Crashes (= 5.0.3)
- AppCenter/Analytics (5.0.3):
- AppCenter/Core
- AppCenter/Core (5.0.1)
- AppCenter/Crashes (5.0.1):
- AppCenter/Core (5.0.3)
- AppCenter/Crashes (5.0.3):
- AppCenter/Core
- device_info_plus (0.0.1):
- Flutter
@@ -62,27 +62,27 @@ PODS:
- fluttertoast (0.0.2):
- Flutter
- Toast
- image_gallery_saver (1.5.0):
- image_gallery_saver (2.0.2):
- Flutter
- OrderedSet (5.0.0)
- package_info_plus (0.4.5):
- Flutter
- path_provider_foundation (0.0.1):
- Flutter
- FlutterMacOS
- permission_handler_apple (9.0.4):
- permission_handler_apple (9.1.1):
- Flutter
- purchases_flutter (4.11.3):
- purchases_flutter (5.6.5):
- Flutter
- PurchasesHybridCommon (= 4.14.3)
- PurchasesHybridCommon (4.14.3):
- RevenueCat (= 4.17.11)
- rate_my_app (1.1.3):
- PurchasesHybridCommon (= 6.1.2)
- PurchasesHybridCommon (6.1.2):
- RevenueCat (= 4.25.9)
- rate_my_app (2.0.0):
- Flutter
- RevenueCat (4.17.11)
- SDWebImage (5.15.2):
- SDWebImage/Core (= 5.15.2)
- SDWebImage/Core (5.15.2)
- RevenueCat (4.25.9)
- SDWebImage (5.17.0):
- SDWebImage/Core (= 5.17.0)
- SDWebImage/Core (5.17.0)
- share_plus (0.0.1):
- Flutter
- shared_preferences_foundation (0.0.1):
@@ -94,7 +94,7 @@ PODS:
- Toast (4.0.0)
- url_launcher_ios (0.0.1):
- Flutter
- wakelock (0.0.1):
- wakelock_plus (0.0.1):
- Flutter

DEPENDENCIES:
@@ -110,15 +110,15 @@ DEPENDENCIES:
- fluttertoast (from `.symlinks/plugins/fluttertoast/ios`)
- image_gallery_saver (from `.symlinks/plugins/image_gallery_saver/ios`)
- package_info_plus (from `.symlinks/plugins/package_info_plus/ios`)
- path_provider_foundation (from `.symlinks/plugins/path_provider_foundation/ios`)
- path_provider_foundation (from `.symlinks/plugins/path_provider_foundation/darwin`)
- permission_handler_apple (from `.symlinks/plugins/permission_handler_apple/ios`)
- purchases_flutter (from `.symlinks/plugins/purchases_flutter/ios`)
- rate_my_app (from `.symlinks/plugins/rate_my_app/ios`)
- share_plus (from `.symlinks/plugins/share_plus/ios`)
- shared_preferences_foundation (from `.symlinks/plugins/shared_preferences_foundation/ios`)
- shared_preferences_foundation (from `.symlinks/plugins/shared_preferences_foundation/darwin`)
- store_checker (from `.symlinks/plugins/store_checker/ios`)
- url_launcher_ios (from `.symlinks/plugins/url_launcher_ios/ios`)
- wakelock (from `.symlinks/plugins/wakelock/ios`)
- wakelock_plus (from `.symlinks/plugins/wakelock_plus/ios`)

SPEC REPOS:
trunk:
@@ -156,7 +156,7 @@ EXTERNAL SOURCES:
package_info_plus:
:path: ".symlinks/plugins/package_info_plus/ios"
path_provider_foundation:
:path: ".symlinks/plugins/path_provider_foundation/ios"
:path: ".symlinks/plugins/path_provider_foundation/darwin"
permission_handler_apple:
:path: ".symlinks/plugins/permission_handler_apple/ios"
purchases_flutter:
@@ -166,17 +166,17 @@ EXTERNAL SOURCES:
share_plus:
:path: ".symlinks/plugins/share_plus/ios"
shared_preferences_foundation:
:path: ".symlinks/plugins/shared_preferences_foundation/ios"
:path: ".symlinks/plugins/shared_preferences_foundation/darwin"
store_checker:
:path: ".symlinks/plugins/store_checker/ios"
url_launcher_ios:
:path: ".symlinks/plugins/url_launcher_ios/ios"
wakelock:
:path: ".symlinks/plugins/wakelock/ios"
wakelock_plus:
:path: ".symlinks/plugins/wakelock_plus/ios"

SPEC CHECKSUMS:
AppCenter: 18153bb6bc4241d14c8cce57466ac1c88136b476
device_info_plus: e5c5da33f982a436e103237c0c85f9031142abed
AppCenter: a4070ec3d4418b5539067a51f57155012e486ebd
device_info_plus: 7545d84d8d1b896cb16a4ff98c19f07ec4b298ea
devicelocale: b22617f40038496deffba44747101255cee005b0
DKImagePickerController: b512c28220a2b8ac7419f21c491fc8534b7601ac
DKPhotoGallery: fdfad5125a9fdda9cc57df834d49df790dbb4179
@@ -186,24 +186,24 @@ SPEC CHECKSUMS:
flutter_inappwebview: bfd58618f49dc62f2676de690fc6dcda1d6c3721
flutter_local_notifications: 0c0b1ae97e741e1521e4c1629a459d04b9aec743
flutter_native_timezone: 5f05b2de06c9776b4cc70e1839f03de178394d22
fluttertoast: eb263d302cc92e04176c053d2385237e9f43fad0
image_gallery_saver: 259eab68fb271cfd57d599904f7acdc7832e7ef2
fluttertoast: fafc4fa4d01a6a9e4f772ecd190ffa525e9e2d9c
image_gallery_saver: cb43cc43141711190510e92c460eb1655cd343cb
OrderedSet: aaeb196f7fef5a9edf55d89760da9176ad40b93c
package_info_plus: 6c92f08e1f853dc01228d6f553146438dafcd14e
path_provider_foundation: 37748e03f12783f9de2cb2c4eadfaa25fe6d4852
permission_handler_apple: 44366e37eaf29454a1e7b1b7d736c2cceaeb17ce
purchases_flutter: 7c0cd1ed4412ac8c9041200cf7f22806c6c4e714
PurchasesHybridCommon: be1e0089c51b4ee7b44914fd7a9ede271f96e4f5
rate_my_app: 96f658ddb7f0a8d2f46d54452583f084760aef57
RevenueCat: 0431c28e4d492c0c88d6cc5c89a289cd40a8b7ea
SDWebImage: 8ab87d4b3e5cc4927bd47f78db6ceb0b94442577
share_plus: 056a1e8ac890df3e33cb503afffaf1e9b4fbae68
shared_preferences_foundation: 297b3ebca31b34ec92be11acd7fb0ba932c822ca
package_info_plus: fd030dabf36271f146f1f3beacd48f564b0f17f7
path_provider_foundation: 29f094ae23ebbca9d3d0cec13889cd9060c0e943
permission_handler_apple: e76247795d700c14ea09e3a2d8855d41ee80a2e6
purchases_flutter: 440e09b1d692a20438574da78a45fda193bb80c5
PurchasesHybridCommon: b7c4205b6d2479cdd8914084a2b536e5e5b1445f
rate_my_app: 80fba94c4885cd31738231b7b1f51c1ad726f5c7
RevenueCat: 4160d04575059fc1edbb973981f541b705ddebef
SDWebImage: 750adf017a315a280c60fde706ab1e552a3ae4e9
share_plus: 599aa54e4ea31d4b4c0e9c911bcc26c55e791028
shared_preferences_foundation: 5b919d13b803cadd15ed2dc053125c68730e5126
store_checker: 359c5051d9ec30ff0a8fa39eb5ec9df021bb745d
SwiftyGif: 93a1cc87bf3a51916001cf8f3d63835fb64c819f
Toast: 91b396c56ee72a5790816f40d3a94dd357abc196
url_launcher_ios: 839c58cdb4279282219f5e248c3321761ff3c4de
wakelock: d0fc7c864128eac40eba1617cb5264d9c940b46f
url_launcher_ios: 08a3dfac5fb39e8759aeb0abbd5d9480f30fc8b4
wakelock_plus: 8b09852c8876491e4b6d179e17dfe2a0b5f60d47

PODFILE CHECKSUM: a1eaf986c30d5a84309bbcd4434682ef102516a3

3 changes: 2 additions & 1 deletion ios/Runner.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
@@ -157,7 +157,7 @@
97C146E61CF9000F007C117D /* Project object */ = {
isa = PBXProject;
attributes = {
LastUpgradeCheck = 1300;
LastUpgradeCheck = 1430;
ORGANIZATIONNAME = "";
TargetAttributes = {
97C146ED1CF9000F007C117D = {
@@ -206,6 +206,7 @@
files = (
);
inputPaths = (
"${TARGET_BUILD_DIR}/${INFOPLIST_PATH}",
);
name = "Thin Binary";
outputPaths = (
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "1300"
LastUpgradeVersion = "1430"
version = "1.3">
<BuildAction
parallelizeBuildables = "YES"
2 changes: 1 addition & 1 deletion lib/application/app_bloc_observer.dart
Original file line number Diff line number Diff line change
@@ -11,4 +11,4 @@ class AppBlocObserver extends BlocObserver {
_logger.error(bloc.runtimeType, 'Bloc error', error, stackTrace);
super.onError(bloc, error, stackTrace);
}
}
}
2 changes: 0 additions & 2 deletions lib/application/artifacts/artifacts_bloc.dart
Original file line number Diff line number Diff line change
@@ -128,14 +128,12 @@ class ArtifactsBloc extends Bloc<ArtifactsEvent, ArtifactsState> {
} else {
data.sort((x, y) => y.name.compareTo(x.name));
}
break;
case ArtifactFilterType.rarity:
if (sortDirectionType == SortDirectionType.asc) {
data.sort((x, y) => x.rarity.compareTo(y.rarity));
} else {
data.sort((x, y) => y.rarity.compareTo(x.rarity));
}
break;
default:
break;
}
6 changes: 3 additions & 3 deletions lib/application/backup_restore/backup_restore_bloc.dart
Original file line number Diff line number Diff line change
@@ -62,7 +62,7 @@ class BackupRestoreBloc extends Bloc<BackupRestoreEvent, BackupRestoreState> {
throw Exception('Invalid state');
}
final result = await _backupRestoreService.createBackup(dataTypes);
await _telemetryService.backupCreated(result.succeed);
await _telemetryService.trackBackupCreated(result.succeed);
if (!result.succeed) {
return currentState.copyWith.call(createResult: result);
}
@@ -93,7 +93,7 @@ class BackupRestoreBloc extends Bloc<BackupRestoreEvent, BackupRestoreState> {
dataTypes: bk?.dataTypes ?? [],
);
if (bk == null) {
await _telemetryService.backupRestored(false);
await _telemetryService.trackBackupRestored(false);
return currentState.copyWith.call(restoreResult: result);
}

@@ -102,7 +102,7 @@ class BackupRestoreBloc extends Bloc<BackupRestoreEvent, BackupRestoreState> {
}

final restored = await _backupRestoreService.restoreBackup(bk, dataTypes);
await _telemetryService.backupRestored(restored);
await _telemetryService.trackBackupRestored(restored);

if (!restored || !imported) {
return currentState.copyWith.call(restoreResult: result);
22 changes: 0 additions & 22 deletions lib/application/banner_history/banner_history_event.dart

This file was deleted.

Original file line number Diff line number Diff line change
@@ -9,28 +9,28 @@ import 'package:shiori/domain/models/models.dart';
import 'package:shiori/domain/services/genshin_service.dart';
import 'package:shiori/domain/services/telemetry_service.dart';

part 'banner_history_bloc.freezed.dart';
part 'banner_history_event.dart';
part 'banner_history_state.dart';
part 'banner_history_count_bloc.freezed.dart';
part 'banner_history_count_event.dart';
part 'banner_history_count_state.dart';

const _initialState = BannerHistoryState.initial(
const _initialState = BannerHistoryCountState.initial(
type: BannerHistoryItemType.character,
sortType: BannerHistorySortType.versionAsc,
banners: [],
versions: [],
maxNumberOfItems: 0,
);

class BannerHistoryBloc extends Bloc<BannerHistoryEvent, BannerHistoryState> {
class BannerHistoryCountBloc extends Bloc<BannerHistoryCountEvent, BannerHistoryCountState> {
final GenshinService _genshinService;
final TelemetryService _telemetryService;
final List<BannerHistoryItemModel> _characterBanners = [];
final List<BannerHistoryItemModel> _weaponBanners = [];

BannerHistoryBloc(this._genshinService, this._telemetryService) : super(_initialState);
BannerHistoryCountBloc(this._genshinService, this._telemetryService) : super(_initialState);

@override
Stream<BannerHistoryState> mapEventToState(BannerHistoryEvent event) async* {
Stream<BannerHistoryCountState> mapEventToState(BannerHistoryCountEvent event) async* {
final s = await event.map(
init: (e) async => _init(),
typeChanged: (e) async => _typeChanged(e.type),
@@ -47,14 +47,14 @@ class BannerHistoryBloc extends Bloc<BannerHistoryEvent, BannerHistoryState> {
return banners.map((e) => ItemCommonWithName(e.key, e.image, e.name)).toSet().toList();
}

Future<BannerHistoryState> _init() async {
Future<BannerHistoryCountState> _init() async {
await _telemetryService.trackBannerHistoryOpened();
_characterBanners.addAll(_genshinService.bannerHistory.getBannerHistory(BannerHistoryItemType.character));
_weaponBanners.addAll(_genshinService.bannerHistory.getBannerHistory(BannerHistoryItemType.weapon));

final versions = _genshinService.bannerHistory.getBannerHistoryVersions(SortDirectionType.asc);
final banners = _getFinalSortedBanners(_characterBanners, versions, state.sortType);
return BannerHistoryState.initial(
return BannerHistoryCountState.initial(
type: BannerHistoryItemType.character,
sortType: _initialState.sortType,
banners: banners,
@@ -63,7 +63,7 @@ class BannerHistoryBloc extends Bloc<BannerHistoryEvent, BannerHistoryState> {
);
}

BannerHistoryState _typeChanged(BannerHistoryItemType type) {
BannerHistoryCountState _typeChanged(BannerHistoryItemType type) {
if (type == state.type) {
return state;
}
@@ -73,10 +73,8 @@ class BannerHistoryBloc extends Bloc<BannerHistoryEvent, BannerHistoryState> {
switch (type) {
case BannerHistoryItemType.character:
banners.addAll(_getFinalSortedBanners(_characterBanners, selectedVersions, state.sortType));
break;
case BannerHistoryItemType.weapon:
banners.addAll(_getFinalSortedBanners(_weaponBanners, selectedVersions, state.sortType));
break;
default:
throw Exception('Banner history item type = $type is not valid');
}
@@ -89,7 +87,7 @@ class BannerHistoryBloc extends Bloc<BannerHistoryEvent, BannerHistoryState> {
);
}

BannerHistoryState _sortTypeChanged(BannerHistorySortType type) {
BannerHistoryCountState _sortTypeChanged(BannerHistorySortType type) {
if (type == state.sortType) {
return state;
}
@@ -99,7 +97,7 @@ class BannerHistoryBloc extends Bloc<BannerHistoryEvent, BannerHistoryState> {
return state.copyWith.call(banners: banners, versions: versions, sortType: type);
}

BannerHistoryState _versionSelected(double version) {
BannerHistoryCountState _versionSelected(double version) {
final selectedVersions = <double>[];
if (state.selectedVersions.contains(version)) {
selectedVersions.addAll(state.selectedVersions.where((value) => value != version));
@@ -118,7 +116,7 @@ class BannerHistoryBloc extends Bloc<BannerHistoryEvent, BannerHistoryState> {
);
}

BannerHistoryState _itemsSelected(List<String> keys) {
BannerHistoryCountState _itemsSelected(List<String> keys) {
if (keys.equals(state.selectedItemKeys)) {
return state;
}
@@ -176,10 +174,8 @@ class BannerHistoryBloc extends Bloc<BannerHistoryEvent, BannerHistoryState> {
switch (type) {
case BannerHistoryItemType.character:
banners.addAll(_characterBanners);
break;
case BannerHistoryItemType.weapon:
banners.addAll(_weaponBanners);
break;
default:
throw Exception('Banner history item type = $type is not valid');
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
part of 'banner_history_count_bloc.dart';

@freezed
class BannerHistoryCountEvent with _$BannerHistoryCountEvent {
const factory BannerHistoryCountEvent.init() = _Init;

const factory BannerHistoryCountEvent.typeChanged({
required BannerHistoryItemType type,
}) = _TypeChanged;

const factory BannerHistoryCountEvent.sortTypeChanged({
required BannerHistorySortType type,
}) = _SortTypeChanged;

const factory BannerHistoryCountEvent.versionSelected({
required double version,
}) = _VersionSelected;

const factory BannerHistoryCountEvent.itemsSelected({
required List<String> keys,
}) = _CharactersSelected;
}
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
part of 'banner_history_bloc.dart';
part of 'banner_history_count_bloc.dart';

@freezed
class BannerHistoryState with _$BannerHistoryState {
const factory BannerHistoryState.initial({
class BannerHistoryCountState with _$BannerHistoryCountState {
const factory BannerHistoryCountState.initial({
required BannerHistoryItemType type,
required BannerHistorySortType sortType,
required List<BannerHistoryItemModel> banners,

This file was deleted.

11 changes: 0 additions & 11 deletions lib/application/banner_history_item/banner_history_item_state.dart

This file was deleted.

Original file line number Diff line number Diff line change
@@ -6,28 +6,28 @@ import 'package:shiori/domain/models/models.dart';
import 'package:shiori/domain/services/genshin_service.dart';
import 'package:shiori/domain/services/telemetry_service.dart';

part 'banner_history_item_bloc.freezed.dart';
part 'banner_history_item_event.dart';
part 'banner_history_item_state.dart';
part 'banner_version_history_bloc.freezed.dart';
part 'banner_version_history_event.dart';
part 'banner_version_history_state.dart';

class BannerHistoryItemBloc extends Bloc<BannerHistoryItemEvent, BannerHistoryItemState> {
class BannerVersionHistoryBloc extends Bloc<BannerVersionHistoryEvent, BannerVersionHistoryState> {
final GenshinService _genshinService;
final TelemetryService _telemetryService;

static const periodDateFormat = 'yyyy/MM/dd';

BannerHistoryItemBloc(this._genshinService, this._telemetryService) : super(const BannerHistoryItemState.loading());
BannerVersionHistoryBloc(this._genshinService, this._telemetryService) : super(const BannerVersionHistoryState.loading());

@override
Stream<BannerHistoryItemState> mapEventToState(BannerHistoryItemEvent event) async* {
Stream<BannerVersionHistoryState> mapEventToState(BannerVersionHistoryEvent event) async* {
final s = await event.map(
init: (e) => _init(e.version),
);

yield s;
}

Future<BannerHistoryItemState> _init(double version) async {
Future<BannerVersionHistoryState> _init(double version) async {
await _telemetryService.trackBannerHistoryItemOpened(version);
final banners = _genshinService.bannerHistory.getBanners(version);
final grouped = banners
@@ -55,6 +55,6 @@ class BannerHistoryItemBloc extends Bloc<BannerHistoryItemEvent, BannerHistoryIt
);
},
).toList();
return BannerHistoryItemState.loadedState(version: version, items: grouped);
return BannerVersionHistoryState.loadedState(version: version, items: grouped);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
part of 'banner_version_history_bloc.dart';

@freezed
class BannerVersionHistoryEvent with _$BannerVersionHistoryEvent {
const factory BannerVersionHistoryEvent.init({
required double version,
}) = _Init;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
part of 'banner_version_history_bloc.dart';

@freezed
class BannerVersionHistoryState with _$BannerVersionHistoryState {
const factory BannerVersionHistoryState.loading() = _LoadingState;

const factory BannerVersionHistoryState.loadedState({
required double version,
required List<BannerHistoryGroupedPeriodModel> items,
}) = _LoadedState;
}
8 changes: 6 additions & 2 deletions lib/application/bloc.dart
Original file line number Diff line number Diff line change
@@ -2,8 +2,8 @@ export 'app_bloc_observer.dart';
export 'artifact/artifact_bloc.dart';
export 'artifacts/artifacts_bloc.dart';
export 'backup_restore/backup_restore_bloc.dart';
export 'banner_history/banner_history_bloc.dart';
export 'banner_history_item/banner_history_item_bloc.dart';
export 'banner_history_count/banner_history_count_bloc.dart';
export 'banner_version_history/banner_version_history_bloc.dart';
export 'calculator_asc_materials/material_item/calculator_asc_materials_item_bloc.dart';
export 'calculator_asc_materials/material_item_quantity/calculator_asc_materials_item_update_quantity_bloc.dart';
export 'calculator_asc_materials/materials/calculator_asc_materials_bloc.dart';
@@ -50,3 +50,7 @@ export 'today_materials/today_materials_bloc.dart';
export 'url_page/url_page_bloc.dart';
export 'weapon/weapon_bloc.dart';
export 'weapons/weapons_bloc.dart';
export 'wish_banner_history/wish_banner_history_bloc.dart';
export 'wish_simulator/wish_simulator_bloc.dart';
export 'wish_simulator_pull_history/wish_simulator_pull_history_bloc.dart';
export 'wish_simulator_result/wish_simulator_result_bloc.dart';
Original file line number Diff line number Diff line change
@@ -9,7 +9,6 @@ import 'package:shiori/domain/models/models.dart';
import 'package:shiori/domain/services/calculator_service.dart';
import 'package:shiori/domain/services/genshin_service.dart';
import 'package:shiori/domain/services/resources_service.dart';
import 'package:tuple/tuple.dart';

part 'calculator_asc_materials_item_bloc.freezed.dart';
part 'calculator_asc_materials_item_event.dart';
@@ -103,8 +102,8 @@ class CalculatorAscMaterialsItemBloc extends Bloc<CalculatorAscMaterialsItemEven

CalculatorAscMaterialsItemState _levelChanged(int currentLevel, int desiredLevel, bool currentChanged) {
final tuple = _checkProvidedLevels(currentLevel, desiredLevel, currentChanged);
final cl = tuple.item1;
final dl = tuple.item2;
final cl = tuple.$1;
final dl = tuple.$2;

final cAsc = _calculatorService.getClosestAscensionLevelFor(cl, currentState.currentAscensionLevel);
final dAsc = _calculatorService.getClosestAscensionLevelFor(dl, currentState.desiredAscensionLevel);
@@ -121,9 +120,9 @@ class CalculatorAscMaterialsItemBloc extends Bloc<CalculatorAscMaterialsItemEven

CalculatorAscMaterialsItemState _ascensionChanged(int currentLevel, int desiredLevel, bool currentChanged) {
final tuple = _checkProvidedLevels(currentLevel, desiredLevel, currentChanged);
final bothAreZero = tuple.item1 == tuple.item2 && tuple.item1 == 0;
final cAsc = tuple.item1;
final dAsc = bothAreZero ? 1 : tuple.item2;
final bothAreZero = tuple.$1 == tuple.$2 && tuple.$1 == 0;
final cAsc = tuple.$1;
final dAsc = bothAreZero ? 1 : tuple.$2;

//Here we consider the 0, because otherwise we will always start from a current level of 1, and sometimes, we want to know the whole thing
//(from 1 to 10 with 1 inclusive)
@@ -140,8 +139,8 @@ class CalculatorAscMaterialsItemBloc extends Bloc<CalculatorAscMaterialsItemEven
_calculatorService.getItemLevelToUse(dAsc, currentState.desiredLevel),
currentChanged,
);
final cl = levelTuple.item1;
final dl = levelTuple.item2;
final cl = levelTuple.$1;
final dl = levelTuple.$2;

final skills = _updateSkills(cAsc, dAsc);
return currentState.copyWith.call(
@@ -168,8 +167,8 @@ class CalculatorAscMaterialsItemBloc extends Bloc<CalculatorAscMaterialsItemEven
currentChanged ? item.desiredLevel : newValue,
currentChanged,
);
final cl = tuple.item1;
final dl = tuple.item2;
final cl = tuple.$1;
final dl = tuple.$2;

if (cl > maxSkillLevel || cl < minSkillLevel) {
return currentState;
@@ -190,18 +189,18 @@ class CalculatorAscMaterialsItemBloc extends Bloc<CalculatorAscMaterialsItemEven
item.copyWith.call(
currentLevel: cl,
desiredLevel: dl,
isCurrentDecEnabled: enableTuple.item1,
isCurrentIncEnabled: enableTuple.item2,
isDesiredDecEnabled: enableTuple.item3,
isDesiredIncEnabled: enableTuple.item4,
isCurrentDecEnabled: enableTuple.$1,
isCurrentIncEnabled: enableTuple.$2,
isDesiredDecEnabled: enableTuple.$3,
isDesiredIncEnabled: enableTuple.$4,
),
);
}

return currentState.copyWith.call(skills: skills);
}

Tuple2<int, int> _checkProvidedLevels(int currentLevel, int desiredLevel, bool currentChanged) {
(int, int) _checkProvidedLevels(int currentLevel, int desiredLevel, bool currentChanged) {
var cl = currentLevel;
var dl = desiredLevel;

@@ -215,7 +214,7 @@ class CalculatorAscMaterialsItemBloc extends Bloc<CalculatorAscMaterialsItemEven
}
}

return Tuple2<int, int>(cl, dl);
return (cl, dl);
}

List<CharacterSkill> _updateSkills(int currentAscensionLevel, int desiredAscensionLevel) {
@@ -236,10 +235,10 @@ class CalculatorAscMaterialsItemBloc extends Bloc<CalculatorAscMaterialsItemEven
skill.copyWith.call(
currentLevel: cSkill,
desiredLevel: dSkill,
isCurrentDecEnabled: enableTuple.item1,
isCurrentIncEnabled: enableTuple.item2,
isDesiredDecEnabled: enableTuple.item3,
isDesiredIncEnabled: enableTuple.item4,
isCurrentDecEnabled: enableTuple.$1,
isCurrentIncEnabled: enableTuple.$2,
isDesiredDecEnabled: enableTuple.$3,
isDesiredIncEnabled: enableTuple.$4,
),
);
}
@@ -270,10 +269,10 @@ class CalculatorAscMaterialsItemBloc extends Bloc<CalculatorAscMaterialsItemEven
position: i,
currentLevel: minSkillLevel,
desiredLevel: maxSkillLevel,
isCurrentDecEnabled: enableTuple.item1,
isCurrentIncEnabled: enableTuple.item2,
isDesiredDecEnabled: enableTuple.item3,
isDesiredIncEnabled: enableTuple.item4,
isCurrentDecEnabled: enableTuple.$1,
isCurrentIncEnabled: enableTuple.$2,
isDesiredDecEnabled: enableTuple.$3,
isDesiredIncEnabled: enableTuple.$4,
);
skills.add(skill);
}
36 changes: 13 additions & 23 deletions lib/application/characters/characters_bloc.dart
Original file line number Diff line number Diff line change
@@ -6,6 +6,7 @@ import 'package:shiori/domain/enums/enums.dart';
import 'package:shiori/domain/models/models.dart';
import 'package:shiori/domain/services/genshin_service.dart';
import 'package:shiori/domain/services/settings_service.dart';
import 'package:shiori/domain/utils/filter_utils.dart';

part 'characters_bloc.freezed.dart';
part 'characters_event.dart';
@@ -32,27 +33,11 @@ class CharactersBloc extends Bloc<CharactersEvent, CharactersState> {
return _buildInitialState(excludeKeys: e.excludeKeys, elementTypes: ElementType.values, weaponTypes: WeaponType.values);
},
characterFilterTypeChanged: (e) => currentState.copyWith.call(tempCharacterFilterType: e.characterFilterType),
elementTypeChanged: (e) {
var types = <ElementType>[];
if (currentState.tempElementTypes.contains(e.elementType)) {
types = currentState.tempElementTypes.where((t) => t != e.elementType).toList();
} else {
types = currentState.tempElementTypes + [e.elementType];
}
return currentState.copyWith.call(tempElementTypes: types);
},
elementTypeChanged: (e) => _elementTypeChanged(e.elementType),
rarityChanged: (e) => currentState.copyWith.call(tempRarity: e.rarity),
itemStatusChanged: (e) => currentState.copyWith.call(tempStatusType: e.statusType),
sortDirectionTypeChanged: (e) => currentState.copyWith.call(tempSortDirectionType: e.sortDirectionType),
weaponTypeChanged: (e) {
var types = <WeaponType>[];
if (currentState.tempWeaponTypes.contains(e.weaponType)) {
types = currentState.tempWeaponTypes.where((t) => t != e.weaponType).toList();
} else {
types = currentState.tempWeaponTypes + [e.weaponType];
}
return currentState.copyWith.call(tempWeaponTypes: types);
},
weaponTypeChanged: (e) => _weaponTypeChanged(e.weaponType),
roleTypeChanged: (e) => currentState.copyWith.call(tempRoleType: e.roleType),
searchChanged: (e) => _buildInitialState(
search: e.search,
@@ -98,6 +83,16 @@ class CharactersBloc extends Bloc<CharactersEvent, CharactersState> {
yield s;
}

CharactersState _elementTypeChanged(ElementType selectedValue) {
final List<ElementType> types = FilterUtils.handleTypeSelected(ElementType.values, currentState.tempElementTypes, selectedValue);
return currentState.copyWith.call(tempElementTypes: types);
}

CharactersState _weaponTypeChanged(WeaponType selectedValue) {
final List<WeaponType> types = FilterUtils.handleTypeSelected(WeaponType.values, currentState.tempWeaponTypes, selectedValue);
return currentState.copyWith.call(tempWeaponTypes: types);
}

CharactersState _buildInitialState({
String? search,
List<String> excludeKeys = const [],
@@ -171,13 +166,10 @@ class CharactersBloc extends Bloc<CharactersEvent, CharactersState> {
switch (statusType) {
case ItemStatusType.released:
characters = characters.where((el) => !el.isComingSoon).toList();
break;
case ItemStatusType.comingSoon:
characters = characters.where((el) => el.isComingSoon).toList();
break;
case ItemStatusType.newItem:
characters = characters.where((el) => el.isNew).toList();
break;
default:
break;
}
@@ -216,14 +208,12 @@ class CharactersBloc extends Bloc<CharactersEvent, CharactersState> {
} else {
data.sort((x, y) => y.name.compareTo(x.name));
}
break;
case CharacterFilterType.rarity:
if (sortDirectionType == SortDirectionType.asc) {
data.sort((x, y) => x.stars.compareTo(y.stars));
} else {
data.sort((x, y) => y.stars.compareTo(x.stars));
}
break;
default:
break;
}
Original file line number Diff line number Diff line change
@@ -59,10 +59,8 @@ class ChartAscensionStatsBloc extends Bloc<ChartAscensionStatsEvent, ChartAscens
switch (itemType) {
case ItemType.character:
ascensionStats.addAll(_characterAscensionStats.take(maxNumberOfColumns));
break;
case ItemType.weapon:
ascensionStats.addAll(_weaponAscensionStats.take(maxNumberOfColumns));
break;
default:
throw Exception('ItemType = $itemType is not valid');
}
@@ -90,10 +88,8 @@ class ChartAscensionStatsBloc extends Bloc<ChartAscensionStatsEvent, ChartAscens
switch (itemType) {
case ItemType.character:
pages = _characterAscensionStats.length / take;
break;
case ItemType.weapon:
pages = _weaponAscensionStats.length / take;
break;
default:
throw Exception('ItemType = $itemType is not valid');
}
@@ -135,10 +131,8 @@ class ChartAscensionStatsBloc extends Bloc<ChartAscensionStatsEvent, ChartAscens
switch (state.itemType) {
case ItemType.character:
ascensionStats.addAll(_characterAscensionStats.skip(skip).take(state.maxNumberOfColumns));
break;
case ItemType.weapon:
ascensionStats.addAll(_weaponAscensionStats.skip(skip).take(state.maxNumberOfColumns));
break;
default:
throw Exception('ItemType = ${state.itemType} is not valid');
}
2 changes: 0 additions & 2 deletions lib/application/materials/materials_bloc.dart
Original file line number Diff line number Diff line change
@@ -106,11 +106,9 @@ class MaterialsBloc extends Bloc<MaterialsEvent, MaterialsState> {
case MaterialType.expWeapon:
case MaterialType.expCharacter:
data = data.where((el) => [MaterialType.expWeapon, MaterialType.expCharacter].contains(el.type)).toList();
break;
case MaterialType.weaponPrimary:
case MaterialType.weapon:
data = data.where((el) => [MaterialType.weaponPrimary, MaterialType.weapon].contains(el.type)).toList();
break;
default:
data = data.where((el) => el.type == type).toList();
break;
34 changes: 2 additions & 32 deletions lib/application/notification/notification_bloc.dart
Original file line number Diff line number Diff line change
@@ -23,11 +23,7 @@ part 'notification_event.dart';
part 'notification_state.dart';

//just a dummy state
const _initialState = NotificationState.resin(
images: [],
showNotification: true,
currentResin: 0,
);
const _initialState = NotificationState.resin(currentResin: 0);

class NotificationBloc extends Bloc<NotificationEvent, NotificationState> {
final DataService _dataService;
@@ -154,7 +150,6 @@ class NotificationBloc extends Bloc<NotificationEvent, NotificationState> {
isTitleValid: true,
isBodyValid: true,
images: _getImagesForResin(),
showNotification: true,
currentResin: 0,
);
}
@@ -167,39 +162,31 @@ class NotificationBloc extends Bloc<NotificationEvent, NotificationState> {
case AppNotificationType.resin:
images.addAll(_getImagesForResin());
state = NotificationState.resin(currentResin: item.currentResinValue);
break;
case AppNotificationType.expedition:
images.addAll(_getImagesForExpeditionNotifications(selectedImage: item.image));
state = NotificationState.expedition(expeditionTimeType: item.expeditionTimeType!, withTimeReduction: item.withTimeReduction);
break;
case AppNotificationType.farmingArtifacts:
images.addAll(_getImagesForFarmingArtifactNotifications(selectedImage: item.image));
state = NotificationState.farmingArtifact(artifactFarmingTimeType: item.artifactFarmingTimeType!);
break;
case AppNotificationType.farmingMaterials:
images.addAll(_getImagesForFarmingMaterialNotifications(selectedImage: item.image));
state = const NotificationState.farmingMaterial();
break;
case AppNotificationType.gadget:
images.addAll(_getImagesForGadgetNotifications(selectedImage: item.image));
state = const NotificationState.gadget();
break;
case AppNotificationType.furniture:
images.addAll(_getImagesForFurnitureNotifications(selectedImage: item.image));
state = NotificationState.furniture(timeType: item.furnitureCraftingTimeType!);
break;
case AppNotificationType.realmCurrency:
images.addAll(_getImagesForRealmCurrencyNotifications(selectedImage: item.image));
state = NotificationState.realmCurrency(
currentTrustRank: item.realmTrustRank!,
currentRealmCurrency: item.realmCurrency!,
currentRealmRankType: item.realmRankType!,
);
break;
case AppNotificationType.weeklyBoss:
images.addAll(_getImagesForWeeklyBossNotifications(selectedImage: item.image));
state = const NotificationState.weeklyBoss();
break;
case AppNotificationType.custom:
images.addAll(_getImagesForCustomNotifications(itemKey: item.itemKey, selectedImage: item.image));
state = NotificationState.custom(
@@ -208,11 +195,9 @@ class NotificationBloc extends Bloc<NotificationEvent, NotificationState> {
language: _localeService.getLocaleWithoutLang(),
useTwentyFourHoursFormat: _settingsService.useTwentyFourHoursFormat,
);
break;
case AppNotificationType.dailyCheckIn:
images.addAll(_getImagesForDailyCheckIn(itemKey: item.itemKey, selectedImage: item.image));
state = const NotificationState.dailyCheckIn();
break;
default:
throw Exception('Invalid notification type = ${item.type}');
}
@@ -245,35 +230,27 @@ class NotificationBloc extends Bloc<NotificationEvent, NotificationState> {
case AppNotificationType.resin:
images.addAll(_getImagesForResin());
updatedState = const NotificationState.resin(currentResin: 0);
break;
case AppNotificationType.expedition:
images.addAll(_getImagesForExpeditionNotifications());
updatedState = const NotificationState.expedition(expeditionTimeType: ExpeditionTimeType.twentyHours, withTimeReduction: false);
break;
case AppNotificationType.farmingArtifacts:
images.addAll(_getImagesForFarmingArtifactNotifications());
updatedState = const NotificationState.farmingArtifact();
break;
case AppNotificationType.farmingMaterials:
images.addAll(_getImagesForFarmingMaterialNotifications());
updatedState = const NotificationState.farmingMaterial();
break;
case AppNotificationType.gadget:
images.addAll(_getImagesForGadgetNotifications());
updatedState = const NotificationState.gadget();
break;
case AppNotificationType.furniture:
images.addAll(_getImagesForFurnitureNotifications());
updatedState = const NotificationState.furniture();
break;
case AppNotificationType.realmCurrency:
images.addAll(_getImagesForRealmCurrencyNotifications());
updatedState = const NotificationState.realmCurrency();
break;
case AppNotificationType.weeklyBoss:
images.addAll(_getImagesForWeeklyBossNotifications());
updatedState = const NotificationState.weeklyBoss();
break;
case AppNotificationType.custom:
images.addAll(_getImagesForCustomNotifications());
updatedState = NotificationState.custom(
@@ -282,11 +259,9 @@ class NotificationBloc extends Bloc<NotificationEvent, NotificationState> {
language: _localeService.getLocaleWithoutLang(),
useTwentyFourHoursFormat: _settingsService.useTwentyFourHoursFormat,
);
break;
case AppNotificationType.dailyCheckIn:
images.addAll(_getImagesForDailyCheckIn());
updatedState = const NotificationState.dailyCheckIn();
break;
default:
throw Exception('The provided app notification type = $newValue is not valid');
}
@@ -314,24 +289,19 @@ class NotificationBloc extends Bloc<NotificationEvent, NotificationState> {
case AppNotificationItemType.character:
final character = _genshinService.characters.getCharactersForCard().first;
images.add(NotificationItemImage(itemKey: character.key, image: character.image, isSelected: true));
break;
case AppNotificationItemType.weapon:
final weapon = _genshinService.weapons.getWeaponsForCard().first;
images.add(NotificationItemImage(itemKey: weapon.key, image: weapon.image, isSelected: true));
break;
case AppNotificationItemType.artifact:
final artifact = _genshinService.artifacts.getArtifactsForCard().first;
images.add(NotificationItemImage(itemKey: artifact.key, image: artifact.image, isSelected: true));
break;
case AppNotificationItemType.monster:
final monster = _genshinService.monsters.getAllMonstersForCard().first;
images.add(NotificationItemImage(itemKey: monster.key, image: monster.image, isSelected: true));
break;
case AppNotificationItemType.material:
final material = _genshinService.materials.getAllMaterialsThatCanBeObtainedFromAnExpedition().first;
final imagePath = _resourceService.getMaterialImagePath(material.image, material.type);
images.add(NotificationItemImage(itemKey: material.key, image: imagePath, isSelected: true));
break;
default:
throw Exception('The provided notification item type = $newValue is not valid');
}
@@ -752,7 +722,7 @@ class NotificationBloc extends Bloc<NotificationEvent, NotificationState> {
List<NotificationItemImage> _getImagesForRealmCurrencyNotifications({String? selectedImage}) {
final material = _genshinService.materials.getRealmCurrencyMaterial();
return [
NotificationItemImage(itemKey: material.key, image: _resourceService.getMaterialImagePath(material.image, material.type), isSelected: true)
NotificationItemImage(itemKey: material.key, image: _resourceService.getMaterialImagePath(material.image, material.type), isSelected: true),
];
}

4 changes: 3 additions & 1 deletion lib/application/splash/splash_bloc.dart
Original file line number Diff line number Diff line change
@@ -9,6 +9,7 @@ import 'package:shiori/domain/services/locale_service.dart';
import 'package:shiori/domain/services/resources_service.dart';
import 'package:shiori/domain/services/settings_service.dart';
import 'package:shiori/domain/services/telemetry_service.dart';
import 'package:shiori/env.dart';

part 'splash_bloc.freezed.dart';
part 'splash_event.dart';
@@ -53,7 +54,8 @@ class SplashBloc extends Bloc<SplashEvent, SplashState> {
await Future.delayed(const Duration(seconds: 1));
}

final skipCheck = !noResourcesHasBeenDownloaded && !_settingsService.checkForUpdatesOnStartup;
final skipCheck =
!noResourcesHasBeenDownloaded && !_settingsService.checkForUpdatesOnStartup && _settingsService.resourceVersion >= Env.minResourceVersion;
if (skipCheck) {
const resultType = AppResourceUpdateResultType.noUpdatesAvailable;
yield SplashState.loaded(
4 changes: 1 addition & 3 deletions lib/application/url_page/url_page_bloc.dart
Original file line number Diff line number Diff line change
@@ -13,7 +13,6 @@ part 'url_page_event.dart';
part 'url_page_state.dart';

class UrlPageBloc extends Bloc<UrlPageEvent, UrlPageState> {
final wishSimulatorUrl = 'https://gi-wish-simulator.uzairashraf.dev';
final officialMapUrl = 'https://act.hoyolab.com/ys/app/interactive-map/index.html';
final unofficialMapUrl = 'https://genshin-impact-map.appsample.com';
final dailyCheckInUrl = 'https://act.hoyolab.com/ys/event/signin-sea-v3/index.html?act_id=e202102251931481';
@@ -36,11 +35,10 @@ class UrlPageBloc extends Bloc<UrlPageEvent, UrlPageState> {
init: (e) async {
final finalMapUrl = _settingsService.useOfficialMap ? _getMapUrl() : unofficialMapUrl;
final isInternetAvailable = await _networkService.isInternetAvailable();
await _telemetryService.trackUrlOpened(e.loadMap, e.loadWishSimulator, e.loadDailyCheckIn, isInternetAvailable);
await _telemetryService.trackUrlOpened(e.loadMap, e.loadDailyCheckIn, isInternetAvailable);
return UrlPageState.loaded(
hasInternetConnection: isInternetAvailable,
mapUrl: finalMapUrl,
wishSimulatorUrl: wishSimulatorUrl,
dailyCheckInUrl: _getDailyCheckInUrl(),
userAgent: _deviceInfoService.userAgent ?? '',
);
1 change: 0 additions & 1 deletion lib/application/url_page/url_page_event.dart
Original file line number Diff line number Diff line change
@@ -4,7 +4,6 @@ part of 'url_page_bloc.dart';
class UrlPageEvent with _$UrlPageEvent {
const factory UrlPageEvent.init({
required bool loadMap,
required bool loadWishSimulator,
required bool loadDailyCheckIn,
}) = _Init;
}
1 change: 0 additions & 1 deletion lib/application/url_page/url_page_state.dart
Original file line number Diff line number Diff line change
@@ -4,7 +4,6 @@ part of 'url_page_bloc.dart';
class UrlPageState with _$UrlPageState {
const factory UrlPageState.loading() = _Loading;
const factory UrlPageState.loaded({
required String wishSimulatorUrl,
required String mapUrl,
required String dailyCheckInUrl,
required bool hasInternetConnection,
20 changes: 7 additions & 13 deletions lib/application/weapons/weapons_bloc.dart
Original file line number Diff line number Diff line change
@@ -6,6 +6,7 @@ import 'package:shiori/domain/enums/enums.dart';
import 'package:shiori/domain/models/models.dart';
import 'package:shiori/domain/services/genshin_service.dart';
import 'package:shiori/domain/services/settings_service.dart';
import 'package:shiori/domain/utils/filter_utils.dart';

part 'weapons_bloc.freezed.dart';
part 'weapons_event.dart';
@@ -38,15 +39,7 @@ class WeaponsBloc extends Bloc<WeaponsEvent, WeaponsState> {
weaponFilterTypeChanged: (e) => currentState.copyWith.call(tempWeaponFilterType: e.filterType),
rarityChanged: (e) => currentState.copyWith.call(tempRarity: e.rarity),
sortDirectionTypeChanged: (e) => currentState.copyWith.call(tempSortDirectionType: e.sortDirectionType),
weaponTypeChanged: (e) {
var types = <WeaponType>[];
if (currentState.tempWeaponTypes.contains(e.weaponType)) {
types = currentState.tempWeaponTypes.where((t) => t != e.weaponType).toList();
} else {
types = currentState.tempWeaponTypes + [e.weaponType];
}
return currentState.copyWith.call(tempWeaponTypes: types);
},
weaponTypeChanged: (e) => _weaponTypeChanged(e.weaponType),
weaponSubStatTypeChanged: (e) => currentState.copyWith.call(tempWeaponSubStatType: e.subStatType),
weaponLocationTypeChanged: (e) => currentState.copyWith.call(tempWeaponLocationType: e.locationType),
searchChanged: (e) => _buildInitialState(
@@ -90,6 +83,11 @@ class WeaponsBloc extends Bloc<WeaponsEvent, WeaponsState> {
yield s;
}

WeaponsState _weaponTypeChanged(WeaponType selectedValue) {
final List<WeaponType> types = FilterUtils.handleTypeSelected(WeaponType.values, currentState.tempWeaponTypes, selectedValue);
return currentState.copyWith.call(tempWeaponTypes: types);
}

WeaponsState _buildInitialState({
String? search,
List<String> excludeKeys = const [],
@@ -182,29 +180,25 @@ class WeaponsBloc extends Bloc<WeaponsEvent, WeaponsState> {
} else {
data.sort((x, y) => y.baseAtk.compareTo(x.baseAtk));
}
break;
case WeaponFilterType.name:
if (sortDirectionType == SortDirectionType.asc) {
data.sort((x, y) => x.name.compareTo(y.name));
} else {
data.sort((x, y) => y.name.compareTo(x.name));
}
break;

case WeaponFilterType.rarity:
if (sortDirectionType == SortDirectionType.asc) {
data.sort((x, y) => x.rarity.compareTo(y.rarity));
} else {
data.sort((x, y) => y.rarity.compareTo(x.rarity));
}
break;
case WeaponFilterType.subStat:
if (sortDirectionType == SortDirectionType.asc) {
data.sort((x, y) => x.subStatValue.compareTo(y.subStatValue));
} else {
data.sort((x, y) => y.subStatValue.compareTo(x.subStatValue));
}
break;
default:
break;
}
160 changes: 160 additions & 0 deletions lib/application/wish_banner_history/wish_banner_history_bloc.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,160 @@
import 'package:bloc/bloc.dart';
import 'package:collection/collection.dart';
import 'package:darq/darq.dart';
import 'package:freezed_annotation/freezed_annotation.dart';
import 'package:shiori/domain/enums/enums.dart';
import 'package:shiori/domain/models/models.dart';
import 'package:shiori/domain/services/genshin_service.dart';

part 'wish_banner_history_bloc.freezed.dart';
part 'wish_banner_history_event.dart';
part 'wish_banner_history_state.dart';

class WishBannerHistoryBloc extends Bloc<WishBannerHistoryEvent, WishBannerHistoryState> {
final GenshinService _genshinService;

WishBannerHistoryBloc(this._genshinService) : super(const WishBannerHistoryState.loading());

@override
Stream<WishBannerHistoryState> mapEventToState(WishBannerHistoryEvent event) async* {
final s = event.map(
init: (_) => _init(SortDirectionType.desc),
groupTypeChanged: (e) => state.maybeMap(
loaded: (state) => _groupTypeChanged(state, e.type),
orElse: () => throw Exception('Invalid state'),
),
sortDirectionTypeChanged: (e) => state.maybeMap(
loaded: (state) => _sortDirectionTypeChanged(state, e.type),
orElse: () => throw Exception('Invalid state'),
),
itemsSelected: (e) => state.maybeMap(
loaded: (state) => _itemsSelected(state, e.keys),
orElse: () => throw Exception('Invalid state'),
),
);

yield s;
}

List<ItemCommonWithNameOnly> getItemsForSearch() {
return state.map(
loading: (_) => throw Exception('Invalid state'),
loaded: (state) => state.filteredPeriods.map((e) => ItemCommonWithNameOnly(e.groupingKey, e.groupingTitle)).toList(),
);
}

WishBannerHistoryState _init(SortDirectionType sortDirectionType) {
final grouped = _genshinService.bannerHistory.getWishBannersHistoryGroupedByVersion()..sort((x, y) => _sort(x, y, sortDirectionType));
return WishBannerHistoryState.loaded(
allPeriods: grouped,
filteredPeriods: grouped,
sortDirectionType: sortDirectionType,
groupedType: WishBannerGroupedType.version,
);
}

WishBannerHistoryState _groupTypeChanged(_LoadedState state, WishBannerGroupedType type) {
if (state.groupedType == type) {
return state;
}
switch (type) {
case WishBannerGroupedType.character:
return _groupByCharacterOrWeapon(state, type);
case WishBannerGroupedType.weapon:
return _groupByCharacterOrWeapon(state, type);
default:
final periods = [...state.allPeriods]..sort((x, y) => _sort(x, y, state.sortDirectionType));
return state.copyWith.call(filteredPeriods: periods, groupedType: type, selectedItemKeys: []);
}
}

WishBannerHistoryState _groupByCharacterOrWeapon(_LoadedState state, WishBannerGroupedType groupedType) {
const sortType = SortDirectionType.asc;
final groups = _getGroupedByCharacterOrWeaponPeriod(state.allPeriods, groupedType)..sort((x, y) => _sort(x, y, sortType));
return state.copyWith(filteredPeriods: groups, groupedType: groupedType, selectedItemKeys: [], sortDirectionType: sortType);
}

List<WishBannerHistoryGroupedPeriodModel> _getGroupedByCharacterOrWeaponPeriod(
List<WishBannerHistoryGroupedPeriodModel> allPeriods,
WishBannerGroupedType groupedType,
) {
assert(groupedType == WishBannerGroupedType.character || groupedType == WishBannerGroupedType.weapon);

final groupByCharacter = groupedType == WishBannerGroupedType.character;
final groupsMap = <String, List<WishBannerHistoryPartItemModel>>{};
final allParts = allPeriods.selectMany((e, _) => e.parts).toList();
for (final part in allParts) {
final items = groupByCharacter ? part.featuredCharacters : part.featuredWeapons;
for (final item in items) {
groupsMap.putIfAbsent(item.key, () => []);
groupsMap[item.key]!.add(part);
}
}

return groupsMap.entries.map((e) {
final parts = e.value..sort((x, y) => x.version.compareTo(y.version));
final firstPart = parts.first;
final groupingTitle = (groupByCharacter ? firstPart.featuredCharacters : firstPart.featuredWeapons).firstWhere((c) => c.key == e.key).name;
return WishBannerHistoryGroupedPeriodModel(groupingKey: e.key, groupingTitle: groupingTitle, parts: parts);
}).toList();
}

WishBannerHistoryState _sortDirectionTypeChanged(_LoadedState state, SortDirectionType type) {
if (state.sortDirectionType == type) {
return state;
}

final periods = [...state.filteredPeriods]..sort((x, y) => _sort(x, y, type));
return state.copyWith(filteredPeriods: periods, sortDirectionType: type);
}

WishBannerHistoryState _itemsSelected(_LoadedState state, List<String> keys) {
if (keys.equals(state.selectedItemKeys)) {
return state;
}

final filteredPeriods = <WishBannerHistoryGroupedPeriodModel>[];
if (keys.isNotEmpty) {
switch (state.groupedType) {
case WishBannerGroupedType.version:
filteredPeriods.addAll(state.allPeriods.where((el) => keys.contains(el.groupingKey)));
case WishBannerGroupedType.character:
case WishBannerGroupedType.weapon:
final groupByCharacter = state.groupedType == WishBannerGroupedType.character;
final periods = _getGroupedByCharacterOrWeaponPeriod(state.allPeriods, state.groupedType);
for (final period in periods) {
final firstPart = period.parts.first;
final promotedItem = (groupByCharacter ? firstPart.featuredCharacters : firstPart.featuredWeapons).firstWhere(
(el) => el.key == period.groupingKey,
);
if (keys.contains(promotedItem.key)) {
filteredPeriods.add(period);
}
}
}
} else {
switch (state.groupedType) {
case WishBannerGroupedType.version:
filteredPeriods.addAll(state.allPeriods);
case WishBannerGroupedType.character:
case WishBannerGroupedType.weapon:
final periods = _getGroupedByCharacterOrWeaponPeriod(state.allPeriods, state.groupedType);
filteredPeriods.addAll(periods);
}
}

return state.copyWith.call(
filteredPeriods: filteredPeriods..sort((x, y) => _sort(x, y, state.sortDirectionType)),
selectedItemKeys: keys,
);
}

int _sort(WishBannerHistoryGroupedPeriodModel x, WishBannerHistoryGroupedPeriodModel y, SortDirectionType type) {
switch (type) {
case SortDirectionType.asc:
return compareNatural(x.groupingTitle, y.groupingTitle);
case SortDirectionType.desc:
return compareNatural(y.groupingTitle, x.groupingTitle);
}
}
}
18 changes: 18 additions & 0 deletions lib/application/wish_banner_history/wish_banner_history_event.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
part of 'wish_banner_history_bloc.dart';

@freezed
class WishBannerHistoryEvent with _$WishBannerHistoryEvent {
const factory WishBannerHistoryEvent.init() = _Init;

const factory WishBannerHistoryEvent.groupTypeChanged(
WishBannerGroupedType type,
) = _GroupTypeChanged;

const factory WishBannerHistoryEvent.sortDirectionTypeChanged(
SortDirectionType type,
) = _SortDirectionTypeChanged;

const factory WishBannerHistoryEvent.itemsSelected({
required List<String> keys,
}) = _ItemsSelected;
}
14 changes: 14 additions & 0 deletions lib/application/wish_banner_history/wish_banner_history_state.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
part of 'wish_banner_history_bloc.dart';

@freezed
class WishBannerHistoryState with _$WishBannerHistoryState {
const factory WishBannerHistoryState.loading() = _LoadingState;

const factory WishBannerHistoryState.loaded({
required List<WishBannerHistoryGroupedPeriodModel> allPeriods,
required List<WishBannerHistoryGroupedPeriodModel> filteredPeriods,
required SortDirectionType sortDirectionType,
required WishBannerGroupedType groupedType,
@Default(<String>[]) List<String> selectedItemKeys,
}) = _LoadedState;
}
87 changes: 87 additions & 0 deletions lib/application/wish_simulator/wish_simulator_bloc.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
import 'package:bloc/bloc.dart';
import 'package:freezed_annotation/freezed_annotation.dart';
import 'package:shiori/domain/enums/enums.dart';
import 'package:shiori/domain/models/models.dart';
import 'package:shiori/domain/services/genshin_service.dart';
import 'package:shiori/domain/services/resources_service.dart';
import 'package:shiori/domain/services/telemetry_service.dart';

part 'wish_simulator_bloc.freezed.dart';
part 'wish_simulator_event.dart';
part 'wish_simulator_state.dart';

class WishSimulatorBloc extends Bloc<WishSimulatorEvent, WishSimulatorState> {
final GenshinService _genshinServiceImpl;
final ResourceService _resourceService;
final TelemetryService _telemetryService;

_LoadedState get currentState => state as _LoadedState;

WishSimulatorBloc(this._genshinServiceImpl, this._resourceService, this._telemetryService) : super(const WishSimulatorState.loading());

@override
Stream<WishSimulatorState> mapEventToState(WishSimulatorEvent event) async* {
final s = await event.map(
init: (e) => _init(),
periodChanged: (e) => _periodChanged(e.version, e.from, e.until),
bannerSelected: (e) async => _bannerChanged(e.index),
);

yield s;
}

void _checkLoadedState() {
if (state is! _LoadedState) {
throw Exception('Invalid state');
}
}

Future<WishSimulatorState> _init() async {
final version = _genshinServiceImpl.bannerHistory.getBannerHistoryVersions(SortDirectionType.asc).last;
final banner = _genshinServiceImpl.bannerHistory.getBanners(version).last;
final period = _genshinServiceImpl.bannerHistory.getWishSimulatorBannerPerPeriod(version, banner.from, banner.until);
await _telemetryService.trackWishSimulatorOpened(version);
return WishSimulatorState.loaded(
selectedBannerIndex: 0,
wishIconImage: _getWishIconImage(period.banners.first.type),
period: period,
);
}

Future<WishSimulatorState> _periodChanged(double version, DateTime from, DateTime until) async {
_checkLoadedState();
await _telemetryService.trackWishSimulatorOpened(version);
final period = _genshinServiceImpl.bannerHistory.getWishSimulatorBannerPerPeriod(version, from, until);
return WishSimulatorState.loaded(
selectedBannerIndex: 0,
wishIconImage: _getWishIconImage(period.banners.first.type),
period: period,
);
}

WishSimulatorState _bannerChanged(int index) {
_checkLoadedState();

if (index < 0 || index > currentState.period.banners.length - 1) {
throw Exception('The provided index = $index is not valid');
}

if (index == currentState.selectedBannerIndex) {
return currentState;
}

return currentState.copyWith(selectedBannerIndex: index, wishIconImage: _getWishIconImage(currentState.period.banners[index].type));
}

String _getWishIconImage(BannerItemType type) {
switch (type) {
case BannerItemType.character:
case BannerItemType.weapon:
final material = _genshinServiceImpl.materials.getIntertwinedFate();
return _resourceService.getMaterialImagePath(material.image, material.type);
case BannerItemType.standard:
final material = _genshinServiceImpl.materials.getAcquaintFate();
return _resourceService.getMaterialImagePath(material.image, material.type);
}
}
}
14 changes: 14 additions & 0 deletions lib/application/wish_simulator/wish_simulator_event.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
part of 'wish_simulator_bloc.dart';

@freezed
class WishSimulatorEvent with _$WishSimulatorEvent {
const factory WishSimulatorEvent.init() = _Init;

const factory WishSimulatorEvent.periodChanged({
required double version,
required DateTime from,
required DateTime until,
}) = _PeriodChanged;

const factory WishSimulatorEvent.bannerSelected({required int index}) = _BannerSelected;
}
12 changes: 12 additions & 0 deletions lib/application/wish_simulator/wish_simulator_state.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
part of 'wish_simulator_bloc.dart';

@freezed
class WishSimulatorState with _$WishSimulatorState {
const factory WishSimulatorState.loading() = _LoadingState;

const factory WishSimulatorState.loaded({
required String wishIconImage,
required int selectedBannerIndex,
required WishSimulatorBannerItemsPerPeriodModel period,
}) = _LoadedState;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:freezed_annotation/freezed_annotation.dart';
import 'package:intl/intl.dart';
import 'package:shiori/domain/enums/enums.dart';
import 'package:shiori/domain/models/models.dart';
import 'package:shiori/domain/services/data_service.dart';
import 'package:shiori/domain/services/genshin_service.dart';

part 'wish_simulator_pull_history_bloc.freezed.dart';
part 'wish_simulator_pull_history_event.dart';
part 'wish_simulator_pull_history_state.dart';

class WishSimulatorPullHistoryBloc extends Bloc<WishSimulatorPullHistoryEvent, WishSimulatorPullHistoryState> {
final GenshinService _genshinService;
final DataService _dataService;
final DateFormat _formatter = DateFormat('yyyy-MM-dd HH:mm:ss');
final List<CharacterCardModel> _allCharacters = [];
final List<WeaponCardModel> _allWeapons = [];

static const int take = 5;

WishSimulatorPullHistoryBloc(this._genshinService, this._dataService) : super(const WishSimulatorPullHistoryState.loading());

@override
Stream<WishSimulatorPullHistoryState> mapEventToState(WishSimulatorPullHistoryEvent event) async* {
if (_allCharacters.isEmpty) {
final allCharacters = _genshinService.characters.getCharactersForCard();
_allCharacters.addAll(allCharacters);
}

if (_allWeapons.isEmpty) {
final allWeapons = _genshinService.weapons.getWeaponsForCard();
_allWeapons.addAll(allWeapons);
}

final s = await event.map(
init: (e) async => state.map(
loading: (_) => _init(e.bannerType),
loaded: (state) {
if (state.bannerType == e.bannerType) {
return state;
}

return _init(e.bannerType);
},
),
pageChanged: (e) async => state.map(
loading: (_) => throw Exception('Invalid state'),
loaded: (state) => _pageChanged(state, e.page),
),
deleteData: (e) => state.map(
loading: (_) => throw Exception('Invalid state'),
loaded: (state) => _deleteData(e.bannerType),
),
);

yield s;
}

WishSimulatorPullHistoryState _init(BannerItemType bannerType) {
final pullHistory = _dataService.wishSimulator.getBannerItemsPullHistoryPerType(bannerType).map((e) {
final type = ItemType.values[e.itemType];
String name;
int rarity;
switch (type) {
case ItemType.character:
final character = _allCharacters.firstWhere((el) => el.key == e.itemKey);
name = character.name;
rarity = character.stars;
case ItemType.weapon:
final weapon = _allWeapons.firstWhere((el) => el.key == e.itemKey);
name = weapon.name;
rarity = weapon.rarity;
default:
throw Exception('Item type = $type is not valid here');
}

return WishSimulatorBannerItemPullHistoryModel(
key: e.itemKey,
name: name,
rarity: rarity,
type: type,
pulledOn: _formatter.format(e.pulledOnDate),
);
}).toList();

return WishSimulatorPullHistoryState.loaded(
bannerType: bannerType,
allItems: pullHistory,
items: pullHistory.take(take).toList(),
currentPage: 1,
maxPage: pullHistory.isEmpty ? 1 : (pullHistory.length / take).ceil(),
);
}

WishSimulatorPullHistoryState _pageChanged(_LoadedState state, int newPage) {
final selectedPage = newPage - 1;
if (selectedPage < 0 || selectedPage > state.maxPage) {
throw Exception('Page = $newPage is not valid');
}

if (state.currentPage == newPage) {
return state;
}

return state.copyWith(
currentPage: newPage,
items: state.allItems.skip(take * selectedPage).take(take).toList(),
);
}

Future<WishSimulatorPullHistoryState> _deleteData(BannerItemType bannerType) async {
await _dataService.wishSimulator.clearBannerItemPullHistory(bannerType);
return _init(bannerType);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
part of 'wish_simulator_pull_history_bloc.dart';

@freezed
class WishSimulatorPullHistoryEvent with _$WishSimulatorPullHistoryEvent {
const factory WishSimulatorPullHistoryEvent.init({required BannerItemType bannerType}) = _Init;

const factory WishSimulatorPullHistoryEvent.pageChanged({required int page}) = _PageChanged;

const factory WishSimulatorPullHistoryEvent.deleteData({required BannerItemType bannerType}) = _DeleteData;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
part of 'wish_simulator_pull_history_bloc.dart';

@freezed
class WishSimulatorPullHistoryState with _$WishSimulatorPullHistoryState {
const factory WishSimulatorPullHistoryState.loading() = _LoadingState;

const factory WishSimulatorPullHistoryState.loaded({
required BannerItemType bannerType,
required List<WishSimulatorBannerItemPullHistoryModel> allItems,
required List<WishSimulatorBannerItemPullHistoryModel> items,
required int currentPage,
required int maxPage,
}) = _LoadedState;
}
241 changes: 241 additions & 0 deletions lib/application/wish_simulator_result/wish_simulator_result_bloc.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,241 @@
import 'dart:math';

import 'package:bloc/bloc.dart';
import 'package:collection/collection.dart';
import 'package:darq/darq.dart';
import 'package:freezed_annotation/freezed_annotation.dart';
import 'package:shiori/domain/enums/enums.dart';
import 'package:shiori/domain/extensions/double_extensions.dart';
import 'package:shiori/domain/models/entities.dart';
import 'package:shiori/domain/models/models.dart';
import 'package:shiori/domain/services/data_service.dart';
import 'package:shiori/domain/services/telemetry_service.dart';
import 'package:shiori/domain/wish_banner_constants.dart';

part 'wish_simulator_result_bloc.freezed.dart';
part 'wish_simulator_result_event.dart';
part 'wish_simulator_result_state.dart';

class WishSimulatorResultBloc extends Bloc<WishSimulatorResultEvent, WishSimulatorResultState> {
final DataService _dataService;
final TelemetryService _telemetryService;
final Random _random;

WishSimulatorResultBloc(this._dataService, this._telemetryService)
: _random = Random(),
super(const WishSimulatorResultState.loading());

@override
Stream<WishSimulatorResultState> mapEventToState(WishSimulatorResultEvent event) async* {
final s = await event.map(
init: (e) => _pull(e.pulls, e.bannerIndex, e.period),
);

yield s;
}

Future<WishSimulatorResultState> _pull(int pulls, int bannerIndex, WishSimulatorBannerItemsPerPeriodModel period) async {
if (pulls <= 0) {
throw Exception('The provided pulls = $pulls is not valid');
}

if (bannerIndex < 0 || period.banners.elementAtOrNull(bannerIndex) == null) {
throw Exception('The provided bannerIndex = $bannerIndex is not valid');
}

final banner = period.banners[bannerIndex];
final bannerRates = _RatesPerBannerType(banner.type);
final history = await _dataService.wishSimulator.getBannerPullHistory(banner.type, defaultXStarCount: bannerRates.getDefaultXStarCount);
final results = <WishSimulatorBannerItemResultModel>[];
for (int i = 1; i <= pulls; i++) {
final int randomRarity = bannerRates.getRarityIfGuaranteed(history) ?? _getRandomItemRarity(history.currentXStarCount, bannerRates);
history.initXStarCountIfNeeded(randomRarity);

final isRarityInFeatured = banner.featuredItems.isEmpty || banner.featuredItems.any((el) => el.rarity == randomRarity);
final winsFiftyFifty = isRarityInFeatured && history.shouldWinFiftyFifty(randomRarity);
final pool = [
...banner.characters.map(
(e) => WishSimulatorBannerItemResultModel.character(key: e.key, image: e.image, rarity: e.rarity, elementType: e.elementType),
),
...banner.weapons.map(
(e) => WishSimulatorBannerItemResultModel.weapon(key: e.key, image: e.image, rarity: e.rarity, weaponType: e.weaponType),
),
].where((el) {
if (el.rarity != randomRarity) {
return false;
}

if (!isRarityInFeatured) {
return true;
}

return banner.featuredItems.isEmpty || banner.featuredItems.any((p) => winsFiftyFifty ? p.key == el.key : p.key != el.key);
}).toList();

assert(pool.isNotEmpty);
pool.shuffle(_random);
final pickedItem = pool[_random.nextInt(pool.length)];
results.add(pickedItem);

final bool? gotFeaturedItem = !bannerRates.canBeGuaranteed(randomRarity)
? true
: !isRarityInFeatured
? null
: winsFiftyFifty;
await history.pull(randomRarity, gotFeaturedItem);

final itemType = pickedItem.map(character: (_) => ItemType.character, weapon: (_) => ItemType.weapon);
await _dataService.wishSimulator.saveBannerItemPullHistory(banner.type, pickedItem.key, itemType);
}
final fromUntilString = '${WishBannerConstants.dateFormat.format(period.from)}/${WishBannerConstants.dateFormat.format(period.until)}';
await _telemetryService.trackWishSimulatorResult(bannerIndex, period.version, banner.type, fromUntilString);

final sortedResults = results.orderByDescending((el) => el.rarity).thenBy((el) {
final typeName = el.map(character: (_) => ItemType.character.name, weapon: (_) => ItemType.weapon.name);
return '$typeName-${el.key}';
}).toList();
return WishSimulatorResultState.loaded(results: sortedResults);
}

int _getRandomItemRarity(Map<int, int> currentXStarCount, _RatesPerBannerType bannerRates) {
assert(bannerRates._rates.isNotEmpty);

final probs = <double>[];
final randomRarities = <int>[];
for (final rate in bannerRates._rates) {
final int pullCount = currentXStarCount[rate.rarity] ?? 0;
final considerPullCount = rate.canBeGuaranteed && pullCount > rate.softRateIncreasesAt;
final double prob = rate.getProb(pullCount, considerPullCount);

final remainingProb = (100 - probs.sum).round();
if (remainingProb <= 0) {
continue;
}
probs.add(prob);
randomRarities.addAll(List.filled(prob.round(), rate.rarity));
}

randomRarities.shuffle();
assert(randomRarities.isNotEmpty);

return randomRarities[_random.nextInt(randomRarities.length)];
}
}

class _BannerRate {
final int rarity;
final int guaranteedAt;
final double initialRate;
final int softRateIncreasesAt;
final int hardRateIncreasesAt;

bool get canBeGuaranteed => !(guaranteedAt == -1);

const _BannerRate(
this.rarity,
this.guaranteedAt,
this.initialRate,
this.softRateIncreasesAt,
this.hardRateIncreasesAt,
) : assert(rarity >= WishBannerConstants.minObtainableRarity),
assert(guaranteedAt > 0 && guaranteedAt > hardRateIncreasesAt),
assert(initialRate > 0 && initialRate < 100),
assert(softRateIncreasesAt > 0 && softRateIncreasesAt < hardRateIncreasesAt);

const _BannerRate.simple(this.rarity, this.initialRate)
: guaranteedAt = -1,
softRateIncreasesAt = -1,
hardRateIncreasesAt = -1;

double _getSoftRateMultiplier() {
if (rarity == WishBannerConstants.maxObtainableRarity) {
return 0.5;
}

return 1.5;
}

double _getHardRateMultiplier() {
if (rarity == WishBannerConstants.maxObtainableRarity) {
return 2;
}
return 3;
}

double _getRate(int pullCount) {
double rate = initialRate / 100;
if (!canBeGuaranteed) {
return rate;
}
if (pullCount >= softRateIncreasesAt && pullCount < hardRateIncreasesAt) {
rate *= _getSoftRateMultiplier();
} else if (pullCount >= hardRateIncreasesAt) {
rate *= _getHardRateMultiplier();
}

return rate;
}

double getProb(int pullCount, bool considerPullCount) {
final double rate = _getRate(pullCount);
double x = -1 * rate;
if (considerPullCount) {
x *= pullCount;
}

final double y = ((1 - exp(x)) * 100).truncateToDecimalPlaces(fractionalDigits: 4);
if (y > 100) {
return 100;
}
return y;
}
}

class _RatesPerBannerType {
final BannerItemType type;
final List<_BannerRate> _rates = [];
final Map<int, int> _defaultXStarCount = {};

Map<int, int> get getDefaultXStarCount => Map.of(_defaultXStarCount);

_RatesPerBannerType(this.type) {
switch (type) {
case BannerItemType.character:
case BannerItemType.standard:
_rates.add(const _BannerRate(5, 90, 0.6, 40, 74));
_rates.add(const _BannerRate(4, 10, 5.1, 4, 7));
_rates.add(const _BannerRate.simple(3, 94.3));
case BannerItemType.weapon:
_rates.add(const _BannerRate(5, 80, 0.7, 30, 64));
_rates.add(const _BannerRate(4, 10, 6.0, 4, 7));
_rates.add(const _BannerRate.simple(3, 93.3));
}
_defaultXStarCount.addAll({for (final v in _rates) v.rarity: 0});
}

bool canBeGuaranteed(int rarity) {
final rate = _rates.firstWhereOrNull((el) => el.rarity == rarity);
if (rate == null) {
throw Exception('Rarity = $rarity does not have an associated rate');
}

return rate.canBeGuaranteed;
}

int? getRarityIfGuaranteed(WishSimulatorBannerPullHistory history) {
if (history.type != type.index) {
throw Exception('The rates only apply to banners of type = $type');
}
for (final rate in _rates) {
if (!rate.canBeGuaranteed) {
continue;
}

if (history.isItemGuaranteed(rate.rarity, rate.guaranteedAt)) {
return rate.rarity;
}
}

return null;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
part of 'wish_simulator_result_bloc.dart';

@freezed
class WishSimulatorResultEvent with _$WishSimulatorResultEvent {
const factory WishSimulatorResultEvent.init({
required int bannerIndex,
required int pulls,
required WishSimulatorBannerItemsPerPeriodModel period,
}) = _Init;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
part of 'wish_simulator_result_bloc.dart';

@freezed
class WishSimulatorResultState with _$WishSimulatorResultState {
const factory WishSimulatorResultState.loading() = _LoadingState;

const factory WishSimulatorResultState.loaded({
required List<WishSimulatorBannerItemResultModel> results,
}) = _LoadedState;
}
19 changes: 7 additions & 12 deletions lib/domain/app_constants.dart
Original file line number Diff line number Diff line change
@@ -196,7 +196,7 @@ const skillAscensionMap = {
3: [3, 4],
4: [5, 6],
5: [7, 8],
6: [9, 10]
6: [9, 10],
};

const characterExp = [
@@ -475,7 +475,7 @@ const weaponExp4Stars = [
ItemExperienceModel.forWeapons(87, 314250, 4979925),
ItemExperienceModel.forWeapons(88, 352700, 5294175),
ItemExperienceModel.forWeapons(89, 395775, 5646875),
ItemExperienceModel.forWeapons(90, -1, 6042650)
ItemExperienceModel.forWeapons(90, -1, 6042650),
];

const weaponExp3Stars = [
@@ -568,7 +568,7 @@ const weaponExp3Stars = [
ItemExperienceModel.forWeapons(87, 207400, 3286825),
ItemExperienceModel.forWeapons(88, 232775, 3494225),
ItemExperienceModel.forWeapons(89, 261200, 3727000),
ItemExperienceModel.forWeapons(90, -1, 3988200)
ItemExperienceModel.forWeapons(90, -1, 3988200),
];

const weaponExp2Stars = [
@@ -641,7 +641,7 @@ const weaponExp2Stars = [
ItemExperienceModel.forWeapons(67, 41750, 951200),
ItemExperienceModel.forWeapons(68, 42825, 992950),
ItemExperienceModel.forWeapons(69, 43900, 1035775),
ItemExperienceModel.forWeapons(70, -1, 1079675)
ItemExperienceModel.forWeapons(70, -1, 1079675),
];

const weaponExp1Star = [
@@ -714,7 +714,7 @@ const weaponExp1Star = [
ItemExperienceModel.forWeapons(67, 27825, 634225),
ItemExperienceModel.forWeapons(68, 28550, 662050),
ItemExperienceModel.forWeapons(69, 29275, 690600),
ItemExperienceModel.forWeapons(70, -1, 719875)
ItemExperienceModel.forWeapons(70, -1, 719875),
];

//Furnishing related
@@ -759,19 +759,14 @@ double getItemTotalExp(int currentLevel, int desiredLevel, int rarity, bool forC
switch (rarity) {
case 5:
items.addAll(weaponExp5Stars);
break;
case 4:
items.addAll(weaponExp4Stars);
break;
case 3:
items.addAll(weaponExp3Stars);
break;
case 2:
items.addAll(weaponExp2Stars);
break;
case 1:
items.addAll(weaponExp1Star);
break;
default:
throw Exception('The provided rarity = $rarity');
}
@@ -841,7 +836,7 @@ Duration getResinDuration(int currentResinValue) {
int getCurrentResin(int initialResinValue, DateTime completesAt) {
final now = DateTime.now();
final createdAt = completesAt.subtract(getResinDuration(initialResinValue));
final elapsedMinutes = (now.difference(createdAt).inMinutes).abs();
final elapsedMinutes = now.difference(createdAt).inMinutes.abs();
final currentResinValue = (elapsedMinutes / resinRefillsEach).floor() + initialResinValue;
return currentResinValue > maxResinValue ? maxResinValue : currentResinValue;
}
@@ -907,7 +902,7 @@ int getCurrentRealmCurrency(int initialRealmCurrency, int currentTrustRank, Real
final maxRealmCurrency = getRealmMaxCurrency(currentTrustRank);
final ratioPerHour = getRealmIncreaseRatio(currentRealmRank);
final createdAt = completesAt.subtract(getRealmCurrencyDuration(initialRealmCurrency, currentTrustRank, currentRealmRank));
final elapsedMinutes = (now.difference(createdAt).inMinutes).abs();
final elapsedMinutes = now.difference(createdAt).inMinutes.abs();
final currentRealmCurrency = (elapsedMinutes * ratioPerHour / 60).floor() + initialRealmCurrency;
return currentRealmCurrency > maxRealmCurrency ? maxRealmCurrency : currentRealmCurrency;
}
23 changes: 23 additions & 0 deletions lib/domain/assets.dart
Original file line number Diff line number Diff line change
@@ -5,6 +5,7 @@ class Assets {
static String elementsBasePath = 'assets/elements';
static String otherImgsBasePath = 'assets/others';
static String weaponTypesBasePath = 'assets/weapon_types';
static String weaponNormalSkillTypesPath = 'assets/weapon_normal_skill_types';

static String noImageAvailablePath = '$otherImgsBasePath/na$imageFileExtension';
static String paimonImagePath = '$otherImgsBasePath/paimon$imageFileExtension';
@@ -14,6 +15,11 @@ class Assets {
static String gachaIconPath = '$otherImgsBasePath/gacha$imageFileExtension';
static String starCrystalIconPath = '$otherImgsBasePath/mark_wind_crystal$imageFileExtension';
static String primogemIconPath = '$otherImgsBasePath/primogem$imageFileExtension';
static String wishBannerBackgroundImgPath = '$otherImgsBasePath/wish_banner_background$imageFileExtension';
static String wishBannerButtonBackgroundImgPath = '$otherImgsBasePath/wish_banner_button$imageFileExtension';
static String wishBannerStandardImgPath = '$otherImgsBasePath/wish_banner_standard$imageFileExtension';
static String wishBannerResultBackgroundImgPath = '$otherImgsBasePath/wish_banner_wish_result_background.webp';
static String wishBannerItemResultBackgroundImgPath = '$otherImgsBasePath/wish_banner_wish_result_item_background.webp';

static String _getElementPath(String name) => '$elementsBasePath/$name';

@@ -75,4 +81,21 @@ class Assets {
throw Exception('Invalid weapon type = $type');
}
}

static String getWeaponSkillAssetPath(WeaponType type) {
switch (type) {
case WeaponType.bow:
return '$weaponNormalSkillTypesPath/bow$imageFileExtension';
case WeaponType.catalyst:
return '$weaponNormalSkillTypesPath/catalyst$imageFileExtension';
case WeaponType.claymore:
return '$weaponNormalSkillTypesPath/claymore$imageFileExtension';
case WeaponType.polearm:
return '$weaponNormalSkillTypesPath/polearm$imageFileExtension';
case WeaponType.sword:
return '$weaponNormalSkillTypesPath/sword$imageFileExtension';
default:
throw Exception('Invalid weapon type = $type');
}
}
}
1 change: 1 addition & 0 deletions lib/domain/enums/app_backup_data_type.dart
Original file line number Diff line number Diff line change
@@ -6,4 +6,5 @@ enum AppBackupDataType {
customBuilds,
gameCodes,
notifications,
wishSimulator,
}
2 changes: 2 additions & 0 deletions lib/domain/enums/app_image_folder_type.dart
Original file line number Diff line number Diff line change
@@ -8,4 +8,6 @@ enum AppImageFolderType {
monsters,
skills,
weapons,
wishBannerHistory,
charactersIcon,
}
5 changes: 5 additions & 0 deletions lib/domain/enums/banner_item_type.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
enum BannerItemType {
character,
weapon,
standard,
}
2 changes: 2 additions & 0 deletions lib/domain/enums/enums.dart
Original file line number Diff line number Diff line change
@@ -15,6 +15,7 @@ export 'artifact_type.dart';
export 'ascension_material_summary_type.dart';
export 'banner_history_item_type.dart';
export 'banner_history_sort_type.dart';
export 'banner_item_type.dart';
export 'character_filter_type.dart';
export 'character_role_subtype.dart';
export 'character_role_type.dart';
@@ -40,3 +41,4 @@ export 'sort_direction_type.dart';
export 'stat_type.dart';
export 'weapon_filter_type.dart';
export 'weapon_type.dart';
export 'wish_banner_grouped_type.dart';
5 changes: 5 additions & 0 deletions lib/domain/enums/wish_banner_grouped_type.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
enum WishBannerGroupedType {
version,
character,
weapon,
}
4 changes: 4 additions & 0 deletions lib/domain/extensions/weapon_type_extensions.dart
Original file line number Diff line number Diff line change
@@ -5,4 +5,8 @@ extension WeaponTypeExtension on WeaponType {
String getWeaponAssetPath() {
return Assets.getWeaponTypePath(this);
}

String getWeaponNormalSkillAssetPath() {
return Assets.getWeaponSkillAssetPath(this);
}
}
1 change: 1 addition & 0 deletions lib/domain/models/backup/backup_model.dart
Original file line number Diff line number Diff line change
@@ -21,6 +21,7 @@ class BackupModel with _$BackupModel {
List<BackupTierListModel>? tierList,
List<BackupGameCodeModel>? gameCodes,
BackupNotificationsModel? notifications,
BackupWishSimulatorModel? wishSimulator,
}) = _BackupModel;

factory BackupModel.fromJson(Map<String, dynamic> json) => _$BackupModelFromJson(json);
40 changes: 40 additions & 0 deletions lib/domain/models/backup/backup_wish_simulator_model.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import 'package:freezed_annotation/freezed_annotation.dart';
import 'package:shiori/domain/enums/enums.dart';

part 'backup_wish_simulator_model.freezed.dart';
part 'backup_wish_simulator_model.g.dart';

@freezed
class BackupWishSimulatorModel with _$BackupWishSimulatorModel {
const factory BackupWishSimulatorModel({
required List<BackupWishSimulatorBannerPullHistory> pullHistory,
required List<BackupWishSimulatorBannerItemPullHistory> itemPullHistory,
}) = _BackupWishSimulatorModel;

factory BackupWishSimulatorModel.fromJson(Map<String, dynamic> json) => _$BackupWishSimulatorModelFromJson(json);
}

@freezed
class BackupWishSimulatorBannerPullHistory with _$BackupWishSimulatorBannerPullHistory {
const factory BackupWishSimulatorBannerPullHistory({
required BannerItemType type,
required Map<int, int> currentXStarCount,
required Map<int, bool> fiftyFiftyXStarGuaranteed,
}) = _BackupWishSimulatorBannerPullHistory;

factory BackupWishSimulatorBannerPullHistory.fromJson(Map<String, dynamic> json) =>
_$BackupWishSimulatorBannerPullHistoryFromJson(json);
}

@freezed
class BackupWishSimulatorBannerItemPullHistory with _$BackupWishSimulatorBannerItemPullHistory {
const factory BackupWishSimulatorBannerItemPullHistory({
required BannerItemType bannerType,
required String itemKey,
required ItemType itemType,
required DateTime pulledOn,
}) = _BackupWishSimulatorBannerItemPullHistory;

factory BackupWishSimulatorBannerItemPullHistory.fromJson(Map<String, dynamic> json) =>
_$BackupWishSimulatorBannerItemPullHistoryFromJson(json);
}
1 change: 1 addition & 0 deletions lib/domain/models/characters/character_card_model.dart
Original file line number Diff line number Diff line change
@@ -8,6 +8,7 @@ class CharacterCardModel with _$CharacterCardModel {
const factory CharacterCardModel({
required String key,
required String image,
required String iconImage,
required String name,
required int stars,
required WeaponType weaponType,
Original file line number Diff line number Diff line change
@@ -12,6 +12,7 @@ class BannerHistoryPeriodFileModel with _$BannerHistoryPeriodFileModel {
required DateTime until,
required double version,
required List<String> itemKeys,
required String imageFilename,
}) = _BannerHistoryPeriodFileModel;

factory BannerHistoryPeriodFileModel.fromJson(Map<String, dynamic> json) => _$BannerHistoryPeriodFileModelFromJson(json);
1 change: 1 addition & 0 deletions lib/domain/models/db/characters/character_file_model.dart
Original file line number Diff line number Diff line change
@@ -16,6 +16,7 @@ class CharacterFileModel with _$CharacterFileModel {
required String image,
required String fullImage,
String? secondFullImage,
required String iconImage,
required RegionType region,
required bool isFemale,
required bool isComingSoon,
2 changes: 2 additions & 0 deletions lib/domain/models/entities.dart
Original file line number Diff line number Diff line change
@@ -21,3 +21,5 @@ export 'entities/notifications/notification_realm_currency.dart';
export 'entities/notifications/notification_resin.dart';
export 'entities/notifications/notification_weekly_boss.dart';
export 'entities/tierlist/tierlist_item.dart';
export 'entities/wish_simulator/wish_simulator_banner_item_pull_history.dart';
export 'entities/wish_simulator/wish_simulator_banner_pull_history.dart';
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import 'package:hive/hive.dart';
import 'package:shiori/domain/enums/enums.dart';

part 'wish_simulator_banner_item_pull_history.g.dart';

@HiveType(typeId: 24)
class WishSimulatorBannerItemPullHistory extends HiveObject {
@HiveField(0)
final int bannerType;

@HiveField(1)
final int itemType;

@HiveField(2)
final String itemKey;

@HiveField(4)
DateTime pulledOnDate;

WishSimulatorBannerItemPullHistory(this.bannerType, this.itemType, this.itemKey, this.pulledOnDate);

WishSimulatorBannerItemPullHistory.newOne(BannerItemType bannerType, ItemType itemType, this.itemKey)
: bannerType = bannerType.index,
itemType = itemType.index,
pulledOnDate = DateTime.now().toUtc();

factory WishSimulatorBannerItemPullHistory.character(BannerItemType bannerType, String itemKey) =>
WishSimulatorBannerItemPullHistory.newOne(bannerType, ItemType.character, itemKey);

factory WishSimulatorBannerItemPullHistory.weapon(BannerItemType bannerType, String itemKey) =>
WishSimulatorBannerItemPullHistory.newOne(bannerType, ItemType.weapon, itemKey);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
import 'dart:math';

import 'package:darq/darq.dart';
import 'package:hive/hive.dart';
import 'package:shiori/domain/enums/enums.dart';

part 'wish_simulator_banner_pull_history.g.dart';

@HiveType(typeId: 23)
class WishSimulatorBannerPullHistory extends HiveObject {
@HiveField(0)
final int type;

@HiveField(1)
Map<int, int> currentXStarCount;

@HiveField(2)
Map<int, bool> fiftyFiftyXStarGuaranteed;

WishSimulatorBannerPullHistory(
this.type,
this.currentXStarCount,
this.fiftyFiftyXStarGuaranteed,
);

WishSimulatorBannerPullHistory.newOne(BannerItemType type, Map<int, int>? defaultXStarCount)
: type = type.index,
currentXStarCount = defaultXStarCount ?? {},
fiftyFiftyXStarGuaranteed = defaultXStarCount?.keys.toMap((rarity) => MapEntry(rarity, false), modifiable: true) ?? {};

void initXStarCountIfNeeded(int rarity) {
if (currentXStarCount.containsKey(rarity)) {
return;
}

currentXStarCount[rarity] = 0;
}

bool isItemGuaranteed(int rarity, int guaranteedAt) {
if (rarity <= 0) {
throw Exception('The provided rarity = $rarity is not valid');
}

if (guaranteedAt <= 0) {
throw Exception('The provided guaranteedAt = $guaranteedAt is not valid');
}

final int current = currentXStarCount[rarity] ?? 0;
return current + 1 >= guaranteedAt;
}

Future<void> pull(int rarity, bool? gotFeaturedItem) {
if (rarity <= 0) {
throw Exception('The provided rarity = $rarity is not valid');
}

_increaseCurrentXStarCount();

if (gotFeaturedItem != null) {
currentXStarCount[rarity] = 0;
//this means that we may have won the 50/50
fiftyFiftyXStarGuaranteed[rarity] = !gotFeaturedItem;
}

return save();
}

bool shouldWinFiftyFifty(int rarity) {
if (fiftyFiftyXStarGuaranteed[rarity] == true) {
return true;
}

return Random().nextBool();
}

void _increaseCurrentXStarCount() {
for (final key in currentXStarCount.keys) {
final int currentCount = currentXStarCount[key] ?? 0;
currentXStarCount[key] = currentCount + 1;
}
}
}
10 changes: 10 additions & 0 deletions lib/domain/models/items/item_common.dart
Original file line number Diff line number Diff line change
@@ -42,3 +42,13 @@ class ItemCommonWithName with _$ItemCommonWithName {
@Implements<ItemCommonBase>()
const factory ItemCommonWithName(String key, String image, String name) = _ItemCommonWithName;
}

@freezed
class ItemCommonWithNameOnly with _$ItemCommonWithNameOnly {
const factory ItemCommonWithNameOnly(String key, String name) = _ItemCommonWithNameOnly;
}

@freezed
class ItemCommonWithNameAndRarity with _$ItemCommonWithNameAndRarity {
const factory ItemCommonWithNameAndRarity(String key, String name, int rarity) = _ItemCommonWithNameAndRarity;
}
5 changes: 5 additions & 0 deletions lib/domain/models/models.dart
Original file line number Diff line number Diff line change
@@ -7,6 +7,7 @@ export 'backup/backup_inventory_model.dart';
export 'backup/backup_model.dart';
export 'backup/backup_notifications_model.dart';
export 'backup/backup_tierlist_model.dart';
export 'backup/backup_wish_simulator_model.dart';
export 'backup/create_backup_result_model.dart';
export 'banner_history/banner_history_item_model.dart';
export 'banner_history/banner_history_period_model.dart';
@@ -86,3 +87,7 @@ export 'tierlist/tierlist_row_model.dart';
export 'weapons/weapon_ascension_model.dart';
export 'weapons/weapon_card_model.dart';
export 'weapons/weapon_file_refinement_model.dart';
export 'wish_banner_history/wish_banner_history_grouped_period_model.dart';
export 'wish_simulator/wish_simulator_banner_item_model.dart';
export 'wish_simulator/wish_simulator_banner_item_pull_history_model.dart';
export 'wish_simulator/wish_simulator_banner_item_result_model.dart';
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import 'package:freezed_annotation/freezed_annotation.dart';
import 'package:shiori/domain/models/models.dart';

part 'wish_banner_history_grouped_period_model.freezed.dart';

@freezed
class WishBannerHistoryGroupedPeriodModel with _$WishBannerHistoryGroupedPeriodModel {
const factory WishBannerHistoryGroupedPeriodModel({
required String groupingKey,
required String groupingTitle,
required List<WishBannerHistoryPartItemModel> parts,
}) = _WishBannerHistoryGroupedPeriodModel;
}

@freezed
class WishBannerHistoryPartItemModel with _$WishBannerHistoryPartItemModel {
const factory WishBannerHistoryPartItemModel({
required List<ItemCommonWithNameAndRarity> featuredCharacters,
required List<ItemCommonWithNameAndRarity> featuredWeapons,
required List<String> bannerImages,
required DateTime from,
required DateTime until,
required double version,
}) = _WishBannerHistoryPartItemModel;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
import 'package:freezed_annotation/freezed_annotation.dart';
import 'package:shiori/domain/enums/enums.dart';
import 'package:shiori/domain/wish_banner_constants.dart';

part 'wish_simulator_banner_item_model.freezed.dart';

@freezed
class WishSimulatorBannerItemsPerPeriodModel with _$WishSimulatorBannerItemsPerPeriodModel {
const factory WishSimulatorBannerItemsPerPeriodModel({
required double version,
required DateTime from,
required DateTime until,
required List<WishSimulatorBannerItemModel> banners,
}) = _WishSimulatorBannerItemsPerPeriodModel;
}

@freezed
class WishSimulatorBannerItemModel with _$WishSimulatorBannerItemModel {
List<String> get featuredImages {
switch (type) {
case BannerItemType.character:
case BannerItemType.weapon:
return featuredItems.where((el) => el.rarity == WishBannerConstants.maxObtainableRarity).map((e) => e.iconImage).toList();
case BannerItemType.standard:
return [characters.firstWhere((el) => el.key == WishBannerConstants.commonFiveStarCharacterKeys.first).iconImage];
}
}

const factory WishSimulatorBannerItemModel({
required BannerItemType type,
required String image,
required List<WishSimulatorBannerFeaturedItemModel> featuredItems,
@Default(<WishSimulatorBannerCharacterModel>[]) List<WishSimulatorBannerCharacterModel> characters,
@Default(<WishSimulatorBannerWeaponModel>[]) List<WishSimulatorBannerWeaponModel> weapons,
}) = _WishSimulatorBannerItemModel;

const WishSimulatorBannerItemModel._();
}

@freezed
class WishSimulatorBannerFeaturedItemModel with _$WishSimulatorBannerFeaturedItemModel {
const factory WishSimulatorBannerFeaturedItemModel({
required String key,
required String iconImage,
required int rarity,
required ItemType type,
}) = _WishSimulatorBannerFeaturedItemModel;
}

@freezed
class WishSimulatorBannerCharacterModel with _$WishSimulatorBannerCharacterModel {
const factory WishSimulatorBannerCharacterModel({
required String key,
required int rarity,
required String iconImage,
required String image,
required ElementType elementType,
}) = _WisBSimulatorBannerCharacterModel;
}

@freezed
class WishSimulatorBannerWeaponModel with _$WishSimulatorBannerWeaponModel {
const factory WishSimulatorBannerWeaponModel({
required String key,
required int rarity,
required String iconImage,
required String image,
required WeaponType weaponType,
}) = _WishSimulatorBannerWeaponModel;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import 'package:freezed_annotation/freezed_annotation.dart';
import 'package:shiori/domain/enums/enums.dart';

part 'wish_simulator_banner_item_pull_history_model.freezed.dart';

@freezed
class WishSimulatorBannerItemPullHistoryModel with _$WishSimulatorBannerItemPullHistoryModel {
const factory WishSimulatorBannerItemPullHistoryModel({
required String key,
required String name,
required int rarity,
required ItemType type,
required String pulledOn,
}) = _WishSimulatorBannerItemPullHistoryModel;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import 'package:freezed_annotation/freezed_annotation.dart';
import 'package:shiori/domain/enums/enums.dart';

part 'wish_simulator_banner_item_result_model.freezed.dart';

@freezed
class WishSimulatorBannerItemResultModel with _$WishSimulatorBannerItemResultModel {
const factory WishSimulatorBannerItemResultModel.character({
required String key,
required String image,
required int rarity,
required ElementType elementType,
}) = _WishSimulatorBannerCharacterResultModel;

const factory WishSimulatorBannerItemResultModel.weapon({
required String key,
required String image,
required int rarity,
required WeaponType weaponType,
}) = _WishSimulatorBannerWeaponResultModel;
}
3 changes: 1 addition & 2 deletions lib/domain/services/calculator_service.dart
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import 'package:shiori/domain/models/models.dart';
import 'package:tuple/tuple.dart';

abstract class CalculatorService {
List<AscensionMaterialsSummary> generateSummary(List<ItemAscensionMaterialModel> current);
@@ -50,7 +49,7 @@ abstract class CalculatorService {
///
/// Returns a tuple with 4 boolean items, the first two represent the [currentLevel] (decrement and increment respectively)
/// and the last two represent the [desiredLevel] (increment and decrement respectively)
Tuple4<bool, bool, bool, bool> isSkillEnabled(
(bool, bool, bool, bool) isSkillEnabled(
int currentLevel,
int desiredLevel,
int currentAscensionLevel,
3 changes: 3 additions & 0 deletions lib/domain/services/data_service.dart
Original file line number Diff line number Diff line change
@@ -7,6 +7,7 @@ import 'package:shiori/domain/services/persistence/game_codes_data_service.dart'
import 'package:shiori/domain/services/persistence/inventory_data_service.dart';
import 'package:shiori/domain/services/persistence/notifications_data_service.dart';
import 'package:shiori/domain/services/persistence/tier_list_data_service.dart';
import 'package:shiori/domain/services/persistence/wish_simulator_data_service.dart';

abstract class DataService {
CalculatorDataService get calculator;
@@ -21,6 +22,8 @@ abstract class DataService {

TierListDataService get tierList;

WishSimulatorDataService get wishSimulator;

Future<void> init({String dir = 'shiori_data'});

@visibleForTesting
6 changes: 6 additions & 0 deletions lib/domain/services/file/banner_history_file_service.dart
Original file line number Diff line number Diff line change
@@ -14,4 +14,10 @@ abstract class BannerHistoryFileService extends BaseFileService {
List<ChartElementItemModel> getElementsForCharts(double fromVersion, double untilVersion);

List<ChartTopItemModel> getTopCharts(bool mostReruns, ChartType type, BannerHistoryItemType bannerType, List<ItemCommonWithName> items);

WishSimulatorBannerItemsPerPeriodModel getWishSimulatorBannerPerPeriod(double version, DateTime from, DateTime until);

List<WishBannerHistoryGroupedPeriodModel> getWishBannersHistoryGroupedByVersion();

WishSimulatorBannerItemModel getWishSimulatorStandardBanner();
}
2 changes: 2 additions & 0 deletions lib/domain/services/file/character_file_service.dart
Original file line number Diff line number Diff line change
@@ -49,4 +49,6 @@ abstract class CharacterFileService extends BaseFileService {
List<ItemCommonWithName> getItemCommonWithNameByRarity(int rarity);

List<ItemCommonWithName> getItemCommonWithNameByStatType(StatType statType);

List<ItemCommonWithName> getItemCommonWithName();
}
4 changes: 4 additions & 0 deletions lib/domain/services/file/material_file_service.dart
Original file line number Diff line number Diff line change
@@ -35,4 +35,8 @@ abstract class MaterialFileService extends BaseFileService {
MaterialFileModel getPrimogemMaterial();

MaterialFileModel getFragileResinMaterial();

MaterialFileModel getIntertwinedFate();

MaterialFileModel getAcquaintFate();
}
2 changes: 2 additions & 0 deletions lib/domain/services/file/weapon_file_service.dart
Original file line number Diff line number Diff line change
@@ -23,4 +23,6 @@ abstract class WeaponFileService extends BaseFileService {
List<ItemCommonWithName> getItemCommonWithNameByRarity(int rarity);

List<ItemCommonWithName> getItemCommonWithNameByStatType(StatType statType);

List<ItemCommonWithName> getItemCommonWithName();
}
7 changes: 1 addition & 6 deletions lib/domain/services/notification_service.dart
Original file line number Diff line number Diff line change
@@ -1,14 +1,9 @@
import 'package:flutter_local_notifications/flutter_local_notifications.dart';
import 'package:shiori/domain/enums/enums.dart';

abstract class NotificationService {
void init();

//TODO: REMOVE THE PLUGIN DEPENDENCY FROM THIS LAYER
Future<void> registerCallBacks({
DidReceiveLocalNotificationCallback? onIosReceiveLocalNotification,
SelectNotificationCallback? onSelectNotification,
});
Future<void> registerCallBacks();

Future<bool> requestIOSPermissions();

20 changes: 20 additions & 0 deletions lib/domain/services/persistence/wish_simulator_data_service.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import 'package:shiori/domain/enums/enums.dart';
import 'package:shiori/domain/models/entities.dart';
import 'package:shiori/domain/models/models.dart';
import 'package:shiori/domain/services/persistence/base_data_service.dart';

abstract class WishSimulatorDataService implements BaseDataService {
Future<WishSimulatorBannerPullHistory> getBannerPullHistory(BannerItemType type, {Map<int, int>? defaultXStarCount});

Future<void> saveBannerItemPullHistory(BannerItemType bannerType, String itemKey, ItemType itemType);

Future<void> clearBannerItemPullHistory(BannerItemType bannerType);

Future<void> clearAllBannerItemPullHistory();

List<WishSimulatorBannerItemPullHistory> getBannerItemsPullHistoryPerType(BannerItemType bannerType);

Future<BackupWishSimulatorModel> getDataForBackup();

Future<void> restoreFromBackup(BackupWishSimulatorModel data);
}
4 changes: 4 additions & 0 deletions lib/domain/services/resources_service.dart
Original file line number Diff line number Diff line change
@@ -23,6 +23,10 @@ abstract class ResourceService {

String getMaterialImagePath(String filename, MaterialType type);

String getWishBannerHistoryImagePath(String filename);

String getCharacterIconImagePath(String filename);

Future<CheckForUpdatesResult> checkForUpdates(
String currentAppVersion,
int currentResourcesVersion, {
10 changes: 7 additions & 3 deletions lib/domain/services/telemetry_service.dart
Original file line number Diff line number Diff line change
@@ -14,7 +14,7 @@ abstract class TelemetryService {

Future<void> trackAscensionMaterialsOpened();

Future<void> trackUrlOpened(bool loadMap, bool loadWishSimulator, bool loadDailyCheckIn, bool networkAvailable);
Future<void> trackUrlOpened(bool loadMap, bool loadDailyCheckIn, bool networkAvailable);

Future<void> trackCalculatorItemAscMaterialLoaded(String item);

@@ -78,7 +78,11 @@ abstract class TelemetryService {

Future<void> trackResourceUpdateCompleted(bool applied, int targetResourceVersion);

Future<void> backupCreated(bool succeed);
Future<void> trackBackupCreated(bool succeed);

Future<void> backupRestored(bool succeed);
Future<void> trackBackupRestored(bool succeed);

Future<void> trackWishSimulatorOpened(double version);

Future<void> trackWishSimulatorResult(int bannerIndex, double version, BannerItemType type, String range);
}
14 changes: 14 additions & 0 deletions lib/domain/utils/filter_utils.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
class FilterUtils {
static List<T> handleTypeSelected<T extends Enum>(List<T> allValues, List<T> tempValues, T selectedValue) {
if (tempValues.length == allValues.length) {
return [selectedValue];
}
if (tempValues.length == 1 && tempValues.first == selectedValue) {
return allValues.toList();
}
if (tempValues.contains(selectedValue)) {
return tempValues.where((t) => t != selectedValue).toList();
}
return tempValues + [selectedValue];
}
}
73 changes: 73 additions & 0 deletions lib/domain/wish_banner_constants.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
import 'package:intl/intl.dart';

class WishBannerConstants {
static const int maxObtainableRarity = 5;
static const int minObtainableRarity = 3;

static final dateFormat = DateFormat('yyyy-MM-dd');

static const commonFiveStarCharacterKeys = [
'qiqi',
'jean',
'tighnari',
'keqing',
'mona',
'dehya',
'diluc',
];

static const fourStarStandardBannerCharacterExclusiveKeys = ['lisa', 'amber', 'kaeya'];

static const commonFiveStarWeaponKeys = [
'amos-bow',
'skyward-harp',
'lost-prayer-to-the-sacred-winds',
'skyward-atlas',
'skyward-pride',
'wolfs-gravestone',
'primordial-jade-winged-spear',
'skyward-spine',
'aquila-favonia',
'skyward-blade',
];

static const commonFourStarWeaponKeys = [
'favonius-warbow',
'rust',
'sacrificial-bow',
'the-stringless',
'eye-of-perception',
'favonius-codex',
'sacrificial-fragments',
'the-widsith',
'favonius-greatsword',
'rainslasher',
'sacrificial-greatsword',
'the-bell',
'dragons-bane',
'favonius-lance',
'favonius-sword',
'lions-roar',
'sacrificial-sword',
'the-flute',
];

static const commonThreeStarWeaponKeys = [
'raven-bow',
'sharpshooters-oath',
'slingshot',
'emerald-orb',
'magic-guide',
'thrilling-tales-of-dragon-slayers',
'bloodtainted-greatsword',
'debate-club',
'ferrous-shadow',
'black-tassel',
'cool-steel',
'harbinger-of-dawn',
'skyrider-sword',
];

static List<String> commonWeaponKeys =
commonFiveStarWeaponKeys + commonFourStarWeaponKeys + commonThreeStarWeaponKeys + commonFiveStarCharacterKeys;
}
83 changes: 53 additions & 30 deletions lib/env.dart
Original file line number Diff line number Diff line change
@@ -1,22 +1,25 @@
import 'package:envied/envied.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter_envify/flutter_envify.dart';

part 'env.g.dart';

class Env {
static const androidAppCenterKey = CommonEnv.androidAppCenterKey;
static const androidPurchasesKey = CommonEnv.androidPurchasesKey;
static const macosAppCenterKey = CommonEnv.macosAppCenterKey;
static const int minResourceVersion = 44;

static const iosPurchasesKey = CommonEnv.iosPurchasesKey;
static const iosAppCenterKey = CommonEnv.iosAppCenterKey;
static const String androidAppCenterKey = CommonEnv.androidAppCenterKey;
static const String androidPurchasesKey = CommonEnv.androidPurchasesKey;

static const commonHeaderName = CommonEnv.commonHeaderName;
static const apiHeaderName = CommonEnv.apiHeaderName;
static const String iosPurchasesKey = CommonEnv.iosPurchasesKey;
static const String iosAppCenterKey = CommonEnv.iosAppCenterKey;

static const publicKey = CommonEnv.publicKey;
static const privateKey = CommonEnv.privateKey;
static const letsEncryptKey = CommonEnv.letsEncryptKey;
static const String macosAppCenterKey = CommonEnv.macosAppCenterKey;

static const String commonHeaderName = CommonEnv.commonHeaderName;
static const String apiHeaderName = CommonEnv.apiHeaderName;

static const String publicKey = CommonEnv.publicKey;
static const String privateKey = CommonEnv.privateKey;
static const String letsEncryptKey = CommonEnv.letsEncryptKey;

static const bool isReleaseMode = kReleaseMode;

@@ -27,33 +30,53 @@ class Env {
static const String apiHeaderValue = isReleaseMode ? ProdEnv.apiHeaderValue : DevEnv.apiHeaderValue;
}

@Envify(path: '.env.dev')
@Envied(path: '.env.dev', name: 'DevEnv')
abstract class DevEnv {
static const apiBaseUrl = _DevEnv.apiBaseUrl;
static const assetsBaseUrl = _DevEnv.assetsBaseUrl;
static const apiHeaderValue = _DevEnv.apiHeaderValue;
@EnviedField(varName: 'API_BASE_URL')
static const String apiBaseUrl = _DevEnv.apiBaseUrl;

@EnviedField(varName: 'ASSETS_BASE_URL')
static const String assetsBaseUrl = _DevEnv.assetsBaseUrl;

@EnviedField(varName: 'API_HEADER_VALUE')
static const String apiHeaderValue = _DevEnv.apiHeaderValue;
}

@Envify(path: '.env.prod')
@Envied(path: '.env.prod', name: 'ProdEnv')
abstract class ProdEnv {
static const apiBaseUrl = _ProdEnv.apiBaseUrl;
static const assetsBaseUrl = _ProdEnv.assetsBaseUrl;
static const apiHeaderValue = _ProdEnv.apiHeaderValue;
@EnviedField(varName: 'API_BASE_URL')
static const String apiBaseUrl = _ProdEnv.apiBaseUrl;

@EnviedField(varName: 'ASSETS_BASE_URL')
static const String assetsBaseUrl = _ProdEnv.assetsBaseUrl;

@EnviedField(varName: 'API_HEADER_VALUE')
static const String apiHeaderValue = _ProdEnv.apiHeaderValue;
}

@Envify(path: '.env.common')
@Envied(path: '.env.common', name: 'CommonEnv')
abstract class CommonEnv {
static const androidAppCenterKey = _CommonEnv.androidAppCenterKey;
static const iosAppCenterKey = _CommonEnv.iosAppCenterKey;
static const macosAppCenterKey = _CommonEnv.macosAppCenterKey;
@EnviedField(varName: 'ANDROID_APP_CENTER_KEY')
static const String androidAppCenterKey = _CommonEnv.androidAppCenterKey;
@EnviedField(varName: 'IOS_APP_CENTER_KEY')
static const String iosAppCenterKey = _CommonEnv.iosAppCenterKey;
@EnviedField(varName: 'MACOS_APP_CENTER_KEY')
static const String macosAppCenterKey = _CommonEnv.macosAppCenterKey;

static const androidPurchasesKey = _CommonEnv.androidPurchasesKey;
static const iosPurchasesKey = _CommonEnv.iosPurchasesKey;
@EnviedField(varName: 'ANDROID_PURCHASES_KEY')
static const String androidPurchasesKey = _CommonEnv.androidPurchasesKey;
@EnviedField(varName: 'IOS_PURCHASES_KEY')
static const String iosPurchasesKey = _CommonEnv.iosPurchasesKey;

static const commonHeaderName = _CommonEnv.commonHeaderName;
static const apiHeaderName = _CommonEnv.apiHeaderName;
@EnviedField(varName: 'COMMON_HEADER_NAME')
static const String commonHeaderName = _CommonEnv.commonHeaderName;
@EnviedField(varName: 'API_HEADER_NAME')
static const String apiHeaderName = _CommonEnv.apiHeaderName;

static const publicKey = _CommonEnv.publicKey;
static const privateKey = _CommonEnv.privateKey;
static const letsEncryptKey = _CommonEnv.letsEncryptKey;
@EnviedField(varName: 'PUBLIC_KEY')
static const String publicKey = _CommonEnv.publicKey;
@EnviedField(varName: 'PRIVATE_KEY')
static const String privateKey = _CommonEnv.privateKey;
@EnviedField(varName: 'LETS_ENCRYPT_KEY')
static const String letsEncryptKey = _CommonEnv.letsEncryptKey;
}
19 changes: 5 additions & 14 deletions lib/infrastructure/backup_restore_service.dart
Original file line number Diff line number Diff line change
@@ -71,31 +71,27 @@ class BackupRestoreServiceImpl implements BackupRestoreService {
case AppBackupDataType.settings:
final settings = _settingsService.getDataForBackup();
bk = bk.copyWith(settings: settings);
break;
case AppBackupDataType.inventory:
final inventory = _dataService.inventory.getDataForBackup();
bk = bk.copyWith(inventory: inventory);
break;
case AppBackupDataType.calculatorAscMaterials:
final calcAscMat = _dataService.calculator.getDataForBackup();
bk = bk.copyWith(calculatorAscMaterials: calcAscMat);
break;
case AppBackupDataType.tierList:
final tierList = _dataService.tierList.getDataForBackup();
bk = bk.copyWith(tierList: tierList);
break;
case AppBackupDataType.customBuilds:
final customBuilds = _dataService.customBuilds.getDataForBackup();
bk = bk.copyWith(customBuilds: customBuilds);
break;
case AppBackupDataType.gameCodes:
final gameCodes = _dataService.gameCodes.getDataForBackup();
bk = bk.copyWith(gameCodes: gameCodes);
break;
case AppBackupDataType.notifications:
final notifications = _dataService.notifications.getDataForBackup();
bk = bk.copyWith(notifications: notifications);
break;
case AppBackupDataType.wishSimulator:
final wishSimulator = await _dataService.wishSimulator.getDataForBackup();
bk = bk.copyWith(wishSimulator: wishSimulator);
}
}

@@ -186,28 +182,23 @@ class BackupRestoreServiceImpl implements BackupRestoreService {
switch (type) {
case AppBackupDataType.settings:
_settingsService.restoreFromBackup(bk.settings!);
break;
case AppBackupDataType.inventory:
await _dataService.inventory.restoreFromBackup(bk.inventory!);
break;
case AppBackupDataType.calculatorAscMaterials:
await _dataService.calculator.restoreFromBackup(bk.calculatorAscMaterials!);
break;
case AppBackupDataType.tierList:
await _dataService.tierList.restoreFromBackup(bk.tierList!);
break;
case AppBackupDataType.customBuilds:
await _dataService.customBuilds.restoreFromBackup(bk.customBuilds!);
break;
case AppBackupDataType.gameCodes:
await _dataService.gameCodes.restoreFromBackup(bk.gameCodes!);
break;
case AppBackupDataType.notifications:
_loggingService.info(runtimeType, 'restoreBackup: Cancelling all notifications...');
await _notificationService.cancelAllNotifications();
final serverResetTime = bk.settings?.serverResetTime ?? _settingsService.serverResetTime;
await _dataService.notifications.restoreFromBackup(bk.notifications!, serverResetTime);
break;
case AppBackupDataType.wishSimulator:
await _dataService.wishSimulator.restoreFromBackup(bk.wishSimulator!);
}
}
_loggingService.info(runtimeType, 'restoreBackup: Process completed');
11 changes: 2 additions & 9 deletions lib/infrastructure/calculator_service.dart
Original file line number Diff line number Diff line change
@@ -4,7 +4,6 @@ import 'package:shiori/domain/models/models.dart';
import 'package:shiori/domain/services/calculator_service.dart';
import 'package:shiori/domain/services/genshin_service.dart';
import 'package:shiori/domain/services/resources_service.dart';
import 'package:tuple/tuple.dart';

class CalculatorServiceImpl implements CalculatorService {
final GenshinService _genshinService;
@@ -54,31 +53,25 @@ class CalculatorServiceImpl implements CalculatorService {
switch (material.type) {
case MaterialType.common:
key = AscensionMaterialSummaryType.common;
break;
//some characters use ingredient / local specialities, so we label them all as local
case MaterialType.local:
case MaterialType.ingredient:
key = AscensionMaterialSummaryType.local;
break;
case MaterialType.currency:
key = AscensionMaterialSummaryType.currency;
break;
//there are some weapon secondary materials used by some characters, so I pretty much group them as common
case MaterialType.weapon:
case MaterialType.weaponPrimary:
key = AscensionMaterialSummaryType.common;
break;
//this case shouldn't be common except for the traveler, since the elementalStone they use is no dropped from boss
case MaterialType.elementalStone:
case MaterialType.jewels:
case MaterialType.talents:
case MaterialType.others:
key = AscensionMaterialSummaryType.others;
break;
case MaterialType.expWeapon:
case MaterialType.expCharacter:
key = AscensionMaterialSummaryType.exp;
break;
}
newValue = MaterialSummary(
key: material.key,
@@ -304,7 +297,7 @@ class CalculatorServiceImpl implements CalculatorService {
}

@override
Tuple4<bool, bool, bool, bool> isSkillEnabled(
(bool, bool, bool, bool) isSkillEnabled(
int currentLevel,
int desiredLevel,
int currentAscensionLevel,
@@ -330,7 +323,7 @@ class CalculatorServiceImpl implements CalculatorService {
maxSkillLevel,
);

return Tuple4<bool, bool, bool, bool>(currentDecEnabled, currentIncEnabled, desiredDecEnabled, desiredIncEnabled);
return (currentDecEnabled, currentIncEnabled, desiredDecEnabled, desiredIncEnabled);
}

List<ItemAscensionMaterialModel> _flatMaterialsList(List<ItemAscensionMaterialModel> current) {
4 changes: 2 additions & 2 deletions lib/infrastructure/device_info_service.dart
Original file line number Diff line number Diff line change
@@ -90,8 +90,8 @@ class DeviceInfoServiceImpl implements DeviceInfoService {
Future<void> _initForIOs() async {
final deviceInfo = DeviceInfoPlugin();
final info = await deviceInfo.iosInfo;
final model = 'Model: ${info.model ?? na} --- Name: ${info.name ?? na}';
final osVersion = '${info.systemName ?? na} : ${info.systemVersion ?? na}';
final model = 'Model: ${info.model} --- Name: ${info.name}';
final osVersion = '${info.systemName} : ${info.systemVersion}';
_setDefaultDeviceInfoProps(model, osVersion);
_setOtherDeviceInfoProps(info.isPhysicalDevice);
}
Loading

0 comments on commit 0af1200

Please sign in to comment.