init
This commit is contained in:
@@ -0,0 +1,76 @@
|
||||
import 'package:isar/isar.dart';
|
||||
|
||||
import '../../../core/constants/app_defaults.dart';
|
||||
|
||||
part 'app_config.g.dart';
|
||||
|
||||
enum EntitlementSource {
|
||||
none,
|
||||
purchase,
|
||||
restore,
|
||||
}
|
||||
|
||||
@collection
|
||||
class AppConfig {
|
||||
AppConfig();
|
||||
|
||||
Id id = AppDefaults.singletonId;
|
||||
|
||||
@Index(unique: true, replace: true)
|
||||
late String configKey;
|
||||
|
||||
@Index(type: IndexType.value)
|
||||
late DateTime updatedAt;
|
||||
|
||||
late DateTime createdAt;
|
||||
int freeTrialLimit = AppDefaults.defaultFreeTrialLimit;
|
||||
int usedTrialCount = 0;
|
||||
bool isVipUnlocked = false;
|
||||
DateTime? vipUnlockedAt;
|
||||
String? lastIapErrorMessage;
|
||||
|
||||
@enumerated
|
||||
EntitlementSource entitlementSource = EntitlementSource.none;
|
||||
|
||||
factory AppConfig.createDefault({DateTime? now}) {
|
||||
final current = now ?? DateTime.now();
|
||||
|
||||
return AppConfig()
|
||||
..configKey = 'local_app_config'
|
||||
..createdAt = current
|
||||
..updatedAt = current
|
||||
..freeTrialLimit = AppDefaults.defaultFreeTrialLimit
|
||||
..usedTrialCount = 0
|
||||
..isVipUnlocked = false
|
||||
..entitlementSource = EntitlementSource.none;
|
||||
}
|
||||
|
||||
int get remainingTrialCount {
|
||||
final remaining = freeTrialLimit - usedTrialCount;
|
||||
return remaining > 0 ? remaining : 0;
|
||||
}
|
||||
|
||||
bool get canStartScoring => isVipUnlocked || usedTrialCount < freeTrialLimit;
|
||||
|
||||
AppConfig copyWith({
|
||||
int? freeTrialLimit,
|
||||
int? usedTrialCount,
|
||||
bool? isVipUnlocked,
|
||||
DateTime? vipUnlockedAt,
|
||||
String? lastIapErrorMessage,
|
||||
EntitlementSource? entitlementSource,
|
||||
DateTime? updatedAt,
|
||||
}) {
|
||||
return AppConfig()
|
||||
..id = id
|
||||
..configKey = configKey
|
||||
..createdAt = createdAt
|
||||
..updatedAt = updatedAt ?? DateTime.now()
|
||||
..freeTrialLimit = freeTrialLimit ?? this.freeTrialLimit
|
||||
..usedTrialCount = usedTrialCount ?? this.usedTrialCount
|
||||
..isVipUnlocked = isVipUnlocked ?? this.isVipUnlocked
|
||||
..vipUnlockedAt = vipUnlockedAt ?? this.vipUnlockedAt
|
||||
..lastIapErrorMessage = lastIapErrorMessage ?? this.lastIapErrorMessage
|
||||
..entitlementSource = entitlementSource ?? this.entitlementSource;
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,3 @@
|
||||
export 'app_config.dart';
|
||||
export 'point_record.dart';
|
||||
export 'user_profile.dart';
|
||||
@@ -0,0 +1,151 @@
|
||||
import 'package:isar/isar.dart';
|
||||
|
||||
part 'point_record.g.dart';
|
||||
|
||||
enum PointRecordStatus {
|
||||
draft,
|
||||
completed,
|
||||
}
|
||||
|
||||
enum BowType {
|
||||
recurve,
|
||||
compound,
|
||||
barebow,
|
||||
traditional,
|
||||
longbow,
|
||||
other,
|
||||
}
|
||||
|
||||
enum TargetFaceType {
|
||||
cm40,
|
||||
cm60,
|
||||
cm80,
|
||||
cm122,
|
||||
vegas3Spot,
|
||||
vertical3Spot,
|
||||
custom,
|
||||
}
|
||||
|
||||
@embedded
|
||||
class HitPoint {
|
||||
HitPoint({
|
||||
this.arrowIndex = 0,
|
||||
this.endIndex = 0,
|
||||
this.normalizedX = 0,
|
||||
this.normalizedY = 0,
|
||||
this.score,
|
||||
this.isMiss = false,
|
||||
});
|
||||
|
||||
int arrowIndex;
|
||||
int endIndex;
|
||||
|
||||
/// Range: -1.0 ~ 1.0. The painter can map the normalized coordinate
|
||||
/// directly onto different target canvases.
|
||||
double normalizedX;
|
||||
double normalizedY;
|
||||
|
||||
int? score;
|
||||
bool isMiss;
|
||||
}
|
||||
|
||||
@collection
|
||||
class PointRecord {
|
||||
PointRecord();
|
||||
|
||||
Id id = Isar.autoIncrement;
|
||||
|
||||
@Index(type: IndexType.value)
|
||||
late DateTime updatedAt;
|
||||
|
||||
@Index(type: IndexType.value)
|
||||
late DateTime createdAt;
|
||||
|
||||
@Index(type: IndexType.value)
|
||||
DateTime? completedAt;
|
||||
|
||||
@enumerated
|
||||
PointRecordStatus status = PointRecordStatus.draft;
|
||||
|
||||
@enumerated
|
||||
BowType bowType = BowType.recurve;
|
||||
|
||||
@enumerated
|
||||
TargetFaceType targetFaceType = TargetFaceType.cm40;
|
||||
|
||||
late String title;
|
||||
int distanceMeters = 18;
|
||||
int endCount = 6;
|
||||
int arrowsPerEnd = 6;
|
||||
String? note;
|
||||
List<int> scores = <int>[];
|
||||
List<HitPoint> hitPoints = <HitPoint>[];
|
||||
|
||||
bool get isDraft => status == PointRecordStatus.draft;
|
||||
int get totalArrows => scores.length;
|
||||
int get totalScore => scores.fold<int>(0, (sum, item) => sum + item);
|
||||
double? get averageScore =>
|
||||
scores.isEmpty ? null : totalScore / scores.length;
|
||||
|
||||
factory PointRecord.createDraft({
|
||||
required BowType bowType,
|
||||
required TargetFaceType targetFaceType,
|
||||
required int distanceMeters,
|
||||
required int endCount,
|
||||
required int arrowsPerEnd,
|
||||
String? title,
|
||||
DateTime? now,
|
||||
}) {
|
||||
final current = now ?? DateTime.now();
|
||||
|
||||
return PointRecord()
|
||||
..title = title ?? '未完成记分'
|
||||
..status = PointRecordStatus.draft
|
||||
..bowType = bowType
|
||||
..targetFaceType = targetFaceType
|
||||
..distanceMeters = distanceMeters
|
||||
..endCount = endCount
|
||||
..arrowsPerEnd = arrowsPerEnd
|
||||
..createdAt = current
|
||||
..updatedAt = current;
|
||||
}
|
||||
|
||||
PointRecord markCompleted({DateTime? completedAt}) {
|
||||
final current = completedAt ?? DateTime.now();
|
||||
|
||||
return PointRecord()
|
||||
..id = id
|
||||
..title = title
|
||||
..status = PointRecordStatus.completed
|
||||
..bowType = bowType
|
||||
..targetFaceType = targetFaceType
|
||||
..distanceMeters = distanceMeters
|
||||
..endCount = endCount
|
||||
..arrowsPerEnd = arrowsPerEnd
|
||||
..note = note
|
||||
..scores = List<int>.from(scores)
|
||||
..hitPoints = List<HitPoint>.from(hitPoints)
|
||||
..createdAt = createdAt
|
||||
..updatedAt = current
|
||||
..completedAt = current;
|
||||
}
|
||||
|
||||
/// Returns a draft copy with updated scores/hit points (used while editing).
|
||||
PointRecord copyWithScores(List<int> scores, List<HitPoint> hitPoints) {
|
||||
return PointRecord()
|
||||
..id = id
|
||||
..title = title
|
||||
..status = status
|
||||
..bowType = bowType
|
||||
..targetFaceType = targetFaceType
|
||||
..distanceMeters = distanceMeters
|
||||
..endCount = endCount
|
||||
..arrowsPerEnd = arrowsPerEnd
|
||||
..note = note
|
||||
..scores = List<int>.from(scores)
|
||||
..hitPoints = List<HitPoint>.from(hitPoints)
|
||||
..createdAt = createdAt
|
||||
..updatedAt = DateTime.now()
|
||||
..completedAt = completedAt;
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,75 @@
|
||||
import 'dart:math';
|
||||
|
||||
import 'package:isar/isar.dart';
|
||||
|
||||
import '../../../core/constants/app_defaults.dart';
|
||||
|
||||
part 'user_profile.g.dart';
|
||||
|
||||
enum AvatarSource {
|
||||
asset,
|
||||
localFile,
|
||||
}
|
||||
|
||||
@collection
|
||||
class UserProfile {
|
||||
UserProfile();
|
||||
|
||||
Id id = AppDefaults.singletonId;
|
||||
|
||||
@Index(unique: true, replace: true)
|
||||
late String profileKey;
|
||||
|
||||
@Index(type: IndexType.value)
|
||||
late DateTime updatedAt;
|
||||
|
||||
late DateTime createdAt;
|
||||
late String nickname;
|
||||
String? avatarLocalPath;
|
||||
late String avatarAssetPath;
|
||||
|
||||
@enumerated
|
||||
AvatarSource avatarSource = AvatarSource.asset;
|
||||
|
||||
factory UserProfile.createDefault({DateTime? now, Random? random}) {
|
||||
final current = now ?? DateTime.now();
|
||||
final generator = random ?? Random();
|
||||
final suffix = AppDefaults.nicknameRandomMin +
|
||||
generator.nextInt(
|
||||
AppDefaults.nicknameRandomMax - AppDefaults.nicknameRandomMin + 1,
|
||||
);
|
||||
|
||||
return UserProfile()
|
||||
..profileKey = 'local_user_profile'
|
||||
..createdAt = current
|
||||
..updatedAt = current
|
||||
..nickname = '弓箭手_$suffix'
|
||||
..avatarAssetPath = AppDefaults.defaultAvatarAssetPath
|
||||
..avatarSource = AvatarSource.asset;
|
||||
}
|
||||
|
||||
String get displayAvatarPath =>
|
||||
avatarSource == AvatarSource.localFile &&
|
||||
avatarLocalPath != null &&
|
||||
avatarLocalPath!.trim().isNotEmpty
|
||||
? avatarLocalPath!
|
||||
: avatarAssetPath;
|
||||
|
||||
UserProfile copyWith({
|
||||
String? nickname,
|
||||
String? avatarLocalPath,
|
||||
String? avatarAssetPath,
|
||||
AvatarSource? avatarSource,
|
||||
DateTime? updatedAt,
|
||||
}) {
|
||||
return UserProfile()
|
||||
..id = id
|
||||
..profileKey = profileKey
|
||||
..createdAt = createdAt
|
||||
..updatedAt = updatedAt ?? DateTime.now()
|
||||
..nickname = nickname ?? this.nickname
|
||||
..avatarLocalPath = avatarLocalPath ?? this.avatarLocalPath
|
||||
..avatarAssetPath = avatarAssetPath ?? this.avatarAssetPath
|
||||
..avatarSource = avatarSource ?? this.avatarSource;
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user