diff --git a/README.md b/README.md index a45aff8..384ddae 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,13 @@ # surf_flutter_summer_school_24 Шаблонный репозиторий для Surf Flutter Summer School '24 +![Screenshot_20240724_223550](https://github.com/user-attachments/assets/fa94c98e-a9d6-411a-9a06-b9aaa0d099ff) +![Screenshot_20240724_223810](https://github.com/user-attachments/assets/5a15f69d-4c85-420f-91f8-6defd15e5310) +![Screenshot_20240724_223903](https://github.com/user-attachments/assets/609be02a-7446-43c5-b17c-c344bc3f26ec) +![Screenshot_20240724_223949](https://github.com/user-attachments/assets/da60db58-081d-4d58-b473-c19fa2904b64) +![Screenshot_20240724_224015](https://github.com/user-attachments/assets/46655c2a-39c3-4d94-8a27-1cad28752049) +![Screenshot_20240724_224044](https://github.com/user-attachments/assets/49cc926d-6b77-404a-8b93-fcb94aeb4898) +![Screenshot_20240724_224117](https://github.com/user-attachments/assets/d52cc536-1ffa-4585-8b55-bca0daeaa60c) +![Screenshot_20240724_224314](https://github.com/user-attachments/assets/6f00962d-2a56-4c8c-b696-20c0c2901f2a) +![Screenshot_20240724_224235](https://github.com/user-attachments/assets/e71b2ff2-3bb9-4f86-a861-e9bd69bfec5c) +![Screenshot_20240724_224210](https://github.com/user-attachments/assets/4c673ea4-a26d-4f1b-af6d-24fa7c085487) diff --git a/fonts/Magnolia.ttf b/fonts/Magnolia.ttf new file mode 100644 index 0000000..76d5581 Binary files /dev/null and b/fonts/Magnolia.ttf differ diff --git a/lib/main.dart b/lib/main.dart deleted file mode 100644 index a725658..0000000 --- a/lib/main.dart +++ /dev/null @@ -1,20 +0,0 @@ -import 'package:flutter/material.dart'; - -void main() { - runApp(const MainApp()); -} - -class MainApp extends StatelessWidget { - const MainApp({super.key}); - - @override - Widget build(BuildContext context) { - return const MaterialApp( - home: Scaffold( - body: Center( - child: Text('Hello World!'), - ), - ), - ); - } -} diff --git a/lib/src/feature/photos/data/i_photo_repository.dart b/lib/src/feature/photos/data/i_photo_repository.dart new file mode 100644 index 0000000..ff4a94b --- /dev/null +++ b/lib/src/feature/photos/data/i_photo_repository.dart @@ -0,0 +1,5 @@ +import '../../../models/photo_entity.dart'; + +abstract interface class IPhotoRepository { + Future> getPhotos(); +} \ No newline at end of file diff --git a/lib/src/feature/photos/data/mock_photo_repository.dart b/lib/src/feature/photos/data/mock_photo_repository.dart new file mode 100644 index 0000000..d4bb31f --- /dev/null +++ b/lib/src/feature/photos/data/mock_photo_repository.dart @@ -0,0 +1,130 @@ +import 'package:surf_flutter_summer_school_24/src/feature/photos/data/i_photo_repository.dart'; +import 'package:surf_flutter_summer_school_24/src/models/photo_entity.dart'; + + +class MockPhotoRepository implements IPhotoRepository { + @override + Future> getPhotos() async{ + return [ + const PhotoEntity( + id: '1', + url: 'https://sun9-67.userapi.com/impg/tX4OvfRLP0qjy8pX-D7WGIrPp6tU9Kk5OQErBw/O7cfSBKIhps.jpg?size=1199x1199&quality=95&sign=032b69843ac686317904906820bf4020&type=album' + ), + const PhotoEntity( + id: '2', + url: 'https://sun9-70.userapi.com/impg/5y5vwIq2-aaxiyscdm9XWy3iOw-KDQUPbWoidg/-J_O_1Pnx9Q.jpg?size=1024x1280&quality=96&sign=285af90845367d97da75d3dbceabbea0&type=album' + ), + const PhotoEntity( + id: '3', + url: 'https://sun9-66.userapi.com/impg/c855428/v855428659/1df047/63VkypHBeZo.jpg?size=604x377&quality=96&sign=84e35c2c415bcf6603d8c81269bcfec4&type=album' + ), + const PhotoEntity( + id: '4', + url: 'https://sun9-55.userapi.com/impg/mDIc6RPZmIznA9Uip0tKXeep7gQg5ot0XUUaVw/OyDLqBzPL0A.jpg?size=1024x1280&quality=96&sign=a38727bf7d11a4b1811446972e048f9e&type=album' + ), + const PhotoEntity( + id: '5', + url: 'https://sun9-38.userapi.com/impg/mtqQmWCnxwMDRjqVGEiczyfZ0CqSu7G7e1BpMg/JOfRS5xYG_w.jpg?size=884x884&quality=96&sign=d972f22e5d4b8d82c82323b81655e86e&type=album' + ), + const PhotoEntity( + id: '6', + url: 'https://sun9-25.userapi.com/impg/U1xodQh60uHV1H75doPQtI9lHR-ps4y7pQ7KTQ/vsRSDAx2Ma8.jpg?size=639x641&quality=96&sign=975c4c8ee9bd5e4c3976f211ef239c98&type=album' + ), + const PhotoEntity( + id: '7', + url: 'https://sun9-24.userapi.com/impg/UVP9-Kst1oeGJvfFz6cOgCFRgMacoQrqYeqdZA/s4SRxgafGxk.jpg?size=637x634&quality=96&sign=80155bf77dc4385ccf467379490b1da4&type=album' + ), + const PhotoEntity( + id: '8', + url: 'https://sun9-30.userapi.com/impg/MO4GiY_8B_f3ep-8NusBQWAZOy-QJVTHWyZ2Cw/EJRz0qfPmJE.jpg?size=564x564&quality=96&sign=ef87156b2be6159ab306e12152df3d29&type=album' + ), + const PhotoEntity( + id: '9', + url: 'https://sun9-67.userapi.com/impg/tX4OvfRLP0qjy8pX-D7WGIrPp6tU9Kk5OQErBw/O7cfSBKIhps.jpg?size=1199x1199&quality=95&sign=032b69843ac686317904906820bf4020&type=album' + ), + const PhotoEntity( + id: '10', + url: 'https://sun9-40.userapi.com/impf/c846017/v846017039/c4d2a/_QCZsc09OYE.jpg?size=540x530&quality=96&sign=733f462520a192a9bbf5b716f00c2a2f&type=album' + ), + const PhotoEntity( + id: '11', + url: 'https://sun9-67.userapi.com/impg/tX4OvfRLP0qjy8pX-D7WGIrPp6tU9Kk5OQErBw/O7cfSBKIhps.jpg?size=1199x1199&quality=95&sign=032b69843ac686317904906820bf4020&type=album' + ), + const PhotoEntity( + id: '12', + url: 'https://sun9-70.userapi.com/impg/5y5vwIq2-aaxiyscdm9XWy3iOw-KDQUPbWoidg/-J_O_1Pnx9Q.jpg?size=1024x1280&quality=96&sign=285af90845367d97da75d3dbceabbea0&type=album' + ), + const PhotoEntity( + id: '13', + url: 'https://sun9-66.userapi.com/impg/c855428/v855428659/1df047/63VkypHBeZo.jpg?size=604x377&quality=96&sign=84e35c2c415bcf6603d8c81269bcfec4&type=album' + ), + const PhotoEntity( + id: '14', + url: 'https://sun9-55.userapi.com/impg/mDIc6RPZmIznA9Uip0tKXeep7gQg5ot0XUUaVw/OyDLqBzPL0A.jpg?size=1024x1280&quality=96&sign=a38727bf7d11a4b1811446972e048f9e&type=album' + ), + const PhotoEntity( + id: '15', + url: 'https://sun9-38.userapi.com/impg/mtqQmWCnxwMDRjqVGEiczyfZ0CqSu7G7e1BpMg/JOfRS5xYG_w.jpg?size=884x884&quality=96&sign=d972f22e5d4b8d82c82323b81655e86e&type=album' + ), + const PhotoEntity( + id: '16', + url: 'https://sun9-25.userapi.com/impg/U1xodQh60uHV1H75doPQtI9lHR-ps4y7pQ7KTQ/vsRSDAx2Ma8.jpg?size=639x641&quality=96&sign=975c4c8ee9bd5e4c3976f211ef239c98&type=album' + ), + const PhotoEntity( + id: '17', + url: 'https://sun9-24.userapi.com/impg/UVP9-Kst1oeGJvfFz6cOgCFRgMacoQrqYeqdZA/s4SRxgafGxk.jpg?size=637x634&quality=96&sign=80155bf77dc4385ccf467379490b1da4&type=album' + ), + const PhotoEntity( + id: '18', + url: 'https://sun9-30.userapi.com/impg/MO4GiY_8B_f3ep-8NusBQWAZOy-QJVTHWyZ2Cw/EJRz0qfPmJE.jpg?size=564x564&quality=96&sign=ef87156b2be6159ab306e12152df3d29&type=album' + ), + const PhotoEntity( + id: '19', + url: 'https://sun9-67.userapi.com/impg/tX4OvfRLP0qjy8pX-D7WGIrPp6tU9Kk5OQErBw/O7cfSBKIhps.jpg?size=1199x1199&quality=95&sign=032b69843ac686317904906820bf4020&type=album' + ), + const PhotoEntity( + id: '20', + url: 'https://sun9-40.userapi.com/impf/c846017/v846017039/c4d2a/_QCZsc09OYE.jpg?size=540x530&quality=96&sign=733f462520a192a9bbf5b716f00c2a2f&type=album' + ),const PhotoEntity( + id: '21', + url: 'https://sun9-67.userapi.com/impg/tX4OvfRLP0qjy8pX-D7WGIrPp6tU9Kk5OQErBw/O7cfSBKIhps.jpg?size=1199x1199&quality=95&sign=032b69843ac686317904906820bf4020&type=album' + ), + const PhotoEntity( + id: '22', + url: 'https://sun9-70.userapi.com/impg/5y5vwIq2-aaxiyscdm9XWy3iOw-KDQUPbWoidg/-J_O_1Pnx9Q.jpg?size=1024x1280&quality=96&sign=285af90845367d97da75d3dbceabbea0&type=album' + ), + const PhotoEntity( + id: '23', + url: 'https://sun9-66.userapi.com/impg/c855428/v855428659/1df047/63VkypHBeZo.jpg?size=604x377&quality=96&sign=84e35c2c415bcf6603d8c81269bcfec4&type=album' + ), + const PhotoEntity( + id: '24', + url: 'https://sun9-55.userapi.com/impg/mDIc6RPZmIznA9Uip0tKXeep7gQg5ot0XUUaVw/OyDLqBzPL0A.jpg?size=1024x1280&quality=96&sign=a38727bf7d11a4b1811446972e048f9e&type=album' + ), + const PhotoEntity( + id: '25', + url: 'https://sun9-38.userapi.com/impg/mtqQmWCnxwMDRjqVGEiczyfZ0CqSu7G7e1BpMg/JOfRS5xYG_w.jpg?size=884x884&quality=96&sign=d972f22e5d4b8d82c82323b81655e86e&type=album' + ), + const PhotoEntity( + id: '26', + url: 'https://sun9-25.userapi.com/impg/U1xodQh60uHV1H75doPQtI9lHR-ps4y7pQ7KTQ/vsRSDAx2Ma8.jpg?size=639x641&quality=96&sign=975c4c8ee9bd5e4c3976f211ef239c98&type=album' + ), + const PhotoEntity( + id: '27', + url: 'https://sun9-24.userapi.com/impg/UVP9-Kst1oeGJvfFz6cOgCFRgMacoQrqYeqdZA/s4SRxgafGxk.jpg?size=637x634&quality=96&sign=80155bf77dc4385ccf467379490b1da4&type=album' + ), + const PhotoEntity( + id: '28', + url: 'https://sun9-30.userapi.com/impg/MO4GiY_8B_f3ep-8NusBQWAZOy-QJVTHWyZ2Cw/EJRz0qfPmJE.jpg?size=564x564&quality=96&sign=ef87156b2be6159ab306e12152df3d29&type=album' + ), + const PhotoEntity( + id: '29', + url: 'https://sun9-67.userapi.com/impg/tX4OvfRLP0qjy8pX-D7WGIrPp6tU9Kk5OQErBw/O7cfSBKIhps.jpg?size=1199x1199&quality=95&sign=032b69843ac686317904906820bf4020&type=album' + ), + const PhotoEntity( + id: '30', + url: 'https://sun9-40.userapi.com/impf/c846017/v846017039/c4d2a/_QCZsc09OYE.jpg?size=540x530&quality=96&sign=733f462520a192a9bbf5b716f00c2a2f&type=album' + ), + ]; + } +} \ No newline at end of file diff --git a/lib/src/feature/theme/data/theme_repository.dart b/lib/src/feature/theme/data/theme_repository.dart new file mode 100644 index 0000000..f66096b --- /dev/null +++ b/lib/src/feature/theme/data/theme_repository.dart @@ -0,0 +1,22 @@ +import 'package:surf_flutter_summer_school_24/src/storage/theme/theme_storage.dart'; +import 'package:flutter/material.dart'; + +class ThemeRepository { + final ThemeStorage _themeStorage; + + ThemeRepository({ + required ThemeStorage themeStorage, + }) : _themeStorage = themeStorage; + + Future setThemeMode( + ThemeMode newThemeMode, + ) async { + await _themeStorage.saveThemeMode( + mode: newThemeMode, + ); + } + + ThemeMode? getThemeMode() { + return _themeStorage.getThemeMode(); + } +} \ No newline at end of file diff --git a/lib/src/feature/theme/di/theme_inherited.dart b/lib/src/feature/theme/di/theme_inherited.dart new file mode 100644 index 0000000..1bd3b65 --- /dev/null +++ b/lib/src/feature/theme/di/theme_inherited.dart @@ -0,0 +1,27 @@ +import 'package:flutter/material.dart'; +import '../domain/theme_controller.dart'; + +class ThemeInherited extends InheritedWidget { + const ThemeInherited({ + super.key, + required this.themeController, + required super.child, + }); + + final ThemeController themeController; + + static ThemeController? maybeOf(BuildContext context) { + return context + .dependOnInheritedWidgetOfExactType() + ?.themeController; + } + + static ThemeController of(BuildContext context) { + final ThemeController? result = maybeOf(context); + assert(result != null, 'No MyThemeInherited found in context'); + return result!; + } + + @override + bool updateShouldNotify(ThemeInherited oldWidget) => false; +} \ No newline at end of file diff --git a/lib/src/feature/theme/domain/theme_controller.dart b/lib/src/feature/theme/domain/theme_controller.dart new file mode 100644 index 0000000..91b15af --- /dev/null +++ b/lib/src/feature/theme/domain/theme_controller.dart @@ -0,0 +1,30 @@ +import 'package:surf_flutter_summer_school_24/src/feature/theme/data/theme_repository.dart'; +import 'package:flutter/foundation.dart'; +import 'package:flutter/material.dart'; + +class ThemeController { + final ThemeRepository _themeRepository; + + ThemeController({ + required ThemeRepository themeRepository, + }) : _themeRepository = themeRepository; + + late final ValueNotifier _themeMode = ValueNotifier( + _themeRepository.getThemeMode() ?? ThemeMode.system, + ); + + ValueListenable get themeMode => _themeMode; + + Future setThemeMode(ThemeMode newThemeMode) async { + if (newThemeMode == _themeMode.value) return; + await _themeRepository.setThemeMode(newThemeMode); + _themeMode.value = newThemeMode; + } + + Future switchThemeMode() async { + final newThemeMode = + _themeMode.value != ThemeMode.light ? ThemeMode.light : ThemeMode.dark; + await _themeRepository.setThemeMode(newThemeMode); + _themeMode.value = newThemeMode; + } +} \ No newline at end of file diff --git a/lib/src/feature/theme/ui/theme_builder.dart b/lib/src/feature/theme/ui/theme_builder.dart new file mode 100644 index 0000000..4adf152 --- /dev/null +++ b/lib/src/feature/theme/ui/theme_builder.dart @@ -0,0 +1,37 @@ +import 'package:surf_flutter_summer_school_24/src/feature/theme/di/theme_inherited.dart'; +import 'package:flutter/material.dart'; + +typedef ThemeWidgetBuilder = Widget Function( + BuildContext context, + ThemeMode themeMode, + ); + +class ThemeBuilder extends StatefulWidget { + const ThemeBuilder({ + required this.builder, + super.key, + }); + + final ThemeWidgetBuilder builder; + + @override + State createState() => _ThemeBuilderState(); +} + +class _ThemeBuilderState extends State { + @override + Widget build(BuildContext context) { + return ValueListenableBuilder( + valueListenable: ThemeInherited.of(context).themeMode, + builder: ( + builderContext, + themeMode, + _, + ) => + widget.builder( + builderContext, + themeMode, + ), + ); + } +} \ No newline at end of file diff --git a/lib/src/main.dart b/lib/src/main.dart new file mode 100644 index 0000000..c6c74bf --- /dev/null +++ b/lib/src/main.dart @@ -0,0 +1,50 @@ +import 'package:flutter/material.dart'; +import 'package:shared_preferences/shared_preferences.dart'; +import 'package:surf_flutter_summer_school_24/src/feature/theme/data/theme_repository.dart'; +import 'package:surf_flutter_summer_school_24/src/feature/theme/di/theme_inherited.dart'; +import 'package:surf_flutter_summer_school_24/src/feature/theme/domain/theme_controller.dart'; +import 'package:surf_flutter_summer_school_24/src/feature/theme/ui/theme_builder.dart'; +import 'package:surf_flutter_summer_school_24/src/pages/gallery/gallery_widget.dart'; +import 'package:surf_flutter_summer_school_24/src/storage/theme/theme_storage.dart'; +import 'package:surf_flutter_summer_school_24/src/uikit/theme/theme_data.dart'; + +void main() async { + WidgetsFlutterBinding.ensureInitialized(); + final prefs = await SharedPreferences.getInstance(); + final themeStorage = ThemeStorage( + prefs: prefs, + ); + final themeRepository = ThemeRepository( + themeStorage: themeStorage, + ); + final themeController = ThemeController( + themeRepository: themeRepository, + ); + runApp(MainApp( + themeController: themeController, + )); +} + +class MainApp extends StatelessWidget { + final ThemeController themeController; + + const MainApp({super.key, required this.themeController,}); + + @override + Widget build(BuildContext context) { + return ThemeInherited( + themeController: themeController, + child: ThemeBuilder( + builder: (_, themeMode) { + return MaterialApp( + theme: AppThemeData.lightTheme, + darkTheme: AppThemeData.darkTheme, + themeMode: themeMode, + home: const GalleryWidget() + ); + }, + ) + ); + } +} + diff --git a/lib/src/models/photo_entity.dart b/lib/src/models/photo_entity.dart new file mode 100644 index 0000000..0dd062f --- /dev/null +++ b/lib/src/models/photo_entity.dart @@ -0,0 +1,20 @@ +import 'package:equatable/equatable.dart'; + +final class PhotoEntity extends Equatable { + final String id; + final String url; + final DateTime? createAt; + + const PhotoEntity({ + required this.id, + required this.url, + this.createAt, + }); + + @override + List get props => [ + id, + url, + createAt, + ]; +} \ No newline at end of file diff --git a/lib/src/pages/gallery/gallery_model.dart b/lib/src/pages/gallery/gallery_model.dart new file mode 100644 index 0000000..0d08a2c --- /dev/null +++ b/lib/src/pages/gallery/gallery_model.dart @@ -0,0 +1,16 @@ +import 'package:elementary/elementary.dart'; +import 'package:flutter/material.dart'; +import '../../feature/photos/data/i_photo_repository.dart'; +import '../../models/photo_entity.dart'; + +class GalleryModel extends ElementaryModel { + final IPhotoRepository photoRepository; + final ValueNotifier> images = ValueNotifier([]); // Используем ValueNotifier + + GalleryModel(this.photoRepository); + + Future fetchPhotos() async { + final photos = await photoRepository.getPhotos(); + images.value = photos; // Обновляем ValueNotifier + } +} \ No newline at end of file diff --git a/lib/src/pages/gallery/gallery_widget.dart b/lib/src/pages/gallery/gallery_widget.dart new file mode 100644 index 0000000..939283e --- /dev/null +++ b/lib/src/pages/gallery/gallery_widget.dart @@ -0,0 +1,49 @@ +import 'package:elementary/elementary.dart'; +import 'package:flutter/material.dart'; + +import '../../feature/photos/data/mock_photo_repository.dart'; +import '../../models/photo_entity.dart'; +import 'widgets/gallery_appbar.dart'; +import 'widgets/photo_gallery_container.dart'; +import 'gallery_model.dart'; +import 'gallery_widget_model.dart'; + +class GalleryWidget extends ElementaryWidget { + const GalleryWidget({ + Key? key, + WidgetModelFactory wm = createGalleryWidgetModel, + }) + : super(wm, key: key); + + static GalleryWidgetModel createGalleryWidgetModel(BuildContext context) => + GalleryWidgetModel(GalleryModel(MockPhotoRepository())); + + @override + Widget build(GalleryWidgetModel wm) { + return Scaffold( + appBar: const GalleryAppbar(), + body: ValueListenableBuilder< + List>( // Используем ValueListenableBuilder + valueListenable: wm.model.images, + builder: (context, images, child) { + return GridView.builder( + primary: false, + padding: const EdgeInsets.all(20), + gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount( + crossAxisCount: 3, + crossAxisSpacing: 3, + mainAxisSpacing: 5, + ), + itemCount: images.length, + itemBuilder: (context, index) { + return GestureDetector( + onTap: () => wm.onPhotoTap(index), + child: PhotoGalleryContainer(urlImage: images[index].url), + ); + }, + ); + }, + ), + ); + } +} \ No newline at end of file diff --git a/lib/src/pages/gallery/gallery_widget_model.dart b/lib/src/pages/gallery/gallery_widget_model.dart new file mode 100644 index 0000000..eab271b --- /dev/null +++ b/lib/src/pages/gallery/gallery_widget_model.dart @@ -0,0 +1,28 @@ +import 'package:elementary/elementary.dart'; +import 'package:flutter/material.dart'; + +import '../../feature/photos/data/mock_photo_repository.dart'; +import '../photo_view/photo_view.dart'; +import 'gallery_model.dart'; +import 'gallery_widget.dart'; + +class GalleryWidgetModel extends WidgetModel { + GalleryWidgetModel(GalleryModel model) : super(model); + + @override + void initWidgetModel() { + super.initWidgetModel(); + model.fetchPhotos(); + } + + void onPhotoTap(int index) { + Navigator.of(context).push( + MaterialPageRoute( + builder: (context) => PhotoView( + images: model.images.value, // Получаем значение из ValueNotifier + initialIndex: index, + ), + ), + ); + } +} \ No newline at end of file diff --git a/lib/src/pages/gallery/widgets/gallery_appbar.dart b/lib/src/pages/gallery/widgets/gallery_appbar.dart new file mode 100644 index 0000000..acc76eb --- /dev/null +++ b/lib/src/pages/gallery/widgets/gallery_appbar.dart @@ -0,0 +1,31 @@ +import 'package:flutter/material.dart'; +import 'package:surf_flutter_summer_school_24/src/pages/gallery/widgets/gallery_bottom_sheet.dart'; + +class GalleryAppbar extends StatelessWidget implements PreferredSizeWidget { + const GalleryAppbar({super.key}); + + @override + Widget build(BuildContext context) { + String title = "Постограм"; + return AppBar( + centerTitle: true, + title: Text(title), + actions: [ + IconButton( + onPressed: () { + showModalBottomSheet( + context: context, + builder: (BuildContext context) { + return const GalleryBottomSheet(); + } + ); + }, + icon: const Icon(Icons.more_vert) + ) + ], + ); + } + + @override + Size get preferredSize => const Size.fromHeight(kToolbarHeight); +} \ No newline at end of file diff --git a/lib/src/pages/gallery/widgets/gallery_bottom_sheet.dart b/lib/src/pages/gallery/widgets/gallery_bottom_sheet.dart new file mode 100644 index 0000000..9081ba4 --- /dev/null +++ b/lib/src/pages/gallery/widgets/gallery_bottom_sheet.dart @@ -0,0 +1,66 @@ +import 'package:flutter/material.dart'; +import '../../../feature/theme/di/theme_inherited.dart'; + +class GalleryBottomSheet extends StatelessWidget { + const GalleryBottomSheet({super.key}); + + @override + Widget build(BuildContext context) { + return SizedBox( + height: 160, + child: Center( + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + mainAxisSize: MainAxisSize.min, + children: [ + ElevatedButton( + child: Row( + children: [ + Padding( + padding: const EdgeInsets.only(left: 25.0), + child: Row( + children: [ + Icon( + ThemeInherited.of(context).themeMode.value == ThemeMode.light + ? Icons.wb_sunny_outlined + : Icons.brightness_2_outlined, + ), + const SizedBox(width: 10), + const Text('Тема'), + ], + ), + ), + const Spacer(), + Padding( + padding: const EdgeInsets.only(right: 25.0), + child: Text( + ThemeInherited.of(context).themeMode.value == ThemeMode.light + ? 'Светлая' + : 'Темная', style: TextStyle(color: Theme.of(context).colorScheme.secondary) + ), + ), + ], + ), + onPressed: () { + ThemeInherited.of(context).switchThemeMode(); + }, + ), + ElevatedButton( + child: const Padding( + padding: EdgeInsets.only(left: 25.0), + child: Row( + children: [ + Icon(Icons.cloud_upload_outlined), + SizedBox(width: 10), + Text('Загрузить фото...'), + ], + ), + ), + onPressed: () {}, + ), + ], + ), + ), + ); + } +} diff --git a/lib/src/pages/gallery/widgets/photo_gallery_container.dart b/lib/src/pages/gallery/widgets/photo_gallery_container.dart new file mode 100644 index 0000000..d21b405 --- /dev/null +++ b/lib/src/pages/gallery/widgets/photo_gallery_container.dart @@ -0,0 +1,40 @@ +import 'package:flutter/material.dart'; +import 'package:shimmer/shimmer.dart'; + + +class PhotoGalleryContainer extends StatefulWidget { + final String urlImage; + + const PhotoGalleryContainer({super.key, required this.urlImage}); + + @override + State createState() => _PhotoGalleryContainerState(); +} + +class _PhotoGalleryContainerState extends State { + + + @override + Widget build(BuildContext context) { + return Image.network( + widget.urlImage, + fit: BoxFit.cover, + loadingBuilder: (context, child, loadingProgress) { + if (loadingProgress == null) { + return child; + } + return Shimmer.fromColors( + baseColor: Theme.of(context).colorScheme.errorContainer, + highlightColor: Theme.of(context).colorScheme.onErrorContainer, + period: const Duration(milliseconds: 600), + direction: ShimmerDirection.ltr, + child: Container( + color: Colors.grey, + width: double.infinity, + height: double.infinity, + ), + ); + }, + ); + } +} diff --git a/lib/src/pages/photo_view/photo_view.dart b/lib/src/pages/photo_view/photo_view.dart new file mode 100644 index 0000000..fc6a599 --- /dev/null +++ b/lib/src/pages/photo_view/photo_view.dart @@ -0,0 +1,62 @@ +import 'package:flutter/foundation.dart'; +import 'package:flutter/material.dart'; +import 'package:surf_flutter_summer_school_24/src/pages/photo_view/widgets/photo_view_appbar.dart'; +import 'package:surf_flutter_summer_school_24/src/pages/photo_view/widgets/photo_view_container.dart'; + +import '../../models/photo_entity.dart'; + +class PhotoView extends StatefulWidget { + final List images; + final int initialIndex; + + const PhotoView({super.key, required this.images, required this.initialIndex}); + + @override + State createState() => _PhotoViewState(); +} + + +class _PhotoViewState extends State with TickerProviderStateMixin { + late PageController _pageViewController; + int _currentPageIndex = 0; + + @override + void initState() { + super.initState(); + _currentPageIndex = widget.initialIndex; + _pageViewController = PageController(initialPage: widget.initialIndex, viewportFraction: 0.8); + } + + @override + void dispose() { + super.dispose(); + _pageViewController.dispose(); + } + + @override + Widget build(BuildContext context) { + final int total = widget.images.length; + return Scaffold( + appBar: PhotoViewAppbar(total: total, current: _currentPageIndex + 1), + body: Stack( + alignment: Alignment.bottomCenter, + children: [ + Stack( + alignment: Alignment.center, + children: [ + PageView.builder( + controller: _pageViewController, + itemCount: widget.images.length, + itemBuilder: (context, index) { + return PhotoViewContainer(urlImage: widget.images[index].url); + }, + ), + ], + ), + ], + ) + ); + } +} + + diff --git a/lib/src/pages/photo_view/widgets/photo_view_appbar.dart b/lib/src/pages/photo_view/widgets/photo_view_appbar.dart new file mode 100644 index 0000000..4cec851 --- /dev/null +++ b/lib/src/pages/photo_view/widgets/photo_view_appbar.dart @@ -0,0 +1,40 @@ +import 'package:flutter/material.dart'; + +import '../../gallery/gallery_widget.dart'; + +class PhotoViewAppbar extends StatelessWidget implements PreferredSizeWidget { + final int total; + final int current; + const PhotoViewAppbar({super.key, required this.total, required this.current}); + + @override + Widget build(BuildContext context) { + return AppBar( + leading: IconButton( + onPressed: () { + Navigator.pushAndRemoveUntil( + context, + MaterialPageRoute( + builder: (context) => const GalleryWidget(), + ), (Route route) => false, + ); + }, + icon: const Icon(Icons.arrow_back_rounded) + ), + actions: [ + Padding( + padding: const EdgeInsets.fromLTRB(0, 0, 10, 0), + child: Row( + children: [ + Text(current.toString()), + Text("/$total", style: TextStyle(color: Theme.of(context).colorScheme.secondary)) + ], + ), + ) + ], + ); + } + + @override + Size get preferredSize => const Size.fromHeight(kToolbarHeight); +} diff --git a/lib/src/pages/photo_view/widgets/photo_view_container.dart b/lib/src/pages/photo_view/widgets/photo_view_container.dart new file mode 100644 index 0000000..f7aeb3d --- /dev/null +++ b/lib/src/pages/photo_view/widgets/photo_view_container.dart @@ -0,0 +1,29 @@ +import 'package:flutter/material.dart'; + +class PhotoViewContainer extends StatelessWidget { + final String urlImage; + + const PhotoViewContainer({super.key, required this.urlImage}); + + @override + Widget build(BuildContext context) { + return Align( + alignment: Alignment.center, + child: Padding( + padding: const EdgeInsets.all(8.0), + child: SizedBox( + width: MediaQuery.of(context).size.width, + child: ClipRRect( + borderRadius: BorderRadius.circular(23), + child: Image.network( + urlImage, + fit: BoxFit.cover, + alignment: Alignment.center, + ), + ), + ), + ), + ); + } +} + diff --git a/lib/src/storage/theme/theme_storage.dart b/lib/src/storage/theme/theme_storage.dart new file mode 100644 index 0000000..23a101a --- /dev/null +++ b/lib/src/storage/theme/theme_storage.dart @@ -0,0 +1,40 @@ +import 'package:collection/collection.dart'; +import 'package:flutter/material.dart'; +import 'package:shared_preferences/shared_preferences.dart'; + +class ThemeStorage { + final SharedPreferences _prefs; + + ThemeStorage({ + required SharedPreferences prefs, + }) : _prefs = prefs; + + ThemeMode? getThemeMode() { + final storedName = _prefs.getString( + ThemeStorageKeys.mode.key, + ); + + if (storedName?.isEmpty ?? true) return null; + + return ThemeMode.values.firstWhereOrNull( + (themeMode) => themeMode.name == storedName, + ); + } + + Future saveThemeMode({ + required ThemeMode mode, + }) { + return _prefs.setString( + ThemeStorageKeys.mode.key, + mode.name, + ); + } +} + +enum ThemeStorageKeys { + mode('theme_mode'); + + final String key; + + const ThemeStorageKeys(this.key); +} \ No newline at end of file diff --git a/lib/src/uikit/colors/color_palette.dart b/lib/src/uikit/colors/color_palette.dart new file mode 100644 index 0000000..afc67bd --- /dev/null +++ b/lib/src/uikit/colors/color_palette.dart @@ -0,0 +1,73 @@ +import 'package:flutter/material.dart'; + +abstract class LightColorPalette { + static const purple = Color(0xFF9824F2); + + static const greenYellow = Color(0xFFBEFF3D); + + static const darkScarlet = Color(0xFF4D052A); + + static const folly = Color(0xFFFF004D); + + static const cultured = Color(0xFFF6F6F6); + + static const chineseBlack = Color(0xFF171717); + + static const white = Colors.white; + + static const black = Colors.black; + + static const vividRaspberry = Color(0xFFFF176B); + + static const lightSilver = Color(0xFFD9D9D9); + + static const silver = Color(0xFFB2B2B2); + + static const lightGreen = Color(0xFFB5CCAE); + + static const darkGreen = Color(0xFF84A58F); + + static const lightViolet = Color(0xFF74305B); + + static const violet = Color(0xFF4A194E); + + static const appleGreen = Color(0xFF83C000); + + static const platinum = Color(0xFFE7E4E0); + + static const raisinBlack = Color(0xFF232323); + + static const darkSilver = Color(0xFF626262); +} + +abstract class DarkColorPalette { + static const hanPurple = Color(0xFF6D38FF); + + static const inchworm = Color(0xFFC6FF57); + + static const maroon = Color(0xFF7B0008); + + static const brinkPink = Color(0xFFFF607D); + + static const raisinBlack = Color(0xFF222222); + + static const lightSilver = Color(0xFFD6D6D6); + + static const darkSilver = Color(0xFF626262); + + static const cyclamen = Color(0xFFFF79A8); + + static const etonBlue = Color(0xFF9CD29C); + + static const russianGreen = Color(0xFF628B6E); + + static const plum = Color(0xFF9E478B); + + static const brownChocolate = Color(0xFF561E43); + + static const vividLimeGreen = Color(0xFF9ECF00); + + static const white = Colors.white; + + static const black = Colors.black; +} \ No newline at end of file diff --git a/lib/src/uikit/colors/color_scheme.dart b/lib/src/uikit/colors/color_scheme.dart new file mode 100644 index 0000000..c55e437 --- /dev/null +++ b/lib/src/uikit/colors/color_scheme.dart @@ -0,0 +1,273 @@ +import 'package:flutter/material.dart'; +import 'package:surf_flutter_summer_school_24/src/uikit/colors/color_palette.dart'; + +const _skeletonOpacity = 0.06; + +@immutable +class AppColorScheme extends ThemeExtension { + final Color primary; + + final Color onPrimary; + + final Color secondary; + + final Color onSecondary; + + final Color surface; + + final Color surfaceSecondary; + + final Color onSurface; + + final Color background; + + final Color backgroundSecondary; + + final Color backgroundTertiary; + + final Color tetradicBackground; + + final Color onBackground; + + final Color onBackgroundSecondary; + + final Color danger; + + final Color dangerSecondary; + + final Color onDanger; + + final Color textField; + + final Color textFieldLabel; + + final Color textFieldHelper; + + final Color frameTextFieldSecondary; + + final Color inactive; + + final Color positive; + + final Color onPositive; + + final Color skeletonPrimary; + + final Color skeletonOnPrimary; + + final Color skeletonSecondary; + + final Color skeletonTertiary; + + final Color shimmer; + + + AppColorScheme.light() + : primary = LightColorPalette.black, + onPrimary = LightColorPalette.white, + secondary = LightColorPalette.darkSilver, + onSecondary = LightColorPalette.platinum, + surface = LightColorPalette.white, + surfaceSecondary = LightColorPalette.cultured, + onSurface = LightColorPalette.chineseBlack, + background = LightColorPalette.cultured, + backgroundSecondary = LightColorPalette.darkScarlet, + backgroundTertiary = LightColorPalette.cultured, + onBackground = LightColorPalette.chineseBlack, + onBackgroundSecondary = LightColorPalette.white, + danger = LightColorPalette.folly, + dangerSecondary = LightColorPalette.vividRaspberry, + onDanger = LightColorPalette.white, + textField = LightColorPalette.chineseBlack, + textFieldLabel = LightColorPalette.black, + textFieldHelper = LightColorPalette.black, + frameTextFieldSecondary = LightColorPalette.chineseBlack, + inactive = LightColorPalette.black, + positive = LightColorPalette.greenYellow, + onPositive = LightColorPalette.chineseBlack, + skeletonPrimary = LightColorPalette.black.withOpacity(_skeletonOpacity), + skeletonOnPrimary = LightColorPalette.white, + skeletonSecondary = LightColorPalette.cultured, + skeletonTertiary = LightColorPalette.lightSilver, + tetradicBackground = LightColorPalette.lightGreen, + shimmer = LightColorPalette.silver; + + AppColorScheme.dark() + : primary = DarkColorPalette.white, + onPrimary = DarkColorPalette.black, + secondary = DarkColorPalette.darkSilver, + onSecondary = DarkColorPalette.black, + surface = DarkColorPalette.raisinBlack, + surfaceSecondary = DarkColorPalette.raisinBlack, + onSurface = DarkColorPalette.white, + background = DarkColorPalette.raisinBlack, + backgroundSecondary = DarkColorPalette.maroon, + backgroundTertiary = DarkColorPalette.raisinBlack, + onBackground = DarkColorPalette.white, + onBackgroundSecondary = DarkColorPalette.white, + danger = DarkColorPalette.brinkPink, + dangerSecondary = DarkColorPalette.cyclamen, + onDanger = DarkColorPalette.white, + textField = DarkColorPalette.lightSilver, + textFieldLabel = DarkColorPalette.white, + textFieldHelper = DarkColorPalette.black, + frameTextFieldSecondary = DarkColorPalette.lightSilver, + inactive = DarkColorPalette.black, + positive = DarkColorPalette.inchworm, + onPositive = DarkColorPalette.black, + skeletonPrimary = DarkColorPalette.black.withOpacity(_skeletonOpacity), + skeletonOnPrimary = DarkColorPalette.white, + skeletonSecondary = DarkColorPalette.raisinBlack, + skeletonTertiary = DarkColorPalette.lightSilver, + tetradicBackground = DarkColorPalette.etonBlue, + shimmer =DarkColorPalette.raisinBlack; + + const AppColorScheme._({ + required this.primary, + required this.onPrimary, + required this.secondary, + required this.onSecondary, + required this.surface, + required this.surfaceSecondary, + required this.onSurface, + required this.background, + required this.backgroundSecondary, + required this.backgroundTertiary, + required this.onBackground, + required this.onBackgroundSecondary, + required this.danger, + required this.dangerSecondary, + required this.onDanger, + required this.textField, + required this.textFieldLabel, + required this.textFieldHelper, + required this.frameTextFieldSecondary, + required this.inactive, + required this.positive, + required this.onPositive, + required this.skeletonPrimary, + required this.skeletonOnPrimary, + required this.skeletonSecondary, + required this.skeletonTertiary, + required this.tetradicBackground, + required this.shimmer, + }); + + @override + ThemeExtension copyWith({ + Color? primary, + Color? onPrimary, + Color? secondary, + Color? onSecondary, + Color? surface, + Color? surfaceSecondary, + Color? onSurface, + Color? background, + Color? backgroundSecondary, + Color? backgroundTertiary, + Color? onBackground, + Color? onBackgroundSecondary, + Color? danger, + Color? dangerSecondary, + Color? onDanger, + Color? textField, + Color? textFieldLabel, + Color? textFieldHelper, + Color? frameTextFieldSecondary, + Color? inactive, + Color? positive, + Color? onPositive, + Color? skeletonPrimary, + Color? skeletonOnPrimary, + Color? skeletonSecondary, + Color? skeletonTertiary, + Color? tetradicBackground, + Color? shimmer, + }) { + return AppColorScheme._( + primary: primary ?? this.primary, + onPrimary: onPrimary ?? this.onPrimary, + secondary: secondary ?? this.secondary, + onSecondary: onSecondary ?? this.onSecondary, + surface: surface ?? this.surface, + surfaceSecondary: surfaceSecondary ?? this.surfaceSecondary, + onSurface: onSurface ?? this.onSurface, + background: background ?? this.background, + backgroundSecondary: backgroundSecondary ?? this.backgroundSecondary, + backgroundTertiary: backgroundTertiary ?? this.backgroundTertiary, + onBackground: onBackground ?? this.onBackground, + onBackgroundSecondary: + onBackgroundSecondary ?? this.onBackgroundSecondary, + danger: danger ?? this.danger, + dangerSecondary: dangerSecondary ?? this.dangerSecondary, + onDanger: onDanger ?? this.onDanger, + textField: textField ?? this.textField, + textFieldLabel: textFieldLabel ?? this.textFieldLabel, + textFieldHelper: textFieldHelper ?? this.textFieldHelper, + frameTextFieldSecondary: + frameTextFieldSecondary ?? this.frameTextFieldSecondary, + inactive: inactive ?? this.inactive, + positive: positive ?? this.positive, + onPositive: onPositive ?? this.onPositive, + skeletonPrimary: skeletonPrimary ?? this.skeletonPrimary, + skeletonOnPrimary: skeletonOnPrimary ?? this.skeletonOnPrimary, + skeletonSecondary: skeletonSecondary ?? this.skeletonSecondary, + skeletonTertiary: skeletonTertiary ?? this.skeletonTertiary, + tetradicBackground: tetradicBackground ?? this.tetradicBackground, + shimmer: shimmer ?? this.shimmer, + ); + } + + @override + ThemeExtension lerp( + ThemeExtension? other, + double t, + ) { + if (other is! AppColorScheme) { + return this; + } + + return AppColorScheme._( + primary: Color.lerp(primary, other.primary, t)!, + onPrimary: Color.lerp(onPrimary, other.onPrimary, t)!, + secondary: Color.lerp(secondary, other.secondary, t)!, + onSecondary: Color.lerp(onSecondary, other.onSecondary, t)!, + surface: Color.lerp(surface, other.surface, t)!, + surfaceSecondary: + Color.lerp(surfaceSecondary, other.surfaceSecondary, t)!, + onSurface: Color.lerp(onSurface, other.onSurface, t)!, + background: Color.lerp(background, other.background, t)!, + backgroundSecondary: + Color.lerp(backgroundSecondary, other.backgroundSecondary, t)!, + backgroundTertiary: + Color.lerp(backgroundTertiary, other.backgroundTertiary, t)!, + onBackground: Color.lerp(onBackground, other.onBackground, t)!, + onBackgroundSecondary: + Color.lerp(onBackgroundSecondary, other.onBackgroundSecondary, t)!, + danger: Color.lerp(danger, other.danger, t)!, + dangerSecondary: Color.lerp(dangerSecondary, other.dangerSecondary, t)!, + onDanger: Color.lerp(onDanger, other.onDanger, t)!, + textField: Color.lerp(textField, other.textField, t)!, + textFieldLabel: Color.lerp(textFieldLabel, other.textFieldLabel, t)!, + textFieldHelper: Color.lerp(textFieldHelper, other.textFieldHelper, t)!, + frameTextFieldSecondary: Color.lerp( + frameTextFieldSecondary, other.frameTextFieldSecondary, t)!, + inactive: Color.lerp(inactive, other.inactive, t)!, + positive: Color.lerp(positive, other.positive, t)!, + onPositive: Color.lerp(onPositive, other.onPositive, t)!, + skeletonPrimary: Color.lerp(skeletonPrimary, other.skeletonPrimary, t)!, + skeletonOnPrimary: + Color.lerp(skeletonOnPrimary, other.skeletonOnPrimary, t)!, + skeletonSecondary: + Color.lerp(skeletonSecondary, other.skeletonSecondary, t)!, + skeletonTertiary: + Color.lerp(skeletonTertiary, other.skeletonTertiary, t)!, + tetradicBackground: + Color.lerp(tetradicBackground, other.tetradicBackground, t)!, + shimmer: Color.lerp(shimmer, other.shimmer, t)!, + ); + } + + static AppColorScheme of(BuildContext context) => + Theme.of(context).extension()!; +} \ No newline at end of file diff --git a/lib/src/uikit/theme/theme_data.dart b/lib/src/uikit/theme/theme_data.dart new file mode 100644 index 0000000..59b1868 --- /dev/null +++ b/lib/src/uikit/theme/theme_data.dart @@ -0,0 +1,92 @@ +import 'package:flutter/material.dart'; +import 'package:surf_flutter_summer_school_24/src/uikit/colors/color_scheme.dart'; +import 'package:surf_flutter_summer_school_24/src/uikit/typography/typography.dart'; + +abstract class AppThemeData { + static final lightTheme = ThemeData( + extensions: [_lightColorScheme], + brightness: Brightness.light, + textTheme: const TextTheme( + titleLarge: headline1, + ), + colorScheme: ColorScheme( + brightness: Brightness.light, + primary: _lightColorScheme.primary, + onPrimary: _lightColorScheme.onPrimary, + secondary: _lightColorScheme.secondary, + onSecondary: _lightColorScheme.onSecondary, + error: _lightColorScheme.danger, + onError: _lightColorScheme.onDanger, + background: _lightColorScheme.background, + onBackground: _lightColorScheme.onBackground, + surface: _lightColorScheme.surface, + onSurface: _lightColorScheme.onSurface, + errorContainer: _lightColorScheme.shimmer, + onErrorContainer: _lightColorScheme.onSecondary + ), + scaffoldBackgroundColor: _lightColorScheme.background, + appBarTheme: AppBarTheme( + color: _lightColorScheme.background, + iconTheme: IconThemeData( + color: _lightColorScheme.primary, + ), + ), + bottomNavigationBarTheme: BottomNavigationBarThemeData( + backgroundColor: _lightColorScheme.background, + selectedItemColor: _lightColorScheme.primary, + unselectedItemColor: _lightColorScheme.onBackground, + ), + snackBarTheme: SnackBarThemeData( + backgroundColor: _lightColorScheme.primary, + contentTextStyle: TextStyle( + color: _lightColorScheme.onPrimary, + ), + ), + ); + + static final darkTheme = ThemeData( + extensions: [_darkColorScheme], + brightness: Brightness.dark, + textTheme: const TextTheme( + titleLarge: headline1, + ), + colorScheme: ColorScheme( + brightness: Brightness.dark, + primary: _darkColorScheme.primary, + onPrimary: _darkColorScheme.onPrimary, + secondary: _darkColorScheme.secondary, + onSecondary: _darkColorScheme.onSecondary, + error: _darkColorScheme.danger, + onError: _darkColorScheme.onDanger, + background: _darkColorScheme.background, + onBackground: _darkColorScheme.onBackground, + surface: _darkColorScheme.surface, + onSurface: _darkColorScheme.onSurface, + errorContainer: _darkColorScheme.shimmer, + onErrorContainer: _darkColorScheme.secondary + ), + scaffoldBackgroundColor: _darkColorScheme.background, + appBarTheme: AppBarTheme( + color: _darkColorScheme.background, + iconTheme: IconThemeData( + color: _darkColorScheme.primary, + ), + ), + bottomNavigationBarTheme: BottomNavigationBarThemeData( + backgroundColor: _darkColorScheme.background, + selectedItemColor: _darkColorScheme.primary, + unselectedItemColor: _darkColorScheme.onBackground, + ), + snackBarTheme: SnackBarThemeData( + backgroundColor: _darkColorScheme.primary, + contentTextStyle: TextStyle( + color: _darkColorScheme.onPrimary, + ), + ), + ); + + static final _lightColorScheme = AppColorScheme.light(); + static final _darkColorScheme = AppColorScheme.dark(); + + +} \ No newline at end of file diff --git a/lib/src/uikit/typography/typography.dart b/lib/src/uikit/typography/typography.dart new file mode 100644 index 0000000..833d3b7 --- /dev/null +++ b/lib/src/uikit/typography/typography.dart @@ -0,0 +1,7 @@ +import 'package:flutter/material.dart'; + +const TextStyle headline1 = TextStyle( + fontFamily: 'Magnolia', + fontSize: 32, + fontWeight: FontWeight.normal, +); diff --git a/pubspec.lock b/pubspec.lock index 9883fc6..326d59e 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -41,6 +41,22 @@ packages: url: "https://pub.dev" source: hosted version: "1.18.0" + elementary: + dependency: "direct main" + description: + name: elementary + sha256: "9b8385fac1c2102918a512d7a80ce0babe61af960f3c86902a671b09131d958c" + url: "https://pub.dev" + source: hosted + version: "3.0.0" + equatable: + dependency: "direct main" + description: + name: equatable + sha256: c2b87cb7756efdf69892005af546c56c0b5037f54d2a88269b4f347a505e3ca2 + url: "https://pub.dev" + source: hosted + version: "2.0.5" fake_async: dependency: transitive description: @@ -49,6 +65,22 @@ packages: url: "https://pub.dev" source: hosted version: "1.3.1" + ffi: + dependency: transitive + description: + name: ffi + sha256: "493f37e7df1804778ff3a53bd691d8692ddf69702cf4c1c1096a2e41b4779e21" + url: "https://pub.dev" + source: hosted + version: "2.1.2" + file: + dependency: transitive + description: + name: file + sha256: "5fc22d7c25582e38ad9a8515372cd9a93834027aacf1801cf01164dac0ffa08c" + url: "https://pub.dev" + source: hosted + version: "7.0.0" flutter: dependency: "direct main" description: flutter @@ -67,6 +99,11 @@ packages: description: flutter source: sdk version: "0.0.0" + flutter_web_plugins: + dependency: transitive + description: flutter + source: sdk + version: "0.0.0" leak_tracker: dependency: transitive description: @@ -131,6 +168,110 @@ packages: url: "https://pub.dev" source: hosted version: "1.9.0" + path_provider_linux: + dependency: transitive + description: + name: path_provider_linux + sha256: f7a1fe3a634fe7734c8d3f2766ad746ae2a2884abe22e241a8b301bf5cac3279 + url: "https://pub.dev" + source: hosted + version: "2.2.1" + path_provider_platform_interface: + dependency: transitive + description: + name: path_provider_platform_interface + sha256: "88f5779f72ba699763fa3a3b06aa4bf6de76c8e5de842cf6f29e2e06476c2334" + url: "https://pub.dev" + source: hosted + version: "2.1.2" + path_provider_windows: + dependency: transitive + description: + name: path_provider_windows + sha256: bd6f00dbd873bfb70d0761682da2b3a2c2fccc2b9e84c495821639601d81afe7 + url: "https://pub.dev" + source: hosted + version: "2.3.0" + platform: + dependency: transitive + description: + name: platform + sha256: "9b71283fc13df574056616011fb138fd3b793ea47cc509c189a6c3fa5f8a1a65" + url: "https://pub.dev" + source: hosted + version: "3.1.5" + plugin_platform_interface: + dependency: transitive + description: + name: plugin_platform_interface + sha256: "4820fbfdb9478b1ebae27888254d445073732dae3d6ea81f0b7e06d5dedc3f02" + url: "https://pub.dev" + source: hosted + version: "2.1.8" + shared_preferences: + dependency: "direct main" + description: + name: shared_preferences + sha256: d3bbe5553a986e83980916ded2f0b435ef2e1893dfaa29d5a7a790d0eca12180 + url: "https://pub.dev" + source: hosted + version: "2.2.3" + shared_preferences_android: + dependency: transitive + description: + name: shared_preferences_android + sha256: "93d0ec9dd902d85f326068e6a899487d1f65ffcd5798721a95330b26c8131577" + url: "https://pub.dev" + source: hosted + version: "2.2.3" + shared_preferences_foundation: + dependency: transitive + description: + name: shared_preferences_foundation + sha256: "0a8a893bf4fd1152f93fec03a415d11c27c74454d96e2318a7ac38dd18683ab7" + url: "https://pub.dev" + source: hosted + version: "2.4.0" + shared_preferences_linux: + dependency: transitive + description: + name: shared_preferences_linux + sha256: "9f2cbcf46d4270ea8be39fa156d86379077c8a5228d9dfdb1164ae0bb93f1faa" + url: "https://pub.dev" + source: hosted + version: "2.3.2" + shared_preferences_platform_interface: + dependency: transitive + description: + name: shared_preferences_platform_interface + sha256: "034650b71e73629ca08a0bd789fd1d83cc63c2d1e405946f7cef7bc37432f93a" + url: "https://pub.dev" + source: hosted + version: "2.4.0" + shared_preferences_web: + dependency: transitive + description: + name: shared_preferences_web + sha256: "9aee1089b36bd2aafe06582b7d7817fd317ef05fc30e6ba14bff247d0933042a" + url: "https://pub.dev" + source: hosted + version: "2.3.0" + shared_preferences_windows: + dependency: transitive + description: + name: shared_preferences_windows + sha256: "841ad54f3c8381c480d0c9b508b89a34036f512482c407e6df7a9c4aa2ef8f59" + url: "https://pub.dev" + source: hosted + version: "2.3.2" + shimmer: + dependency: "direct main" + description: + name: shimmer + sha256: "5f88c883a22e9f9f299e5ba0e4f7e6054857224976a5d9f839d4ebdc94a14ac9" + url: "https://pub.dev" + source: hosted + version: "3.0.0" sky_engine: dependency: transitive description: flutter @@ -200,6 +341,22 @@ packages: url: "https://pub.dev" source: hosted version: "14.2.1" + web: + dependency: transitive + description: + name: web + sha256: "97da13628db363c635202ad97068d47c5b8aa555808e7a9411963c533b449b27" + url: "https://pub.dev" + source: hosted + version: "0.5.1" + xdg_directories: + dependency: transitive + description: + name: xdg_directories + sha256: faea9dee56b520b55a566385b84f2e8de55e7496104adada9962e0bd11bcff1d + url: "https://pub.dev" + source: hosted + version: "1.0.4" sdks: dart: ">=3.4.3 <4.0.0" - flutter: ">=3.18.0-18.0.pre.54" + flutter: ">=3.22.0" diff --git a/pubspec.yaml b/pubspec.yaml index 13e222f..b50800b 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -9,6 +9,10 @@ environment: dependencies: flutter: sdk: flutter + shared_preferences: ^2.2.3 + shimmer: ^3.0.0 + equatable: ^2.0.5 + elementary: ^3.0.0 dev_dependencies: flutter_test: @@ -17,3 +21,12 @@ dev_dependencies: flutter: uses-material-design: true + + assets: + + fonts: + - family: Magnolia + fonts: + - asset: fonts/Magnolia.ttf + +