This commit is contained in:
2026-08-08 10:57:54 +08:00
parent d0d48c1ffc
commit 604af909b0
184 changed files with 12521 additions and 12722 deletions
+76
View File
@@ -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
+3
View File
@@ -0,0 +1,3 @@
export 'app_config.dart';
export 'point_record.dart';
export 'user_profile.dart';
+151
View File
@@ -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
+75
View File
@@ -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