屏幕适配

This commit is contained in:
2026-06-04 14:34:46 +08:00
parent 02c1c87b46
commit 5ddcb95358
17 changed files with 286 additions and 126 deletions

View File

@@ -1,20 +1,22 @@
import 'package:flutter/material.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';
import 'package:recording_tool/shared/widgets/app_network_image.dart';
class AppAvatar extends StatelessWidget {
const AppAvatar({super.key, this.imageUrl, this.initials, this.size = 40});
const AppAvatar({super.key, this.imageUrl, this.initials, this.size});
final String? imageUrl;
final String? initials;
final double size;
final double? size;
@override
Widget build(BuildContext context) {
final radius = BorderRadius.circular(size / 2);
final effectiveSize = size ?? 40.r;
final radius = BorderRadius.circular(effectiveSize / 2);
return ClipRRect(
borderRadius: radius,
child: SizedBox.square(
dimension: size,
dimension: effectiveSize,
child: imageUrl == null || imageUrl!.isEmpty
? ColoredBox(
color: Theme.of(context).colorScheme.primaryContainer,

View File

@@ -1,4 +1,5 @@
import 'package:flutter/material.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';
enum AppButtonVariant { primary, secondary, outline, text, danger }
@@ -11,7 +12,7 @@ class AppButton extends StatelessWidget {
this.variant = AppButtonVariant.primary,
this.isLoading = false,
this.expand = false,
this.height = 44,
this.height,
});
final String label;
@@ -20,7 +21,7 @@ class AppButton extends StatelessWidget {
final AppButtonVariant variant;
final bool isLoading;
final bool expand;
final double height;
final double? height;
@override
Widget build(BuildContext context) {
@@ -30,7 +31,7 @@ class AppButton extends StatelessWidget {
isLoading: isLoading,
);
final enabled = isLoading ? null : onPressed;
final size = Size(expand ? double.infinity : 0, height);
final size = Size(expand ? double.infinity : 0, height ?? 44.h);
return switch (variant) {
AppButtonVariant.primary => ElevatedButton(
@@ -79,9 +80,9 @@ class _ButtonContent extends StatelessWidget {
@override
Widget build(BuildContext context) {
if (isLoading) {
return const SizedBox.square(
dimension: 18,
child: CircularProgressIndicator(strokeWidth: 2),
return SizedBox.square(
dimension: 18.r,
child: CircularProgressIndicator(strokeWidth: 2.r),
);
}
@@ -92,7 +93,7 @@ class _ButtonContent extends StatelessWidget {
mainAxisAlignment: MainAxisAlignment.center,
children: [
icon!,
const SizedBox(width: 8),
SizedBox(width: 8.w),
Flexible(child: Text(label, overflow: TextOverflow.ellipsis)),
],
);

View File

@@ -1,17 +1,18 @@
import 'package:flutter/material.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';
import 'package:recording_tool/app/theme/app_theme.dart';
class AppCard extends StatelessWidget {
const AppCard({
super.key,
required this.child,
this.padding = const EdgeInsets.all(AppSpacing.lg),
this.padding,
this.margin,
this.onTap,
});
final Widget child;
final EdgeInsetsGeometry padding;
final EdgeInsetsGeometry? padding;
final EdgeInsetsGeometry? margin;
final VoidCallback? onTap;
@@ -19,10 +20,10 @@ class AppCard extends StatelessWidget {
Widget build(BuildContext context) {
final card = Container(
margin: margin,
padding: padding,
padding: padding ?? EdgeInsets.all(AppSpacing.lg),
decoration: BoxDecoration(
color: Theme.of(context).colorScheme.surface,
borderRadius: BorderRadius.circular(8),
borderRadius: BorderRadius.circular(8.r),
border: Border.all(color: AppTheme.border),
),
child: child,
@@ -32,7 +33,7 @@ class AppCard extends StatelessWidget {
return InkWell(
onTap: onTap,
borderRadius: BorderRadius.circular(8),
borderRadius: BorderRadius.circular(8.r),
child: card,
);
}

View File

@@ -1,4 +1,5 @@
import 'package:flutter/material.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';
class AppEmptyView extends StatelessWidget {
const AppEmptyView({
@@ -19,15 +20,15 @@ class AppEmptyView extends StatelessWidget {
final colors = Theme.of(context).colorScheme;
return Center(
child: Padding(
padding: const EdgeInsets.all(24),
padding: EdgeInsets.all(24.r),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Icon(icon, size: 56, color: colors.outline),
const SizedBox(height: 12),
Icon(icon, size: 56.r, color: colors.outline),
SizedBox(height: 12.h),
Text(title, style: Theme.of(context).textTheme.titleMedium),
if (message != null) ...[
const SizedBox(height: 6),
SizedBox(height: 6.h),
Text(
message!,
textAlign: TextAlign.center,
@@ -36,7 +37,7 @@ class AppEmptyView extends StatelessWidget {
),
),
],
if (action != null) ...[const SizedBox(height: 16), action!],
if (action != null) ...[SizedBox(height: 16.h), action!],
],
),
),

View File

@@ -1,4 +1,5 @@
import 'package:flutter/material.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';
import 'package:recording_tool/shared/widgets/app_button.dart';
class AppErrorView extends StatelessWidget {
@@ -17,18 +18,18 @@ class AppErrorView extends StatelessWidget {
Widget build(BuildContext context) {
return Center(
child: Padding(
padding: const EdgeInsets.all(24),
padding: EdgeInsets.all(24.r),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Icon(
Icons.error_outline,
size: 56,
size: 56.r,
color: Theme.of(context).colorScheme.error,
),
const SizedBox(height: 12),
SizedBox(height: 12.h),
Text(title, style: Theme.of(context).textTheme.titleMedium),
const SizedBox(height: 6),
SizedBox(height: 6.h),
Text(
message,
textAlign: TextAlign.center,
@@ -37,10 +38,10 @@ class AppErrorView extends StatelessWidget {
),
),
if (onRetry != null) ...[
const SizedBox(height: 16),
SizedBox(height: 16.h),
AppButton(
label: '重试',
icon: const Icon(Icons.refresh, size: 18),
icon: Icon(Icons.refresh, size: 18.r),
variant: AppButtonVariant.outline,
onPressed: onRetry,
),

View File

@@ -1,22 +1,24 @@
import 'package:flutter/material.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';
class AppLoadingView extends StatelessWidget {
const AppLoadingView({super.key, this.message = '加载中', this.size = 24});
const AppLoadingView({super.key, this.message = '加载中', this.size});
final String message;
final double size;
final double? size;
@override
Widget build(BuildContext context) {
final effectiveSize = size ?? 24.r;
return Center(
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
SizedBox.square(
dimension: size,
child: const CircularProgressIndicator(strokeWidth: 2.4),
dimension: effectiveSize,
child: CircularProgressIndicator(strokeWidth: 2.4.r),
),
const SizedBox(height: 12),
SizedBox(height: 12.h),
Text(
message,
style: Theme.of(context).textTheme.bodyMedium?.copyWith(

View File

@@ -1,5 +1,6 @@
import 'package:cached_network_image/cached_network_image.dart';
import 'package:flutter/material.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';
class AppNetworkImage extends StatelessWidget {
const AppNetworkImage({
@@ -26,10 +27,10 @@ class AppNetworkImage extends StatelessWidget {
fit: fit,
placeholder: (_, _) => ColoredBox(
color: Theme.of(context).colorScheme.surfaceContainerHighest,
child: const Center(
child: Center(
child: SizedBox.square(
dimension: 20,
child: CircularProgressIndicator(strokeWidth: 2),
dimension: 20.r,
child: CircularProgressIndicator(strokeWidth: 2.r),
),
),
),

View File

@@ -1,4 +1,5 @@
import 'package:flutter/material.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';
import 'package:pull_to_refresh/pull_to_refresh.dart';
class AppRefreshList<T> extends StatefulWidget {
@@ -62,7 +63,7 @@ class _AppRefreshListState<T> extends State<AppRefreshList<T>> {
padding: widget.padding,
itemCount: widget.items.length,
separatorBuilder: (_, _) =>
widget.separator ?? const SizedBox(height: 8),
widget.separator ?? SizedBox(height: 8.h),
itemBuilder: (context, index) {
return widget.itemBuilder(context, widget.items[index], index);
},

View File

@@ -1,4 +1,5 @@
import 'package:flutter/material.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';
enum AppTagTone { neutral, success, warning, danger, info }
@@ -18,17 +19,17 @@ class AppTag extends StatelessWidget {
Widget build(BuildContext context) {
final (foreground, background) = _colors(context);
return Container(
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4),
padding: EdgeInsets.symmetric(horizontal: 8.r, vertical: 4.r),
decoration: BoxDecoration(
color: background,
borderRadius: BorderRadius.circular(999),
borderRadius: BorderRadius.circular(999.r),
),
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
if (icon != null) ...[
Icon(icon, size: 14, color: foreground),
const SizedBox(width: 4),
Icon(icon, size: 14.r, color: foreground),
SizedBox(width: 4.w),
],
Text(
label,

View File

@@ -2,6 +2,7 @@ import 'dart:async';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';
class AppTextField extends StatefulWidget {
const AppTextField({
@@ -105,7 +106,7 @@ class _AppTextFieldState extends State<AppTextField> {
hintText: widget.hint,
prefixIcon: widget.prefixIcon,
suffixIcon: widget.suffixIcon,
border: OutlineInputBorder(borderRadius: BorderRadius.circular(8)),
border: OutlineInputBorder(borderRadius: BorderRadius.circular(8.r)),
counterText: '',
),
);

View File

@@ -1,6 +1,7 @@
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';
import 'package:recording_tool/app/router/app_navigator.dart';
class AppToast {
@@ -68,15 +69,15 @@ class _ToastWidget extends StatelessWidget {
constraints: BoxConstraints(
maxWidth: MediaQuery.sizeOf(context).width * 0.8,
),
padding: const EdgeInsets.symmetric(horizontal: 18, vertical: 11),
padding: EdgeInsets.symmetric(horizontal: 18.r, vertical: 11.r),
decoration: BoxDecoration(
color: Colors.black.withValues(alpha: 0.82),
borderRadius: BorderRadius.circular(8),
borderRadius: BorderRadius.circular(8.r),
),
child: Text(
message,
textAlign: TextAlign.center,
style: const TextStyle(color: Colors.white, fontSize: 14),
style: TextStyle(color: Colors.white, fontSize: 14.sp),
),
),
),