diff --git a/assets/icons/star_filled.svg b/assets/icons/star_filled.svg
new file mode 100644
index 0000000..3ae6fef
--- /dev/null
+++ b/assets/icons/star_filled.svg
@@ -0,0 +1,41 @@
+
+
+
diff --git a/assets/icons/star_outline.svg b/assets/icons/star_outline.svg
new file mode 100644
index 0000000..99b6849
--- /dev/null
+++ b/assets/icons/star_outline.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/lib/features/core/abstracts/app_setup.dart b/lib/features/core/abstracts/app_setup.dart
index 854f6a2..05b08be 100644
--- a/lib/features/core/abstracts/app_setup.dart
+++ b/lib/features/core/abstracts/app_setup.dart
@@ -1,14 +1,14 @@
import 'dart:async';
import 'package:flutter/foundation.dart';
-import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
+import 'package:flutter/widgets.dart';
+import 'package:hive_flutter/hive_flutter.dart';
import '/l10n/generated/l10n.dart';
import '/locator.dart';
abstract class AppSetup {
- // TODO: When locator is properly refactored we should not have to use these stub methods for testing
static Future initialise() async {
WidgetsFlutterBinding.ensureInitialized();
@@ -19,6 +19,8 @@ abstract class AppSetup {
DeviceOrientation.portraitDown,
]);
+ await Hive.initFlutter();
+
await Locator.setup();
}
diff --git a/lib/features/core/data/constants/const_media.dart b/lib/features/core/data/constants/const_media.dart
new file mode 100644
index 0000000..9e2af06
--- /dev/null
+++ b/lib/features/core/data/constants/const_media.dart
@@ -0,0 +1,26 @@
+import 'package:flutter/widgets.dart';
+import 'package:flutter_svg/flutter_svg.dart';
+
+abstract class ConstMedia {
+ static const String favStarFilled = 'assets/icons/star_filled.svg';
+ static const String favStarOutline = 'assets/icons/star_outline.svg';
+
+ static SvgPicture buildIcon(
+ String iconReference, {
+ Color? color,
+ double? width,
+ double? height,
+ BoxFit fit = BoxFit.contain,
+ Clip clipBehavior = Clip.hardEdge,
+ Alignment alignment = Alignment.center,
+ }) =>
+ SvgPicture.asset(
+ iconReference,
+ color: color,
+ width: width,
+ height: height,
+ fit: fit,
+ clipBehavior: clipBehavior,
+ alignment: alignment,
+ );
+}
diff --git a/lib/features/core/data/extensions/map_extensions.dart b/lib/features/core/data/extensions/map_extensions.dart
index 26f310a..74de75a 100644
--- a/lib/features/core/data/extensions/map_extensions.dart
+++ b/lib/features/core/data/extensions/map_extensions.dart
@@ -1,6 +1,17 @@
+import 'dart:collection';
+
extension MapExtensions on Map {
Map get deepCopy => {...this};
/// Returns the values of a [Map] at given [keys] indices.
Iterable valuesByKeys({required Iterable keys}) => keys.map((final key) => this[key]!);
}
+
+extension LinkedHashMapExtensions on LinkedHashMap {
+ /// Updated the value at [valueIndex] to [newValue], in addition to preserving the order.
+ void updateValueAt({
+ required int valueIndex,
+ required B newValue,
+ }) =>
+ this[keys.toList()[valueIndex]] = newValue;
+}
diff --git a/lib/features/core/data/extensions/value_notifier_extensions.dart b/lib/features/core/data/extensions/value_notifier_extensions.dart
new file mode 100644
index 0000000..8d4b278
--- /dev/null
+++ b/lib/features/core/data/extensions/value_notifier_extensions.dart
@@ -0,0 +1,5 @@
+import 'package:flutter/cupertino.dart';
+
+extension ValueNotifierBoolExtensions on ValueNotifier {
+ void flipValue() => value = !value;
+}
diff --git a/lib/features/core/services/local_storage_service.dart b/lib/features/core/services/local_storage_service.dart
new file mode 100644
index 0000000..0b4898c
--- /dev/null
+++ b/lib/features/core/services/local_storage_service.dart
@@ -0,0 +1,47 @@
+import 'package:hive/hive.dart';
+import 'package:mc_gallery/features/core/services/logging_service.dart';
+import 'package:mc_gallery/locator.dart';
+
+class LocalStorageService {
+ LocalStorageService() {
+ _init();
+ }
+
+ final LoggingService _loggingService = LoggingService.locate;
+
+ static const String _userBoxKey = 'userBoxKey';
+
+ late final Box _userBox;
+
+ Future _init() async {
+ _userBox = await Hive.openBox(_userBoxKey);
+
+ Locator.instance().signalReady(this);
+ }
+
+ Iterable get storedFavouritesStates => _userBox.values;
+
+ void initNewFavourites({required Iterable newValues}) {
+ _userBox.addAll(newValues);
+ _loggingService.info('Adding new favourites value');
+ }
+
+ void updateFavourite({
+ required index,
+ required bool newValue,
+ }) {
+ try {
+ _userBox.putAt(index, newValue);
+ _loggingService.good('Successfully updated favourite status at $index -> $newValue');
+ } on Exception catch (ex, stackTrace) {
+ _loggingService.handleException(ex, stackTrace);
+ }
+ }
+
+ void resetFavourites() {
+ _userBox.clear();
+ _loggingService.info('Cleared favourites table');
+ }
+
+ static LocalStorageService get locate => Locator.locate();
+}
diff --git a/lib/features/core/services/logging_service.dart b/lib/features/core/services/logging_service.dart
index 6144583..8a968b1 100644
--- a/lib/features/core/services/logging_service.dart
+++ b/lib/features/core/services/logging_service.dart
@@ -25,7 +25,8 @@ class LoggingService {
void Function(dynamic msg, [Object? exception, StackTrace? stackTrace]) get fine => _talker.fine;
void Function(dynamic msg, [Object? exception, StackTrace? stackTrace]) get good => _talker.good;
- void Function(dynamic msg, [Object? exception, StackTrace? stackTrace]) get info => _talker.info;
+ void Function(dynamic msg, [Object? exception, StackTrace? stackTrace]) get info =>
+ _talker.verbose;
void Function(dynamic msg, [Object exception, StackTrace stackTrace]) get warning =>
_talker.warning;
diff --git a/lib/features/home/abstracts/images_api.dart b/lib/features/home/abstracts/images_api.dart
index de9314f..ecc1e1c 100644
--- a/lib/features/home/abstracts/images_api.dart
+++ b/lib/features/home/abstracts/images_api.dart
@@ -1,13 +1,15 @@
import 'dart:async';
+import '../data/dtos/image_model_dto.dart';
+
/// Interface for implementing image-fetching strategies, specific to a resource location on the internet.
///
/// Since I used a site that was more obscure than the ones in the examples, this (otherwise pointless
/// and convoluting) interface is for adding a bit of flexibility to change strategy to some other site.
abstract class ImagesApi {
- FutureOr>> fetchImageUri({required String token});
+ FutureOr> fetchImageUri({required String token});
- FutureOr>> searchImages({
+ FutureOr> searchImages({
required String searchStr,
required String token,
});
diff --git a/lib/features/home/api/unsplash_images_api.dart b/lib/features/home/api/unsplash_images_api.dart
index 0ed3974..283fcab 100644
--- a/lib/features/home/api/unsplash_images_api.dart
+++ b/lib/features/home/api/unsplash_images_api.dart
@@ -7,18 +7,19 @@ import '/features/core/services/logging_service.dart';
import '/l10n/generated/l10n.dart';
import '/locator.dart';
import '../abstracts/images_api.dart';
-import '../data/models/image_model.dart';
+import '../data/dtos/image_model_dto.dart';
class UnsplashImagesApi implements ImagesApi {
final LoggingService _loggingService = LoggingService.locate;
final random = Random();
@override
- FutureOr>> fetchImageUri({required String token}) async {
+ FutureOr> fetchImageUri({required String token}) async {
// Dummy fetching delay emulation
await Future.delayed(const Duration(
milliseconds: ConstValues.defaultEmulatedLatencyMillis * ConstValues.numberOfImages));
+ final Iterable