|
|
@ -8,7 +8,7 @@ import 'package:flutter_module_books/api.dart';
|
|
|
|
void main() => runApp(const MyApp());
|
|
|
|
void main() => runApp(const MyApp());
|
|
|
|
|
|
|
|
|
|
|
|
class MyApp extends StatelessWidget {
|
|
|
|
class MyApp extends StatelessWidget {
|
|
|
|
const MyApp({Key key}) : super(key: key);
|
|
|
|
const MyApp({Key? key}) : super(key: key);
|
|
|
|
|
|
|
|
|
|
|
|
@override
|
|
|
|
@override
|
|
|
|
Widget build(BuildContext context) {
|
|
|
|
Widget build(BuildContext context) {
|
|
|
@ -30,30 +30,28 @@ class FlutterBookApiHandler extends FlutterBookApi {
|
|
|
|
|
|
|
|
|
|
|
|
@override
|
|
|
|
@override
|
|
|
|
void displayBookDetails(Book book) {
|
|
|
|
void displayBookDetails(Book book) {
|
|
|
|
assert(
|
|
|
|
|
|
|
|
book != null,
|
|
|
|
|
|
|
|
'Non-null book expected from FlutterBookApi.displayBookDetails call.',
|
|
|
|
|
|
|
|
);
|
|
|
|
|
|
|
|
callback(book);
|
|
|
|
callback(book);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
class BookDetail extends StatefulWidget {
|
|
|
|
class BookDetail extends StatefulWidget {
|
|
|
|
const BookDetail({this.hostApi, this.flutterApi, Key key}) : super(key: key);
|
|
|
|
const BookDetail({Key? key, this.hostApi, this.flutterApi, this.book})
|
|
|
|
|
|
|
|
: super(key: key);
|
|
|
|
|
|
|
|
|
|
|
|
// These are the outgoing and incoming APIs that are here for injection for
|
|
|
|
// These are the outgoing and incoming APIs that are here for injection for
|
|
|
|
// tests.
|
|
|
|
// tests.
|
|
|
|
final HostBookApi hostApi;
|
|
|
|
final HostBookApi? hostApi;
|
|
|
|
final FlutterBookApi flutterApi;
|
|
|
|
final FlutterBookApi? flutterApi;
|
|
|
|
|
|
|
|
final Book? book;
|
|
|
|
|
|
|
|
|
|
|
|
@override
|
|
|
|
@override
|
|
|
|
_BookDetailState createState() => _BookDetailState();
|
|
|
|
State<BookDetail> createState() => _BookDetailState();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
class _BookDetailState extends State<BookDetail> {
|
|
|
|
class _BookDetailState extends State<BookDetail> {
|
|
|
|
Book book;
|
|
|
|
Book? book;
|
|
|
|
|
|
|
|
|
|
|
|
HostBookApi hostApi;
|
|
|
|
late HostBookApi hostApi;
|
|
|
|
|
|
|
|
|
|
|
|
FocusNode textFocusNode = FocusNode();
|
|
|
|
FocusNode textFocusNode = FocusNode();
|
|
|
|
TextEditingController titleTextController = TextEditingController();
|
|
|
|
TextEditingController titleTextController = TextEditingController();
|
|
|
@ -63,6 +61,7 @@ class _BookDetailState extends State<BookDetail> {
|
|
|
|
@override
|
|
|
|
@override
|
|
|
|
void initState() {
|
|
|
|
void initState() {
|
|
|
|
super.initState();
|
|
|
|
super.initState();
|
|
|
|
|
|
|
|
book = widget.book;
|
|
|
|
|
|
|
|
|
|
|
|
// This `HostBookApi` class instance lets us make outgoing calls to the
|
|
|
|
// This `HostBookApi` class instance lets us make outgoing calls to the
|
|
|
|
// platform.
|
|
|
|
// platform.
|
|
|
@ -80,19 +79,19 @@ class _BookDetailState extends State<BookDetail> {
|
|
|
|
// This book model is what we're going to return to Kotlin eventually.
|
|
|
|
// This book model is what we're going to return to Kotlin eventually.
|
|
|
|
// Keep it bound to the UI.
|
|
|
|
// Keep it bound to the UI.
|
|
|
|
this.book = book;
|
|
|
|
this.book = book;
|
|
|
|
titleTextController.text = book.title;
|
|
|
|
titleTextController.text = book.title ?? '';
|
|
|
|
titleTextController.addListener(() {
|
|
|
|
titleTextController.addListener(() {
|
|
|
|
this.book?.title = titleTextController.text;
|
|
|
|
this.book!.title = titleTextController.text;
|
|
|
|
});
|
|
|
|
});
|
|
|
|
// Subtitle could be null.
|
|
|
|
// Subtitle could be null.
|
|
|
|
// TODO(gaaclarke): https://github.com/flutter/flutter/issues/59118.
|
|
|
|
// TODO(gaaclarke): https://github.com/flutter/flutter/issues/59118.
|
|
|
|
subtitleTextController.text = book.subtitle ?? '';
|
|
|
|
subtitleTextController.text = book.subtitle ?? '';
|
|
|
|
subtitleTextController.addListener(() {
|
|
|
|
subtitleTextController.addListener(() {
|
|
|
|
this.book?.subtitle = subtitleTextController.text;
|
|
|
|
this.book!.subtitle = subtitleTextController.text;
|
|
|
|
});
|
|
|
|
});
|
|
|
|
authorTextController.text = book.author;
|
|
|
|
authorTextController.text = book.author ?? '';
|
|
|
|
authorTextController.addListener(() {
|
|
|
|
authorTextController.addListener(() {
|
|
|
|
this.book?.author = authorTextController.text;
|
|
|
|
this.book!.author = authorTextController.text;
|
|
|
|
});
|
|
|
|
});
|
|
|
|
});
|
|
|
|
});
|
|
|
|
}));
|
|
|
|
}));
|
|
|
@ -123,10 +122,12 @@ class _BookDetailState extends State<BookDetail> {
|
|
|
|
IconButton(
|
|
|
|
IconButton(
|
|
|
|
icon: const Icon(Icons.check),
|
|
|
|
icon: const Icon(Icons.check),
|
|
|
|
// Pressing save sends the updated book to the platform.
|
|
|
|
// Pressing save sends the updated book to the platform.
|
|
|
|
onPressed: () {
|
|
|
|
onPressed: book != null
|
|
|
|
hostApi.finishEditingBook(book);
|
|
|
|
? () {
|
|
|
|
clear();
|
|
|
|
hostApi.finishEditingBook(book!);
|
|
|
|
},
|
|
|
|
clear();
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
: null,
|
|
|
|
),
|
|
|
|
),
|
|
|
|
],
|
|
|
|
],
|
|
|
|
),
|
|
|
|
),
|
|
|
@ -134,70 +135,98 @@ class _BookDetailState extends State<BookDetail> {
|
|
|
|
// Draw a spinner until the platform gives us the book to show details
|
|
|
|
// Draw a spinner until the platform gives us the book to show details
|
|
|
|
// for.
|
|
|
|
// for.
|
|
|
|
? const Center(child: CircularProgressIndicator())
|
|
|
|
? const Center(child: CircularProgressIndicator())
|
|
|
|
: Focus(
|
|
|
|
: BookForm(
|
|
|
|
|
|
|
|
book: book!,
|
|
|
|
focusNode: textFocusNode,
|
|
|
|
focusNode: textFocusNode,
|
|
|
|
child: ListView(
|
|
|
|
authorTextController: authorTextController,
|
|
|
|
padding: const EdgeInsets.all(24),
|
|
|
|
subtitleTextController: subtitleTextController,
|
|
|
|
children: [
|
|
|
|
titleTextController: titleTextController,
|
|
|
|
TextField(
|
|
|
|
),
|
|
|
|
controller: titleTextController,
|
|
|
|
);
|
|
|
|
decoration: const InputDecoration(
|
|
|
|
}
|
|
|
|
border: OutlineInputBorder(),
|
|
|
|
}
|
|
|
|
filled: true,
|
|
|
|
|
|
|
|
hintText: "Title",
|
|
|
|
class BookForm extends StatelessWidget {
|
|
|
|
labelText: "Title",
|
|
|
|
const BookForm({
|
|
|
|
),
|
|
|
|
Key? key,
|
|
|
|
),
|
|
|
|
required this.book,
|
|
|
|
const SizedBox(height: 24),
|
|
|
|
required this.focusNode,
|
|
|
|
TextField(
|
|
|
|
required this.authorTextController,
|
|
|
|
controller: subtitleTextController,
|
|
|
|
required this.subtitleTextController,
|
|
|
|
maxLines: 2,
|
|
|
|
required this.titleTextController,
|
|
|
|
decoration: const InputDecoration(
|
|
|
|
}) : super(key: key);
|
|
|
|
border: OutlineInputBorder(),
|
|
|
|
|
|
|
|
filled: true,
|
|
|
|
final Book book;
|
|
|
|
hintText: "Subtitle",
|
|
|
|
final FocusNode focusNode;
|
|
|
|
labelText: "Subtitle",
|
|
|
|
final TextEditingController titleTextController;
|
|
|
|
),
|
|
|
|
final TextEditingController subtitleTextController;
|
|
|
|
),
|
|
|
|
final TextEditingController authorTextController;
|
|
|
|
const SizedBox(height: 24),
|
|
|
|
|
|
|
|
TextField(
|
|
|
|
@override
|
|
|
|
controller: authorTextController,
|
|
|
|
Widget build(BuildContext context) {
|
|
|
|
decoration: const InputDecoration(
|
|
|
|
return Focus(
|
|
|
|
border: OutlineInputBorder(),
|
|
|
|
focusNode: focusNode,
|
|
|
|
filled: true,
|
|
|
|
child: ListView(
|
|
|
|
hintText: "Author",
|
|
|
|
padding: const EdgeInsets.all(24),
|
|
|
|
labelText: "Author",
|
|
|
|
children: [
|
|
|
|
),
|
|
|
|
TextField(
|
|
|
|
),
|
|
|
|
controller: titleTextController,
|
|
|
|
const SizedBox(height: 32),
|
|
|
|
decoration: const InputDecoration(
|
|
|
|
const Divider(),
|
|
|
|
border: OutlineInputBorder(),
|
|
|
|
Center(
|
|
|
|
filled: true,
|
|
|
|
child: Padding(
|
|
|
|
hintText: "Title",
|
|
|
|
padding: const EdgeInsets.all(8.0),
|
|
|
|
labelText: "Title",
|
|
|
|
child: Text(
|
|
|
|
),
|
|
|
|
'${book.pageCount} pages ~ published ${book.publishDate}'),
|
|
|
|
),
|
|
|
|
),
|
|
|
|
const SizedBox(height: 24),
|
|
|
|
),
|
|
|
|
TextField(
|
|
|
|
const Divider(),
|
|
|
|
controller: subtitleTextController,
|
|
|
|
const SizedBox(height: 32),
|
|
|
|
maxLines: 2,
|
|
|
|
const Center(
|
|
|
|
decoration: const InputDecoration(
|
|
|
|
child: Text(
|
|
|
|
border: OutlineInputBorder(),
|
|
|
|
'BOOK DESCRIPTION',
|
|
|
|
filled: true,
|
|
|
|
style: TextStyle(
|
|
|
|
hintText: "Subtitle",
|
|
|
|
fontSize: 15,
|
|
|
|
labelText: "Subtitle",
|
|
|
|
fontWeight: FontWeight.bold,
|
|
|
|
),
|
|
|
|
decoration: TextDecoration.underline,
|
|
|
|
),
|
|
|
|
),
|
|
|
|
const SizedBox(height: 24),
|
|
|
|
),
|
|
|
|
TextField(
|
|
|
|
),
|
|
|
|
controller: authorTextController,
|
|
|
|
const SizedBox(height: 12),
|
|
|
|
decoration: const InputDecoration(
|
|
|
|
Text(
|
|
|
|
border: OutlineInputBorder(),
|
|
|
|
book.summary,
|
|
|
|
filled: true,
|
|
|
|
style: TextStyle(color: Colors.grey.shade600, height: 1.24),
|
|
|
|
hintText: "Author",
|
|
|
|
),
|
|
|
|
labelText: "Author",
|
|
|
|
],
|
|
|
|
),
|
|
|
|
|
|
|
|
),
|
|
|
|
|
|
|
|
const SizedBox(height: 32),
|
|
|
|
|
|
|
|
const Divider(),
|
|
|
|
|
|
|
|
Center(
|
|
|
|
|
|
|
|
child: Padding(
|
|
|
|
|
|
|
|
padding: const EdgeInsets.all(8.0),
|
|
|
|
|
|
|
|
child: Text(
|
|
|
|
|
|
|
|
'${book.pageCount} pages ~ published ${book.publishDate}'),
|
|
|
|
|
|
|
|
),
|
|
|
|
|
|
|
|
),
|
|
|
|
|
|
|
|
const Divider(),
|
|
|
|
|
|
|
|
const SizedBox(height: 32),
|
|
|
|
|
|
|
|
const Center(
|
|
|
|
|
|
|
|
child: Text(
|
|
|
|
|
|
|
|
'BOOK DESCRIPTION',
|
|
|
|
|
|
|
|
style: TextStyle(
|
|
|
|
|
|
|
|
fontSize: 15,
|
|
|
|
|
|
|
|
fontWeight: FontWeight.bold,
|
|
|
|
|
|
|
|
decoration: TextDecoration.underline,
|
|
|
|
),
|
|
|
|
),
|
|
|
|
),
|
|
|
|
),
|
|
|
|
|
|
|
|
),
|
|
|
|
|
|
|
|
const SizedBox(height: 12),
|
|
|
|
|
|
|
|
Text(
|
|
|
|
|
|
|
|
book.summary ?? '',
|
|
|
|
|
|
|
|
style: TextStyle(color: Colors.grey.shade600, height: 1.24),
|
|
|
|
|
|
|
|
),
|
|
|
|
|
|
|
|
],
|
|
|
|
|
|
|
|
),
|
|
|
|
);
|
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|