diff --git a/lib/add_book_page/add_book_page.dart b/lib/add_book_page/add_book_page.dart new file mode 100644 index 0000000..925f082 --- /dev/null +++ b/lib/add_book_page/add_book_page.dart @@ -0,0 +1,113 @@ +//import 'package:book_store_app/bookmodel.dart'; +import 'package:book_store_app/add_book_page/mytextfield2.dart'; +import 'package:book_store_app/bookmodel.dart'; +import 'package:flutter/material.dart'; + +import 'app_bar2.dart'; + +class AddBook extends StatefulWidget { + const AddBook({ + Key? key, + }) : super(key: key); + + @override + State createState() => _AddBookState(); +} + +class _AddBookState extends State { + final book_name = TextEditingController(); + final author_name = TextEditingController(); + final price = TextEditingController(); + final description = TextEditingController(); + final image_link = TextEditingController(); + + @override + Widget build(BuildContext context) { + return SafeArea( + child: Scaffold( + backgroundColor: Color.fromARGB(255, 245, 245, 245), + body: Padding( + padding: const EdgeInsets.all(20), + child: Column( + children: [ + MyAppBar2(), + Padding( + padding: const EdgeInsets.symmetric(horizontal: 15), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisAlignment: MainAxisAlignment.center, + children: [ + const Padding( + padding: EdgeInsets.only(bottom: 20), + child: Text( + 'Add Book', + style: TextStyle( + fontSize: 24, + fontWeight: FontWeight.bold, + color: Colors.black, + ), + ), + ), + MyTextField2( + hint: 'Book Name..', + maxlines: 1, + mycontroller: book_name, + ), + MyTextField2( + hint: 'Author Name..', + maxlines: 1, + mycontroller: author_name, + ), + MyTextField2( + hint: 'Book Price..', + maxlines: 1, + mycontroller: price, + ), + MyTextField2( + hint: 'Image Link..', + maxlines: 1, + mycontroller: image_link, + ), + MyTextField2( + hint: 'Book Description..', + maxlines: 5, + mycontroller: description, + ), + TextButton( + onPressed: () { + Book book = Book( + book_name.text, + author_name.text, + description.text, + price.text, + image_link.text, + ); + Book.books.add(book); + }, + child: Container( + width: 335, + height: 60, + margin: EdgeInsets.symmetric(vertical: 20), + padding: EdgeInsets.all(15), + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(20), + color: Colors.black), + child: const Text( + 'Add', + textAlign: TextAlign.center, + style: TextStyle( + fontSize: 18, + color: Colors.white, + fontWeight: FontWeight.bold), + ), + ), + ) + ], + ), + ) + ], + ), + )), + ); + } +} diff --git a/lib/add_book_page/app_bar2.dart b/lib/add_book_page/app_bar2.dart new file mode 100644 index 0000000..655e8c5 --- /dev/null +++ b/lib/add_book_page/app_bar2.dart @@ -0,0 +1,30 @@ +import 'package:flutter/material.dart'; + +class MyAppBar2 extends StatelessWidget { + const MyAppBar2({ + Key? key, + }) : super(key: key); + + @override + Widget build(BuildContext context) { + return Padding( + padding: const EdgeInsets.symmetric(vertical: 20), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + TextButton( + onPressed: () { + Navigator.pop(context); + }, + child: Icon(Icons.arrow_back_ios, size: 24, color: Colors.black), + ), + //Expanded(child: SizedBox()), + TextButton( + onPressed: () {}, + child: Icon(Icons.more_vert, size: 26, color: Colors.black)) + ], + ), + ); + } +} diff --git a/lib/add_book_page/mytextfield2.dart b/lib/add_book_page/mytextfield2.dart new file mode 100644 index 0000000..1fcd723 --- /dev/null +++ b/lib/add_book_page/mytextfield2.dart @@ -0,0 +1,48 @@ +import 'package:flutter/material.dart'; +import 'package:get/get.dart'; + +class MyTextField2 extends StatefulWidget { + const MyTextField2({ + Key? key, + required this.hint, + required this.maxlines, + required this.mycontroller, + }) : super(key: key); + final String hint; + final int maxlines; + final TextEditingController mycontroller; + + @override + State createState() => _MyTextField2State(); +} + +class _MyTextField2State extends State { + @override + Widget build(BuildContext context) { + return Padding( + padding: const EdgeInsets.symmetric(vertical: 15), + child: TextField( + controller: widget.mycontroller, + onSubmitted: (x) { + widget.mycontroller.text = x; + setState(() {}); + }, + style: const TextStyle(fontSize: 20), + maxLines: widget.maxlines, + decoration: InputDecoration( + filled: true, + fillColor: Colors.white, + hintText: widget.hint, + enabledBorder: OutlineInputBorder( + borderRadius: BorderRadius.circular(20), + borderSide: BorderSide.none, + ), + focusedBorder: OutlineInputBorder( + borderRadius: BorderRadius.circular(20), + borderSide: BorderSide.none, + ), + ), + ), + ); + } +} diff --git a/lib/book_details/MyButtons.dart b/lib/book_details/MyButtons.dart new file mode 100644 index 0000000..99074b7 --- /dev/null +++ b/lib/book_details/MyButtons.dart @@ -0,0 +1,45 @@ +import 'package:flutter/material.dart'; + +class MyButton extends StatelessWidget { + const MyButton({ + Key? key, + required this.icon, + required this.text, + }) : super(key: key); + final IconData icon; + final String text; + @override + Widget build(BuildContext context) { + return Container( + width: 154, + height: 40, + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(15), color: Colors.white), + child: Align( + alignment: Alignment.center, + child: Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Icon( + icon, + size: 24, + color: Colors.black, + ), + SizedBox( + width: 8, + ), + Text( + text, + style: TextStyle( + fontSize: 18, + fontWeight: FontWeight.bold, + color: Colors.black), + ) + ], + ), + ), + ); + } +} +/* +*/ \ No newline at end of file diff --git a/lib/book_details/book_details.dart b/lib/book_details/book_details.dart new file mode 100644 index 0000000..2f44147 --- /dev/null +++ b/lib/book_details/book_details.dart @@ -0,0 +1,79 @@ +import 'package:book_store_app/bookmodel.dart'; +import 'package:flutter/material.dart'; + +class MyBookDetails extends StatelessWidget { + const MyBookDetails({ + Key? key, + required this.book, + }) : super(key: key); + + final Book book; + + @override + Widget build(BuildContext context) { + return Expanded( + flex: 3, + child: Column( + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Padding( + padding: const EdgeInsets.only(bottom: 10), + child: Text( + book.book_name, + style: const TextStyle( + fontSize: 26, + color: Colors.black, + fontWeight: FontWeight.bold, + ), + ), + ), + Padding( + padding: const EdgeInsets.only(bottom: 10), + child: Text( + book.author, + style: const TextStyle( + fontSize: 16, + color: Colors.grey, + fontWeight: FontWeight.normal, + ), + ), + ), + Padding( + padding: const EdgeInsets.only(bottom: 10), + child: Row(mainAxisAlignment: MainAxisAlignment.center, children: [ + for (var i = 0; i < 4; i++) + const Padding( + padding: EdgeInsets.only(right: 2), + child: Icon(Icons.star, size: 16, color: Colors.orange), + ), + const Icon(Icons.star, size: 16, color: Colors.grey), + const Padding( + padding: EdgeInsets.symmetric(horizontal: 5), + child: Text( + '4.0/5.0', + style: TextStyle( + fontSize: 16, + color: Colors.black, + fontWeight: FontWeight.bold, + ), + ), + ) + ]), + ), + Padding( + padding: const EdgeInsets.only(bottom: 10), + child: Text( + book.description, + textAlign: TextAlign.center, + style: const TextStyle( + fontSize: 16, + color: Colors.grey, + fontWeight: FontWeight.normal, + ), + ), + ), + ], + ), + ); + } +} diff --git a/lib/book_details/book_details_page.dart b/lib/book_details/book_details_page.dart new file mode 100644 index 0000000..e81cba8 --- /dev/null +++ b/lib/book_details/book_details_page.dart @@ -0,0 +1,69 @@ +import 'package:book_store_app/book_details/MyButtons.dart'; +import 'package:book_store_app/book_details/book_details.dart'; +import 'package:book_store_app/book_details/book_image.dart'; +import 'package:book_store_app/bookmodel.dart'; +import 'package:flutter/material.dart'; + +import '../add_book_page/app_bar2.dart'; + +class BookDetails extends StatelessWidget { + const BookDetails({Key? key, required this.book}) : super(key: key); + + final Book book; + + @override + Widget build(BuildContext context) { + return SafeArea( + child: Scaffold( + backgroundColor: Color.fromARGB(255, 245, 245, 245), + body: Padding( + padding: const EdgeInsets.all(20), + child: Column( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + MyAppBar2(), + MyBookImage(book: book), + MyBookDetails(book: book), + Stack(children: [ + Column( + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceAround, + crossAxisAlignment: CrossAxisAlignment.center, + children: const [ + MyButton(icon: Icons.menu_outlined, text: 'Preview'), + MyButton(icon: Icons.comment_outlined, text: 'Reviews') + ], + ), + TextButton( + onPressed: () { + Book.cart_books.add(book); + }, + child: Container( + width: 335, + height: 60, + margin: EdgeInsets.symmetric(vertical: 20), + padding: EdgeInsets.all(15), + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(20), + color: Colors.black), + child: Text( + 'Buy Now for ${book.price}', + textAlign: TextAlign.center, + style: const TextStyle( + fontSize: 18, + color: Colors.white, + fontWeight: FontWeight.bold), + ), + ), + ) + ], + ) + ]) + ], + ), + ), + ), + ); + } +} diff --git a/lib/book_details/book_image.dart b/lib/book_details/book_image.dart new file mode 100644 index 0000000..8516cf0 --- /dev/null +++ b/lib/book_details/book_image.dart @@ -0,0 +1,27 @@ +import 'package:book_store_app/bookmodel.dart'; +import 'package:flutter/material.dart'; + +class MyBookImage extends StatelessWidget { + const MyBookImage({ + Key? key, + required this.book, + }) : super(key: key); + + final Book book; + + @override + Widget build(BuildContext context) { + return Expanded( + flex: 5, + child: Container( + margin: EdgeInsets.symmetric(vertical: 20), + width: 250, + decoration: BoxDecoration( + image: DecorationImage( + image: NetworkImage(book.image_url.toString()), + fit: BoxFit.cover), + borderRadius: BorderRadius.circular(10)), + ), + ); + } +} diff --git a/lib/bookmodel.dart b/lib/bookmodel.dart new file mode 100644 index 0000000..aa9535b --- /dev/null +++ b/lib/bookmodel.dart @@ -0,0 +1,41 @@ +import 'package:get/get.dart'; + +class Book { + final String book_name; + final String author; + final String description; + final String price; + final String image_url; + + //final Image image ; + + Book(this.book_name, this.author, this.description, this.price, + this.image_url); + + static RxList books = [book1, book2, book3].obs; + static RxList cart_books = [].obs; +} + +Book book1 = Book( + 'Littel Women', + 'Louisa May Alcott', + 'Little Women is one of the best loved books of all time. Lovely Meg, talented Jo, frail Beth, spoiled Amy: these are hard lessons of poverty and of growing up in New England during the Civil War. ', + r'$ 5.99', + 'https://i.pinimg.com/originals/df/f9/b1/dff9b167d16a84b32bde5ebc13b22172.jpg', +); + +Book book2 = Book( + 'Littel Men', + 'Louisa May Alcott', + "Little Men, or Life at Plumfield with Jo's Boys, is a children's novel by American author Louisa May Alcott, which was first published in 1871 by Roberts Brothers.", + r'$ 6.99', + 'https://images-na.ssl-images-amazon.com/images/I/51BLMDQGTlL._SX360_BO1,204,203,200_.jpg', +); + +Book book3 = Book( + '1984', + 'George Orwell', + "1984 is a dystopian novella by George Orwell published in 1949, which follows the life of Winston Smith, a low ranking member of 'the Party'.", + r'$ 6.00', + 'https://upload.wikimedia.org/wikipedia/commons/thumb/c/c3/1984first.jpg/400px-1984first.jpg', +); diff --git a/lib/cart_page/cart_page.dart b/lib/cart_page/cart_page.dart new file mode 100644 index 0000000..bf2cdf6 --- /dev/null +++ b/lib/cart_page/cart_page.dart @@ -0,0 +1,54 @@ +import 'package:book_store_app/add_book_page/add_book_page.dart'; +import 'package:book_store_app/bookmodel.dart'; +import 'package:book_store_app/floatingbutton.dart'; +import 'package:book_store_app/home_page/homepage.dart'; +import 'package:book_store_app/mybook.dart'; +import 'package:book_store_app/myicons.dart'; +import 'package:flutter/material.dart'; + +import '../add_book_page/app_bar2.dart'; + +class CartPage extends StatelessWidget { + const CartPage({Key? key}) : super(key: key); + + @override + Widget build(BuildContext context) { + return SafeArea( + child: Scaffold( + backgroundColor: Color.fromARGB(255, 245, 245, 245), + floatingActionButton: SizedBox( + height: 60, + width: 200, + child: MyFloatingButton(), + ), + floatingActionButtonLocation: FloatingActionButtonLocation.centerFloat, + body: Padding( + padding: const EdgeInsets.all(20), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisAlignment: MainAxisAlignment.center, + children: [ + MyAppBar2(), + const Padding( + padding: EdgeInsets.only(left: 15, bottom: 20), + child: Text( + 'Cart', + style: TextStyle( + fontSize: 24, + fontWeight: FontWeight.bold, + color: Colors.black, + ), + ), + ), + Expanded( + child: ListView( + children: + Book.cart_books.map((e) => MyBook(book: e)).toList()), + ), + ], + ), + ), + ), + ); + } +} diff --git a/lib/floatingbutton.dart b/lib/floatingbutton.dart new file mode 100644 index 0000000..b7d71b9 --- /dev/null +++ b/lib/floatingbutton.dart @@ -0,0 +1,41 @@ +import 'package:book_store_app/home_page/homepage.dart'; +import 'package:book_store_app/myicons.dart'; +import 'package:flutter/material.dart'; + +import 'add_book_page/add_book_page.dart'; +import 'cart_page/cart_page.dart'; + +class MyFloatingButton extends StatelessWidget { + const MyFloatingButton({ + Key? key, + }) : super(key: key); + + @override + Widget build(BuildContext context) { + return FloatingActionButton( + backgroundColor: Colors.white, + shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(20)), + child: Row(mainAxisAlignment: MainAxisAlignment.spaceAround, children: [ + TextButton( + onPressed: () { + Navigator.push(context, + MaterialPageRoute(builder: (context) => const HomePage())); + }, + child: MyIcons(icon: Icons.home)), + TextButton( + onPressed: () { + Navigator.push(context, + MaterialPageRoute(builder: (context) => const CartPage())); + }, + child: MyIcons(icon: Icons.shopping_cart_outlined)), + TextButton( + onPressed: () { + Navigator.push(context, + MaterialPageRoute(builder: (context) => const AddBook())); + }, + child: MyIcons(icon: Icons.add)), + ]), + onPressed: () {}, + ); + } +} diff --git a/lib/home_page/homepage.dart b/lib/home_page/homepage.dart new file mode 100644 index 0000000..fa81314 --- /dev/null +++ b/lib/home_page/homepage.dart @@ -0,0 +1,75 @@ +import 'package:book_store_app/add_book_page/add_book_page.dart'; +import 'package:book_store_app/bookmodel.dart'; +import 'package:book_store_app/cart_page/cart_page.dart'; +import 'package:book_store_app/floatingbutton.dart'; +import 'package:book_store_app/mybook.dart'; +import 'package:flutter/material.dart'; +import 'package:get/get.dart'; +import '../myicons.dart'; +import 'myappbar.dart'; +import 'mytextfield.dart'; + +class HomePage extends StatefulWidget { + const HomePage({ + Key? key, + }) : super(key: key); + + @override + State createState() => _HomePageState(); +} + +class _HomePageState extends State { + final TextEditingController myController = TextEditingController(); + RxString typed = ''.obs; + @override + Widget build(BuildContext context) { + return SafeArea( + child: Scaffold( + backgroundColor: Color.fromARGB(255, 245, 245, 245), + floatingActionButton: SizedBox( + height: 60, + width: 200, + child: MyFloatingButton(), + ), + floatingActionButtonLocation: FloatingActionButtonLocation.centerFloat, + body: Padding( + padding: const EdgeInsets.all(20), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + MyAppBar(), + MyTextField( + myController: myController, + typed: typed, + ), + const Padding( + padding: EdgeInsets.only(bottom: 20), + child: Text( + 'Book List', + style: TextStyle( + fontSize: 24, + fontWeight: FontWeight.bold, + color: Colors.black, + ), + ), + ), + Expanded( + child: Obx(() { + return ListView( + children: typed.value.isEmpty + ? Book.books.map((e) => MyBook(book: e)).toList() + : Book.books + .where((e) => e.book_name + .toLowerCase() + .contains(typed.toLowerCase())) + .map((e) => MyBook(book: e)) + .toList()); + }), + ), + ], + ), + ), + ), + ); + } +} diff --git a/lib/home_page/myappbar.dart b/lib/home_page/myappbar.dart new file mode 100644 index 0000000..27bb413 --- /dev/null +++ b/lib/home_page/myappbar.dart @@ -0,0 +1,33 @@ +import 'package:flutter/material.dart'; + +class MyAppBar extends StatelessWidget { + const MyAppBar({ + Key? key, + }) : super(key: key); + + @override + Widget build(BuildContext context) { + return Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Container( + margin: EdgeInsets.only(right: 10), + width: 40, + height: 40, + decoration: BoxDecoration( + image: const DecorationImage( + image: AssetImage('assets/profile.png'), fit: BoxFit.cover), + borderRadius: BorderRadius.circular(10)), + ), + const Expanded( + child: Text( + 'Hi,Zahraa!', + style: TextStyle( + fontSize: 16, fontWeight: FontWeight.bold, color: Colors.black), + ), + ), + Icon(Icons.more_vert, size: 24, color: Colors.black) + ], + ); + } +} diff --git a/lib/home_page/mytextfield.dart b/lib/home_page/mytextfield.dart new file mode 100644 index 0000000..810560b --- /dev/null +++ b/lib/home_page/mytextfield.dart @@ -0,0 +1,50 @@ +import 'package:flutter/material.dart'; +import 'package:get/get.dart'; + +class MyTextField extends StatelessWidget { + const MyTextField({ + Key? key, + required this.myController, + required this.typed, + }) : super(key: key); + + final TextEditingController myController; + final RxString typed; + + @override + Widget build(BuildContext context) { + return Padding( + padding: const EdgeInsets.symmetric(vertical: 20), + child: TextField( + controller: myController, + onChanged: (x) { + typed.value = x; + }, + style: const TextStyle(fontSize: 20), + decoration: InputDecoration( + filled: true, + fillColor: Colors.white, + hintText: 'Search ...', + enabledBorder: OutlineInputBorder( + borderRadius: BorderRadius.circular(20), + borderSide: BorderSide.none, + ), + focusedBorder: OutlineInputBorder( + borderRadius: BorderRadius.circular(20), + borderSide: BorderSide.none, + ), + suffixIcon: TextButton( + child: Obx(() { + return Icon( + Icons.search_rounded, + color: typed.isEmpty ? Colors.grey : Colors.blue, + size: 30, + ); + }), + onPressed: () {}, + ), + ), + ), + ); + } +} diff --git a/lib/main.dart b/lib/main.dart index bcc58f7..608188d 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -1,3 +1,4 @@ +import 'package:book_store_app/home_page/homepage.dart'; import 'package:flutter/material.dart'; void main() { @@ -9,12 +10,11 @@ class MyApp extends StatelessWidget { @override Widget build(BuildContext context) { - return const MaterialApp( - home: Scaffold( - body: Center( - child: Text("Book Store App"), - ), - ), + return MaterialApp( + debugShowCheckedModeBanner: false, + title: 'Book Store', + theme: ThemeData(primaryColor: Colors.blue), + home: HomePage(), ); } } diff --git a/lib/mybook.dart b/lib/mybook.dart new file mode 100644 index 0000000..0b957dc --- /dev/null +++ b/lib/mybook.dart @@ -0,0 +1,86 @@ +import 'package:flutter/material.dart'; + +import 'bookmodel.dart'; +import 'book_details/book_details_page.dart'; + +class MyBook extends StatefulWidget { + const MyBook({Key? key, required this.book}) : super(key: key); + + final Book book; + + @override + State createState() => _MyBookState(); +} + +class _MyBookState extends State { + @override + Widget build(BuildContext context) { + return TextButton( + onPressed: () { + Navigator.push( + context, + MaterialPageRoute( + builder: (context) => BookDetails(book: widget.book))); + }, + child: Padding( + padding: const EdgeInsets.only(bottom: 15), + child: Row( + children: [ + Container( + margin: EdgeInsets.only(right: 10), + width: 72, + height: 105, + decoration: BoxDecoration( + image: DecorationImage( + image: NetworkImage(widget.book.image_url), + fit: BoxFit.cover), + borderRadius: BorderRadius.circular(5), + color: Colors.blue), + ), + Container( + height: 100, + child: Column( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + widget.book.book_name, + style: const TextStyle( + fontSize: 18, + color: Colors.black, + fontWeight: FontWeight.bold, + ), + ), + Text( + widget.book.author, + style: const TextStyle( + fontSize: 14, + color: Colors.grey, + fontWeight: FontWeight.normal, + ), + ), + Text( + widget.book.price, + style: const TextStyle( + fontSize: 16, + color: Colors.black, + fontWeight: FontWeight.bold, + ), + ), + Row(mainAxisAlignment: MainAxisAlignment.start, children: [ + for (var i = 0; i < 4; i++) + const Padding( + padding: EdgeInsets.only(right: 2), + child: Icon(Icons.star, size: 16, color: Colors.orange), + ), + const Icon(Icons.star, size: 16, color: Colors.grey), + ]) + ], + ), + ) + ], + ), + ), + ); + } +} diff --git a/lib/myicons.dart b/lib/myicons.dart new file mode 100644 index 0000000..c2b70a6 --- /dev/null +++ b/lib/myicons.dart @@ -0,0 +1,21 @@ +import 'dart:html'; + +import 'package:flutter/material.dart'; + +class MyIcons extends StatelessWidget { + const MyIcons({ + Key? key, + required this.icon, + }) : super(key: key); + + final IconData icon; + + @override + Widget build(BuildContext context) { + return Icon( + icon, + color: Colors.grey, + size: 30, + ); + } +} diff --git a/pubspec.lock b/pubspec.lock index 7bc8bdd..62f34da 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -74,6 +74,13 @@ packages: description: flutter source: sdk version: "0.0.0" + get: + dependency: "direct main" + description: + name: get + url: "https://pub.dartlang.org" + source: hosted + version: "4.6.5" lints: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index cd0f457..eac2c9f 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -29,6 +29,7 @@ environment: dependencies: flutter: sdk: flutter + get: any # The following adds the Cupertino Icons font to your application. @@ -58,9 +59,12 @@ flutter: uses-material-design: true # To add assets to your application, add an assets section, like this: - # assets: - # - images/a_dot_burr.jpeg - # - images/a_dot_ham.jpeg + assets: + - assets/book1.jpg + - assets/book2.jpg + - assets/profile.png + + # An image asset can refer to one or more resolution-specific "variants", see # https://flutter.dev/assets-and-images/#resolution-aware