Add Flutter app skeleton

This commit is contained in:
Codex
2026-07-02 10:44:29 +09:00
parent 0e26060de7
commit 86a61939af
74 changed files with 2670 additions and 1 deletions
+20
View File
@@ -0,0 +1,20 @@
import 'package:flutter/material.dart';
import 'core/router/app_router.dart';
class Tdc114PlusApp extends StatelessWidget {
const Tdc114PlusApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp.router(
title: 'tdc114plus',
debugShowCheckedModeBanner: false,
theme: ThemeData(
colorScheme: ColorScheme.fromSeed(seedColor: const Color(0xFF006D77)),
useMaterial3: true,
),
routerConfig: appRouter,
);
}
}
@@ -0,0 +1,22 @@
class AppEnvironment {
const AppEnvironment({
required this.ssoBaseUrl,
required this.orgFrontBaseUrl,
});
factory AppEnvironment.fromDartDefine() {
return const AppEnvironment(
ssoBaseUrl: String.fromEnvironment(
'SSO_BASE_URL',
defaultValue: 'https://sso.example.invalid',
),
orgFrontBaseUrl: String.fromEnvironment(
'ORGFRONT_BASE_URL',
defaultValue: 'https://orgfront.example.invalid',
),
);
}
final String ssoBaseUrl;
final String orgFrontBaseUrl;
}
+18
View File
@@ -0,0 +1,18 @@
import 'package:go_router/go_router.dart';
import '../../features/auth/presentation/login_screen.dart';
import '../../features/directory/presentation/directory_screen.dart';
final appRouter = GoRouter(
initialLocation: LoginScreen.routePath,
routes: [
GoRoute(
path: LoginScreen.routePath,
builder: (context, state) => const LoginScreen(),
),
GoRoute(
path: DirectoryScreen.routePath,
builder: (context, state) => const DirectoryScreen(),
),
],
);
@@ -0,0 +1,72 @@
import 'package:flutter/material.dart';
import 'package:go_router/go_router.dart';
import '../../directory/presentation/directory_screen.dart';
class LoginScreen extends StatefulWidget {
const LoginScreen({super.key});
static const routePath = '/login';
@override
State<LoginScreen> createState() => _LoginScreenState();
}
class _LoginScreenState extends State<LoginScreen> {
final _phoneController = TextEditingController();
@override
void dispose() {
_phoneController.dispose();
super.dispose();
}
void _submit() {
if (_phoneController.text.trim().isEmpty) {
return;
}
context.go(DirectoryScreen.routePath);
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('tdc114plus')),
body: SafeArea(
child: ListView(
padding: const EdgeInsets.all(24),
children: [
const SizedBox(height: 32),
Text(
'Baron SSO 로그인',
style: Theme.of(context).textTheme.headlineSmall,
),
const SizedBox(height: 8),
Text(
'Baron SSO에 등록된 전화번호를 입력하세요.',
style: Theme.of(context).textTheme.bodyMedium,
),
const SizedBox(height: 24),
TextField(
controller: _phoneController,
keyboardType: TextInputType.phone,
textInputAction: TextInputAction.done,
decoration: const InputDecoration(
border: OutlineInputBorder(),
labelText: '전화번호',
prefixIcon: Icon(Icons.phone_android),
),
onSubmitted: (_) => _submit(),
),
const SizedBox(height: 16),
FilledButton.icon(
onPressed: _submit,
icon: const Icon(Icons.login),
label: const Text('로그인'),
),
],
),
),
);
}
}
@@ -0,0 +1,81 @@
import 'package:flutter/material.dart';
class DirectoryScreen extends StatelessWidget {
const DirectoryScreen({super.key});
static const routePath = '/directory';
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('직원검색')),
body: SafeArea(
child: ListView(
padding: const EdgeInsets.all(16),
children: const [
_SearchField(),
SizedBox(height: 16),
_FeatureTile(
icon: Icons.manage_search,
title: '직원검색',
subtitle: '이름, 부서, 직위로 직원을 검색합니다.',
),
_FeatureTile(
icon: Icons.phone,
title: '전화번호검색',
subtitle: '전화번호로 직원을 검색합니다.',
),
_FeatureTile(
icon: Icons.account_tree,
title: '조직도',
subtitle: '가족사와 부서 기준으로 직원을 탐색합니다.',
),
_FeatureTile(
icon: Icons.star,
title: '즐겨찾기',
subtitle: '자주 연락하는 직원을 저장합니다.',
),
],
),
),
);
}
}
class _SearchField extends StatelessWidget {
const _SearchField();
@override
Widget build(BuildContext context) {
return const TextField(
decoration: InputDecoration(
border: OutlineInputBorder(),
hintText: '직원명 또는 전화번호 검색',
prefixIcon: Icon(Icons.search),
),
);
}
}
class _FeatureTile extends StatelessWidget {
const _FeatureTile({
required this.icon,
required this.title,
required this.subtitle,
});
final IconData icon;
final String title;
final String subtitle;
@override
Widget build(BuildContext context) {
return Card(
child: ListTile(
leading: Icon(icon),
title: Text(title),
subtitle: Text(subtitle),
),
);
}
}