chore: bootstrap flutter reader app skeleton

This commit is contained in:
2026-03-23 15:57:38 +07:00
parent f5e7813548
commit 4f202936fa
150 changed files with 6278 additions and 0 deletions
@@ -0,0 +1,19 @@
import 'package:flutter/material.dart';
import '../../../shared/widgets/feature_placeholder.dart';
class LoginScreen extends StatelessWidget {
const LoginScreen({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('Dang nhap')),
body: const FeaturePlaceholder(
title: 'Google Login',
description:
'Khung dang nhap Google OAuth cho mobile auth endpoint. Se bo sung token refresh va secure storage trong phase tiep theo.',
),
);
}
}
@@ -0,0 +1,48 @@
import 'package:flutter/material.dart';
import '../../../shared/widgets/feature_placeholder.dart';
class BookshelfScreen extends StatelessWidget {
const BookshelfScreen({super.key});
@override
Widget build(BuildContext context) {
return DefaultTabController(
length: 4,
child: Scaffold(
appBar: AppBar(
title: const Text('Tu sach'),
bottom: const TabBar(
isScrollable: true,
tabs: [
Tab(text: 'Dang doc'),
Tab(text: 'Danh dau'),
Tab(text: 'Da doc'),
Tab(text: 'De cu'),
],
),
),
body: const TabBarView(
children: [
FeaturePlaceholder(
title: 'Dang doc',
description: 'Danh sach truyện dang doc theo progress sync.',
),
FeaturePlaceholder(
title: 'Danh dau',
description: 'Tat ca truyện da bookmark cua user.',
),
FeaturePlaceholder(
title: 'Da doc',
description: 'Danh sach truyện da hoan thanh.',
),
FeaturePlaceholder(
title: 'De cu',
description: 'Danh sach truyện user da de cu.',
),
],
),
),
);
}
}
@@ -0,0 +1,41 @@
import 'package:flutter/material.dart';
class CommentsScreen extends StatelessWidget {
const CommentsScreen({
super.key,
required this.novelId,
this.chapterId,
});
final String novelId;
final String? chapterId;
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('Binh luan')),
body: ListView(
padding: const EdgeInsets.all(20),
children: [
Text('Novel ID: ${novelId.isEmpty ? '(missing)' : novelId}'),
Text('Chapter ID: ${chapterId ?? '(all novel comments)'}'),
const SizedBox(height: 12),
const Text('Khung danh sach binh luan + form gui comment + phan trang.'),
const SizedBox(height: 20),
TextField(
maxLines: 4,
decoration: const InputDecoration(
hintText: 'Viet binh luan cua ban...',
border: OutlineInputBorder(),
),
),
const SizedBox(height: 12),
FilledButton(
onPressed: () {},
child: const Text('Gui binh luan'),
),
],
),
);
}
}
@@ -0,0 +1,19 @@
import 'package:flutter/material.dart';
import '../../../shared/widgets/feature_placeholder.dart';
class GenresScreen extends StatelessWidget {
const GenresScreen({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('The loai')),
body: const FeaturePlaceholder(
title: 'Genre Discovery',
description:
'Khung danh sach the loai va man hinh truyện theo the loai slug de dong bo hanh vi voi web.',
),
);
}
}
@@ -0,0 +1,27 @@
import 'package:flutter/material.dart';
import 'package:go_router/go_router.dart';
import '../../../app/router/route_names.dart';
import '../../../shared/widgets/feature_placeholder.dart';
class HomeScreen extends StatelessWidget {
const HomeScreen({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('Trang chu')),
body: FeaturePlaceholder(
title: 'Home Feed',
description:
'Khung trang chu cho carousel hot, random grid, bang de cu, bang xep hang, truyện moi cap nhat va comments gan day.',
actions: [
FilledButton(
onPressed: () => context.go(RouteNames.search),
child: const Text('Mo tim kiem'),
),
],
),
);
}
}
@@ -0,0 +1,43 @@
import 'package:flutter/material.dart';
import 'package:go_router/go_router.dart';
import '../../../app/router/route_names.dart';
class NovelDetailScreen extends StatelessWidget {
const NovelDetailScreen({super.key, required this.novelId});
final String novelId;
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('Chi tiet truyện')),
body: ListView(
padding: const EdgeInsets.all(20),
children: [
Text('Novel ID: ${novelId.isEmpty ? '(missing)' : novelId}'),
const SizedBox(height: 12),
const Text(
'Khung chi tiet truyện: metadata, series, chapter list, rating, bookmark, recommendation, comments.',
),
const SizedBox(height: 20),
Wrap(
spacing: 10,
runSpacing: 10,
children: [
FilledButton(
onPressed: () => context.push('${RouteNames.reader}?chapterId=1'),
child: const Text('Doc chuong 1'),
),
OutlinedButton(
onPressed: () =>
context.push('${RouteNames.comments}?novelId=$novelId'),
child: const Text('Xem binh luan'),
),
],
),
],
),
);
}
}
@@ -0,0 +1,27 @@
import 'package:flutter/material.dart';
import 'package:go_router/go_router.dart';
import '../../../app/router/route_names.dart';
import '../../../shared/widgets/feature_placeholder.dart';
class ProfileScreen extends StatelessWidget {
const ProfileScreen({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('Tai khoan')),
body: FeaturePlaceholder(
title: 'User Profile',
description:
'Khung profile user, thong tin session, thong ke bookmark/de cu va cac cai dat doc dong bo.',
actions: [
FilledButton(
onPressed: () => context.push(RouteNames.settings),
child: const Text('Mo cai dat doc'),
),
],
),
);
}
}
@@ -0,0 +1,72 @@
import 'package:flutter/material.dart';
import 'package:go_router/go_router.dart';
import '../../../app/router/route_names.dart';
class ReaderScreen extends StatefulWidget {
const ReaderScreen({super.key, required this.chapterId});
final String chapterId;
@override
State<ReaderScreen> createState() => _ReaderScreenState();
}
class _ReaderScreenState extends State<ReaderScreen> {
double fontSize = 18;
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Doc chuong ${widget.chapterId.isEmpty ? '?' : widget.chapterId}'),
actions: [
IconButton(
onPressed: () => context.push(RouteNames.settings),
icon: const Icon(Icons.tune),
tooltip: 'Cai dat doc',
),
],
),
body: Padding(
padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Text('Reader body placeholder with TOC, TTS, offline marker.'),
const SizedBox(height: 12),
Text('Co chu hien tai: ${fontSize.toStringAsFixed(0)}'),
Slider(
min: 14,
max: 26,
value: fontSize,
onChanged: (v) => setState(() => fontSize = v),
),
const Spacer(),
Row(
children: [
Expanded(
child: OutlinedButton(
onPressed: () {},
child: const Text('Chuong truoc'),
),
),
const SizedBox(width: 12),
Expanded(
child: FilledButton(
onPressed: () {},
child: const Text('Chuong sau'),
),
),
],
),
],
),
),
floatingActionButton: FloatingActionButton.small(
onPressed: () {},
child: const Icon(Icons.record_voice_over),
),
);
}
}
@@ -0,0 +1,19 @@
import 'package:flutter/material.dart';
import '../../../shared/widgets/feature_placeholder.dart';
class SearchScreen extends StatelessWidget {
const SearchScreen({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('Tim kiem')),
body: const FeaturePlaceholder(
title: 'Search + Filters',
description:
'Khung tim kiem truyện voi goi y theo tu khoa, loc theo the loai/trang thai va sap xep theo views-rating-latest.',
),
);
}
}
@@ -0,0 +1,54 @@
import 'package:flutter/material.dart';
class SettingsScreen extends StatefulWidget {
const SettingsScreen({super.key});
@override
State<SettingsScreen> createState() => _SettingsScreenState();
}
class _SettingsScreenState extends State<SettingsScreen> {
double fontSize = 18;
double lineHeight = 1.8;
double letterSpacing = 0;
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('Cai dat doc')),
body: ListView(
padding: const EdgeInsets.all(20),
children: [
Text('Co chu: ${fontSize.toStringAsFixed(0)}'),
Slider(
min: 14,
max: 26,
value: fontSize,
onChanged: (v) => setState(() => fontSize = v),
),
const SizedBox(height: 12),
Text('Line-height: ${lineHeight.toStringAsFixed(1)}'),
Slider(
min: 1.2,
max: 2.4,
value: lineHeight,
onChanged: (v) => setState(() => lineHeight = v),
),
const SizedBox(height: 12),
Text('Letter-spacing: ${letterSpacing.toStringAsFixed(1)}'),
Slider(
min: -0.5,
max: 2,
value: letterSpacing,
onChanged: (v) => setState(() => letterSpacing = v),
),
const SizedBox(height: 24),
FilledButton(
onPressed: () {},
child: const Text('Luu va dong bo'),
),
],
),
);
}
}
@@ -0,0 +1,38 @@
import 'package:flutter/material.dart';
import 'package:go_router/go_router.dart';
import '../../../app/router/route_names.dart';
class SplashScreen extends StatefulWidget {
const SplashScreen({super.key});
@override
State<SplashScreen> createState() => _SplashScreenState();
}
class _SplashScreenState extends State<SplashScreen> {
@override
void initState() {
super.initState();
Future<void>.delayed(const Duration(milliseconds: 700), () {
if (!mounted) return;
context.go(RouteNames.home);
});
}
@override
Widget build(BuildContext context) {
return const Scaffold(
body: Center(
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Icon(Icons.menu_book_rounded, size: 48),
SizedBox(height: 12),
Text('Reader App'),
],
),
),
);
}
}