From 396f2586e6000d94b3b48fd66f7ec9447c220587 Mon Sep 17 00:00:00 2001
From: Pedro Alvarez <14023675+pnalvarez@users.noreply.github.com>
Date: Wed, 10 Sep 2025 21:13:00 -0300
Subject: [PATCH 01/17] [feat](pnalvarez): implemented ReadingDataSource
---
ios/Flutter/AppFrameworkInfo.plist | 2 +-
ios/Podfile | 2 +-
ios/Podfile.lock | 4 +-
ios/Runner.xcodeproj/project.pbxproj | 6 +-
.../designsystem/organisms/app_nav_bar.dart | 17 ++++--
.../designsystem/organisms/list_item.dart | 3 +
lib/core/di/di.config.dart | 55 ++++++++++++++++++
.../data/datasource/reading_data_source.dart | 58 +++++++++++++++++++
lib/layers/data/models/reading_data.dart | 18 ++++++
lib/layers/data/models/reading_data.g.dart | 15 +++++
.../data/repository/reading_repository.dart | 0
.../search_repository.dart | 0
.../domain/repository/reading_repository.dart | 0
lib/layers/domain/usecases/start_reading.dart | 17 ++++++
.../bookdetails/bool_details_page.dart | 42 +++++++++-----
pubspec.lock | 28 ++++-----
16 files changed, 225 insertions(+), 42 deletions(-)
create mode 100644 lib/core/di/di.config.dart
create mode 100644 lib/layers/data/datasource/reading_data_source.dart
create mode 100644 lib/layers/data/models/reading_data.dart
create mode 100644 lib/layers/data/models/reading_data.g.dart
create mode 100644 lib/layers/data/repository/reading_repository.dart
rename lib/layers/data/{datasource => repository}/search_repository.dart (100%)
create mode 100644 lib/layers/domain/repository/reading_repository.dart
create mode 100644 lib/layers/domain/usecases/start_reading.dart
diff --git a/ios/Flutter/AppFrameworkInfo.plist b/ios/Flutter/AppFrameworkInfo.plist
index 7c56964..1dc6cf7 100644
--- a/ios/Flutter/AppFrameworkInfo.plist
+++ b/ios/Flutter/AppFrameworkInfo.plist
@@ -21,6 +21,6 @@
CFBundleVersion
1.0
MinimumOSVersion
- 12.0
+ 13.0
diff --git a/ios/Podfile b/ios/Podfile
index e549ee2..620e46e 100644
--- a/ios/Podfile
+++ b/ios/Podfile
@@ -1,5 +1,5 @@
# Uncomment this line to define a global platform for your project
-# platform :ios, '12.0'
+# platform :ios, '13.0'
# CocoaPods analytics sends network stats synchronously affecting flutter build latency.
ENV['COCOAPODS_DISABLE_STATS'] = 'true'
diff --git a/ios/Podfile.lock b/ios/Podfile.lock
index 5d5df2f..65a07d2 100644
--- a/ios/Podfile.lock
+++ b/ios/Podfile.lock
@@ -27,11 +27,11 @@ EXTERNAL SOURCES:
:path: ".symlinks/plugins/sqflite_darwin/darwin"
SPEC CHECKSUMS:
- Flutter: e0871f40cf51350855a761d2e70bf5af5b9b5de7
+ Flutter: cabc95a1d2626b1b06e7179b784ebcf0c0cde467
path_provider_foundation: 2b6b4c569c0fb62ec74538f866245ac84301af46
shared_preferences_foundation: fcdcbc04712aee1108ac7fda236f363274528f78
sqflite_darwin: 5a7236e3b501866c1c9befc6771dfd73ffb8702d
-PODFILE CHECKSUM: 4305caec6b40dde0ae97be1573c53de1882a07e5
+PODFILE CHECKSUM: 3c63482e143d1b91d2d2560aee9fb04ecc74ac7e
COCOAPODS: 1.15.2
diff --git a/ios/Runner.xcodeproj/project.pbxproj b/ios/Runner.xcodeproj/project.pbxproj
index 40e99ab..8f07c66 100644
--- a/ios/Runner.xcodeproj/project.pbxproj
+++ b/ios/Runner.xcodeproj/project.pbxproj
@@ -455,7 +455,7 @@
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
- IPHONEOS_DEPLOYMENT_TARGET = 12.0;
+ IPHONEOS_DEPLOYMENT_TARGET = 13.0;
MTL_ENABLE_DEBUG_INFO = NO;
SDKROOT = iphoneos;
SUPPORTED_PLATFORMS = iphoneos;
@@ -585,7 +585,7 @@
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
- IPHONEOS_DEPLOYMENT_TARGET = 12.0;
+ IPHONEOS_DEPLOYMENT_TARGET = 13.0;
MTL_ENABLE_DEBUG_INFO = YES;
ONLY_ACTIVE_ARCH = YES;
SDKROOT = iphoneos;
@@ -636,7 +636,7 @@
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
- IPHONEOS_DEPLOYMENT_TARGET = 12.0;
+ IPHONEOS_DEPLOYMENT_TARGET = 13.0;
MTL_ENABLE_DEBUG_INFO = NO;
SDKROOT = iphoneos;
SUPPORTED_PLATFORMS = iphoneos;
diff --git a/lib/core/designsystem/organisms/app_nav_bar.dart b/lib/core/designsystem/organisms/app_nav_bar.dart
index 4ca346e..0cf4e27 100644
--- a/lib/core/designsystem/organisms/app_nav_bar.dart
+++ b/lib/core/designsystem/organisms/app_nav_bar.dart
@@ -65,12 +65,17 @@ class AppNavBar extends StatelessWidget implements PreferredSizeWidget {
Widget _title() {
return DisplayAsLoader(
isLoading: isTitleLoading,
- child: Text(
- titleText,
- style: TextStyle(
- color: Colors.black,
- fontSize: titleText.length > 20 ? 14 : 22,
- fontWeight: FontWeight.bold,
+ child: ConstrainedBox(
+ constraints: const BoxConstraints(maxWidth: 250),
+ child: Text(
+ titleText,
+ maxLines: 1,
+ overflow: TextOverflow.ellipsis,
+ style: TextStyle(
+ color: Colors.black,
+ fontSize: titleText.length > 20 ? 14 : 22,
+ fontWeight: FontWeight.bold,
+ ),
),
),
);
diff --git a/lib/core/designsystem/organisms/list_item.dart b/lib/core/designsystem/organisms/list_item.dart
index d524803..82a0e6d 100644
--- a/lib/core/designsystem/organisms/list_item.dart
+++ b/lib/core/designsystem/organisms/list_item.dart
@@ -73,11 +73,13 @@ class GenericInput extends ListItemInput {
class ListItem extends StatelessWidget {
final ListItemInput input;
+ final bool isExpanded;
final Function()? onTap;
const ListItem({
super.key,
required this.input,
+ this.isExpanded = false,
this.onTap,
});
@@ -86,6 +88,7 @@ class ListItem extends StatelessWidget {
return InkWell(
onTap: onTap,
child: Container(
+ width: isExpanded ? double.infinity : null,
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(16),
color: onBackground,
diff --git a/lib/core/di/di.config.dart b/lib/core/di/di.config.dart
new file mode 100644
index 0000000..e98717b
--- /dev/null
+++ b/lib/core/di/di.config.dart
@@ -0,0 +1,55 @@
+// dart format width=80
+// GENERATED CODE - DO NOT MODIFY BY HAND
+
+// **************************************************************************
+// InjectableConfigGenerator
+// **************************************************************************
+
+// ignore_for_file: type=lint
+// coverage:ignore-file
+
+// ignore_for_file: no_leading_underscores_for_library_prefixes
+import 'package:get_it/get_it.dart' as _i174;
+import 'package:injectable/injectable.dart' as _i526;
+import 'package:mibook/layers/data/api/api_client.dart' as _i721;
+import 'package:mibook/layers/data/datasource/search_data_source.dart' as _i400;
+import 'package:mibook/layers/data/repository/search_repository.dart' as _i967;
+import 'package:mibook/layers/domain/repository/search_repository.dart'
+ as _i303;
+import 'package:mibook/layers/domain/usecases/get_book_details.dart' as _i814;
+import 'package:mibook/layers/domain/usecases/search_books.dart' as _i663;
+import 'package:mibook/layers/presentation/screens/bookdetails/book_details_view_model.dart'
+ as _i46;
+import 'package:mibook/layers/presentation/screens/booksearch/book_search_view_model.dart'
+ as _i688;
+
+extension GetItInjectableX on _i174.GetIt {
+ // initializes the registration of main-scope dependencies inside of GetIt
+ _i174.GetIt init({
+ String? environment,
+ _i526.EnvironmentFilter? environmentFilter,
+ }) {
+ final gh = _i526.GetItHelper(this, environment, environmentFilter);
+ gh.factory<_i721.IApiClient>(() => _i721.ApiClient());
+ gh.factory<_i400.ISearchDataSource>(
+ () => _i400.SearchDataSource(gh<_i721.IApiClient>()),
+ );
+ gh.factory<_i303.ISearchRepository>(
+ () => _i967.SearchRepository(gh<_i400.ISearchDataSource>()),
+ );
+ gh.factory<_i663.ISearchBooks>(
+ () => _i663.SearchBooks(gh<_i303.ISearchRepository>()),
+ );
+ gh.factory<_i814.IGetBookDetails>(
+ () => _i814.GetBookDetails(gh<_i303.ISearchRepository>()),
+ );
+ gh.factoryParam<_i46.BookDetailsViewModel, String?, dynamic>(
+ (bookId, _) =>
+ _i46.BookDetailsViewModel(gh<_i814.IGetBookDetails>(), bookId),
+ );
+ gh.factory<_i688.BookSearchViewModel>(
+ () => _i688.BookSearchViewModel(gh<_i663.ISearchBooks>()),
+ );
+ return this;
+ }
+}
diff --git a/lib/layers/data/datasource/reading_data_source.dart b/lib/layers/data/datasource/reading_data_source.dart
new file mode 100644
index 0000000..7179bda
--- /dev/null
+++ b/lib/layers/data/datasource/reading_data_source.dart
@@ -0,0 +1,58 @@
+import 'dart:convert';
+
+import 'package:encrypted_shared_preferences/encrypted_shared_preferences.dart';
+import 'package:mibook/layers/data/models/reading_data.dart';
+
+const _readingListKey = 'reading_list';
+
+abstract class IReadingDataSource {
+ Future startReading({
+ required ReadingData readingData,
+ });
+ Future> getReadingData();
+}
+
+class ReadingDataSource implements IReadingDataSource {
+ final _storage = EncryptedSharedPreferences();
+
+ @override
+ Future startReading({
+ required ReadingData readingData,
+ }) async {
+ final readingList = await _storage.getString(_readingListKey);
+ if (readingList.isEmpty) {
+ final string = jsonEncode([readingData.toJson()]);
+ await _storage.setString(_readingListKey, string);
+ } else {
+ final json = jsonDecode(readingList) as List;
+ final list = json
+ .map((e) => ReadingData.fromJson(e as Map))
+ .toList();
+ final index = list.indexWhere(
+ (element) => element.bookId == readingData.bookId,
+ );
+ if (index != -1) {
+ list[index] = readingData;
+ } else {
+ list.add(readingData);
+ }
+ final string = jsonEncode(list.map((e) => e.toJson()).toList());
+ await _storage.setString(_readingListKey, string);
+ }
+ final json = readingData.toJson();
+ await _storage.setString(readingData.bookId, json.toString());
+ }
+
+ @override
+ Future> getReadingData() async {
+ final readingList = await _storage.getString(_readingListKey);
+ if (readingList.isEmpty) {
+ return [];
+ }
+ final json = jsonDecode(readingList) as List;
+ final list = json
+ .map((e) => ReadingData.fromJson(e as Map))
+ .toList();
+ return list;
+ }
+}
diff --git a/lib/layers/data/models/reading_data.dart b/lib/layers/data/models/reading_data.dart
new file mode 100644
index 0000000..e67e22f
--- /dev/null
+++ b/lib/layers/data/models/reading_data.dart
@@ -0,0 +1,18 @@
+import 'package:freezed_annotation/freezed_annotation.dart';
+part 'reading_data.g.dart';
+
+@JsonSerializable()
+class ReadingData {
+ final String bookId;
+ final double progress;
+
+ ReadingData({
+ required this.bookId,
+ required this.progress,
+ });
+
+ factory ReadingData.fromJson(Map json) =>
+ _$ReadingDataFromJson(json);
+
+ Map toJson() => _$ReadingDataToJson(this);
+}
diff --git a/lib/layers/data/models/reading_data.g.dart b/lib/layers/data/models/reading_data.g.dart
new file mode 100644
index 0000000..149bfa3
--- /dev/null
+++ b/lib/layers/data/models/reading_data.g.dart
@@ -0,0 +1,15 @@
+// GENERATED CODE - DO NOT MODIFY BY HAND
+
+part of 'reading_data.dart';
+
+// **************************************************************************
+// JsonSerializableGenerator
+// **************************************************************************
+
+ReadingData _$ReadingDataFromJson(Map json) => ReadingData(
+ bookId: json['bookId'] as String,
+ progress: (json['progress'] as num).toDouble(),
+);
+
+Map _$ReadingDataToJson(ReadingData instance) =>
+ {'bookId': instance.bookId, 'progress': instance.progress};
diff --git a/lib/layers/data/repository/reading_repository.dart b/lib/layers/data/repository/reading_repository.dart
new file mode 100644
index 0000000..e69de29
diff --git a/lib/layers/data/datasource/search_repository.dart b/lib/layers/data/repository/search_repository.dart
similarity index 100%
rename from lib/layers/data/datasource/search_repository.dart
rename to lib/layers/data/repository/search_repository.dart
diff --git a/lib/layers/domain/repository/reading_repository.dart b/lib/layers/domain/repository/reading_repository.dart
new file mode 100644
index 0000000..e69de29
diff --git a/lib/layers/domain/usecases/start_reading.dart b/lib/layers/domain/usecases/start_reading.dart
new file mode 100644
index 0000000..47d3a6e
--- /dev/null
+++ b/lib/layers/domain/usecases/start_reading.dart
@@ -0,0 +1,17 @@
+abstract class IStartReading {
+ Future call({
+ required String bookId,
+ required double progress,
+ });
+}
+
+class StartReading implements IStartReading {
+ @override
+ Future call({
+ required String bookId,
+ required double progress,
+ }) {
+ // TODO: implement call
+ throw UnimplementedError();
+ }
+}
diff --git a/lib/layers/presentation/screens/bookdetails/bool_details_page.dart b/lib/layers/presentation/screens/bookdetails/bool_details_page.dart
index 9ac907b..f127664 100644
--- a/lib/layers/presentation/screens/bookdetails/bool_details_page.dart
+++ b/lib/layers/presentation/screens/bookdetails/bool_details_page.dart
@@ -1,8 +1,10 @@
import 'package:auto_route/auto_route.dart';
+import 'package:cached_network_image/cached_network_image.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flutter_html/flutter_html.dart';
import 'package:mibook/core/designsystem/organisms/app_nav_bar.dart';
+import 'package:mibook/core/designsystem/organisms/list_item.dart';
import 'package:mibook/core/di/di.dart';
import 'package:mibook/layers/presentation/screens/bookdetails/book_details_state.dart';
import 'package:mibook/layers/presentation/screens/bookdetails/book_details_view_model.dart';
@@ -69,26 +71,36 @@ class _BookDetailsContent extends StatelessWidget {
return Padding(
padding: const EdgeInsets.all(16.0),
child: Column(
- crossAxisAlignment: CrossAxisAlignment.start,
+ crossAxisAlignment: CrossAxisAlignment.center,
children: [
if (book.thumbnail != null)
- Image.network(book.thumbnail!),
+ CachedNetworkImage(imageUrl: book.thumbnail!),
const SizedBox(height: 16),
- Text(
- book.title,
- style: TextStyle(
- fontSize: 24,
- fontWeight: FontWeight.bold,
+ ListItem(
+ isExpanded: true,
+ input: GenericInput(
+ child: Column(
+ crossAxisAlignment: CrossAxisAlignment.start,
+ children: [
+ Text(
+ book.title,
+ style: TextStyle(
+ fontSize: 24,
+ fontWeight: FontWeight.bold,
+ ),
+ ),
+ const SizedBox(height: 8),
+ if (book.authors.isNotEmpty)
+ Text(
+ 'By ${book.authors}',
+ style: Theme.of(context).textTheme.bodyMedium,
+ ),
+ const SizedBox(height: 16),
+ Html(data: book.description),
+ ],
+ ),
),
),
- const SizedBox(height: 8),
- if (book.authors.isNotEmpty)
- Text(
- 'By ${book.authors}',
- style: Theme.of(context).textTheme.bodyMedium,
- ),
- const SizedBox(height: 16),
- Html(data: book.description),
],
),
);
diff --git a/pubspec.lock b/pubspec.lock
index eaa4bf2..3058123 100644
--- a/pubspec.lock
+++ b/pubspec.lock
@@ -500,26 +500,26 @@ packages:
dependency: transitive
description:
name: leak_tracker
- sha256: "6bb818ecbdffe216e81182c2f0714a2e62b593f4a4f13098713ff1685dfb6ab0"
+ sha256: "8dcda04c3fc16c14f48a7bb586d4be1f0d1572731b6d81d51772ef47c02081e0"
url: "https://pub.dev"
source: hosted
- version: "10.0.9"
+ version: "11.0.1"
leak_tracker_flutter_testing:
dependency: transitive
description:
name: leak_tracker_flutter_testing
- sha256: f8b613e7e6a13ec79cfdc0e97638fddb3ab848452eff057653abd3edba760573
+ sha256: "1dbc140bb5a23c75ea9c4811222756104fbcd1a27173f0c34ca01e16bea473c1"
url: "https://pub.dev"
source: hosted
- version: "3.0.9"
+ version: "3.0.10"
leak_tracker_testing:
dependency: transitive
description:
name: leak_tracker_testing
- sha256: "6ba465d5d76e67ddf503e1161d1f4a6bc42306f9d66ca1e8f079a47290fb06d3"
+ sha256: "8d5a2d49f4a66b49744b23b018848400d23e54caf9463f4eb20df3eb8acb2eb1"
url: "https://pub.dev"
source: hosted
- version: "3.0.1"
+ version: "3.0.2"
lints:
dependency: transitive
description:
@@ -993,26 +993,26 @@ packages:
dependency: "direct dev"
description:
name: test
- sha256: "301b213cd241ca982e9ba50266bd3f5bd1ea33f1455554c5abb85d1be0e2d87e"
+ sha256: "65e29d831719be0591f7b3b1a32a3cda258ec98c58c7b25f7b84241bc31215bb"
url: "https://pub.dev"
source: hosted
- version: "1.25.15"
+ version: "1.26.2"
test_api:
dependency: transitive
description:
name: test_api
- sha256: fb31f383e2ee25fbbfe06b40fe21e1e458d14080e3c67e7ba0acfde4df4e0bbd
+ sha256: "522f00f556e73044315fa4585ec3270f1808a4b186c936e612cab0b565ff1e00"
url: "https://pub.dev"
source: hosted
- version: "0.7.4"
+ version: "0.7.6"
test_core:
dependency: transitive
description:
name: test_core
- sha256: "84d17c3486c8dfdbe5e12a50c8ae176d15e2a771b96909a9442b40173649ccaa"
+ sha256: "80bf5a02b60af04b09e14f6fe68b921aad119493e26e490deaca5993fef1b05a"
url: "https://pub.dev"
source: hosted
- version: "0.6.8"
+ version: "0.6.11"
timing:
dependency: transitive
description:
@@ -1041,10 +1041,10 @@ packages:
dependency: transitive
description:
name: vector_math
- sha256: "80b3257d1492ce4d091729e3a67a60407d227c27241d6927be0130c98e741803"
+ sha256: d530bd74fea330e6e364cda7a85019c434070188383e1cd8d9777ee586914c5b
url: "https://pub.dev"
source: hosted
- version: "2.1.4"
+ version: "2.2.0"
vm_service:
dependency: transitive
description:
From 54d2426a1d340906ee104f9441e26acf19a071a5 Mon Sep 17 00:00:00 2001
From: Pedro Alvarez <14023675+pnalvarez@users.noreply.github.com>
Date: Wed, 10 Sep 2025 21:14:44 -0300
Subject: [PATCH 02/17] [feat](pnalvarez): implemented ReadingRepository
---
.../data/repository/reading_repository.dart | 21 +++++++++++++++++++
1 file changed, 21 insertions(+)
diff --git a/lib/layers/data/repository/reading_repository.dart b/lib/layers/data/repository/reading_repository.dart
index e69de29..19ceebf 100644
--- a/lib/layers/data/repository/reading_repository.dart
+++ b/lib/layers/data/repository/reading_repository.dart
@@ -0,0 +1,21 @@
+import 'package:mibook/layers/data/datasource/reading_data_source.dart';
+import 'package:mibook/layers/data/models/reading_data.dart';
+import 'package:mibook/layers/domain/repository/reading_repository.dart';
+
+class ReadingRepository implements IReadingRepository {
+ final IReadingDataSource _dataSource;
+
+ ReadingRepository(this._dataSource);
+
+ @override
+ Future startReading({
+ required String bookId,
+ required double progress,
+ }) async {
+ final readingData = ReadingData(
+ bookId: bookId,
+ progress: progress,
+ );
+ await _dataSource.startReading(readingData: readingData);
+ }
+}
From 5403a15a4aa4c030f3fbd3cb2fa4363a8f0eeef3 Mon Sep 17 00:00:00 2001
From: Pedro Alvarez <14023675+pnalvarez@users.noreply.github.com>
Date: Thu, 11 Sep 2025 09:32:16 -0300
Subject: [PATCH 03/17] [feat](pnalvarez): displaying start reading dialog
---
lib/core/di/di.config.dart | 26 +-
lib/core/utils/strings.dart | 1 +
.../data/datasource/reading_data_source.dart | 2 +
lib/layers/data/models/book_list_data.dart | 5 +-
lib/layers/data/models/book_list_data.g.dart | 265 ++++++++++++++++++
lib/layers/data/models/reading_data.dart | 11 +
.../data/repository/reading_repository.dart | 20 +-
.../domain/models/book_list_domain.dart | 2 +
lib/layers/domain/models/reading_domain.dart | 9 +
.../domain/repository/reading_repository.dart | 8 +
lib/layers/domain/usecases/start_reading.dart | 20 +-
.../bookdetails/book_details_event.dart | 5 +-
.../bookdetails/book_details_view_model.dart | 19 +-
.../bookdetails/bool_details_page.dart | 82 ++++++
.../screens/booksearch/book_ui.dart | 3 +
15 files changed, 457 insertions(+), 21 deletions(-)
create mode 100644 lib/layers/data/models/book_list_data.g.dart
create mode 100644 lib/layers/domain/models/reading_domain.dart
diff --git a/lib/core/di/di.config.dart b/lib/core/di/di.config.dart
index e98717b..1959414 100644
--- a/lib/core/di/di.config.dart
+++ b/lib/core/di/di.config.dart
@@ -12,12 +12,21 @@
import 'package:get_it/get_it.dart' as _i174;
import 'package:injectable/injectable.dart' as _i526;
import 'package:mibook/layers/data/api/api_client.dart' as _i721;
+import 'package:mibook/layers/data/datasource/reading_data_source.dart' as _i44;
import 'package:mibook/layers/data/datasource/search_data_source.dart' as _i400;
+<<<<<<< HEAD
import 'package:mibook/layers/data/repository/search_repository.dart' as _i967;
+=======
+import 'package:mibook/layers/data/repository/reading_repository.dart' as _i600;
+import 'package:mibook/layers/data/repository/search_repository.dart' as _i967;
+import 'package:mibook/layers/domain/repository/reading_repository.dart'
+ as _i649;
+>>>>>>> 7741511 ([feat](pnalvarez): displaying start reading dialog)
import 'package:mibook/layers/domain/repository/search_repository.dart'
as _i303;
import 'package:mibook/layers/domain/usecases/get_book_details.dart' as _i814;
import 'package:mibook/layers/domain/usecases/search_books.dart' as _i663;
+import 'package:mibook/layers/domain/usecases/start_reading.dart' as _i369;
import 'package:mibook/layers/presentation/screens/bookdetails/book_details_view_model.dart'
as _i46;
import 'package:mibook/layers/presentation/screens/booksearch/book_search_view_model.dart'
@@ -31,11 +40,21 @@ extension GetItInjectableX on _i174.GetIt {
}) {
final gh = _i526.GetItHelper(this, environment, environmentFilter);
gh.factory<_i721.IApiClient>(() => _i721.ApiClient());
+ gh.factory<_i44.IReadingDataSource>(() => _i44.ReadingDataSource());
gh.factory<_i400.ISearchDataSource>(
() => _i400.SearchDataSource(gh<_i721.IApiClient>()),
);
+ gh.factory<_i649.IReadingRepository>(
+ () => _i600.ReadingRepository(gh<_i44.IReadingDataSource>()),
+ );
gh.factory<_i303.ISearchRepository>(
() => _i967.SearchRepository(gh<_i400.ISearchDataSource>()),
+<<<<<<< HEAD
+=======
+ );
+ gh.factory<_i369.IStartReading>(
+ () => _i369.StartReading(gh<_i649.IReadingRepository>()),
+>>>>>>> 7741511 ([feat](pnalvarez): displaying start reading dialog)
);
gh.factory<_i663.ISearchBooks>(
() => _i663.SearchBooks(gh<_i303.ISearchRepository>()),
@@ -44,8 +63,11 @@ extension GetItInjectableX on _i174.GetIt {
() => _i814.GetBookDetails(gh<_i303.ISearchRepository>()),
);
gh.factoryParam<_i46.BookDetailsViewModel, String?, dynamic>(
- (bookId, _) =>
- _i46.BookDetailsViewModel(gh<_i814.IGetBookDetails>(), bookId),
+ (bookId, _) => _i46.BookDetailsViewModel(
+ gh<_i814.IGetBookDetails>(),
+ gh<_i369.IStartReading>(),
+ bookId,
+ ),
);
gh.factory<_i688.BookSearchViewModel>(
() => _i688.BookSearchViewModel(gh<_i663.ISearchBooks>()),
diff --git a/lib/core/utils/strings.dart b/lib/core/utils/strings.dart
index 0669853..eafefd8 100644
--- a/lib/core/utils/strings.dart
+++ b/lib/core/utils/strings.dart
@@ -2,3 +2,4 @@ const home = 'Home';
const search = 'Search';
const objectives = 'Objectives';
const searchBooks = 'Search Books';
+const startReading = 'Start Reading';
diff --git a/lib/layers/data/datasource/reading_data_source.dart b/lib/layers/data/datasource/reading_data_source.dart
index 7179bda..a9d9228 100644
--- a/lib/layers/data/datasource/reading_data_source.dart
+++ b/lib/layers/data/datasource/reading_data_source.dart
@@ -1,6 +1,7 @@
import 'dart:convert';
import 'package:encrypted_shared_preferences/encrypted_shared_preferences.dart';
+import 'package:injectable/injectable.dart';
import 'package:mibook/layers/data/models/reading_data.dart';
const _readingListKey = 'reading_list';
@@ -12,6 +13,7 @@ abstract class IReadingDataSource {
Future> getReadingData();
}
+@Injectable(as: IReadingDataSource)
class ReadingDataSource implements IReadingDataSource {
final _storage = EncryptedSharedPreferences();
diff --git a/lib/layers/data/models/book_list_data.dart b/lib/layers/data/models/book_list_data.dart
index e89e5e5..8b49b68 100644
--- a/lib/layers/data/models/book_list_data.dart
+++ b/lib/layers/data/models/book_list_data.dart
@@ -64,6 +64,7 @@ class BookItem {
authors: volumeInfo.authors,
description: volumeInfo.description,
thumbnail: volumeInfo.imageLinks?.thumbnail,
+ pageCount: volumeInfo.pageCount,
);
}
}
@@ -78,7 +79,7 @@ class VolumeInfo {
final String? description;
final List? industryIdentifiers;
final ReadingModes? readingModes;
- final int? pageCount;
+ final int pageCount;
final String? printType;
final List? categories;
final double? averageRating;
@@ -102,7 +103,7 @@ class VolumeInfo {
this.description,
this.industryIdentifiers,
this.readingModes,
- this.pageCount,
+ required this.pageCount,
this.printType,
this.categories,
this.averageRating,
diff --git a/lib/layers/data/models/book_list_data.g.dart b/lib/layers/data/models/book_list_data.g.dart
new file mode 100644
index 0000000..cd2940d
--- /dev/null
+++ b/lib/layers/data/models/book_list_data.g.dart
@@ -0,0 +1,265 @@
+// GENERATED CODE - DO NOT MODIFY BY HAND
+
+part of 'book_list_data.dart';
+
+// **************************************************************************
+// JsonSerializableGenerator
+// **************************************************************************
+
+BookListData _$BookListDataFromJson(Map json) => BookListData(
+ kind: json['kind'] as String,
+ totalItems: (json['totalItems'] as num).toInt(),
+ items:
+ (json['items'] as List?)
+ ?.map((e) => BookItem.fromJson(e as Map))
+ .toList() ??
+ [],
+);
+
+Map _$BookListDataToJson(BookListData instance) =>
+ {
+ 'kind': instance.kind,
+ 'totalItems': instance.totalItems,
+ 'items': instance.items,
+ };
+
+BookItem _$BookItemFromJson(Map json) => BookItem(
+ kind: json['kind'] as String,
+ id: json['id'] as String,
+ etag: json['etag'] as String,
+ selfLink: json['selfLink'] as String,
+ volumeInfo: VolumeInfo.fromJson(json['volumeInfo'] as Map),
+ saleInfo: SaleInfo.fromJson(json['saleInfo'] as Map),
+ accessInfo: AccessInfo.fromJson(json['accessInfo'] as Map),
+ searchInfo: json['searchInfo'] == null
+ ? null
+ : SearchInfo.fromJson(json['searchInfo'] as Map),
+);
+
+Map _$BookItemToJson(BookItem instance) => {
+ 'kind': instance.kind,
+ 'id': instance.id,
+ 'etag': instance.etag,
+ 'selfLink': instance.selfLink,
+ 'volumeInfo': instance.volumeInfo,
+ 'saleInfo': instance.saleInfo,
+ 'accessInfo': instance.accessInfo,
+ 'searchInfo': instance.searchInfo,
+};
+
+VolumeInfo _$VolumeInfoFromJson(Map json) => VolumeInfo(
+ title: json['title'] as String,
+ authors: (json['authors'] as List?)
+ ?.map((e) => e as String)
+ .toList(),
+ subtitle: json['subtitle'] as String?,
+ publisher: json['publisher'] as String?,
+ publishedDate: json['publishedDate'] as String?,
+ description: json['description'] as String?,
+ industryIdentifiers: (json['industryIdentifiers'] as List?)
+ ?.map((e) => IndustryIdentifier.fromJson(e as Map))
+ .toList(),
+ readingModes: json['readingModes'] == null
+ ? null
+ : ReadingModes.fromJson(json['readingModes'] as Map),
+ pageCount: (json['pageCount'] as num).toInt(),
+ printType: json['printType'] as String?,
+ categories: (json['categories'] as List?)
+ ?.map((e) => e as String)
+ .toList(),
+ averageRating: (json['averageRating'] as num?)?.toDouble(),
+ ratingsCount: (json['ratingsCount'] as num?)?.toInt(),
+ maturityRating: json['maturityRating'] as String?,
+ allowAnonLogging: json['allowAnonLogging'] as bool?,
+ contentVersion: json['contentVersion'] as String?,
+ panelizationSummary: json['panelizationSummary'] == null
+ ? null
+ : PanelizationSummary.fromJson(
+ json['panelizationSummary'] as Map,
+ ),
+ imageLinks: json['imageLinks'] == null
+ ? null
+ : ImageLinks.fromJson(json['imageLinks'] as Map),
+ language: json['language'] as String?,
+ previewLink: json['previewLink'] as String?,
+ infoLink: json['infoLink'] as String?,
+ canonicalVolumeLink: json['canonicalVolumeLink'] as String?,
+);
+
+Map _$VolumeInfoToJson(VolumeInfo instance) =>
+ {
+ 'title': instance.title,
+ 'authors': instance.authors,
+ 'subtitle': instance.subtitle,
+ 'publisher': instance.publisher,
+ 'publishedDate': instance.publishedDate,
+ 'description': instance.description,
+ 'industryIdentifiers': instance.industryIdentifiers,
+ 'readingModes': instance.readingModes,
+ 'pageCount': instance.pageCount,
+ 'printType': instance.printType,
+ 'categories': instance.categories,
+ 'averageRating': instance.averageRating,
+ 'ratingsCount': instance.ratingsCount,
+ 'maturityRating': instance.maturityRating,
+ 'allowAnonLogging': instance.allowAnonLogging,
+ 'contentVersion': instance.contentVersion,
+ 'panelizationSummary': instance.panelizationSummary,
+ 'imageLinks': instance.imageLinks,
+ 'language': instance.language,
+ 'previewLink': instance.previewLink,
+ 'infoLink': instance.infoLink,
+ 'canonicalVolumeLink': instance.canonicalVolumeLink,
+ };
+
+IndustryIdentifier _$IndustryIdentifierFromJson(Map json) =>
+ IndustryIdentifier(
+ type: json['type'] as String,
+ identifier: json['identifier'] as String,
+ );
+
+Map _$IndustryIdentifierToJson(IndustryIdentifier instance) =>
+ {'type': instance.type, 'identifier': instance.identifier};
+
+ReadingModes _$ReadingModesFromJson(Map json) =>
+ ReadingModes(text: json['text'] as bool, image: json['image'] as bool);
+
+Map _$ReadingModesToJson(ReadingModes instance) =>
+ {'text': instance.text, 'image': instance.image};
+
+PanelizationSummary _$PanelizationSummaryFromJson(Map json) =>
+ PanelizationSummary(
+ containsEpubBubbles: json['containsEpubBubbles'] as bool,
+ containsImageBubbles: json['containsImageBubbles'] as bool,
+ );
+
+Map _$PanelizationSummaryToJson(
+ PanelizationSummary instance,
+) => {
+ 'containsEpubBubbles': instance.containsEpubBubbles,
+ 'containsImageBubbles': instance.containsImageBubbles,
+};
+
+ImageLinks _$ImageLinksFromJson(Map json) => ImageLinks(
+ smallThumbnail: json['smallThumbnail'] as String?,
+ thumbnail: json['thumbnail'] as String?,
+);
+
+Map _$ImageLinksToJson(ImageLinks instance) =>
+ {
+ 'smallThumbnail': instance.smallThumbnail,
+ 'thumbnail': instance.thumbnail,
+ };
+
+SaleInfo _$SaleInfoFromJson(Map json) => SaleInfo(
+ country: json['country'] as String,
+ saleability: json['saleability'] as String,
+ isEbook: json['isEbook'] as bool,
+ listPrice: json['listPrice'] == null
+ ? null
+ : Price.fromJson(json['listPrice'] as Map),
+ retailPrice: json['retailPrice'] == null
+ ? null
+ : Price.fromJson(json['retailPrice'] as Map),
+ buyLink: json['buyLink'] as String?,
+ offers: (json['offers'] as List?)
+ ?.map((e) => Offer.fromJson(e as Map))
+ .toList(),
+);
+
+Map _$SaleInfoToJson(SaleInfo instance) => {
+ 'country': instance.country,
+ 'saleability': instance.saleability,
+ 'isEbook': instance.isEbook,
+ 'listPrice': instance.listPrice,
+ 'retailPrice': instance.retailPrice,
+ 'buyLink': instance.buyLink,
+ 'offers': instance.offers,
+};
+
+Price _$PriceFromJson(Map json) => Price(
+ amount: (json['amount'] as num?)?.toDouble(),
+ currencyCode: json['currencyCode'] as String?,
+ amountInMicros: (json['amountInMicros'] as num?)?.toInt(),
+);
+
+Map _$PriceToJson(Price instance) => {
+ 'amount': instance.amount,
+ 'currencyCode': instance.currencyCode,
+ 'amountInMicros': instance.amountInMicros,
+};
+
+Offer _$OfferFromJson(Map json) => Offer(
+ finskyOfferType: (json['finskyOfferType'] as num).toInt(),
+ listPrice: json['listPrice'] == null
+ ? null
+ : Price.fromJson(json['listPrice'] as Map),
+ retailPrice: json['retailPrice'] == null
+ ? null
+ : Price.fromJson(json['retailPrice'] as Map),
+ giftable: json['giftable'] as bool?,
+);
+
+Map _$OfferToJson(Offer instance) => {
+ 'finskyOfferType': instance.finskyOfferType,
+ 'listPrice': instance.listPrice,
+ 'retailPrice': instance.retailPrice,
+ 'giftable': instance.giftable,
+};
+
+AccessInfo _$AccessInfoFromJson(Map json) => AccessInfo(
+ country: json['country'] as String,
+ viewability: json['viewability'] as String,
+ embeddable: json['embeddable'] as bool,
+ publicDomain: json['publicDomain'] as bool,
+ textToSpeechPermission: json['textToSpeechPermission'] as String,
+ epub: json['epub'] == null
+ ? null
+ : EpubInfo.fromJson(json['epub'] as Map),
+ pdf: json['pdf'] == null
+ ? null
+ : PdfInfo.fromJson(json['pdf'] as Map),
+ webReaderLink: json['webReaderLink'] as String?,
+ accessViewStatus: json['accessViewStatus'] as String?,
+ quoteSharingAllowed: json['quoteSharingAllowed'] as bool?,
+);
+
+Map _$AccessInfoToJson(AccessInfo instance) =>
+ {
+ 'country': instance.country,
+ 'viewability': instance.viewability,
+ 'embeddable': instance.embeddable,
+ 'publicDomain': instance.publicDomain,
+ 'textToSpeechPermission': instance.textToSpeechPermission,
+ 'epub': instance.epub,
+ 'pdf': instance.pdf,
+ 'webReaderLink': instance.webReaderLink,
+ 'accessViewStatus': instance.accessViewStatus,
+ 'quoteSharingAllowed': instance.quoteSharingAllowed,
+ };
+
+EpubInfo _$EpubInfoFromJson(Map json) => EpubInfo(
+ isAvailable: json['isAvailable'] as bool,
+ acsTokenLink: json['acsTokenLink'] as String?,
+);
+
+Map _$EpubInfoToJson(EpubInfo instance) => {
+ 'isAvailable': instance.isAvailable,
+ 'acsTokenLink': instance.acsTokenLink,
+};
+
+PdfInfo _$PdfInfoFromJson(Map json) => PdfInfo(
+ isAvailable: json['isAvailable'] as bool,
+ acsTokenLink: json['acsTokenLink'] as String?,
+);
+
+Map _$PdfInfoToJson(PdfInfo instance) => {
+ 'isAvailable': instance.isAvailable,
+ 'acsTokenLink': instance.acsTokenLink,
+};
+
+SearchInfo _$SearchInfoFromJson(Map json) =>
+ SearchInfo(textSnippet: json['textSnippet'] as String?);
+
+Map _$SearchInfoToJson(SearchInfo instance) =>
+ {'textSnippet': instance.textSnippet};
diff --git a/lib/layers/data/models/reading_data.dart b/lib/layers/data/models/reading_data.dart
index e67e22f..6ad64ce 100644
--- a/lib/layers/data/models/reading_data.dart
+++ b/lib/layers/data/models/reading_data.dart
@@ -1,4 +1,5 @@
import 'package:freezed_annotation/freezed_annotation.dart';
+import 'package:mibook/layers/domain/models/reading_domain.dart';
part 'reading_data.g.dart';
@JsonSerializable()
@@ -15,4 +16,14 @@ class ReadingData {
_$ReadingDataFromJson(json);
Map toJson() => _$ReadingDataToJson(this);
+
+ factory ReadingData.fromDomain(ReadingDomain domain) => ReadingData(
+ bookId: domain.bookId,
+ progress: domain.progress,
+ );
+
+ ReadingDomain toDomain() => ReadingDomain(
+ bookId: bookId,
+ progress: progress,
+ );
}
diff --git a/lib/layers/data/repository/reading_repository.dart b/lib/layers/data/repository/reading_repository.dart
index 19ceebf..a225bbe 100644
--- a/lib/layers/data/repository/reading_repository.dart
+++ b/lib/layers/data/repository/reading_repository.dart
@@ -1,7 +1,10 @@
+import 'package:injectable/injectable.dart';
import 'package:mibook/layers/data/datasource/reading_data_source.dart';
import 'package:mibook/layers/data/models/reading_data.dart';
+import 'package:mibook/layers/domain/models/reading_domain.dart';
import 'package:mibook/layers/domain/repository/reading_repository.dart';
+@Injectable(as: IReadingRepository)
class ReadingRepository implements IReadingRepository {
final IReadingDataSource _dataSource;
@@ -9,13 +12,18 @@ class ReadingRepository implements IReadingRepository {
@override
Future startReading({
- required String bookId,
- required double progress,
+ required ReadingDomain reading,
}) async {
- final readingData = ReadingData(
- bookId: bookId,
- progress: progress,
+ final data = ReadingData(
+ bookId: reading.bookId,
+ progress: reading.progress,
);
- await _dataSource.startReading(readingData: readingData);
+ await _dataSource.startReading(readingData: data);
+ }
+
+ @override
+ Future> getReadings() async {
+ final data = await _dataSource.getReadingData();
+ return data.map((e) => e.toDomain()).toList();
}
}
diff --git a/lib/layers/domain/models/book_list_domain.dart b/lib/layers/domain/models/book_list_domain.dart
index f106814..c130ea3 100644
--- a/lib/layers/domain/models/book_list_domain.dart
+++ b/lib/layers/domain/models/book_list_domain.dart
@@ -15,6 +15,7 @@ class BookDomain {
final List? authors;
final String? description;
final String? thumbnail;
+ final int pageCount;
BookDomain({
required this.id,
@@ -23,5 +24,6 @@ class BookDomain {
required this.authors,
required this.description,
required this.thumbnail,
+ required this.pageCount,
});
}
diff --git a/lib/layers/domain/models/reading_domain.dart b/lib/layers/domain/models/reading_domain.dart
new file mode 100644
index 0000000..7dca4af
--- /dev/null
+++ b/lib/layers/domain/models/reading_domain.dart
@@ -0,0 +1,9 @@
+class ReadingDomain {
+ final String bookId;
+ final double progress;
+
+ ReadingDomain({
+ required this.bookId,
+ required this.progress,
+ });
+}
diff --git a/lib/layers/domain/repository/reading_repository.dart b/lib/layers/domain/repository/reading_repository.dart
index e69de29..899ae66 100644
--- a/lib/layers/domain/repository/reading_repository.dart
+++ b/lib/layers/domain/repository/reading_repository.dart
@@ -0,0 +1,8 @@
+import 'package:mibook/layers/domain/models/reading_domain.dart';
+
+abstract class IReadingRepository {
+ Future startReading({
+ required ReadingDomain reading,
+ });
+ Future> getReadings();
+}
diff --git a/lib/layers/domain/usecases/start_reading.dart b/lib/layers/domain/usecases/start_reading.dart
index 47d3a6e..494bd88 100644
--- a/lib/layers/domain/usecases/start_reading.dart
+++ b/lib/layers/domain/usecases/start_reading.dart
@@ -1,17 +1,23 @@
+import 'package:injectable/injectable.dart';
+import 'package:mibook/layers/domain/models/reading_domain.dart';
+import 'package:mibook/layers/domain/repository/reading_repository.dart';
+
abstract class IStartReading {
Future call({
- required String bookId,
- required double progress,
+ required ReadingDomain reading,
});
}
+@Injectable(as: IStartReading)
class StartReading implements IStartReading {
+ final IReadingRepository _repository;
+
+ StartReading(this._repository);
+
@override
Future call({
- required String bookId,
- required double progress,
- }) {
- // TODO: implement call
- throw UnimplementedError();
+ required ReadingDomain reading,
+ }) async {
+ await _repository.startReading(reading: reading);
}
}
diff --git a/lib/layers/presentation/screens/bookdetails/book_details_event.dart b/lib/layers/presentation/screens/bookdetails/book_details_event.dart
index ff95196..264a989 100644
--- a/lib/layers/presentation/screens/bookdetails/book_details_event.dart
+++ b/lib/layers/presentation/screens/bookdetails/book_details_event.dart
@@ -5,4 +5,7 @@ class DidLoadEvent extends BookDetailsEvent {
DidLoadEvent(this.bookId);
}
-class DidClickStartReadingEvent extends BookDetailsEvent {}
+class DidClickStartReadingEvent extends BookDetailsEvent {
+ final double progress;
+ DidClickStartReadingEvent(this.progress);
+}
diff --git a/lib/layers/presentation/screens/bookdetails/book_details_view_model.dart b/lib/layers/presentation/screens/bookdetails/book_details_view_model.dart
index 1a7f8de..834c0be 100644
--- a/lib/layers/presentation/screens/bookdetails/book_details_view_model.dart
+++ b/lib/layers/presentation/screens/bookdetails/book_details_view_model.dart
@@ -1,6 +1,8 @@
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:injectable/injectable.dart';
+import 'package:mibook/layers/domain/models/reading_domain.dart';
import 'package:mibook/layers/domain/usecases/get_book_details.dart';
+import 'package:mibook/layers/domain/usecases/start_reading.dart';
import 'package:mibook/layers/presentation/screens/bookdetails/book_details_event.dart';
import 'package:mibook/layers/presentation/screens/bookdetails/book_details_state.dart';
import 'package:mibook/layers/presentation/screens/bookdetails/book_details_ui.dart';
@@ -8,10 +10,12 @@ import 'package:mibook/layers/presentation/screens/bookdetails/book_details_ui.d
@injectable
class BookDetailsViewModel extends Bloc {
final IGetBookDetails _getBookDetails;
+ final IStartReading _startReading;
String? bookId;
BookDetailsViewModel(
this._getBookDetails,
+ this._startReading,
@factoryParam this.bookId,
) : super(BookDetailsState.initial()) {
on((event, emit) async {
@@ -38,9 +42,7 @@ class BookDetailsViewModel extends Bloc {
);
}
});
- on((event, emit) async {
- // Handle start reading event
- });
+ on((event, emit) async {});
}
Future loadBookDetails() async {
@@ -51,4 +53,15 @@ class BookDetailsViewModel extends Bloc {
return null;
}
}
+
+ Future startReading({required double progress}) async {
+ if (bookId != null) {
+ await _startReading(
+ reading: ReadingDomain(
+ bookId: bookId!,
+ progress: progress,
+ ),
+ );
+ }
+ }
}
diff --git a/lib/layers/presentation/screens/bookdetails/bool_details_page.dart b/lib/layers/presentation/screens/bookdetails/bool_details_page.dart
index f127664..2446f75 100644
--- a/lib/layers/presentation/screens/bookdetails/bool_details_page.dart
+++ b/lib/layers/presentation/screens/bookdetails/bool_details_page.dart
@@ -3,10 +3,13 @@ import 'package:cached_network_image/cached_network_image.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flutter_html/flutter_html.dart';
+import 'package:mibook/core/designsystem/molecules/buttons/primary_button.dart';
import 'package:mibook/core/designsystem/organisms/app_nav_bar.dart';
import 'package:mibook/core/designsystem/organisms/list_item.dart';
import 'package:mibook/core/di/di.dart';
+import 'package:mibook/core/utils/strings.dart';
import 'package:mibook/layers/presentation/screens/bookdetails/book_details_state.dart';
+import 'package:mibook/layers/presentation/screens/bookdetails/book_details_ui.dart';
import 'package:mibook/layers/presentation/screens/bookdetails/book_details_view_model.dart';
import 'package:mibook/layers/presentation/screens/bookdetails/book_details_event.dart';
@@ -101,6 +104,55 @@ class _BookDetailsContent extends StatelessWidget {
),
),
),
+ const SizedBox(height: 24),
+ PrimaryButton(
+ title: startReading,
+ onPressed: () => showGeneralDialog(
+ context: context,
+ barrierDismissible:
+ true, // allows tap outside to close
+ barrierLabel: "Dismiss",
+ barrierColor: Colors.black54, // dim background
+ transitionDuration: const Duration(milliseconds: 300),
+ pageBuilder: (context, anim1, anim2) {
+ // Must return a full-screen widget so barrier can detect taps
+ return SafeArea(
+ child: Builder(
+ builder: (context) {
+ return Center(
+ child: Material(
+ color: Colors.transparent,
+ child: AlertDialog(
+ content: _StartReadingDialogContent(
+ book: book,
+ onClickStartReading:
+ (double progress) {
+ Navigator.of(context).pop();
+ },
+ ),
+ ),
+ ),
+ );
+ },
+ ),
+ );
+ },
+ transitionBuilder: (context, anim1, anim2, child) {
+ // Slide + fade
+ return SlideTransition(
+ position: Tween(
+ begin: const Offset(0, 0.2), // from bottom
+ end: Offset.zero,
+ ).animate(anim1),
+ child: FadeTransition(
+ opacity: anim1,
+ child: child,
+ ),
+ );
+ },
+ ),
+ ),
+ const SizedBox(height: 32),
],
),
);
@@ -114,3 +166,33 @@ class _BookDetailsContent extends StatelessWidget {
);
}
}
+
+class _StartReadingDialogContent extends StatelessWidget {
+ final BookDetailsUI book;
+ final Function(double) onClickStartReading;
+
+ const _StartReadingDialogContent({
+ required this.book,
+ required this.onClickStartReading,
+ });
+
+ @override
+ Widget build(BuildContext context) {
+ return Column(
+ mainAxisSize: MainAxisSize.min,
+ children: [
+ if (book.thumbnail != null)
+ CachedNetworkImage(imageUrl: book.thumbnail!),
+ const SizedBox(height: 16),
+ Text(
+ book.title,
+ style: TextStyle(
+ fontSize: 24,
+ fontWeight: FontWeight.bold,
+ ),
+ ),
+ const SizedBox(height: 8),
+ ],
+ );
+ }
+}
diff --git a/lib/layers/presentation/screens/booksearch/book_ui.dart b/lib/layers/presentation/screens/booksearch/book_ui.dart
index 346cef2..53d5c12 100644
--- a/lib/layers/presentation/screens/booksearch/book_ui.dart
+++ b/lib/layers/presentation/screens/booksearch/book_ui.dart
@@ -7,6 +7,7 @@ class BookUI {
final String authors;
final String description;
final String? thumbnail;
+ final int pageCount;
BookUI({
required this.id,
@@ -15,6 +16,7 @@ class BookUI {
required this.authors,
required this.description,
required this.thumbnail,
+ required this.pageCount,
});
factory BookUI.fromDomain(BookDomain domain) {
@@ -25,6 +27,7 @@ class BookUI {
authors: (domain.authors ?? []).join(', '),
description: domain.description ?? '',
thumbnail: domain.thumbnail,
+ pageCount: domain.pageCount,
);
}
}
From 811ada6078e59c340ab84cca36000188a4c974c6 Mon Sep 17 00:00:00 2001
From: Pedro Alvarez <14023675+pnalvarez@users.noreply.github.com>
Date: Thu, 11 Sep 2025 12:27:39 -0300
Subject: [PATCH 04/17] [feat](pnalvarez): showing progress input field
---
.../molecules/inputfields/input_field.dart | 9 +++
lib/core/utils/strings.dart | 4 +
.../screens/bookdetails/book_details_ui.dart | 3 +
.../bookdetails/bool_details_page.dart | 80 ++++++++++++++-----
4 files changed, 75 insertions(+), 21 deletions(-)
diff --git a/lib/core/designsystem/molecules/inputfields/input_field.dart b/lib/core/designsystem/molecules/inputfields/input_field.dart
index b112c54..71e1cf3 100644
--- a/lib/core/designsystem/molecules/inputfields/input_field.dart
+++ b/lib/core/designsystem/molecules/inputfields/input_field.dart
@@ -6,6 +6,9 @@ class InputField extends StatelessWidget {
final TextEditingController controller;
final bool isEnabled;
final String placeholder;
+ final String? suffixText;
+ final String? prefixText;
+ final TextInputType? keyboardType;
final Function(String) onChanged;
const InputField({
@@ -14,6 +17,9 @@ class InputField extends StatelessWidget {
required this.controller,
this.isEnabled = true,
this.placeholder = '',
+ this.suffixText,
+ this.prefixText,
+ this.keyboardType,
required this.onChanged,
});
@@ -27,11 +33,14 @@ class InputField extends StatelessWidget {
SizedBox(
height: 48,
child: TextField(
+ keyboardType: keyboardType,
decoration: InputDecoration(
border: OutlineInputBorder(),
focusedBorder: OutlineInputBorder(
borderSide: BorderSide(color: onBorder),
),
+ suffixText: suffixText,
+ prefixText: prefixText,
),
onChanged: onChanged,
),
diff --git a/lib/core/utils/strings.dart b/lib/core/utils/strings.dart
index eafefd8..8166bd2 100644
--- a/lib/core/utils/strings.dart
+++ b/lib/core/utils/strings.dart
@@ -3,3 +3,7 @@ const search = 'Search';
const objectives = 'Objectives';
const searchBooks = 'Search Books';
const startReading = 'Start Reading';
+const percent = '%';
+const page = 'Page';
+const progress = 'Progress';
+const confirm = 'Confirm';
diff --git a/lib/layers/presentation/screens/bookdetails/book_details_ui.dart b/lib/layers/presentation/screens/bookdetails/book_details_ui.dart
index e495b4d..97cf405 100644
--- a/lib/layers/presentation/screens/bookdetails/book_details_ui.dart
+++ b/lib/layers/presentation/screens/bookdetails/book_details_ui.dart
@@ -7,6 +7,7 @@ class BookDetailsUI {
final String authors;
final String description;
final String? thumbnail;
+ final int pageCount;
BookDetailsUI({
required this.id,
@@ -15,6 +16,7 @@ class BookDetailsUI {
required this.authors,
required this.description,
required this.thumbnail,
+ required this.pageCount,
});
factory BookDetailsUI.fromDomain(BookDomain domain) {
@@ -25,6 +27,7 @@ class BookDetailsUI {
authors: (domain.authors ?? []).join(', '),
description: domain.description ?? '',
thumbnail: domain.thumbnail,
+ pageCount: domain.pageCount,
);
}
}
diff --git a/lib/layers/presentation/screens/bookdetails/bool_details_page.dart b/lib/layers/presentation/screens/bookdetails/bool_details_page.dart
index 2446f75..020feb5 100644
--- a/lib/layers/presentation/screens/bookdetails/bool_details_page.dart
+++ b/lib/layers/presentation/screens/bookdetails/bool_details_page.dart
@@ -4,6 +4,7 @@ import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flutter_html/flutter_html.dart';
import 'package:mibook/core/designsystem/molecules/buttons/primary_button.dart';
+import 'package:mibook/core/designsystem/molecules/inputfields/input_field.dart';
import 'package:mibook/core/designsystem/organisms/app_nav_bar.dart';
import 'package:mibook/core/designsystem/organisms/list_item.dart';
import 'package:mibook/core/di/di.dart';
@@ -123,12 +124,24 @@ class _BookDetailsContent extends StatelessWidget {
child: Material(
color: Colors.transparent,
child: AlertDialog(
- content: _StartReadingDialogContent(
- book: book,
- onClickStartReading:
- (double progress) {
- Navigator.of(context).pop();
- },
+ backgroundColor: Colors.white,
+ insetPadding:
+ EdgeInsets.zero, // remove margin
+ contentPadding: EdgeInsets
+ .zero, // remove inner padding if needed
+ content: SizedBox(
+ width:
+ MediaQuery.of(
+ context,
+ ).size.width -
+ 32,
+ child: _StartReadingDialogContent(
+ book: book,
+ onClickStartReading:
+ (double progress) {
+ Navigator.of(context).pop();
+ },
+ ),
),
),
),
@@ -169,30 +182,55 @@ class _BookDetailsContent extends StatelessWidget {
class _StartReadingDialogContent extends StatelessWidget {
final BookDetailsUI book;
+ final TextEditingController _controller = TextEditingController();
final Function(double) onClickStartReading;
- const _StartReadingDialogContent({
+ _StartReadingDialogContent({
required this.book,
required this.onClickStartReading,
});
@override
Widget build(BuildContext context) {
- return Column(
- mainAxisSize: MainAxisSize.min,
- children: [
- if (book.thumbnail != null)
- CachedNetworkImage(imageUrl: book.thumbnail!),
- const SizedBox(height: 16),
- Text(
- book.title,
- style: TextStyle(
- fontSize: 24,
- fontWeight: FontWeight.bold,
+ return Padding(
+ padding: const EdgeInsets.all(16.0),
+ child: Column(
+ mainAxisSize: MainAxisSize.min,
+ children: [
+ Align(
+ alignment: Alignment.topLeft,
+ child: IconButton(
+ icon: const Icon(Icons.close),
+ onPressed: () => Navigator.of(context).pop(),
+ ),
),
- ),
- const SizedBox(height: 8),
- ],
+ if (book.thumbnail != null)
+ CachedNetworkImage(imageUrl: book.thumbnail!),
+ const SizedBox(height: 16),
+ Text(
+ book.title,
+ style: TextStyle(
+ fontSize: 24,
+ fontWeight: FontWeight.bold,
+ ),
+ ),
+ const SizedBox(height: 8),
+ Text('Maximum pages: ${book.pageCount}'),
+ const SizedBox(height: 24),
+ InputField(
+ keyboardType: TextInputType.number,
+ label: progress,
+ controller: _controller,
+ onChanged: (_) {},
+ prefixText: '$page ',
+ ),
+ const SizedBox(height: 24),
+ PrimaryButton(
+ title: confirm,
+ onPressed: () {},
+ ),
+ ],
+ ),
);
}
}
From 9a980dddffe896c0886abf0a7da89b4bb7f66944 Mon Sep 17 00:00:00 2001
From: Pedro Alvarez <14023675+pnalvarez@users.noreply.github.com>
Date: Fri, 12 Sep 2025 23:42:56 -0300
Subject: [PATCH 05/17] [feat](pnalvarez): start reading page
---
lib/core/designsystem/atoms/colors.dart | 2 +
.../molecules/buttons/primary_button.dart | 56 +++--
.../molecules/buttons/secondary_button.dart | 1 +
.../indicators/progress_stepper.dart | 25 +++
.../molecules/inputfields/input_field.dart | 7 +
lib/core/di/di.config.dart | 7 +
lib/core/routes/app_router.dart | 3 +-
lib/core/routes/app_router.gr.dart | 155 +++++++++-----
lib/core/utils/strings.dart | 2 +
lib/layers/data/models/book_list_data.dart | 10 +-
lib/layers/data/models/book_list_data.g.dart | 6 +-
.../bookdetails/book_details_event.dart | 5 +
...tails_page.dart => book_details_page.dart} | 88 +++-----
.../bookdetails/book_details_state.dart | 4 +
.../book_details_state.freezed.dart | 202 ++++++++++++++++++
.../bookdetails/book_details_view_model.dart | 7 +
.../screens/booksearch/book_search_page.dart | 2 +-
.../startreading/start_reading_event.dart | 8 +
.../startreading/start_reading_page.dart | 121 +++++++++++
.../startreading/start_reading_state.dart | 24 +++
.../start_reading_state.freezed.dart | 201 +++++++++++++++++
.../start_reading_view_model.dart | 39 ++++
22 files changed, 823 insertions(+), 152 deletions(-)
create mode 100644 lib/core/designsystem/molecules/buttons/secondary_button.dart
create mode 100644 lib/core/designsystem/molecules/indicators/progress_stepper.dart
rename lib/layers/presentation/screens/bookdetails/{bool_details_page.dart => book_details_page.dart} (64%)
create mode 100644 lib/layers/presentation/screens/bookdetails/book_details_state.freezed.dart
create mode 100644 lib/layers/presentation/screens/startreading/start_reading_event.dart
create mode 100644 lib/layers/presentation/screens/startreading/start_reading_page.dart
create mode 100644 lib/layers/presentation/screens/startreading/start_reading_state.dart
create mode 100644 lib/layers/presentation/screens/startreading/start_reading_state.freezed.dart
create mode 100644 lib/layers/presentation/screens/startreading/start_reading_view_model.dart
diff --git a/lib/core/designsystem/atoms/colors.dart b/lib/core/designsystem/atoms/colors.dart
index d2383d8..ebbd25a 100644
--- a/lib/core/designsystem/atoms/colors.dart
+++ b/lib/core/designsystem/atoms/colors.dart
@@ -4,3 +4,5 @@ const primary = Color(0xFF1338BE);
const disabled = Color(0xFF565656);
const onBorder = Color(0xFF0D5EAF);
const onBackground = Color(0xFFC6E0FA);
+const background = Color(0xFF3D67FF);
+const error = Color.fromARGB(255, 196, 8, 8);
diff --git a/lib/core/designsystem/molecules/buttons/primary_button.dart b/lib/core/designsystem/molecules/buttons/primary_button.dart
index 87ac8e3..9ecf1ba 100644
--- a/lib/core/designsystem/molecules/buttons/primary_button.dart
+++ b/lib/core/designsystem/molecules/buttons/primary_button.dart
@@ -4,6 +4,7 @@ import 'package:mibook/core/designsystem/atoms/colors.dart';
class PrimaryButton extends StatelessWidget {
final String title;
final bool isEnabled;
+ final bool isExpanded;
final bool isLoading;
final VoidCallback onPressed;
@@ -11,36 +12,47 @@ class PrimaryButton extends StatelessWidget {
super.key,
required this.title,
this.isEnabled = true,
+ this.isExpanded = false,
this.isLoading = false,
required this.onPressed,
});
@override
Widget build(BuildContext context) {
- return SizedBox(
- height: 48,
- child: ElevatedButton(
- onPressed: isEnabled ? onPressed : null,
- style: ElevatedButton.styleFrom(
- backgroundColor: isEnabled ? primary : disabled,
- foregroundColor: Colors.white,
- padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12),
- textStyle: const TextStyle(fontSize: 16, fontWeight: FontWeight.w500),
- shape: RoundedRectangleBorder(
- borderRadius: BorderRadius.circular(16),
- ),
- minimumSize: const Size(80, 48),
+ final button = ElevatedButton(
+ onPressed: isEnabled ? onPressed : null,
+ style: ElevatedButton.styleFrom(
+ backgroundColor: isEnabled ? primary : disabled,
+ foregroundColor: Colors.white,
+ padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12),
+ textStyle: const TextStyle(fontSize: 16, fontWeight: FontWeight.w500),
+ shape: RoundedRectangleBorder(
+ borderRadius: BorderRadius.circular(16),
),
- child: isLoading
- ? SizedBox(
- width: 20,
- height: 20,
- child: CircularProgressIndicator(
- color: Colors.white,
- ),
- )
- : Text(title),
+ minimumSize: const Size(80, 48),
),
+ child: isLoading
+ ? SizedBox(
+ width: 20,
+ height: 20,
+ child: CircularProgressIndicator(
+ color: Colors.white,
+ ),
+ )
+ : Text(title),
);
+
+ if (isExpanded) {
+ return SizedBox(
+ height: 48,
+ width: double.infinity,
+ child: button,
+ );
+ } else {
+ return SizedBox(
+ height: 48,
+ child: button,
+ );
+ }
}
}
diff --git a/lib/core/designsystem/molecules/buttons/secondary_button.dart b/lib/core/designsystem/molecules/buttons/secondary_button.dart
new file mode 100644
index 0000000..7937c68
--- /dev/null
+++ b/lib/core/designsystem/molecules/buttons/secondary_button.dart
@@ -0,0 +1 @@
+g
\ No newline at end of file
diff --git a/lib/core/designsystem/molecules/indicators/progress_stepper.dart b/lib/core/designsystem/molecules/indicators/progress_stepper.dart
new file mode 100644
index 0000000..39f108b
--- /dev/null
+++ b/lib/core/designsystem/molecules/indicators/progress_stepper.dart
@@ -0,0 +1,25 @@
+import 'package:flutter/material.dart';
+import 'package:mibook/core/designsystem/atoms/colors.dart';
+
+class ProgressStepper extends StatelessWidget {
+ final double progress;
+
+ const ProgressStepper({super.key, required this.progress});
+
+ @override
+ Widget build(BuildContext context) {
+ return Column(
+ crossAxisAlignment: CrossAxisAlignment.start,
+ children: [
+ Text('Progress: ${(progress * 100).toStringAsFixed(1)}%'),
+ const SizedBox(height: 8),
+ LinearProgressIndicator(
+ value: progress,
+ minHeight: 8,
+ backgroundColor: Colors.grey[300],
+ valueColor: AlwaysStoppedAnimation(background),
+ ),
+ ],
+ );
+ }
+}
diff --git a/lib/core/designsystem/molecules/inputfields/input_field.dart b/lib/core/designsystem/molecules/inputfields/input_field.dart
index 71e1cf3..54edef9 100644
--- a/lib/core/designsystem/molecules/inputfields/input_field.dart
+++ b/lib/core/designsystem/molecules/inputfields/input_field.dart
@@ -8,6 +8,7 @@ class InputField extends StatelessWidget {
final String placeholder;
final String? suffixText;
final String? prefixText;
+ final String? errorMessage;
final TextInputType? keyboardType;
final Function(String) onChanged;
@@ -19,6 +20,7 @@ class InputField extends StatelessWidget {
this.placeholder = '',
this.suffixText,
this.prefixText,
+ this.errorMessage,
this.keyboardType,
required this.onChanged,
});
@@ -39,8 +41,13 @@ class InputField extends StatelessWidget {
focusedBorder: OutlineInputBorder(
borderSide: BorderSide(color: onBorder),
),
+ errorBorder: OutlineInputBorder(
+ borderSide: BorderSide(color: error),
+ ),
+ hintText: placeholder,
suffixText: suffixText,
prefixText: prefixText,
+ errorText: errorMessage,
),
onChanged: onChanged,
),
diff --git a/lib/core/di/di.config.dart b/lib/core/di/di.config.dart
index 1959414..99bb7cd 100644
--- a/lib/core/di/di.config.dart
+++ b/lib/core/di/di.config.dart
@@ -27,10 +27,14 @@ import 'package:mibook/layers/domain/repository/search_repository.dart'
import 'package:mibook/layers/domain/usecases/get_book_details.dart' as _i814;
import 'package:mibook/layers/domain/usecases/search_books.dart' as _i663;
import 'package:mibook/layers/domain/usecases/start_reading.dart' as _i369;
+import 'package:mibook/layers/presentation/screens/bookdetails/book_details_ui.dart'
+ as _i66;
import 'package:mibook/layers/presentation/screens/bookdetails/book_details_view_model.dart'
as _i46;
import 'package:mibook/layers/presentation/screens/booksearch/book_search_view_model.dart'
as _i688;
+import 'package:mibook/layers/presentation/screens/startreading/start_reading_view_model.dart'
+ as _i35;
extension GetItInjectableX on _i174.GetIt {
// initializes the registration of main-scope dependencies inside of GetIt
@@ -69,6 +73,9 @@ extension GetItInjectableX on _i174.GetIt {
bookId,
),
);
+ gh.factoryParam<_i35.StartReadingViewModel, _i66.BookDetailsUI, dynamic>(
+ (book, _) => _i35.StartReadingViewModel(gh<_i369.IStartReading>(), book),
+ );
gh.factory<_i688.BookSearchViewModel>(
() => _i688.BookSearchViewModel(gh<_i663.ISearchBooks>()),
);
diff --git a/lib/core/routes/app_router.dart b/lib/core/routes/app_router.dart
index 3771925..9d79f0d 100644
--- a/lib/core/routes/app_router.dart
+++ b/lib/core/routes/app_router.dart
@@ -18,7 +18,8 @@ class AppRouter extends RootStackRouter {
page: SearchRoute.page,
children: [
AutoRoute(page: BookSearchRoute.page),
- AutoRoute(page: BoolDetailsRoute.page),
+ AutoRoute(page: BookDetailsRoute.page),
+ AutoRoute(page: StartReadingRoute.page),
],
),
AutoRoute(
diff --git a/lib/core/routes/app_router.gr.dart b/lib/core/routes/app_router.gr.dart
index 7e0406f..650fdbd 100644
--- a/lib/core/routes/app_router.gr.dart
+++ b/lib/core/routes/app_router.gr.dart
@@ -9,8 +9,8 @@
// coverage:ignore-file
// ignore_for_file: no_leading_underscores_for_library_prefixes
-import 'package:auto_route/auto_route.dart' as _i10;
-import 'package:flutter/material.dart' as _i11;
+import 'package:auto_route/auto_route.dart' as _i11;
+import 'package:flutter/material.dart' as _i12;
import 'package:mibook/layers/presentation/navigation/dashboard/dashboard_page.dart'
as _i4;
import 'package:mibook/layers/presentation/navigation/tabs/home_tab.dart'
@@ -19,79 +19,83 @@ import 'package:mibook/layers/presentation/navigation/tabs/objective_tab.dart'
as _i6;
import 'package:mibook/layers/presentation/navigation/tabs/search_tab.dart'
as _i9;
-import 'package:mibook/layers/presentation/screens/bookdetails/bool_details_page.dart'
- as _i2;
-import 'package:mibook/layers/presentation/screens/booksearch/book_search_page.dart'
+import 'package:mibook/layers/presentation/screens/bookdetails/book_details_page.dart'
as _i1;
+import 'package:mibook/layers/presentation/screens/bookdetails/book_details_ui.dart'
+ as _i13;
+import 'package:mibook/layers/presentation/screens/booksearch/book_search_page.dart'
+ as _i2;
import 'package:mibook/layers/presentation/screens/currentobjective/current_objective_page.dart'
as _i3;
import 'package:mibook/layers/presentation/screens/onboarding/pre_onboarding_page.dart'
as _i7;
import 'package:mibook/layers/presentation/screens/readinglist/reading_list_page.dart'
as _i8;
+import 'package:mibook/layers/presentation/screens/startreading/start_reading_page.dart'
+ as _i10;
/// generated route for
-/// [_i1.BookSearchPage]
-class BookSearchRoute extends _i10.PageRouteInfo {
- const BookSearchRoute({List<_i10.PageRouteInfo>? children})
- : super(BookSearchRoute.name, initialChildren: children);
-
- static const String name = 'BookSearchRoute';
-
- static _i10.PageInfo page = _i10.PageInfo(
- name,
- builder: (data) {
- return const _i1.BookSearchPage();
- },
- );
-}
-
-/// generated route for
-/// [_i2.BoolDetailsPage]
-class BoolDetailsRoute extends _i10.PageRouteInfo {
- BoolDetailsRoute({
- _i11.Key? key,
+/// [_i1.BookDetailsPage]
+class BookDetailsRoute extends _i11.PageRouteInfo {
+ BookDetailsRoute({
+ _i12.Key? key,
required String id,
- List<_i10.PageRouteInfo>? children,
+ List<_i11.PageRouteInfo>? children,
}) : super(
- BoolDetailsRoute.name,
- args: BoolDetailsRouteArgs(key: key, id: id),
+ BookDetailsRoute.name,
+ args: BookDetailsRouteArgs(key: key, id: id),
initialChildren: children,
);
- static const String name = 'BoolDetailsRoute';
+ static const String name = 'BookDetailsRoute';
- static _i10.PageInfo page = _i10.PageInfo(
+ static _i11.PageInfo page = _i11.PageInfo(
name,
builder: (data) {
- final args = data.argsAs();
- return _i2.BoolDetailsPage(key: args.key, id: args.id);
+ final args = data.argsAs();
+ return _i1.BookDetailsPage(key: args.key, id: args.id);
},
);
}
-class BoolDetailsRouteArgs {
- const BoolDetailsRouteArgs({this.key, required this.id});
+class BookDetailsRouteArgs {
+ const BookDetailsRouteArgs({this.key, required this.id});
- final _i11.Key? key;
+ final _i12.Key? key;
final String id;
@override
String toString() {
- return 'BoolDetailsRouteArgs{key: $key, id: $id}';
+ return 'BookDetailsRouteArgs{key: $key, id: $id}';
}
}
+/// generated route for
+/// [_i2.BookSearchPage]
+class BookSearchRoute extends _i11.PageRouteInfo {
+ const BookSearchRoute({List<_i11.PageRouteInfo>? children})
+ : super(BookSearchRoute.name, initialChildren: children);
+
+ static const String name = 'BookSearchRoute';
+
+ static _i11.PageInfo page = _i11.PageInfo(
+ name,
+ builder: (data) {
+ return const _i2.BookSearchPage();
+ },
+ );
+}
+
/// generated route for
/// [_i3.CurrentObjectivePage]
-class CurrentObjectiveRoute extends _i10.PageRouteInfo {
- const CurrentObjectiveRoute({List<_i10.PageRouteInfo>? children})
+class CurrentObjectiveRoute extends _i11.PageRouteInfo {
+ const CurrentObjectiveRoute({List<_i11.PageRouteInfo>? children})
: super(CurrentObjectiveRoute.name, initialChildren: children);
static const String name = 'CurrentObjectiveRoute';
- static _i10.PageInfo page = _i10.PageInfo(
+ static _i11.PageInfo page = _i11.PageInfo(
name,
builder: (data) {
return const _i3.CurrentObjectivePage();
@@ -101,13 +105,13 @@ class CurrentObjectiveRoute extends _i10.PageRouteInfo {
/// generated route for
/// [_i4.DashboardPage]
-class DashboardRoute extends _i10.PageRouteInfo {
- const DashboardRoute({List<_i10.PageRouteInfo>? children})
+class DashboardRoute extends _i11.PageRouteInfo {
+ const DashboardRoute({List<_i11.PageRouteInfo>? children})
: super(DashboardRoute.name, initialChildren: children);
static const String name = 'DashboardRoute';
- static _i10.PageInfo page = _i10.PageInfo(
+ static _i11.PageInfo page = _i11.PageInfo(
name,
builder: (data) {
return const _i4.DashboardPage();
@@ -117,13 +121,13 @@ class DashboardRoute extends _i10.PageRouteInfo {
/// generated route for
/// [_i5.HomeTab]
-class HomeRoute extends _i10.PageRouteInfo {
- const HomeRoute({List<_i10.PageRouteInfo>? children})
+class HomeRoute extends _i11.PageRouteInfo {
+ const HomeRoute({List<_i11.PageRouteInfo>? children})
: super(HomeRoute.name, initialChildren: children);
static const String name = 'HomeRoute';
- static _i10.PageInfo page = _i10.PageInfo(
+ static _i11.PageInfo page = _i11.PageInfo(
name,
builder: (data) {
return const _i5.HomeTab();
@@ -133,13 +137,13 @@ class HomeRoute extends _i10.PageRouteInfo {
/// generated route for
/// [_i6.ObjectiveTab]
-class ObjectiveRoute extends _i10.PageRouteInfo {
- const ObjectiveRoute({List<_i10.PageRouteInfo>? children})
+class ObjectiveRoute extends _i11.PageRouteInfo {
+ const ObjectiveRoute({List<_i11.PageRouteInfo>? children})
: super(ObjectiveRoute.name, initialChildren: children);
static const String name = 'ObjectiveRoute';
- static _i10.PageInfo page = _i10.PageInfo(
+ static _i11.PageInfo page = _i11.PageInfo(
name,
builder: (data) {
return const _i6.ObjectiveTab();
@@ -149,13 +153,13 @@ class ObjectiveRoute extends _i10.PageRouteInfo {
/// generated route for
/// [_i7.PreOnboardingPage]
-class PreOnboardingRoute extends _i10.PageRouteInfo {
- const PreOnboardingRoute({List<_i10.PageRouteInfo>? children})
+class PreOnboardingRoute extends _i11.PageRouteInfo {
+ const PreOnboardingRoute({List<_i11.PageRouteInfo>? children})
: super(PreOnboardingRoute.name, initialChildren: children);
static const String name = 'PreOnboardingRoute';
- static _i10.PageInfo page = _i10.PageInfo(
+ static _i11.PageInfo page = _i11.PageInfo(
name,
builder: (data) {
return const _i7.PreOnboardingPage();
@@ -165,13 +169,13 @@ class PreOnboardingRoute extends _i10.PageRouteInfo {
/// generated route for
/// [_i8.ReadingListPage]
-class ReadingListRoute extends _i10.PageRouteInfo {
- const ReadingListRoute({List<_i10.PageRouteInfo>? children})
+class ReadingListRoute extends _i11.PageRouteInfo {
+ const ReadingListRoute({List<_i11.PageRouteInfo>? children})
: super(ReadingListRoute.name, initialChildren: children);
static const String name = 'ReadingListRoute';
- static _i10.PageInfo page = _i10.PageInfo(
+ static _i11.PageInfo page = _i11.PageInfo(
name,
builder: (data) {
return const _i8.ReadingListPage();
@@ -181,16 +185,53 @@ class ReadingListRoute extends _i10.PageRouteInfo {
/// generated route for
/// [_i9.SearchTab]
-class SearchRoute extends _i10.PageRouteInfo {
- const SearchRoute({List<_i10.PageRouteInfo>? children})
+class SearchRoute extends _i11.PageRouteInfo {
+ const SearchRoute({List<_i11.PageRouteInfo>? children})
: super(SearchRoute.name, initialChildren: children);
static const String name = 'SearchRoute';
- static _i10.PageInfo page = _i10.PageInfo(
+ static _i11.PageInfo page = _i11.PageInfo(
name,
builder: (data) {
return const _i9.SearchTab();
},
);
}
+
+/// generated route for
+/// [_i10.StartReadingPage]
+class StartReadingRoute extends _i11.PageRouteInfo {
+ StartReadingRoute({
+ _i12.Key? key,
+ required _i13.BookDetailsUI book,
+ List<_i11.PageRouteInfo>? children,
+ }) : super(
+ StartReadingRoute.name,
+ args: StartReadingRouteArgs(key: key, book: book),
+ initialChildren: children,
+ );
+
+ static const String name = 'StartReadingRoute';
+
+ static _i11.PageInfo page = _i11.PageInfo(
+ name,
+ builder: (data) {
+ final args = data.argsAs();
+ return _i10.StartReadingPage(key: args.key, book: args.book);
+ },
+ );
+}
+
+class StartReadingRouteArgs {
+ const StartReadingRouteArgs({this.key, required this.book});
+
+ final _i12.Key? key;
+
+ final _i13.BookDetailsUI book;
+
+ @override
+ String toString() {
+ return 'StartReadingRouteArgs{key: $key, book: $book}';
+ }
+}
diff --git a/lib/core/utils/strings.dart b/lib/core/utils/strings.dart
index 8166bd2..24cf561 100644
--- a/lib/core/utils/strings.dart
+++ b/lib/core/utils/strings.dart
@@ -7,3 +7,5 @@ const percent = '%';
const page = 'Page';
const progress = 'Progress';
const confirm = 'Confirm';
+const progressErrorMessage = 'You can t enter more pages than the book maximum';
+const zero = '0';
diff --git a/lib/layers/data/models/book_list_data.dart b/lib/layers/data/models/book_list_data.dart
index 8b49b68..3171981 100644
--- a/lib/layers/data/models/book_list_data.dart
+++ b/lib/layers/data/models/book_list_data.dart
@@ -6,7 +6,7 @@ part 'book_list_data.g.dart';
@JsonSerializable()
class BookListData {
final String kind;
- final int totalItems;
+ final int? totalItems;
@JsonKey(defaultValue: [])
final List items;
@@ -23,7 +23,7 @@ class BookListData {
BookListDomain toDomain() {
return BookListDomain(
- totalItems: totalItems,
+ totalItems: totalItems ?? 0,
books: items.map((item) => item.toDomain()).toList(),
);
}
@@ -64,7 +64,7 @@ class BookItem {
authors: volumeInfo.authors,
description: volumeInfo.description,
thumbnail: volumeInfo.imageLinks?.thumbnail,
- pageCount: volumeInfo.pageCount,
+ pageCount: volumeInfo.pageCount ?? 100,
);
}
}
@@ -79,7 +79,7 @@ class VolumeInfo {
final String? description;
final List? industryIdentifiers;
final ReadingModes? readingModes;
- final int pageCount;
+ final int? pageCount;
final String? printType;
final List? categories;
final double? averageRating;
@@ -234,7 +234,7 @@ class Price {
@JsonSerializable()
class Offer {
- final int finskyOfferType;
+ final int? finskyOfferType;
final Price? listPrice;
final Price? retailPrice;
final bool? giftable;
diff --git a/lib/layers/data/models/book_list_data.g.dart b/lib/layers/data/models/book_list_data.g.dart
index cd2940d..295b723 100644
--- a/lib/layers/data/models/book_list_data.g.dart
+++ b/lib/layers/data/models/book_list_data.g.dart
@@ -8,7 +8,7 @@ part of 'book_list_data.dart';
BookListData _$BookListDataFromJson(Map json) => BookListData(
kind: json['kind'] as String,
- totalItems: (json['totalItems'] as num).toInt(),
+ totalItems: (json['totalItems'] as num?)?.toInt(),
items:
(json['items'] as List?)
?.map((e) => BookItem.fromJson(e as Map))
@@ -62,7 +62,7 @@ VolumeInfo _$VolumeInfoFromJson(Map json) => VolumeInfo(
readingModes: json['readingModes'] == null
? null
: ReadingModes.fromJson(json['readingModes'] as Map),
- pageCount: (json['pageCount'] as num).toInt(),
+ pageCount: (json['pageCount'] as num?)?.toInt(),
printType: json['printType'] as String?,
categories: (json['categories'] as List?)
?.map((e) => e as String)
@@ -190,7 +190,7 @@ Map _$PriceToJson(Price instance) => {
};
Offer _$OfferFromJson(Map json) => Offer(
- finskyOfferType: (json['finskyOfferType'] as num).toInt(),
+ finskyOfferType: (json['finskyOfferType'] as num?)?.toInt(),
listPrice: json['listPrice'] == null
? null
: Price.fromJson(json['listPrice'] as Map),
diff --git a/lib/layers/presentation/screens/bookdetails/book_details_event.dart b/lib/layers/presentation/screens/bookdetails/book_details_event.dart
index 264a989..cc0b1c3 100644
--- a/lib/layers/presentation/screens/bookdetails/book_details_event.dart
+++ b/lib/layers/presentation/screens/bookdetails/book_details_event.dart
@@ -9,3 +9,8 @@ class DidClickStartReadingEvent extends BookDetailsEvent {
final double progress;
DidClickStartReadingEvent(this.progress);
}
+
+class DidChangeProgressTextEvent extends BookDetailsEvent {
+ final int progress;
+ DidChangeProgressTextEvent(this.progress);
+}
diff --git a/lib/layers/presentation/screens/bookdetails/bool_details_page.dart b/lib/layers/presentation/screens/bookdetails/book_details_page.dart
similarity index 64%
rename from lib/layers/presentation/screens/bookdetails/bool_details_page.dart
rename to lib/layers/presentation/screens/bookdetails/book_details_page.dart
index 020feb5..ef1e94b 100644
--- a/lib/layers/presentation/screens/bookdetails/bool_details_page.dart
+++ b/lib/layers/presentation/screens/bookdetails/book_details_page.dart
@@ -4,21 +4,23 @@ import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flutter_html/flutter_html.dart';
import 'package:mibook/core/designsystem/molecules/buttons/primary_button.dart';
+import 'package:mibook/core/designsystem/molecules/indicators/progress_stepper.dart';
import 'package:mibook/core/designsystem/molecules/inputfields/input_field.dart';
import 'package:mibook/core/designsystem/organisms/app_nav_bar.dart';
import 'package:mibook/core/designsystem/organisms/list_item.dart';
import 'package:mibook/core/di/di.dart';
-import 'package:mibook/core/utils/strings.dart';
+import 'package:mibook/core/routes/app_router.gr.dart';
+import 'package:mibook/core/utils/strings.dart' as strings;
import 'package:mibook/layers/presentation/screens/bookdetails/book_details_state.dart';
import 'package:mibook/layers/presentation/screens/bookdetails/book_details_ui.dart';
import 'package:mibook/layers/presentation/screens/bookdetails/book_details_view_model.dart';
import 'package:mibook/layers/presentation/screens/bookdetails/book_details_event.dart';
@RoutePage()
-class BoolDetailsPage extends StatelessWidget {
+class BookDetailsPage extends StatelessWidget {
final String id;
- const BoolDetailsPage({
+ const BookDetailsPage({
super.key,
required this.id,
});
@@ -61,6 +63,7 @@ class _BookDetailsContent extends StatelessWidget {
@override
Widget build(BuildContext context) {
+ final viewModel = context.read();
return SingleChildScrollView(
child: Column(
children: [
@@ -72,6 +75,8 @@ class _BookDetailsContent extends StatelessWidget {
return Center(child: Text('Error: ${state.errorMessage}'));
} else if (state.bookDetails != null) {
final book = state.bookDetails!;
+ final bookProgress = state.bookProgress;
+
return Padding(
padding: const EdgeInsets.all(16.0),
child: Column(
@@ -107,63 +112,14 @@ class _BookDetailsContent extends StatelessWidget {
),
const SizedBox(height: 24),
PrimaryButton(
- title: startReading,
- onPressed: () => showGeneralDialog(
- context: context,
- barrierDismissible:
- true, // allows tap outside to close
- barrierLabel: "Dismiss",
- barrierColor: Colors.black54, // dim background
- transitionDuration: const Duration(milliseconds: 300),
- pageBuilder: (context, anim1, anim2) {
- // Must return a full-screen widget so barrier can detect taps
- return SafeArea(
- child: Builder(
- builder: (context) {
- return Center(
- child: Material(
- color: Colors.transparent,
- child: AlertDialog(
- backgroundColor: Colors.white,
- insetPadding:
- EdgeInsets.zero, // remove margin
- contentPadding: EdgeInsets
- .zero, // remove inner padding if needed
- content: SizedBox(
- width:
- MediaQuery.of(
- context,
- ).size.width -
- 32,
- child: _StartReadingDialogContent(
- book: book,
- onClickStartReading:
- (double progress) {
- Navigator.of(context).pop();
- },
- ),
- ),
- ),
- ),
- );
- },
- ),
- );
- },
- transitionBuilder: (context, anim1, anim2, child) {
- // Slide + fade
- return SlideTransition(
- position: Tween(
- begin: const Offset(0, 0.2), // from bottom
- end: Offset.zero,
- ).animate(anim1),
- child: FadeTransition(
- opacity: anim1,
- child: child,
- ),
+ title: strings.startReading,
+ onPressed: () {
+ if (state.bookDetails != null) {
+ context.router.push(
+ StartReadingRoute(book: state.bookDetails!),
);
- },
- ),
+ }
+ },
),
const SizedBox(height: 32),
],
@@ -182,16 +138,21 @@ class _BookDetailsContent extends StatelessWidget {
class _StartReadingDialogContent extends StatelessWidget {
final BookDetailsUI book;
+ final double progress;
final TextEditingController _controller = TextEditingController();
+ final Function(String) onChangeProgressText;
final Function(double) onClickStartReading;
_StartReadingDialogContent({
required this.book,
+ required this.progress,
+ required this.onChangeProgressText,
required this.onClickStartReading,
});
@override
Widget build(BuildContext context) {
+ debugPrint('Progress $progress');
return Padding(
padding: const EdgeInsets.all(16.0),
child: Column(
@@ -219,14 +180,15 @@ class _StartReadingDialogContent extends StatelessWidget {
const SizedBox(height: 24),
InputField(
keyboardType: TextInputType.number,
- label: progress,
+ label: strings.progress,
controller: _controller,
- onChanged: (_) {},
- prefixText: '$page ',
+ onChanged: onChangeProgressText,
),
const SizedBox(height: 24),
+ ProgressStepper(progress: progress),
+ const SizedBox(height: 24),
PrimaryButton(
- title: confirm,
+ title: strings.confirm,
onPressed: () {},
),
],
diff --git a/lib/layers/presentation/screens/bookdetails/book_details_state.dart b/lib/layers/presentation/screens/bookdetails/book_details_state.dart
index 2e7a4aa..841c49f 100644
--- a/lib/layers/presentation/screens/bookdetails/book_details_state.dart
+++ b/lib/layers/presentation/screens/bookdetails/book_details_state.dart
@@ -10,16 +10,20 @@ class BookDetailsState with _$BookDetailsState {
final String? errorMessage;
@override
final BookDetailsUI? bookDetails;
+ @override
+ final double bookProgress;
BookDetailsState(
this.errorMessage,
this.bookDetails, {
required this.isLoading,
+ required this.bookProgress,
});
factory BookDetailsState.initial() => BookDetailsState(
null,
null,
isLoading: false,
+ bookProgress: 0.0,
);
}
diff --git a/lib/layers/presentation/screens/bookdetails/book_details_state.freezed.dart b/lib/layers/presentation/screens/bookdetails/book_details_state.freezed.dart
new file mode 100644
index 0000000..20619d4
--- /dev/null
+++ b/lib/layers/presentation/screens/bookdetails/book_details_state.freezed.dart
@@ -0,0 +1,202 @@
+// GENERATED CODE - DO NOT MODIFY BY HAND
+// coverage:ignore-file
+// ignore_for_file: type=lint
+// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark
+
+part of 'book_details_state.dart';
+
+// **************************************************************************
+// FreezedGenerator
+// **************************************************************************
+
+// dart format off
+T _$identity(T value) => value;
+/// @nodoc
+mixin _$BookDetailsState {
+
+ bool get isLoading; String? get errorMessage; BookDetailsUI? get bookDetails; double get bookProgress;
+/// Create a copy of BookDetailsState
+/// with the given fields replaced by the non-null parameter values.
+@JsonKey(includeFromJson: false, includeToJson: false)
+@pragma('vm:prefer-inline')
+$BookDetailsStateCopyWith get copyWith => _$BookDetailsStateCopyWithImpl(this as BookDetailsState, _$identity);
+
+
+
+@override
+bool operator ==(Object other) {
+ return identical(this, other) || (other.runtimeType == runtimeType&&other is BookDetailsState&&(identical(other.isLoading, isLoading) || other.isLoading == isLoading)&&(identical(other.errorMessage, errorMessage) || other.errorMessage == errorMessage)&&(identical(other.bookDetails, bookDetails) || other.bookDetails == bookDetails)&&(identical(other.bookProgress, bookProgress) || other.bookProgress == bookProgress));
+}
+
+
+@override
+int get hashCode => Object.hash(runtimeType,isLoading,errorMessage,bookDetails,bookProgress);
+
+@override
+String toString() {
+ return 'BookDetailsState(isLoading: $isLoading, errorMessage: $errorMessage, bookDetails: $bookDetails, bookProgress: $bookProgress)';
+}
+
+
+}
+
+/// @nodoc
+abstract mixin class $BookDetailsStateCopyWith<$Res> {
+ factory $BookDetailsStateCopyWith(BookDetailsState value, $Res Function(BookDetailsState) _then) = _$BookDetailsStateCopyWithImpl;
+@useResult
+$Res call({
+ String? errorMessage, BookDetailsUI? bookDetails, bool isLoading, double bookProgress
+});
+
+
+
+
+}
+/// @nodoc
+class _$BookDetailsStateCopyWithImpl<$Res>
+ implements $BookDetailsStateCopyWith<$Res> {
+ _$BookDetailsStateCopyWithImpl(this._self, this._then);
+
+ final BookDetailsState _self;
+ final $Res Function(BookDetailsState) _then;
+
+/// Create a copy of BookDetailsState
+/// with the given fields replaced by the non-null parameter values.
+@pragma('vm:prefer-inline') @override $Res call({Object? errorMessage = freezed,Object? bookDetails = freezed,Object? isLoading = null,Object? bookProgress = null,}) {
+ return _then(BookDetailsState(
+freezed == errorMessage ? _self.errorMessage : errorMessage // ignore: cast_nullable_to_non_nullable
+as String?,freezed == bookDetails ? _self.bookDetails : bookDetails // ignore: cast_nullable_to_non_nullable
+as BookDetailsUI?,isLoading: null == isLoading ? _self.isLoading : isLoading // ignore: cast_nullable_to_non_nullable
+as bool,bookProgress: null == bookProgress ? _self.bookProgress : bookProgress // ignore: cast_nullable_to_non_nullable
+as double,
+ ));
+}
+
+}
+
+
+/// Adds pattern-matching-related methods to [BookDetailsState].
+extension BookDetailsStatePatterns on BookDetailsState {
+/// A variant of `map` that fallback to returning `orElse`.
+///
+/// It is equivalent to doing:
+/// ```dart
+/// switch (sealedClass) {
+/// case final Subclass value:
+/// return ...;
+/// case _:
+/// return orElse();
+/// }
+/// ```
+
+@optionalTypeArgs TResult maybeMap({required TResult orElse(),}){
+final _that = this;
+switch (_that) {
+case _:
+ return orElse();
+
+}
+}
+/// A `switch`-like method, using callbacks.
+///
+/// Callbacks receives the raw object, upcasted.
+/// It is equivalent to doing:
+/// ```dart
+/// switch (sealedClass) {
+/// case final Subclass value:
+/// return ...;
+/// case final Subclass2 value:
+/// return ...;
+/// }
+/// ```
+
+@optionalTypeArgs TResult map(){
+final _that = this;
+switch (_that) {
+case _:
+ throw StateError('Unexpected subclass');
+
+}
+}
+/// A variant of `map` that fallback to returning `null`.
+///
+/// It is equivalent to doing:
+/// ```dart
+/// switch (sealedClass) {
+/// case final Subclass value:
+/// return ...;
+/// case _:
+/// return null;
+/// }
+/// ```
+
+@optionalTypeArgs TResult? mapOrNull(){
+final _that = this;
+switch (_that) {
+case _:
+ return null;
+
+}
+}
+/// A variant of `when` that fallback to an `orElse` callback.
+///
+/// It is equivalent to doing:
+/// ```dart
+/// switch (sealedClass) {
+/// case Subclass(:final field):
+/// return ...;
+/// case _:
+/// return orElse();
+/// }
+/// ```
+
+@optionalTypeArgs TResult maybeWhen({required TResult orElse(),}) {final _that = this;
+switch (_that) {
+case _:
+ return orElse();
+
+}
+}
+/// A `switch`-like method, using callbacks.
+///
+/// As opposed to `map`, this offers destructuring.
+/// It is equivalent to doing:
+/// ```dart
+/// switch (sealedClass) {
+/// case Subclass(:final field):
+/// return ...;
+/// case Subclass2(:final field2):
+/// return ...;
+/// }
+/// ```
+
+@optionalTypeArgs TResult when() {final _that = this;
+switch (_that) {
+case _:
+ throw StateError('Unexpected subclass');
+
+}
+}
+/// A variant of `when` that fallback to returning `null`
+///
+/// It is equivalent to doing:
+/// ```dart
+/// switch (sealedClass) {
+/// case Subclass(:final field):
+/// return ...;
+/// case _:
+/// return null;
+/// }
+/// ```
+
+@optionalTypeArgs TResult? whenOrNull() {final _that = this;
+switch (_that) {
+case _:
+ return null;
+
+}
+}
+
+}
+
+// dart format on
diff --git a/lib/layers/presentation/screens/bookdetails/book_details_view_model.dart b/lib/layers/presentation/screens/bookdetails/book_details_view_model.dart
index 834c0be..e89c06d 100644
--- a/lib/layers/presentation/screens/bookdetails/book_details_view_model.dart
+++ b/lib/layers/presentation/screens/bookdetails/book_details_view_model.dart
@@ -43,6 +43,13 @@ class BookDetailsViewModel extends Bloc {
}
});
on((event, emit) async {});
+ on((event, emit) {
+ final pageCount = state.bookDetails?.pageCount;
+ if (pageCount != null) {
+ final progress = event.progress.toDouble() / pageCount.toDouble();
+ emit(state.copyWith(bookProgress: progress));
+ }
+ });
}
Future loadBookDetails() async {
diff --git a/lib/layers/presentation/screens/booksearch/book_search_page.dart b/lib/layers/presentation/screens/booksearch/book_search_page.dart
index 4ca0d02..15ff8d6 100644
--- a/lib/layers/presentation/screens/booksearch/book_search_page.dart
+++ b/lib/layers/presentation/screens/booksearch/book_search_page.dart
@@ -110,7 +110,7 @@ class _SearchScaffold extends StatelessWidget {
),
child: ListItem(
onTap: () => context.router.push(
- BoolDetailsRoute(id: book.id),
+ BookDetailsRoute(id: book.id),
),
input: BookItemInput(
id: book.id,
diff --git a/lib/layers/presentation/screens/startreading/start_reading_event.dart b/lib/layers/presentation/screens/startreading/start_reading_event.dart
new file mode 100644
index 0000000..a5a0be4
--- /dev/null
+++ b/lib/layers/presentation/screens/startreading/start_reading_event.dart
@@ -0,0 +1,8 @@
+abstract class StartReadingEvent {}
+
+class DidEditProgressEvent extends StartReadingEvent {
+ int progress;
+ DidEditProgressEvent({required this.progress});
+}
+
+class DidClickConfirmEvent extends StartReadingEvent {}
diff --git a/lib/layers/presentation/screens/startreading/start_reading_page.dart b/lib/layers/presentation/screens/startreading/start_reading_page.dart
new file mode 100644
index 0000000..93cb2d8
--- /dev/null
+++ b/lib/layers/presentation/screens/startreading/start_reading_page.dart
@@ -0,0 +1,121 @@
+import 'package:auto_route/auto_route.dart';
+import 'package:cached_network_image/cached_network_image.dart';
+import 'package:flutter/material.dart';
+import 'package:flutter_bloc/flutter_bloc.dart';
+import 'package:mibook/core/designsystem/molecules/buttons/primary_button.dart';
+import 'package:mibook/core/designsystem/molecules/indicators/progress_stepper.dart';
+import 'package:mibook/core/designsystem/molecules/inputfields/input_field.dart';
+import 'package:mibook/core/designsystem/organisms/app_nav_bar.dart';
+import 'package:mibook/core/di/di.dart';
+import 'package:mibook/core/utils/strings.dart';
+import 'package:mibook/core/utils/strings.dart' as strings;
+import 'package:mibook/layers/presentation/screens/bookdetails/book_details_ui.dart';
+import 'package:mibook/layers/presentation/screens/startreading/start_reading_event.dart';
+import 'package:mibook/layers/presentation/screens/startreading/start_reading_state.dart';
+import 'package:mibook/layers/presentation/screens/startreading/start_reading_view_model.dart';
+
+@RoutePage()
+class StartReadingPage extends StatelessWidget {
+ final BookDetailsUI book;
+
+ const StartReadingPage({
+ super.key,
+ required this.book,
+ });
+
+ @override
+ Widget build(BuildContext context) {
+ return BlocProvider.value(
+ value: getIt(param1: book),
+ child: _StartReadingScaffold(),
+ );
+ }
+}
+
+class _StartReadingScaffold extends StatelessWidget {
+ @override
+ Widget build(BuildContext context) {
+ return Scaffold(
+ appBar: AppNavBar(
+ titleText: startReading,
+ onBack: context.router.maybePop,
+ ),
+ body: BlocBuilder(
+ builder: (context, state) {
+ final viewModel = context.read();
+ return _StartReadingContent(
+ book: viewModel.book,
+ progress: state.progress,
+ errorMessage: state.inputErrorMessage,
+ onChangeProgressText: (progress) {
+ viewModel.add(
+ DidEditProgressEvent(progress: int.tryParse(progress) ?? 0),
+ );
+ },
+ onClickStartReading: () {
+ viewModel.add(DidClickConfirmEvent());
+ },
+ );
+ },
+ ),
+ );
+ }
+}
+
+class _StartReadingContent extends StatelessWidget {
+ final BookDetailsUI book;
+ final double progress;
+ final String? errorMessage;
+ final TextEditingController _controller = TextEditingController();
+ final Function(String) onChangeProgressText;
+ final Function onClickStartReading;
+
+ _StartReadingContent({
+ required this.book,
+ required this.progress,
+ required this.errorMessage,
+ required this.onChangeProgressText,
+ required this.onClickStartReading,
+ });
+
+ @override
+ Widget build(BuildContext context) {
+ return Padding(
+ padding: const EdgeInsets.all(16.0),
+ child: Column(
+ mainAxisSize: MainAxisSize.min,
+ children: [
+ if (book.thumbnail != null)
+ CachedNetworkImage(imageUrl: book.thumbnail!),
+ const SizedBox(height: 16),
+ Text(
+ book.title,
+ style: TextStyle(
+ fontSize: 24,
+ fontWeight: FontWeight.bold,
+ ),
+ ),
+ const SizedBox(height: 8),
+ Text('Maximum pages: ${book.pageCount}'),
+ const SizedBox(height: 24),
+ InputField(
+ keyboardType: TextInputType.number,
+ label: strings.progress,
+ controller: _controller,
+ onChanged: onChangeProgressText,
+ placeholder: strings.zero,
+ errorMessage: errorMessage,
+ ),
+ const SizedBox(height: 24),
+ ProgressStepper(progress: progress),
+ const Spacer(),
+ PrimaryButton(
+ title: strings.confirm,
+ isExpanded: true,
+ onPressed: () {},
+ ),
+ ],
+ ),
+ );
+ }
+}
diff --git a/lib/layers/presentation/screens/startreading/start_reading_state.dart b/lib/layers/presentation/screens/startreading/start_reading_state.dart
new file mode 100644
index 0000000..4036d73
--- /dev/null
+++ b/lib/layers/presentation/screens/startreading/start_reading_state.dart
@@ -0,0 +1,24 @@
+import 'package:freezed_annotation/freezed_annotation.dart';
+part 'start_reading_state.freezed.dart';
+
+@freezed
+class StartReadingState with _$StartReadingState {
+ @override
+ final String? inputErrorMessage;
+ @override
+ final double progress;
+ @override
+ final bool shouldNavigateBack;
+
+ StartReadingState({
+ required this.inputErrorMessage,
+ required this.progress,
+ required this.shouldNavigateBack,
+ });
+
+ static StartReadingState get initial => StartReadingState(
+ inputErrorMessage: null,
+ progress: 0.0,
+ shouldNavigateBack: false,
+ );
+}
diff --git a/lib/layers/presentation/screens/startreading/start_reading_state.freezed.dart b/lib/layers/presentation/screens/startreading/start_reading_state.freezed.dart
new file mode 100644
index 0000000..52af551
--- /dev/null
+++ b/lib/layers/presentation/screens/startreading/start_reading_state.freezed.dart
@@ -0,0 +1,201 @@
+// GENERATED CODE - DO NOT MODIFY BY HAND
+// coverage:ignore-file
+// ignore_for_file: type=lint
+// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark
+
+part of 'start_reading_state.dart';
+
+// **************************************************************************
+// FreezedGenerator
+// **************************************************************************
+
+// dart format off
+T _$identity(T value) => value;
+/// @nodoc
+mixin _$StartReadingState {
+
+ String? get inputErrorMessage; double get progress; bool get shouldNavigateBack;
+/// Create a copy of StartReadingState
+/// with the given fields replaced by the non-null parameter values.
+@JsonKey(includeFromJson: false, includeToJson: false)
+@pragma('vm:prefer-inline')
+$StartReadingStateCopyWith get copyWith => _$StartReadingStateCopyWithImpl(this as StartReadingState, _$identity);
+
+
+
+@override
+bool operator ==(Object other) {
+ return identical(this, other) || (other.runtimeType == runtimeType&&other is StartReadingState&&(identical(other.inputErrorMessage, inputErrorMessage) || other.inputErrorMessage == inputErrorMessage)&&(identical(other.progress, progress) || other.progress == progress)&&(identical(other.shouldNavigateBack, shouldNavigateBack) || other.shouldNavigateBack == shouldNavigateBack));
+}
+
+
+@override
+int get hashCode => Object.hash(runtimeType,inputErrorMessage,progress,shouldNavigateBack);
+
+@override
+String toString() {
+ return 'StartReadingState(inputErrorMessage: $inputErrorMessage, progress: $progress, shouldNavigateBack: $shouldNavigateBack)';
+}
+
+
+}
+
+/// @nodoc
+abstract mixin class $StartReadingStateCopyWith<$Res> {
+ factory $StartReadingStateCopyWith(StartReadingState value, $Res Function(StartReadingState) _then) = _$StartReadingStateCopyWithImpl;
+@useResult
+$Res call({
+ String? inputErrorMessage, double progress, bool shouldNavigateBack
+});
+
+
+
+
+}
+/// @nodoc
+class _$StartReadingStateCopyWithImpl<$Res>
+ implements $StartReadingStateCopyWith<$Res> {
+ _$StartReadingStateCopyWithImpl(this._self, this._then);
+
+ final StartReadingState _self;
+ final $Res Function(StartReadingState) _then;
+
+/// Create a copy of StartReadingState
+/// with the given fields replaced by the non-null parameter values.
+@pragma('vm:prefer-inline') @override $Res call({Object? inputErrorMessage = freezed,Object? progress = null,Object? shouldNavigateBack = null,}) {
+ return _then(StartReadingState(
+inputErrorMessage: freezed == inputErrorMessage ? _self.inputErrorMessage : inputErrorMessage // ignore: cast_nullable_to_non_nullable
+as String?,progress: null == progress ? _self.progress : progress // ignore: cast_nullable_to_non_nullable
+as double,shouldNavigateBack: null == shouldNavigateBack ? _self.shouldNavigateBack : shouldNavigateBack // ignore: cast_nullable_to_non_nullable
+as bool,
+ ));
+}
+
+}
+
+
+/// Adds pattern-matching-related methods to [StartReadingState].
+extension StartReadingStatePatterns on StartReadingState {
+/// A variant of `map` that fallback to returning `orElse`.
+///
+/// It is equivalent to doing:
+/// ```dart
+/// switch (sealedClass) {
+/// case final Subclass value:
+/// return ...;
+/// case _:
+/// return orElse();
+/// }
+/// ```
+
+@optionalTypeArgs TResult maybeMap({required TResult orElse(),}){
+final _that = this;
+switch (_that) {
+case _:
+ return orElse();
+
+}
+}
+/// A `switch`-like method, using callbacks.
+///
+/// Callbacks receives the raw object, upcasted.
+/// It is equivalent to doing:
+/// ```dart
+/// switch (sealedClass) {
+/// case final Subclass value:
+/// return ...;
+/// case final Subclass2 value:
+/// return ...;
+/// }
+/// ```
+
+@optionalTypeArgs TResult map(){
+final _that = this;
+switch (_that) {
+case _:
+ throw StateError('Unexpected subclass');
+
+}
+}
+/// A variant of `map` that fallback to returning `null`.
+///
+/// It is equivalent to doing:
+/// ```dart
+/// switch (sealedClass) {
+/// case final Subclass value:
+/// return ...;
+/// case _:
+/// return null;
+/// }
+/// ```
+
+@optionalTypeArgs TResult? mapOrNull(){
+final _that = this;
+switch (_that) {
+case _:
+ return null;
+
+}
+}
+/// A variant of `when` that fallback to an `orElse` callback.
+///
+/// It is equivalent to doing:
+/// ```dart
+/// switch (sealedClass) {
+/// case Subclass(:final field):
+/// return ...;
+/// case _:
+/// return orElse();
+/// }
+/// ```
+
+@optionalTypeArgs TResult maybeWhen({required TResult orElse(),}) {final _that = this;
+switch (_that) {
+case _:
+ return orElse();
+
+}
+}
+/// A `switch`-like method, using callbacks.
+///
+/// As opposed to `map`, this offers destructuring.
+/// It is equivalent to doing:
+/// ```dart
+/// switch (sealedClass) {
+/// case Subclass(:final field):
+/// return ...;
+/// case Subclass2(:final field2):
+/// return ...;
+/// }
+/// ```
+
+@optionalTypeArgs TResult when() {final _that = this;
+switch (_that) {
+case _:
+ throw StateError('Unexpected subclass');
+
+}
+}
+/// A variant of `when` that fallback to returning `null`
+///
+/// It is equivalent to doing:
+/// ```dart
+/// switch (sealedClass) {
+/// case Subclass(:final field):
+/// return ...;
+/// case _:
+/// return null;
+/// }
+/// ```
+
+@optionalTypeArgs TResult? whenOrNull() {final _that = this;
+switch (_that) {
+case _:
+ return null;
+
+}
+}
+
+}
+
+// dart format on
diff --git a/lib/layers/presentation/screens/startreading/start_reading_view_model.dart b/lib/layers/presentation/screens/startreading/start_reading_view_model.dart
new file mode 100644
index 0000000..54cae43
--- /dev/null
+++ b/lib/layers/presentation/screens/startreading/start_reading_view_model.dart
@@ -0,0 +1,39 @@
+import 'package:flutter_bloc/flutter_bloc.dart';
+import 'package:injectable/injectable.dart';
+import 'package:mibook/core/utils/strings.dart';
+import 'package:mibook/layers/domain/usecases/start_reading.dart';
+import 'package:mibook/layers/presentation/screens/bookdetails/book_details_ui.dart';
+import 'package:mibook/layers/presentation/screens/startreading/start_reading_event.dart';
+import 'package:mibook/layers/presentation/screens/startreading/start_reading_state.dart';
+
+@injectable
+class StartReadingViewModel extends Bloc {
+ final IStartReading _startReading;
+ final BookDetailsUI book;
+
+ StartReadingViewModel(
+ this._startReading,
+ @factoryParam this.book,
+ ) : super(StartReadingState.initial) {
+ on((event, emit) {
+ final progress = event.progress.toDouble() / book.pageCount.toDouble();
+ if (progress > 1.0) {
+ emit(
+ state.copyWith(
+ inputErrorMessage: progressErrorMessage,
+ ),
+ );
+ } else {
+ emit(
+ state.copyWith(
+ progress: progress,
+ inputErrorMessage: null,
+ ),
+ );
+ }
+ });
+ on((event, emit) {
+ // TO DO
+ });
+ }
+}
From ea8f4d797976430c902d1cfea2e3b037c90a4d26 Mon Sep 17 00:00:00 2001
From: Pedro Alvarez <14023675+pnalvarez@users.noreply.github.com>
Date: Fri, 26 Sep 2025 21:34:02 -0300
Subject: [PATCH 06/17] [feat](pnalvarez): removed radio box and finishing book
---
lib/core/designsystem/atoms/colors.dart | 1 +
.../molecules/buttons/secondary_button.dart | 68 +-
.../molecules/indicators/radio_box.dart | 32 +
.../molecules/inputfields/input_field.dart | 43 +-
lib/core/utils/strings.dart | 2 +
.../startreading/start_reading_event.dart | 2 +
.../startreading/start_reading_page.dart | 24 +-
.../start_reading_view_model.dart | 58 +-
macos/Podfile | 42 +
macos/Podfile.lock | 37 +
macos/Runner.xcodeproj/project.pbxproj | 801 ++++++++++++++++++
11 files changed, 1073 insertions(+), 37 deletions(-)
create mode 100644 lib/core/designsystem/molecules/indicators/radio_box.dart
create mode 100644 macos/Podfile
create mode 100644 macos/Podfile.lock
create mode 100644 macos/Runner.xcodeproj/project.pbxproj
diff --git a/lib/core/designsystem/atoms/colors.dart b/lib/core/designsystem/atoms/colors.dart
index ebbd25a..d620da6 100644
--- a/lib/core/designsystem/atoms/colors.dart
+++ b/lib/core/designsystem/atoms/colors.dart
@@ -6,3 +6,4 @@ const onBorder = Color(0xFF0D5EAF);
const onBackground = Color(0xFFC6E0FA);
const background = Color(0xFF3D67FF);
const error = Color.fromARGB(255, 196, 8, 8);
+const primaryText = Color(0xFF110000);
diff --git a/lib/core/designsystem/molecules/buttons/secondary_button.dart b/lib/core/designsystem/molecules/buttons/secondary_button.dart
index 7937c68..735b4aa 100644
--- a/lib/core/designsystem/molecules/buttons/secondary_button.dart
+++ b/lib/core/designsystem/molecules/buttons/secondary_button.dart
@@ -1 +1,67 @@
-g
\ No newline at end of file
+import 'package:flutter/material.dart';
+import 'package:mibook/core/designsystem/atoms/colors.dart';
+
+class SecondaryButton extends StatelessWidget {
+ final String title;
+ final bool isEnabled;
+ final bool isExpanded;
+ final bool isLoading;
+ final VoidCallback onPressed;
+
+ const SecondaryButton({
+ super.key,
+ required this.title,
+ this.isEnabled = true,
+ this.isExpanded = false,
+ this.isLoading = false,
+ required this.onPressed,
+ });
+
+ @override
+ Widget build(BuildContext context) {
+ final button = OutlinedButton(
+ onPressed: isEnabled ? onPressed : null,
+ style: OutlinedButton.styleFrom(
+ side: BorderSide(
+ color: isEnabled ? primary : disabled,
+ width: 1.0,
+ ),
+ foregroundColor: isEnabled ? primary : disabled,
+ padding: const EdgeInsets.symmetric(
+ horizontal: 16,
+ vertical: 12,
+ ),
+ textStyle: const TextStyle(
+ fontSize: 16,
+ fontWeight: FontWeight.w500,
+ ),
+ shape: RoundedRectangleBorder(
+ borderRadius: BorderRadius.circular(16),
+ ),
+ minimumSize: const Size(80, 48),
+ ),
+ child: isLoading
+ ? SizedBox(
+ width: 20,
+ height: 20,
+ child: CircularProgressIndicator(
+ color: Colors.white,
+ ),
+ )
+ : Text(title),
+ );
+
+ if (isExpanded) {
+ return SizedBox(
+ height: 48,
+ width: double.infinity,
+ child: button,
+ );
+ } else {
+ return SizedBox(
+ height: 48,
+ child: button,
+ );
+ }
+ }
+}
diff --git a/lib/core/designsystem/molecules/indicators/radio_box.dart b/lib/core/designsystem/molecules/indicators/radio_box.dart
new file mode 100644
index 0000000..0ab17a3
--- /dev/null
+++ b/lib/core/designsystem/molecules/indicators/radio_box.dart
@@ -0,0 +1,32 @@
+import 'package:flutter/material.dart';
+import 'package:mibook/core/designsystem/atoms/colors.dart';
+
+class RadioBox extends StatelessWidget {
+ final bool value;
+ final ValueChanged onChanged;
+ final String label;
+
+ const RadioBox({
+ super.key,
+ required this.value,
+ required this.onChanged,
+ required this.label,
+ });
+
+ @override
+ Widget build(BuildContext context) {
+ return InkWell(
+ onTap: () => onChanged(!value),
+ child: Row(
+ children: [
+ Checkbox(
+ fillColor: WidgetStateProperty.all(primary),
+ value: value,
+ onChanged: onChanged,
+ ),
+ Text(label),
+ ],
+ ),
+ );
+ }
+}
diff --git a/lib/core/designsystem/molecules/inputfields/input_field.dart b/lib/core/designsystem/molecules/inputfields/input_field.dart
index 54edef9..f8ffc4e 100644
--- a/lib/core/designsystem/molecules/inputfields/input_field.dart
+++ b/lib/core/designsystem/molecules/inputfields/input_field.dart
@@ -30,27 +30,34 @@ class InputField extends StatelessWidget {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
- if (label.isNotEmpty) Text(label),
+ if (label.isNotEmpty)
+ Text(
+ label,
+ style: TextStyle(
+ color: errorMessage == null ? primaryText : error,
+ ),
+ ),
const SizedBox(height: 8),
- SizedBox(
- height: 48,
- child: TextField(
- keyboardType: keyboardType,
- decoration: InputDecoration(
- border: OutlineInputBorder(),
- focusedBorder: OutlineInputBorder(
- borderSide: BorderSide(color: onBorder),
- ),
- errorBorder: OutlineInputBorder(
- borderSide: BorderSide(color: error),
- ),
- hintText: placeholder,
- suffixText: suffixText,
- prefixText: prefixText,
- errorText: errorMessage,
+ TextField(
+ keyboardType: keyboardType,
+ decoration: InputDecoration(
+ border: OutlineInputBorder(),
+ focusedBorder: OutlineInputBorder(
+ borderSide: BorderSide(color: onBorder),
+ ),
+ errorBorder: OutlineInputBorder(
+ borderSide: BorderSide(color: error),
+ ),
+ hintText: placeholder,
+ suffixText: suffixText,
+ prefixText: prefixText,
+ errorText: errorMessage,
+ contentPadding: const EdgeInsets.symmetric(
+ vertical: 16, // controls height
+ horizontal: 16,
),
- onChanged: onChanged,
),
+ onChanged: onChanged,
),
],
);
diff --git a/lib/core/utils/strings.dart b/lib/core/utils/strings.dart
index 24cf561..0be5186 100644
--- a/lib/core/utils/strings.dart
+++ b/lib/core/utils/strings.dart
@@ -9,3 +9,5 @@ const progress = 'Progress';
const confirm = 'Confirm';
const progressErrorMessage = 'You can t enter more pages than the book maximum';
const zero = '0';
+const finishedBook = 'I finished this book';
+const finishBook = 'Finish Book';
diff --git a/lib/layers/presentation/screens/startreading/start_reading_event.dart b/lib/layers/presentation/screens/startreading/start_reading_event.dart
index a5a0be4..013f7cb 100644
--- a/lib/layers/presentation/screens/startreading/start_reading_event.dart
+++ b/lib/layers/presentation/screens/startreading/start_reading_event.dart
@@ -6,3 +6,5 @@ class DidEditProgressEvent extends StartReadingEvent {
}
class DidClickConfirmEvent extends StartReadingEvent {}
+
+class DidClickFinishBookEvent extends StartReadingEvent {}
diff --git a/lib/layers/presentation/screens/startreading/start_reading_page.dart b/lib/layers/presentation/screens/startreading/start_reading_page.dart
index 93cb2d8..42cc59e 100644
--- a/lib/layers/presentation/screens/startreading/start_reading_page.dart
+++ b/lib/layers/presentation/screens/startreading/start_reading_page.dart
@@ -3,7 +3,9 @@ import 'package:cached_network_image/cached_network_image.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:mibook/core/designsystem/molecules/buttons/primary_button.dart';
+import 'package:mibook/core/designsystem/molecules/buttons/secondary_button.dart';
import 'package:mibook/core/designsystem/molecules/indicators/progress_stepper.dart';
+import 'package:mibook/core/designsystem/molecules/indicators/radio_box.dart';
import 'package:mibook/core/designsystem/molecules/inputfields/input_field.dart';
import 'package:mibook/core/designsystem/organisms/app_nav_bar.dart';
import 'package:mibook/core/di/di.dart';
@@ -43,6 +45,9 @@ class _StartReadingScaffold extends StatelessWidget {
body: BlocBuilder(
builder: (context, state) {
final viewModel = context.read();
+ if (state.shouldNavigateBack) {
+ context.router.maybePop();
+ }
return _StartReadingContent(
book: viewModel.book,
progress: state.progress,
@@ -55,6 +60,9 @@ class _StartReadingScaffold extends StatelessWidget {
onClickStartReading: () {
viewModel.add(DidClickConfirmEvent());
},
+ onClickFinishBook: () {
+ viewModel.add(DidClickFinishBookEvent());
+ },
);
},
),
@@ -69,6 +77,7 @@ class _StartReadingContent extends StatelessWidget {
final TextEditingController _controller = TextEditingController();
final Function(String) onChangeProgressText;
final Function onClickStartReading;
+ final Function onClickFinishBook;
_StartReadingContent({
required this.book,
@@ -76,6 +85,7 @@ class _StartReadingContent extends StatelessWidget {
required this.errorMessage,
required this.onChangeProgressText,
required this.onClickStartReading,
+ required this.onClickFinishBook,
});
@override
@@ -108,11 +118,23 @@ class _StartReadingContent extends StatelessWidget {
),
const SizedBox(height: 24),
ProgressStepper(progress: progress),
+ const SizedBox(
+ height: 24,
+ ),
const Spacer(),
PrimaryButton(
title: strings.confirm,
+ isEnabled: errorMessage == null,
+ isExpanded: true,
+ onPressed: onClickStartReading as VoidCallback,
+ ),
+ const SizedBox(
+ height: 16,
+ ),
+ SecondaryButton(
+ title: finishBook,
isExpanded: true,
- onPressed: () {},
+ onPressed: onClickFinishBook as VoidCallback,
),
],
),
diff --git a/lib/layers/presentation/screens/startreading/start_reading_view_model.dart b/lib/layers/presentation/screens/startreading/start_reading_view_model.dart
index 54cae43..c8f1d7e 100644
--- a/lib/layers/presentation/screens/startreading/start_reading_view_model.dart
+++ b/lib/layers/presentation/screens/startreading/start_reading_view_model.dart
@@ -1,6 +1,7 @@
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:injectable/injectable.dart';
import 'package:mibook/core/utils/strings.dart';
+import 'package:mibook/layers/domain/models/reading_domain.dart';
import 'package:mibook/layers/domain/usecases/start_reading.dart';
import 'package:mibook/layers/presentation/screens/bookdetails/book_details_ui.dart';
import 'package:mibook/layers/presentation/screens/startreading/start_reading_event.dart';
@@ -15,25 +16,48 @@ class StartReadingViewModel extends Bloc {
this._startReading,
@factoryParam this.book,
) : super(StartReadingState.initial) {
+ // Handle DidEditProgress Event
on((event, emit) {
- final progress = event.progress.toDouble() / book.pageCount.toDouble();
- if (progress > 1.0) {
- emit(
- state.copyWith(
- inputErrorMessage: progressErrorMessage,
- ),
- );
- } else {
- emit(
- state.copyWith(
- progress: progress,
- inputErrorMessage: null,
- ),
- );
- }
+ emit(_didEditProgress(event));
});
- on((event, emit) {
- // TO DO
+ // Handle DidClickConfirm Event
+ on((event, emit) async {
+ final response = await _didClickConfirm();
+ emit(response);
});
+ // Handle DidFinishBook Event
+ on((event, emit) async {
+ final response = await _didClickFinishBook();
+ emit(response);
+ });
+ }
+
+ // Event Handler to DidEditProgressEvent
+ StartReadingState _didEditProgress(DidEditProgressEvent event) {
+ final progress = event.progress.toDouble() / book.pageCount.toDouble();
+ if (progress > 1.0) {
+ return state.copyWith(
+ inputErrorMessage: progressErrorMessage,
+ );
+ } else {
+ return state.copyWith(
+ progress: progress,
+ inputErrorMessage: null,
+ );
+ }
+ }
+
+ // Event Handler to DidClickConfirm Event
+ Future _didClickConfirm() async =>
+ _handleStartReading(state.progress);
+
+ // Event Handler to DidClickFinishBook Event
+ Future _didClickFinishBook() async =>
+ _handleStartReading(1.0);
+
+ Future _handleStartReading(double progress) async {
+ final reading = ReadingDomain(bookId: book.id, progress: progress);
+ await _startReading(reading: reading);
+ return state.copyWith(shouldNavigateBack: true);
}
}
diff --git a/macos/Podfile b/macos/Podfile
new file mode 100644
index 0000000..ff5ddb3
--- /dev/null
+++ b/macos/Podfile
@@ -0,0 +1,42 @@
+platform :osx, '10.15'
+
+# CocoaPods analytics sends network stats synchronously affecting flutter build latency.
+ENV['COCOAPODS_DISABLE_STATS'] = 'true'
+
+project 'Runner', {
+ 'Debug' => :debug,
+ 'Profile' => :release,
+ 'Release' => :release,
+}
+
+def flutter_root
+ generated_xcode_build_settings_path = File.expand_path(File.join('..', 'Flutter', 'ephemeral', 'Flutter-Generated.xcconfig'), __FILE__)
+ unless File.exist?(generated_xcode_build_settings_path)
+ raise "#{generated_xcode_build_settings_path} must exist. If you're running pod install manually, make sure \"flutter pub get\" is executed first"
+ end
+
+ File.foreach(generated_xcode_build_settings_path) do |line|
+ matches = line.match(/FLUTTER_ROOT\=(.*)/)
+ return matches[1].strip if matches
+ end
+ raise "FLUTTER_ROOT not found in #{generated_xcode_build_settings_path}. Try deleting Flutter-Generated.xcconfig, then run \"flutter pub get\""
+end
+
+require File.expand_path(File.join('packages', 'flutter_tools', 'bin', 'podhelper'), flutter_root)
+
+flutter_macos_podfile_setup
+
+target 'Runner' do
+ use_frameworks!
+
+ flutter_install_all_macos_pods File.dirname(File.realpath(__FILE__))
+ target 'RunnerTests' do
+ inherit! :search_paths
+ end
+end
+
+post_install do |installer|
+ installer.pods_project.targets.each do |target|
+ flutter_additional_macos_build_settings(target)
+ end
+end
diff --git a/macos/Podfile.lock b/macos/Podfile.lock
new file mode 100644
index 0000000..8b72aeb
--- /dev/null
+++ b/macos/Podfile.lock
@@ -0,0 +1,37 @@
+PODS:
+ - FlutterMacOS (1.0.0)
+ - path_provider_foundation (0.0.1):
+ - Flutter
+ - FlutterMacOS
+ - shared_preferences_foundation (0.0.1):
+ - Flutter
+ - FlutterMacOS
+ - sqflite_darwin (0.0.4):
+ - Flutter
+ - FlutterMacOS
+
+DEPENDENCIES:
+ - FlutterMacOS (from `Flutter/ephemeral`)
+ - path_provider_foundation (from `Flutter/ephemeral/.symlinks/plugins/path_provider_foundation/darwin`)
+ - shared_preferences_foundation (from `Flutter/ephemeral/.symlinks/plugins/shared_preferences_foundation/darwin`)
+ - sqflite_darwin (from `Flutter/ephemeral/.symlinks/plugins/sqflite_darwin/darwin`)
+
+EXTERNAL SOURCES:
+ FlutterMacOS:
+ :path: Flutter/ephemeral
+ path_provider_foundation:
+ :path: Flutter/ephemeral/.symlinks/plugins/path_provider_foundation/darwin
+ shared_preferences_foundation:
+ :path: Flutter/ephemeral/.symlinks/plugins/shared_preferences_foundation/darwin
+ sqflite_darwin:
+ :path: Flutter/ephemeral/.symlinks/plugins/sqflite_darwin/darwin
+
+SPEC CHECKSUMS:
+ FlutterMacOS: d0db08ddef1a9af05a5ec4b724367152bb0500b1
+ path_provider_foundation: 2b6b4c569c0fb62ec74538f866245ac84301af46
+ shared_preferences_foundation: fcdcbc04712aee1108ac7fda236f363274528f78
+ sqflite_darwin: 5a7236e3b501866c1c9befc6771dfd73ffb8702d
+
+PODFILE CHECKSUM: 54d867c82ac51cbd61b565781b9fada492027009
+
+COCOAPODS: 1.15.2
diff --git a/macos/Runner.xcodeproj/project.pbxproj b/macos/Runner.xcodeproj/project.pbxproj
new file mode 100644
index 0000000..6e789d1
--- /dev/null
+++ b/macos/Runner.xcodeproj/project.pbxproj
@@ -0,0 +1,801 @@
+// !$*UTF8*$!
+{
+ archiveVersion = 1;
+ classes = {
+ };
+ objectVersion = 54;
+ objects = {
+
+/* Begin PBXAggregateTarget section */
+ 33CC111A2044C6BA0003C045 /* Flutter Assemble */ = {
+ isa = PBXAggregateTarget;
+ buildConfigurationList = 33CC111B2044C6BA0003C045 /* Build configuration list for PBXAggregateTarget "Flutter Assemble" */;
+ buildPhases = (
+ 33CC111E2044C6BF0003C045 /* ShellScript */,
+ );
+ dependencies = (
+ );
+ name = "Flutter Assemble";
+ productName = FLX;
+ };
+/* End PBXAggregateTarget section */
+
+/* Begin PBXBuildFile section */
+ 331C80D8294CF71000263BE5 /* RunnerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 331C80D7294CF71000263BE5 /* RunnerTests.swift */; };
+ 335BBD1B22A9A15E00E9071D /* GeneratedPluginRegistrant.swift in Sources */ = {isa = PBXBuildFile; fileRef = 335BBD1A22A9A15E00E9071D /* GeneratedPluginRegistrant.swift */; };
+ 33CC10F12044A3C60003C045 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 33CC10F02044A3C60003C045 /* AppDelegate.swift */; };
+ 33CC10F32044A3C60003C045 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 33CC10F22044A3C60003C045 /* Assets.xcassets */; };
+ 33CC10F62044A3C60003C045 /* MainMenu.xib in Resources */ = {isa = PBXBuildFile; fileRef = 33CC10F42044A3C60003C045 /* MainMenu.xib */; };
+ 33CC11132044BFA00003C045 /* MainFlutterWindow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 33CC11122044BFA00003C045 /* MainFlutterWindow.swift */; };
+ 86598D225E2B3C9C652FB981 /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = A1C44362B8606DEA4742800A /* Pods_Runner.framework */; };
+ C5DD5E3E437B30F2BB9B6CBD /* Pods_RunnerTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 7A51812E6CA24747E4C5F14F /* Pods_RunnerTests.framework */; };
+/* End PBXBuildFile section */
+
+/* Begin PBXContainerItemProxy section */
+ 331C80D9294CF71000263BE5 /* PBXContainerItemProxy */ = {
+ isa = PBXContainerItemProxy;
+ containerPortal = 33CC10E52044A3C60003C045 /* Project object */;
+ proxyType = 1;
+ remoteGlobalIDString = 33CC10EC2044A3C60003C045;
+ remoteInfo = Runner;
+ };
+ 33CC111F2044C79F0003C045 /* PBXContainerItemProxy */ = {
+ isa = PBXContainerItemProxy;
+ containerPortal = 33CC10E52044A3C60003C045 /* Project object */;
+ proxyType = 1;
+ remoteGlobalIDString = 33CC111A2044C6BA0003C045;
+ remoteInfo = FLX;
+ };
+/* End PBXContainerItemProxy section */
+
+/* Begin PBXCopyFilesBuildPhase section */
+ 33CC110E2044A8840003C045 /* Bundle Framework */ = {
+ isa = PBXCopyFilesBuildPhase;
+ buildActionMask = 2147483647;
+ dstPath = "";
+ dstSubfolderSpec = 10;
+ files = (
+ );
+ name = "Bundle Framework";
+ runOnlyForDeploymentPostprocessing = 0;
+ };
+/* End PBXCopyFilesBuildPhase section */
+
+/* Begin PBXFileReference section */
+ 331C80D5294CF71000263BE5 /* RunnerTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = RunnerTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
+ 331C80D7294CF71000263BE5 /* RunnerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RunnerTests.swift; sourceTree = ""; };
+ 333000ED22D3DE5D00554162 /* Warnings.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Warnings.xcconfig; sourceTree = ""; };
+ 335BBD1A22A9A15E00E9071D /* GeneratedPluginRegistrant.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GeneratedPluginRegistrant.swift; sourceTree = ""; };
+ 33CC10ED2044A3C60003C045 /* mibook.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = mibook.app; sourceTree = BUILT_PRODUCTS_DIR; };
+ 33CC10F02044A3C60003C045 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; };
+ 33CC10F22044A3C60003C045 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; name = Assets.xcassets; path = Runner/Assets.xcassets; sourceTree = ""; };
+ 33CC10F52044A3C60003C045 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/MainMenu.xib; sourceTree = ""; };
+ 33CC10F72044A3C60003C045 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; name = Info.plist; path = Runner/Info.plist; sourceTree = ""; };
+ 33CC11122044BFA00003C045 /* MainFlutterWindow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MainFlutterWindow.swift; sourceTree = ""; };
+ 33CEB47222A05771004F2AC0 /* Flutter-Debug.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = "Flutter-Debug.xcconfig"; sourceTree = ""; };
+ 33CEB47422A05771004F2AC0 /* Flutter-Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = "Flutter-Release.xcconfig"; sourceTree = ""; };
+ 33CEB47722A0578A004F2AC0 /* Flutter-Generated.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = "Flutter-Generated.xcconfig"; path = "ephemeral/Flutter-Generated.xcconfig"; sourceTree = ""; };
+ 33E51913231747F40026EE4D /* DebugProfile.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = DebugProfile.entitlements; sourceTree = ""; };
+ 33E51914231749380026EE4D /* Release.entitlements */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.entitlements; path = Release.entitlements; sourceTree = ""; };
+ 33E5194F232828860026EE4D /* AppInfo.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = AppInfo.xcconfig; sourceTree = ""; };
+ 61747C17440559CA3799386A /* Pods-RunnerTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.debug.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.debug.xcconfig"; sourceTree = ""; };
+ 7A51812E6CA24747E4C5F14F /* Pods_RunnerTests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_RunnerTests.framework; sourceTree = BUILT_PRODUCTS_DIR; };
+ 7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Release.xcconfig; sourceTree = ""; };
+ 9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; path = Debug.xcconfig; sourceTree = ""; };
+ 9F8C8FE9A8D09F432132BDFB /* Pods-RunnerTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.release.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.release.xcconfig"; sourceTree = ""; };
+ A1C44362B8606DEA4742800A /* Pods_Runner.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Runner.framework; sourceTree = BUILT_PRODUCTS_DIR; };
+ A87E62B5834E483BD7947E1F /* Pods-RunnerTests.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.profile.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.profile.xcconfig"; sourceTree = ""; };
+ C7AA65BE03122A7CE76B0214 /* 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 = ""; };
+ CBE43B9703A2C6829E30C67E /* 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 = ""; };
+ FDC70B5307628862A4C1C6D0 /* 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 = ""; };
+/* End PBXFileReference section */
+
+/* Begin PBXFrameworksBuildPhase section */
+ 331C80D2294CF70F00263BE5 /* Frameworks */ = {
+ isa = PBXFrameworksBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ C5DD5E3E437B30F2BB9B6CBD /* Pods_RunnerTests.framework in Frameworks */,
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
+ 33CC10EA2044A3C60003C045 /* Frameworks */ = {
+ isa = PBXFrameworksBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ 86598D225E2B3C9C652FB981 /* Pods_Runner.framework in Frameworks */,
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
+/* End PBXFrameworksBuildPhase section */
+
+/* Begin PBXGroup section */
+ 331C80D6294CF71000263BE5 /* RunnerTests */ = {
+ isa = PBXGroup;
+ children = (
+ 331C80D7294CF71000263BE5 /* RunnerTests.swift */,
+ );
+ path = RunnerTests;
+ sourceTree = "";
+ };
+ 33BA886A226E78AF003329D5 /* Configs */ = {
+ isa = PBXGroup;
+ children = (
+ 33E5194F232828860026EE4D /* AppInfo.xcconfig */,
+ 9740EEB21CF90195004384FC /* Debug.xcconfig */,
+ 7AFA3C8E1D35360C0083082E /* Release.xcconfig */,
+ 333000ED22D3DE5D00554162 /* Warnings.xcconfig */,
+ );
+ path = Configs;
+ sourceTree = "";
+ };
+ 33CC10E42044A3C60003C045 = {
+ isa = PBXGroup;
+ children = (
+ 33FAB671232836740065AC1E /* Runner */,
+ 33CEB47122A05771004F2AC0 /* Flutter */,
+ 331C80D6294CF71000263BE5 /* RunnerTests */,
+ 33CC10EE2044A3C60003C045 /* Products */,
+ D73912EC22F37F3D000D13A0 /* Frameworks */,
+ 3FE533F525665BFD9FA8A471 /* Pods */,
+ );
+ sourceTree = "";
+ };
+ 33CC10EE2044A3C60003C045 /* Products */ = {
+ isa = PBXGroup;
+ children = (
+ 33CC10ED2044A3C60003C045 /* mibook.app */,
+ 331C80D5294CF71000263BE5 /* RunnerTests.xctest */,
+ );
+ name = Products;
+ sourceTree = "";
+ };
+ 33CC11242044D66E0003C045 /* Resources */ = {
+ isa = PBXGroup;
+ children = (
+ 33CC10F22044A3C60003C045 /* Assets.xcassets */,
+ 33CC10F42044A3C60003C045 /* MainMenu.xib */,
+ 33CC10F72044A3C60003C045 /* Info.plist */,
+ );
+ name = Resources;
+ path = ..;
+ sourceTree = "";
+ };
+ 33CEB47122A05771004F2AC0 /* Flutter */ = {
+ isa = PBXGroup;
+ children = (
+ 335BBD1A22A9A15E00E9071D /* GeneratedPluginRegistrant.swift */,
+ 33CEB47222A05771004F2AC0 /* Flutter-Debug.xcconfig */,
+ 33CEB47422A05771004F2AC0 /* Flutter-Release.xcconfig */,
+ 33CEB47722A0578A004F2AC0 /* Flutter-Generated.xcconfig */,
+ );
+ path = Flutter;
+ sourceTree = "";
+ };
+ 33FAB671232836740065AC1E /* Runner */ = {
+ isa = PBXGroup;
+ children = (
+ 33CC10F02044A3C60003C045 /* AppDelegate.swift */,
+ 33CC11122044BFA00003C045 /* MainFlutterWindow.swift */,
+ 33E51913231747F40026EE4D /* DebugProfile.entitlements */,
+ 33E51914231749380026EE4D /* Release.entitlements */,
+ 33CC11242044D66E0003C045 /* Resources */,
+ 33BA886A226E78AF003329D5 /* Configs */,
+ );
+ path = Runner;
+ sourceTree = "";
+ };
+ 3FE533F525665BFD9FA8A471 /* Pods */ = {
+ isa = PBXGroup;
+ children = (
+ CBE43B9703A2C6829E30C67E /* Pods-Runner.debug.xcconfig */,
+ FDC70B5307628862A4C1C6D0 /* Pods-Runner.release.xcconfig */,
+ C7AA65BE03122A7CE76B0214 /* Pods-Runner.profile.xcconfig */,
+ 61747C17440559CA3799386A /* Pods-RunnerTests.debug.xcconfig */,
+ 9F8C8FE9A8D09F432132BDFB /* Pods-RunnerTests.release.xcconfig */,
+ A87E62B5834E483BD7947E1F /* Pods-RunnerTests.profile.xcconfig */,
+ );
+ name = Pods;
+ path = Pods;
+ sourceTree = "";
+ };
+ D73912EC22F37F3D000D13A0 /* Frameworks */ = {
+ isa = PBXGroup;
+ children = (
+ A1C44362B8606DEA4742800A /* Pods_Runner.framework */,
+ 7A51812E6CA24747E4C5F14F /* Pods_RunnerTests.framework */,
+ );
+ name = Frameworks;
+ sourceTree = "";
+ };
+/* End PBXGroup section */
+
+/* Begin PBXNativeTarget section */
+ 331C80D4294CF70F00263BE5 /* RunnerTests */ = {
+ isa = PBXNativeTarget;
+ buildConfigurationList = 331C80DE294CF71000263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */;
+ buildPhases = (
+ 7512FB06EA385FF9B3AB8DB2 /* [CP] Check Pods Manifest.lock */,
+ 331C80D1294CF70F00263BE5 /* Sources */,
+ 331C80D2294CF70F00263BE5 /* Frameworks */,
+ 331C80D3294CF70F00263BE5 /* Resources */,
+ );
+ buildRules = (
+ );
+ dependencies = (
+ 331C80DA294CF71000263BE5 /* PBXTargetDependency */,
+ );
+ name = RunnerTests;
+ productName = RunnerTests;
+ productReference = 331C80D5294CF71000263BE5 /* RunnerTests.xctest */;
+ productType = "com.apple.product-type.bundle.unit-test";
+ };
+ 33CC10EC2044A3C60003C045 /* Runner */ = {
+ isa = PBXNativeTarget;
+ buildConfigurationList = 33CC10FB2044A3C60003C045 /* Build configuration list for PBXNativeTarget "Runner" */;
+ buildPhases = (
+ DC4E08B37EDF9B9FDC1FECDD /* [CP] Check Pods Manifest.lock */,
+ 33CC10E92044A3C60003C045 /* Sources */,
+ 33CC10EA2044A3C60003C045 /* Frameworks */,
+ 33CC10EB2044A3C60003C045 /* Resources */,
+ 33CC110E2044A8840003C045 /* Bundle Framework */,
+ 3399D490228B24CF009A79C7 /* ShellScript */,
+ ECC94004D71F30E414DB5311 /* [CP] Embed Pods Frameworks */,
+ );
+ buildRules = (
+ );
+ dependencies = (
+ 33CC11202044C79F0003C045 /* PBXTargetDependency */,
+ );
+ name = Runner;
+ productName = Runner;
+ productReference = 33CC10ED2044A3C60003C045 /* mibook.app */;
+ productType = "com.apple.product-type.application";
+ };
+/* End PBXNativeTarget section */
+
+/* Begin PBXProject section */
+ 33CC10E52044A3C60003C045 /* Project object */ = {
+ isa = PBXProject;
+ attributes = {
+ BuildIndependentTargetsInParallel = YES;
+ LastSwiftUpdateCheck = 0920;
+ LastUpgradeCheck = 1510;
+ ORGANIZATIONNAME = "";
+ TargetAttributes = {
+ 331C80D4294CF70F00263BE5 = {
+ CreatedOnToolsVersion = 14.0;
+ TestTargetID = 33CC10EC2044A3C60003C045;
+ };
+ 33CC10EC2044A3C60003C045 = {
+ CreatedOnToolsVersion = 9.2;
+ LastSwiftMigration = 1100;
+ ProvisioningStyle = Automatic;
+ SystemCapabilities = {
+ com.apple.Sandbox = {
+ enabled = 1;
+ };
+ };
+ };
+ 33CC111A2044C6BA0003C045 = {
+ CreatedOnToolsVersion = 9.2;
+ ProvisioningStyle = Manual;
+ };
+ };
+ };
+ buildConfigurationList = 33CC10E82044A3C60003C045 /* Build configuration list for PBXProject "Runner" */;
+ compatibilityVersion = "Xcode 9.3";
+ developmentRegion = en;
+ hasScannedForEncodings = 0;
+ knownRegions = (
+ en,
+ Base,
+ );
+ mainGroup = 33CC10E42044A3C60003C045;
+ productRefGroup = 33CC10EE2044A3C60003C045 /* Products */;
+ projectDirPath = "";
+ projectRoot = "";
+ targets = (
+ 33CC10EC2044A3C60003C045 /* Runner */,
+ 331C80D4294CF70F00263BE5 /* RunnerTests */,
+ 33CC111A2044C6BA0003C045 /* Flutter Assemble */,
+ );
+ };
+/* End PBXProject section */
+
+/* Begin PBXResourcesBuildPhase section */
+ 331C80D3294CF70F00263BE5 /* Resources */ = {
+ isa = PBXResourcesBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
+ 33CC10EB2044A3C60003C045 /* Resources */ = {
+ isa = PBXResourcesBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ 33CC10F32044A3C60003C045 /* Assets.xcassets in Resources */,
+ 33CC10F62044A3C60003C045 /* MainMenu.xib in Resources */,
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
+/* End PBXResourcesBuildPhase section */
+
+/* Begin PBXShellScriptBuildPhase section */
+ 3399D490228B24CF009A79C7 /* ShellScript */ = {
+ isa = PBXShellScriptBuildPhase;
+ alwaysOutOfDate = 1;
+ buildActionMask = 2147483647;
+ files = (
+ );
+ inputFileListPaths = (
+ );
+ inputPaths = (
+ );
+ outputFileListPaths = (
+ );
+ outputPaths = (
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ shellPath = /bin/sh;
+ shellScript = "echo \"$PRODUCT_NAME.app\" > \"$PROJECT_DIR\"/Flutter/ephemeral/.app_filename && \"$FLUTTER_ROOT\"/packages/flutter_tools/bin/macos_assemble.sh embed\n";
+ };
+ 33CC111E2044C6BF0003C045 /* ShellScript */ = {
+ isa = PBXShellScriptBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ );
+ inputFileListPaths = (
+ Flutter/ephemeral/FlutterInputs.xcfilelist,
+ );
+ inputPaths = (
+ Flutter/ephemeral/tripwire,
+ );
+ outputFileListPaths = (
+ Flutter/ephemeral/FlutterOutputs.xcfilelist,
+ );
+ outputPaths = (
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ shellPath = /bin/sh;
+ shellScript = "\"$FLUTTER_ROOT\"/packages/flutter_tools/bin/macos_assemble.sh && touch Flutter/ephemeral/tripwire";
+ };
+ 7512FB06EA385FF9B3AB8DB2 /* [CP] Check Pods Manifest.lock */ = {
+ isa = PBXShellScriptBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ );
+ inputFileListPaths = (
+ );
+ inputPaths = (
+ "${PODS_PODFILE_DIR_PATH}/Podfile.lock",
+ "${PODS_ROOT}/Manifest.lock",
+ );
+ name = "[CP] Check Pods Manifest.lock";
+ outputFileListPaths = (
+ );
+ outputPaths = (
+ "$(DERIVED_FILE_DIR)/Pods-RunnerTests-checkManifestLockResult.txt",
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ shellPath = /bin/sh;
+ 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";
+ showEnvVarsInLog = 0;
+ };
+ DC4E08B37EDF9B9FDC1FECDD /* [CP] Check Pods Manifest.lock */ = {
+ isa = PBXShellScriptBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ );
+ inputFileListPaths = (
+ );
+ inputPaths = (
+ "${PODS_PODFILE_DIR_PATH}/Podfile.lock",
+ "${PODS_ROOT}/Manifest.lock",
+ );
+ name = "[CP] Check Pods Manifest.lock";
+ outputFileListPaths = (
+ );
+ outputPaths = (
+ "$(DERIVED_FILE_DIR)/Pods-Runner-checkManifestLockResult.txt",
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ shellPath = /bin/sh;
+ 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";
+ showEnvVarsInLog = 0;
+ };
+ ECC94004D71F30E414DB5311 /* [CP] Embed Pods Frameworks */ = {
+ isa = PBXShellScriptBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ );
+ inputFileListPaths = (
+ "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-input-files.xcfilelist",
+ );
+ name = "[CP] Embed Pods Frameworks";
+ outputFileListPaths = (
+ "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-output-files.xcfilelist",
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ shellPath = /bin/sh;
+ shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh\"\n";
+ showEnvVarsInLog = 0;
+ };
+/* End PBXShellScriptBuildPhase section */
+
+/* Begin PBXSourcesBuildPhase section */
+ 331C80D1294CF70F00263BE5 /* Sources */ = {
+ isa = PBXSourcesBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ 331C80D8294CF71000263BE5 /* RunnerTests.swift in Sources */,
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
+ 33CC10E92044A3C60003C045 /* Sources */ = {
+ isa = PBXSourcesBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ 33CC11132044BFA00003C045 /* MainFlutterWindow.swift in Sources */,
+ 33CC10F12044A3C60003C045 /* AppDelegate.swift in Sources */,
+ 335BBD1B22A9A15E00E9071D /* GeneratedPluginRegistrant.swift in Sources */,
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
+/* End PBXSourcesBuildPhase section */
+
+/* Begin PBXTargetDependency section */
+ 331C80DA294CF71000263BE5 /* PBXTargetDependency */ = {
+ isa = PBXTargetDependency;
+ target = 33CC10EC2044A3C60003C045 /* Runner */;
+ targetProxy = 331C80D9294CF71000263BE5 /* PBXContainerItemProxy */;
+ };
+ 33CC11202044C79F0003C045 /* PBXTargetDependency */ = {
+ isa = PBXTargetDependency;
+ target = 33CC111A2044C6BA0003C045 /* Flutter Assemble */;
+ targetProxy = 33CC111F2044C79F0003C045 /* PBXContainerItemProxy */;
+ };
+/* End PBXTargetDependency section */
+
+/* Begin PBXVariantGroup section */
+ 33CC10F42044A3C60003C045 /* MainMenu.xib */ = {
+ isa = PBXVariantGroup;
+ children = (
+ 33CC10F52044A3C60003C045 /* Base */,
+ );
+ name = MainMenu.xib;
+ path = Runner;
+ sourceTree = "";
+ };
+/* End PBXVariantGroup section */
+
+/* Begin XCBuildConfiguration section */
+ 331C80DB294CF71000263BE5 /* Debug */ = {
+ isa = XCBuildConfiguration;
+ baseConfigurationReference = 61747C17440559CA3799386A /* Pods-RunnerTests.debug.xcconfig */;
+ buildSettings = {
+ BUNDLE_LOADER = "$(TEST_HOST)";
+ CURRENT_PROJECT_VERSION = 1;
+ GENERATE_INFOPLIST_FILE = YES;
+ MARKETING_VERSION = 1.0;
+ PRODUCT_BUNDLE_IDENTIFIER = com.example.mibook.RunnerTests;
+ PRODUCT_NAME = "$(TARGET_NAME)";
+ SWIFT_VERSION = 5.0;
+ TEST_HOST = "$(BUILT_PRODUCTS_DIR)/mibook.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/mibook";
+ };
+ name = Debug;
+ };
+ 331C80DC294CF71000263BE5 /* Release */ = {
+ isa = XCBuildConfiguration;
+ baseConfigurationReference = 9F8C8FE9A8D09F432132BDFB /* Pods-RunnerTests.release.xcconfig */;
+ buildSettings = {
+ BUNDLE_LOADER = "$(TEST_HOST)";
+ CURRENT_PROJECT_VERSION = 1;
+ GENERATE_INFOPLIST_FILE = YES;
+ MARKETING_VERSION = 1.0;
+ PRODUCT_BUNDLE_IDENTIFIER = com.example.mibook.RunnerTests;
+ PRODUCT_NAME = "$(TARGET_NAME)";
+ SWIFT_VERSION = 5.0;
+ TEST_HOST = "$(BUILT_PRODUCTS_DIR)/mibook.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/mibook";
+ };
+ name = Release;
+ };
+ 331C80DD294CF71000263BE5 /* Profile */ = {
+ isa = XCBuildConfiguration;
+ baseConfigurationReference = A87E62B5834E483BD7947E1F /* Pods-RunnerTests.profile.xcconfig */;
+ buildSettings = {
+ BUNDLE_LOADER = "$(TEST_HOST)";
+ CURRENT_PROJECT_VERSION = 1;
+ GENERATE_INFOPLIST_FILE = YES;
+ MARKETING_VERSION = 1.0;
+ PRODUCT_BUNDLE_IDENTIFIER = com.example.mibook.RunnerTests;
+ PRODUCT_NAME = "$(TARGET_NAME)";
+ SWIFT_VERSION = 5.0;
+ TEST_HOST = "$(BUILT_PRODUCTS_DIR)/mibook.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/mibook";
+ };
+ name = Profile;
+ };
+ 338D0CE9231458BD00FA5F75 /* Profile */ = {
+ isa = XCBuildConfiguration;
+ baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */;
+ buildSettings = {
+ ALWAYS_SEARCH_USER_PATHS = NO;
+ ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
+ CLANG_ANALYZER_NONNULL = YES;
+ CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
+ CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
+ CLANG_CXX_LIBRARY = "libc++";
+ CLANG_ENABLE_MODULES = YES;
+ CLANG_ENABLE_OBJC_ARC = YES;
+ CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
+ CLANG_WARN_BOOL_CONVERSION = YES;
+ CLANG_WARN_CONSTANT_CONVERSION = YES;
+ CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
+ CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
+ CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
+ CLANG_WARN_EMPTY_BODY = YES;
+ CLANG_WARN_ENUM_CONVERSION = YES;
+ CLANG_WARN_INFINITE_RECURSION = YES;
+ CLANG_WARN_INT_CONVERSION = YES;
+ CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
+ CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
+ CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
+ CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
+ CLANG_WARN_SUSPICIOUS_MOVE = YES;
+ CODE_SIGN_IDENTITY = "-";
+ COPY_PHASE_STRIP = NO;
+ DEAD_CODE_STRIPPING = YES;
+ DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
+ ENABLE_NS_ASSERTIONS = NO;
+ ENABLE_STRICT_OBJC_MSGSEND = YES;
+ ENABLE_USER_SCRIPT_SANDBOXING = NO;
+ GCC_C_LANGUAGE_STANDARD = gnu11;
+ GCC_NO_COMMON_BLOCKS = YES;
+ GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
+ GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
+ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
+ GCC_WARN_UNUSED_FUNCTION = YES;
+ GCC_WARN_UNUSED_VARIABLE = YES;
+ MACOSX_DEPLOYMENT_TARGET = 10.15;
+ MTL_ENABLE_DEBUG_INFO = NO;
+ SDKROOT = macosx;
+ SWIFT_COMPILATION_MODE = wholemodule;
+ SWIFT_OPTIMIZATION_LEVEL = "-O";
+ };
+ name = Profile;
+ };
+ 338D0CEA231458BD00FA5F75 /* Profile */ = {
+ isa = XCBuildConfiguration;
+ baseConfigurationReference = 33E5194F232828860026EE4D /* AppInfo.xcconfig */;
+ buildSettings = {
+ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
+ CLANG_ENABLE_MODULES = YES;
+ CODE_SIGN_ENTITLEMENTS = Runner/DebugProfile.entitlements;
+ CODE_SIGN_STYLE = Automatic;
+ COMBINE_HIDPI_IMAGES = YES;
+ INFOPLIST_FILE = Runner/Info.plist;
+ LD_RUNPATH_SEARCH_PATHS = (
+ "$(inherited)",
+ "@executable_path/../Frameworks",
+ );
+ PROVISIONING_PROFILE_SPECIFIER = "";
+ SWIFT_VERSION = 5.0;
+ };
+ name = Profile;
+ };
+ 338D0CEB231458BD00FA5F75 /* Profile */ = {
+ isa = XCBuildConfiguration;
+ buildSettings = {
+ CODE_SIGN_STYLE = Manual;
+ PRODUCT_NAME = "$(TARGET_NAME)";
+ };
+ name = Profile;
+ };
+ 33CC10F92044A3C60003C045 /* Debug */ = {
+ isa = XCBuildConfiguration;
+ baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */;
+ buildSettings = {
+ ALWAYS_SEARCH_USER_PATHS = NO;
+ ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
+ CLANG_ANALYZER_NONNULL = YES;
+ CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
+ CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
+ CLANG_CXX_LIBRARY = "libc++";
+ CLANG_ENABLE_MODULES = YES;
+ CLANG_ENABLE_OBJC_ARC = YES;
+ CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
+ CLANG_WARN_BOOL_CONVERSION = YES;
+ CLANG_WARN_CONSTANT_CONVERSION = YES;
+ CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
+ CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
+ CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
+ CLANG_WARN_EMPTY_BODY = YES;
+ CLANG_WARN_ENUM_CONVERSION = YES;
+ CLANG_WARN_INFINITE_RECURSION = YES;
+ CLANG_WARN_INT_CONVERSION = YES;
+ CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
+ CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
+ CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
+ CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
+ CLANG_WARN_SUSPICIOUS_MOVE = YES;
+ CODE_SIGN_IDENTITY = "-";
+ COPY_PHASE_STRIP = NO;
+ DEAD_CODE_STRIPPING = YES;
+ DEBUG_INFORMATION_FORMAT = dwarf;
+ ENABLE_STRICT_OBJC_MSGSEND = YES;
+ ENABLE_TESTABILITY = YES;
+ ENABLE_USER_SCRIPT_SANDBOXING = NO;
+ GCC_C_LANGUAGE_STANDARD = gnu11;
+ GCC_DYNAMIC_NO_PIC = NO;
+ GCC_NO_COMMON_BLOCKS = YES;
+ GCC_OPTIMIZATION_LEVEL = 0;
+ GCC_PREPROCESSOR_DEFINITIONS = (
+ "DEBUG=1",
+ "$(inherited)",
+ );
+ GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
+ GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
+ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
+ GCC_WARN_UNUSED_FUNCTION = YES;
+ GCC_WARN_UNUSED_VARIABLE = YES;
+ MACOSX_DEPLOYMENT_TARGET = 10.15;
+ MTL_ENABLE_DEBUG_INFO = YES;
+ ONLY_ACTIVE_ARCH = YES;
+ SDKROOT = macosx;
+ SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;
+ SWIFT_OPTIMIZATION_LEVEL = "-Onone";
+ };
+ name = Debug;
+ };
+ 33CC10FA2044A3C60003C045 /* Release */ = {
+ isa = XCBuildConfiguration;
+ baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */;
+ buildSettings = {
+ ALWAYS_SEARCH_USER_PATHS = NO;
+ ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
+ CLANG_ANALYZER_NONNULL = YES;
+ CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
+ CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
+ CLANG_CXX_LIBRARY = "libc++";
+ CLANG_ENABLE_MODULES = YES;
+ CLANG_ENABLE_OBJC_ARC = YES;
+ CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
+ CLANG_WARN_BOOL_CONVERSION = YES;
+ CLANG_WARN_CONSTANT_CONVERSION = YES;
+ CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
+ CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
+ CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
+ CLANG_WARN_EMPTY_BODY = YES;
+ CLANG_WARN_ENUM_CONVERSION = YES;
+ CLANG_WARN_INFINITE_RECURSION = YES;
+ CLANG_WARN_INT_CONVERSION = YES;
+ CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
+ CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
+ CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
+ CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
+ CLANG_WARN_SUSPICIOUS_MOVE = YES;
+ CODE_SIGN_IDENTITY = "-";
+ COPY_PHASE_STRIP = NO;
+ DEAD_CODE_STRIPPING = YES;
+ DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
+ ENABLE_NS_ASSERTIONS = NO;
+ ENABLE_STRICT_OBJC_MSGSEND = YES;
+ ENABLE_USER_SCRIPT_SANDBOXING = NO;
+ GCC_C_LANGUAGE_STANDARD = gnu11;
+ GCC_NO_COMMON_BLOCKS = YES;
+ GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
+ GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
+ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
+ GCC_WARN_UNUSED_FUNCTION = YES;
+ GCC_WARN_UNUSED_VARIABLE = YES;
+ MACOSX_DEPLOYMENT_TARGET = 10.15;
+ MTL_ENABLE_DEBUG_INFO = NO;
+ SDKROOT = macosx;
+ SWIFT_COMPILATION_MODE = wholemodule;
+ SWIFT_OPTIMIZATION_LEVEL = "-O";
+ };
+ name = Release;
+ };
+ 33CC10FC2044A3C60003C045 /* Debug */ = {
+ isa = XCBuildConfiguration;
+ baseConfigurationReference = 33E5194F232828860026EE4D /* AppInfo.xcconfig */;
+ buildSettings = {
+ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
+ CLANG_ENABLE_MODULES = YES;
+ CODE_SIGN_ENTITLEMENTS = Runner/DebugProfile.entitlements;
+ CODE_SIGN_STYLE = Automatic;
+ COMBINE_HIDPI_IMAGES = YES;
+ INFOPLIST_FILE = Runner/Info.plist;
+ LD_RUNPATH_SEARCH_PATHS = (
+ "$(inherited)",
+ "@executable_path/../Frameworks",
+ );
+ PROVISIONING_PROFILE_SPECIFIER = "";
+ SWIFT_OPTIMIZATION_LEVEL = "-Onone";
+ SWIFT_VERSION = 5.0;
+ };
+ name = Debug;
+ };
+ 33CC10FD2044A3C60003C045 /* Release */ = {
+ isa = XCBuildConfiguration;
+ baseConfigurationReference = 33E5194F232828860026EE4D /* AppInfo.xcconfig */;
+ buildSettings = {
+ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
+ CLANG_ENABLE_MODULES = YES;
+ CODE_SIGN_ENTITLEMENTS = Runner/Release.entitlements;
+ CODE_SIGN_STYLE = Automatic;
+ COMBINE_HIDPI_IMAGES = YES;
+ INFOPLIST_FILE = Runner/Info.plist;
+ LD_RUNPATH_SEARCH_PATHS = (
+ "$(inherited)",
+ "@executable_path/../Frameworks",
+ );
+ PROVISIONING_PROFILE_SPECIFIER = "";
+ SWIFT_VERSION = 5.0;
+ };
+ name = Release;
+ };
+ 33CC111C2044C6BA0003C045 /* Debug */ = {
+ isa = XCBuildConfiguration;
+ buildSettings = {
+ CODE_SIGN_STYLE = Manual;
+ PRODUCT_NAME = "$(TARGET_NAME)";
+ };
+ name = Debug;
+ };
+ 33CC111D2044C6BA0003C045 /* Release */ = {
+ isa = XCBuildConfiguration;
+ buildSettings = {
+ CODE_SIGN_STYLE = Automatic;
+ PRODUCT_NAME = "$(TARGET_NAME)";
+ };
+ name = Release;
+ };
+/* End XCBuildConfiguration section */
+
+/* Begin XCConfigurationList section */
+ 331C80DE294CF71000263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */ = {
+ isa = XCConfigurationList;
+ buildConfigurations = (
+ 331C80DB294CF71000263BE5 /* Debug */,
+ 331C80DC294CF71000263BE5 /* Release */,
+ 331C80DD294CF71000263BE5 /* Profile */,
+ );
+ defaultConfigurationIsVisible = 0;
+ defaultConfigurationName = Release;
+ };
+ 33CC10E82044A3C60003C045 /* Build configuration list for PBXProject "Runner" */ = {
+ isa = XCConfigurationList;
+ buildConfigurations = (
+ 33CC10F92044A3C60003C045 /* Debug */,
+ 33CC10FA2044A3C60003C045 /* Release */,
+ 338D0CE9231458BD00FA5F75 /* Profile */,
+ );
+ defaultConfigurationIsVisible = 0;
+ defaultConfigurationName = Release;
+ };
+ 33CC10FB2044A3C60003C045 /* Build configuration list for PBXNativeTarget "Runner" */ = {
+ isa = XCConfigurationList;
+ buildConfigurations = (
+ 33CC10FC2044A3C60003C045 /* Debug */,
+ 33CC10FD2044A3C60003C045 /* Release */,
+ 338D0CEA231458BD00FA5F75 /* Profile */,
+ );
+ defaultConfigurationIsVisible = 0;
+ defaultConfigurationName = Release;
+ };
+ 33CC111B2044C6BA0003C045 /* Build configuration list for PBXAggregateTarget "Flutter Assemble" */ = {
+ isa = XCConfigurationList;
+ buildConfigurations = (
+ 33CC111C2044C6BA0003C045 /* Debug */,
+ 33CC111D2044C6BA0003C045 /* Release */,
+ 338D0CEB231458BD00FA5F75 /* Profile */,
+ );
+ defaultConfigurationIsVisible = 0;
+ defaultConfigurationName = Release;
+ };
+/* End XCConfigurationList section */
+ };
+ rootObject = 33CC10E52044A3C60003C045 /* Project object */;
+}
From 6293afdc1865575f957fb940c5e05bb471555414 Mon Sep 17 00:00:00 2001
From: Pedro Alvarez <14023675+pnalvarez@users.noreply.github.com>
Date: Tue, 30 Sep 2025 09:41:23 -0300
Subject: [PATCH 07/17] [test](pnalvarez): start reading feature unit tests
---
lib/core/di/app_module.dart | 9 ++
lib/core/di/di.config.dart | 20 +--
.../data/datasource/reading_data_source.dart | 4 +-
pubspec.lock | 2 +-
pubspec.yaml | 1 +
.../mock_encrypted_shared_preferences.dart | 5 +
.../repository/reading_repository_test.dart | 60 +++++++++
.../layers/domain/fakes/fake_book_domain.dart | 1 +
test/layers/domain/start_reading_test.dart | 41 +++++++
.../start_reading_view_model_test.dart | 116 ++++++++++++++++++
10 files changed, 249 insertions(+), 10 deletions(-)
create mode 100644 lib/core/di/app_module.dart
create mode 100644 test/layers/data/mocks/mock_encrypted_shared_preferences.dart
create mode 100644 test/layers/data/repository/reading_repository_test.dart
create mode 100644 test/layers/domain/start_reading_test.dart
create mode 100644 test/layers/presentation/viewmodel/start_reading_view_model_test.dart
diff --git a/lib/core/di/app_module.dart b/lib/core/di/app_module.dart
new file mode 100644
index 0000000..b6c1486
--- /dev/null
+++ b/lib/core/di/app_module.dart
@@ -0,0 +1,9 @@
+import 'package:encrypted_shared_preferences/encrypted_shared_preferences.dart';
+import 'package:injectable/injectable.dart';
+
+@module
+abstract class AppModule {
+ @lazySingleton
+ EncryptedSharedPreferences get encryptedSharedPreferences =>
+ EncryptedSharedPreferences();
+}
diff --git a/lib/core/di/di.config.dart b/lib/core/di/di.config.dart
index 99bb7cd..f92a118 100644
--- a/lib/core/di/di.config.dart
+++ b/lib/core/di/di.config.dart
@@ -9,19 +9,18 @@
// coverage:ignore-file
// ignore_for_file: no_leading_underscores_for_library_prefixes
+import 'package:encrypted_shared_preferences/encrypted_shared_preferences.dart'
+ as _i847;
import 'package:get_it/get_it.dart' as _i174;
import 'package:injectable/injectable.dart' as _i526;
+import 'package:mibook/core/di/app_module.dart' as _i516;
import 'package:mibook/layers/data/api/api_client.dart' as _i721;
import 'package:mibook/layers/data/datasource/reading_data_source.dart' as _i44;
import 'package:mibook/layers/data/datasource/search_data_source.dart' as _i400;
-<<<<<<< HEAD
-import 'package:mibook/layers/data/repository/search_repository.dart' as _i967;
-=======
import 'package:mibook/layers/data/repository/reading_repository.dart' as _i600;
import 'package:mibook/layers/data/repository/search_repository.dart' as _i967;
import 'package:mibook/layers/domain/repository/reading_repository.dart'
as _i649;
->>>>>>> 7741511 ([feat](pnalvarez): displaying start reading dialog)
import 'package:mibook/layers/domain/repository/search_repository.dart'
as _i303;
import 'package:mibook/layers/domain/usecases/get_book_details.dart' as _i814;
@@ -43,8 +42,14 @@ extension GetItInjectableX on _i174.GetIt {
_i526.EnvironmentFilter? environmentFilter,
}) {
final gh = _i526.GetItHelper(this, environment, environmentFilter);
+ final appModule = _$AppModule();
+ gh.lazySingleton<_i847.EncryptedSharedPreferences>(
+ () => appModule.encryptedSharedPreferences,
+ );
gh.factory<_i721.IApiClient>(() => _i721.ApiClient());
- gh.factory<_i44.IReadingDataSource>(() => _i44.ReadingDataSource());
+ gh.factory<_i44.IReadingDataSource>(
+ () => _i44.ReadingDataSource(gh<_i847.EncryptedSharedPreferences>()),
+ );
gh.factory<_i400.ISearchDataSource>(
() => _i400.SearchDataSource(gh<_i721.IApiClient>()),
);
@@ -53,12 +58,9 @@ extension GetItInjectableX on _i174.GetIt {
);
gh.factory<_i303.ISearchRepository>(
() => _i967.SearchRepository(gh<_i400.ISearchDataSource>()),
-<<<<<<< HEAD
-=======
);
gh.factory<_i369.IStartReading>(
() => _i369.StartReading(gh<_i649.IReadingRepository>()),
->>>>>>> 7741511 ([feat](pnalvarez): displaying start reading dialog)
);
gh.factory<_i663.ISearchBooks>(
() => _i663.SearchBooks(gh<_i303.ISearchRepository>()),
@@ -82,3 +84,5 @@ extension GetItInjectableX on _i174.GetIt {
return this;
}
}
+
+class _$AppModule extends _i516.AppModule {}
diff --git a/lib/layers/data/datasource/reading_data_source.dart b/lib/layers/data/datasource/reading_data_source.dart
index a9d9228..937d9b0 100644
--- a/lib/layers/data/datasource/reading_data_source.dart
+++ b/lib/layers/data/datasource/reading_data_source.dart
@@ -15,7 +15,9 @@ abstract class IReadingDataSource {
@Injectable(as: IReadingDataSource)
class ReadingDataSource implements IReadingDataSource {
- final _storage = EncryptedSharedPreferences();
+ final EncryptedSharedPreferences _storage;
+
+ ReadingDataSource(this._storage);
@override
Future startReading({
diff --git a/pubspec.lock b/pubspec.lock
index 3058123..049542f 100644
--- a/pubspec.lock
+++ b/pubspec.lock
@@ -753,7 +753,7 @@ packages:
source: hosted
version: "0.28.0"
shared_preferences:
- dependency: transitive
+ dependency: "direct main"
description:
name: shared_preferences
sha256: "6e8bf70b7fef813df4e9a36f658ac46d107db4b4cfe1048b477d4e453a8159f5"
diff --git a/pubspec.yaml b/pubspec.yaml
index 8c40f00..07dee38 100644
--- a/pubspec.yaml
+++ b/pubspec.yaml
@@ -30,6 +30,7 @@ dependencies:
mockito: ^5.4.6
shimmer: ^3.0.0
flutter_html: ^3.0.0
+ shared_preferences: ^2.5.3
dev_dependencies:
flutter_test:
diff --git a/test/layers/data/mocks/mock_encrypted_shared_preferences.dart b/test/layers/data/mocks/mock_encrypted_shared_preferences.dart
new file mode 100644
index 0000000..88068db
--- /dev/null
+++ b/test/layers/data/mocks/mock_encrypted_shared_preferences.dart
@@ -0,0 +1,5 @@
+import 'package:encrypted_shared_preferences/encrypted_shared_preferences.dart';
+import 'package:mockito/mockito.dart';
+
+class MockEncryptedSharedPreferences extends Mock
+ implements EncryptedSharedPreferences {}
diff --git a/test/layers/data/repository/reading_repository_test.dart b/test/layers/data/repository/reading_repository_test.dart
new file mode 100644
index 0000000..8fc0e0a
--- /dev/null
+++ b/test/layers/data/repository/reading_repository_test.dart
@@ -0,0 +1,60 @@
+import 'package:flutter_test/flutter_test.dart';
+import 'package:mibook/layers/data/datasource/reading_data_source.dart';
+import 'package:mibook/layers/data/models/reading_data.dart';
+import 'package:mibook/layers/data/repository/reading_repository.dart';
+import 'package:mibook/layers/domain/models/reading_domain.dart';
+import 'package:mockito/annotations.dart';
+import 'package:mockito/mockito.dart';
+
+import 'reading_repository_test.mocks.dart';
+
+@GenerateNiceMocks([MockSpec()])
+void main() {
+ late MockIReadingDataSource mockIReadingDataSource;
+ late ReadingRepository sut;
+
+ setUp() {
+ mockIReadingDataSource = MockIReadingDataSource();
+ sut = ReadingRepository(mockIReadingDataSource);
+ }
+
+ group('test ReadingRepository', () {
+ setUp();
+
+ test('startReading', () async {
+ final readingDomain = ReadingDomain(bookId: 'id1', progress: 0.5);
+ final readingData = ReadingData(bookId: 'id1', progress: 0.5);
+
+ await sut.startReading(reading: readingDomain);
+
+ verify(
+ mockIReadingDataSource.startReading(
+ readingData: argThat(
+ predicate(
+ (data) =>
+ data.bookId == readingData.bookId &&
+ data.progress == readingData.progress,
+ ),
+ named: 'readingData',
+ ),
+ ),
+ ).called(1);
+ });
+ });
+
+ test('getReadings', () async {
+ final fakeData = [
+ ReadingData(bookId: 'id1', progress: 0.5),
+ ReadingData(bookId: 'id2', progress: 0.5),
+ ];
+
+ when(
+ mockIReadingDataSource.getReadingData(),
+ ).thenAnswer((_) async => fakeData);
+
+ final response = await sut.getReadings();
+
+ verify(mockIReadingDataSource.getReadingData()).called(1);
+ expect(response.length, 2);
+ });
+}
diff --git a/test/layers/domain/fakes/fake_book_domain.dart b/test/layers/domain/fakes/fake_book_domain.dart
index e7aa8a9..a5fad09 100644
--- a/test/layers/domain/fakes/fake_book_domain.dart
+++ b/test/layers/domain/fakes/fake_book_domain.dart
@@ -7,4 +7,5 @@ final fakeBookDomain = BookDomain(
thumbnail: 'thumbnail',
authors: [],
kind: 'kind',
+ pageCount: 0,
);
diff --git a/test/layers/domain/start_reading_test.dart b/test/layers/domain/start_reading_test.dart
new file mode 100644
index 0000000..11c7848
--- /dev/null
+++ b/test/layers/domain/start_reading_test.dart
@@ -0,0 +1,41 @@
+@GenerateNiceMocks([MockSpec()])
+import 'package:flutter_test/flutter_test.dart';
+import 'package:mibook/layers/domain/models/reading_domain.dart';
+import 'package:mibook/layers/domain/repository/reading_repository.dart';
+import 'package:mibook/layers/domain/usecases/start_reading.dart';
+import 'package:mockito/annotations.dart';
+import 'package:mockito/mockito.dart';
+
+import 'start_reading_test.mocks.dart';
+
+void main() {
+ late MockIReadingRepository mockIReadingRepository;
+ late StartReading sut;
+
+ setUp() {
+ mockIReadingRepository = MockIReadingRepository();
+ sut = StartReading(mockIReadingRepository);
+ }
+
+ group('test StartReading', () {
+ setUp();
+
+ test('start', () async {
+ final readingDomain = ReadingDomain(bookId: 'id1', progress: 0.5);
+ // when(
+ // mockIReadingRepository.startReading(reading: readingDomain),
+ // ).thenAnswer((_) async => Future.value);
+ await sut(reading: readingDomain);
+ verify(
+ mockIReadingRepository.startReading(
+ reading: argThat(
+ predicate(
+ (data) => data.bookId == 'id1' && data.progress == 0.5,
+ ),
+ named: 'reading',
+ ),
+ ),
+ ).called(1);
+ });
+ });
+}
diff --git a/test/layers/presentation/viewmodel/start_reading_view_model_test.dart b/test/layers/presentation/viewmodel/start_reading_view_model_test.dart
new file mode 100644
index 0000000..1e304af
--- /dev/null
+++ b/test/layers/presentation/viewmodel/start_reading_view_model_test.dart
@@ -0,0 +1,116 @@
+import 'package:flutter_test/flutter_test.dart';
+import 'package:mibook/core/utils/strings.dart';
+import 'package:mibook/layers/domain/usecases/start_reading.dart';
+import 'package:mibook/layers/presentation/screens/bookdetails/book_details_ui.dart';
+import 'package:mibook/layers/presentation/screens/startreading/start_reading_event.dart';
+import 'package:mibook/layers/presentation/screens/startreading/start_reading_state.dart';
+import 'package:mibook/layers/presentation/screens/startreading/start_reading_view_model.dart';
+import 'package:mockito/annotations.dart';
+
+@GenerateNiceMocks([MockSpec()])
+import 'start_reading_view_model_test.mocks.dart';
+
+void main() {
+ late MockIStartReading startReading;
+ late StartReadingViewModel sut;
+ final fakeBook = BookDetailsUI(
+ id: 'id',
+ kind: 'kind',
+ title: 'title',
+ authors: 'authors',
+ description: 'description',
+ thumbnail: 'thumbnail',
+ pageCount: 100,
+ );
+
+ setUp() {
+ startReading = MockIStartReading();
+ sut = StartReadingViewModel(startReading, fakeBook);
+ }
+
+ group('test StartReadingViewModel', () {
+ setUp();
+ test(
+ 'given progress > 1.0, when add didEditProgressEvent, should state have input error message',
+ () async {
+ sut.add(DidEditProgressEvent(progress: 200));
+
+ await expectLater(
+ sut.stream,
+ emits(
+ predicate(
+ (state) =>
+ state.inputErrorMessage == progressErrorMessage &&
+ state.progress == 0.0,
+ ),
+ ),
+ );
+ },
+ );
+
+ test(
+ 'given progress <= 1.0, when add didEditProgressEvent, should state have progress',
+ () async {
+ sut.add(DidEditProgressEvent(progress: 50));
+
+ await expectLater(
+ sut.stream,
+ emits(
+ predicate(
+ (state) =>
+ state.inputErrorMessage == null && state.progress == 0.5,
+ ),
+ ),
+ );
+ },
+ );
+
+ test(
+ 'didClickConfirmEvent',
+ () async {
+ sut.emit(
+ StartReadingState(
+ inputErrorMessage: null,
+ progress: 0.5,
+ shouldNavigateBack: false,
+ ),
+ );
+
+ sut.add(DidClickConfirmEvent());
+
+ await expectLater(
+ sut.stream,
+ emits(
+ predicate(
+ (state) => state.shouldNavigateBack,
+ ),
+ ),
+ );
+ },
+ );
+
+ test(
+ 'didFinishBookEvent',
+ () async {
+ sut.emit(
+ StartReadingState(
+ inputErrorMessage: null,
+ progress: 0.5,
+ shouldNavigateBack: false,
+ ),
+ );
+
+ sut.add(DidClickFinishBookEvent());
+
+ await expectLater(
+ sut.stream,
+ emits(
+ predicate(
+ (state) => state.shouldNavigateBack,
+ ),
+ ),
+ );
+ },
+ );
+ });
+}
From 821911b04cc3458e1738380857b9915e2c743c2c Mon Sep 17 00:00:00 2001
From: Pedro Alvarez <14023675+pnalvarez@users.noreply.github.com>
Date: Tue, 30 Sep 2025 18:32:47 -0300
Subject: [PATCH 08/17] [refactor](pnalvarez): addressed Github warnings
---
.../bookdetails/book_details_page.dart | 63 -------------------
1 file changed, 63 deletions(-)
diff --git a/lib/layers/presentation/screens/bookdetails/book_details_page.dart b/lib/layers/presentation/screens/bookdetails/book_details_page.dart
index ef1e94b..0015cbb 100644
--- a/lib/layers/presentation/screens/bookdetails/book_details_page.dart
+++ b/lib/layers/presentation/screens/bookdetails/book_details_page.dart
@@ -63,7 +63,6 @@ class _BookDetailsContent extends StatelessWidget {
@override
Widget build(BuildContext context) {
- final viewModel = context.read();
return SingleChildScrollView(
child: Column(
children: [
@@ -75,7 +74,6 @@ class _BookDetailsContent extends StatelessWidget {
return Center(child: Text('Error: ${state.errorMessage}'));
} else if (state.bookDetails != null) {
final book = state.bookDetails!;
- final bookProgress = state.bookProgress;
return Padding(
padding: const EdgeInsets.all(16.0),
@@ -135,64 +133,3 @@ class _BookDetailsContent extends StatelessWidget {
);
}
}
-
-class _StartReadingDialogContent extends StatelessWidget {
- final BookDetailsUI book;
- final double progress;
- final TextEditingController _controller = TextEditingController();
- final Function(String) onChangeProgressText;
- final Function(double) onClickStartReading;
-
- _StartReadingDialogContent({
- required this.book,
- required this.progress,
- required this.onChangeProgressText,
- required this.onClickStartReading,
- });
-
- @override
- Widget build(BuildContext context) {
- debugPrint('Progress $progress');
- return Padding(
- padding: const EdgeInsets.all(16.0),
- child: Column(
- mainAxisSize: MainAxisSize.min,
- children: [
- Align(
- alignment: Alignment.topLeft,
- child: IconButton(
- icon: const Icon(Icons.close),
- onPressed: () => Navigator.of(context).pop(),
- ),
- ),
- if (book.thumbnail != null)
- CachedNetworkImage(imageUrl: book.thumbnail!),
- const SizedBox(height: 16),
- Text(
- book.title,
- style: TextStyle(
- fontSize: 24,
- fontWeight: FontWeight.bold,
- ),
- ),
- const SizedBox(height: 8),
- Text('Maximum pages: ${book.pageCount}'),
- const SizedBox(height: 24),
- InputField(
- keyboardType: TextInputType.number,
- label: strings.progress,
- controller: _controller,
- onChanged: onChangeProgressText,
- ),
- const SizedBox(height: 24),
- ProgressStepper(progress: progress),
- const SizedBox(height: 24),
- PrimaryButton(
- title: strings.confirm,
- onPressed: () {},
- ),
- ],
- ),
- );
- }
-}
From 69d9d68eeb519727177018004160ec086aa45d0e Mon Sep 17 00:00:00 2001
From: Pedro Alvarez <14023675+pnalvarez@users.noreply.github.com>
Date: Wed, 1 Oct 2025 08:20:10 -0300
Subject: [PATCH 09/17] removed auto generated file
---
.../molecules/buttons/primary_button.dart | 55 ++--
.../designsystem/organisms/app_nav_bar.dart | 9 +-
lib/core/routes/app_router.gr.dart | 237 ------------------
pubspec.lock | 8 +
pubspec.yaml | 1 +
5 files changed, 38 insertions(+), 272 deletions(-)
delete mode 100644 lib/core/routes/app_router.gr.dart
diff --git a/lib/core/designsystem/molecules/buttons/primary_button.dart b/lib/core/designsystem/molecules/buttons/primary_button.dart
index 9ecf1ba..70e42c6 100644
--- a/lib/core/designsystem/molecules/buttons/primary_button.dart
+++ b/lib/core/designsystem/molecules/buttons/primary_button.dart
@@ -19,40 +19,31 @@ class PrimaryButton extends StatelessWidget {
@override
Widget build(BuildContext context) {
- final button = ElevatedButton(
- onPressed: isEnabled ? onPressed : null,
- style: ElevatedButton.styleFrom(
- backgroundColor: isEnabled ? primary : disabled,
- foregroundColor: Colors.white,
- padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12),
- textStyle: const TextStyle(fontSize: 16, fontWeight: FontWeight.w500),
- shape: RoundedRectangleBorder(
- borderRadius: BorderRadius.circular(16),
+ return SizedBox(
+ height: 48,
+ width: isExpanded ? double.infinity : null,
+ child: ElevatedButton(
+ onPressed: isEnabled ? onPressed : null,
+ style: ElevatedButton.styleFrom(
+ backgroundColor: isEnabled ? primary : disabled,
+ foregroundColor: Colors.white,
+ padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12),
+ textStyle: const TextStyle(fontSize: 16, fontWeight: FontWeight.w500),
+ shape: RoundedRectangleBorder(
+ borderRadius: BorderRadius.circular(16),
+ ),
+ minimumSize: const Size(80, 48),
),
- minimumSize: const Size(80, 48),
+ child: isLoading
+ ? SizedBox(
+ width: 20,
+ height: 20,
+ child: CircularProgressIndicator(
+ color: Colors.white,
+ ),
+ )
+ : Text(title),
),
- child: isLoading
- ? SizedBox(
- width: 20,
- height: 20,
- child: CircularProgressIndicator(
- color: Colors.white,
- ),
- )
- : Text(title),
);
-
- if (isExpanded) {
- return SizedBox(
- height: 48,
- width: double.infinity,
- child: button,
- );
- } else {
- return SizedBox(
- height: 48,
- child: button,
- );
- }
}
}
diff --git a/lib/core/designsystem/organisms/app_nav_bar.dart b/lib/core/designsystem/organisms/app_nav_bar.dart
index 0cf4e27..a8928cc 100644
--- a/lib/core/designsystem/organisms/app_nav_bar.dart
+++ b/lib/core/designsystem/organisms/app_nav_bar.dart
@@ -1,3 +1,4 @@
+import 'package:auto_size_text/auto_size_text.dart';
import 'package:flutter/material.dart';
import 'package:mibook/core/designsystem/modifiers/display_as_loader.dart';
@@ -67,15 +68,17 @@ class AppNavBar extends StatelessWidget implements PreferredSizeWidget {
isLoading: isTitleLoading,
child: ConstrainedBox(
constraints: const BoxConstraints(maxWidth: 250),
- child: Text(
+ child: AutoSizeText(
titleText,
- maxLines: 1,
+ maxLines: 2,
overflow: TextOverflow.ellipsis,
style: TextStyle(
color: Colors.black,
- fontSize: titleText.length > 20 ? 14 : 22,
+ fontSize: 22,
fontWeight: FontWeight.bold,
),
+ minFontSize: 12,
+ stepGranularity: 1,
),
),
);
diff --git a/lib/core/routes/app_router.gr.dart b/lib/core/routes/app_router.gr.dart
deleted file mode 100644
index 650fdbd..0000000
--- a/lib/core/routes/app_router.gr.dart
+++ /dev/null
@@ -1,237 +0,0 @@
-// dart format width=80
-// GENERATED CODE - DO NOT MODIFY BY HAND
-
-// **************************************************************************
-// AutoRouterGenerator
-// **************************************************************************
-
-// ignore_for_file: type=lint
-// coverage:ignore-file
-
-// ignore_for_file: no_leading_underscores_for_library_prefixes
-import 'package:auto_route/auto_route.dart' as _i11;
-import 'package:flutter/material.dart' as _i12;
-import 'package:mibook/layers/presentation/navigation/dashboard/dashboard_page.dart'
- as _i4;
-import 'package:mibook/layers/presentation/navigation/tabs/home_tab.dart'
- as _i5;
-import 'package:mibook/layers/presentation/navigation/tabs/objective_tab.dart'
- as _i6;
-import 'package:mibook/layers/presentation/navigation/tabs/search_tab.dart'
- as _i9;
-import 'package:mibook/layers/presentation/screens/bookdetails/book_details_page.dart'
- as _i1;
-import 'package:mibook/layers/presentation/screens/bookdetails/book_details_ui.dart'
- as _i13;
-import 'package:mibook/layers/presentation/screens/booksearch/book_search_page.dart'
- as _i2;
-import 'package:mibook/layers/presentation/screens/currentobjective/current_objective_page.dart'
- as _i3;
-import 'package:mibook/layers/presentation/screens/onboarding/pre_onboarding_page.dart'
- as _i7;
-import 'package:mibook/layers/presentation/screens/readinglist/reading_list_page.dart'
- as _i8;
-import 'package:mibook/layers/presentation/screens/startreading/start_reading_page.dart'
- as _i10;
-
-/// generated route for
-/// [_i1.BookDetailsPage]
-class BookDetailsRoute extends _i11.PageRouteInfo {
- BookDetailsRoute({
- _i12.Key? key,
- required String id,
- List<_i11.PageRouteInfo>? children,
- }) : super(
- BookDetailsRoute.name,
- args: BookDetailsRouteArgs(key: key, id: id),
- initialChildren: children,
- );
-
- static const String name = 'BookDetailsRoute';
-
- static _i11.PageInfo page = _i11.PageInfo(
- name,
- builder: (data) {
- final args = data.argsAs();
- return _i1.BookDetailsPage(key: args.key, id: args.id);
- },
- );
-}
-
-class BookDetailsRouteArgs {
- const BookDetailsRouteArgs({this.key, required this.id});
-
- final _i12.Key? key;
-
- final String id;
-
- @override
- String toString() {
- return 'BookDetailsRouteArgs{key: $key, id: $id}';
- }
-}
-
-/// generated route for
-/// [_i2.BookSearchPage]
-class BookSearchRoute extends _i11.PageRouteInfo {
- const BookSearchRoute({List<_i11.PageRouteInfo>? children})
- : super(BookSearchRoute.name, initialChildren: children);
-
- static const String name = 'BookSearchRoute';
-
- static _i11.PageInfo page = _i11.PageInfo(
- name,
- builder: (data) {
- return const _i2.BookSearchPage();
- },
- );
-}
-
-/// generated route for
-/// [_i3.CurrentObjectivePage]
-class CurrentObjectiveRoute extends _i11.PageRouteInfo {
- const CurrentObjectiveRoute({List<_i11.PageRouteInfo>? children})
- : super(CurrentObjectiveRoute.name, initialChildren: children);
-
- static const String name = 'CurrentObjectiveRoute';
-
- static _i11.PageInfo page = _i11.PageInfo(
- name,
- builder: (data) {
- return const _i3.CurrentObjectivePage();
- },
- );
-}
-
-/// generated route for
-/// [_i4.DashboardPage]
-class DashboardRoute extends _i11.PageRouteInfo {
- const DashboardRoute({List<_i11.PageRouteInfo>? children})
- : super(DashboardRoute.name, initialChildren: children);
-
- static const String name = 'DashboardRoute';
-
- static _i11.PageInfo page = _i11.PageInfo(
- name,
- builder: (data) {
- return const _i4.DashboardPage();
- },
- );
-}
-
-/// generated route for
-/// [_i5.HomeTab]
-class HomeRoute extends _i11.PageRouteInfo {
- const HomeRoute({List<_i11.PageRouteInfo>? children})
- : super(HomeRoute.name, initialChildren: children);
-
- static const String name = 'HomeRoute';
-
- static _i11.PageInfo page = _i11.PageInfo(
- name,
- builder: (data) {
- return const _i5.HomeTab();
- },
- );
-}
-
-/// generated route for
-/// [_i6.ObjectiveTab]
-class ObjectiveRoute extends _i11.PageRouteInfo {
- const ObjectiveRoute({List<_i11.PageRouteInfo>? children})
- : super(ObjectiveRoute.name, initialChildren: children);
-
- static const String name = 'ObjectiveRoute';
-
- static _i11.PageInfo page = _i11.PageInfo(
- name,
- builder: (data) {
- return const _i6.ObjectiveTab();
- },
- );
-}
-
-/// generated route for
-/// [_i7.PreOnboardingPage]
-class PreOnboardingRoute extends _i11.PageRouteInfo {
- const PreOnboardingRoute({List<_i11.PageRouteInfo>? children})
- : super(PreOnboardingRoute.name, initialChildren: children);
-
- static const String name = 'PreOnboardingRoute';
-
- static _i11.PageInfo page = _i11.PageInfo(
- name,
- builder: (data) {
- return const _i7.PreOnboardingPage();
- },
- );
-}
-
-/// generated route for
-/// [_i8.ReadingListPage]
-class ReadingListRoute extends _i11.PageRouteInfo {
- const ReadingListRoute({List<_i11.PageRouteInfo>? children})
- : super(ReadingListRoute.name, initialChildren: children);
-
- static const String name = 'ReadingListRoute';
-
- static _i11.PageInfo page = _i11.PageInfo(
- name,
- builder: (data) {
- return const _i8.ReadingListPage();
- },
- );
-}
-
-/// generated route for
-/// [_i9.SearchTab]
-class SearchRoute extends _i11.PageRouteInfo {
- const SearchRoute({List<_i11.PageRouteInfo>? children})
- : super(SearchRoute.name, initialChildren: children);
-
- static const String name = 'SearchRoute';
-
- static _i11.PageInfo page = _i11.PageInfo(
- name,
- builder: (data) {
- return const _i9.SearchTab();
- },
- );
-}
-
-/// generated route for
-/// [_i10.StartReadingPage]
-class StartReadingRoute extends _i11.PageRouteInfo {
- StartReadingRoute({
- _i12.Key? key,
- required _i13.BookDetailsUI book,
- List<_i11.PageRouteInfo>? children,
- }) : super(
- StartReadingRoute.name,
- args: StartReadingRouteArgs(key: key, book: book),
- initialChildren: children,
- );
-
- static const String name = 'StartReadingRoute';
-
- static _i11.PageInfo page = _i11.PageInfo(
- name,
- builder: (data) {
- final args = data.argsAs();
- return _i10.StartReadingPage(key: args.key, book: args.book);
- },
- );
-}
-
-class StartReadingRouteArgs {
- const StartReadingRouteArgs({this.key, required this.book});
-
- final _i12.Key? key;
-
- final _i13.BookDetailsUI book;
-
- @override
- String toString() {
- return 'StartReadingRouteArgs{key: $key, book: $book}';
- }
-}
diff --git a/pubspec.lock b/pubspec.lock
index 049542f..f970ac6 100644
--- a/pubspec.lock
+++ b/pubspec.lock
@@ -57,6 +57,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "9.3.1"
+ auto_size_text:
+ dependency: "direct main"
+ description:
+ name: auto_size_text
+ sha256: "3f5261cd3fb5f2a9ab4e2fc3fba84fd9fcaac8821f20a1d4e71f557521b22599"
+ url: "https://pub.dev"
+ source: hosted
+ version: "3.0.0"
bloc:
dependency: transitive
description:
diff --git a/pubspec.yaml b/pubspec.yaml
index 07dee38..3b33d7d 100644
--- a/pubspec.yaml
+++ b/pubspec.yaml
@@ -31,6 +31,7 @@ dependencies:
shimmer: ^3.0.0
flutter_html: ^3.0.0
shared_preferences: ^2.5.3
+ auto_size_text: ^3.0.0
dev_dependencies:
flutter_test:
From 3cb301a32993bc61372b304977c3106de979ae0e Mon Sep 17 00:00:00 2001
From: Pedro Alvarez <14023675+pnalvarez@users.noreply.github.com>
Date: Wed, 1 Oct 2025 08:24:33 -0300
Subject: [PATCH 10/17] [feat](pnalvarez): ignoring gr.dart files
---
.gitignore | 2 ++
1 file changed, 2 insertions(+)
diff --git a/.gitignore b/.gitignore
index 0122a41..6341c5e 100644
--- a/.gitignore
+++ b/.gitignore
@@ -170,3 +170,5 @@ app.*.symbols
*/di.config.dart
*.mocks.dart
lib/core/di/di.config.dart
+*.gr.dart
+
From 6acdf748aa208d15365cebf0e0df910b394037c8 Mon Sep 17 00:00:00 2001
From: Pedro Alvarez <14023675+pnalvarez@users.noreply.github.com>
Date: Wed, 1 Oct 2025 08:28:43 -0300
Subject: [PATCH 11/17] [feat](pnalvarez): ignoring g.dart files
---
.../data/datasource/reading_data_source.dart | 39 +--
lib/layers/data/models/book_list_data.g.dart | 265 ------------------
lib/layers/data/models/reading_data.g.dart | 15 -
3 files changed, 2 insertions(+), 317 deletions(-)
delete mode 100644 lib/layers/data/models/book_list_data.g.dart
delete mode 100644 lib/layers/data/models/reading_data.g.dart
diff --git a/lib/layers/data/datasource/reading_data_source.dart b/lib/layers/data/datasource/reading_data_source.dart
index 937d9b0..b7dd76f 100644
--- a/lib/layers/data/datasource/reading_data_source.dart
+++ b/lib/layers/data/datasource/reading_data_source.dart
@@ -1,11 +1,7 @@
-import 'dart:convert';
-
import 'package:encrypted_shared_preferences/encrypted_shared_preferences.dart';
import 'package:injectable/injectable.dart';
import 'package:mibook/layers/data/models/reading_data.dart';
-const _readingListKey = 'reading_list';
-
abstract class IReadingDataSource {
Future startReading({
required ReadingData readingData,
@@ -23,40 +19,9 @@ class ReadingDataSource implements IReadingDataSource {
Future startReading({
required ReadingData readingData,
}) async {
- final readingList = await _storage.getString(_readingListKey);
- if (readingList.isEmpty) {
- final string = jsonEncode([readingData.toJson()]);
- await _storage.setString(_readingListKey, string);
- } else {
- final json = jsonDecode(readingList) as List;
- final list = json
- .map((e) => ReadingData.fromJson(e as Map))
- .toList();
- final index = list.indexWhere(
- (element) => element.bookId == readingData.bookId,
- );
- if (index != -1) {
- list[index] = readingData;
- } else {
- list.add(readingData);
- }
- final string = jsonEncode(list.map((e) => e.toJson()).toList());
- await _storage.setString(_readingListKey, string);
- }
- final json = readingData.toJson();
- await _storage.setString(readingData.bookId, json.toString());
+ // TO DO
}
@override
- Future> getReadingData() async {
- final readingList = await _storage.getString(_readingListKey);
- if (readingList.isEmpty) {
- return [];
- }
- final json = jsonDecode(readingList) as List;
- final list = json
- .map((e) => ReadingData.fromJson(e as Map))
- .toList();
- return list;
- }
+ Future> getReadingData() async => [];
}
diff --git a/lib/layers/data/models/book_list_data.g.dart b/lib/layers/data/models/book_list_data.g.dart
deleted file mode 100644
index 295b723..0000000
--- a/lib/layers/data/models/book_list_data.g.dart
+++ /dev/null
@@ -1,265 +0,0 @@
-// GENERATED CODE - DO NOT MODIFY BY HAND
-
-part of 'book_list_data.dart';
-
-// **************************************************************************
-// JsonSerializableGenerator
-// **************************************************************************
-
-BookListData _$BookListDataFromJson(Map json) => BookListData(
- kind: json['kind'] as String,
- totalItems: (json['totalItems'] as num?)?.toInt(),
- items:
- (json['items'] as List?)
- ?.map((e) => BookItem.fromJson(e as Map))
- .toList() ??
- [],
-);
-
-Map _$BookListDataToJson(BookListData instance) =>
- {
- 'kind': instance.kind,
- 'totalItems': instance.totalItems,
- 'items': instance.items,
- };
-
-BookItem _$BookItemFromJson(Map json) => BookItem(
- kind: json['kind'] as String,
- id: json['id'] as String,
- etag: json['etag'] as String,
- selfLink: json['selfLink'] as String,
- volumeInfo: VolumeInfo.fromJson(json['volumeInfo'] as Map),
- saleInfo: SaleInfo.fromJson(json['saleInfo'] as Map),
- accessInfo: AccessInfo.fromJson(json['accessInfo'] as Map),
- searchInfo: json['searchInfo'] == null
- ? null
- : SearchInfo.fromJson(json['searchInfo'] as Map),
-);
-
-Map _$BookItemToJson(BookItem instance) => {
- 'kind': instance.kind,
- 'id': instance.id,
- 'etag': instance.etag,
- 'selfLink': instance.selfLink,
- 'volumeInfo': instance.volumeInfo,
- 'saleInfo': instance.saleInfo,
- 'accessInfo': instance.accessInfo,
- 'searchInfo': instance.searchInfo,
-};
-
-VolumeInfo _$VolumeInfoFromJson(Map json) => VolumeInfo(
- title: json['title'] as String,
- authors: (json['authors'] as List?)
- ?.map((e) => e as String)
- .toList(),
- subtitle: json['subtitle'] as String?,
- publisher: json['publisher'] as String?,
- publishedDate: json['publishedDate'] as String?,
- description: json['description'] as String?,
- industryIdentifiers: (json['industryIdentifiers'] as List?)
- ?.map((e) => IndustryIdentifier.fromJson(e as Map