generated from ttipo/project-base
feat(flutter): 홈 화면 구현과 반려동물 등록 연결
시안 screen-home 의 흐름을 옮겼다. - 인사말(소셜 프로필 이름, 모르면 "집사") · 알림 아이콘 - 우리 아이: 없으면 등록 안내 카드 → 등록 화면, 있으면 아이 고르기 + 펫 명함 빈 슬롯 - 일정(오늘 일정·오늘 할 일·예약 일정)·다이어리는 해당 기능 전까지 시안의 빈 상태로 표시 - 등록·수정 화면을 탭바 위에 덮는 라우트로 연결, 토스트를 모든 화면 위에 표시 Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -10,6 +10,9 @@ abstract interface class AuthRepository {
|
||||
/// 저장된 세션이 있는지 (앱 시작 시 진입 화면 결정에 쓴다)
|
||||
bool get hasSession;
|
||||
|
||||
/// 인사말에 쓰는 이름 — 소셜 계정 프로필에서 받는다. 모르면 null
|
||||
String? get nickname;
|
||||
|
||||
Future<void> signIn(SocialProvider provider);
|
||||
|
||||
Future<void> signOut();
|
||||
@@ -27,6 +30,10 @@ class DemoAuthRepository implements AuthRepository {
|
||||
@override
|
||||
bool get hasSession => _prefs.containsKey(_kDemoSessionKey);
|
||||
|
||||
/// 소셜 프로필을 받지 않으므로 이름을 알 수 없다
|
||||
@override
|
||||
String? get nickname => null;
|
||||
|
||||
@override
|
||||
Future<void> signIn(SocialProvider provider) async {
|
||||
await _prefs.setString(_kDemoSessionKey, provider.name);
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:tma_app/features/auth/data/repositories/auth_repository.dart';
|
||||
import 'package:tma_app/features/entry/presentation/providers/entry_provider.dart';
|
||||
|
||||
/// 이름을 모를 때 부르는 호칭 — 로그인 화면의 "반가워요, 집사님!" 과 맞춘다
|
||||
const String kDefaultNickname = '집사';
|
||||
|
||||
/// 인사말에 쓰는 사용자 이름 (로그인·로그아웃하면 다시 읽는다)
|
||||
final userNicknameProvider = Provider<String>((ref) {
|
||||
ref.watch(entryProvider.select((s) => s.signedIn));
|
||||
return ref.watch(authRepositoryProvider).nickname ?? kDefaultNickname;
|
||||
});
|
||||
@@ -0,0 +1,120 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
import 'package:tma_app/core/constants/app_colors.dart';
|
||||
import 'package:tma_app/core/constants/app_sizes.dart';
|
||||
import 'package:tma_app/core/router/app_routes.dart';
|
||||
import 'package:tma_app/features/auth/presentation/providers/user_provider.dart';
|
||||
import 'package:tma_app/features/home/presentation/widgets/home_pets_section.dart';
|
||||
import 'package:tma_app/features/home/presentation/widgets/home_schedule_section.dart';
|
||||
import 'package:tma_app/shared/widgets/icons/rup_icon.dart';
|
||||
import 'package:tma_app/shared/widgets/rup_card.dart';
|
||||
import 'package:tma_app/shared/widgets/section_title.dart';
|
||||
|
||||
/// 탭바 뒤로 콘텐츠가 가려지지 않게 두는 여유 (시안 .rup-screen 아래 124 에서 탭바 몫을 뺀 값)
|
||||
const double _kBottomGap = 30;
|
||||
|
||||
/// 홈 — 인사말, 우리 아이, 오늘 일정·예약 일정, 다이어리
|
||||
class HomePage extends ConsumerWidget {
|
||||
const HomePage({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
final nickname = ref.watch(userNicknameProvider);
|
||||
final today = DateTime.now();
|
||||
|
||||
Widget padded(Widget child) => Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: AppSpacing.xl),
|
||||
child: child,
|
||||
);
|
||||
|
||||
return SafeArea(
|
||||
bottom: false,
|
||||
child: ListView(
|
||||
padding: EdgeInsets.only(
|
||||
top: AppSpacing.xs,
|
||||
// 탭바 높이는 Scaffold(extendBody) 가 padding.bottom 에 더해 준다
|
||||
bottom: MediaQuery.paddingOf(context).bottom + _kBottomGap,
|
||||
),
|
||||
children: [
|
||||
padded(_HomeHeader(nickname: nickname)),
|
||||
// 시안: 헤더 아래 18 + 소제목 위 6 (소제목 줄의 링크 터치 여백만큼 뺀다)
|
||||
const SizedBox(height: 16),
|
||||
padded(
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: AppSpacing.xs),
|
||||
child: SectionTitle(
|
||||
title: '우리 아이',
|
||||
sub: true,
|
||||
trailing: '아이 상세보기',
|
||||
onTrailingTap: () => context.go(AppRoutes.myPage),
|
||||
),
|
||||
),
|
||||
),
|
||||
padded(
|
||||
HomePetsSection(
|
||||
onRegister: () => context.push(AppRoutes.petRegister),
|
||||
// 펫 명함 만들기는 명함 탭에서 이어서 만든다
|
||||
onCreateCard: (pet) => context.go(AppRoutes.petCards),
|
||||
),
|
||||
),
|
||||
const DividerBand(),
|
||||
padded(
|
||||
HomeScheduleSection(
|
||||
today: today,
|
||||
onSeeAll: () => context.go(AppRoutes.records),
|
||||
),
|
||||
),
|
||||
const DividerBand(),
|
||||
padded(
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: AppSpacing.xs),
|
||||
child: SectionTitle(
|
||||
title: '다이어리',
|
||||
trailing: '더보기',
|
||||
onTrailingTap: () => context.go(AppRoutes.records),
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 19),
|
||||
padded(const RupCard(child: EmptyMessage('아직 작성한 다이어리가 없어요'))),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _HomeHeader extends StatelessWidget {
|
||||
const _HomeHeader({required this.nickname});
|
||||
|
||||
final String nickname;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: Text(
|
||||
'안녕하세요, $nickname님',
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
style: Theme.of(context).textTheme.headlineSmall,
|
||||
),
|
||||
),
|
||||
// 알림 화면은 흐름 순서상 뒤에 만든다 — 지금은 아이콘만 둔다
|
||||
const SizedBox.square(
|
||||
dimension: 42,
|
||||
child: Center(
|
||||
child: RupIcon(
|
||||
RupIconName.bell,
|
||||
size: 23,
|
||||
stroke: 2.2,
|
||||
color: AppColors.ink2,
|
||||
semanticLabel: '알림',
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,167 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:tma_app/core/constants/app_colors.dart';
|
||||
import 'package:tma_app/core/constants/app_sizes.dart';
|
||||
import 'package:tma_app/features/pet/domain/pet.dart';
|
||||
import 'package:tma_app/features/pet/presentation/providers/pet_providers.dart';
|
||||
import 'package:tma_app/features/pet/presentation/widgets/pet_avatar.dart';
|
||||
import 'package:tma_app/features/pet_card/presentation/widgets/name_card_empty.dart';
|
||||
import 'package:tma_app/shared/widgets/icons/rup_icon.dart';
|
||||
import 'package:tma_app/shared/widgets/rup_card.dart';
|
||||
|
||||
/// 우리 아이 고르기 — 원형 사진 치수 (시안 .rup-petpick__ring 58 / 아바타 46)
|
||||
const double _kRingSize = 58;
|
||||
const double _kAvatarSize = 46;
|
||||
const double _kRingBorder = 2.5;
|
||||
|
||||
/// 홈 "우리 아이" — 아이가 없으면 등록 안내, 있으면 아이 고르기 + 대표 명함 자리
|
||||
class HomePetsSection extends ConsumerWidget {
|
||||
const HomePetsSection({super.key, required this.onRegister, required this.onCreateCard});
|
||||
|
||||
final VoidCallback onRegister;
|
||||
final ValueChanged<Pet> onCreateCard;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
final pets = ref.watch(petListProvider).value;
|
||||
// 불러오는 동안은 자리만 잡는다 — "없어요" 가 스치지 않게
|
||||
if (pets == null) return const SizedBox(height: _kRingSize + 40);
|
||||
if (pets.isEmpty) {
|
||||
// 시안: 카드 위 26, 아래는 비어 있는 아이 목록 줄의 여백(4+8)이 남는다
|
||||
return Padding(
|
||||
padding: const EdgeInsets.only(top: 22, bottom: AppSpacing.md),
|
||||
child: _RegisterPetCard(onTap: onRegister),
|
||||
);
|
||||
}
|
||||
|
||||
final selected = ref.watch(selectedPetProvider);
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: [
|
||||
// 시안: 소제목 아래 12 (소제목 줄의 링크 터치 여백만큼 뺀다)
|
||||
const SizedBox(height: AppSpacing.sm),
|
||||
_PetPicker(
|
||||
pets: pets,
|
||||
selectedId: selected?.id,
|
||||
onSelect: (id) => ref.read(selectedPetIdProvider.notifier).select(id),
|
||||
),
|
||||
if (selected != null) ...[
|
||||
const SizedBox(height: 20),
|
||||
// 펫 명함 기능 전까지는 모든 아이가 "명함 없음" 상태다
|
||||
NameCardEmpty(petName: selected.name, onCreate: () => onCreateCard(selected)),
|
||||
],
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// 아이가 한 마리도 없을 때 — 등록 안내 카드
|
||||
class _RegisterPetCard extends StatelessWidget {
|
||||
const _RegisterPetCard({required this.onTap});
|
||||
|
||||
final VoidCallback onTap;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final textTheme = Theme.of(context).textTheme;
|
||||
return Semantics(
|
||||
button: true,
|
||||
child: RupCard(
|
||||
onTap: onTap,
|
||||
padding: const EdgeInsets.symmetric(horizontal: AppSpacing.lg, vertical: 15),
|
||||
child: Row(
|
||||
children: [
|
||||
Container(
|
||||
width: 44,
|
||||
height: 44,
|
||||
decoration: const BoxDecoration(color: AppColors.primary50, shape: BoxShape.circle),
|
||||
alignment: Alignment.center,
|
||||
child: const RupIcon(RupIconName.plus, size: 22, stroke: 2.4, color: AppColors.primary600),
|
||||
),
|
||||
const SizedBox(width: AppSpacing.md),
|
||||
Expanded(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
'우리 아이를 등록해 주세요',
|
||||
style: textTheme.titleSmall!.copyWith(fontSize: 14),
|
||||
),
|
||||
const SizedBox(height: 2),
|
||||
Text(
|
||||
'등록하면 일정과 펫 명함을 쓸 수 있어요',
|
||||
style: textTheme.bodySmall!.copyWith(fontSize: 12.5, color: AppColors.muted),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
const RupIcon(RupIconName.chevR, size: 16, stroke: 2.2, color: AppColors.muted),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// 가로로 넘기는 아이 목록 — 고른 아이는 주황 테두리
|
||||
class _PetPicker extends StatelessWidget {
|
||||
const _PetPicker({required this.pets, required this.selectedId, required this.onSelect});
|
||||
|
||||
final List<Pet> pets;
|
||||
final String? selectedId;
|
||||
final ValueChanged<String> onSelect;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final textTheme = Theme.of(context).textTheme;
|
||||
return SingleChildScrollView(
|
||||
scrollDirection: Axis.horizontal,
|
||||
clipBehavior: Clip.none,
|
||||
padding: const EdgeInsets.fromLTRB(2, AppSpacing.xs, 2, AppSpacing.sm),
|
||||
child: Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
for (final (i, pet) in pets.indexed) ...[
|
||||
if (i > 0) const SizedBox(width: 14),
|
||||
Semantics(
|
||||
button: true,
|
||||
selected: pet.id == selectedId,
|
||||
label: pet.name,
|
||||
excludeSemantics: true,
|
||||
child: GestureDetector(
|
||||
onTap: () => onSelect(pet.id),
|
||||
child: Column(
|
||||
children: [
|
||||
AnimatedContainer(
|
||||
duration: AppDurations.fast,
|
||||
width: _kRingSize,
|
||||
height: _kRingSize,
|
||||
decoration: BoxDecoration(
|
||||
shape: BoxShape.circle,
|
||||
border: Border.all(
|
||||
color: pet.id == selectedId ? AppColors.primary : Colors.transparent,
|
||||
width: _kRingBorder,
|
||||
),
|
||||
),
|
||||
alignment: Alignment.center,
|
||||
child: PetAvatar(pet: pet, size: _kAvatarSize),
|
||||
),
|
||||
const SizedBox(height: 7),
|
||||
Text(
|
||||
pet.name,
|
||||
style: textTheme.bodySmall!.copyWith(
|
||||
fontSize: 12.5,
|
||||
fontWeight: pet.id == selectedId ? FontWeight.w700 : FontWeight.w600,
|
||||
color: pet.id == selectedId ? AppColors.primary600 : AppColors.muted,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,142 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:tma_app/core/constants/app_colors.dart';
|
||||
import 'package:tma_app/core/constants/app_sizes.dart';
|
||||
import 'package:tma_app/core/utils/date_format.dart';
|
||||
import 'package:tma_app/shared/widgets/icons/rup_icon.dart';
|
||||
import 'package:tma_app/shared/widgets/rup_card.dart';
|
||||
import 'package:tma_app/shared/widgets/section_title.dart';
|
||||
|
||||
/// 홈 "일정" — 오늘 일정(다음 일정·오늘 할 일) + 예약 일정.
|
||||
/// 일정 기능을 만들기 전이라 시안의 "일정 없음" 상태를 보여 준다.
|
||||
class HomeScheduleSection extends StatelessWidget {
|
||||
const HomeScheduleSection({super.key, required this.today, required this.onSeeAll});
|
||||
|
||||
final DateTime today;
|
||||
final VoidCallback onSeeAll;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: [
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: AppSpacing.xs),
|
||||
child: SectionTitle(title: '일정', trailing: '전체보기', onTrailingTap: onSeeAll),
|
||||
),
|
||||
const SizedBox(height: 17),
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: AppSpacing.xs),
|
||||
child: SectionTitle(
|
||||
title: '오늘 일정',
|
||||
sub: true,
|
||||
trailing: '${formatMonthDay(today)} ${weekdayKo(today)}요일',
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 10),
|
||||
const _NextTaskOff(text: '오늘 등록된 일정이 없어요'),
|
||||
const SizedBox(height: AppSpacing.md),
|
||||
const RupCard(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: [
|
||||
_TodayProgress(done: 0, total: 0),
|
||||
EmptyMessage('오늘은 등록된 일정이 없어요'),
|
||||
],
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 18),
|
||||
const Padding(
|
||||
padding: EdgeInsets.symmetric(horizontal: AppSpacing.xs),
|
||||
child: SectionTitle(title: '예약 일정', sub: true, trailing: '병원 · 미용 · 검진'),
|
||||
),
|
||||
const SizedBox(height: 10),
|
||||
const RupCard(child: EmptyMessage('예약된 일정이 없어요')),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// 다음 일정 카드의 비활성 상태 (.rup-nextcard--off)
|
||||
class _NextTaskOff extends StatelessWidget {
|
||||
const _NextTaskOff({required this.text});
|
||||
|
||||
final String text;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Opacity(
|
||||
opacity: .8,
|
||||
child: RupCard(
|
||||
color: AppColors.surface2,
|
||||
shadow: false,
|
||||
padding: const EdgeInsets.symmetric(horizontal: 14, vertical: 13),
|
||||
child: Row(
|
||||
children: [
|
||||
const RupIcon(RupIconName.check, size: 16, stroke: 2.6, color: AppColors.muted),
|
||||
const SizedBox(width: 9),
|
||||
Expanded(
|
||||
child: Text(
|
||||
text,
|
||||
style: Theme.of(context).textTheme.bodyMedium!.copyWith(
|
||||
fontWeight: FontWeight.w700,
|
||||
color: AppColors.muted,
|
||||
height: 1.3,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// 오늘 할 일 진행도 — "오늘 할 일 [■■□] n/m 완료"
|
||||
class _TodayProgress extends StatelessWidget {
|
||||
const _TodayProgress({required this.done, required this.total});
|
||||
|
||||
final int done;
|
||||
final int total;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final allDone = total > 0 && done == total;
|
||||
final label = Theme.of(context).textTheme.bodySmall!.copyWith(
|
||||
fontSize: 12.5,
|
||||
fontWeight: FontWeight.w700,
|
||||
color: AppColors.muted,
|
||||
height: 1.2,
|
||||
);
|
||||
return Container(
|
||||
padding: const EdgeInsets.symmetric(horizontal: AppSpacing.lg, vertical: 13),
|
||||
decoration: const BoxDecoration(
|
||||
border: Border(bottom: BorderSide(color: AppColors.line2)),
|
||||
),
|
||||
child: Row(
|
||||
children: [
|
||||
Text('오늘 할 일', style: label),
|
||||
const SizedBox(width: 11),
|
||||
Expanded(
|
||||
child: ClipRRect(
|
||||
borderRadius: BorderRadius.circular(6),
|
||||
child: LinearProgressIndicator(
|
||||
value: total == 0 ? 0 : done / total,
|
||||
minHeight: 6,
|
||||
backgroundColor: AppColors.line2,
|
||||
color: AppColors.primary,
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 11),
|
||||
Text(
|
||||
'$done/$total 완료',
|
||||
style: label.copyWith(
|
||||
fontWeight: FontWeight.w800,
|
||||
color: allDone ? AppColors.accent : AppColors.muted,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,107 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_svg/flutter_svg.dart';
|
||||
import 'package:tma_app/core/constants/app_assets.dart';
|
||||
import 'package:tma_app/core/constants/app_colors.dart';
|
||||
import 'package:tma_app/core/constants/app_sizes.dart';
|
||||
import 'package:tma_app/core/theme/app_typography.dart';
|
||||
import 'package:tma_app/shared/widgets/dashed_border_box.dart';
|
||||
import 'package:tma_app/shared/widgets/icons/rup_icon.dart';
|
||||
import 'package:tma_app/shared/widgets/pressable.dart';
|
||||
|
||||
/// 빈 명함 슬롯 치수 — 시안 .rup-namecard-empty
|
||||
const double _kMinHeight = 193;
|
||||
const double _kRadius = 18;
|
||||
|
||||
/// 아직 펫 명함이 없는 아이 — 이름 + 안내 + "명함 만들기"
|
||||
class NameCardEmpty extends StatelessWidget {
|
||||
const NameCardEmpty({super.key, required this.petName, required this.onCreate});
|
||||
|
||||
final String petName;
|
||||
final VoidCallback onCreate;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final textTheme = Theme.of(context).textTheme;
|
||||
return Semantics(
|
||||
button: true,
|
||||
label: '$petName의 펫 명함 만들기',
|
||||
child: MouseRegion(
|
||||
cursor: SystemMouseCursors.click,
|
||||
child: GestureDetector(
|
||||
onTap: onCreate,
|
||||
child: DashedBorderBox(
|
||||
color: AppColors.emptyCardBorder,
|
||||
radius: _kRadius,
|
||||
backgroundColor: AppColors.emptyCardBg,
|
||||
child: ConstrainedBox(
|
||||
constraints: const BoxConstraints(minHeight: _kMinHeight, minWidth: double.infinity),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 22, vertical: 18),
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
SvgPicture.asset(
|
||||
AppAssets.addCard,
|
||||
width: 40,
|
||||
height: 40,
|
||||
colorFilter: const ColorFilter.mode(AppColors.emptyCardIcon, BlendMode.srcIn),
|
||||
),
|
||||
const SizedBox(height: 9),
|
||||
Text.rich(
|
||||
TextSpan(children: [
|
||||
TextSpan(
|
||||
text: petName,
|
||||
style: const TextStyle(color: AppColors.primary600),
|
||||
),
|
||||
const TextSpan(text: '의 펫 명함이 아직 없어요'),
|
||||
]),
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
style: textTheme.titleSmall!.copyWith(fontWeight: FontWeight.w600),
|
||||
),
|
||||
const SizedBox(height: 5),
|
||||
Text(
|
||||
'등록한 정보로 나만의 명함을 만들어 보세요',
|
||||
textAlign: TextAlign.center,
|
||||
style: textTheme.bodySmall!.copyWith(
|
||||
fontSize: 12.5,
|
||||
fontWeight: FontWeight.w500,
|
||||
color: AppColors.muted,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 15),
|
||||
Pressable(
|
||||
onTap: onCreate,
|
||||
pressedScale: .96,
|
||||
child: Container(
|
||||
width: double.infinity,
|
||||
padding: const EdgeInsets.symmetric(horizontal: AppSpacing.xxl, vertical: 12),
|
||||
decoration: BoxDecoration(
|
||||
color: AppColors.surface2,
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
border: Border.all(color: AppColors.emptyCardButtonBorder, width: 1.5),
|
||||
),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
const RupIcon(RupIconName.plus, size: 17, stroke: 2.2, color: AppColors.primary600),
|
||||
const SizedBox(width: 7),
|
||||
Text(
|
||||
'명함 만들기',
|
||||
style: AppTypography.button(13.5, color: AppColors.primary600)
|
||||
.copyWith(height: 1),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user