- 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.
112 lines
3.4 KiB
Dart
112 lines
3.4 KiB
Dart
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'),
|
|
],
|
|
);
|
|
}
|
|
}
|