feat: Phase 2 — upgrade deps to null-safe, replace deprecated packages

- pubspec: bump all maintained deps to null-safe latest; flutter pub get resolves
- Remove 12 packages (0-ref or no null-safe): splashscreen, flappy_search_bar,
  searchable_dropdown, share, platform_detect, flutter_inappwebview, countdown,
  gender_selection, flutter_dash, smooth_star_rating, hovering, ffi
- Add flutter_rating_bar
- Keep discontinued-but-null-safe for now (Phase 5): pull_to_refresh, hive, catcher
- New null-safe custom components (API-compatible, callers change only imports):
  utils/countdown.dart, widgets/general/{hover_widget,gender_selection,
  smooth_star_rating,search_bar}.dart
- main.dart: drop dead SplashScreen _buildBody + _to field
- 23 consuming files: swap deprecated imports to local components

Null-safety migration + upgraded-API adaptation = Phase 3.
This commit is contained in:
2026-07-24 03:10:12 +08:00
parent 7ffcc848d0
commit 1a578c925d
37 changed files with 1252 additions and 574 deletions

View File

@@ -0,0 +1,111 @@
import 'package:flutter/material.dart';
/// 自定义实现,替代已停维的 `gender_selection` 包。
///
/// 保留原 `Gender` 枚举与 `GenderSelection` 组件的命名参数,
/// 使调用方仅改 import 即可编译。大部分装饰性参数为兼容保留(暂未全部生效,
/// 后续美化阶段 Phase 5 再完善视觉)。
enum Gender { Male, Female }
class GenderSelection extends StatefulWidget {
final String? maleText;
final String? femaleText;
final Color? selectedGenderIconBackgroundColor;
final Alignment? checkIconAlignment;
final IconData? selectedGenderCheckIcon;
final void Function(Gender gender)? onChanged;
final bool? equallyAligned;
final Duration? animationDuration;
final bool? isCircular;
final bool? isSelectedGenderIconCircular;
final double? opacityOfGradient;
final EdgeInsets? padding;
final double? size;
final Gender? selectedGender;
const GenderSelection({
super.key,
this.maleText,
this.femaleText,
this.selectedGenderIconBackgroundColor,
this.checkIconAlignment,
this.selectedGenderCheckIcon,
this.onChanged,
this.equallyAligned,
this.animationDuration,
this.isCircular,
this.isSelectedGenderIconCircular,
this.opacityOfGradient,
this.padding,
this.size,
this.selectedGender,
});
@override
State<GenderSelection> createState() => _GenderSelectionState();
}
class _GenderSelectionState extends State<GenderSelection> {
Gender? _selected;
@override
void initState() {
super.initState();
_selected = widget.selectedGender;
}
Widget _buildOption(Gender gender, String label) {
final selected = _selected == gender;
final color = widget.selectedGenderIconBackgroundColor ?? Colors.indigo;
return Expanded(
child: Padding(
padding: widget.padding ?? const EdgeInsets.all(3.0),
child: InkWell(
onTap: () {
setState(() => _selected = gender);
widget.onChanged?.call(gender);
},
child: Container(
height: widget.size ?? 50.0,
alignment: Alignment.center,
decoration: BoxDecoration(
color: selected ? color : color.withOpacity(0.06),
borderRadius:
(widget.isCircular ?? false) ? BorderRadius.circular(60) : null,
border: Border.all(
color: selected ? color : Colors.black26,
width: selected ? 2 : 1,
),
),
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
if (selected && widget.selectedGenderCheckIcon != null)
Icon(widget.selectedGenderCheckIcon, color: Colors.white, size: 18),
if (selected && widget.selectedGenderCheckIcon != null)
const SizedBox(width: 6),
Text(
label,
style: TextStyle(
color: selected ? Colors.white : Colors.black87,
fontWeight: FontWeight.w600,
),
),
],
),
),
),
),
);
}
@override
Widget build(BuildContext context) {
return Row(
children: <Widget>[
_buildOption(Gender.Male, widget.maleText ?? 'Mr'),
_buildOption(Gender.Female, widget.femaleText ?? 'Ms'),
],
);
}
}