diff --git a/README.md b/README.md index a45aff8..ec06621 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,12 @@ # surf_flutter_summer_school_24 Шаблонный репозиторий для Surf Flutter Summer School '24 + + + + + + + + + \ No newline at end of file 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/dowload_image_to_cload.dart b/lib/src/feature/photos/data/dowload_image_to_cload.dart new file mode 100644 index 0000000..f7a0cbb --- /dev/null +++ b/lib/src/feature/photos/data/dowload_image_to_cload.dart @@ -0,0 +1,47 @@ +import 'dart:convert'; +import 'dart:io'; + +import 'package:http/http.dart' as http; + +import '../domain/models/photo_entity.dart'; + +Future<List<PhotoEntity>> downloadImageFromYandexCloud() async { + const token = 'y0_AgAAAABZVuavAADLWwAAAAELdHsRAAAWFyiqDOJE_LPjdaheqhXn63NSWA'; + + final uri = Uri.https( + 'cloud-api.yandex.net', + 'v1/disk/resources', + { + "path": '/', + }, + ); + + final response = await http.get( + uri, + headers: { + HttpHeaders.authorizationHeader: 'OAuth $token', + }, + ); + + if (response.statusCode == 200) { + final body = response.body; + final json = jsonDecode(body) as Map<String, dynamic>; + final items = json['_embedded']['items'] as List<dynamic>; + + return items.where((item) { + final url = item['file'] as String?; + return url != null && url.isNotEmpty; + }).map((item) { + final id = item['name'] as String; + final url = item['file'] as String; + final createAt = DateTime.tryParse(item['created']) ?? null; + return PhotoEntity( + id: id, + url: url, + createAt: createAt, + ); + }).toList(); + } else { + throw Exception('Ошибка получения фотографий с Яндекс.Диска'); + } +} \ No newline at end of file diff --git a/lib/src/feature/photos/data/photo_repository.dart b/lib/src/feature/photos/data/photo_repository.dart new file mode 100644 index 0000000..406da1c --- /dev/null +++ b/lib/src/feature/photos/data/photo_repository.dart @@ -0,0 +1,16 @@ +import '../domain/i_photo_repository.dart'; +import '../domain/models/photo_entity.dart'; + + +import 'dowload_image_to_cload.dart'; + +class PhotoRepository implements IPhotoRepository { + + @override + Future<List<PhotoEntity>> getPhotos() async { + final photos = await downloadImageFromYandexCloud(); + return photos; + } + +} + diff --git a/lib/src/feature/photos/data/upload_image_to_cload.dart b/lib/src/feature/photos/data/upload_image_to_cload.dart new file mode 100644 index 0000000..e6b9e78 --- /dev/null +++ b/lib/src/feature/photos/data/upload_image_to_cload.dart @@ -0,0 +1,36 @@ +import 'dart:convert'; +import 'dart:io'; + +import 'package:dio/dio.dart'; +import 'package:http/http.dart' as http; + +Future<void> uploadImageToYandexCloud(String imagePath) async { + const token = 'y0_AgAAAABZVuavAADLWwAAAAELdHsRAAAWFyiqDOJE_LPjdaheqhXn63NSWA'; + + final uri = Uri.https( + 'cloud-api.yandex.net', + 'v1/disk/resources/upload', + { + "path": imagePath.split('/').last, + }, + ); + + final response = await http.get( + uri, + headers: { + HttpHeaders.authorizationHeader: 'OAuth $token', + }, + ); + + final body = response.body; + final json = jsonDecode(body); + json as Map<String, dynamic>; + final linkToUpload = json['href'] as String; + + final dio = Dio(); + final file = File(imagePath); + final formData = FormData.fromMap({ + 'file': await MultipartFile.fromFile(file.path), + }); + await dio.put(linkToUpload, data: formData); +} \ No newline at end of file diff --git a/lib/src/feature/photos/domain/i_photo_repository.dart b/lib/src/feature/photos/domain/i_photo_repository.dart new file mode 100644 index 0000000..11acdf2 --- /dev/null +++ b/lib/src/feature/photos/domain/i_photo_repository.dart @@ -0,0 +1,5 @@ +import 'models/photo_entity.dart'; + +abstract interface class IPhotoRepository { + Future<List<PhotoEntity>> getPhotos(); +} \ No newline at end of file diff --git a/lib/src/feature/photos/domain/models/photo_entity.dart b/lib/src/feature/photos/domain/models/photo_entity.dart new file mode 100644 index 0000000..0dd062f --- /dev/null +++ b/lib/src/feature/photos/domain/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<Object?> get props => [ + id, + url, + createAt, + ]; +} \ 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<void> 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<ThemeInherited>() + ?.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> _themeMode = ValueNotifier<ThemeMode>( + _themeRepository.getThemeMode() ?? ThemeMode.system, + ); + + ValueListenable<ThemeMode> get themeMode => _themeMode; + + Future<void> setThemeMode(ThemeMode newThemeMode) async { + if (newThemeMode == _themeMode.value) return; + await _themeRepository.setThemeMode(newThemeMode); + _themeMode.value = newThemeMode; + } + + Future<void> 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<ThemeBuilder> createState() => _ThemeBuilderState(); +} + +class _ThemeBuilderState extends State<ThemeBuilder> { + @override + Widget build(BuildContext context) { + return ValueListenableBuilder<ThemeMode>( + 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/pages/gallery/gallery_model.dart b/lib/src/pages/gallery/gallery_model.dart new file mode 100644 index 0000000..8be3959 --- /dev/null +++ b/lib/src/pages/gallery/gallery_model.dart @@ -0,0 +1,17 @@ +import 'package:elementary/elementary.dart'; +import 'package:flutter/material.dart'; + +import '../../feature/photos/domain/i_photo_repository.dart'; +import '../../feature/photos/domain/models/photo_entity.dart'; + +class GalleryModel extends ElementaryModel { + final IPhotoRepository photoRepository; + final ValueNotifier<List<PhotoEntity>> images = ValueNotifier([]); + + GalleryModel(this.photoRepository); + + Future<void> fetchPhotos() async { + final photos = await photoRepository.getPhotos(); + images.value = photos; + } +} \ 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..cf6e8b5 --- /dev/null +++ b/lib/src/pages/gallery/gallery_widget.dart @@ -0,0 +1,43 @@ +import 'package:elementary/elementary.dart'; +import 'package:flutter/material.dart'; + +import '../../feature/photos/domain/models/photo_entity.dart'; +import 'widgets/gallery_appbar.dart'; +import 'widgets/photo_gallery_container.dart'; +import 'gallery_widget_model.dart'; + +class GalleryWidget extends ElementaryWidget<GalleryWidgetModel> { + const GalleryWidget({ + Key? key, + WidgetModelFactory<GalleryWidgetModel> wm = createGalleryWidgetModel, + }) + : super(wm, key: key); + + @override + Widget build(GalleryWidgetModel wm) { + return Scaffold( + appBar: const GalleryAppbar(), + body: ValueListenableBuilder<List<PhotoEntity>>( + 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..5a1185e --- /dev/null +++ b/lib/src/pages/gallery/gallery_widget_model.dart @@ -0,0 +1,31 @@ +import 'package:elementary/elementary.dart'; +import 'package:flutter/material.dart'; + +import '../../feature/photos/data/photo_repository.dart'; +import '../photo_view/photo_view_widget.dart'; +import 'gallery_model.dart'; +import 'gallery_widget.dart'; + +class GalleryWidgetModel extends WidgetModel<GalleryWidget, GalleryModel> { + GalleryWidgetModel(GalleryModel model) : super(model); + + @override + void initWidgetModel() { + super.initWidgetModel(); + model.fetchPhotos(); + } + + void onPhotoTap(int index) { + Navigator.of(context).push( + MaterialPageRoute( + builder: (context) => PhotoViewWidget( + images: model.images.value, + initialIndex: index, + ), + ), + ); + } +} + +GalleryWidgetModel createGalleryWidgetModel(BuildContext _) => +GalleryWidgetModel(GalleryModel(PhotoRepository())); \ 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<void>( + 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..ebe4621 --- /dev/null +++ b/lib/src/pages/gallery/widgets/gallery_bottom_sheet.dart @@ -0,0 +1,123 @@ +import 'dart:convert'; +import 'dart:io'; + +import 'package:dio/dio.dart'; +import 'package:flutter/material.dart'; +import 'package:image_picker/image_picker.dart'; +import '../../../feature/photos/data/photo_repository.dart'; +import '../../../feature/photos/data/upload_image_to_cload.dart'; +import '../../../feature/theme/di/theme_inherited.dart'; + +import 'package:http/http.dart' as http; + +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: <Widget>[ + 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: () { + showDialog( + context: context, + builder: (BuildContext context) { + return AlertDialog( + content: Column( + mainAxisSize: MainAxisSize.min, + children: [ + TextButton( + onPressed: () async { + _pickImage(ImageSource.gallery, context); + }, + child: const Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Icon(Icons.photo_library_rounded), + SizedBox(width: 10), + Text('Галерея'), + ], + ), + ), + TextButton( + onPressed: () async { + _pickImage(ImageSource.camera, context); + }, + child:const Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Icon(Icons.camera_alt_rounded), + SizedBox(width: 10), + Text('Камера'), + ], + ), + ), + ], + ), + ); + }, + ); + }, + ), + ], + ), + ), + ); + } +} + +Future<void> _pickImage(ImageSource source, BuildContext context) async { + final picker = ImagePicker(); + final pickedFile = await picker.pickImage(source: source); + + if (pickedFile != null) { + await uploadImageToYandexCloud(pickedFile.path); + } +} 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<PhotoGalleryContainer> createState() => _PhotoGalleryContainerState(); +} + +class _PhotoGalleryContainerState extends State<PhotoGalleryContainer> { + + + @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_model.dart b/lib/src/pages/photo_view/photo_view_model.dart new file mode 100644 index 0000000..52aa60b --- /dev/null +++ b/lib/src/pages/photo_view/photo_view_model.dart @@ -0,0 +1,26 @@ +import 'package:elementary/elementary.dart'; +import 'package:flutter/material.dart'; + +import '../../feature/photos/domain/models/photo_entity.dart'; + +class PhotoViewModel extends ElementaryModel { + final ValueNotifier<int> _currentPageIndex; + final PageController _pageController; + final List<PhotoEntity> images; + + int get currentPageIndex => _currentPageIndex.value; + ValueNotifier<int> get currentPageIndexValueNotifier => _currentPageIndex; + PageController get pageController => _pageController; + + PhotoViewModel(this.images, int initialIndex) + : _currentPageIndex = ValueNotifier(initialIndex), + _pageController = PageController( + initialPage: initialIndex, + viewportFraction: 0.8 + + ); + + void handlePageViewChanged(int currentPageIndex) { + _currentPageIndex.value = currentPageIndex; + } +} \ No newline at end of file diff --git a/lib/src/pages/photo_view/photo_view_widget.dart b/lib/src/pages/photo_view/photo_view_widget.dart new file mode 100644 index 0000000..c6ea0ca --- /dev/null +++ b/lib/src/pages/photo_view/photo_view_widget.dart @@ -0,0 +1,49 @@ +import 'package:elementary/elementary.dart'; +import 'package:flutter/material.dart'; +import 'package:surf_flutter_summer_school_24/src/pages/photo_view/photo_view_widget_model.dart'; + +import '../../feature/photos/domain/models/photo_entity.dart'; +import 'widgets/photo_view_appbar.dart'; +import 'widgets/photo_view_container.dart'; + +class PhotoViewWidget extends ElementaryWidget<PhotoViewWidgetModel> { + final List<PhotoEntity> images; + final int initialIndex; + + const PhotoViewWidget({ + Key? key, + required this.images, + required this.initialIndex, + WidgetModelFactory<PhotoViewWidgetModel> wm = createPhotoViewWidgetModel + }) + : super(wm, key: key); + + @override + Widget build(PhotoViewWidgetModel wm) { + final total = images.length; + return Scaffold( + appBar: PhotoViewAppbar( + total: total, + currentPageIndex: wm.model.currentPageIndexValueNotifier + ), + body: Stack( + alignment: Alignment.bottomCenter, + children: <Widget>[ + Stack( + alignment: Alignment.center, + children: [ + PageView.builder( + controller: wm.model.pageController, + onPageChanged: (index) => wm.model.handlePageViewChanged(index), + itemCount: images.length, + itemBuilder: (context, index) { + return PhotoViewContainer(urlImage: images[index].url); + }, + ), + ], + ), + ], + ), + ); + } +} \ No newline at end of file diff --git a/lib/src/pages/photo_view/photo_view_widget_model.dart b/lib/src/pages/photo_view/photo_view_widget_model.dart new file mode 100644 index 0000000..659b9e8 --- /dev/null +++ b/lib/src/pages/photo_view/photo_view_widget_model.dart @@ -0,0 +1,24 @@ +import 'package:elementary/elementary.dart'; +import 'package:flutter/cupertino.dart'; +import 'package:surf_flutter_summer_school_24/src/pages/photo_view/photo_view_model.dart'; +import 'package:surf_flutter_summer_school_24/src/pages/photo_view/photo_view_widget.dart'; + + +class PhotoViewWidgetModel extends WidgetModel<PhotoViewWidget, PhotoViewModel> { + PhotoViewWidgetModel(PhotoViewModel model) : super(model); + + @override + void initWidgetModel() { + super.initWidgetModel(); + model.pageController.addListener(() { + model.handlePageViewChanged(model.pageController.page!.toInt()); + }); + } + +} + +PhotoViewWidgetModel createPhotoViewWidgetModel(BuildContext context) { + final images = (context.widget as PhotoViewWidget).images; + final initialIndex = (context.widget as PhotoViewWidget).initialIndex; + return PhotoViewWidgetModel(PhotoViewModel(images, initialIndex)); +} 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..c88fdf7 --- /dev/null +++ b/lib/src/pages/photo_view/widgets/photo_view_appbar.dart @@ -0,0 +1,46 @@ +import 'package:flutter/foundation.dart'; +import 'package:flutter/material.dart'; + +import '../../gallery/gallery_widget.dart'; + +class PhotoViewAppbar extends StatelessWidget implements PreferredSizeWidget { + final int total; + final ValueNotifier<int> currentPageIndex; + const PhotoViewAppbar({super.key, required this.total, required this.currentPageIndex}); + + @override + Widget build(BuildContext context) { + return ValueListenableBuilder<int>( + valueListenable: currentPageIndex, + builder: (context, currentValue, child) { + return AppBar( + leading: IconButton( + onPressed: () { + Navigator.pushAndRemoveUntil( + context, + MaterialPageRoute( + builder: (context) => const GalleryWidget(), + ), (Route<dynamic> route) => false, + ); + }, + icon: const Icon(Icons.arrow_back_rounded) + ), + actions: [ + Padding( + padding: const EdgeInsets.fromLTRB(0, 0, 10, 0), + child: Row( + children: [ + Text((currentPageIndex.value + 1).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<void> 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<AppColorScheme> { + 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<AppColorScheme> 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<AppColorScheme> lerp( + ThemeExtension<AppColorScheme>? 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<AppColorScheme>()!; +} \ 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..b7f68f6 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -41,6 +41,46 @@ packages: url: "https://pub.dev" source: hosted version: "1.18.0" + cross_file: + dependency: transitive + description: + name: cross_file + sha256: "2f9d2cbccb76127ba28528cb3ae2c2326a122446a83de5a056aaa3880d3882c5" + url: "https://pub.dev" + source: hosted + version: "0.3.3+7" + dio: + dependency: "direct main" + description: + name: dio + sha256: e17f6b3097b8c51b72c74c9f071a605c47bcc8893839bd66732457a5ebe73714 + url: "https://pub.dev" + source: hosted + version: "5.5.0+1" + dio_web_adapter: + dependency: transitive + description: + name: dio_web_adapter + sha256: "36c5b2d79eb17cdae41e974b7a8284fec631651d2a6f39a8a2ff22327e90aeac" + url: "https://pub.dev" + source: hosted + version: "1.0.1" + 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 +89,54 @@ 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" + file_selector_linux: + dependency: transitive + description: + name: file_selector_linux + sha256: "045d372bf19b02aeb69cacf8b4009555fb5f6f0b7ad8016e5f46dd1387ddd492" + url: "https://pub.dev" + source: hosted + version: "0.9.2+1" + file_selector_macos: + dependency: transitive + description: + name: file_selector_macos + sha256: f42eacb83b318e183b1ae24eead1373ab1334084404c8c16e0354f9a3e55d385 + url: "https://pub.dev" + source: hosted + version: "0.9.4" + file_selector_platform_interface: + dependency: transitive + description: + name: file_selector_platform_interface + sha256: a3994c26f10378a039faa11de174d7b78eb8f79e4dd0af2a451410c1a5c3f66b + url: "https://pub.dev" + source: hosted + version: "2.6.2" + file_selector_windows: + dependency: transitive + description: + name: file_selector_windows + sha256: "2ad726953f6e8affbc4df8dc78b77c3b4a060967a291e528ef72ae846c60fb69" + url: "https://pub.dev" + source: hosted + version: "0.9.3+2" flutter: dependency: "direct main" description: flutter @@ -62,11 +150,112 @@ packages: url: "https://pub.dev" source: hosted version: "3.0.2" + flutter_plugin_android_lifecycle: + dependency: transitive + description: + name: flutter_plugin_android_lifecycle + sha256: c6b0b4c05c458e1c01ad9bcc14041dd7b1f6783d487be4386f793f47a8a4d03e + url: "https://pub.dev" + source: hosted + version: "2.0.20" flutter_test: dependency: "direct dev" description: flutter source: sdk version: "0.0.0" + flutter_web_plugins: + dependency: transitive + description: flutter + source: sdk + version: "0.0.0" + http: + dependency: "direct main" + description: + name: http + sha256: b9c29a161230ee03d3ccf545097fccd9b87a5264228c5d348202e0f0c28f9010 + url: "https://pub.dev" + source: hosted + version: "1.2.2" + http_parser: + dependency: transitive + description: + name: http_parser + sha256: "2aa08ce0341cc9b354a498388e30986515406668dbcc4f7c950c3e715496693b" + url: "https://pub.dev" + source: hosted + version: "4.0.2" + image_picker: + dependency: "direct main" + description: + name: image_picker + sha256: "021834d9c0c3de46bf0fe40341fa07168407f694d9b2bb18d532dc1261867f7a" + url: "https://pub.dev" + source: hosted + version: "1.1.2" + image_picker_android: + dependency: transitive + description: + name: image_picker_android + sha256: a26dc9a03fe042440c1e4be554fb0fceae2bf6d887d7467fc48c688fa4a81889 + url: "https://pub.dev" + source: hosted + version: "0.8.12+7" + image_picker_for_web: + dependency: transitive + description: + name: image_picker_for_web + sha256: e2423c53a68b579a7c37a1eda967b8ae536c3d98518e5db95ca1fe5719a730a3 + url: "https://pub.dev" + source: hosted + version: "3.0.2" + image_picker_ios: + dependency: transitive + description: + name: image_picker_ios + sha256: "6703696ad49f5c3c8356d576d7ace84d1faf459afb07accbb0fae780753ff447" + url: "https://pub.dev" + source: hosted + version: "0.8.12" + image_picker_linux: + dependency: transitive + description: + name: image_picker_linux + sha256: "4ed1d9bb36f7cd60aa6e6cd479779cc56a4cb4e4de8f49d487b1aaad831300fa" + url: "https://pub.dev" + source: hosted + version: "0.2.1+1" + image_picker_macos: + dependency: transitive + description: + name: image_picker_macos + sha256: "3f5ad1e8112a9a6111c46d0b57a7be2286a9a07fc6e1976fdf5be2bd31d4ff62" + url: "https://pub.dev" + source: hosted + version: "0.2.1+1" + image_picker_platform_interface: + dependency: transitive + description: + name: image_picker_platform_interface + sha256: "9ec26d410ff46f483c5519c29c02ef0e02e13a543f882b152d4bfd2f06802f80" + url: "https://pub.dev" + source: hosted + version: "2.10.0" + image_picker_windows: + dependency: transitive + description: + name: image_picker_windows + sha256: "6ad07afc4eb1bc25f3a01084d28520496c4a3bb0cb13685435838167c9dcedeb" + url: "https://pub.dev" + source: hosted + version: "0.2.1+1" + js: + dependency: transitive + description: + name: js + sha256: f2c445dce49627136094980615a031419f7f3eb393237e4ecd97ac15dea343f3 + url: "https://pub.dev" + source: hosted + version: "0.6.7" leak_tracker: dependency: transitive description: @@ -123,6 +312,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.12.0" + mime: + dependency: transitive + description: + name: mime + sha256: "2e123074287cc9fd6c09de8336dae606d1ddb88d9ac47358826db698c176a1f2" + url: "https://pub.dev" + source: hosted + version: "1.0.5" path: dependency: transitive description: @@ -131,6 +328,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: d762709c2bbe80626ecc819143013cc820fa49ca5e363620ee20a8b15a3e3daf + url: "https://pub.dev" + source: hosted + version: "2.2.1" + 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 @@ -184,6 +485,14 @@ packages: url: "https://pub.dev" source: hosted version: "0.7.0" + typed_data: + dependency: transitive + description: + name: typed_data + sha256: facc8d6582f16042dd49f2463ff1bd6e2c9ef9f3d5da3d9b087e244a7b564b3c + url: "https://pub.dev" + source: hosted + version: "1.3.2" vector_math: dependency: transitive description: @@ -200,6 +509,22 @@ packages: url: "https://pub.dev" source: hosted version: "14.2.1" + web: + dependency: transitive + description: + name: web + sha256: d43c1d6b787bf0afad444700ae7f4db8827f701bc61c255ac8d328c6f4d52062 + url: "https://pub.dev" + source: hosted + version: "1.0.0" + 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..3e96455 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -9,6 +9,13 @@ environment: dependencies: flutter: sdk: flutter + shared_preferences: ^2.2.3 + shimmer: ^3.0.0 + equatable: ^2.0.5 + elementary: ^3.0.0 + http: ^1.2.2 + dio: ^5.5.0+1 + image_picker: ^1.1.2 dev_dependencies: flutter_test: @@ -17,3 +24,12 @@ dev_dependencies: flutter: uses-material-design: true + + assets: + + fonts: + - family: Magnolia + fonts: + - asset: fonts/Magnolia.ttf + +