android:v0.2.9

This commit is contained in:
DelLevin-Home
2026-08-06 23:49:49 +08:00
parent 31418eb2b7
commit eee15e0aa8
23 changed files with 1463 additions and 334 deletions

View File

@@ -53,11 +53,13 @@ class _GameDetailPageState extends State<GameDetailPage> {
List<String> _editPlatforms = [];
List<String> _editVersions = [];
List<String> _editGenres = [];
List<String> _editDeveloper = [];
List<String> _editPurchasePlatforms = [];
String? _editCoverPath;
String _editStatus = 'want_to_play';
String _editCategory = 'digital';
DateTime? _editPurchaseDate;
DateTime? _editReleaseDate;
bool _editIsDownloading = false;
final ImagePicker _picker = ImagePicker();
@@ -81,11 +83,13 @@ class _GameDetailPageState extends State<GameDetailPage> {
_editPlatforms = List.from(g.platforms);
_editVersions = List.from(g.versions);
_editGenres = List.from(g.genres);
_editDeveloper = List.from(g.developer);
_editPurchasePlatforms = List.from(g.purchasePlatforms);
_editCoverPath = g.coverPath;
_editStatus = g.status;
_editCategory = g.category;
_editPurchaseDate = g.purchaseDate;
_editReleaseDate = g.releaseDate;
}
void _enterEditMode() {
@@ -101,11 +105,13 @@ class _GameDetailPageState extends State<GameDetailPage> {
_editPlatforms = List.from(latest.platforms);
_editVersions = List.from(latest.versions);
_editGenres = List.from(latest.genres);
_editDeveloper = List.from(latest.developer);
_editPurchasePlatforms = List.from(latest.purchasePlatforms);
_editCoverPath = latest.coverPath;
_editStatus = latest.status;
_editCategory = latest.category;
_editPurchaseDate = latest.purchaseDate;
_editReleaseDate = latest.releaseDate;
setState(() => _isEditing = true);
}
@@ -252,8 +258,14 @@ class _GameDetailPageState extends State<GameDetailPage> {
)),
]),
],
if (game.developer.isNotEmpty)
_buildDesktopInfoRow('开发者', game.developer.join(''), colors),
if (game.releaseDate != null)
_buildDesktopInfoRow('发售时间', _formatDate(game.releaseDate!), colors),
if (game.playTimeHours > 0 || game.playTimeMinutes > 0)
_buildDesktopInfoRow('游玩时长', '${game.playTimeHours}小时${game.playTimeMinutes}分钟', colors),
if (game.playCount > 0)
_buildDesktopInfoRow('游玩次数', '${game.playCount}', colors),
if (game.purchasePlatforms.isNotEmpty)
_buildDesktopInfoRow('购买平台', game.purchasePlatforms.join(''), colors),
if (game.purchaseDate != null)
@@ -556,6 +568,21 @@ class _GameDetailPageState extends State<GameDetailPage> {
if (result != null) setState(() => _editGenres = result);
}),
const SizedBox(height: 16),
// 开发者
_buildEditChipField('开发者', _editDeveloper, onTap: () async {
final provider = context.read<AppProvider>();
final data = provider.games.map((g) => g.developer).toList();
final result = await GenreSelectorPage.show(
context: context, title: '选择开发者',
existingTagsFuture: compute(_collectUnique, data),
initialSelected: _editDeveloper, hint: '任天堂、FromSoftware',
);
if (result != null) setState(() => _editDeveloper = result);
}),
const SizedBox(height: 16),
// 发售时间
_buildEditDateField('发售时间', _editReleaseDate, (d) => setState(() => _editReleaseDate = d), clearable: true),
const SizedBox(height: 16),
// 购买平台
_buildEditChipField('购买平台', _editPurchasePlatforms, onTap: () async {
final provider = context.read<AppProvider>();
@@ -889,6 +916,7 @@ class _GameDetailPageState extends State<GameDetailPage> {
platforms: _editPlatforms,
versions: _editVersions,
genres: _editGenres,
developer: _editDeveloper,
purchasePlatforms: _editPurchasePlatforms,
purchasePrice: _purchasePriceCtrl.text.trim().isEmpty ? null : _purchasePriceCtrl.text.trim(),
playTimeHours: int.tryParse(_playTimeHoursCtrl.text) ?? 0,
@@ -898,6 +926,7 @@ class _GameDetailPageState extends State<GameDetailPage> {
status: _editStatus,
category: _editCategory,
purchaseDate: _editPurchaseDate,
releaseDate: _editReleaseDate,
updatedAt: DateTime.now(),
);
await context.read<AppProvider>().updateGame(updated);
@@ -954,8 +983,14 @@ class _GameDetailPageState extends State<GameDetailPage> {
_buildInfoSection('版本', game.versions.join('')),
if (game.genres.isNotEmpty)
_buildGenresSection(game),
if (game.developer.isNotEmpty)
_buildInfoSection('开发者', game.developer.join('')),
if (game.releaseDate != null)
_buildInfoSection('发售时间', _formatDate(game.releaseDate!)),
if (game.playTimeHours > 0 || game.playTimeMinutes > 0)
_buildInfoSection('游玩时长', '${game.playTimeHours}小时${game.playTimeMinutes}分钟'),
if (game.playCount > 0)
_buildInfoSection('游玩次数', '${game.playCount}'),
if (game.purchasePlatforms.isNotEmpty)
_buildInfoSection('购买平台', game.purchasePlatforms.join('')),
if (game.purchaseDate != null)
@@ -1097,8 +1132,14 @@ class _GameDetailPageState extends State<GameDetailPage> {
const SizedBox(height: 12),
_buildOverlayGenres(game),
],
if (game.developer.isNotEmpty)
_buildOverlayInfoRow('开发者', game.developer.join('')),
if (game.releaseDate != null)
_buildOverlayInfoRow('发售时间', _formatDate(game.releaseDate!)),
if (game.playTimeHours > 0 || game.playTimeMinutes > 0)
_buildOverlayInfoRow('游玩时长', '${game.playTimeHours}小时${game.playTimeMinutes}分钟'),
if (game.playCount > 0)
_buildOverlayInfoRow('游玩次数', '${game.playCount}'),
if (game.purchasePlatforms.isNotEmpty)
_buildOverlayInfoRow('购买平台', game.purchasePlatforms.join('')),
if (game.purchaseDate != null)

View File

@@ -41,17 +41,20 @@ class _GameFormPageState extends State<GameFormPage> {
late TextEditingController _ratingController;
late TextEditingController _playTimeHoursController;
late TextEditingController _playTimeMinutesController;
int _playCount = 0;
late TextEditingController _purchasePriceController;
late TextEditingController _summaryController;
List<String> _platforms = [];
List<String> _versions = [];
List<String> _genres = [];
List<String> _developer = [];
List<String> _purchasePlatforms = [];
String? _coverPath;
String _status = 'want_to_play';
String _category = 'digital';
DateTime? _purchaseDate;
DateTime? _releaseDate;
bool _isDownloading = false;
@override
@@ -83,11 +86,14 @@ class _GameFormPageState extends State<GameFormPage> {
_platforms = List.from(game.platforms);
_versions = List.from(game.versions);
_genres = List.from(game.genres);
_developer = List.from(game.developer);
_purchasePlatforms = List.from(game.purchasePlatforms);
_coverPath = game.coverPath;
_status = game.status;
_category = game.category;
_purchaseDate = game.purchaseDate;
_releaseDate = game.releaseDate;
_playCount = game.playCount;
} else if (widget.initialStatus != null) {
_status = widget.initialStatus!;
}
@@ -278,6 +284,51 @@ class _GameFormPageState extends State<GameFormPage> {
},
),
),
// 开发者
SizedBox(
width: (MediaQuery.of(context).size.width - 52) / 2,
height: 90,
child: _buildInfoCard(
label: '开发者',
value: _developer.isEmpty
? ''
: '${_developer.length}个:${_developer.join('')}',
icon: Icons.code_outlined,
scrollHorizontal: true,
onTap: () async {
final provider = context.read<AppProvider>();
final data = provider.games.map((g) => g.developer).toList();
final result = await GenreSelectorPage.show(
context: context,
title: '选择开发者',
existingTagsFuture: compute(_collectUnique, data),
initialSelected: _developer,
hint: '任天堂、FromSoftware',
);
if (!mounted) return;
if (result != null) setState(() => _developer = result);
},
),
),
// 发售时间
SizedBox(
width: (MediaQuery.of(context).size.width - 52) / 2,
height: 90,
child: _buildInfoCard(
label: '发售时间',
value: _releaseDate != null
? '${_releaseDate!.year}.${_releaseDate!.month.toString().padLeft(2, '0')}.${_releaseDate!.day.toString().padLeft(2, '0')}'
: '',
icon: Icons.event_outlined,
trailing: _releaseDate != null
? GestureDetector(
onTap: () => setState(() => _releaseDate = null),
child: Icon(Icons.close, size: 16, color: colors.onSurface.withValues(alpha: 0.35)),
)
: null,
onTap: () => _selectReleaseDate(),
),
),
// 游玩时长
SizedBox(
width: (MediaQuery.of(context).size.width - 52) / 2,
@@ -289,6 +340,19 @@ class _GameFormPageState extends State<GameFormPage> {
onTap: () => _showPlayTimePicker(),
),
),
// 游玩次数
SizedBox(
width: (MediaQuery.of(context).size.width - 52) / 2,
height: 90,
child: _buildInfoCard(
label: '游玩次数',
value: _playCount > 0 ? '$_playCount' : '',
icon: Icons.repeat_outlined,
onTap: () => _editPlayCount(),
),
),
// 购买平台
SizedBox(
width: (MediaQuery.of(context).size.width - 52) / 2,
@@ -943,6 +1007,30 @@ class _GameFormPageState extends State<GameFormPage> {
}
}
Future<void> _editPlayCount() async {
final controller = TextEditingController(text: _playCount > 0 ? '$_playCount' : '');
final result = await showDialog<String>(
context: context,
builder: (ctx) => AlertDialog(
title: const Text('游玩次数'),
content: TextField(
controller: controller,
keyboardType: TextInputType.number,
autofocus: true,
decoration: const InputDecoration(hintText: '输入次数'),
),
actions: [
TextButton(onPressed: () => Navigator.pop(ctx), child: const Text('取消')),
TextButton(onPressed: () => Navigator.pop(ctx, controller.text), child: const Text('确定')),
],
),
);
if (result != null) {
final val = int.tryParse(result) ?? 0;
setState(() => _playCount = val < 0 ? 0 : val);
}
}
void _showPlayTimePicker() {
final colors = Theme.of(context).colorScheme;
final hoursController = TextEditingController(text: _playTimeHoursController.text);
@@ -1025,14 +1113,28 @@ class _GameFormPageState extends State<GameFormPage> {
}
}
Future<void> _selectReleaseDate() async {
final picked = await showDatePicker(
context: context,
initialDate: _releaseDate ?? DateTime.now(),
firstDate: DateTime(1970),
lastDate: DateTime.now().add(const Duration(days: 365 * 5)),
builder: (context, child) => child!,
);
if (!mounted) return;
if (picked != null) {
setState(() => _releaseDate = picked);
}
}
bool _hasContent() {
if (widget.game != null) return true;
if (_titleController.text.trim().isNotEmpty) return true;
if (_ratingController.text.trim().isNotEmpty) return true;
if (_coverPath != null) return true;
if (_platforms.isNotEmpty || _versions.isNotEmpty || _genres.isNotEmpty) return true;
if (_platforms.isNotEmpty || _versions.isNotEmpty || _genres.isNotEmpty || _developer.isNotEmpty) return true;
if (_purchasePlatforms.isNotEmpty) return true;
if (_purchaseDate != null) return true;
if (_purchaseDate != null || _releaseDate != null) return true;
if (_purchasePriceController.text.trim().isNotEmpty) return true;
if (_summaryController.text.trim().isNotEmpty) return true;
if (int.tryParse(_playTimeHoursController.text) != null && int.parse(_playTimeHoursController.text) > 0) return true;
@@ -1100,10 +1202,13 @@ class _GameFormPageState extends State<GameFormPage> {
platforms: _platforms,
versions: _versions,
genres: _genres,
developer: _developer,
playTimeHours: playTimeHours,
playTimeMinutes: playTimeMinutes,
playCount: _playCount,
purchasePlatforms: _purchasePlatforms,
purchaseDate: _purchaseDate,
releaseDate: _releaseDate,
purchasePrice: _purchasePriceController.text.trim().isNotEmpty
? _purchasePriceController.text.trim()
: null,
@@ -1126,10 +1231,13 @@ class _GameFormPageState extends State<GameFormPage> {
platforms: _platforms,
versions: _versions,
genres: _genres,
developer: _developer,
playTimeHours: playTimeHours,
playTimeMinutes: playTimeMinutes,
playCount: _playCount,
purchasePlatforms: _purchasePlatforms,
purchaseDate: _purchaseDate,
releaseDate: _releaseDate,
purchasePrice: _purchasePriceController.text.trim().isNotEmpty
? _purchasePriceController.text.trim()
: null,

View File

@@ -35,6 +35,7 @@ class _GameTabPageState extends State<GameTabPage> {
int _lastEditRefreshCounter = 0;
int _prevGameCount = -1;
int _prevLayoutStyle = -1;
int _prevSortMode = -1;
double _swipeOffset = 0.0;
static const _statusMap = {0: 'completed', 1: 'playing', 2: 'want_to_play', 3: 'abandoned'};
@@ -73,6 +74,7 @@ class _GameTabPageState extends State<GameTabPage> {
final statusChanged = provider.gameStatusIndex != _lastStatusIndex;
final layoutChanged = provider.gameLayoutStyle != _prevLayoutStyle;
final countChanged = provider.games.length != _prevGameCount;
final sortModeChanged = UserPrefs().gameSortMode != _prevSortMode;
final editRefreshed = provider.editRefreshCounter > _lastEditRefreshCounter;
if (editRefreshed && provider.lastEditedItemId != null) {
_lastEditRefreshCounter = provider.editRefreshCounter;
@@ -97,8 +99,9 @@ class _GameTabPageState extends State<GameTabPage> {
}
return;
}
if (statusChanged || layoutChanged || countChanged || editRefreshed) {
if (statusChanged || layoutChanged || sortModeChanged || countChanged || editRefreshed) {
_prevLayoutStyle = provider.gameLayoutStyle;
_prevSortMode = UserPrefs().gameSortMode;
_prevGameCount = provider.games.length;
_loadFirst();
}