├── ios ├── Runner │ ├── Runner-Bridging-Header.h │ ├── Assets.xcassets │ │ ├── LaunchImage.imageset │ │ │ ├── LaunchImage.png │ │ │ ├── LaunchImage@2x.png │ │ │ ├── LaunchImage@3x.png │ │ │ ├── README.md │ │ │ └── Contents.json │ │ └── AppIcon.appiconset │ │ │ ├── Icon-App-20x20@1x.png │ │ │ ├── Icon-App-20x20@2x.png │ │ │ ├── Icon-App-20x20@3x.png │ │ │ ├── Icon-App-29x29@1x.png │ │ │ ├── Icon-App-29x29@2x.png │ │ │ ├── Icon-App-29x29@3x.png │ │ │ ├── Icon-App-40x40@1x.png │ │ │ ├── Icon-App-40x40@2x.png │ │ │ ├── Icon-App-40x40@3x.png │ │ │ ├── Icon-App-60x60@2x.png │ │ │ ├── Icon-App-60x60@3x.png │ │ │ ├── Icon-App-76x76@1x.png │ │ │ ├── Icon-App-76x76@2x.png │ │ │ ├── Icon-App-1024x1024@1x.png │ │ │ ├── Icon-App-83.5x83.5@2x.png │ │ │ └── Contents.json │ ├── AppDelegate.swift │ ├── Base.lproj │ │ ├── Main.storyboard │ │ └── LaunchScreen.storyboard │ └── Info.plist ├── Flutter │ ├── Debug.xcconfig │ ├── Release.xcconfig │ └── AppFrameworkInfo.plist ├── Runner.xcodeproj │ ├── project.xcworkspace │ │ └── contents.xcworkspacedata │ ├── xcshareddata │ │ └── xcschemes │ │ │ └── Runner.xcscheme │ └── project.pbxproj ├── Runner.xcworkspace │ └── contents.xcworkspacedata ├── .gitignore ├── Podfile.lock └── Podfile ├── assets └── images │ ├── book.png │ ├── shelf.png │ └── add_book.png ├── android ├── app │ ├── .settings │ │ └── org.eclipse.buildship.core.prefs │ ├── src │ │ ├── main │ │ │ ├── res │ │ │ │ ├── mipmap-hdpi │ │ │ │ │ └── ic_launcher.png │ │ │ │ ├── mipmap-mdpi │ │ │ │ │ └── ic_launcher.png │ │ │ │ ├── mipmap-xhdpi │ │ │ │ │ └── ic_launcher.png │ │ │ │ ├── mipmap-xxhdpi │ │ │ │ │ └── ic_launcher.png │ │ │ │ ├── mipmap-xxxhdpi │ │ │ │ │ └── ic_launcher.png │ │ │ │ ├── values │ │ │ │ │ └── styles.xml │ │ │ │ └── drawable │ │ │ │ │ └── launch_background.xml │ │ │ ├── kotlin │ │ │ │ └── com │ │ │ │ │ └── example │ │ │ │ │ └── molib │ │ │ │ │ └── MainActivity.kt │ │ │ └── AndroidManifest.xml │ │ ├── debug │ │ │ └── AndroidManifest.xml │ │ └── profile │ │ │ └── AndroidManifest.xml │ ├── .classpath │ ├── .project │ └── build.gradle ├── gradle.properties ├── .gitignore ├── gradle │ └── wrapper │ │ └── gradle-wrapper.properties ├── .settings │ └── org.eclipse.buildship.core.prefs ├── .project ├── settings.gradle └── build.gradle ├── lib ├── domain │ ├── value_objects │ │ ├── Failure.dart │ │ ├── Identity.dart │ │ ├── Title.dart │ │ ├── Author.dart │ │ ├── ISBN.dart │ │ └── PublishDate.dart │ ├── DomainExcpetion.dart │ ├── repositories │ │ ├── IBookShelfRepository.dart │ │ └── IBookRepository.dart │ ├── entities │ │ ├── Book.dart │ │ └── BookShelf.dart │ └── factories │ │ └── IEntityFactory.dart ├── application │ ├── boundaries │ │ ├── create_shelf │ │ │ ├── ICreateShelfUseCase.dart │ │ │ └── CreateShelfOuput.dart │ │ ├── get_all_books │ │ │ ├── IGetAllBooksUseCase.dart │ │ │ ├── GetAllBooksOutput.dart │ │ │ └── BookDto.dart │ │ ├── get_book_by_id │ │ │ ├── GetBookByIdInput.dart │ │ │ ├── GetBookByIdOuput.dart │ │ │ └── IGetBookByIdUseCase.dart │ │ ├── IUseCase.dart │ │ └── add_book │ │ │ ├── IAddBookUseCase.dart │ │ │ ├── AddBookOutput.dart │ │ │ └── AddBookInput.dart │ └── usecases │ │ ├── CreateShelfUseCase.dart │ │ ├── GetAllBookUseCase.dart │ │ ├── GetBookByIdUseCase.dart │ │ └── AddBookUseCase.dart ├── presentation │ ├── models │ │ ├── BookShelf.dart │ │ └── Book.dart │ ├── states │ │ ├── home_page │ │ │ ├── homepage_event.dart │ │ │ ├── homepage_state.dart │ │ │ └── homepage_bloc.dart │ │ └── add_book_page │ │ │ ├── addbook_state.dart │ │ │ ├── addbook_event.dart │ │ │ └── addbook_bloc.dart │ ├── utils │ │ └── RandomColorGenerator.dart │ ├── UI │ │ ├── widgets │ │ │ └── BookWidget.dart │ │ └── pages │ │ │ ├── HomePage.dart │ │ │ └── AddBookPage.dart │ └── viewmodels │ │ ├── HomeViewModel.dart │ │ └── AddBookViewModel.dart ├── infrastructure │ ├── models │ │ ├── ShelfModel.dart │ │ └── BookModel.dart │ ├── datasources │ │ ├── IDatasource.dart │ │ └── SqfliteDatasource.dart │ ├── repositories │ │ ├── fakes │ │ │ ├── FakeShelfRepository.dart │ │ │ └── FakeBookRepository.dart │ │ ├── ShelfRepository.dart │ │ └── BookRepsitory.dart │ └── factories │ │ ├── EntityFactory.dart │ │ └── db_factory.dart ├── main.dart └── MoLibUIComposer.dart ├── README.md ├── .metadata ├── .vscode └── settings.json ├── .gitignore ├── test ├── domain │ ├── value_objects │ │ ├── title_test.dart │ │ ├── author_test.dart │ │ ├── publishdate_test.dart │ │ └── isbn_test.dart │ └── entities │ │ └── book_shel_test.dart ├── widget_test.dart ├── infrastructue │ ├── factories │ │ └── entity_factory_test.dart │ ├── datasources │ │ └── sqflite_datasource_test.dart │ └── repositories │ │ └── book_repository_test.dart ├── application │ └── usecases │ │ ├── get_all_books_test.dart │ │ ├── get_book_by_id_test.dart │ │ └── add_book_usecase_test.dart └── presentation │ ├── add_book_viewmodel_test.dart │ ├── states │ ├── home_page_bloc_test.dart │ └── add_book_bloc_test.dart │ └── viewmodels │ └── home_viewmodel_test.dart ├── pubspec.yaml └── pubspec.lock /ios/Runner/Runner-Bridging-Header.h: -------------------------------------------------------------------------------- 1 | #import "GeneratedPluginRegistrant.h" -------------------------------------------------------------------------------- /assets/images/book.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vykes-mac/molib/HEAD/assets/images/book.png -------------------------------------------------------------------------------- /assets/images/shelf.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vykes-mac/molib/HEAD/assets/images/shelf.png -------------------------------------------------------------------------------- /assets/images/add_book.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vykes-mac/molib/HEAD/assets/images/add_book.png -------------------------------------------------------------------------------- /android/app/.settings/org.eclipse.buildship.core.prefs: -------------------------------------------------------------------------------- 1 | connection.project.dir=.. 2 | eclipse.preferences.version=1 3 | -------------------------------------------------------------------------------- /lib/domain/value_objects/Failure.dart: -------------------------------------------------------------------------------- 1 | class Failure { 2 | final String message; 3 | 4 | Failure(this.message); 5 | } 6 | -------------------------------------------------------------------------------- /ios/Flutter/Debug.xcconfig: -------------------------------------------------------------------------------- 1 | #include "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig" 2 | #include "Generated.xcconfig" 3 | -------------------------------------------------------------------------------- /android/gradle.properties: -------------------------------------------------------------------------------- 1 | org.gradle.jvmargs=-Xmx1536M 2 | android.enableR8=true 3 | android.useAndroidX=true 4 | android.enableJetifier=true 5 | -------------------------------------------------------------------------------- /ios/Flutter/Release.xcconfig: -------------------------------------------------------------------------------- 1 | #include "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig" 2 | #include "Generated.xcconfig" 3 | -------------------------------------------------------------------------------- /lib/domain/DomainExcpetion.dart: -------------------------------------------------------------------------------- 1 | class DomainException implements Exception { 2 | String message; 3 | DomainException(this.message); 4 | } 5 | -------------------------------------------------------------------------------- /android/.gitignore: -------------------------------------------------------------------------------- 1 | gradle-wrapper.jar 2 | /.gradle 3 | /captures/ 4 | /gradlew 5 | /gradlew.bat 6 | /local.properties 7 | GeneratedPluginRegistrant.java 8 | -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vykes-mac/molib/HEAD/android/app/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vykes-mac/molib/HEAD/android/app/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vykes-mac/molib/HEAD/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vykes-mac/molib/HEAD/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vykes-mac/molib/HEAD/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # molib 2 | 3 | This project explores the concepts of Domain Driven Design when creating flutter applications. 4 | 5 | Youtube: https://youtu.be/h4Bc2xc-5JY 6 | -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vykes-mac/molib/HEAD/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vykes-mac/molib/HEAD/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vykes-mac/molib/HEAD/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vykes-mac/molib/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vykes-mac/molib/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vykes-mac/molib/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vykes-mac/molib/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vykes-mac/molib/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vykes-mac/molib/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vykes-mac/molib/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vykes-mac/molib/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vykes-mac/molib/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vykes-mac/molib/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vykes-mac/molib/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vykes-mac/molib/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vykes-mac/molib/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vykes-mac/molib/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vykes-mac/molib/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png -------------------------------------------------------------------------------- /lib/application/boundaries/create_shelf/ICreateShelfUseCase.dart: -------------------------------------------------------------------------------- 1 | import 'CreateShelfOuput.dart'; 2 | 3 | abstract class ICreateShelfUseCase { 4 | Future execute(); 5 | } 6 | -------------------------------------------------------------------------------- /lib/application/boundaries/get_all_books/IGetAllBooksUseCase.dart: -------------------------------------------------------------------------------- 1 | import 'GetAllBooksOutput.dart'; 2 | 3 | abstract class IGetAllBooksUseCase { 4 | Future execute(); 5 | } 6 | -------------------------------------------------------------------------------- /lib/presentation/models/BookShelf.dart: -------------------------------------------------------------------------------- 1 | import 'package:molib/presentation/models/Book.dart'; 2 | 3 | class BookShelf { 4 | final String id; 5 | List books = []; 6 | 7 | BookShelf(this.id); 8 | } 9 | -------------------------------------------------------------------------------- /lib/application/boundaries/create_shelf/CreateShelfOuput.dart: -------------------------------------------------------------------------------- 1 | import 'package:molib/domain/value_objects/Identity.dart'; 2 | 3 | class CreateShelfOutput { 4 | final Identity shelfId; 5 | 6 | CreateShelfOutput(this.shelfId); 7 | } 8 | -------------------------------------------------------------------------------- /lib/application/boundaries/get_book_by_id/GetBookByIdInput.dart: -------------------------------------------------------------------------------- 1 | import 'package:molib/domain/value_objects/Identity.dart'; 2 | 3 | class GetBookByIdInput { 4 | final Identity bookId; 5 | 6 | GetBookByIdInput({this.bookId}); 7 | } 8 | -------------------------------------------------------------------------------- /ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /lib/application/boundaries/get_book_by_id/GetBookByIdOuput.dart: -------------------------------------------------------------------------------- 1 | import 'package:molib/application/boundaries/get_all_books/BookDto.dart'; 2 | 3 | class GetBookByIdOutput { 4 | final BookDto book; 5 | 6 | GetBookByIdOutput({this.book}); 7 | } 8 | -------------------------------------------------------------------------------- /lib/application/boundaries/get_all_books/GetAllBooksOutput.dart: -------------------------------------------------------------------------------- 1 | import 'dart:collection'; 2 | 3 | import 'BookDto.dart'; 4 | 5 | class GetAllBooksOutput { 6 | final UnmodifiableListView books; 7 | 8 | GetAllBooksOutput({this.books}); 9 | } 10 | -------------------------------------------------------------------------------- /lib/application/boundaries/IUseCase.dart: -------------------------------------------------------------------------------- 1 | import 'package:dartz/dartz.dart'; 2 | import 'package:molib/domain/value_objects/Failure.dart'; 3 | 4 | abstract class IUseCase { 5 | Future> execute(TUseCaseInput input); 6 | } 7 | -------------------------------------------------------------------------------- /android/gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Fri Jun 23 08:50:38 CEST 2017 2 | distributionBase=GRADLE_USER_HOME 3 | distributionPath=wrapper/dists 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | distributionUrl=https\://services.gradle.org/distributions/gradle-5.6.2-all.zip 7 | -------------------------------------------------------------------------------- /lib/domain/repositories/IBookShelfRepository.dart: -------------------------------------------------------------------------------- 1 | import 'package:molib/domain/entities/BookShelf.dart'; 2 | import 'package:molib/domain/value_objects/Identity.dart'; 3 | 4 | abstract class IBookShelfRepository { 5 | create(BookShelf bookShelf); 6 | Future find(Identity shelfId); 7 | } 8 | -------------------------------------------------------------------------------- /lib/application/boundaries/get_book_by_id/IGetBookByIdUseCase.dart: -------------------------------------------------------------------------------- 1 | import 'package:molib/application/boundaries/IUseCase.dart'; 2 | 3 | import 'GetBookByIdInput.dart'; 4 | import 'GetBookByIdOuput.dart'; 5 | 6 | abstract class IGetBookByIdUseCase 7 | extends IUseCase {} 8 | -------------------------------------------------------------------------------- /ios/Runner.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /.metadata: -------------------------------------------------------------------------------- 1 | # This file tracks properties of this Flutter project. 2 | # Used by Flutter tool to assess capabilities and perform upgrades etc. 3 | # 4 | # This file should be version controlled and should not be manually edited. 5 | 6 | version: 7 | revision: 0b8abb4724aa590dd0f429683339b1e045a1594d 8 | channel: stable 9 | 10 | project_type: app 11 | -------------------------------------------------------------------------------- /lib/application/boundaries/add_book/IAddBookUseCase.dart: -------------------------------------------------------------------------------- 1 | import 'package:molib/application/boundaries/IUseCase.dart'; 2 | import 'package:molib/application/boundaries/add_book/AddBookInput.dart'; 3 | import 'package:molib/application/boundaries/add_book/AddBookOutput.dart'; 4 | 5 | abstract class IAddBookUseCase extends IUseCase {} 6 | -------------------------------------------------------------------------------- /lib/domain/value_objects/Identity.dart: -------------------------------------------------------------------------------- 1 | import 'package:equatable/equatable.dart'; 2 | 3 | class Identity extends Equatable { 4 | final String id; 5 | 6 | factory Identity.fromString(String id) { 7 | return Identity._internal(id); 8 | } 9 | 10 | Identity._internal(this.id); 11 | 12 | @override 13 | List get props => [id]; 14 | } 15 | -------------------------------------------------------------------------------- /lib/presentation/states/home_page/homepage_event.dart: -------------------------------------------------------------------------------- 1 | part of 'homepage_bloc.dart'; 2 | 3 | @immutable 4 | abstract class HomePageEvent { 5 | const HomePageEvent(); 6 | factory HomePageEvent.onBookShelvesRequested() => BookShelvesRequested(); 7 | } 8 | 9 | class BookShelvesRequested extends HomePageEvent { 10 | const BookShelvesRequested(); 11 | } 12 | -------------------------------------------------------------------------------- /lib/presentation/utils/RandomColorGenerator.dart: -------------------------------------------------------------------------------- 1 | import 'dart:math'; 2 | 3 | import 'package:flutter/material.dart'; 4 | 5 | class RandomColorGenerator { 6 | static Random random = new Random(); 7 | static Color getColor() { 8 | return Color.fromARGB( 9 | 255, random.nextInt(255), random.nextInt(255), random.nextInt(255)); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /android/app/.classpath: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /android/app/src/debug/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /android/app/src/profile/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md: -------------------------------------------------------------------------------- 1 | # Launch Screen Assets 2 | 3 | You can customize the launch screen with your own desired assets by replacing the image files in this directory. 4 | 5 | You can also do it by opening your Flutter project's Xcode project with `open ios/Runner.xcworkspace`, selecting `Runner/Assets.xcassets` in the Project Navigator and dropping in the desired images. -------------------------------------------------------------------------------- /android/app/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 8 | 9 | -------------------------------------------------------------------------------- /lib/domain/repositories/IBookRepository.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/foundation.dart'; 2 | import 'package:molib/domain/entities/Book.dart'; 3 | import 'package:molib/domain/value_objects/Identity.dart'; 4 | 5 | abstract class IBookRepository { 6 | add(Book book); 7 | update(Book book); 8 | delete({@required Identity bookId}); 9 | Future find({@required Identity bookId}); 10 | Future> findAll(); 11 | } 12 | -------------------------------------------------------------------------------- /lib/infrastructure/models/ShelfModel.dart: -------------------------------------------------------------------------------- 1 | import 'package:molib/domain/value_objects/Identity.dart'; 2 | 3 | class ShelfModel { 4 | final Identity id; 5 | 6 | ShelfModel({this.id}); 7 | 8 | factory ShelfModel.fromMap(Map map) { 9 | return ShelfModel( 10 | id: Identity.fromString(map['Shelf_Id']), 11 | ); 12 | } 13 | 14 | toMap() { 15 | return {"Shelf_Id": id.id}; 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /lib/presentation/models/Book.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/foundation.dart'; 2 | 3 | class Book { 4 | final String id; 5 | final String title; 6 | final String author; 7 | final String isbn; 8 | final String publishDate; 9 | 10 | const Book({ 11 | @required this.id, 12 | @required this.title, 13 | @required this.author, 14 | @required this.isbn, 15 | @required this.publishDate, 16 | }); 17 | } 18 | -------------------------------------------------------------------------------- /android/.settings/org.eclipse.buildship.core.prefs: -------------------------------------------------------------------------------- 1 | arguments= 2 | auto.sync=false 3 | build.scans.enabled=false 4 | connection.gradle.distribution=GRADLE_DISTRIBUTION(WRAPPER) 5 | connection.project.dir= 6 | eclipse.preferences.version=1 7 | gradle.user.home= 8 | java.home=/Library/Java/JavaVirtualMachines/jdk1.8.0_131.jdk/Contents/Home 9 | jvm.arguments= 10 | offline.mode=false 11 | override.workspace.settings=true 12 | show.console.view=true 13 | show.executions.view=true 14 | -------------------------------------------------------------------------------- /ios/Runner/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | import UIKit 2 | import Flutter 3 | 4 | @UIApplicationMain 5 | @objc class AppDelegate: FlutterAppDelegate { 6 | override func application( 7 | _ application: UIApplication, 8 | didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? 9 | ) -> Bool { 10 | GeneratedPluginRegistrant.register(with: self) 11 | return super.application(application, didFinishLaunchingWithOptions: launchOptions) 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /android/app/src/main/kotlin/com/example/molib/MainActivity.kt: -------------------------------------------------------------------------------- 1 | package com.example.molib 2 | 3 | import androidx.annotation.NonNull; 4 | import io.flutter.embedding.android.FlutterActivity 5 | import io.flutter.embedding.engine.FlutterEngine 6 | import io.flutter.plugins.GeneratedPluginRegistrant 7 | 8 | class MainActivity: FlutterActivity() { 9 | override fun configureFlutterEngine(@NonNull flutterEngine: FlutterEngine) { 10 | GeneratedPluginRegistrant.registerWith(flutterEngine); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /android/.project: -------------------------------------------------------------------------------- 1 | 2 | 3 | android 4 | Project android created by Buildship. 5 | 6 | 7 | 8 | 9 | org.eclipse.buildship.core.gradleprojectbuilder 10 | 11 | 12 | 13 | 14 | 15 | org.eclipse.buildship.core.gradleprojectnature 16 | 17 | 18 | -------------------------------------------------------------------------------- /android/app/src/main/res/drawable/launch_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 12 | 13 | -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "LaunchImage.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "filename" : "LaunchImage@2x.png", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "universal", 15 | "filename" : "LaunchImage@3x.png", 16 | "scale" : "3x" 17 | } 18 | ], 19 | "info" : { 20 | "version" : 1, 21 | "author" : "xcode" 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /lib/domain/value_objects/Title.dart: -------------------------------------------------------------------------------- 1 | import 'package:dartz/dartz.dart'; 2 | import 'package:equatable/equatable.dart'; 3 | 4 | import 'Failure.dart'; 5 | 6 | class Title extends Equatable { 7 | final String value; 8 | 9 | Title._(this.value); 10 | 11 | static Either create(String value) { 12 | if (value.isEmpty || value == null) 13 | return Left(Failure('title cannot be empty of null')); 14 | else 15 | return Right(Title._(value)); 16 | } 17 | 18 | @override 19 | List get props => [value]; 20 | } 21 | -------------------------------------------------------------------------------- /android/settings.gradle: -------------------------------------------------------------------------------- 1 | include ':app' 2 | 3 | def flutterProjectRoot = rootProject.projectDir.parentFile.toPath() 4 | 5 | def plugins = new Properties() 6 | def pluginsFile = new File(flutterProjectRoot.toFile(), '.flutter-plugins') 7 | if (pluginsFile.exists()) { 8 | pluginsFile.withReader('UTF-8') { reader -> plugins.load(reader) } 9 | } 10 | 11 | plugins.each { name, path -> 12 | def pluginDirectory = flutterProjectRoot.resolve(path).resolve('android').toFile() 13 | include ":$name" 14 | project(":$name").projectDir = pluginDirectory 15 | } 16 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "files.exclude": { 3 | "android": true, 4 | "ios": true, 5 | "**/.idea": true, 6 | "**/.idea.*": true, 7 | "**/build": true, 8 | "**/build.*": true, 9 | "*.dart_tool": true, 10 | "*.dart_tool.*": true, 11 | ".vscode": true, 12 | ".gitignore": true, 13 | ".metadata": true, 14 | ".packages": true, 15 | "molib.iml": true, 16 | "pubspec.lock": true, 17 | "README.md": true, 18 | ".flutter-plugins-dependencies": true, 19 | ".flutter-plugins": true 20 | }, 21 | "editor.fontSize": 20 22 | } -------------------------------------------------------------------------------- /lib/infrastructure/datasources/IDatasource.dart: -------------------------------------------------------------------------------- 1 | import 'package:molib/domain/value_objects/Identity.dart'; 2 | import 'package:molib/infrastructure/models/BookModel.dart'; 3 | import 'package:molib/infrastructure/models/ShelfModel.dart'; 4 | 5 | abstract class IDatasource { 6 | addBook(BookModel model); 7 | Future> findAllBooks(); 8 | Future findBook(Identity bookId); 9 | Future createShelf(ShelfModel model); 10 | Future findShelf(Identity shelfId); 11 | Future> findBooksOnSelf(Identity shelfId); 12 | } 13 | -------------------------------------------------------------------------------- /lib/domain/value_objects/Author.dart: -------------------------------------------------------------------------------- 1 | import 'package:dartz/dartz.dart'; 2 | import 'package:equatable/equatable.dart'; 3 | import 'package:molib/domain/value_objects/Failure.dart'; 4 | 5 | class Author extends Equatable { 6 | final String value; 7 | 8 | Author._(this.value); 9 | 10 | static Either create(String value) { 11 | if (value.isEmpty || value == null) 12 | return Left(Failure('author cannot be empty or null')); 13 | else 14 | return Right(Author._(value)); 15 | } 16 | 17 | @override 18 | List get props => [value]; 19 | } 20 | -------------------------------------------------------------------------------- /lib/domain/entities/Book.dart: -------------------------------------------------------------------------------- 1 | import 'package:molib/domain/value_objects/Author.dart'; 2 | import 'package:molib/domain/value_objects/ISBN.dart'; 3 | import 'package:molib/domain/value_objects/Identity.dart'; 4 | import 'package:molib/domain/value_objects/PublishDate.dart'; 5 | import 'package:molib/domain/value_objects/Title.dart'; 6 | 7 | class Book { 8 | Identity id; 9 | Identity shelfId; 10 | Title title; 11 | Author author; 12 | ISBN isbn; 13 | PublishDate publishDate; 14 | 15 | Book({ 16 | this.id, 17 | this.shelfId, 18 | this.title, 19 | this.author, 20 | this.isbn, 21 | this.publishDate, 22 | }); 23 | } 24 | -------------------------------------------------------------------------------- /lib/domain/entities/BookShelf.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/foundation.dart'; 2 | import 'package:molib/domain/value_objects/Identity.dart'; 3 | 4 | import '../DomainExcpetion.dart'; 5 | import 'Book.dart'; 6 | 7 | class BookShelf { 8 | static const MAX_CAPACITY = 10; 9 | Identity id; 10 | List _books; 11 | List get books => _books; 12 | 13 | BookShelf({@required this.id}) { 14 | _books = List(); 15 | } 16 | 17 | addBook(Book book) { 18 | if (_books.length == MAX_CAPACITY) 19 | throw DomainException('Book shelf has reached maximum capacity'); 20 | book.shelfId = id; 21 | _books.add(book); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /lib/main.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:molib/MoLibUIComposer.dart'; 3 | 4 | import 'infrastructure/factories/db_factory.dart'; 5 | 6 | void main() async { 7 | WidgetsFlutterBinding.ensureInitialized(); 8 | final db = await DatabaseFactory().createDatabase(); 9 | MoLibUIComposer.configure(db); 10 | runApp(MyApp()); 11 | } 12 | 13 | class MyApp extends StatelessWidget { 14 | @override 15 | Widget build(BuildContext context) { 16 | return MaterialApp( 17 | title: 'My Virtual Library', 18 | debugShowCheckedModeBanner: false, 19 | home: MoLibUIComposer.composeHomePage(), 20 | ); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /lib/infrastructure/repositories/fakes/FakeShelfRepository.dart: -------------------------------------------------------------------------------- 1 | import 'package:molib/domain/entities/BookShelf.dart'; 2 | import 'package:molib/domain/repositories/IBookShelfRepository.dart'; 3 | import 'package:molib/domain/value_objects/Identity.dart'; 4 | 5 | class FakeShelfRepository implements IBookShelfRepository { 6 | List shelves = [BookShelf(id: Identity.fromString('bbb'))]; 7 | 8 | @override 9 | Future create(BookShelf bookShelf) async { 10 | return shelves.add(bookShelf); 11 | } 12 | 13 | @override 14 | Future find(Identity shelfId) async { 15 | return shelves.where((sh) => sh.id == shelfId).first; 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /ios/.gitignore: -------------------------------------------------------------------------------- 1 | *.mode1v3 2 | *.mode2v3 3 | *.moved-aside 4 | *.pbxuser 5 | *.perspectivev3 6 | **/*sync/ 7 | .sconsign.dblite 8 | .tags* 9 | **/.vagrant/ 10 | **/DerivedData/ 11 | Icon? 12 | **/Pods/ 13 | **/.symlinks/ 14 | profile 15 | xcuserdata 16 | **/.generated/ 17 | Flutter/App.framework 18 | Flutter/Flutter.framework 19 | Flutter/Flutter.podspec 20 | Flutter/Generated.xcconfig 21 | Flutter/app.flx 22 | Flutter/app.zip 23 | Flutter/flutter_assets/ 24 | Flutter/flutter_export_environment.sh 25 | ServiceDefinitions.json 26 | Runner/GeneratedPluginRegistrant.* 27 | 28 | # Exceptions to above rules. 29 | !default.mode1v3 30 | !default.mode2v3 31 | !default.pbxuser 32 | !default.perspectivev3 33 | -------------------------------------------------------------------------------- /lib/domain/factories/IEntityFactory.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/foundation.dart'; 2 | import 'package:molib/domain/entities/Book.dart'; 3 | import 'package:molib/domain/entities/BookShelf.dart'; 4 | import 'package:molib/domain/value_objects/Author.dart'; 5 | import 'package:molib/domain/value_objects/ISBN.dart'; 6 | import 'package:molib/domain/value_objects/PublishDate.dart'; 7 | import 'package:molib/domain/value_objects/Title.dart'; 8 | 9 | abstract class IEntityFactory { 10 | Book newBook({ 11 | @required Title title, 12 | @required Author author, 13 | @required ISBN isbn, 14 | @required PublishDate publishDate, 15 | }); 16 | 17 | BookShelf newBookShelf(); 18 | } 19 | -------------------------------------------------------------------------------- /android/app/.project: -------------------------------------------------------------------------------- 1 | 2 | 3 | app 4 | Project app created by Buildship. 5 | 6 | 7 | 8 | 9 | org.eclipse.jdt.core.javabuilder 10 | 11 | 12 | 13 | 14 | org.eclipse.buildship.core.gradleprojectbuilder 15 | 16 | 17 | 18 | 19 | 20 | org.eclipse.jdt.core.javanature 21 | org.eclipse.buildship.core.gradleprojectnature 22 | 23 | 24 | -------------------------------------------------------------------------------- /android/build.gradle: -------------------------------------------------------------------------------- 1 | buildscript { 2 | ext.kotlin_version = '1.3.50' 3 | repositories { 4 | google() 5 | jcenter() 6 | } 7 | 8 | dependencies { 9 | classpath 'com.android.tools.build:gradle:3.5.0' 10 | classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" 11 | } 12 | } 13 | 14 | allprojects { 15 | repositories { 16 | google() 17 | jcenter() 18 | } 19 | } 20 | 21 | rootProject.buildDir = '../build' 22 | subprojects { 23 | project.buildDir = "${rootProject.buildDir}/${project.name}" 24 | } 25 | subprojects { 26 | project.evaluationDependsOn(':app') 27 | } 28 | 29 | task clean(type: Delete) { 30 | delete rootProject.buildDir 31 | } 32 | -------------------------------------------------------------------------------- /ios/Podfile.lock: -------------------------------------------------------------------------------- 1 | PODS: 2 | - Flutter (1.0.0) 3 | - FMDB (2.7.5): 4 | - FMDB/standard (= 2.7.5) 5 | - FMDB/standard (2.7.5) 6 | - sqflite (0.0.1): 7 | - Flutter 8 | - FMDB (~> 2.7.2) 9 | 10 | DEPENDENCIES: 11 | - Flutter (from `Flutter`) 12 | - sqflite (from `.symlinks/plugins/sqflite/ios`) 13 | 14 | SPEC REPOS: 15 | trunk: 16 | - FMDB 17 | 18 | EXTERNAL SOURCES: 19 | Flutter: 20 | :path: Flutter 21 | sqflite: 22 | :path: ".symlinks/plugins/sqflite/ios" 23 | 24 | SPEC CHECKSUMS: 25 | Flutter: 0e3d915762c693b495b44d77113d4970485de6ec 26 | FMDB: 2ce00b547f966261cd18927a3ddb07cb6f3db82a 27 | sqflite: 4001a31ff81d210346b500c55b17f4d6c7589dd0 28 | 29 | PODFILE CHECKSUM: 1b66dae606f75376c5f2135a8290850eeb09ae83 30 | 31 | COCOAPODS: 1.9.1 32 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Miscellaneous 2 | *.class 3 | *.log 4 | *.pyc 5 | *.swp 6 | .DS_Store 7 | .atom/ 8 | .buildlog/ 9 | .history 10 | .svn/ 11 | 12 | # IntelliJ related 13 | *.iml 14 | *.ipr 15 | *.iws 16 | .idea/ 17 | 18 | # The .vscode folder contains launch configuration and tasks you configure in 19 | # VS Code which you may wish to be included in version control, so this line 20 | # is commented out by default. 21 | #.vscode/ 22 | 23 | # Flutter/Dart/Pub related 24 | **/doc/api/ 25 | .dart_tool/ 26 | .flutter-plugins 27 | .flutter-plugins-dependencies 28 | .packages 29 | .pub-cache/ 30 | .pub/ 31 | /build/ 32 | 33 | # Web related 34 | lib/generated_plugin_registrant.dart 35 | 36 | # Exceptions to above rules. 37 | !/packages/flutter_tools/test/data/dart_dependencies_test/**/.packages 38 | -------------------------------------------------------------------------------- /lib/application/usecases/CreateShelfUseCase.dart: -------------------------------------------------------------------------------- 1 | import 'package:molib/application/boundaries/create_shelf/CreateShelfOuput.dart'; 2 | import 'package:molib/application/boundaries/create_shelf/ICreateShelfUseCase.dart'; 3 | import 'package:molib/domain/factories/IEntityFactory.dart'; 4 | import 'package:molib/domain/repositories/IBookShelfRepository.dart'; 5 | 6 | class CreateShelfUseCase implements ICreateShelfUseCase { 7 | final IBookShelfRepository _shelfRepository; 8 | final IEntityFactory _entityFactory; 9 | 10 | CreateShelfUseCase(this._shelfRepository, this._entityFactory); 11 | 12 | @override 13 | Future execute() async { 14 | var bookShelf = _entityFactory.newBookShelf(); 15 | await _shelfRepository.create(bookShelf); 16 | return CreateShelfOutput(bookShelf.id); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /test/domain/value_objects/title_test.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter_test/flutter_test.dart'; 2 | import 'package:matcher/matcher.dart' as matcher; 3 | import 'package:molib/domain/value_objects/Failure.dart'; 4 | import 'package:molib/domain/value_objects/Title.dart'; 5 | 6 | void main() { 7 | group('Title', () { 8 | test('should return Failure when value is empty', () { 9 | //arrange 10 | var title = Title.create('').fold((err) => err, (title) => title); 11 | 12 | //assert 13 | expect(title, matcher.TypeMatcher()); 14 | }); 15 | 16 | test('should create title when value is not empty', () { 17 | //arrange 18 | var str = 'Programming 101'; 19 | var title = Title.create(str).getOrElse(null); 20 | 21 | //assert 22 | expect(title.value, str); 23 | }); 24 | }); 25 | } 26 | -------------------------------------------------------------------------------- /lib/application/boundaries/add_book/AddBookOutput.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/foundation.dart'; 2 | import 'package:molib/domain/value_objects/Author.dart'; 3 | import 'package:molib/domain/value_objects/ISBN.dart'; 4 | import 'package:molib/domain/value_objects/Identity.dart'; 5 | import 'package:molib/domain/value_objects/PublishDate.dart'; 6 | import 'package:molib/domain/value_objects/Title.dart'; 7 | 8 | class AddBookOutput { 9 | final Identity bookId; 10 | final Identity shelfId; 11 | final Title title; 12 | final Author author; 13 | final ISBN isbn; 14 | final PublishDate publishDate; 15 | 16 | const AddBookOutput({ 17 | @required this.bookId, 18 | @required this.shelfId, 19 | @required this.title, 20 | @required this.author, 21 | @required this.isbn, 22 | @required this.publishDate, 23 | }); 24 | } 25 | -------------------------------------------------------------------------------- /test/domain/value_objects/author_test.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter_test/flutter_test.dart'; 2 | import 'package:matcher/matcher.dart' as matcher; 3 | import 'package:molib/domain/value_objects/Author.dart'; 4 | import 'package:molib/domain/value_objects/Failure.dart'; 5 | 6 | void main() { 7 | group('Author', () { 8 | test('should return Failure when value is empty', () { 9 | //arrange 10 | var author = Author.create('').fold((err) => err, (title) => title); 11 | 12 | //assert 13 | expect(author, matcher.TypeMatcher()); 14 | }); 15 | 16 | test('should create author when value is not empty', () { 17 | //arrange 18 | var str = 'Programming 101'; 19 | var author = Author.create(str).getOrElse(null); 20 | 21 | //assert 22 | expect(author.value, str); 23 | }); 24 | }); 25 | } 26 | -------------------------------------------------------------------------------- /lib/domain/value_objects/ISBN.dart: -------------------------------------------------------------------------------- 1 | import 'package:dartz/dartz.dart'; 2 | import 'package:equatable/equatable.dart'; 3 | 4 | import 'Failure.dart'; 5 | 6 | class ISBN extends Equatable { 7 | final String value; 8 | 9 | ISBN._(this.value); 10 | 11 | static Either create(String value) { 12 | if (!_isValid(value)) 13 | return Left(Failure('invalid isbn')); 14 | else 15 | return Right(ISBN._(value)); 16 | } 17 | 18 | static bool _isValid(String value) { 19 | var regex = 20 | r'^(?:ISBN(?:-1[03])?:? )?(?=[0-9X]{10}$|(?=(?:[0-9]+[- ]){3})[- 0-9X]{13}$|97[89][0-9]{10}$|(?=(?:[0-9]+[- ]){4})[- 0-9]{17}$)(?:97[89][- ]?)?[0-9]{1,5}[- ]?[0-9]+[- ]?[0-9]+[- ]?[0-9X]$'; 21 | 22 | RegExp exp = RegExp(regex); 23 | 24 | return exp.hasMatch(value); 25 | } 26 | 27 | @override 28 | List get props => [value]; 29 | } 30 | -------------------------------------------------------------------------------- /test/domain/value_objects/publishdate_test.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter_test/flutter_test.dart'; 2 | import 'package:matcher/matcher.dart' as matcher; 3 | import 'package:molib/domain/value_objects/Failure.dart'; 4 | import 'package:molib/domain/value_objects/PublishDate.dart'; 5 | 6 | void main() { 7 | group('PublishDate', () { 8 | test('should return failure when date is not formatted properly', () { 9 | //arrange 10 | var date = 11 | PublishDate.create('2019.01.20').fold((err) => err, (_) => null); 12 | 13 | //assert 14 | expect(date, matcher.TypeMatcher()); 15 | }); 16 | 17 | test('should set date when date is valid', () { 18 | //arrange 19 | var dateStr = '2019-01-20'; 20 | var date = PublishDate.create(dateStr).getOrElse(null); 21 | 22 | //assert 23 | expect(date.toDate(), DateTime(2019, 01, 20)); 24 | }); 25 | }); 26 | } 27 | -------------------------------------------------------------------------------- /lib/domain/value_objects/PublishDate.dart: -------------------------------------------------------------------------------- 1 | import 'package:dartz/dartz.dart'; 2 | import 'package:equatable/equatable.dart'; 3 | import 'package:intl/intl.dart'; 4 | 5 | import 'Failure.dart'; 6 | 7 | ///PublishDate must be formatted as [YYYY-MM-DD] 8 | ///eg. [2019-01-20] 9 | class PublishDate extends Equatable { 10 | final String _value; 11 | 12 | PublishDate._(this._value); 13 | 14 | static Either create(String value) { 15 | final formatter = DateFormat('yyyy-MM-dd'); 16 | try { 17 | formatter.parseStrict(value); 18 | } catch (e) { 19 | return Left(Failure('incorrect date format [yyyy-mm-dd]')); 20 | } 21 | return Right(PublishDate._(value)); 22 | } 23 | 24 | DateTime toDate() => DateTime.parse(_value); 25 | 26 | @override 27 | String toString() { 28 | return this._value; 29 | } 30 | 31 | @override 32 | List get props => [_value]; 33 | } 34 | -------------------------------------------------------------------------------- /ios/Flutter/AppFrameworkInfo.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | App 9 | CFBundleIdentifier 10 | io.flutter.flutter.app 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | App 15 | CFBundlePackageType 16 | FMWK 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1.0 23 | MinimumOSVersion 24 | 8.0 25 | 26 | 27 | -------------------------------------------------------------------------------- /lib/application/usecases/GetAllBookUseCase.dart: -------------------------------------------------------------------------------- 1 | import 'dart:collection'; 2 | 3 | import 'package:molib/application/boundaries/get_all_books/BookDto.dart'; 4 | import 'package:molib/application/boundaries/get_all_books/GetAllBooksOutput.dart'; 5 | import 'package:molib/application/boundaries/get_all_books/IGetAllBooksUseCase.dart'; 6 | import 'package:molib/domain/repositories/IBookRepository.dart'; 7 | 8 | class GetAllBooksUseCase implements IGetAllBooksUseCase { 9 | final IBookRepository _bookRepository; 10 | 11 | GetAllBooksUseCase({IBookRepository bookRepository}) 12 | : _bookRepository = bookRepository; 13 | 14 | @override 15 | Future execute() async { 16 | var books = await _bookRepository.findAll(); 17 | 18 | List output = 19 | books.map((book) => BookDto.fromEntity(book)).toList(); 20 | return GetAllBooksOutput(books: UnmodifiableListView(output)); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /lib/application/boundaries/add_book/AddBookInput.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/foundation.dart'; 2 | import 'package:molib/domain/value_objects/Author.dart'; 3 | import 'package:molib/domain/value_objects/ISBN.dart'; 4 | import 'package:molib/domain/value_objects/Identity.dart'; 5 | import 'package:molib/domain/value_objects/PublishDate.dart'; 6 | import 'package:molib/domain/value_objects/Title.dart'; 7 | 8 | class AddBookInput { 9 | final Identity shelfId; 10 | final Title title; 11 | final Author author; 12 | final ISBN isbn; 13 | final PublishDate publishDate; 14 | 15 | const AddBookInput({ 16 | @required this.shelfId, 17 | @required this.title, 18 | @required this.author, 19 | @required this.isbn, 20 | @required this.publishDate, 21 | }) : assert(shelfId != null), 22 | assert(title != null), 23 | assert(author != null), 24 | assert(isbn != null), 25 | assert(publishDate != null); 26 | } 27 | -------------------------------------------------------------------------------- /lib/application/boundaries/get_all_books/BookDto.dart: -------------------------------------------------------------------------------- 1 | import 'package:molib/domain/entities/Book.dart'; 2 | import 'package:molib/domain/value_objects/Author.dart'; 3 | import 'package:molib/domain/value_objects/ISBN.dart'; 4 | import 'package:molib/domain/value_objects/Identity.dart'; 5 | import 'package:molib/domain/value_objects/PublishDate.dart'; 6 | import 'package:molib/domain/value_objects/Title.dart'; 7 | 8 | class BookDto { 9 | Identity id; 10 | Identity shelfId; 11 | Title title; 12 | Author author; 13 | ISBN isbn; 14 | PublishDate publishDate; 15 | 16 | BookDto({ 17 | this.id, 18 | this.shelfId, 19 | this.title, 20 | this.author, 21 | this.isbn, 22 | this.publishDate, 23 | }); 24 | 25 | BookDto.fromEntity(Book book) 26 | : id = book.id, 27 | shelfId = book.shelfId, 28 | title = book.title, 29 | author = book.author, 30 | isbn = book.isbn, 31 | publishDate = book.publishDate; 32 | } 33 | -------------------------------------------------------------------------------- /lib/presentation/states/add_book_page/addbook_state.dart: -------------------------------------------------------------------------------- 1 | part of 'addbook_bloc.dart'; 2 | 3 | @immutable 4 | abstract class AddBookState { 5 | final Book book; 6 | final String errMessage; 7 | final bool isCommitting; 8 | const AddBookState( 9 | this.book, 10 | this.isCommitting, 11 | this.errMessage, 12 | ); 13 | 14 | factory AddBookState.initial() => 15 | _AddBookState(book: null, errMessage: '', isCommitting: false); 16 | 17 | factory AddBookState.bookAdded( 18 | {Book book, String errMessage, bool isCommitting}) => 19 | _AddBookState( 20 | book: book, 21 | errMessage: errMessage, 22 | isCommitting: isCommitting, 23 | ); 24 | } 25 | 26 | class _AddBookState extends AddBookState { 27 | final Book book; 28 | final String errMessage; 29 | final bool isCommitting; 30 | const _AddBookState({ 31 | this.book, 32 | this.isCommitting, 33 | this.errMessage, 34 | }) : super( 35 | book, 36 | isCommitting, 37 | errMessage, 38 | ); 39 | } 40 | -------------------------------------------------------------------------------- /test/domain/value_objects/isbn_test.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter_test/flutter_test.dart'; 2 | import 'package:matcher/matcher.dart' as matcher; 3 | import 'package:molib/domain/value_objects/Failure.dart'; 4 | import 'package:molib/domain/value_objects/ISBN.dart'; 5 | 6 | void main() { 7 | group('ISBN', () { 8 | test('should return failure when invalid isbn', () { 9 | //arrange 10 | var isbn = ISBN.create('89990').fold((err) => err, (isbn) => isbn); 11 | 12 | //arrange 13 | expect(isbn, matcher.TypeMatcher()); 14 | }); 15 | 16 | test('should return isbn when value is valid isbn-10', () { 17 | //arrange 18 | String str = 'ISBN-10: 0-596-52068-9'; 19 | var isbn = ISBN.create(str).getOrElse(null); 20 | 21 | expect(isbn.value, str); 22 | }); 23 | 24 | test('should return isbn when value is valid isbn-13', () { 25 | //arrange 26 | String str = 'ISBN-13: 978-0-596-52068-7'; 27 | var isbn = ISBN.create(str).getOrElse(null); 28 | 29 | expect(isbn.value, str); 30 | }); 31 | }); 32 | } 33 | -------------------------------------------------------------------------------- /lib/application/usecases/GetBookByIdUseCase.dart: -------------------------------------------------------------------------------- 1 | import 'package:dartz/dartz.dart'; 2 | import 'package:molib/application/boundaries/get_all_books/BookDto.dart'; 3 | import 'package:molib/application/boundaries/get_book_by_id/GetBookByIdInput.dart'; 4 | import 'package:molib/application/boundaries/get_book_by_id/GetBookByIdOuput.dart'; 5 | import 'package:molib/application/boundaries/get_book_by_id/IGetBookByIdUseCase.dart'; 6 | import 'package:molib/domain/repositories/IBookRepository.dart'; 7 | import 'package:molib/domain/value_objects/Failure.dart'; 8 | 9 | class GetBookByIdUseCase implements IGetBookByIdUseCase { 10 | final IBookRepository _bookRepository; 11 | 12 | GetBookByIdUseCase({IBookRepository bookRepository}) 13 | : _bookRepository = bookRepository; 14 | @override 15 | Future> execute( 16 | GetBookByIdInput input) async { 17 | var book = await _bookRepository.find(bookId: input.bookId); 18 | 19 | if (book == null) return Left(Failure('Book not found')); 20 | 21 | return Right(GetBookByIdOutput(book: BookDto.fromEntity(book))); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /lib/presentation/states/home_page/homepage_state.dart: -------------------------------------------------------------------------------- 1 | part of 'homepage_bloc.dart'; 2 | 3 | @immutable 4 | abstract class HomePageState { 5 | final List shelves; 6 | final bool isFetching; 7 | final String errMessages; 8 | 9 | HomePageState({this.shelves, this.isFetching, this.errMessages}); 10 | 11 | factory HomePageState.initial() => 12 | _HomePageState(shelves: [], isFetching: false, errMessages: ''); 13 | 14 | factory HomePageState.onBookShelvesRequested({ 15 | List shelves, 16 | bool isFetching, 17 | String errorMessage, 18 | }) => 19 | _HomePageState( 20 | shelves: shelves, 21 | isFetching: isFetching, 22 | errMessages: errorMessage, 23 | ); 24 | } 25 | 26 | class _HomePageState extends HomePageState { 27 | final List shelves; 28 | final bool isFetching; 29 | final String errMessages; 30 | 31 | _HomePageState({this.shelves, this.isFetching, this.errMessages}) 32 | : super( 33 | shelves: shelves, 34 | isFetching: isFetching, 35 | errMessages: errMessages, 36 | ); 37 | } 38 | -------------------------------------------------------------------------------- /test/domain/entities/book_shel_test.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter_test/flutter_test.dart'; 2 | import 'package:matcher/matcher.dart' as matcher; 3 | import 'package:molib/domain/DomainExcpetion.dart'; 4 | import 'package:molib/domain/entities/Book.dart'; 5 | import 'package:molib/domain/entities/BookShelf.dart'; 6 | import 'package:molib/domain/value_objects/Identity.dart'; 7 | 8 | void main() { 9 | BookShelf sut; 10 | 11 | setUp(() { 12 | sut = BookShelf(id: Identity.fromString('aaa')); 13 | }); 14 | 15 | group('BookShelf', () { 16 | test( 17 | 'addBook should throw and DomainException when bookshelf exceeds its capacity', 18 | () { 19 | //arrange 20 | for (int i = 0; i < 10; i++) { 21 | sut.addBook(Book()); 22 | } 23 | 24 | expect(() => sut.addBook(Book()), 25 | throwsA(matcher.TypeMatcher())); 26 | }); 27 | 28 | test('addBook should add book to shelf', () { 29 | //arrange 30 | var book = Book(); 31 | 32 | //act 33 | sut.addBook(book); 34 | 35 | //assert 36 | expect(sut.books.length, 1); 37 | }); 38 | }); 39 | } 40 | -------------------------------------------------------------------------------- /lib/infrastructure/repositories/ShelfRepository.dart: -------------------------------------------------------------------------------- 1 | import 'package:molib/domain/entities/BookShelf.dart'; 2 | import 'package:molib/domain/repositories/IBookShelfRepository.dart'; 3 | import 'package:molib/domain/value_objects/Identity.dart'; 4 | import 'package:molib/infrastructure/datasources/IDatasource.dart'; 5 | import 'package:molib/infrastructure/models/BookModel.dart'; 6 | import 'package:molib/infrastructure/models/ShelfModel.dart'; 7 | 8 | class ShelfRepository implements IBookShelfRepository { 9 | IDatasource _datasource; 10 | 11 | ShelfRepository(this._datasource); 12 | 13 | @override 14 | create(BookShelf bookShelf) async { 15 | var shelModel = ShelfModel(id: bookShelf.id); 16 | return await _datasource.createShelf(shelModel); 17 | } 18 | 19 | @override 20 | Future find(Identity shelfId) async { 21 | ShelfModel shelf = await _datasource.findShelf(shelfId); 22 | List books = []; 23 | if (shelf != null) books = await _datasource.findBooksOnSelf(shelfId); 24 | 25 | BookShelf bookShelf = BookShelf(id: shelfId); 26 | books.forEach((b) => bookShelf.addBook(b)); 27 | return bookShelf; 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /test/widget_test.dart: -------------------------------------------------------------------------------- 1 | // This is a basic Flutter widget test. 2 | // 3 | // To perform an interaction with a widget in your test, use the WidgetTester 4 | // utility that Flutter provides. For example, you can send tap and scroll 5 | // gestures. You can also use WidgetTester to find child widgets in the widget 6 | // tree, read text, and verify that the values of widget properties are correct. 7 | 8 | import 'package:flutter/material.dart'; 9 | import 'package:flutter_test/flutter_test.dart'; 10 | 11 | import 'package:molib/main.dart'; 12 | 13 | void main() { 14 | testWidgets('Counter increments smoke test', (WidgetTester tester) async { 15 | // Build our app and trigger a frame. 16 | await tester.pumpWidget(MyApp()); 17 | 18 | // Verify that our counter starts at 0. 19 | expect(find.text('0'), findsOneWidget); 20 | expect(find.text('1'), findsNothing); 21 | 22 | // Tap the '+' icon and trigger a frame. 23 | await tester.tap(find.byIcon(Icons.add)); 24 | await tester.pump(); 25 | 26 | // Verify that our counter has incremented. 27 | expect(find.text('0'), findsNothing); 28 | expect(find.text('1'), findsOneWidget); 29 | }); 30 | } 31 | -------------------------------------------------------------------------------- /lib/infrastructure/factories/EntityFactory.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/foundation.dart'; 2 | import 'package:molib/domain/entities/Book.dart'; 3 | import 'package:molib/domain/entities/BookShelf.dart'; 4 | import 'package:molib/domain/factories/IEntityFactory.dart'; 5 | import 'package:molib/domain/value_objects/Author.dart'; 6 | import 'package:molib/domain/value_objects/ISBN.dart'; 7 | import 'package:molib/domain/value_objects/Identity.dart'; 8 | import 'package:molib/domain/value_objects/PublishDate.dart'; 9 | import 'package:molib/domain/value_objects/Title.dart'; 10 | import 'package:uuid/uuid.dart'; 11 | 12 | class EntityFactory implements IEntityFactory { 13 | @override 14 | Book newBook({ 15 | @required Title title, 16 | @required Author author, 17 | @required ISBN isbn, 18 | @required PublishDate publishDate, 19 | }) { 20 | return Book( 21 | id: Identity.fromString(Uuid().v4()), 22 | title: title, 23 | author: author, 24 | isbn: isbn, 25 | publishDate: publishDate); 26 | } 27 | 28 | @override 29 | BookShelf newBookShelf() { 30 | return BookShelf(id: Identity.fromString(Uuid().v4())); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /test/infrastructue/factories/entity_factory_test.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter_test/flutter_test.dart'; 2 | import 'package:molib/domain/value_objects/Author.dart'; 3 | import 'package:molib/domain/value_objects/ISBN.dart'; 4 | import 'package:molib/domain/value_objects/PublishDate.dart'; 5 | import 'package:molib/domain/value_objects/Title.dart'; 6 | import 'package:molib/infrastructure/factories/EntityFactory.dart'; 7 | 8 | void main() { 9 | EntityFactory sut; 10 | 11 | setUp(() { 12 | sut = EntityFactory(); 13 | }); 14 | 15 | test('should create a book from the provided information', () { 16 | //arrange 17 | var title = Title.create('Domain Driven Design').getOrElse(null); 18 | var author = Author.create('Vaugh Vernon').getOrElse(null); 19 | var isbn = ISBN.create('ISBN-10: 0-596-52068-9').getOrElse(null); 20 | var publishDate = PublishDate.create('2019-01-20').getOrElse(null); 21 | 22 | //act 23 | var book = sut.newBook( 24 | title: title, 25 | author: author, 26 | isbn: isbn, 27 | publishDate: publishDate, 28 | ); 29 | 30 | //assert 31 | expect(book.id, isNotNull); 32 | expect(book.title, title); 33 | }); 34 | } 35 | -------------------------------------------------------------------------------- /lib/presentation/states/add_book_page/addbook_event.dart: -------------------------------------------------------------------------------- 1 | part of 'addbook_bloc.dart'; 2 | 3 | @immutable 4 | abstract class AddBookEvent { 5 | final String title; 6 | final String author; 7 | final String isbn; 8 | final String publishDate; 9 | final String shelfId; 10 | 11 | const AddBookEvent( 12 | this.title, 13 | this.author, 14 | this.isbn, 15 | this.publishDate, 16 | this.shelfId, 17 | ); 18 | 19 | factory AddBookEvent.onAddBookRequested({ 20 | @required String title, 21 | @required String author, 22 | @required String isbn, 23 | @required String publishDate, 24 | @required String shelfId, 25 | }) => 26 | _AddBookEvent( 27 | title, 28 | author, 29 | isbn, 30 | publishDate, 31 | shelfId, 32 | ); 33 | } 34 | 35 | class _AddBookEvent extends AddBookEvent { 36 | final String title; 37 | final String author; 38 | final String isbn; 39 | final String publishDate; 40 | final String shelfId; 41 | 42 | const _AddBookEvent( 43 | this.title, 44 | this.author, 45 | this.isbn, 46 | this.publishDate, 47 | this.shelfId, 48 | ) : super( 49 | title, 50 | author, 51 | isbn, 52 | publishDate, 53 | shelfId, 54 | ); 55 | } 56 | -------------------------------------------------------------------------------- /test/application/usecases/get_all_books_test.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter_test/flutter_test.dart'; 2 | import 'package:mockito/mockito.dart'; 3 | import 'package:molib/application/usecases/GetAllBookUseCase.dart'; 4 | import 'package:molib/domain/entities/Book.dart'; 5 | 6 | import 'add_book_usecase_test.dart'; 7 | 8 | void main() { 9 | GetAllBooksUseCase sut; 10 | MockBookRepository mockBookRepository; 11 | 12 | setUp(() { 13 | mockBookRepository = MockBookRepository(); 14 | sut = GetAllBooksUseCase(bookRepository: mockBookRepository); 15 | }); 16 | 17 | group('GetAllBooksUseCase', () { 18 | test('should return an empty list when no books are found', () async { 19 | //arrange 20 | when(mockBookRepository.findAll()).thenAnswer((_) async => []); 21 | 22 | //act 23 | var result = await sut.execute(); 24 | 25 | //assert 26 | expect(result.books, isEmpty); 27 | }); 28 | 29 | test('should return list of books', () async { 30 | //arrange 31 | var books = [Book()]; 32 | when(mockBookRepository.findAll()).thenAnswer((_) async => books); 33 | 34 | //act 35 | var result = await sut.execute(); 36 | 37 | //assert 38 | expect(result.books, isNotEmpty); 39 | verify(mockBookRepository.findAll()); 40 | }); 41 | }); 42 | } 43 | -------------------------------------------------------------------------------- /lib/infrastructure/factories/db_factory.dart: -------------------------------------------------------------------------------- 1 | import 'package:path/path.dart'; 2 | import 'package:sqflite/sqflite.dart'; 3 | 4 | class DatabaseFactory { 5 | Future createDatabase() async { 6 | String databasesPath = await getDatabasesPath(); 7 | String dbPath = join(databasesPath, 'molib.db'); 8 | 9 | var database = await openDatabase(dbPath, version: 1, onCreate: populateDb); 10 | return database; 11 | } 12 | 13 | void populateDb(Database db, int version) async { 14 | await _createBookTable(db); 15 | await _createShelfTable(db); 16 | } 17 | 18 | _createBookTable(Database db) async { 19 | await db 20 | .execute( 21 | """CREATE TABLE books( 22 | Book_Id TEXT PRIMARY KEY, 23 | Shelf_Id TEXT, 24 | Title TEXT, 25 | Author Text, 26 | ISBN TEXT, 27 | Publish_Date TEXT)""", 28 | ) 29 | .then((_) => print('creating table books...')) 30 | .catchError((e) => print('error creating books table: $e')); 31 | } 32 | 33 | _createShelfTable(Database db) async { 34 | await db 35 | .execute(""" 36 | CREATE TABLE shelf(Shelf_Id TEXT PRIMARY KEY) 37 | """) 38 | .then((_) => print('creating table shelf')) 39 | .catchError((e) => print('error creating shelf table: $e')); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /lib/infrastructure/repositories/BookRepsitory.dart: -------------------------------------------------------------------------------- 1 | import 'package:molib/domain/entities/Book.dart'; 2 | import 'package:molib/domain/repositories/IBookRepository.dart'; 3 | import 'package:molib/domain/value_objects/Identity.dart'; 4 | import 'package:molib/infrastructure/datasources/IDatasource.dart'; 5 | import 'package:molib/infrastructure/models/BookModel.dart'; 6 | 7 | class BookRepository implements IBookRepository { 8 | IDatasource _datasource; 9 | 10 | BookRepository({IDatasource datasource}) : _datasource = datasource; 11 | 12 | @override 13 | add(Book book) async { 14 | var model = BookModel( 15 | id: book.id, 16 | shelfId: book.shelfId, 17 | title: book.title, 18 | author: book.author, 19 | isbn: book.isbn, 20 | publishDate: book.publishDate, 21 | ); 22 | 23 | return await _datasource.addBook(model); 24 | } 25 | 26 | @override 27 | delete({Identity bookId}) { 28 | // TODO: implement delete 29 | return null; 30 | } 31 | 32 | @override 33 | Future find({Identity bookId}) async { 34 | return await _datasource.findBook(bookId); 35 | } 36 | 37 | @override 38 | Future> findAll() async { 39 | return await _datasource.findAllBooks(); 40 | } 41 | 42 | @override 43 | update(Book book) { 44 | // TODO: implement update 45 | return null; 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /lib/presentation/states/home_page/homepage_bloc.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | 3 | import 'package:bloc/bloc.dart'; 4 | import 'package:meta/meta.dart'; 5 | import 'package:molib/presentation/models/BookShelf.dart'; 6 | import 'package:molib/presentation/viewmodels/HomeViewModel.dart'; 7 | 8 | part 'homepage_event.dart'; 9 | part 'homepage_state.dart'; 10 | 11 | class HomePageBloc extends Bloc { 12 | final HomePageViewModel _viewModel; 13 | 14 | void getBooksOnShelves() => add(HomePageEvent.onBookShelvesRequested()); 15 | 16 | HomePageBloc(this._viewModel); 17 | 18 | @override 19 | HomePageState get initialState => HomePageState.initial(); 20 | 21 | @override 22 | Stream mapEventToState( 23 | HomePageEvent event, 24 | ) async* { 25 | try { 26 | yield HomePageState.onBookShelvesRequested( 27 | shelves: [], 28 | isFetching: true, 29 | errorMessage: '', 30 | ); 31 | await _viewModel.getBooksOnShelves(); 32 | yield HomePageState.onBookShelvesRequested( 33 | shelves: _viewModel.shelves, 34 | isFetching: false, 35 | errorMessage: '', 36 | ); 37 | } on Exception catch (e) { 38 | yield HomePageState.onBookShelvesRequested( 39 | shelves: [], 40 | isFetching: false, 41 | errorMessage: e.toString(), 42 | ); 43 | } 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /lib/infrastructure/repositories/fakes/FakeBookRepository.dart: -------------------------------------------------------------------------------- 1 | import 'package:molib/domain/entities/Book.dart'; 2 | import 'package:molib/domain/repositories/IBookRepository.dart'; 3 | import 'package:molib/domain/value_objects/Identity.dart'; 4 | import 'package:molib/infrastructure/models/BookModel.dart'; 5 | 6 | class FakeBookRepository implements IBookRepository { 7 | var map = { 8 | "Book_Id": "aaa", 9 | "Shelf_Id": "bbb", 10 | "Title": "Red Rose", 11 | "Author": "Jan Jensen", 12 | "ISBN": "ISBN-10: 0-596-52068-9", 13 | "Publish_Date": "2020-01-20" 14 | }; 15 | 16 | var map2 = { 17 | "Book_Id": "aab", 18 | "Shelf_Id": "bbb", 19 | "Title": "Whisper", 20 | "Author": "Jan Jensen", 21 | "ISBN": "ISBN-10: 0-596-52069-9", 22 | "Publish_Date": "2015-01-20" 23 | }; 24 | 25 | List books; 26 | 27 | FakeBookRepository() { 28 | books = [ 29 | BookModel.fromMap(map), 30 | BookModel.fromMap(map2), 31 | BookModel.fromMap(map), 32 | BookModel.fromMap(map2), 33 | BookModel.fromMap(map) 34 | ]; 35 | } 36 | 37 | @override 38 | add(Book book) { 39 | return books.add(book); 40 | } 41 | 42 | @override 43 | delete({Identity bookId}) { 44 | return null; 45 | } 46 | 47 | @override 48 | Future find({Identity bookId}) { 49 | return null; 50 | } 51 | 52 | @override 53 | Future> findAll() async { 54 | return books; 55 | } 56 | 57 | @override 58 | update(Book book) { 59 | return null; 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /lib/infrastructure/models/BookModel.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/foundation.dart'; 2 | import 'package:molib/domain/entities/Book.dart'; 3 | import 'package:molib/domain/value_objects/Author.dart'; 4 | import 'package:molib/domain/value_objects/ISBN.dart'; 5 | import 'package:molib/domain/value_objects/Identity.dart'; 6 | import 'package:molib/domain/value_objects/PublishDate.dart'; 7 | import 'package:molib/domain/value_objects/Title.dart'; 8 | 9 | class BookModel extends Book { 10 | Identity id; 11 | Identity shelfId; 12 | Title title; 13 | Author author; 14 | ISBN isbn; 15 | PublishDate publishDate; 16 | 17 | BookModel({ 18 | @required this.id, 19 | @required this.shelfId, 20 | @required this.title, 21 | @required this.author, 22 | @required this.isbn, 23 | @required this.publishDate, 24 | }); 25 | 26 | factory BookModel.fromMap(Map map) { 27 | return BookModel( 28 | id: Identity.fromString(map["Book_Id"]), 29 | shelfId: Identity.fromString(map["Shelf_Id"]), 30 | title: Title.create(map["Title"]).getOrElse(null), 31 | author: Author.create(map["Author"]).getOrElse(null), 32 | isbn: ISBN.create(map["ISBN"]).getOrElse(null), 33 | publishDate: PublishDate.create(map["Publish_Date"]).getOrElse(null), 34 | ); 35 | } 36 | 37 | Map toMap() { 38 | return { 39 | "Book_Id": id.id, 40 | "Shelf_Id": shelfId.id, 41 | "Title": title.value, 42 | "Author": author.value, 43 | "ISBN": isbn.value, 44 | "Publish_Date": publishDate.toString() 45 | }; 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /test/application/usecases/get_book_by_id_test.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter_test/flutter_test.dart'; 2 | import 'package:mockito/mockito.dart'; 3 | import 'package:molib/application/boundaries/get_book_by_id/GetBookByIdInput.dart'; 4 | import 'package:molib/application/usecases/GetBookByIdUseCase.dart'; 5 | import 'package:molib/domain/entities/Book.dart'; 6 | import 'package:molib/domain/value_objects/Identity.dart'; 7 | 8 | import 'add_book_usecase_test.dart'; 9 | 10 | void main() { 11 | GetBookByIdUseCase sut; 12 | MockBookRepository mockBookRepository; 13 | 14 | setUp(() { 15 | mockBookRepository = MockBookRepository(); 16 | sut = GetBookByIdUseCase(bookRepository: mockBookRepository); 17 | }); 18 | 19 | group('GetBookByIdUseCase', () { 20 | var input = GetBookByIdInput(bookId: Identity.fromString('aaaa')); 21 | 22 | test('should a Failure when book was not found', () async { 23 | //arrange 24 | when(mockBookRepository.find(bookId: null)).thenAnswer((_) => null); 25 | 26 | //act 27 | var result = await sut.execute(input); 28 | 29 | //assert 30 | expect(result.isLeft(), true); 31 | }); 32 | 33 | test('should return book when it is found', () async { 34 | //arrange 35 | var book = Book(); 36 | when(mockBookRepository.find(bookId: input.bookId)) 37 | .thenAnswer((_) async => book); 38 | 39 | // act 40 | var result = await sut.execute(input); 41 | 42 | //assert 43 | expect(result.isRight(), true); 44 | verify(mockBookRepository.find(bookId: input.bookId)); 45 | }); 46 | }); 47 | } 48 | -------------------------------------------------------------------------------- /android/app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 8 | 12 | 19 | 20 | 21 | 22 | 23 | 24 | 26 | 29 | 30 | 31 | -------------------------------------------------------------------------------- /lib/presentation/states/add_book_page/addbook_bloc.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | 3 | import 'package:bloc/bloc.dart'; 4 | import 'package:meta/meta.dart'; 5 | import 'package:molib/presentation/models/Book.dart'; 6 | import 'package:molib/presentation/viewmodels/AddBookViewModel.dart'; 7 | 8 | part 'addbook_event.dart'; 9 | part 'addbook_state.dart'; 10 | 11 | class AddBookBloc extends Bloc { 12 | AddBookViewModel _viewModel; 13 | 14 | AddBookBloc(this._viewModel); 15 | 16 | void addBook( 17 | {@required String title, 18 | @required String author, 19 | @required String isbn, 20 | @required String publishDate, 21 | @required String shelfId}) => 22 | add( 23 | AddBookEvent.onAddBookRequested( 24 | title: title, 25 | author: author, 26 | isbn: isbn, 27 | publishDate: publishDate, 28 | shelfId: shelfId), 29 | ); 30 | 31 | @override 32 | AddBookState get initialState => AddBookState.initial(); 33 | 34 | @override 35 | Stream mapEventToState( 36 | AddBookEvent event, 37 | ) async* { 38 | try { 39 | yield AddBookState.bookAdded( 40 | book: null, isCommitting: true, errMessage: ''); 41 | await _viewModel.addBook(event.title, event.author, event.isbn, 42 | event.publishDate, event.shelfId); 43 | yield AddBookState.bookAdded( 44 | book: _viewModel.book, isCommitting: false, errMessage: ''); 45 | } on Exception catch (e) { 46 | yield AddBookState.bookAdded( 47 | book: null, isCommitting: false, errMessage: e.toString()); 48 | } 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /ios/Runner/Base.lproj/Main.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /ios/Runner/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | molib 15 | CFBundlePackageType 16 | APPL 17 | CFBundleShortVersionString 18 | $(FLUTTER_BUILD_NAME) 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | $(FLUTTER_BUILD_NUMBER) 23 | LSRequiresIPhoneOS 24 | 25 | UILaunchStoryboardName 26 | LaunchScreen 27 | UIMainStoryboardFile 28 | Main 29 | UISupportedInterfaceOrientations 30 | 31 | UIInterfaceOrientationPortrait 32 | UIInterfaceOrientationLandscapeLeft 33 | UIInterfaceOrientationLandscapeRight 34 | 35 | UISupportedInterfaceOrientations~ipad 36 | 37 | UIInterfaceOrientationPortrait 38 | UIInterfaceOrientationPortraitUpsideDown 39 | UIInterfaceOrientationLandscapeLeft 40 | UIInterfaceOrientationLandscapeRight 41 | 42 | UIViewControllerBasedStatusBarAppearance 43 | 44 | 45 | 46 | -------------------------------------------------------------------------------- /lib/presentation/UI/widgets/BookWidget.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:molib/presentation/models/Book.dart'; 3 | import 'package:molib/presentation/utils/RandomColorGenerator.dart'; 4 | 5 | class BookWidget extends StatelessWidget { 6 | final Book book; 7 | 8 | BookWidget({this.book}); 9 | 10 | @override 11 | Widget build(BuildContext context) { 12 | return Container( 13 | decoration: BoxDecoration(boxShadow: [ 14 | BoxShadow( 15 | color: Colors.black26, offset: Offset(-8.0, 0.0), blurRadius: 6.0) 16 | ]), 17 | child: Stack( 18 | fit: StackFit.expand, 19 | children: [ 20 | Image.asset('assets/images/book.png', 21 | color: RandomColorGenerator.getColor(), 22 | colorBlendMode: BlendMode.hardLight, 23 | fit: BoxFit.cover), 24 | _bookInfo() 25 | ], 26 | ), 27 | ); 28 | } 29 | 30 | Widget _bookInfo() => Column( 31 | mainAxisAlignment: MainAxisAlignment.center, 32 | children: [ 33 | Padding( 34 | padding: const EdgeInsets.all(16.0), 35 | child: Text( 36 | book.title, 37 | style: TextStyle( 38 | color: Colors.black, 39 | fontWeight: FontWeight.bold, 40 | fontSize: 26, 41 | ), 42 | ), 43 | ), 44 | SizedBox(height: 8.0), 45 | Padding( 46 | padding: const EdgeInsets.all(16.0), 47 | child: Text( 48 | book.author, 49 | style: TextStyle( 50 | color: Colors.black, 51 | fontWeight: FontWeight.normal, 52 | fontSize: 18.0), 53 | ), 54 | ) 55 | ], 56 | ); 57 | } 58 | -------------------------------------------------------------------------------- /test/presentation/add_book_viewmodel_test.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter_test/flutter_test.dart'; 2 | import 'package:matcher/matcher.dart' as matcher; 3 | import 'package:molib/application/usecases/AddBookUseCase.dart'; 4 | import 'package:molib/infrastructure/factories/EntityFactory.dart'; 5 | import 'package:molib/infrastructure/repositories/fakes/FakeBookRepository.dart'; 6 | import 'package:molib/infrastructure/repositories/fakes/FakeShelfRepository.dart'; 7 | import 'package:molib/presentation/viewmodels/AddBookViewModel.dart'; 8 | 9 | void main() { 10 | AddBookViewModel sut; 11 | AddBookUseCase addBookUseCase; 12 | FakeBookRepository fakeBookRepository; 13 | FakeShelfRepository fakeShelfRepository; 14 | EntityFactory entityFactory; 15 | 16 | setUp(() { 17 | fakeShelfRepository = FakeShelfRepository(); 18 | fakeBookRepository = FakeBookRepository(); 19 | entityFactory = EntityFactory(); 20 | 21 | addBookUseCase = AddBookUseCase( 22 | bookShelfRepository: fakeShelfRepository, 23 | bookRepository: fakeBookRepository, 24 | entityFactory: entityFactory); 25 | 26 | sut = AddBookViewModel(addBookUseCase); 27 | }); 28 | 29 | group('AddBookViewModel.addBook', () { 30 | String title = ""; 31 | String author = "Jan Jensen"; 32 | String isbn = "ISBN-10: 0-596-52068-9"; 33 | String publishDate = "2020-01-01"; 34 | String shelfId = "bbb"; 35 | test('should throw Exception when errors with input', () { 36 | //assert 37 | expect(() => sut.addBook(title, author, isbn, publishDate, shelfId), 38 | throwsA(matcher.TypeMatcher())); 39 | }); 40 | 41 | test('should add book successfully and return book with id', () async { 42 | //arrange 43 | var atitle = "Red Book"; 44 | //act 45 | 46 | await sut.addBook(atitle, author, isbn, publishDate, shelfId); 47 | 48 | //assert 49 | expect(sut.book, isNotNull); 50 | expect(sut.book.id, isNotNull); 51 | }); 52 | }); 53 | } 54 | -------------------------------------------------------------------------------- /lib/infrastructure/datasources/SqfliteDatasource.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/foundation.dart'; 2 | import 'package:molib/domain/value_objects/Identity.dart'; 3 | import 'package:molib/infrastructure/datasources/IDatasource.dart'; 4 | import 'package:molib/infrastructure/models/BookModel.dart'; 5 | import 'package:molib/infrastructure/models/ShelfModel.dart'; 6 | import 'package:sqflite/sqlite_api.dart'; 7 | 8 | class SqfliteDatasource implements IDatasource { 9 | final Database _db; 10 | const SqfliteDatasource({@required Database db}) : _db = db; 11 | 12 | @override 13 | addBook(BookModel model) async { 14 | await _db.insert('books', model.toMap(), 15 | conflictAlgorithm: ConflictAlgorithm.replace); 16 | } 17 | 18 | @override 19 | Future> findAllBooks() async { 20 | var listOfMaps = await _db.query('books'); 21 | if (listOfMaps.isEmpty) return []; 22 | 23 | return listOfMaps.map((map) => BookModel.fromMap(map)).toList(); 24 | } 25 | 26 | @override 27 | Future findBook(Identity bookId) async { 28 | var listOfMaps = await _db.query( 29 | 'books', 30 | where: 'Book_Id = ?', 31 | whereArgs: [bookId.id], 32 | ); 33 | return BookModel.fromMap(listOfMaps.first); 34 | } 35 | 36 | @override 37 | Future createShelf(ShelfModel model) async { 38 | return await _db.insert('shelf', model.toMap()); 39 | } 40 | 41 | @override 42 | Future> findBooksOnSelf(Identity shelfId) async { 43 | var listOfMaps = await _db.query( 44 | 'books', 45 | where: 'Shelf_Id = ?', 46 | whereArgs: [shelfId.id], 47 | ); 48 | 49 | return listOfMaps.map((map) => BookModel.fromMap(map)).toList(); 50 | } 51 | 52 | @override 53 | Future findShelf(Identity shelfId) async { 54 | var listOfMaps = await _db.query( 55 | 'shelf', 56 | where: 'Shelf_Id = ?', 57 | whereArgs: [shelfId.id], 58 | ); 59 | return ShelfModel.fromMap(listOfMaps.first); 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /lib/presentation/viewmodels/HomeViewModel.dart: -------------------------------------------------------------------------------- 1 | import 'package:collection/collection.dart'; 2 | import 'package:flutter/foundation.dart'; 3 | import 'package:molib/application/boundaries/create_shelf/ICreateShelfUseCase.dart'; 4 | import 'package:molib/application/boundaries/get_all_books/BookDto.dart'; 5 | import 'package:molib/application/boundaries/get_all_books/IGetAllBooksUseCase.dart'; 6 | import 'package:molib/presentation/models/Book.dart'; 7 | import 'package:molib/presentation/models/BookShelf.dart'; 8 | 9 | const BOOKS_PER_SHELF = 6; 10 | 11 | class HomePageViewModel { 12 | List _shelves = []; 13 | 14 | List get shelves => _shelves; 15 | 16 | final ICreateShelfUseCase _createShelfUseCase; 17 | final IGetAllBooksUseCase _getAllBooksUseCase; 18 | 19 | HomePageViewModel( 20 | {@required ICreateShelfUseCase createShelfUseCase, 21 | @required IGetAllBooksUseCase getAllBooksUseCase}) 22 | : _createShelfUseCase = createShelfUseCase, 23 | _getAllBooksUseCase = getAllBooksUseCase; 24 | 25 | Future getBooksOnShelves() async { 26 | _shelves = []; 27 | var result = await _getAllBooksUseCase.execute(); 28 | if (result.books.isEmpty) { 29 | await createShelf(); 30 | return; 31 | } 32 | 33 | var groupByShelfId = groupBy(result.books, (BookDto book) => book.shelfId); 34 | groupByShelfId.forEach( 35 | (id, books) => _createShelfWithBooks(id.id, books), 36 | ); 37 | 38 | if (_shelves.last.books.length == BOOKS_PER_SHELF) { 39 | await createShelf(); 40 | } 41 | } 42 | 43 | Future createShelf() async { 44 | var result = await _createShelfUseCase.execute(); 45 | _shelves.add(BookShelf(result.shelfId.id)); 46 | } 47 | 48 | _createShelfWithBooks(String id, List books) { 49 | BookShelf shelf = BookShelf(id); 50 | shelf.books = books 51 | .map( 52 | (b) => Book( 53 | id: b.id.id, 54 | title: b.title.value, 55 | author: b.author.value, 56 | isbn: b.isbn.value, 57 | publishDate: b.publishDate.toString(), 58 | ), 59 | ) 60 | .toList(); 61 | 62 | _shelves.add(shelf); 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /lib/presentation/viewmodels/AddBookViewModel.dart: -------------------------------------------------------------------------------- 1 | import 'package:molib/application/boundaries/add_book/AddBookInput.dart'; 2 | import 'package:molib/application/boundaries/add_book/IAddBookUseCase.dart'; 3 | import 'package:molib/domain/value_objects/Author.dart'; 4 | import 'package:molib/domain/value_objects/ISBN.dart'; 5 | import 'package:molib/domain/value_objects/Identity.dart'; 6 | import 'package:molib/domain/value_objects/PublishDate.dart'; 7 | import 'package:molib/domain/value_objects/Title.dart'; 8 | import 'package:molib/presentation/models/Book.dart'; 9 | 10 | class AddBookViewModel { 11 | final IAddBookUseCase _addBookUseCase; 12 | List _errMessages = []; 13 | Book _book; 14 | 15 | Book get book => _book; 16 | 17 | AddBookViewModel(this._addBookUseCase); 18 | 19 | Future addBook(String title, String author, String isbn, 20 | String publishDate, String shelfId) async { 21 | _errMessages = []; 22 | Title vtitle = Title.create(title).fold((err) { 23 | _errMessages.add(err.message); 24 | return null; 25 | }, (val) => val); 26 | 27 | Author vauthor = Author.create(author).fold((err) { 28 | _errMessages.add(err.message); 29 | return null; 30 | }, (val) => val); 31 | 32 | ISBN visbn = ISBN.create(isbn).fold((err) { 33 | _errMessages.add(err.message); 34 | return null; 35 | }, (val) => val); 36 | 37 | PublishDate vpublishDate = PublishDate.create(publishDate).fold((err) { 38 | _errMessages.add(err.message); 39 | return null; 40 | }, (val) => val); 41 | 42 | Identity vshelfId = Identity.fromString(shelfId); 43 | 44 | if (_errMessages.isNotEmpty) throw Exception(_errMessages.join('\n')); 45 | 46 | var input = AddBookInput( 47 | shelfId: vshelfId, 48 | title: vtitle, 49 | author: vauthor, 50 | isbn: visbn, 51 | publishDate: vpublishDate); 52 | 53 | var result = await _addBookUseCase.execute(input); 54 | 55 | result.fold( 56 | (e) => throw Exception(e.message), 57 | (o) => _book = Book( 58 | id: o.bookId.id, 59 | title: o.title.value, 60 | author: o.author.value, 61 | isbn: o.isbn.value, 62 | publishDate: o.publishDate.toString(), 63 | ), 64 | ); 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /android/app/build.gradle: -------------------------------------------------------------------------------- 1 | def localProperties = new Properties() 2 | def localPropertiesFile = rootProject.file('local.properties') 3 | if (localPropertiesFile.exists()) { 4 | localPropertiesFile.withReader('UTF-8') { reader -> 5 | localProperties.load(reader) 6 | } 7 | } 8 | 9 | def flutterRoot = localProperties.getProperty('flutter.sdk') 10 | if (flutterRoot == null) { 11 | throw new GradleException("Flutter SDK not found. Define location with flutter.sdk in the local.properties file.") 12 | } 13 | 14 | def flutterVersionCode = localProperties.getProperty('flutter.versionCode') 15 | if (flutterVersionCode == null) { 16 | flutterVersionCode = '1' 17 | } 18 | 19 | def flutterVersionName = localProperties.getProperty('flutter.versionName') 20 | if (flutterVersionName == null) { 21 | flutterVersionName = '1.0' 22 | } 23 | 24 | apply plugin: 'com.android.application' 25 | apply plugin: 'kotlin-android' 26 | apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle" 27 | 28 | android { 29 | compileSdkVersion 28 30 | 31 | sourceSets { 32 | main.java.srcDirs += 'src/main/kotlin' 33 | } 34 | 35 | lintOptions { 36 | disable 'InvalidPackage' 37 | } 38 | 39 | defaultConfig { 40 | // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html). 41 | applicationId "com.example.molib" 42 | minSdkVersion 16 43 | targetSdkVersion 28 44 | versionCode flutterVersionCode.toInteger() 45 | versionName flutterVersionName 46 | testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" 47 | } 48 | 49 | buildTypes { 50 | release { 51 | // TODO: Add your own signing config for the release build. 52 | // Signing with the debug keys for now, so `flutter run --release` works. 53 | signingConfig signingConfigs.debug 54 | } 55 | } 56 | } 57 | 58 | flutter { 59 | source '../..' 60 | } 61 | 62 | dependencies { 63 | implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" 64 | testImplementation 'junit:junit:4.12' 65 | androidTestImplementation 'androidx.test:runner:1.1.1' 66 | androidTestImplementation 'androidx.test.espresso:espresso-core:3.1.1' 67 | } 68 | -------------------------------------------------------------------------------- /test/presentation/states/home_page_bloc_test.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter_test/flutter_test.dart'; 2 | import 'package:molib/application/boundaries/create_shelf/ICreateShelfUseCase.dart'; 3 | import 'package:molib/application/boundaries/get_all_books/IGetAllBooksUseCase.dart'; 4 | import 'package:molib/application/usecases/CreateShelfUseCase.dart'; 5 | import 'package:molib/application/usecases/GetAllBookUseCase.dart'; 6 | import 'package:molib/domain/factories/IEntityFactory.dart'; 7 | import 'package:molib/infrastructure/factories/EntityFactory.dart'; 8 | import 'package:molib/infrastructure/repositories/fakes/FakeBookRepository.dart'; 9 | import 'package:molib/infrastructure/repositories/fakes/FakeShelfRepository.dart'; 10 | import 'package:molib/presentation/states/home_page/homepage_bloc.dart'; 11 | import 'package:molib/presentation/viewmodels/HomeViewModel.dart'; 12 | 13 | void main() { 14 | HomePageBloc sut; 15 | HomePageViewModel viewModel; 16 | IGetAllBooksUseCase getAllBooksUseCase; 17 | ICreateShelfUseCase createShelfUseCase; 18 | IEntityFactory entityFactory; 19 | setUp(() { 20 | entityFactory = EntityFactory(); 21 | createShelfUseCase = 22 | CreateShelfUseCase(FakeShelfRepository(), entityFactory); 23 | getAllBooksUseCase = 24 | GetAllBooksUseCase(bookRepository: FakeBookRepository()); 25 | viewModel = HomePageViewModel( 26 | getAllBooksUseCase: getAllBooksUseCase, 27 | createShelfUseCase: createShelfUseCase, 28 | ); 29 | sut = HomePageBloc(viewModel); 30 | }); 31 | 32 | tearDown(() => sut.close()); 33 | 34 | group('HomePageBloc.getBooksOnShelves', () { 35 | test('should return shelf with books from fake repository', () async { 36 | //arrange 37 | var idx = 0; 38 | 39 | //act 40 | sut.getBooksOnShelves(); 41 | 42 | //assert 43 | sut.listen(expectAsync1((state) { 44 | _expectedStates(state, idx); 45 | idx++; 46 | }, count: 3)); 47 | }); 48 | }); 49 | } 50 | 51 | void _expectedStates(HomePageState state, int idx) { 52 | var expectedStates = [ 53 | {'numOfShelves': 0, 'isFetching': false, 'errMessage': ''}, 54 | {'numOfShelves': 0, 'isFetching': true, 'errMessage': ''}, 55 | {'numOfShelves': 1, 'isFetching': false, 'errMessage': ''} 56 | ]; 57 | 58 | expect(state.shelves.length, expectedStates[idx]['numOfShelves']); 59 | expect(state.isFetching, expectedStates[idx]['isFetching']); 60 | expect(state.errMessages, expectedStates[idx]['errMessage']); 61 | } 62 | -------------------------------------------------------------------------------- /test/infrastructue/datasources/sqflite_datasource_test.dart: -------------------------------------------------------------------------------- 1 | import 'dart:math'; 2 | 3 | import 'package:flutter_test/flutter_test.dart'; 4 | import 'package:mockito/mockito.dart'; 5 | import 'package:molib/domain/value_objects/Identity.dart'; 6 | import 'package:molib/infrastructure/datasources/SqfliteDatasource.dart'; 7 | import 'package:molib/infrastructure/models/BookModel.dart'; 8 | import 'package:sqflite/sqflite.dart'; 9 | 10 | class MockSqfliteDatabase extends Mock implements Database {} 11 | 12 | void main() { 13 | SqfliteDatasource sut; 14 | MockSqfliteDatabase database; 15 | 16 | setUp(() { 17 | database = MockSqfliteDatabase(); 18 | sut = SqfliteDatasource(db: database); 19 | }); 20 | 21 | var map = { 22 | "Book_Id": "aaa", 23 | "Shelf_Id": "bbb", 24 | "Title": "Title", 25 | "Author": "Author", 26 | "ISBN": "ISBN-10: 0-596-52068-9", 27 | "Publish_Date": "2020-01-20" 28 | }; 29 | group('SqfliteDatasource.addBook', () { 30 | test('should perform a database insert', () async { 31 | //arrange 32 | var bookModel = BookModel.fromMap(map); 33 | when(database.insert('books', bookModel.toMap(), 34 | conflictAlgorithm: ConflictAlgorithm.replace)) 35 | .thenAnswer((_) async => 1); 36 | //act 37 | await sut.addBook(bookModel); 38 | 39 | //assert 40 | verify(database.insert('books', bookModel.toMap(), 41 | conflictAlgorithm: ConflictAlgorithm.replace)) 42 | .called(1); 43 | }); 44 | }); 45 | 46 | group('SqfliteDatasource.findBook', () { 47 | test('should perform a database qurey and return a matching record', 48 | () async { 49 | //arrange 50 | when(database.query('books', 51 | where: anyNamed('where'), whereArgs: anyNamed('whereArgs'))) 52 | .thenAnswer((_) async => [map]); 53 | //act 54 | var bookModel = await sut.findBook(Identity.fromString('aaa')); 55 | 56 | //assert 57 | expect(bookModel, isNotNull); 58 | expect(bookModel.id.id, 'aaa'); 59 | }); 60 | }); 61 | 62 | group('SqfliteDatasource.findAllBooks', () { 63 | test('should perform database query and return all records', () async { 64 | //arrange 65 | when(database.query('books')).thenAnswer((_) async => [map]); 66 | 67 | //act 68 | var bookModels = await sut.findAllBooks(); 69 | 70 | //assert 71 | expect(bookModels, isNotEmpty); 72 | verify(database.query('books')); 73 | }); 74 | }); 75 | } 76 | -------------------------------------------------------------------------------- /ios/Runner/Base.lproj/LaunchScreen.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | -------------------------------------------------------------------------------- /test/presentation/viewmodels/home_viewmodel_test.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter_test/flutter_test.dart'; 2 | import 'package:molib/application/usecases/CreateShelfUseCase.dart'; 3 | import 'package:molib/application/usecases/GetAllBookUseCase.dart'; 4 | import 'package:molib/infrastructure/factories/EntityFactory.dart'; 5 | import 'package:molib/infrastructure/models/BookModel.dart'; 6 | import 'package:molib/infrastructure/repositories/fakes/FakeBookRepository.dart'; 7 | import 'package:molib/infrastructure/repositories/fakes/FakeShelfRepository.dart'; 8 | import 'package:molib/presentation/viewmodels/HomeViewModel.dart'; 9 | 10 | void main() { 11 | HomePageViewModel sut; 12 | CreateShelfUseCase createShelfUseCase; 13 | GetAllBooksUseCase getAllBooksUseCase; 14 | FakeBookRepository fakeBookRepository; 15 | FakeShelfRepository fakeShelfRepository; 16 | EntityFactory entityFactory; 17 | setUp(() { 18 | entityFactory = EntityFactory(); 19 | fakeShelfRepository = FakeShelfRepository(); 20 | fakeBookRepository = FakeBookRepository(); 21 | 22 | getAllBooksUseCase = GetAllBooksUseCase(bookRepository: fakeBookRepository); 23 | createShelfUseCase = CreateShelfUseCase(fakeShelfRepository, entityFactory); 24 | 25 | sut = HomePageViewModel( 26 | createShelfUseCase: createShelfUseCase, 27 | getAllBooksUseCase: getAllBooksUseCase, 28 | ); 29 | }); 30 | 31 | group('HomePageViewModel.getBooksOnShelves', () { 32 | test('should create an empty shelf when no books in storage', () async { 33 | //arrange 34 | fakeBookRepository.books = []; 35 | //act 36 | await sut.getBooksOnShelves(); 37 | //assert 38 | expect(sut.shelves.length, 1); 39 | expect(sut.shelves.first.books, isEmpty); 40 | }); 41 | 42 | test('should return books from storage and group by shelf', () async { 43 | //act 44 | await sut.getBooksOnShelves(); 45 | //assert 46 | expect(sut.shelves, isNotEmpty); 47 | expect(sut.shelves.first.books, isNotEmpty); 48 | }); 49 | 50 | test('create empty shelf if last shelf reaches maximum capacity', () async { 51 | //arrange 52 | var map = { 53 | "Book_Id": "aaa", 54 | "Shelf_Id": "bbb", 55 | "Title": "Red Rose", 56 | "Author": "Jan Jensen", 57 | "ISBN": "ISBN-10: 0-596-52068-9", 58 | "Publish_Date": "2020-01-20" 59 | }; 60 | fakeBookRepository.books.add(BookModel.fromMap(map)); 61 | //act 62 | await sut.getBooksOnShelves(); 63 | //assert 64 | expect(sut.shelves[sut.shelves.length - 2].books.length, BOOKS_PER_SHELF); 65 | expect(sut.shelves.last.books, isEmpty); 66 | }); 67 | }); 68 | } 69 | -------------------------------------------------------------------------------- /test/presentation/states/add_book_bloc_test.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter_test/flutter_test.dart'; 2 | import 'package:molib/application/boundaries/add_book/IAddBookUseCase.dart'; 3 | import 'package:molib/application/usecases/AddBookUseCase.dart'; 4 | import 'package:molib/domain/factories/IEntityFactory.dart'; 5 | import 'package:molib/infrastructure/factories/EntityFactory.dart'; 6 | import 'package:molib/infrastructure/repositories/fakes/FakeBookRepository.dart'; 7 | import 'package:molib/infrastructure/repositories/fakes/FakeShelfRepository.dart'; 8 | import 'package:molib/presentation/states/add_book_page/addbook_bloc.dart'; 9 | import 'package:molib/presentation/viewmodels/AddBookViewModel.dart'; 10 | 11 | void main() { 12 | AddBookBloc sut; 13 | AddBookViewModel viewModel; 14 | IAddBookUseCase addBookUseCase; 15 | IEntityFactory entityFactory; 16 | setUp(() { 17 | entityFactory = EntityFactory(); 18 | addBookUseCase = AddBookUseCase( 19 | bookShelfRepository: FakeShelfRepository(), 20 | bookRepository: FakeBookRepository(), 21 | entityFactory: entityFactory); 22 | 23 | viewModel = AddBookViewModel(addBookUseCase); 24 | 25 | sut = AddBookBloc(viewModel); 26 | }); 27 | 28 | tearDown(() => sut.close()); 29 | 30 | group('AddBookPageBloc.addBook', () { 31 | test('should return error state with messages when invalid inputs', 32 | () async { 33 | //arrange 34 | var title = "Title"; 35 | var author = "Author"; 36 | var isbn = "ISBN-10: 0-596-52068-9"; 37 | var publishDate = ""; 38 | var shelfId = "bbb"; 39 | 40 | List states = []; 41 | 42 | //act 43 | sut.addBook( 44 | title: title, 45 | author: author, 46 | isbn: isbn, 47 | publishDate: publishDate, 48 | shelfId: shelfId, 49 | ); 50 | 51 | //assert 52 | sut 53 | .listen((state) => states.add(state)) 54 | .asFuture() 55 | .whenComplete(() => expect(states.last.errMessage, isNotEmpty)); 56 | }); 57 | 58 | test('should return state with created book', () async { 59 | //arrange 60 | var title = "Title"; 61 | var author = "Author"; 62 | var isbn = "ISBN-10: 0-596-52068-9"; 63 | var publishDate = "2020-01-01"; 64 | var shelfId = "bbb"; 65 | 66 | List states = []; 67 | 68 | //act 69 | sut.addBook( 70 | title: title, 71 | author: author, 72 | isbn: isbn, 73 | publishDate: publishDate, 74 | shelfId: shelfId, 75 | ); 76 | 77 | //assert 78 | sut 79 | .listen((state) => states.add(state)) 80 | .asFuture() 81 | .whenComplete(() => expect(states.last.book.id, isNotEmpty)); 82 | }); 83 | }); 84 | } 85 | -------------------------------------------------------------------------------- /lib/application/usecases/AddBookUseCase.dart: -------------------------------------------------------------------------------- 1 | import 'package:dartz/dartz.dart'; 2 | import 'package:molib/application/boundaries/add_book/AddBookInput.dart'; 3 | import 'package:molib/application/boundaries/add_book/AddBookOutput.dart'; 4 | import 'package:molib/application/boundaries/add_book/IAddBookUseCase.dart'; 5 | import 'package:molib/domain/DomainExcpetion.dart'; 6 | import 'package:molib/domain/entities/Book.dart'; 7 | import 'package:molib/domain/entities/BookShelf.dart'; 8 | import 'package:molib/domain/factories/IEntityFactory.dart'; 9 | import 'package:molib/domain/repositories/IBookRepository.dart'; 10 | import 'package:molib/domain/repositories/IBookShelfRepository.dart'; 11 | import 'package:molib/domain/value_objects/Failure.dart'; 12 | import 'package:molib/domain/value_objects/Identity.dart'; 13 | 14 | class AddBookUseCase implements IAddBookUseCase { 15 | final IBookShelfRepository _bookShelfRepository; 16 | final IBookRepository _bookRepository; 17 | final IEntityFactory _entityFactory; 18 | 19 | const AddBookUseCase( 20 | {IBookShelfRepository bookShelfRepository, 21 | IBookRepository bookRepository, 22 | IEntityFactory entityFactory}) 23 | : _bookShelfRepository = bookShelfRepository, 24 | _bookRepository = bookRepository, 25 | _entityFactory = entityFactory; 26 | 27 | @override 28 | Future> execute(AddBookInput input) async { 29 | Book newBook = _createBookFromInput(input); 30 | 31 | Either result = 32 | await _addBookToShelf(newBook, input.shelfId); 33 | if (result.isLeft()) return result.fold((err) => Left(err), (_) => null); 34 | 35 | var bookshelf = result.getOrElse(null); 36 | 37 | await _bookRepository.add(newBook); 38 | return _buildOutputFromNewBook(newBook, bookshelf); 39 | } 40 | 41 | Either _buildOutputFromNewBook( 42 | Book newBook, BookShelf bookShelf) { 43 | var output = AddBookOutput( 44 | bookId: newBook.id, 45 | shelfId: bookShelf.id, 46 | title: newBook.title, 47 | author: newBook.author, 48 | isbn: newBook.isbn, 49 | publishDate: newBook.publishDate); 50 | 51 | return Right(output); 52 | } 53 | 54 | Future> _addBookToShelf( 55 | Book newBook, Identity shelfId) async { 56 | BookShelf bookShelf = await _bookShelfRepository.find(shelfId); 57 | if (bookShelf == null) return Left(Failure('book shelf not found')); 58 | try { 59 | bookShelf.addBook(newBook); 60 | } on DomainException { 61 | return Left(Failure('book shelf has reached its maximum capacity')); 62 | } 63 | 64 | return Right(bookShelf); 65 | } 66 | 67 | Book _createBookFromInput(AddBookInput input) { 68 | return _entityFactory.newBook( 69 | title: input.title, 70 | author: input.author, 71 | isbn: input.isbn, 72 | publishDate: input.publishDate); 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: molib 2 | description: A new Flutter project. 3 | 4 | # The following defines the version and build number for your application. 5 | # A version number is three numbers separated by dots, like 1.2.43 6 | # followed by an optional build number separated by a +. 7 | # Both the version and the builder number may be overridden in flutter 8 | # build by specifying --build-name and --build-number, respectively. 9 | # In Android, build-name is used as versionName while build-number used as versionCode. 10 | # Read more about Android versioning at https://developer.android.com/studio/publish/versioning 11 | # In iOS, build-name is used as CFBundleShortVersionString while build-number used as CFBundleVersion. 12 | # Read more about iOS versioning at 13 | # https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html 14 | version: 1.0.0+1 15 | 16 | environment: 17 | sdk: '>=2.1.0 <3.0.0' 18 | 19 | dependencies: 20 | flutter: 21 | sdk: flutter 22 | equatable: ^1.1.1 23 | uuid: ^2.0.4 24 | intl: ^0.16.1 25 | dartz: ^0.8.9 26 | mockito: ^4.1.1 27 | sqflite: ^1.3.0 28 | path: ^1.6.4 29 | flutter_bloc: ^4.0.0 30 | 31 | # The following adds the Cupertino Icons font to your application. 32 | # Use with the CupertinoIcons class for iOS style icons. 33 | cupertino_icons: ^0.1.2 34 | 35 | dev_dependencies: 36 | flutter_test: 37 | sdk: flutter 38 | 39 | # For information on the generic Dart part of this file, see the 40 | # following page: https://dart.dev/tools/pub/pubspec 41 | 42 | # The following section is specific to Flutter. 43 | flutter: 44 | # The following line ensures that the Material Icons font is 45 | # included with your application, so that you can use the icons in 46 | # the material Icons class. 47 | uses-material-design: true 48 | # To add assets to your application, add an assets section, like this: 49 | assets: 50 | - assets/images/shelf.png 51 | - assets/images/book.png 52 | - assets/images/add_book.png 53 | # An image asset can refer to one or more resolution-specific "variants", see 54 | # https://flutter.dev/assets-and-images/#resolution-aware. 55 | # For details regarding adding assets from package dependencies, see 56 | # https://flutter.dev/assets-and-images/#from-packages 57 | # To add custom fonts to your application, add a fonts section here, 58 | # in this "flutter" section. Each entry in this list should have a 59 | # "family" key with the font family name, and a "fonts" key with a 60 | # list giving the asset and other descriptors for the font. For 61 | # example: 62 | # fonts: 63 | # - family: Schyler 64 | # fonts: 65 | # - asset: fonts/Schyler-Regular.ttf 66 | # - asset: fonts/Schyler-Italic.ttf 67 | # style: italic 68 | # - family: Trajan Pro 69 | # fonts: 70 | # - asset: fonts/TrajanPro.ttf 71 | # - asset: fonts/TrajanPro_Bold.ttf 72 | # weight: 700 73 | # 74 | # For details regarding fonts from package dependencies, 75 | # see https://flutter.dev/custom-fonts/#from-packages 76 | -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "size" : "20x20", 5 | "idiom" : "iphone", 6 | "filename" : "Icon-App-20x20@2x.png", 7 | "scale" : "2x" 8 | }, 9 | { 10 | "size" : "20x20", 11 | "idiom" : "iphone", 12 | "filename" : "Icon-App-20x20@3x.png", 13 | "scale" : "3x" 14 | }, 15 | { 16 | "size" : "29x29", 17 | "idiom" : "iphone", 18 | "filename" : "Icon-App-29x29@1x.png", 19 | "scale" : "1x" 20 | }, 21 | { 22 | "size" : "29x29", 23 | "idiom" : "iphone", 24 | "filename" : "Icon-App-29x29@2x.png", 25 | "scale" : "2x" 26 | }, 27 | { 28 | "size" : "29x29", 29 | "idiom" : "iphone", 30 | "filename" : "Icon-App-29x29@3x.png", 31 | "scale" : "3x" 32 | }, 33 | { 34 | "size" : "40x40", 35 | "idiom" : "iphone", 36 | "filename" : "Icon-App-40x40@2x.png", 37 | "scale" : "2x" 38 | }, 39 | { 40 | "size" : "40x40", 41 | "idiom" : "iphone", 42 | "filename" : "Icon-App-40x40@3x.png", 43 | "scale" : "3x" 44 | }, 45 | { 46 | "size" : "60x60", 47 | "idiom" : "iphone", 48 | "filename" : "Icon-App-60x60@2x.png", 49 | "scale" : "2x" 50 | }, 51 | { 52 | "size" : "60x60", 53 | "idiom" : "iphone", 54 | "filename" : "Icon-App-60x60@3x.png", 55 | "scale" : "3x" 56 | }, 57 | { 58 | "size" : "20x20", 59 | "idiom" : "ipad", 60 | "filename" : "Icon-App-20x20@1x.png", 61 | "scale" : "1x" 62 | }, 63 | { 64 | "size" : "20x20", 65 | "idiom" : "ipad", 66 | "filename" : "Icon-App-20x20@2x.png", 67 | "scale" : "2x" 68 | }, 69 | { 70 | "size" : "29x29", 71 | "idiom" : "ipad", 72 | "filename" : "Icon-App-29x29@1x.png", 73 | "scale" : "1x" 74 | }, 75 | { 76 | "size" : "29x29", 77 | "idiom" : "ipad", 78 | "filename" : "Icon-App-29x29@2x.png", 79 | "scale" : "2x" 80 | }, 81 | { 82 | "size" : "40x40", 83 | "idiom" : "ipad", 84 | "filename" : "Icon-App-40x40@1x.png", 85 | "scale" : "1x" 86 | }, 87 | { 88 | "size" : "40x40", 89 | "idiom" : "ipad", 90 | "filename" : "Icon-App-40x40@2x.png", 91 | "scale" : "2x" 92 | }, 93 | { 94 | "size" : "76x76", 95 | "idiom" : "ipad", 96 | "filename" : "Icon-App-76x76@1x.png", 97 | "scale" : "1x" 98 | }, 99 | { 100 | "size" : "76x76", 101 | "idiom" : "ipad", 102 | "filename" : "Icon-App-76x76@2x.png", 103 | "scale" : "2x" 104 | }, 105 | { 106 | "size" : "83.5x83.5", 107 | "idiom" : "ipad", 108 | "filename" : "Icon-App-83.5x83.5@2x.png", 109 | "scale" : "2x" 110 | }, 111 | { 112 | "size" : "1024x1024", 113 | "idiom" : "ios-marketing", 114 | "filename" : "Icon-App-1024x1024@1x.png", 115 | "scale" : "1x" 116 | } 117 | ], 118 | "info" : { 119 | "version" : 1, 120 | "author" : "xcode" 121 | } 122 | } 123 | -------------------------------------------------------------------------------- /test/infrastructue/repositories/book_repository_test.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter_test/flutter_test.dart'; 2 | import 'package:mockito/mockito.dart'; 3 | import 'package:molib/domain/entities/Book.dart'; 4 | import 'package:molib/domain/value_objects/Author.dart'; 5 | import 'package:molib/domain/value_objects/ISBN.dart'; 6 | import 'package:molib/domain/value_objects/Identity.dart'; 7 | import 'package:molib/domain/value_objects/PublishDate.dart'; 8 | import 'package:molib/domain/value_objects/Title.dart'; 9 | import 'package:molib/infrastructure/datasources/IDatasource.dart'; 10 | import 'package:molib/infrastructure/models/BookModel.dart'; 11 | import 'package:molib/infrastructure/repositories/BookRepsitory.dart'; 12 | 13 | class MockDatasource extends Mock implements IDatasource {} 14 | 15 | void main() { 16 | BookRepository sut; 17 | MockDatasource mockDatasource; 18 | 19 | setUp(() { 20 | mockDatasource = MockDatasource(); 21 | sut = BookRepository(datasource: mockDatasource); 22 | }); 23 | 24 | group('BookRepository.add', () { 25 | var title = Title.create('Domain Driven Design').getOrElse(null); 26 | var author = Author.create('Vaugh Vernon').getOrElse(null); 27 | var isbn = ISBN.create('ISBN-10: 0-596-52068-9').getOrElse(null); 28 | var publishDate = PublishDate.create('2019-01-20').getOrElse(null); 29 | 30 | var book = Book( 31 | id: Identity.fromString('aaa'), 32 | title: title, 33 | author: author, 34 | isbn: isbn, 35 | publishDate: publishDate, 36 | ); 37 | 38 | test('should add a book when call to the datasource is successful', 39 | () async { 40 | //act 41 | await sut.add(book); 42 | 43 | //assert 44 | verify(mockDatasource.addBook(any)).called(1); 45 | }); 46 | }); 47 | 48 | group('BookRepository.findAll', () { 49 | test( 50 | 'should return a list of books when the call to the datasource is successful', 51 | () async { 52 | //arrange 53 | var map = { 54 | "Book_Id": "aaa", 55 | "Shelf_id": "bbb", 56 | "Title": "Title", 57 | "Author": "Author", 58 | "ISBN": "ISBN-10: 0-596-52068-9", 59 | "Publish_Date": "2020-01-20" 60 | }; 61 | when(mockDatasource.findAllBooks()) 62 | .thenAnswer((_) async => [BookModel.fromMap(map)]); 63 | //act 64 | var books = await sut.findAll(); 65 | 66 | //assert 67 | expect(books, isNotEmpty); 68 | verify(mockDatasource.findAllBooks()).called(1); 69 | }); 70 | }); 71 | 72 | group('BookRepository.find', () { 73 | test('should return a books when the call to the datasource is successful', 74 | () async { 75 | //arrange 76 | var map = { 77 | "Book_Id": "aaa", 78 | "Shelf_id": "bbb", 79 | "Title": "Title", 80 | "Author": "Author", 81 | "ISBN": "ISBN-10: 0-596-52068-9", 82 | "Publish_Date": "2020-01-20" 83 | }; 84 | when(mockDatasource.findBook(any)) 85 | .thenAnswer((_) async => BookModel.fromMap(map)); 86 | //act 87 | var book = await sut.find(bookId: Identity.fromString('aaa')); 88 | 89 | //assert 90 | expect(book, isNotNull); 91 | verify(mockDatasource.findBook(any)).called(1); 92 | }); 93 | }); 94 | } 95 | -------------------------------------------------------------------------------- /lib/MoLibUIComposer.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter_bloc/flutter_bloc.dart'; 3 | import 'package:molib/application/boundaries/add_book/IAddBookUseCase.dart'; 4 | import 'package:molib/application/usecases/AddBookUseCase.dart'; 5 | import 'package:molib/application/usecases/CreateShelfUseCase.dart'; 6 | import 'package:molib/application/usecases/GetAllBookUseCase.dart'; 7 | import 'package:molib/domain/repositories/IBookRepository.dart'; 8 | import 'package:molib/domain/repositories/IBookShelfRepository.dart'; 9 | import 'package:molib/infrastructure/datasources/SqfliteDatasource.dart'; 10 | import 'package:molib/infrastructure/factories/EntityFactory.dart'; 11 | import 'package:molib/infrastructure/repositories/BookRepsitory.dart'; 12 | import 'package:molib/infrastructure/repositories/ShelfRepository.dart'; 13 | import 'package:molib/presentation/UI/pages/AddBookPage.dart'; 14 | import 'package:molib/presentation/UI/pages/HomePage.dart'; 15 | import 'package:molib/presentation/viewmodels/AddBookViewModel.dart'; 16 | import 'package:molib/presentation/viewmodels/HomeViewModel.dart'; 17 | import 'package:sqflite/sqflite.dart'; 18 | 19 | import 'presentation/states/add_book_page/addbook_bloc.dart'; 20 | import 'presentation/states/home_page/homepage_bloc.dart'; 21 | 22 | class HomePageCoordinator implements HomePageDelegate { 23 | @override 24 | Future onAddBook(BuildContext context, String shelfId) { 25 | return Navigator.push( 26 | context, 27 | PageRouteBuilder( 28 | fullscreenDialog: true, 29 | barrierDismissible: true, 30 | barrierColor: Colors.black38, 31 | opaque: false, 32 | pageBuilder: (BuildContext context, _, __) => 33 | MoLibUIComposer.composeAddBookPage(shelfId), 34 | ), 35 | ); 36 | } 37 | } 38 | 39 | class MoLibUIComposer { 40 | static SqfliteDatasource _datasource; 41 | static final EntityFactory _entityFactory = EntityFactory(); 42 | static IBookRepository _bookRepository; 43 | static IBookShelfRepository _shelfRepository; 44 | 45 | static configure(Database db) { 46 | _datasource = SqfliteDatasource(db: db); 47 | _bookRepository = BookRepository(datasource: _datasource); 48 | _shelfRepository = ShelfRepository(_datasource); 49 | } 50 | 51 | static Widget composeHomePage() { 52 | HomePageCoordinator coordinator = HomePageCoordinator(); 53 | 54 | GetAllBooksUseCase getAllBooksUseCase = 55 | GetAllBooksUseCase(bookRepository: _bookRepository); 56 | CreateShelfUseCase createShelfUseCase = 57 | CreateShelfUseCase(_shelfRepository, _entityFactory); 58 | 59 | HomePageViewModel viewModel = HomePageViewModel( 60 | createShelfUseCase: createShelfUseCase, 61 | getAllBooksUseCase: getAllBooksUseCase, 62 | ); 63 | 64 | return BlocProvider( 65 | create: (context) => HomePageBloc(viewModel), 66 | child: HomePage(delegate: coordinator), 67 | ); 68 | } 69 | 70 | static Widget composeAddBookPage(String shelfId) { 71 | IAddBookUseCase addBookUseCase = AddBookUseCase( 72 | bookShelfRepository: _shelfRepository, 73 | bookRepository: _bookRepository, 74 | entityFactory: _entityFactory); 75 | 76 | AddBookViewModel viewModel = AddBookViewModel(addBookUseCase); 77 | 78 | return BlocProvider( 79 | create: (_) => AddBookBloc(viewModel), 80 | child: AddBookPage(shelfId), 81 | ); 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /test/application/usecases/add_book_usecase_test.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter_test/flutter_test.dart'; 2 | import 'package:mockito/mockito.dart'; 3 | import 'package:molib/application/boundaries/add_book/AddBookInput.dart'; 4 | import 'package:molib/application/usecases/AddBookUseCase.dart'; 5 | import 'package:molib/domain/entities/Book.dart'; 6 | import 'package:molib/domain/entities/BookShelf.dart'; 7 | import 'package:molib/domain/factories/IEntityFactory.dart'; 8 | import 'package:molib/domain/repositories/IBookRepository.dart'; 9 | import 'package:molib/domain/repositories/IBookShelfRepository.dart'; 10 | import 'package:molib/domain/value_objects/Author.dart'; 11 | import 'package:molib/domain/value_objects/ISBN.dart'; 12 | import 'package:molib/domain/value_objects/Identity.dart'; 13 | import 'package:molib/domain/value_objects/PublishDate.dart'; 14 | import 'package:molib/domain/value_objects/Title.dart'; 15 | 16 | class MockShelfRepository extends Mock implements IBookShelfRepository {} 17 | 18 | class MockBookRepository extends Mock implements IBookRepository {} 19 | 20 | class MockEntityFactory extends Mock implements IEntityFactory {} 21 | 22 | void main() { 23 | AddBookUseCase sut; 24 | MockShelfRepository mockShelfRepository; 25 | MockBookRepository mockBookRepository; 26 | MockEntityFactory mockEntityFactory; 27 | 28 | setUp(() { 29 | mockBookRepository = MockBookRepository(); 30 | mockShelfRepository = MockShelfRepository(); 31 | mockEntityFactory = MockEntityFactory(); 32 | 33 | sut = AddBookUseCase( 34 | bookShelfRepository: mockShelfRepository, 35 | bookRepository: mockBookRepository, 36 | entityFactory: mockEntityFactory); 37 | }); 38 | 39 | group('AddBookUseCase', () { 40 | var title = Title.create('Book Title').getOrElse(null); 41 | var author = Author.create('Book Author').getOrElse(null); 42 | var isbn = ISBN.create('ISBN-10: 0-596-52068-9').getOrElse(null); 43 | var publishDate = PublishDate.create('2020-01-20').getOrElse(null); 44 | 45 | var input = AddBookInput( 46 | shelfId: Identity.fromString('add'), 47 | title: title, 48 | author: author, 49 | isbn: isbn, 50 | publishDate: publishDate); 51 | 52 | test('should return a Failure when bookshelf does not exist', () async { 53 | //arrange 54 | when(mockShelfRepository.find(input.shelfId)).thenAnswer((_) => null); 55 | 56 | //act 57 | var result = await sut.execute(input); 58 | 59 | //assert 60 | expect(result.isLeft(), true); 61 | }); 62 | 63 | test('should return book with created id when added succesfully', () async { 64 | //arrange 65 | when(mockShelfRepository.find(input.shelfId)) 66 | .thenAnswer((_) async => BookShelf(id: input.shelfId)); 67 | 68 | when(mockEntityFactory.newBook( 69 | title: anyNamed('title'), 70 | author: anyNamed('author'), 71 | isbn: anyNamed('isbn'), 72 | publishDate: anyNamed('publishDate'))) 73 | .thenReturn( 74 | Book( 75 | id: Identity.fromString('bb'), 76 | title: input.title, 77 | author: input.author, 78 | isbn: input.isbn, 79 | publishDate: input.publishDate), 80 | ); 81 | 82 | //act 83 | var result = await sut.execute(input); 84 | 85 | //assert 86 | expect(result.isRight(), true); 87 | expect(result.getOrElse(null).bookId, isNotNull); 88 | verify(mockBookRepository.add(any)).called(1); 89 | }); 90 | }); 91 | } 92 | -------------------------------------------------------------------------------- /ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 32 | 33 | 39 | 40 | 41 | 42 | 43 | 44 | 54 | 56 | 62 | 63 | 64 | 65 | 66 | 67 | 73 | 75 | 81 | 82 | 83 | 84 | 86 | 87 | 90 | 91 | 92 | -------------------------------------------------------------------------------- /ios/Podfile: -------------------------------------------------------------------------------- 1 | # Uncomment this line to define a global platform for your project 2 | # platform :ios, '9.0' 3 | 4 | # CocoaPods analytics sends network stats synchronously affecting flutter build latency. 5 | ENV['COCOAPODS_DISABLE_STATS'] = 'true' 6 | 7 | project 'Runner', { 8 | 'Debug' => :debug, 9 | 'Profile' => :release, 10 | 'Release' => :release, 11 | } 12 | 13 | def parse_KV_file(file, separator='=') 14 | file_abs_path = File.expand_path(file) 15 | if !File.exists? file_abs_path 16 | return []; 17 | end 18 | generated_key_values = {} 19 | skip_line_start_symbols = ["#", "/"] 20 | File.foreach(file_abs_path) do |line| 21 | next if skip_line_start_symbols.any? { |symbol| line =~ /^\s*#{symbol}/ } 22 | plugin = line.split(pattern=separator) 23 | if plugin.length == 2 24 | podname = plugin[0].strip() 25 | path = plugin[1].strip() 26 | podpath = File.expand_path("#{path}", file_abs_path) 27 | generated_key_values[podname] = podpath 28 | else 29 | puts "Invalid plugin specification: #{line}" 30 | end 31 | end 32 | generated_key_values 33 | end 34 | 35 | target 'Runner' do 36 | use_frameworks! 37 | use_modular_headers! 38 | 39 | # Flutter Pod 40 | 41 | copied_flutter_dir = File.join(__dir__, 'Flutter') 42 | copied_framework_path = File.join(copied_flutter_dir, 'Flutter.framework') 43 | copied_podspec_path = File.join(copied_flutter_dir, 'Flutter.podspec') 44 | unless File.exist?(copied_framework_path) && File.exist?(copied_podspec_path) 45 | # Copy Flutter.framework and Flutter.podspec to Flutter/ to have something to link against if the xcode backend script has not run yet. 46 | # That script will copy the correct debug/profile/release version of the framework based on the currently selected Xcode configuration. 47 | # CocoaPods will not embed the framework on pod install (before any build phases can generate) if the dylib does not exist. 48 | 49 | generated_xcode_build_settings_path = File.join(copied_flutter_dir, 'Generated.xcconfig') 50 | unless File.exist?(generated_xcode_build_settings_path) 51 | raise "Generated.xcconfig must exist. If you're running pod install manually, make sure flutter pub get is executed first" 52 | end 53 | generated_xcode_build_settings = parse_KV_file(generated_xcode_build_settings_path) 54 | cached_framework_dir = generated_xcode_build_settings['FLUTTER_FRAMEWORK_DIR']; 55 | 56 | unless File.exist?(copied_framework_path) 57 | FileUtils.cp_r(File.join(cached_framework_dir, 'Flutter.framework'), copied_flutter_dir) 58 | end 59 | unless File.exist?(copied_podspec_path) 60 | FileUtils.cp(File.join(cached_framework_dir, 'Flutter.podspec'), copied_flutter_dir) 61 | end 62 | end 63 | 64 | # Keep pod path relative so it can be checked into Podfile.lock. 65 | pod 'Flutter', :path => 'Flutter' 66 | 67 | # Plugin Pods 68 | 69 | # Prepare symlinks folder. We use symlinks to avoid having Podfile.lock 70 | # referring to absolute paths on developers' machines. 71 | system('rm -rf .symlinks') 72 | system('mkdir -p .symlinks/plugins') 73 | plugin_pods = parse_KV_file('../.flutter-plugins') 74 | plugin_pods.each do |name, path| 75 | symlink = File.join('.symlinks', 'plugins', name) 76 | File.symlink(path, symlink) 77 | pod name, :path => File.join(symlink, 'ios') 78 | end 79 | end 80 | 81 | # Prevent Cocoapods from embedding a second Flutter framework and causing an error with the new Xcode build system. 82 | install! 'cocoapods', :disable_input_output_paths => true 83 | 84 | post_install do |installer| 85 | installer.pods_project.targets.each do |target| 86 | target.build_configurations.each do |config| 87 | config.build_settings['ENABLE_BITCODE'] = 'NO' 88 | end 89 | end 90 | end 91 | -------------------------------------------------------------------------------- /lib/presentation/UI/pages/HomePage.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter_bloc/flutter_bloc.dart'; 3 | import 'package:molib/presentation/UI/widgets/BookWidget.dart'; 4 | import 'package:molib/presentation/models/Book.dart'; 5 | import 'package:molib/presentation/models/BookShelf.dart'; 6 | import 'package:molib/presentation/states/home_page/homepage_bloc.dart'; 7 | import 'package:molib/presentation/viewmodels/HomeViewModel.dart'; 8 | 9 | abstract class HomePageDelegate { 10 | Future onAddBook(BuildContext context, String shelfId); 11 | } 12 | 13 | class HomePage extends StatefulWidget { 14 | final HomePageDelegate delegate; 15 | 16 | HomePage({this.delegate}); 17 | 18 | @override 19 | _HomePageState createState() => _HomePageState(); 20 | } 21 | 22 | class _HomePageState extends State { 23 | final _controller = PageController(initialPage: 0); 24 | 25 | List _shelves; 26 | 27 | @override 28 | void initState() { 29 | BlocProvider.of(context).getBooksOnShelves(); 30 | super.initState(); 31 | } 32 | 33 | @override 34 | Widget build(BuildContext context) { 35 | return Scaffold( 36 | appBar: AppBar( 37 | title: Text( 38 | 'Vitrual Library', 39 | style: TextStyle( 40 | color: Colors.black, 41 | fontWeight: FontWeight.bold, 42 | ), 43 | ), 44 | elevation: 0.0, 45 | backgroundColor: Colors.white, 46 | brightness: Brightness.light, 47 | ), 48 | body: SafeArea( 49 | child: Stack(fit: StackFit.expand, children: [ 50 | _background(), 51 | //_buildShelfUI(this.shelves) 52 | _buildUI(), 53 | ])), 54 | ); 55 | } 56 | 57 | Widget _buildUI() => BlocBuilder( 58 | builder: (context, state) { 59 | if (state.isFetching) 60 | return Center( 61 | child: CircularProgressIndicator(), 62 | ); 63 | else 64 | return _buildShelfUI(state.shelves); 65 | }, 66 | ); 67 | 68 | _background() => Container( 69 | child: Image.asset( 70 | 'assets/images/shelf.png', 71 | fit: BoxFit.fill, 72 | ), 73 | ); 74 | 75 | _buildShelfUI(List shelves) { 76 | this._shelves = shelves; 77 | return PageView.builder( 78 | itemCount: shelves.length, 79 | itemBuilder: (_, idx) => _books(shelves[idx].books), 80 | controller: _controller, 81 | ); 82 | } 83 | 84 | _books(List books) { 85 | List children = _createGridviewChildren(books); 86 | 87 | return Container( 88 | child: GridView.count( 89 | addAutomaticKeepAlives: true, 90 | physics: NeverScrollableScrollPhysics(), 91 | padding: EdgeInsets.all(24.0), 92 | childAspectRatio: 8.0 / 9.0, 93 | crossAxisCount: 2, 94 | crossAxisSpacing: 20, 95 | mainAxisSpacing: 37, 96 | children: children, 97 | ), 98 | ); 99 | } 100 | 101 | _createGridviewChildren(List books) { 102 | List children = []; 103 | books.forEach((book) => children.add(BookWidget(book: book))); 104 | if (children.length < BOOKS_PER_SHELF) { 105 | children.add( 106 | Hero( 107 | transitionOnUserGestures: true, 108 | tag: 'addBook', 109 | child: GestureDetector( 110 | onTap: () => _showCreateBookPage(), 111 | child: Image.asset( 112 | 'assets/images/add_book.png', 113 | fit: BoxFit.cover, 114 | ), 115 | ), 116 | ), 117 | ); 118 | } 119 | return children; 120 | } 121 | 122 | _showCreateBookPage() async { 123 | var shelf = _shelves[_controller.page.toInt()]; 124 | var book = await widget.delegate.onAddBook(context, shelf.id); 125 | 126 | if (book) { 127 | BlocProvider.of(context).getBooksOnShelves(); 128 | } 129 | } 130 | } 131 | -------------------------------------------------------------------------------- /lib/presentation/UI/pages/AddBookPage.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter_bloc/flutter_bloc.dart'; 3 | import 'package:molib/presentation/states/add_book_page/addbook_bloc.dart'; 4 | 5 | class AddBookPage extends StatefulWidget { 6 | final shelfId; 7 | 8 | AddBookPage(this.shelfId); 9 | 10 | @override 11 | _AddBookPageState createState() => _AddBookPageState(); 12 | } 13 | 14 | class _AddBookPageState extends State { 15 | String _title = ""; 16 | String _author = ""; 17 | String _isbn = ""; 18 | String _publishDate = ""; 19 | String _shelfId; 20 | 21 | @override 22 | void initState() { 23 | _shelfId = widget.shelfId; 24 | super.initState(); 25 | } 26 | 27 | @override 28 | Widget build(BuildContext context) { 29 | return GestureDetector( 30 | onTap: () => Navigator.of(context).pop(), 31 | child: Scaffold( 32 | extendBodyBehindAppBar: true, 33 | backgroundColor: Colors.transparent, 34 | body: Container( 35 | child: Center( 36 | child: Hero( 37 | tag: 'addBook', 38 | child: Card( 39 | child: Stack(children: [ 40 | Image.asset( 41 | 'assets/images/book.png', 42 | fit: BoxFit.fill, 43 | ), 44 | BlocConsumer( 45 | bloc: BlocProvider.of(context), 46 | builder: (context, state) { 47 | if (state.isCommitting) 48 | return Center(child: CircularProgressIndicator()); 49 | else 50 | return _buildUI(); 51 | }, 52 | listener: (context, state) { 53 | if (state.errMessage.isNotEmpty) { 54 | Scaffold.of(context).showSnackBar(SnackBar( 55 | content: Text(state.errMessage.toString()), 56 | )); 57 | } 58 | 59 | if (state.book != null) { 60 | Navigator.of(context).pop(true); 61 | } 62 | }) 63 | ])), 64 | )), 65 | ), 66 | ), 67 | ); 68 | } 69 | 70 | Widget _buildUI() => Container( 71 | height: 349, 72 | width: 309, 73 | padding: EdgeInsets.only(left: 50, right: 20, top: 40), 74 | child: ListView( 75 | physics: NeverScrollableScrollPhysics(), 76 | children: [ 77 | _createTextField( 78 | hint: 'Title', 79 | fontSize: 26, 80 | fontWeight: FontWeight.bold, 81 | onChanged: (val) => _title = val, 82 | ), 83 | _createTextField( 84 | hint: 'Author', 85 | fontSize: 18, 86 | fontWeight: FontWeight.normal, 87 | onChanged: (val) => _author = val, 88 | ), 89 | _createTextField( 90 | hint: 'ISBN', 91 | fontSize: 18, 92 | fontWeight: FontWeight.normal, 93 | onChanged: (val) => _isbn = val, 94 | ), 95 | _createTextField( 96 | hint: 'Date', 97 | fontSize: 18, 98 | fontWeight: FontWeight.normal, 99 | onChanged: (val) => _publishDate = val, 100 | ), 101 | Container( 102 | alignment: Alignment.bottomRight, 103 | child: FlatButton( 104 | onPressed: () { 105 | addBook(context); 106 | }, 107 | child: Text( 108 | 'Add', 109 | style: TextStyle( 110 | fontSize: 20, 111 | color: Colors.blue, 112 | ), 113 | ), 114 | ), 115 | ) 116 | ], 117 | ), 118 | ); 119 | 120 | addBook(BuildContext context) { 121 | BlocProvider.of(context).addBook( 122 | title: _title, 123 | author: _author, 124 | isbn: _isbn, 125 | publishDate: _publishDate, 126 | shelfId: _shelfId); 127 | } 128 | 129 | TextField _createTextField({ 130 | String hint, 131 | double fontSize, 132 | FontWeight fontWeight, 133 | Function(String val) onChanged, 134 | }) { 135 | return TextField( 136 | onChanged: onChanged, 137 | cursorColor: Colors.black87, 138 | style: TextStyle( 139 | color: Colors.black, fontWeight: fontWeight, fontSize: fontSize), 140 | decoration: InputDecoration( 141 | hintText: hint, 142 | hintStyle: TextStyle( 143 | fontSize: fontSize, 144 | fontWeight: fontWeight, 145 | ), 146 | border: InputBorder.none), 147 | ); 148 | } 149 | } 150 | -------------------------------------------------------------------------------- /pubspec.lock: -------------------------------------------------------------------------------- 1 | # Generated by pub 2 | # See https://dart.dev/tools/pub/glossary#lockfile 3 | packages: 4 | archive: 5 | dependency: transitive 6 | description: 7 | name: archive 8 | url: "https://pub.dartlang.org" 9 | source: hosted 10 | version: "2.0.13" 11 | args: 12 | dependency: transitive 13 | description: 14 | name: args 15 | url: "https://pub.dartlang.org" 16 | source: hosted 17 | version: "1.6.0" 18 | async: 19 | dependency: transitive 20 | description: 21 | name: async 22 | url: "https://pub.dartlang.org" 23 | source: hosted 24 | version: "2.4.1" 25 | bloc: 26 | dependency: transitive 27 | description: 28 | name: bloc 29 | url: "https://pub.dartlang.org" 30 | source: hosted 31 | version: "4.0.0" 32 | boolean_selector: 33 | dependency: transitive 34 | description: 35 | name: boolean_selector 36 | url: "https://pub.dartlang.org" 37 | source: hosted 38 | version: "2.0.0" 39 | charcode: 40 | dependency: transitive 41 | description: 42 | name: charcode 43 | url: "https://pub.dartlang.org" 44 | source: hosted 45 | version: "1.1.3" 46 | collection: 47 | dependency: transitive 48 | description: 49 | name: collection 50 | url: "https://pub.dartlang.org" 51 | source: hosted 52 | version: "1.14.12" 53 | convert: 54 | dependency: transitive 55 | description: 56 | name: convert 57 | url: "https://pub.dartlang.org" 58 | source: hosted 59 | version: "2.1.1" 60 | crypto: 61 | dependency: transitive 62 | description: 63 | name: crypto 64 | url: "https://pub.dartlang.org" 65 | source: hosted 66 | version: "2.1.4" 67 | cupertino_icons: 68 | dependency: "direct main" 69 | description: 70 | name: cupertino_icons 71 | url: "https://pub.dartlang.org" 72 | source: hosted 73 | version: "0.1.3" 74 | dartz: 75 | dependency: "direct main" 76 | description: 77 | name: dartz 78 | url: "https://pub.dartlang.org" 79 | source: hosted 80 | version: "0.8.9" 81 | equatable: 82 | dependency: "direct main" 83 | description: 84 | name: equatable 85 | url: "https://pub.dartlang.org" 86 | source: hosted 87 | version: "1.1.1" 88 | flutter: 89 | dependency: "direct main" 90 | description: flutter 91 | source: sdk 92 | version: "0.0.0" 93 | flutter_bloc: 94 | dependency: "direct main" 95 | description: 96 | name: flutter_bloc 97 | url: "https://pub.dartlang.org" 98 | source: hosted 99 | version: "4.0.0" 100 | flutter_test: 101 | dependency: "direct dev" 102 | description: flutter 103 | source: sdk 104 | version: "0.0.0" 105 | image: 106 | dependency: transitive 107 | description: 108 | name: image 109 | url: "https://pub.dartlang.org" 110 | source: hosted 111 | version: "2.1.12" 112 | intl: 113 | dependency: "direct main" 114 | description: 115 | name: intl 116 | url: "https://pub.dartlang.org" 117 | source: hosted 118 | version: "0.16.1" 119 | matcher: 120 | dependency: transitive 121 | description: 122 | name: matcher 123 | url: "https://pub.dartlang.org" 124 | source: hosted 125 | version: "0.12.6" 126 | meta: 127 | dependency: transitive 128 | description: 129 | name: meta 130 | url: "https://pub.dartlang.org" 131 | source: hosted 132 | version: "1.1.8" 133 | mockito: 134 | dependency: "direct main" 135 | description: 136 | name: mockito 137 | url: "https://pub.dartlang.org" 138 | source: hosted 139 | version: "4.1.1" 140 | nested: 141 | dependency: transitive 142 | description: 143 | name: nested 144 | url: "https://pub.dartlang.org" 145 | source: hosted 146 | version: "0.0.4" 147 | path: 148 | dependency: "direct main" 149 | description: 150 | name: path 151 | url: "https://pub.dartlang.org" 152 | source: hosted 153 | version: "1.6.4" 154 | petitparser: 155 | dependency: transitive 156 | description: 157 | name: petitparser 158 | url: "https://pub.dartlang.org" 159 | source: hosted 160 | version: "2.4.0" 161 | provider: 162 | dependency: transitive 163 | description: 164 | name: provider 165 | url: "https://pub.dartlang.org" 166 | source: hosted 167 | version: "4.1.2" 168 | quiver: 169 | dependency: transitive 170 | description: 171 | name: quiver 172 | url: "https://pub.dartlang.org" 173 | source: hosted 174 | version: "2.1.3" 175 | sky_engine: 176 | dependency: transitive 177 | description: flutter 178 | source: sdk 179 | version: "0.0.99" 180 | source_span: 181 | dependency: transitive 182 | description: 183 | name: source_span 184 | url: "https://pub.dartlang.org" 185 | source: hosted 186 | version: "1.7.0" 187 | sqflite: 188 | dependency: "direct main" 189 | description: 190 | name: sqflite 191 | url: "https://pub.dartlang.org" 192 | source: hosted 193 | version: "1.3.0" 194 | sqflite_common: 195 | dependency: transitive 196 | description: 197 | name: sqflite_common 198 | url: "https://pub.dartlang.org" 199 | source: hosted 200 | version: "1.0.0+1" 201 | stack_trace: 202 | dependency: transitive 203 | description: 204 | name: stack_trace 205 | url: "https://pub.dartlang.org" 206 | source: hosted 207 | version: "1.9.3" 208 | stream_channel: 209 | dependency: transitive 210 | description: 211 | name: stream_channel 212 | url: "https://pub.dartlang.org" 213 | source: hosted 214 | version: "2.0.0" 215 | string_scanner: 216 | dependency: transitive 217 | description: 218 | name: string_scanner 219 | url: "https://pub.dartlang.org" 220 | source: hosted 221 | version: "1.0.5" 222 | synchronized: 223 | dependency: transitive 224 | description: 225 | name: synchronized 226 | url: "https://pub.dartlang.org" 227 | source: hosted 228 | version: "2.2.0" 229 | term_glyph: 230 | dependency: transitive 231 | description: 232 | name: term_glyph 233 | url: "https://pub.dartlang.org" 234 | source: hosted 235 | version: "1.1.0" 236 | test_api: 237 | dependency: transitive 238 | description: 239 | name: test_api 240 | url: "https://pub.dartlang.org" 241 | source: hosted 242 | version: "0.2.15" 243 | typed_data: 244 | dependency: transitive 245 | description: 246 | name: typed_data 247 | url: "https://pub.dartlang.org" 248 | source: hosted 249 | version: "1.1.6" 250 | uuid: 251 | dependency: "direct main" 252 | description: 253 | name: uuid 254 | url: "https://pub.dartlang.org" 255 | source: hosted 256 | version: "2.0.4" 257 | vector_math: 258 | dependency: transitive 259 | description: 260 | name: vector_math 261 | url: "https://pub.dartlang.org" 262 | source: hosted 263 | version: "2.0.8" 264 | xml: 265 | dependency: transitive 266 | description: 267 | name: xml 268 | url: "https://pub.dartlang.org" 269 | source: hosted 270 | version: "3.6.1" 271 | sdks: 272 | dart: ">=2.7.0 <3.0.0" 273 | flutter: ">=1.16.0 <2.0.0" 274 | -------------------------------------------------------------------------------- /ios/Runner.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 46; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; }; 11 | 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; }; 12 | 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74858FAE1ED2DC5600515810 /* AppDelegate.swift */; }; 13 | 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; }; 14 | 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; }; 15 | 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; }; 16 | D10D10F3E8D1629FF3AF5A90 /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 0DF51C9C1A20E6CB07A54D74 /* Pods_Runner.framework */; }; 17 | /* End PBXBuildFile section */ 18 | 19 | /* Begin PBXCopyFilesBuildPhase section */ 20 | 9705A1C41CF9048500538489 /* Embed Frameworks */ = { 21 | isa = PBXCopyFilesBuildPhase; 22 | buildActionMask = 2147483647; 23 | dstPath = ""; 24 | dstSubfolderSpec = 10; 25 | files = ( 26 | ); 27 | name = "Embed Frameworks"; 28 | runOnlyForDeploymentPostprocessing = 0; 29 | }; 30 | /* End PBXCopyFilesBuildPhase section */ 31 | 32 | /* Begin PBXFileReference section */ 33 | 0DF51C9C1A20E6CB07A54D74 /* Pods_Runner.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Runner.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 34 | 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = ""; }; 35 | 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = ""; }; 36 | 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = ""; }; 37 | 4190886B0C551EED10F9AF3E /* Pods-Runner.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.debug.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"; sourceTree = ""; }; 38 | 6082BD1562EADEF24CD26C75 /* Pods-Runner.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.release.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"; sourceTree = ""; }; 39 | 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Runner-Bridging-Header.h"; sourceTree = ""; }; 40 | 74858FAE1ED2DC5600515810 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 41 | 7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = ""; }; 42 | 9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Debug.xcconfig; path = Flutter/Debug.xcconfig; sourceTree = ""; }; 43 | 9740EEB31CF90195004384FC /* Generated.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Generated.xcconfig; path = Flutter/Generated.xcconfig; sourceTree = ""; }; 44 | 97C146EE1CF9000F007C117D /* Runner.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Runner.app; sourceTree = BUILT_PRODUCTS_DIR; }; 45 | 97C146FB1CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; 46 | 97C146FD1CF9000F007C117D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 47 | 97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 48 | 97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 49 | C27914E54D2B8D19ACD2DFB4 /* Pods-Runner.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.profile.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.profile.xcconfig"; sourceTree = ""; }; 50 | /* End PBXFileReference section */ 51 | 52 | /* Begin PBXFrameworksBuildPhase section */ 53 | 97C146EB1CF9000F007C117D /* Frameworks */ = { 54 | isa = PBXFrameworksBuildPhase; 55 | buildActionMask = 2147483647; 56 | files = ( 57 | D10D10F3E8D1629FF3AF5A90 /* Pods_Runner.framework in Frameworks */, 58 | ); 59 | runOnlyForDeploymentPostprocessing = 0; 60 | }; 61 | /* End PBXFrameworksBuildPhase section */ 62 | 63 | /* Begin PBXGroup section */ 64 | 3AB3575B0A225780DC0B6CE8 /* Frameworks */ = { 65 | isa = PBXGroup; 66 | children = ( 67 | 0DF51C9C1A20E6CB07A54D74 /* Pods_Runner.framework */, 68 | ); 69 | name = Frameworks; 70 | sourceTree = ""; 71 | }; 72 | 9740EEB11CF90186004384FC /* Flutter */ = { 73 | isa = PBXGroup; 74 | children = ( 75 | 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */, 76 | 9740EEB21CF90195004384FC /* Debug.xcconfig */, 77 | 7AFA3C8E1D35360C0083082E /* Release.xcconfig */, 78 | 9740EEB31CF90195004384FC /* Generated.xcconfig */, 79 | ); 80 | name = Flutter; 81 | sourceTree = ""; 82 | }; 83 | 97C146E51CF9000F007C117D = { 84 | isa = PBXGroup; 85 | children = ( 86 | 9740EEB11CF90186004384FC /* Flutter */, 87 | 97C146F01CF9000F007C117D /* Runner */, 88 | 97C146EF1CF9000F007C117D /* Products */, 89 | 9A471C93BD0DB67EEF1F22BC /* Pods */, 90 | 3AB3575B0A225780DC0B6CE8 /* Frameworks */, 91 | ); 92 | sourceTree = ""; 93 | }; 94 | 97C146EF1CF9000F007C117D /* Products */ = { 95 | isa = PBXGroup; 96 | children = ( 97 | 97C146EE1CF9000F007C117D /* Runner.app */, 98 | ); 99 | name = Products; 100 | sourceTree = ""; 101 | }; 102 | 97C146F01CF9000F007C117D /* Runner */ = { 103 | isa = PBXGroup; 104 | children = ( 105 | 97C146FA1CF9000F007C117D /* Main.storyboard */, 106 | 97C146FD1CF9000F007C117D /* Assets.xcassets */, 107 | 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */, 108 | 97C147021CF9000F007C117D /* Info.plist */, 109 | 97C146F11CF9000F007C117D /* Supporting Files */, 110 | 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */, 111 | 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */, 112 | 74858FAE1ED2DC5600515810 /* AppDelegate.swift */, 113 | 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */, 114 | ); 115 | path = Runner; 116 | sourceTree = ""; 117 | }; 118 | 97C146F11CF9000F007C117D /* Supporting Files */ = { 119 | isa = PBXGroup; 120 | children = ( 121 | ); 122 | name = "Supporting Files"; 123 | sourceTree = ""; 124 | }; 125 | 9A471C93BD0DB67EEF1F22BC /* Pods */ = { 126 | isa = PBXGroup; 127 | children = ( 128 | 4190886B0C551EED10F9AF3E /* Pods-Runner.debug.xcconfig */, 129 | 6082BD1562EADEF24CD26C75 /* Pods-Runner.release.xcconfig */, 130 | C27914E54D2B8D19ACD2DFB4 /* Pods-Runner.profile.xcconfig */, 131 | ); 132 | name = Pods; 133 | path = Pods; 134 | sourceTree = ""; 135 | }; 136 | /* End PBXGroup section */ 137 | 138 | /* Begin PBXNativeTarget section */ 139 | 97C146ED1CF9000F007C117D /* Runner */ = { 140 | isa = PBXNativeTarget; 141 | buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */; 142 | buildPhases = ( 143 | 2D154FC53BC8DF92E3672E26 /* [CP] Check Pods Manifest.lock */, 144 | 9740EEB61CF901F6004384FC /* Run Script */, 145 | 97C146EA1CF9000F007C117D /* Sources */, 146 | 97C146EB1CF9000F007C117D /* Frameworks */, 147 | 97C146EC1CF9000F007C117D /* Resources */, 148 | 9705A1C41CF9048500538489 /* Embed Frameworks */, 149 | 3B06AD1E1E4923F5004D2608 /* Thin Binary */, 150 | E050EE0F6B9C68EB35405664 /* [CP] Embed Pods Frameworks */, 151 | ); 152 | buildRules = ( 153 | ); 154 | dependencies = ( 155 | ); 156 | name = Runner; 157 | productName = Runner; 158 | productReference = 97C146EE1CF9000F007C117D /* Runner.app */; 159 | productType = "com.apple.product-type.application"; 160 | }; 161 | /* End PBXNativeTarget section */ 162 | 163 | /* Begin PBXProject section */ 164 | 97C146E61CF9000F007C117D /* Project object */ = { 165 | isa = PBXProject; 166 | attributes = { 167 | LastUpgradeCheck = 1020; 168 | ORGANIZATIONNAME = "The Chromium Authors"; 169 | TargetAttributes = { 170 | 97C146ED1CF9000F007C117D = { 171 | CreatedOnToolsVersion = 7.3.1; 172 | LastSwiftMigration = 1100; 173 | }; 174 | }; 175 | }; 176 | buildConfigurationList = 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */; 177 | compatibilityVersion = "Xcode 3.2"; 178 | developmentRegion = en; 179 | hasScannedForEncodings = 0; 180 | knownRegions = ( 181 | en, 182 | Base, 183 | ); 184 | mainGroup = 97C146E51CF9000F007C117D; 185 | productRefGroup = 97C146EF1CF9000F007C117D /* Products */; 186 | projectDirPath = ""; 187 | projectRoot = ""; 188 | targets = ( 189 | 97C146ED1CF9000F007C117D /* Runner */, 190 | ); 191 | }; 192 | /* End PBXProject section */ 193 | 194 | /* Begin PBXResourcesBuildPhase section */ 195 | 97C146EC1CF9000F007C117D /* Resources */ = { 196 | isa = PBXResourcesBuildPhase; 197 | buildActionMask = 2147483647; 198 | files = ( 199 | 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */, 200 | 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */, 201 | 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */, 202 | 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */, 203 | ); 204 | runOnlyForDeploymentPostprocessing = 0; 205 | }; 206 | /* End PBXResourcesBuildPhase section */ 207 | 208 | /* Begin PBXShellScriptBuildPhase section */ 209 | 2D154FC53BC8DF92E3672E26 /* [CP] Check Pods Manifest.lock */ = { 210 | isa = PBXShellScriptBuildPhase; 211 | buildActionMask = 2147483647; 212 | files = ( 213 | ); 214 | inputFileListPaths = ( 215 | ); 216 | inputPaths = ( 217 | "${PODS_PODFILE_DIR_PATH}/Podfile.lock", 218 | "${PODS_ROOT}/Manifest.lock", 219 | ); 220 | name = "[CP] Check Pods Manifest.lock"; 221 | outputFileListPaths = ( 222 | ); 223 | outputPaths = ( 224 | "$(DERIVED_FILE_DIR)/Pods-Runner-checkManifestLockResult.txt", 225 | ); 226 | runOnlyForDeploymentPostprocessing = 0; 227 | shellPath = /bin/sh; 228 | shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; 229 | showEnvVarsInLog = 0; 230 | }; 231 | 3B06AD1E1E4923F5004D2608 /* Thin Binary */ = { 232 | isa = PBXShellScriptBuildPhase; 233 | buildActionMask = 2147483647; 234 | files = ( 235 | ); 236 | inputPaths = ( 237 | ); 238 | name = "Thin Binary"; 239 | outputPaths = ( 240 | ); 241 | runOnlyForDeploymentPostprocessing = 0; 242 | shellPath = /bin/sh; 243 | shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" embed_and_thin"; 244 | }; 245 | 9740EEB61CF901F6004384FC /* Run Script */ = { 246 | isa = PBXShellScriptBuildPhase; 247 | buildActionMask = 2147483647; 248 | files = ( 249 | ); 250 | inputPaths = ( 251 | ); 252 | name = "Run Script"; 253 | outputPaths = ( 254 | ); 255 | runOnlyForDeploymentPostprocessing = 0; 256 | shellPath = /bin/sh; 257 | shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build"; 258 | }; 259 | E050EE0F6B9C68EB35405664 /* [CP] Embed Pods Frameworks */ = { 260 | isa = PBXShellScriptBuildPhase; 261 | buildActionMask = 2147483647; 262 | files = ( 263 | ); 264 | inputPaths = ( 265 | ); 266 | name = "[CP] Embed Pods Frameworks"; 267 | outputPaths = ( 268 | ); 269 | runOnlyForDeploymentPostprocessing = 0; 270 | shellPath = /bin/sh; 271 | shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh\"\n"; 272 | showEnvVarsInLog = 0; 273 | }; 274 | /* End PBXShellScriptBuildPhase section */ 275 | 276 | /* Begin PBXSourcesBuildPhase section */ 277 | 97C146EA1CF9000F007C117D /* Sources */ = { 278 | isa = PBXSourcesBuildPhase; 279 | buildActionMask = 2147483647; 280 | files = ( 281 | 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */, 282 | 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */, 283 | ); 284 | runOnlyForDeploymentPostprocessing = 0; 285 | }; 286 | /* End PBXSourcesBuildPhase section */ 287 | 288 | /* Begin PBXVariantGroup section */ 289 | 97C146FA1CF9000F007C117D /* Main.storyboard */ = { 290 | isa = PBXVariantGroup; 291 | children = ( 292 | 97C146FB1CF9000F007C117D /* Base */, 293 | ); 294 | name = Main.storyboard; 295 | sourceTree = ""; 296 | }; 297 | 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */ = { 298 | isa = PBXVariantGroup; 299 | children = ( 300 | 97C147001CF9000F007C117D /* Base */, 301 | ); 302 | name = LaunchScreen.storyboard; 303 | sourceTree = ""; 304 | }; 305 | /* End PBXVariantGroup section */ 306 | 307 | /* Begin XCBuildConfiguration section */ 308 | 249021D3217E4FDB00AE95B9 /* Profile */ = { 309 | isa = XCBuildConfiguration; 310 | baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; 311 | buildSettings = { 312 | ALWAYS_SEARCH_USER_PATHS = NO; 313 | CLANG_ANALYZER_NONNULL = YES; 314 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 315 | CLANG_CXX_LIBRARY = "libc++"; 316 | CLANG_ENABLE_MODULES = YES; 317 | CLANG_ENABLE_OBJC_ARC = YES; 318 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 319 | CLANG_WARN_BOOL_CONVERSION = YES; 320 | CLANG_WARN_COMMA = YES; 321 | CLANG_WARN_CONSTANT_CONVERSION = YES; 322 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 323 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 324 | CLANG_WARN_EMPTY_BODY = YES; 325 | CLANG_WARN_ENUM_CONVERSION = YES; 326 | CLANG_WARN_INFINITE_RECURSION = YES; 327 | CLANG_WARN_INT_CONVERSION = YES; 328 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 329 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 330 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 331 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 332 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 333 | CLANG_WARN_STRICT_PROTOTYPES = YES; 334 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 335 | CLANG_WARN_UNREACHABLE_CODE = YES; 336 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 337 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 338 | COPY_PHASE_STRIP = NO; 339 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 340 | ENABLE_NS_ASSERTIONS = NO; 341 | ENABLE_STRICT_OBJC_MSGSEND = YES; 342 | GCC_C_LANGUAGE_STANDARD = gnu99; 343 | GCC_NO_COMMON_BLOCKS = YES; 344 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 345 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 346 | GCC_WARN_UNDECLARED_SELECTOR = YES; 347 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 348 | GCC_WARN_UNUSED_FUNCTION = YES; 349 | GCC_WARN_UNUSED_VARIABLE = YES; 350 | IPHONEOS_DEPLOYMENT_TARGET = 8.0; 351 | MTL_ENABLE_DEBUG_INFO = NO; 352 | SDKROOT = iphoneos; 353 | SUPPORTED_PLATFORMS = iphoneos; 354 | TARGETED_DEVICE_FAMILY = "1,2"; 355 | VALIDATE_PRODUCT = YES; 356 | }; 357 | name = Profile; 358 | }; 359 | 249021D4217E4FDB00AE95B9 /* Profile */ = { 360 | isa = XCBuildConfiguration; 361 | baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; 362 | buildSettings = { 363 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 364 | CLANG_ENABLE_MODULES = YES; 365 | CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; 366 | ENABLE_BITCODE = NO; 367 | FRAMEWORK_SEARCH_PATHS = ( 368 | "$(inherited)", 369 | "$(PROJECT_DIR)/Flutter", 370 | ); 371 | INFOPLIST_FILE = Runner/Info.plist; 372 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 373 | LIBRARY_SEARCH_PATHS = ( 374 | "$(inherited)", 375 | "$(PROJECT_DIR)/Flutter", 376 | ); 377 | PRODUCT_BUNDLE_IDENTIFIER = com.example.molib; 378 | PRODUCT_NAME = "$(TARGET_NAME)"; 379 | SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; 380 | SWIFT_VERSION = 5.0; 381 | VERSIONING_SYSTEM = "apple-generic"; 382 | }; 383 | name = Profile; 384 | }; 385 | 97C147031CF9000F007C117D /* Debug */ = { 386 | isa = XCBuildConfiguration; 387 | baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */; 388 | buildSettings = { 389 | ALWAYS_SEARCH_USER_PATHS = NO; 390 | CLANG_ANALYZER_NONNULL = YES; 391 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 392 | CLANG_CXX_LIBRARY = "libc++"; 393 | CLANG_ENABLE_MODULES = YES; 394 | CLANG_ENABLE_OBJC_ARC = YES; 395 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 396 | CLANG_WARN_BOOL_CONVERSION = YES; 397 | CLANG_WARN_COMMA = YES; 398 | CLANG_WARN_CONSTANT_CONVERSION = YES; 399 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 400 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 401 | CLANG_WARN_EMPTY_BODY = YES; 402 | CLANG_WARN_ENUM_CONVERSION = YES; 403 | CLANG_WARN_INFINITE_RECURSION = YES; 404 | CLANG_WARN_INT_CONVERSION = YES; 405 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 406 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 407 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 408 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 409 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 410 | CLANG_WARN_STRICT_PROTOTYPES = YES; 411 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 412 | CLANG_WARN_UNREACHABLE_CODE = YES; 413 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 414 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 415 | COPY_PHASE_STRIP = NO; 416 | DEBUG_INFORMATION_FORMAT = dwarf; 417 | ENABLE_STRICT_OBJC_MSGSEND = YES; 418 | ENABLE_TESTABILITY = YES; 419 | GCC_C_LANGUAGE_STANDARD = gnu99; 420 | GCC_DYNAMIC_NO_PIC = NO; 421 | GCC_NO_COMMON_BLOCKS = YES; 422 | GCC_OPTIMIZATION_LEVEL = 0; 423 | GCC_PREPROCESSOR_DEFINITIONS = ( 424 | "DEBUG=1", 425 | "$(inherited)", 426 | ); 427 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 428 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 429 | GCC_WARN_UNDECLARED_SELECTOR = YES; 430 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 431 | GCC_WARN_UNUSED_FUNCTION = YES; 432 | GCC_WARN_UNUSED_VARIABLE = YES; 433 | IPHONEOS_DEPLOYMENT_TARGET = 8.0; 434 | MTL_ENABLE_DEBUG_INFO = YES; 435 | ONLY_ACTIVE_ARCH = YES; 436 | SDKROOT = iphoneos; 437 | TARGETED_DEVICE_FAMILY = "1,2"; 438 | }; 439 | name = Debug; 440 | }; 441 | 97C147041CF9000F007C117D /* Release */ = { 442 | isa = XCBuildConfiguration; 443 | baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; 444 | buildSettings = { 445 | ALWAYS_SEARCH_USER_PATHS = NO; 446 | CLANG_ANALYZER_NONNULL = YES; 447 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 448 | CLANG_CXX_LIBRARY = "libc++"; 449 | CLANG_ENABLE_MODULES = YES; 450 | CLANG_ENABLE_OBJC_ARC = YES; 451 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 452 | CLANG_WARN_BOOL_CONVERSION = YES; 453 | CLANG_WARN_COMMA = YES; 454 | CLANG_WARN_CONSTANT_CONVERSION = YES; 455 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 456 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 457 | CLANG_WARN_EMPTY_BODY = YES; 458 | CLANG_WARN_ENUM_CONVERSION = YES; 459 | CLANG_WARN_INFINITE_RECURSION = YES; 460 | CLANG_WARN_INT_CONVERSION = YES; 461 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 462 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 463 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 464 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 465 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 466 | CLANG_WARN_STRICT_PROTOTYPES = YES; 467 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 468 | CLANG_WARN_UNREACHABLE_CODE = YES; 469 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 470 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 471 | COPY_PHASE_STRIP = NO; 472 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 473 | ENABLE_NS_ASSERTIONS = NO; 474 | ENABLE_STRICT_OBJC_MSGSEND = YES; 475 | GCC_C_LANGUAGE_STANDARD = gnu99; 476 | GCC_NO_COMMON_BLOCKS = YES; 477 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 478 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 479 | GCC_WARN_UNDECLARED_SELECTOR = YES; 480 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 481 | GCC_WARN_UNUSED_FUNCTION = YES; 482 | GCC_WARN_UNUSED_VARIABLE = YES; 483 | IPHONEOS_DEPLOYMENT_TARGET = 8.0; 484 | MTL_ENABLE_DEBUG_INFO = NO; 485 | SDKROOT = iphoneos; 486 | SUPPORTED_PLATFORMS = iphoneos; 487 | SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; 488 | TARGETED_DEVICE_FAMILY = "1,2"; 489 | VALIDATE_PRODUCT = YES; 490 | }; 491 | name = Release; 492 | }; 493 | 97C147061CF9000F007C117D /* Debug */ = { 494 | isa = XCBuildConfiguration; 495 | baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */; 496 | buildSettings = { 497 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 498 | CLANG_ENABLE_MODULES = YES; 499 | CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; 500 | ENABLE_BITCODE = NO; 501 | FRAMEWORK_SEARCH_PATHS = ( 502 | "$(inherited)", 503 | "$(PROJECT_DIR)/Flutter", 504 | ); 505 | INFOPLIST_FILE = Runner/Info.plist; 506 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 507 | LIBRARY_SEARCH_PATHS = ( 508 | "$(inherited)", 509 | "$(PROJECT_DIR)/Flutter", 510 | ); 511 | PRODUCT_BUNDLE_IDENTIFIER = com.example.molib; 512 | PRODUCT_NAME = "$(TARGET_NAME)"; 513 | SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; 514 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 515 | SWIFT_VERSION = 5.0; 516 | VERSIONING_SYSTEM = "apple-generic"; 517 | }; 518 | name = Debug; 519 | }; 520 | 97C147071CF9000F007C117D /* Release */ = { 521 | isa = XCBuildConfiguration; 522 | baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; 523 | buildSettings = { 524 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 525 | CLANG_ENABLE_MODULES = YES; 526 | CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; 527 | ENABLE_BITCODE = NO; 528 | FRAMEWORK_SEARCH_PATHS = ( 529 | "$(inherited)", 530 | "$(PROJECT_DIR)/Flutter", 531 | ); 532 | INFOPLIST_FILE = Runner/Info.plist; 533 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 534 | LIBRARY_SEARCH_PATHS = ( 535 | "$(inherited)", 536 | "$(PROJECT_DIR)/Flutter", 537 | ); 538 | PRODUCT_BUNDLE_IDENTIFIER = com.example.molib; 539 | PRODUCT_NAME = "$(TARGET_NAME)"; 540 | SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; 541 | SWIFT_VERSION = 5.0; 542 | VERSIONING_SYSTEM = "apple-generic"; 543 | }; 544 | name = Release; 545 | }; 546 | /* End XCBuildConfiguration section */ 547 | 548 | /* Begin XCConfigurationList section */ 549 | 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */ = { 550 | isa = XCConfigurationList; 551 | buildConfigurations = ( 552 | 97C147031CF9000F007C117D /* Debug */, 553 | 97C147041CF9000F007C117D /* Release */, 554 | 249021D3217E4FDB00AE95B9 /* Profile */, 555 | ); 556 | defaultConfigurationIsVisible = 0; 557 | defaultConfigurationName = Release; 558 | }; 559 | 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */ = { 560 | isa = XCConfigurationList; 561 | buildConfigurations = ( 562 | 97C147061CF9000F007C117D /* Debug */, 563 | 97C147071CF9000F007C117D /* Release */, 564 | 249021D4217E4FDB00AE95B9 /* Profile */, 565 | ); 566 | defaultConfigurationIsVisible = 0; 567 | defaultConfigurationName = Release; 568 | }; 569 | /* End XCConfigurationList section */ 570 | }; 571 | rootObject = 97C146E61CF9000F007C117D /* Project object */; 572 | } 573 | --------------------------------------------------------------------------------