initial commit (migrated)

This commit is contained in:
fiatcode 2025-10-20 16:43:59 +07:00
commit b594facb51
143 changed files with 11057 additions and 0 deletions

22
lib/core/app_updater.dart Normal file
View file

@ -0,0 +1,22 @@
import 'package:in_app_update/in_app_update.dart';
/// A wrapper of in-app update plugin
abstract class AppUpdater {
/// Check for in-app update
Future<AppUpdateInfo> checkForUpdate();
/// Start update process
Future<AppUpdateResult> update();
}
class AppUpdaterImpl implements AppUpdater {
@override
Future<AppUpdateInfo> checkForUpdate() async {
return InAppUpdate.checkForUpdate();
}
@override
Future<AppUpdateResult> update() async {
return InAppUpdate.performImmediateUpdate();
}
}

View file

@ -0,0 +1,11 @@
/// Config base class
abstract class Config<T> {
/// Get config value
Future<T?> get();
/// Set config value
Future<void> set(T value);
/// Remove config value
Future<void> remove();
}

View file

@ -0,0 +1,47 @@
import 'package:kuwot/core/data/local/config.dart';
import 'package:flutter/material.dart';
import 'package:shared_preferences/shared_preferences.dart';
/// Theme mode shared preferences key
const themeModeConfigKey = 'themeMode';
/// Theme mode configuration
class ThemeModeConfig extends Config<ThemeMode> {
/// Default constructor
ThemeModeConfig({required this.sharedPreferences});
/// Shared preferences instance
final SharedPreferences sharedPreferences;
@override
Future<ThemeMode> get() async {
final mode = sharedPreferences.getString(themeModeConfigKey);
switch (mode) {
case 'dark':
return ThemeMode.dark;
case 'light':
return ThemeMode.light;
case 'system':
return ThemeMode.system;
default:
return ThemeMode.system;
}
}
@override
Future<void> set(ThemeMode value) async {
switch (value) {
case ThemeMode.dark:
await sharedPreferences.setString(themeModeConfigKey, 'dark');
case ThemeMode.light:
await sharedPreferences.setString(themeModeConfigKey, 'light');
case ThemeMode.system:
await sharedPreferences.setString(themeModeConfigKey, 'system');
}
}
@override
Future<void> remove() async {
await sharedPreferences.remove(themeModeConfigKey);
}
}

View file

@ -0,0 +1,45 @@
import 'dart:convert';
import 'package:freezed_annotation/freezed_annotation.dart';
import 'package:kuwot/core/data/local/config.dart';
import 'package:shared_preferences/shared_preferences.dart';
part 'translation_target_config.freezed.dart';
part 'translation_target_config.g.dart';
const translationTargetConfigKey = 'translationTarget';
const defaultTranslationTarget = TranslationTarget(id: 'en', name: 'English');
@freezed
abstract class TranslationTarget with _$TranslationTarget {
const factory TranslationTarget({required String id, required String name}) =
_TranslationTarget;
factory TranslationTarget.fromJson(Map<String, dynamic> json) =>
_$TranslationTargetFromJson(json);
}
class TranslationTargetConfig extends Config<TranslationTarget> {
TranslationTargetConfig({required this.sharedPreferences});
final SharedPreferences sharedPreferences;
@override
Future<TranslationTarget?> get() async {
final data = sharedPreferences.getString(translationTargetConfigKey);
return data != null ? TranslationTarget.fromJson(jsonDecode(data)) : null;
}
@override
Future<void> set(TranslationTarget value) async {
await sharedPreferences.setString(
translationTargetConfigKey,
jsonEncode(value.toJson()),
);
}
@override
Future<void> remove() async {
await sharedPreferences.remove(translationTargetConfigKey);
}
}

View file

@ -0,0 +1,280 @@
// 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 'translation_target_config.dart';
// **************************************************************************
// FreezedGenerator
// **************************************************************************
// dart format off
T _$identity<T>(T value) => value;
/// @nodoc
mixin _$TranslationTarget {
String get id; String get name;
/// Create a copy of TranslationTarget
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
@pragma('vm:prefer-inline')
$TranslationTargetCopyWith<TranslationTarget> get copyWith => _$TranslationTargetCopyWithImpl<TranslationTarget>(this as TranslationTarget, _$identity);
/// Serializes this TranslationTarget to a JSON map.
Map<String, dynamic> toJson();
@override
bool operator ==(Object other) {
return identical(this, other) || (other.runtimeType == runtimeType&&other is TranslationTarget&&(identical(other.id, id) || other.id == id)&&(identical(other.name, name) || other.name == name));
}
@JsonKey(includeFromJson: false, includeToJson: false)
@override
int get hashCode => Object.hash(runtimeType,id,name);
@override
String toString() {
return 'TranslationTarget(id: $id, name: $name)';
}
}
/// @nodoc
abstract mixin class $TranslationTargetCopyWith<$Res> {
factory $TranslationTargetCopyWith(TranslationTarget value, $Res Function(TranslationTarget) _then) = _$TranslationTargetCopyWithImpl;
@useResult
$Res call({
String id, String name
});
}
/// @nodoc
class _$TranslationTargetCopyWithImpl<$Res>
implements $TranslationTargetCopyWith<$Res> {
_$TranslationTargetCopyWithImpl(this._self, this._then);
final TranslationTarget _self;
final $Res Function(TranslationTarget) _then;
/// Create a copy of TranslationTarget
/// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline') @override $Res call({Object? id = null,Object? name = null,}) {
return _then(_self.copyWith(
id: null == id ? _self.id : id // ignore: cast_nullable_to_non_nullable
as String,name: null == name ? _self.name : name // ignore: cast_nullable_to_non_nullable
as String,
));
}
}
/// Adds pattern-matching-related methods to [TranslationTarget].
extension TranslationTargetPatterns on TranslationTarget {
/// 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<TResult extends Object?>(TResult Function( _TranslationTarget value)? $default,{required TResult orElse(),}){
final _that = this;
switch (_that) {
case _TranslationTarget() when $default != null:
return $default(_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<TResult extends Object?>(TResult Function( _TranslationTarget value) $default,){
final _that = this;
switch (_that) {
case _TranslationTarget():
return $default(_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<TResult extends Object?>(TResult? Function( _TranslationTarget value)? $default,){
final _that = this;
switch (_that) {
case _TranslationTarget() when $default != null:
return $default(_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<TResult extends Object?>(TResult Function( String id, String name)? $default,{required TResult orElse(),}) {final _that = this;
switch (_that) {
case _TranslationTarget() when $default != null:
return $default(_that.id,_that.name);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<TResult extends Object?>(TResult Function( String id, String name) $default,) {final _that = this;
switch (_that) {
case _TranslationTarget():
return $default(_that.id,_that.name);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<TResult extends Object?>(TResult? Function( String id, String name)? $default,) {final _that = this;
switch (_that) {
case _TranslationTarget() when $default != null:
return $default(_that.id,_that.name);case _:
return null;
}
}
}
/// @nodoc
@JsonSerializable()
class _TranslationTarget implements TranslationTarget {
const _TranslationTarget({required this.id, required this.name});
factory _TranslationTarget.fromJson(Map<String, dynamic> json) => _$TranslationTargetFromJson(json);
@override final String id;
@override final String name;
/// Create a copy of TranslationTarget
/// with the given fields replaced by the non-null parameter values.
@override @JsonKey(includeFromJson: false, includeToJson: false)
@pragma('vm:prefer-inline')
_$TranslationTargetCopyWith<_TranslationTarget> get copyWith => __$TranslationTargetCopyWithImpl<_TranslationTarget>(this, _$identity);
@override
Map<String, dynamic> toJson() {
return _$TranslationTargetToJson(this, );
}
@override
bool operator ==(Object other) {
return identical(this, other) || (other.runtimeType == runtimeType&&other is _TranslationTarget&&(identical(other.id, id) || other.id == id)&&(identical(other.name, name) || other.name == name));
}
@JsonKey(includeFromJson: false, includeToJson: false)
@override
int get hashCode => Object.hash(runtimeType,id,name);
@override
String toString() {
return 'TranslationTarget(id: $id, name: $name)';
}
}
/// @nodoc
abstract mixin class _$TranslationTargetCopyWith<$Res> implements $TranslationTargetCopyWith<$Res> {
factory _$TranslationTargetCopyWith(_TranslationTarget value, $Res Function(_TranslationTarget) _then) = __$TranslationTargetCopyWithImpl;
@override @useResult
$Res call({
String id, String name
});
}
/// @nodoc
class __$TranslationTargetCopyWithImpl<$Res>
implements _$TranslationTargetCopyWith<$Res> {
__$TranslationTargetCopyWithImpl(this._self, this._then);
final _TranslationTarget _self;
final $Res Function(_TranslationTarget) _then;
/// Create a copy of TranslationTarget
/// with the given fields replaced by the non-null parameter values.
@override @pragma('vm:prefer-inline') $Res call({Object? id = null,Object? name = null,}) {
return _then(_TranslationTarget(
id: null == id ? _self.id : id // ignore: cast_nullable_to_non_nullable
as String,name: null == name ? _self.name : name // ignore: cast_nullable_to_non_nullable
as String,
));
}
}
// dart format on

View file

@ -0,0 +1,13 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'translation_target_config.dart';
// **************************************************************************
// JsonSerializableGenerator
// **************************************************************************
_TranslationTarget _$TranslationTargetFromJson(Map<String, dynamic> json) =>
_TranslationTarget(id: json['id'] as String, name: json['name'] as String);
Map<String, dynamic> _$TranslationTargetToJson(_TranslationTarget instance) =>
<String, dynamic>{'id': instance.id, 'name': instance.name};

View file

@ -0,0 +1,8 @@
import 'package:equatable/equatable.dart';
class NoParams extends Equatable {
const NoParams();
@override
List<Object> get props => [];
}

View file

@ -0,0 +1,9 @@
import 'package:fpdart/fpdart.dart';
import 'package:kuwot/core/error/failure.dart';
/// [TReturnType] is the return type of a successful use case call.
/// [TParams] are the parameters that are required to call the use case.
abstract class UseCase<TReturnType, TParams> {
/// Execute the use case
Future<Either<Failure, TReturnType>> call(TParams params);
}

View file

@ -0,0 +1,11 @@
import 'package:fpdart/fpdart.dart';
typedef Function1<T, R> = R Function(T t);
extension EitherFutureX<L, R1> on Future<Either<L, R1>> {
Future<Either<L, R2>> chain<R2>(
Function1<R1, Future<Either<L, R2>>> f,
) async {
return (await this).fold(left, f);
}
}

31
lib/core/env.dart Normal file
View file

@ -0,0 +1,31 @@
// ignore_for_file: public_member_api_docs
abstract class Env {
String get quoteApiScheme;
String get quoteApiHost;
int? get quoteApiPort;
String get authPublicKey;
String get sentryDsn;
}
class EnvImpl implements Env {
@override
String get quoteApiScheme => const String.fromEnvironment('QUOTE_API_SCHEME');
@override
String get quoteApiHost => const String.fromEnvironment('QUOTE_API_HOST');
@override
int? get quoteApiPort =>
int.tryParse(const String.fromEnvironment('QUOTE_API_PORT'));
@override
String get authPublicKey => const String.fromEnvironment('AUTH_PUBLIC_KEY');
@override
String get sentryDsn => const String.fromEnvironment('SENTRY_DSN');
}

View file

@ -0,0 +1,16 @@
/// Exception class for server error
/// Generally, this exception is thrown when the server returns an error response
class ServerException implements Exception {
const ServerException(this.message);
final String message;
}
/// Exception class for unauthorized client error
/// this exception is thrown when the client is not authorized
/// to access the resource (server returns 401)
class UnauthorizedException implements Exception {
const UnauthorizedException(this.message);
final String message;
}

View file

@ -0,0 +1,28 @@
import 'package:equatable/equatable.dart';
/// Base class for all failures
abstract class Failure extends Equatable {
/// Default constructor
const Failure({required this.message, this.cause});
/// Message of the failure
final String message;
/// Cause of the failure
final Exception? cause;
@override
List<Object?> get props => [message, cause];
}
class ClientFailure extends Failure {
const ClientFailure({required super.message, super.cause});
}
class ServerFailure extends Failure {
const ServerFailure({required super.message, super.cause});
}
class UnknownFailure extends Failure {
const UnknownFailure({required super.message, super.cause});
}

View file

@ -0,0 +1,8 @@
import 'package:sentry_flutter/sentry_flutter.dart';
/// Report error to Sentry
void reportError({required dynamic error, StackTrace? stackTrace}) {
if (error == null) return;
Sentry.captureException(error, stackTrace: stackTrace);
}

View file

@ -0,0 +1,13 @@
import 'package:freezed_annotation/freezed_annotation.dart';
part 'server_error_model.freezed.dart';
part 'server_error_model.g.dart';
@freezed
abstract class ServerErrorModel with _$ServerErrorModel {
const factory ServerErrorModel({required String message, required int code}) =
_ServerErrorModel;
factory ServerErrorModel.fromJson(Map<String, dynamic> json) =>
_$ServerErrorModelFromJson(json);
}

View file

@ -0,0 +1,280 @@
// 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 'server_error_model.dart';
// **************************************************************************
// FreezedGenerator
// **************************************************************************
// dart format off
T _$identity<T>(T value) => value;
/// @nodoc
mixin _$ServerErrorModel {
String get message; int get code;
/// Create a copy of ServerErrorModel
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
@pragma('vm:prefer-inline')
$ServerErrorModelCopyWith<ServerErrorModel> get copyWith => _$ServerErrorModelCopyWithImpl<ServerErrorModel>(this as ServerErrorModel, _$identity);
/// Serializes this ServerErrorModel to a JSON map.
Map<String, dynamic> toJson();
@override
bool operator ==(Object other) {
return identical(this, other) || (other.runtimeType == runtimeType&&other is ServerErrorModel&&(identical(other.message, message) || other.message == message)&&(identical(other.code, code) || other.code == code));
}
@JsonKey(includeFromJson: false, includeToJson: false)
@override
int get hashCode => Object.hash(runtimeType,message,code);
@override
String toString() {
return 'ServerErrorModel(message: $message, code: $code)';
}
}
/// @nodoc
abstract mixin class $ServerErrorModelCopyWith<$Res> {
factory $ServerErrorModelCopyWith(ServerErrorModel value, $Res Function(ServerErrorModel) _then) = _$ServerErrorModelCopyWithImpl;
@useResult
$Res call({
String message, int code
});
}
/// @nodoc
class _$ServerErrorModelCopyWithImpl<$Res>
implements $ServerErrorModelCopyWith<$Res> {
_$ServerErrorModelCopyWithImpl(this._self, this._then);
final ServerErrorModel _self;
final $Res Function(ServerErrorModel) _then;
/// Create a copy of ServerErrorModel
/// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline') @override $Res call({Object? message = null,Object? code = null,}) {
return _then(_self.copyWith(
message: null == message ? _self.message : message // ignore: cast_nullable_to_non_nullable
as String,code: null == code ? _self.code : code // ignore: cast_nullable_to_non_nullable
as int,
));
}
}
/// Adds pattern-matching-related methods to [ServerErrorModel].
extension ServerErrorModelPatterns on ServerErrorModel {
/// 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<TResult extends Object?>(TResult Function( _ServerErrorModel value)? $default,{required TResult orElse(),}){
final _that = this;
switch (_that) {
case _ServerErrorModel() when $default != null:
return $default(_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<TResult extends Object?>(TResult Function( _ServerErrorModel value) $default,){
final _that = this;
switch (_that) {
case _ServerErrorModel():
return $default(_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<TResult extends Object?>(TResult? Function( _ServerErrorModel value)? $default,){
final _that = this;
switch (_that) {
case _ServerErrorModel() when $default != null:
return $default(_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<TResult extends Object?>(TResult Function( String message, int code)? $default,{required TResult orElse(),}) {final _that = this;
switch (_that) {
case _ServerErrorModel() when $default != null:
return $default(_that.message,_that.code);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<TResult extends Object?>(TResult Function( String message, int code) $default,) {final _that = this;
switch (_that) {
case _ServerErrorModel():
return $default(_that.message,_that.code);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<TResult extends Object?>(TResult? Function( String message, int code)? $default,) {final _that = this;
switch (_that) {
case _ServerErrorModel() when $default != null:
return $default(_that.message,_that.code);case _:
return null;
}
}
}
/// @nodoc
@JsonSerializable()
class _ServerErrorModel implements ServerErrorModel {
const _ServerErrorModel({required this.message, required this.code});
factory _ServerErrorModel.fromJson(Map<String, dynamic> json) => _$ServerErrorModelFromJson(json);
@override final String message;
@override final int code;
/// Create a copy of ServerErrorModel
/// with the given fields replaced by the non-null parameter values.
@override @JsonKey(includeFromJson: false, includeToJson: false)
@pragma('vm:prefer-inline')
_$ServerErrorModelCopyWith<_ServerErrorModel> get copyWith => __$ServerErrorModelCopyWithImpl<_ServerErrorModel>(this, _$identity);
@override
Map<String, dynamic> toJson() {
return _$ServerErrorModelToJson(this, );
}
@override
bool operator ==(Object other) {
return identical(this, other) || (other.runtimeType == runtimeType&&other is _ServerErrorModel&&(identical(other.message, message) || other.message == message)&&(identical(other.code, code) || other.code == code));
}
@JsonKey(includeFromJson: false, includeToJson: false)
@override
int get hashCode => Object.hash(runtimeType,message,code);
@override
String toString() {
return 'ServerErrorModel(message: $message, code: $code)';
}
}
/// @nodoc
abstract mixin class _$ServerErrorModelCopyWith<$Res> implements $ServerErrorModelCopyWith<$Res> {
factory _$ServerErrorModelCopyWith(_ServerErrorModel value, $Res Function(_ServerErrorModel) _then) = __$ServerErrorModelCopyWithImpl;
@override @useResult
$Res call({
String message, int code
});
}
/// @nodoc
class __$ServerErrorModelCopyWithImpl<$Res>
implements _$ServerErrorModelCopyWith<$Res> {
__$ServerErrorModelCopyWithImpl(this._self, this._then);
final _ServerErrorModel _self;
final $Res Function(_ServerErrorModel) _then;
/// Create a copy of ServerErrorModel
/// with the given fields replaced by the non-null parameter values.
@override @pragma('vm:prefer-inline') $Res call({Object? message = null,Object? code = null,}) {
return _then(_ServerErrorModel(
message: null == message ? _self.message : message // ignore: cast_nullable_to_non_nullable
as String,code: null == code ? _self.code : code // ignore: cast_nullable_to_non_nullable
as int,
));
}
}
// dart format on

View file

@ -0,0 +1,16 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'server_error_model.dart';
// **************************************************************************
// JsonSerializableGenerator
// **************************************************************************
_ServerErrorModel _$ServerErrorModelFromJson(Map<String, dynamic> json) =>
_ServerErrorModel(
message: json['message'] as String,
code: (json['code'] as num).toInt(),
);
Map<String, dynamic> _$ServerErrorModelToJson(_ServerErrorModel instance) =>
<String, dynamic>{'message': instance.message, 'code': instance.code};

View file

@ -0,0 +1,51 @@
import 'dart:convert';
import 'dart:io';
import 'package:flutter/foundation.dart';
import 'package:http/http.dart' as http;
import 'package:kuwot/core/error/exception.dart';
/// Network interface
abstract class Network {
/// Get data from uri
Future<String> get(Uri uri, {Map<String, String>? headers});
/// Post data to uri
Future<String> post(Uri uri, {Map<String, String>? headers, Object? body});
}
/// Network implementation
class NetworkImpl implements Network {
final _client = http.Client();
@override
Future<String> get(Uri uri, {Map<String, String>? headers}) async {
if (kDebugMode) {
print('GET: $uri, headers: $headers');
}
final response = await _client.get(uri, headers: headers);
final stringResponse = utf8.decode(response.bodyBytes);
if (response.statusCode == HttpStatus.unauthorized) {
throw UnauthorizedException(stringResponse);
}
if (response.statusCode != HttpStatus.ok) {
throw ServerException(stringResponse);
}
return stringResponse;
}
@override
Future<String> post(
Uri uri, {
Map<String, String>? headers,
Object? body,
}) async {
if (kDebugMode) {
print('POST: $uri, headers: $headers, body: $body');
}
final response = await _client.post(uri, headers: headers, body: body);
return utf8.decode(response.bodyBytes);
}
}

View file

@ -0,0 +1,21 @@
// ignore_for_file: public_member_api_docs, strict_raw_type
import 'dart:developer' as dev;
import 'package:flutter_bloc/flutter_bloc.dart';
class AppBlocObserver extends BlocObserver {
@override
void onError(BlocBase bloc, Object error, StackTrace stackTrace) {
dev.log("[bloc_error] $bloc\nerror: $error\nstacktrace: $stackTrace");
super.onError(bloc, error, stackTrace);
}
@override
void onChange(BlocBase bloc, Change change) {
dev.log(
"[${bloc.runtimeType}] ${DateTime.now().toIso8601String()}\nFrom: ${change.currentState}\nNext: ${change.nextState}",
);
super.onChange(bloc, change);
}
}

View file

@ -0,0 +1,24 @@
import 'package:kuwot/core/data/local/config.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
/// Theme mode cubit for theme mode management
class ThemeModeCubit extends Cubit<ThemeMode> {
/// Default [ThemeMode] is [ThemeMode.system]
ThemeModeCubit({
required this.themeModeConfig,
required this.initialThemeMode,
}) : super(initialThemeMode);
/// Theme mode config
final Config<ThemeMode> themeModeConfig;
/// Initial theme mode
final ThemeMode initialThemeMode;
/// Set theme mode
void setThemeMode(ThemeMode themeMode) {
themeModeConfig.set(themeMode);
emit(themeMode);
}
}

View file

@ -0,0 +1,18 @@
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:kuwot/core/data/local/config.dart';
import 'package:kuwot/core/data/local/translation_target_config.dart';
class TranslationTargetCubit extends Cubit<TranslationTarget> {
TranslationTargetCubit({
required this.translationTargetConfig,
required this.initialTranslationTarget,
}) : super(initialTranslationTarget);
final Config<TranslationTarget> translationTargetConfig;
final TranslationTarget initialTranslationTarget;
void set(TranslationTarget translationTarget) {
translationTargetConfig.set(translationTarget);
emit(translationTarget);
}
}

View file

@ -0,0 +1,6 @@
abstract class ErrorState {
final String message;
final Exception? cause;
const ErrorState({required this.message, this.cause});
}

View file

@ -0,0 +1,44 @@
import 'package:flutter/material.dart';
class ErrorRetrySnackbar {
final BuildContext context;
final String errorMessage;
final VoidCallback onRetry;
const ErrorRetrySnackbar(
this.context, {
required this.errorMessage,
required this.onRetry,
});
SnackBar _build() {
return SnackBar(
behavior: SnackBarBehavior.floating,
duration: const Duration(days: 1),
backgroundColor: Colors.red[600],
content: Text(errorMessage),
action: SnackBarAction(
textColor: Colors.white,
label: 'RETRY',
onPressed: () {
onRetry();
ScaffoldMessenger.of(context).hideCurrentSnackBar();
},
),
);
}
static void show(
BuildContext context, {
required String errorMessage,
required VoidCallback onRetry,
}) {
ScaffoldMessenger.of(context).showSnackBar(
ErrorRetrySnackbar(
context,
errorMessage: errorMessage,
onRetry: onRetry,
)._build(),
);
}
}

View file

@ -0,0 +1,19 @@
import 'package:flutter/material.dart';
import 'package:google_fonts/google_fonts.dart';
/// App light theme
ThemeData lightTheme = ThemeData(
colorScheme: ColorScheme.fromSeed(seedColor: const Color(0xFF343A40)),
useMaterial3: true,
fontFamily: GoogleFonts.dmSans().fontFamily,
);
/// App dark theme
ThemeData darkTheme = ThemeData(
colorScheme: ColorScheme.fromSeed(
seedColor: const Color(0xFF212529),
brightness: Brightness.dark,
),
useMaterial3: true,
fontFamily: GoogleFonts.dmSans().fontFamily,
);

View file

@ -0,0 +1,13 @@
import 'package:auto_route/auto_route.dart';
import 'package:kuwot/core/router/app_router.gr.dart';
@AutoRouterConfig()
class AppRouter extends RootStackRouter {
@override
List<AutoRoute> get routes => [
AutoRoute(page: AppUpdateRoute.page, initial: true),
AutoRoute(page: QuoteRoute.page),
AutoRoute(page: AppSettingsRoute.page),
AutoRoute(page: DonationRoute.page),
];
}

View file

@ -0,0 +1,83 @@
// 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 _i5;
import 'package:kuwot/features/app_settings/presentation/app_settings_page.dart'
as _i1;
import 'package:kuwot/features/in_app_purchase/presentation/donation_page.dart'
as _i3;
import 'package:kuwot/features/in_app_update/presentation/app_update_page.dart'
as _i2;
import 'package:kuwot/features/quote/presentation/quote_page.dart' as _i4;
/// generated route for
/// [_i1.AppSettingsPage]
class AppSettingsRoute extends _i5.PageRouteInfo<void> {
const AppSettingsRoute({List<_i5.PageRouteInfo>? children})
: super(AppSettingsRoute.name, initialChildren: children);
static const String name = 'AppSettingsRoute';
static _i5.PageInfo page = _i5.PageInfo(
name,
builder: (data) {
return const _i1.AppSettingsPage();
},
);
}
/// generated route for
/// [_i2.AppUpdatePage]
class AppUpdateRoute extends _i5.PageRouteInfo<void> {
const AppUpdateRoute({List<_i5.PageRouteInfo>? children})
: super(AppUpdateRoute.name, initialChildren: children);
static const String name = 'AppUpdateRoute';
static _i5.PageInfo page = _i5.PageInfo(
name,
builder: (data) {
return const _i2.AppUpdatePage();
},
);
}
/// generated route for
/// [_i3.DonationPage]
class DonationRoute extends _i5.PageRouteInfo<void> {
const DonationRoute({List<_i5.PageRouteInfo>? children})
: super(DonationRoute.name, initialChildren: children);
static const String name = 'DonationRoute';
static _i5.PageInfo page = _i5.PageInfo(
name,
builder: (data) {
return const _i3.DonationPage();
},
);
}
/// generated route for
/// [_i4.QuotePage]
class QuoteRoute extends _i5.PageRouteInfo<void> {
const QuoteRoute({List<_i5.PageRouteInfo>? children})
: super(QuoteRoute.name, initialChildren: children);
static const String name = 'QuoteRoute';
static _i5.PageInfo page = _i5.PageInfo(
name,
builder: (data) {
return const _i4.QuotePage();
},
);
}

13
lib/core/time.dart Normal file
View file

@ -0,0 +1,13 @@
/// Humble class for time related operations.
abstract class Time {
/// Get unix timestamp in seconds.
int getUnixTimestamp();
}
/// Implementation of [Time] using [DateTime].
class TimeImpl implements Time {
@override
int getUnixTimestamp() {
return DateTime.now().millisecondsSinceEpoch ~/ 1000;
}
}

View file

@ -0,0 +1,64 @@
import 'package:auto_route/auto_route.dart';
import 'package:kuwot/core/presentation/bloc/config/theme_mode_cubit.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:kuwot/features/app_settings/presentation/widgets/about_widget.dart';
import 'package:package_info_plus/package_info_plus.dart';
@RoutePage()
class AppSettingsPage extends StatefulWidget {
const AppSettingsPage({super.key});
@override
State<AppSettingsPage> createState() => _AppSettingsPageState();
}
class _AppSettingsPageState extends State<AppSettingsPage> {
@override
Widget build(BuildContext context) {
final themeSetting = Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
const Text('App Theme'),
DropdownButton<ThemeMode>(
items: const [
DropdownMenuItem(value: ThemeMode.system, child: Text('System')),
DropdownMenuItem(value: ThemeMode.light, child: Text('Light')),
DropdownMenuItem(value: ThemeMode.dark, child: Text('Dark')),
],
value: context.watch<ThemeModeCubit>().state,
onChanged: (value) {
context.read<ThemeModeCubit>().setThemeMode(value!);
},
),
],
);
final appVersion = Center(
child: FutureBuilder<PackageInfo>(
future: PackageInfo.fromPlatform(),
builder: (context, snapshot) {
if (snapshot.hasData) {
return Text(
'v${snapshot.data!.version} (${snapshot.data!.buildNumber})',
);
}
return const Text('...');
},
),
);
return Scaffold(
appBar: AppBar(title: const Text('App Settings')),
body: ListView(
padding: const EdgeInsets.symmetric(horizontal: 16),
children: [
themeSetting,
const SizedBox(height: 20),
appVersion,
const SizedBox(height: 20),
const AboutWidget(),
],
),
);
}
}

View file

@ -0,0 +1,69 @@
import 'package:flutter/material.dart';
import 'package:url_launcher/url_launcher_string.dart';
class AboutWidget extends StatelessWidget {
const AboutWidget({super.key});
@override
Widget build(BuildContext context) {
return Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
Text('Links & Credits', style: Theme.of(context).textTheme.titleMedium),
const Divider(),
_buildCreditItem(
context,
title: 'Kuwot App Source',
description:
'Source code for this app. Any suggestions or contributions are welcome.',
url: 'https://github.com/dhemasnurjaya/kuwot-app',
),
_buildCreditItem(
context,
title: 'Quotes-500K',
description:
'Large quotes dataset by Shivali Goel, Rishi Madhok, Shweta Garg. Initially created for "Proposing Contextually Relevant Quotes for Images" journal.',
url: 'https://github.com/ShivaliGoel/Quotes-500K',
),
_buildCreditItem(
context,
title: 'Unsplash',
description:
'Over 6 million free high-resolution photos and illustrations brought to you by the worlds most generous community of contributors.',
url: 'https://unsplash.com/',
),
],
);
}
Widget _buildCreditItem(
BuildContext context, {
required String title,
required String description,
required String url,
}) {
return Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
Text(title, style: Theme.of(context).textTheme.titleMedium),
Text(description),
const SizedBox(height: 4),
GestureDetector(
child: Text(
url,
style: Theme.of(context).textTheme.bodyMedium?.copyWith(
decoration: TextDecoration.underline,
color: Colors.indigoAccent,
decorationColor: Colors.indigoAccent,
),
),
onTap: () async {
await launchUrlString(url);
},
),
const SizedBox(height: 16),
],
);
}
}

View file

@ -0,0 +1,48 @@
import 'package:in_app_purchase/in_app_purchase.dart';
const _consumableProductIds = <String>{
'donate_consumable_low',
'donate_consumable_mid',
'donate_consumable_high',
};
abstract class InAppPurchaseRemoteDataSource {
Stream<List<PurchaseDetails>> get purchaseStream;
Future<List<ProductDetails>> getConsumableProducts();
Future<bool> purchaseConsumableProduct(ProductDetails product);
Future<void> completePurchase(PurchaseDetails purchaseDetails);
}
class InAppPurchaseRemoteDataSourceImpl
implements InAppPurchaseRemoteDataSource {
InAppPurchaseRemoteDataSourceImpl({required this.iap});
final InAppPurchase iap;
@override
Stream<List<PurchaseDetails>> get purchaseStream => iap.purchaseStream;
@override
Future<List<ProductDetails>> getConsumableProducts() async {
final response = await iap.queryProductDetails(_consumableProductIds);
final productDetails = response.productDetails;
productDetails.sort((a, b) => a.price.compareTo(b.price));
return productDetails;
}
@override
Future<bool> purchaseConsumableProduct(ProductDetails product) async {
final response = await iap.buyConsumable(
purchaseParam: PurchaseParam(productDetails: product),
);
return response;
}
@override
Future<void> completePurchase(PurchaseDetails purchaseDetails) {
return iap.completePurchase(purchaseDetails);
}
}

View file

@ -0,0 +1,51 @@
import 'package:fpdart/fpdart.dart';
import 'package:in_app_purchase/in_app_purchase.dart';
import 'package:kuwot/core/error/failure.dart';
import 'package:kuwot/features/in_app_purchase/data/data_sources/remote/in_app_purchase_remote_data_source.dart';
import 'package:kuwot/features/in_app_purchase/domain/repositories/in_app_purchase_repository.dart';
class InAppPurchaseRepositoryImpl implements InAppPurchaseRepository {
InAppPurchaseRepositoryImpl({required this.inAppPurchaseDataSource});
final InAppPurchaseRemoteDataSource inAppPurchaseDataSource;
@override
Stream<List<PurchaseDetails>> get purchaseStream =>
inAppPurchaseDataSource.purchaseStream;
@override
Future<Either<Failure, List<ProductDetails>>> getConsumableProducts() async {
try {
final response = await inAppPurchaseDataSource.getConsumableProducts();
return right(response);
} catch (e) {
return left(UnknownFailure(message: e.toString()));
}
}
@override
Future<Either<Failure, bool>> purchaseConsumableProduct(
ProductDetails product,
) async {
try {
final response = await inAppPurchaseDataSource.purchaseConsumableProduct(
product,
);
return right(response);
} catch (e) {
return left(UnknownFailure(message: e.toString()));
}
}
@override
Future<Either<Failure, void>> completePurchase(
PurchaseDetails purchaseDetails,
) async {
try {
await inAppPurchaseDataSource.completePurchase(purchaseDetails);
return right(null);
} on Exception catch (e) {
return left(UnknownFailure(message: e.toString()));
}
}
}

View file

@ -0,0 +1,17 @@
import 'package:fpdart/fpdart.dart';
import 'package:in_app_purchase/in_app_purchase.dart';
import 'package:kuwot/core/error/failure.dart';
abstract class InAppPurchaseRepository {
Stream<List<PurchaseDetails>> get purchaseStream;
Future<Either<Failure, List<ProductDetails>>> getConsumableProducts();
Future<Either<Failure, bool>> purchaseConsumableProduct(
ProductDetails product,
);
Future<Either<Failure, void>> completePurchase(
PurchaseDetails purchaseDetails,
);
}

View file

@ -0,0 +1,17 @@
import 'package:fpdart/fpdart.dart';
import 'package:in_app_purchase/in_app_purchase.dart';
import 'package:kuwot/core/domain/no_params.dart';
import 'package:kuwot/core/domain/use_case.dart';
import 'package:kuwot/core/error/failure.dart';
import 'package:kuwot/features/in_app_purchase/domain/repositories/in_app_purchase_repository.dart';
class GetConsumableProducts extends UseCase<List<ProductDetails>, NoParams> {
final InAppPurchaseRepository repository;
GetConsumableProducts(this.repository);
@override
Future<Either<Failure, List<ProductDetails>>> call(NoParams params) async {
return repository.getConsumableProducts();
}
}

View file

@ -0,0 +1,21 @@
import 'dart:async';
import 'package:fpdart/fpdart.dart';
import 'package:in_app_purchase/in_app_purchase.dart';
import 'package:kuwot/core/domain/no_params.dart';
import 'package:kuwot/core/domain/use_case.dart';
import 'package:kuwot/core/error/failure.dart';
import 'package:kuwot/features/in_app_purchase/domain/repositories/in_app_purchase_repository.dart';
class ListenPurchase
implements UseCase<Stream<List<PurchaseDetails>>, NoParams> {
ListenPurchase(this.repository);
final InAppPurchaseRepository repository;
@override
Future<Either<Failure, Stream<List<PurchaseDetails>>>> call(NoParams params) {
// it always return right because I dont think it will throw an error
return Future.value(right(repository.purchaseStream));
}
}

View file

@ -0,0 +1,29 @@
import 'package:equatable/equatable.dart';
import 'package:fpdart/fpdart.dart';
import 'package:in_app_purchase/in_app_purchase.dart';
import 'package:kuwot/core/domain/use_case.dart';
import 'package:kuwot/core/error/failure.dart';
import 'package:kuwot/features/in_app_purchase/domain/repositories/in_app_purchase_repository.dart';
class PurchaseConsumableProduct
extends UseCase<bool, PurchaseConsumableProductParams> {
final InAppPurchaseRepository repository;
PurchaseConsumableProduct(this.repository);
@override
Future<Either<Failure, bool>> call(
PurchaseConsumableProductParams params,
) async {
return repository.purchaseConsumableProduct(params.productDetails);
}
}
class PurchaseConsumableProductParams extends Equatable {
final ProductDetails productDetails;
const PurchaseConsumableProductParams({required this.productDetails});
@override
List<Object?> get props => [productDetails];
}

View file

@ -0,0 +1,59 @@
import 'dart:async';
import 'package:equatable/equatable.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:in_app_purchase/in_app_purchase.dart';
import 'package:kuwot/core/domain/no_params.dart';
import 'package:kuwot/core/presentation/bloc/error_state.dart';
import 'package:kuwot/features/in_app_purchase/domain/use_case/get_consumable_products.dart';
import 'package:kuwot/features/in_app_purchase/domain/use_case/purchase_consumable_product.dart';
part 'in_app_purchase_events.dart';
part 'in_app_purchase_states.dart';
class InAppPurchaseBloc extends Bloc<InAppPurchaseEvent, InAppPurchaseState> {
InAppPurchaseBloc({
required this.getConsumableProducts,
required this.purchaseConsumableProduct,
}) : super(const InAppPurchaseInitialState()) {
on<GetConsumableProductsEvent>(_getConsumableProducts);
on<PurchaseConsumableProductEvent>(_purchaseConsumableProduct);
}
final GetConsumableProducts getConsumableProducts;
final PurchaseConsumableProduct purchaseConsumableProduct;
Future<void> _getConsumableProducts(
GetConsumableProductsEvent event,
Emitter<InAppPurchaseState> emit,
) async {
emit(const GettingConsumableProductsState());
final result = await getConsumableProducts(const NoParams());
result.fold(
(failure) {
emit(PurchaseErrorState(message: failure.message));
},
(products) {
emit(ConsumableProductsLoadedState(products));
},
);
}
Future<void> _purchaseConsumableProduct(
PurchaseConsumableProductEvent event,
Emitter<InAppPurchaseState> emit,
) async {
emit(PurchasingConsumableProductState(event.productDetails));
final result = await purchaseConsumableProduct(
PurchaseConsumableProductParams(productDetails: event.productDetails),
);
result.fold(
(failure) {
emit(PurchaseErrorState(message: failure.message));
},
(success) {
emit(ConsumableProductPurchasedState(result: success));
},
);
}
}

View file

@ -0,0 +1,21 @@
part of 'in_app_purchase_bloc.dart';
abstract class InAppPurchaseEvent extends Equatable {
const InAppPurchaseEvent();
}
class GetConsumableProductsEvent extends InAppPurchaseEvent {
const GetConsumableProductsEvent();
@override
List<Object> get props => [];
}
class PurchaseConsumableProductEvent extends InAppPurchaseEvent {
final ProductDetails productDetails;
const PurchaseConsumableProductEvent(this.productDetails);
@override
List<Object> get props => [productDetails];
}

View file

@ -0,0 +1,59 @@
part of 'in_app_purchase_bloc.dart';
abstract class InAppPurchaseState extends Equatable {
const InAppPurchaseState();
}
class InAppPurchaseInitialState extends InAppPurchaseState {
const InAppPurchaseInitialState();
@override
List<Object> get props => [];
}
class GettingConsumableProductsState extends InAppPurchaseState {
const GettingConsumableProductsState();
@override
List<Object> get props => [];
}
class ConsumableProductsLoadedState extends InAppPurchaseState {
final List<ProductDetails> products;
const ConsumableProductsLoadedState(this.products);
@override
List<Object> get props => [products];
}
class PurchasingConsumableProductState extends InAppPurchaseState {
final ProductDetails productDetails;
const PurchasingConsumableProductState(this.productDetails);
@override
List<Object> get props => [productDetails];
}
class ConsumableProductPurchasedState extends InAppPurchaseState {
const ConsumableProductPurchasedState({required this.result});
final bool result;
@override
List<Object> get props => [result];
}
class PurchaseErrorState extends InAppPurchaseState implements ErrorState {
@override
final String message;
@override
final Exception? cause;
const PurchaseErrorState({required this.message, this.cause});
@override
List<Object?> get props => [message, cause];
}

View file

@ -0,0 +1,34 @@
import 'dart:async';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:in_app_purchase/in_app_purchase.dart';
import 'package:kuwot/features/in_app_purchase/domain/repositories/in_app_purchase_repository.dart';
/// Cubit that listens to purchase details stream from [InAppPurchaseRepository]
class PurchaseDetailsCubit extends Cubit<List<PurchaseDetails>> {
/// Repository to get purchase details stream
final InAppPurchaseRepository repository;
/// Subscription to purchase details stream
StreamSubscription<List<PurchaseDetails>>? _purchaseDetailsSubscription;
PurchaseDetailsCubit(this.repository) : super([]) {
// listen to purchase details stream and emit the event to the UI
_purchaseDetailsSubscription = repository.purchaseStream.listen((event) {
emit(event);
// complete the purchases
event
.where((element) => element.status == PurchaseStatus.purchased)
.forEach((element) {
unawaited(repository.completePurchase(element));
});
});
}
@override
Future<void> close() {
_purchaseDetailsSubscription?.cancel();
return super.close();
}
}

View file

@ -0,0 +1,92 @@
import 'package:auto_route/auto_route.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:in_app_purchase/in_app_purchase.dart';
import 'package:kuwot/features/in_app_purchase/presentation/bloc/in_app_purchase_bloc.dart';
@RoutePage()
class DonationPage extends StatefulWidget {
const DonationPage({super.key});
@override
State<DonationPage> createState() => _DonationPageState();
}
class _DonationPageState extends State<DonationPage> {
final _donationMessage =
'I built this app with love and coffee. If you find it useful, please consider buying me a coffee. Your donation will help me keep the app running and updated. Thank you! ☕';
final List<ProductDetails> _products = [];
@override
void initState() {
super.initState();
// get consumable products
context.read<InAppPurchaseBloc>().add(const GetConsumableProductsEvent());
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('Coffee time?')),
body: BlocListener<InAppPurchaseBloc, InAppPurchaseState>(
listener: (context, state) {
if (state is ConsumableProductsLoadedState) {
setState(() {
_products.clear();
_products.addAll(state.products);
});
}
},
child: ListView(
padding: const EdgeInsets.all(16),
children: [
Padding(
padding: const EdgeInsets.fromLTRB(8, 0, 8, 16),
child: Text(_donationMessage),
),
..._buildProductList(),
],
),
),
);
}
List<Widget> _buildProductList() {
return _products.map((product) {
final title = product.title.replaceAll('(Kuwot)', '');
final description = product.description.replaceAll(
RegExp(r'[\r\n]+'),
'',
);
final productCard = Card(
clipBehavior: Clip.antiAlias,
child: InkWell(
onTap: () => context.read<InAppPurchaseBloc>().add(
PurchaseConsumableProductEvent(product),
),
child: Padding(
padding: const EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
Text(title, style: Theme.of(context).textTheme.bodyLarge),
Text(description),
const SizedBox(height: 8),
Text(
product.price,
style: Theme.of(
context,
).textTheme.bodySmall?.copyWith(fontWeight: FontWeight.bold),
),
],
),
),
),
);
return productCard;
}).toList();
}
}

View file

@ -0,0 +1,31 @@
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:in_app_purchase/in_app_purchase.dart';
import 'package:kuwot/features/in_app_purchase/presentation/bloc/purchase_details_cubit.dart';
import 'package:kuwot/utilities.dart';
class InAppPurchaseListener extends StatelessWidget {
const InAppPurchaseListener({required this.child, super.key});
final Widget child;
@override
Widget build(BuildContext context) {
return BlocListener<PurchaseDetailsCubit, List<PurchaseDetails>>(
listener: (context, state) {
if (state.last.status == PurchaseStatus.purchased) {
showSnackbar('Coffee received, thank you! ☕');
}
if (state.last.status == PurchaseStatus.pending) {
showSnackbar('Coffee is on the way! ☕');
}
if (state.last.status == PurchaseStatus.error) {
showSnackbar('Something went wrong, failed to send coffee 😢');
}
},
child: child,
);
}
}

View file

@ -0,0 +1,58 @@
import 'package:auto_route/auto_route.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flutter_svg/svg.dart';
import 'package:kuwot/core/router/app_router.gr.dart';
import 'package:kuwot/features/in_app_update/presentation/bloc/in_app_update_bloc.dart';
@RoutePage()
class AppUpdatePage extends StatefulWidget {
const AppUpdatePage({super.key});
@override
State<AppUpdatePage> createState() => _AppUpdatePageState();
}
class _AppUpdatePageState extends State<AppUpdatePage> {
@override
void initState() {
// check for update
context.read<InAppUpdateBloc>().add(const InAppUpdateCheckEvent());
super.initState();
}
@override
Widget build(BuildContext context) {
return BlocListener<InAppUpdateBloc, InAppUpdateState>(
listener: (context, state) {
if (state is InAppUpdateAvailableState) {
context.read<InAppUpdateBloc>().add(const InAppUpdateStartEvent());
}
if (state is InAppUpdateUnavailableState) {
AutoRouter.of(context).replace(const QuoteRoute());
}
},
child: Scaffold(
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
SvgPicture.asset(
'assets/svgs/chat-quote.svg',
colorFilter: ColorFilter.mode(
Theme.of(context).colorScheme.onSurface,
BlendMode.srcIn,
),
height: 100,
),
const SizedBox(height: 20),
const SizedBox(width: 100, child: LinearProgressIndicator()),
],
),
),
),
);
}
}

View file

@ -0,0 +1,41 @@
import 'package:equatable/equatable.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:in_app_update/in_app_update.dart';
import 'package:kuwot/core/app_updater.dart';
part 'in_app_update_bloc_events.dart';
part 'in_app_update_bloc_states.dart';
class InAppUpdateBloc extends Bloc<InAppUpdateEvent, InAppUpdateState> {
InAppUpdateBloc({required this.appUpdater})
: super(const InAppUpdateInitialState()) {
on<InAppUpdateCheckEvent>(_onInAppUpdateCheckEvent);
on<InAppUpdateStartEvent>(_onInAppUpdateStartEvent);
}
final AppUpdater appUpdater;
Future<void> _onInAppUpdateCheckEvent(
InAppUpdateCheckEvent event,
Emitter<InAppUpdateState> emit,
) async {
try {
emit(const InAppUpdateCheckingState());
final updateInfo = await appUpdater.checkForUpdate();
if (updateInfo.updateAvailability == UpdateAvailability.updateAvailable) {
emit(const InAppUpdateAvailableState());
} else {
emit(const InAppUpdateUnavailableState());
}
} on Exception catch (_) {
emit(const InAppUpdateUnavailableState());
}
}
Future<void> _onInAppUpdateStartEvent(
InAppUpdateStartEvent event,
Emitter<InAppUpdateState> emit,
) async {
await appUpdater.update();
}
}

View file

@ -0,0 +1,19 @@
part of 'in_app_update_bloc.dart';
abstract class InAppUpdateEvent extends Equatable {
const InAppUpdateEvent();
}
class InAppUpdateCheckEvent extends InAppUpdateEvent {
const InAppUpdateCheckEvent();
@override
List<Object?> get props => [];
}
class InAppUpdateStartEvent extends InAppUpdateEvent {
const InAppUpdateStartEvent();
@override
List<Object?> get props => [];
}

View file

@ -0,0 +1,33 @@
part of 'in_app_update_bloc.dart';
abstract class InAppUpdateState extends Equatable {
const InAppUpdateState();
}
class InAppUpdateInitialState extends InAppUpdateState {
const InAppUpdateInitialState();
@override
List<Object> get props => [];
}
class InAppUpdateCheckingState extends InAppUpdateState {
const InAppUpdateCheckingState();
@override
List<Object> get props => [];
}
class InAppUpdateAvailableState extends InAppUpdateState {
const InAppUpdateAvailableState();
@override
List<Object> get props => [];
}
class InAppUpdateUnavailableState extends InAppUpdateState {
const InAppUpdateUnavailableState();
@override
List<Object> get props => [];
}

View file

@ -0,0 +1,84 @@
import 'dart:convert';
import 'package:kuwot/core/env.dart';
import 'package:kuwot/core/network/network.dart';
import 'package:kuwot/features/quote/data/models/image_model.dart';
import 'package:kuwot/features/quote/data/models/quote_model.dart';
import 'package:kuwot/features/quote/data/models/translation_model.dart';
const imagesPerPage = 10;
abstract class KuwotApiRemoteDataSource {
Future<QuoteModel> getQuote({String? query});
Future<QuoteModel> getTranslatedQuote(int id, {String? query});
Future<List<ImageModel>> getRandomImages();
Future<List<TranslationModel>> getTranslations();
}
class KuwotApiRemoteApiImpl implements KuwotApiRemoteDataSource {
final Env env;
final Network network;
KuwotApiRemoteApiImpl({required this.env, required this.network});
@override
Future<QuoteModel> getQuote({String? query}) async {
final uri = Uri(
scheme: env.quoteApiScheme,
host: env.quoteApiHost,
port: env.quoteApiPort,
path: 'kuwot/v1/quotes/random',
query: query,
);
final response = await network.get(uri);
final jsonResponse = jsonDecode(response) as Map<String, dynamic>;
return QuoteModel.fromJson(jsonResponse);
}
@override
Future<QuoteModel> getTranslatedQuote(int id, {String? query}) async {
final uri = Uri(
scheme: env.quoteApiScheme,
host: env.quoteApiHost,
port: env.quoteApiPort,
path: 'kuwot/v1/quotes/$id',
query: query,
);
final response = await network.get(uri);
final jsonResponse = jsonDecode(response) as Map<String, dynamic>;
return QuoteModel.fromJson(jsonResponse);
}
@override
Future<List<ImageModel>> getRandomImages() async {
final uri = Uri(
scheme: env.quoteApiScheme,
host: env.quoteApiHost,
port: env.quoteApiPort,
path: 'kuwot/v1/images',
);
final response = await network.get(uri);
final jsonResponse = jsonDecode(response) as List;
return jsonResponse
.map((e) => ImageModel.fromJson(e as Map<String, dynamic>))
.toList();
}
@override
Future<List<TranslationModel>> getTranslations() async {
final uri = Uri(
scheme: env.quoteApiScheme,
host: env.quoteApiHost,
port: env.quoteApiPort,
path: 'kuwot/v1/translations',
);
final response = await network.get(uri);
final jsonResponse = jsonDecode(response) as List;
return jsonResponse
.map((e) => TranslationModel.fromJson(e as Map<String, dynamic>))
.toList();
}
}

View file

@ -0,0 +1,27 @@
import 'package:freezed_annotation/freezed_annotation.dart';
part 'image_model.freezed.dart';
part 'image_model.g.dart';
@freezed
abstract class ImageModel with _$ImageModel {
const factory ImageModel({
required String id,
required String description,
required String color,
required String blurHash,
required String url,
required String originUrl,
required String authorName,
required String authorBio,
required String authorLocation,
required int authorTotalLikes,
required int authorTotalPhotos,
required bool authorIsForHire,
required String authorProfileImageUrl,
required String authorUrl,
}) = _ImageModel;
factory ImageModel.fromJson(Map<String, dynamic> json) =>
_$ImageModelFromJson(json);
}

View file

@ -0,0 +1,316 @@
// 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 'image_model.dart';
// **************************************************************************
// FreezedGenerator
// **************************************************************************
// dart format off
T _$identity<T>(T value) => value;
/// @nodoc
mixin _$ImageModel {
String get id; String get description; String get color; String get blurHash; String get url; String get originUrl; String get authorName; String get authorBio; String get authorLocation; int get authorTotalLikes; int get authorTotalPhotos; bool get authorIsForHire; String get authorProfileImageUrl; String get authorUrl;
/// Create a copy of ImageModel
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
@pragma('vm:prefer-inline')
$ImageModelCopyWith<ImageModel> get copyWith => _$ImageModelCopyWithImpl<ImageModel>(this as ImageModel, _$identity);
/// Serializes this ImageModel to a JSON map.
Map<String, dynamic> toJson();
@override
bool operator ==(Object other) {
return identical(this, other) || (other.runtimeType == runtimeType&&other is ImageModel&&(identical(other.id, id) || other.id == id)&&(identical(other.description, description) || other.description == description)&&(identical(other.color, color) || other.color == color)&&(identical(other.blurHash, blurHash) || other.blurHash == blurHash)&&(identical(other.url, url) || other.url == url)&&(identical(other.originUrl, originUrl) || other.originUrl == originUrl)&&(identical(other.authorName, authorName) || other.authorName == authorName)&&(identical(other.authorBio, authorBio) || other.authorBio == authorBio)&&(identical(other.authorLocation, authorLocation) || other.authorLocation == authorLocation)&&(identical(other.authorTotalLikes, authorTotalLikes) || other.authorTotalLikes == authorTotalLikes)&&(identical(other.authorTotalPhotos, authorTotalPhotos) || other.authorTotalPhotos == authorTotalPhotos)&&(identical(other.authorIsForHire, authorIsForHire) || other.authorIsForHire == authorIsForHire)&&(identical(other.authorProfileImageUrl, authorProfileImageUrl) || other.authorProfileImageUrl == authorProfileImageUrl)&&(identical(other.authorUrl, authorUrl) || other.authorUrl == authorUrl));
}
@JsonKey(includeFromJson: false, includeToJson: false)
@override
int get hashCode => Object.hash(runtimeType,id,description,color,blurHash,url,originUrl,authorName,authorBio,authorLocation,authorTotalLikes,authorTotalPhotos,authorIsForHire,authorProfileImageUrl,authorUrl);
@override
String toString() {
return 'ImageModel(id: $id, description: $description, color: $color, blurHash: $blurHash, url: $url, originUrl: $originUrl, authorName: $authorName, authorBio: $authorBio, authorLocation: $authorLocation, authorTotalLikes: $authorTotalLikes, authorTotalPhotos: $authorTotalPhotos, authorIsForHire: $authorIsForHire, authorProfileImageUrl: $authorProfileImageUrl, authorUrl: $authorUrl)';
}
}
/// @nodoc
abstract mixin class $ImageModelCopyWith<$Res> {
factory $ImageModelCopyWith(ImageModel value, $Res Function(ImageModel) _then) = _$ImageModelCopyWithImpl;
@useResult
$Res call({
String id, String description, String color, String blurHash, String url, String originUrl, String authorName, String authorBio, String authorLocation, int authorTotalLikes, int authorTotalPhotos, bool authorIsForHire, String authorProfileImageUrl, String authorUrl
});
}
/// @nodoc
class _$ImageModelCopyWithImpl<$Res>
implements $ImageModelCopyWith<$Res> {
_$ImageModelCopyWithImpl(this._self, this._then);
final ImageModel _self;
final $Res Function(ImageModel) _then;
/// Create a copy of ImageModel
/// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline') @override $Res call({Object? id = null,Object? description = null,Object? color = null,Object? blurHash = null,Object? url = null,Object? originUrl = null,Object? authorName = null,Object? authorBio = null,Object? authorLocation = null,Object? authorTotalLikes = null,Object? authorTotalPhotos = null,Object? authorIsForHire = null,Object? authorProfileImageUrl = null,Object? authorUrl = null,}) {
return _then(_self.copyWith(
id: null == id ? _self.id : id // ignore: cast_nullable_to_non_nullable
as String,description: null == description ? _self.description : description // ignore: cast_nullable_to_non_nullable
as String,color: null == color ? _self.color : color // ignore: cast_nullable_to_non_nullable
as String,blurHash: null == blurHash ? _self.blurHash : blurHash // ignore: cast_nullable_to_non_nullable
as String,url: null == url ? _self.url : url // ignore: cast_nullable_to_non_nullable
as String,originUrl: null == originUrl ? _self.originUrl : originUrl // ignore: cast_nullable_to_non_nullable
as String,authorName: null == authorName ? _self.authorName : authorName // ignore: cast_nullable_to_non_nullable
as String,authorBio: null == authorBio ? _self.authorBio : authorBio // ignore: cast_nullable_to_non_nullable
as String,authorLocation: null == authorLocation ? _self.authorLocation : authorLocation // ignore: cast_nullable_to_non_nullable
as String,authorTotalLikes: null == authorTotalLikes ? _self.authorTotalLikes : authorTotalLikes // ignore: cast_nullable_to_non_nullable
as int,authorTotalPhotos: null == authorTotalPhotos ? _self.authorTotalPhotos : authorTotalPhotos // ignore: cast_nullable_to_non_nullable
as int,authorIsForHire: null == authorIsForHire ? _self.authorIsForHire : authorIsForHire // ignore: cast_nullable_to_non_nullable
as bool,authorProfileImageUrl: null == authorProfileImageUrl ? _self.authorProfileImageUrl : authorProfileImageUrl // ignore: cast_nullable_to_non_nullable
as String,authorUrl: null == authorUrl ? _self.authorUrl : authorUrl // ignore: cast_nullable_to_non_nullable
as String,
));
}
}
/// Adds pattern-matching-related methods to [ImageModel].
extension ImageModelPatterns on ImageModel {
/// 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<TResult extends Object?>(TResult Function( _ImageModel value)? $default,{required TResult orElse(),}){
final _that = this;
switch (_that) {
case _ImageModel() when $default != null:
return $default(_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<TResult extends Object?>(TResult Function( _ImageModel value) $default,){
final _that = this;
switch (_that) {
case _ImageModel():
return $default(_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<TResult extends Object?>(TResult? Function( _ImageModel value)? $default,){
final _that = this;
switch (_that) {
case _ImageModel() when $default != null:
return $default(_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<TResult extends Object?>(TResult Function( String id, String description, String color, String blurHash, String url, String originUrl, String authorName, String authorBio, String authorLocation, int authorTotalLikes, int authorTotalPhotos, bool authorIsForHire, String authorProfileImageUrl, String authorUrl)? $default,{required TResult orElse(),}) {final _that = this;
switch (_that) {
case _ImageModel() when $default != null:
return $default(_that.id,_that.description,_that.color,_that.blurHash,_that.url,_that.originUrl,_that.authorName,_that.authorBio,_that.authorLocation,_that.authorTotalLikes,_that.authorTotalPhotos,_that.authorIsForHire,_that.authorProfileImageUrl,_that.authorUrl);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<TResult extends Object?>(TResult Function( String id, String description, String color, String blurHash, String url, String originUrl, String authorName, String authorBio, String authorLocation, int authorTotalLikes, int authorTotalPhotos, bool authorIsForHire, String authorProfileImageUrl, String authorUrl) $default,) {final _that = this;
switch (_that) {
case _ImageModel():
return $default(_that.id,_that.description,_that.color,_that.blurHash,_that.url,_that.originUrl,_that.authorName,_that.authorBio,_that.authorLocation,_that.authorTotalLikes,_that.authorTotalPhotos,_that.authorIsForHire,_that.authorProfileImageUrl,_that.authorUrl);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<TResult extends Object?>(TResult? Function( String id, String description, String color, String blurHash, String url, String originUrl, String authorName, String authorBio, String authorLocation, int authorTotalLikes, int authorTotalPhotos, bool authorIsForHire, String authorProfileImageUrl, String authorUrl)? $default,) {final _that = this;
switch (_that) {
case _ImageModel() when $default != null:
return $default(_that.id,_that.description,_that.color,_that.blurHash,_that.url,_that.originUrl,_that.authorName,_that.authorBio,_that.authorLocation,_that.authorTotalLikes,_that.authorTotalPhotos,_that.authorIsForHire,_that.authorProfileImageUrl,_that.authorUrl);case _:
return null;
}
}
}
/// @nodoc
@JsonSerializable()
class _ImageModel implements ImageModel {
const _ImageModel({required this.id, required this.description, required this.color, required this.blurHash, required this.url, required this.originUrl, required this.authorName, required this.authorBio, required this.authorLocation, required this.authorTotalLikes, required this.authorTotalPhotos, required this.authorIsForHire, required this.authorProfileImageUrl, required this.authorUrl});
factory _ImageModel.fromJson(Map<String, dynamic> json) => _$ImageModelFromJson(json);
@override final String id;
@override final String description;
@override final String color;
@override final String blurHash;
@override final String url;
@override final String originUrl;
@override final String authorName;
@override final String authorBio;
@override final String authorLocation;
@override final int authorTotalLikes;
@override final int authorTotalPhotos;
@override final bool authorIsForHire;
@override final String authorProfileImageUrl;
@override final String authorUrl;
/// Create a copy of ImageModel
/// with the given fields replaced by the non-null parameter values.
@override @JsonKey(includeFromJson: false, includeToJson: false)
@pragma('vm:prefer-inline')
_$ImageModelCopyWith<_ImageModel> get copyWith => __$ImageModelCopyWithImpl<_ImageModel>(this, _$identity);
@override
Map<String, dynamic> toJson() {
return _$ImageModelToJson(this, );
}
@override
bool operator ==(Object other) {
return identical(this, other) || (other.runtimeType == runtimeType&&other is _ImageModel&&(identical(other.id, id) || other.id == id)&&(identical(other.description, description) || other.description == description)&&(identical(other.color, color) || other.color == color)&&(identical(other.blurHash, blurHash) || other.blurHash == blurHash)&&(identical(other.url, url) || other.url == url)&&(identical(other.originUrl, originUrl) || other.originUrl == originUrl)&&(identical(other.authorName, authorName) || other.authorName == authorName)&&(identical(other.authorBio, authorBio) || other.authorBio == authorBio)&&(identical(other.authorLocation, authorLocation) || other.authorLocation == authorLocation)&&(identical(other.authorTotalLikes, authorTotalLikes) || other.authorTotalLikes == authorTotalLikes)&&(identical(other.authorTotalPhotos, authorTotalPhotos) || other.authorTotalPhotos == authorTotalPhotos)&&(identical(other.authorIsForHire, authorIsForHire) || other.authorIsForHire == authorIsForHire)&&(identical(other.authorProfileImageUrl, authorProfileImageUrl) || other.authorProfileImageUrl == authorProfileImageUrl)&&(identical(other.authorUrl, authorUrl) || other.authorUrl == authorUrl));
}
@JsonKey(includeFromJson: false, includeToJson: false)
@override
int get hashCode => Object.hash(runtimeType,id,description,color,blurHash,url,originUrl,authorName,authorBio,authorLocation,authorTotalLikes,authorTotalPhotos,authorIsForHire,authorProfileImageUrl,authorUrl);
@override
String toString() {
return 'ImageModel(id: $id, description: $description, color: $color, blurHash: $blurHash, url: $url, originUrl: $originUrl, authorName: $authorName, authorBio: $authorBio, authorLocation: $authorLocation, authorTotalLikes: $authorTotalLikes, authorTotalPhotos: $authorTotalPhotos, authorIsForHire: $authorIsForHire, authorProfileImageUrl: $authorProfileImageUrl, authorUrl: $authorUrl)';
}
}
/// @nodoc
abstract mixin class _$ImageModelCopyWith<$Res> implements $ImageModelCopyWith<$Res> {
factory _$ImageModelCopyWith(_ImageModel value, $Res Function(_ImageModel) _then) = __$ImageModelCopyWithImpl;
@override @useResult
$Res call({
String id, String description, String color, String blurHash, String url, String originUrl, String authorName, String authorBio, String authorLocation, int authorTotalLikes, int authorTotalPhotos, bool authorIsForHire, String authorProfileImageUrl, String authorUrl
});
}
/// @nodoc
class __$ImageModelCopyWithImpl<$Res>
implements _$ImageModelCopyWith<$Res> {
__$ImageModelCopyWithImpl(this._self, this._then);
final _ImageModel _self;
final $Res Function(_ImageModel) _then;
/// Create a copy of ImageModel
/// with the given fields replaced by the non-null parameter values.
@override @pragma('vm:prefer-inline') $Res call({Object? id = null,Object? description = null,Object? color = null,Object? blurHash = null,Object? url = null,Object? originUrl = null,Object? authorName = null,Object? authorBio = null,Object? authorLocation = null,Object? authorTotalLikes = null,Object? authorTotalPhotos = null,Object? authorIsForHire = null,Object? authorProfileImageUrl = null,Object? authorUrl = null,}) {
return _then(_ImageModel(
id: null == id ? _self.id : id // ignore: cast_nullable_to_non_nullable
as String,description: null == description ? _self.description : description // ignore: cast_nullable_to_non_nullable
as String,color: null == color ? _self.color : color // ignore: cast_nullable_to_non_nullable
as String,blurHash: null == blurHash ? _self.blurHash : blurHash // ignore: cast_nullable_to_non_nullable
as String,url: null == url ? _self.url : url // ignore: cast_nullable_to_non_nullable
as String,originUrl: null == originUrl ? _self.originUrl : originUrl // ignore: cast_nullable_to_non_nullable
as String,authorName: null == authorName ? _self.authorName : authorName // ignore: cast_nullable_to_non_nullable
as String,authorBio: null == authorBio ? _self.authorBio : authorBio // ignore: cast_nullable_to_non_nullable
as String,authorLocation: null == authorLocation ? _self.authorLocation : authorLocation // ignore: cast_nullable_to_non_nullable
as String,authorTotalLikes: null == authorTotalLikes ? _self.authorTotalLikes : authorTotalLikes // ignore: cast_nullable_to_non_nullable
as int,authorTotalPhotos: null == authorTotalPhotos ? _self.authorTotalPhotos : authorTotalPhotos // ignore: cast_nullable_to_non_nullable
as int,authorIsForHire: null == authorIsForHire ? _self.authorIsForHire : authorIsForHire // ignore: cast_nullable_to_non_nullable
as bool,authorProfileImageUrl: null == authorProfileImageUrl ? _self.authorProfileImageUrl : authorProfileImageUrl // ignore: cast_nullable_to_non_nullable
as String,authorUrl: null == authorUrl ? _self.authorUrl : authorUrl // ignore: cast_nullable_to_non_nullable
as String,
));
}
}
// dart format on

View file

@ -0,0 +1,42 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'image_model.dart';
// **************************************************************************
// JsonSerializableGenerator
// **************************************************************************
_ImageModel _$ImageModelFromJson(Map<String, dynamic> json) => _ImageModel(
id: json['id'] as String,
description: json['description'] as String,
color: json['color'] as String,
blurHash: json['blurHash'] as String,
url: json['url'] as String,
originUrl: json['originUrl'] as String,
authorName: json['authorName'] as String,
authorBio: json['authorBio'] as String,
authorLocation: json['authorLocation'] as String,
authorTotalLikes: (json['authorTotalLikes'] as num).toInt(),
authorTotalPhotos: (json['authorTotalPhotos'] as num).toInt(),
authorIsForHire: json['authorIsForHire'] as bool,
authorProfileImageUrl: json['authorProfileImageUrl'] as String,
authorUrl: json['authorUrl'] as String,
);
Map<String, dynamic> _$ImageModelToJson(_ImageModel instance) =>
<String, dynamic>{
'id': instance.id,
'description': instance.description,
'color': instance.color,
'blurHash': instance.blurHash,
'url': instance.url,
'originUrl': instance.originUrl,
'authorName': instance.authorName,
'authorBio': instance.authorBio,
'authorLocation': instance.authorLocation,
'authorTotalLikes': instance.authorTotalLikes,
'authorTotalPhotos': instance.authorTotalPhotos,
'authorIsForHire': instance.authorIsForHire,
'authorProfileImageUrl': instance.authorProfileImageUrl,
'authorUrl': instance.authorUrl,
};

View file

@ -0,0 +1,58 @@
import 'package:freezed_annotation/freezed_annotation.dart';
part 'photo_list_model.freezed.dart';
part 'photo_list_model.g.dart';
@freezed
abstract class PhotoListModel with _$PhotoListModel {
@JsonSerializable(fieldRename: FieldRename.snake)
const factory PhotoListModel({
required int page,
required int perPage,
required List<PhotoModel> photos,
required int totalResults,
required String nextPage,
}) = _PhotoListModel;
factory PhotoListModel.fromJson(Map<String, dynamic> json) =>
_$PhotoListModelFromJson(json);
}
@freezed
abstract class PhotoModel with _$PhotoModel {
@JsonSerializable(fieldRename: FieldRename.snake)
const factory PhotoModel({
required int id,
required int width,
required int height,
required String url,
required String photographer,
required String photographerUrl,
required int photographerId,
required String avgColor,
required SrcModel src,
required bool liked,
required String alt,
}) = _PhotoModel;
factory PhotoModel.fromJson(Map<String, dynamic> json) =>
_$PhotoModelFromJson(json);
}
@freezed
abstract class SrcModel with _$SrcModel {
@JsonSerializable(fieldRename: FieldRename.snake)
const factory SrcModel({
required String original,
required String large2x,
required String large,
required String medium,
required String small,
required String portrait,
required String landscape,
required String tiny,
}) = _SrcModel;
factory SrcModel.fromJson(Map<String, dynamic> json) =>
_$SrcModelFromJson(json);
}

View file

@ -0,0 +1,890 @@
// 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 'photo_list_model.dart';
// **************************************************************************
// FreezedGenerator
// **************************************************************************
// dart format off
T _$identity<T>(T value) => value;
/// @nodoc
mixin _$PhotoListModel {
int get page; int get perPage; List<PhotoModel> get photos; int get totalResults; String get nextPage;
/// Create a copy of PhotoListModel
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
@pragma('vm:prefer-inline')
$PhotoListModelCopyWith<PhotoListModel> get copyWith => _$PhotoListModelCopyWithImpl<PhotoListModel>(this as PhotoListModel, _$identity);
/// Serializes this PhotoListModel to a JSON map.
Map<String, dynamic> toJson();
@override
bool operator ==(Object other) {
return identical(this, other) || (other.runtimeType == runtimeType&&other is PhotoListModel&&(identical(other.page, page) || other.page == page)&&(identical(other.perPage, perPage) || other.perPage == perPage)&&const DeepCollectionEquality().equals(other.photos, photos)&&(identical(other.totalResults, totalResults) || other.totalResults == totalResults)&&(identical(other.nextPage, nextPage) || other.nextPage == nextPage));
}
@JsonKey(includeFromJson: false, includeToJson: false)
@override
int get hashCode => Object.hash(runtimeType,page,perPage,const DeepCollectionEquality().hash(photos),totalResults,nextPage);
@override
String toString() {
return 'PhotoListModel(page: $page, perPage: $perPage, photos: $photos, totalResults: $totalResults, nextPage: $nextPage)';
}
}
/// @nodoc
abstract mixin class $PhotoListModelCopyWith<$Res> {
factory $PhotoListModelCopyWith(PhotoListModel value, $Res Function(PhotoListModel) _then) = _$PhotoListModelCopyWithImpl;
@useResult
$Res call({
int page, int perPage, List<PhotoModel> photos, int totalResults, String nextPage
});
}
/// @nodoc
class _$PhotoListModelCopyWithImpl<$Res>
implements $PhotoListModelCopyWith<$Res> {
_$PhotoListModelCopyWithImpl(this._self, this._then);
final PhotoListModel _self;
final $Res Function(PhotoListModel) _then;
/// Create a copy of PhotoListModel
/// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline') @override $Res call({Object? page = null,Object? perPage = null,Object? photos = null,Object? totalResults = null,Object? nextPage = null,}) {
return _then(_self.copyWith(
page: null == page ? _self.page : page // ignore: cast_nullable_to_non_nullable
as int,perPage: null == perPage ? _self.perPage : perPage // ignore: cast_nullable_to_non_nullable
as int,photos: null == photos ? _self.photos : photos // ignore: cast_nullable_to_non_nullable
as List<PhotoModel>,totalResults: null == totalResults ? _self.totalResults : totalResults // ignore: cast_nullable_to_non_nullable
as int,nextPage: null == nextPage ? _self.nextPage : nextPage // ignore: cast_nullable_to_non_nullable
as String,
));
}
}
/// Adds pattern-matching-related methods to [PhotoListModel].
extension PhotoListModelPatterns on PhotoListModel {
/// 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<TResult extends Object?>(TResult Function( _PhotoListModel value)? $default,{required TResult orElse(),}){
final _that = this;
switch (_that) {
case _PhotoListModel() when $default != null:
return $default(_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<TResult extends Object?>(TResult Function( _PhotoListModel value) $default,){
final _that = this;
switch (_that) {
case _PhotoListModel():
return $default(_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<TResult extends Object?>(TResult? Function( _PhotoListModel value)? $default,){
final _that = this;
switch (_that) {
case _PhotoListModel() when $default != null:
return $default(_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<TResult extends Object?>(TResult Function( int page, int perPage, List<PhotoModel> photos, int totalResults, String nextPage)? $default,{required TResult orElse(),}) {final _that = this;
switch (_that) {
case _PhotoListModel() when $default != null:
return $default(_that.page,_that.perPage,_that.photos,_that.totalResults,_that.nextPage);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<TResult extends Object?>(TResult Function( int page, int perPage, List<PhotoModel> photos, int totalResults, String nextPage) $default,) {final _that = this;
switch (_that) {
case _PhotoListModel():
return $default(_that.page,_that.perPage,_that.photos,_that.totalResults,_that.nextPage);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<TResult extends Object?>(TResult? Function( int page, int perPage, List<PhotoModel> photos, int totalResults, String nextPage)? $default,) {final _that = this;
switch (_that) {
case _PhotoListModel() when $default != null:
return $default(_that.page,_that.perPage,_that.photos,_that.totalResults,_that.nextPage);case _:
return null;
}
}
}
/// @nodoc
@JsonSerializable(fieldRename: FieldRename.snake)
class _PhotoListModel implements PhotoListModel {
const _PhotoListModel({required this.page, required this.perPage, required final List<PhotoModel> photos, required this.totalResults, required this.nextPage}): _photos = photos;
factory _PhotoListModel.fromJson(Map<String, dynamic> json) => _$PhotoListModelFromJson(json);
@override final int page;
@override final int perPage;
final List<PhotoModel> _photos;
@override List<PhotoModel> get photos {
if (_photos is EqualUnmodifiableListView) return _photos;
// ignore: implicit_dynamic_type
return EqualUnmodifiableListView(_photos);
}
@override final int totalResults;
@override final String nextPage;
/// Create a copy of PhotoListModel
/// with the given fields replaced by the non-null parameter values.
@override @JsonKey(includeFromJson: false, includeToJson: false)
@pragma('vm:prefer-inline')
_$PhotoListModelCopyWith<_PhotoListModel> get copyWith => __$PhotoListModelCopyWithImpl<_PhotoListModel>(this, _$identity);
@override
Map<String, dynamic> toJson() {
return _$PhotoListModelToJson(this, );
}
@override
bool operator ==(Object other) {
return identical(this, other) || (other.runtimeType == runtimeType&&other is _PhotoListModel&&(identical(other.page, page) || other.page == page)&&(identical(other.perPage, perPage) || other.perPage == perPage)&&const DeepCollectionEquality().equals(other._photos, _photos)&&(identical(other.totalResults, totalResults) || other.totalResults == totalResults)&&(identical(other.nextPage, nextPage) || other.nextPage == nextPage));
}
@JsonKey(includeFromJson: false, includeToJson: false)
@override
int get hashCode => Object.hash(runtimeType,page,perPage,const DeepCollectionEquality().hash(_photos),totalResults,nextPage);
@override
String toString() {
return 'PhotoListModel(page: $page, perPage: $perPage, photos: $photos, totalResults: $totalResults, nextPage: $nextPage)';
}
}
/// @nodoc
abstract mixin class _$PhotoListModelCopyWith<$Res> implements $PhotoListModelCopyWith<$Res> {
factory _$PhotoListModelCopyWith(_PhotoListModel value, $Res Function(_PhotoListModel) _then) = __$PhotoListModelCopyWithImpl;
@override @useResult
$Res call({
int page, int perPage, List<PhotoModel> photos, int totalResults, String nextPage
});
}
/// @nodoc
class __$PhotoListModelCopyWithImpl<$Res>
implements _$PhotoListModelCopyWith<$Res> {
__$PhotoListModelCopyWithImpl(this._self, this._then);
final _PhotoListModel _self;
final $Res Function(_PhotoListModel) _then;
/// Create a copy of PhotoListModel
/// with the given fields replaced by the non-null parameter values.
@override @pragma('vm:prefer-inline') $Res call({Object? page = null,Object? perPage = null,Object? photos = null,Object? totalResults = null,Object? nextPage = null,}) {
return _then(_PhotoListModel(
page: null == page ? _self.page : page // ignore: cast_nullable_to_non_nullable
as int,perPage: null == perPage ? _self.perPage : perPage // ignore: cast_nullable_to_non_nullable
as int,photos: null == photos ? _self._photos : photos // ignore: cast_nullable_to_non_nullable
as List<PhotoModel>,totalResults: null == totalResults ? _self.totalResults : totalResults // ignore: cast_nullable_to_non_nullable
as int,nextPage: null == nextPage ? _self.nextPage : nextPage // ignore: cast_nullable_to_non_nullable
as String,
));
}
}
/// @nodoc
mixin _$PhotoModel {
int get id; int get width; int get height; String get url; String get photographer; String get photographerUrl; int get photographerId; String get avgColor; SrcModel get src; bool get liked; String get alt;
/// Create a copy of PhotoModel
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
@pragma('vm:prefer-inline')
$PhotoModelCopyWith<PhotoModel> get copyWith => _$PhotoModelCopyWithImpl<PhotoModel>(this as PhotoModel, _$identity);
/// Serializes this PhotoModel to a JSON map.
Map<String, dynamic> toJson();
@override
bool operator ==(Object other) {
return identical(this, other) || (other.runtimeType == runtimeType&&other is PhotoModel&&(identical(other.id, id) || other.id == id)&&(identical(other.width, width) || other.width == width)&&(identical(other.height, height) || other.height == height)&&(identical(other.url, url) || other.url == url)&&(identical(other.photographer, photographer) || other.photographer == photographer)&&(identical(other.photographerUrl, photographerUrl) || other.photographerUrl == photographerUrl)&&(identical(other.photographerId, photographerId) || other.photographerId == photographerId)&&(identical(other.avgColor, avgColor) || other.avgColor == avgColor)&&(identical(other.src, src) || other.src == src)&&(identical(other.liked, liked) || other.liked == liked)&&(identical(other.alt, alt) || other.alt == alt));
}
@JsonKey(includeFromJson: false, includeToJson: false)
@override
int get hashCode => Object.hash(runtimeType,id,width,height,url,photographer,photographerUrl,photographerId,avgColor,src,liked,alt);
@override
String toString() {
return 'PhotoModel(id: $id, width: $width, height: $height, url: $url, photographer: $photographer, photographerUrl: $photographerUrl, photographerId: $photographerId, avgColor: $avgColor, src: $src, liked: $liked, alt: $alt)';
}
}
/// @nodoc
abstract mixin class $PhotoModelCopyWith<$Res> {
factory $PhotoModelCopyWith(PhotoModel value, $Res Function(PhotoModel) _then) = _$PhotoModelCopyWithImpl;
@useResult
$Res call({
int id, int width, int height, String url, String photographer, String photographerUrl, int photographerId, String avgColor, SrcModel src, bool liked, String alt
});
$SrcModelCopyWith<$Res> get src;
}
/// @nodoc
class _$PhotoModelCopyWithImpl<$Res>
implements $PhotoModelCopyWith<$Res> {
_$PhotoModelCopyWithImpl(this._self, this._then);
final PhotoModel _self;
final $Res Function(PhotoModel) _then;
/// Create a copy of PhotoModel
/// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline') @override $Res call({Object? id = null,Object? width = null,Object? height = null,Object? url = null,Object? photographer = null,Object? photographerUrl = null,Object? photographerId = null,Object? avgColor = null,Object? src = null,Object? liked = null,Object? alt = null,}) {
return _then(_self.copyWith(
id: null == id ? _self.id : id // ignore: cast_nullable_to_non_nullable
as int,width: null == width ? _self.width : width // ignore: cast_nullable_to_non_nullable
as int,height: null == height ? _self.height : height // ignore: cast_nullable_to_non_nullable
as int,url: null == url ? _self.url : url // ignore: cast_nullable_to_non_nullable
as String,photographer: null == photographer ? _self.photographer : photographer // ignore: cast_nullable_to_non_nullable
as String,photographerUrl: null == photographerUrl ? _self.photographerUrl : photographerUrl // ignore: cast_nullable_to_non_nullable
as String,photographerId: null == photographerId ? _self.photographerId : photographerId // ignore: cast_nullable_to_non_nullable
as int,avgColor: null == avgColor ? _self.avgColor : avgColor // ignore: cast_nullable_to_non_nullable
as String,src: null == src ? _self.src : src // ignore: cast_nullable_to_non_nullable
as SrcModel,liked: null == liked ? _self.liked : liked // ignore: cast_nullable_to_non_nullable
as bool,alt: null == alt ? _self.alt : alt // ignore: cast_nullable_to_non_nullable
as String,
));
}
/// Create a copy of PhotoModel
/// with the given fields replaced by the non-null parameter values.
@override
@pragma('vm:prefer-inline')
$SrcModelCopyWith<$Res> get src {
return $SrcModelCopyWith<$Res>(_self.src, (value) {
return _then(_self.copyWith(src: value));
});
}
}
/// Adds pattern-matching-related methods to [PhotoModel].
extension PhotoModelPatterns on PhotoModel {
/// 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<TResult extends Object?>(TResult Function( _PhotoModel value)? $default,{required TResult orElse(),}){
final _that = this;
switch (_that) {
case _PhotoModel() when $default != null:
return $default(_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<TResult extends Object?>(TResult Function( _PhotoModel value) $default,){
final _that = this;
switch (_that) {
case _PhotoModel():
return $default(_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<TResult extends Object?>(TResult? Function( _PhotoModel value)? $default,){
final _that = this;
switch (_that) {
case _PhotoModel() when $default != null:
return $default(_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<TResult extends Object?>(TResult Function( int id, int width, int height, String url, String photographer, String photographerUrl, int photographerId, String avgColor, SrcModel src, bool liked, String alt)? $default,{required TResult orElse(),}) {final _that = this;
switch (_that) {
case _PhotoModel() when $default != null:
return $default(_that.id,_that.width,_that.height,_that.url,_that.photographer,_that.photographerUrl,_that.photographerId,_that.avgColor,_that.src,_that.liked,_that.alt);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<TResult extends Object?>(TResult Function( int id, int width, int height, String url, String photographer, String photographerUrl, int photographerId, String avgColor, SrcModel src, bool liked, String alt) $default,) {final _that = this;
switch (_that) {
case _PhotoModel():
return $default(_that.id,_that.width,_that.height,_that.url,_that.photographer,_that.photographerUrl,_that.photographerId,_that.avgColor,_that.src,_that.liked,_that.alt);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<TResult extends Object?>(TResult? Function( int id, int width, int height, String url, String photographer, String photographerUrl, int photographerId, String avgColor, SrcModel src, bool liked, String alt)? $default,) {final _that = this;
switch (_that) {
case _PhotoModel() when $default != null:
return $default(_that.id,_that.width,_that.height,_that.url,_that.photographer,_that.photographerUrl,_that.photographerId,_that.avgColor,_that.src,_that.liked,_that.alt);case _:
return null;
}
}
}
/// @nodoc
@JsonSerializable(fieldRename: FieldRename.snake)
class _PhotoModel implements PhotoModel {
const _PhotoModel({required this.id, required this.width, required this.height, required this.url, required this.photographer, required this.photographerUrl, required this.photographerId, required this.avgColor, required this.src, required this.liked, required this.alt});
factory _PhotoModel.fromJson(Map<String, dynamic> json) => _$PhotoModelFromJson(json);
@override final int id;
@override final int width;
@override final int height;
@override final String url;
@override final String photographer;
@override final String photographerUrl;
@override final int photographerId;
@override final String avgColor;
@override final SrcModel src;
@override final bool liked;
@override final String alt;
/// Create a copy of PhotoModel
/// with the given fields replaced by the non-null parameter values.
@override @JsonKey(includeFromJson: false, includeToJson: false)
@pragma('vm:prefer-inline')
_$PhotoModelCopyWith<_PhotoModel> get copyWith => __$PhotoModelCopyWithImpl<_PhotoModel>(this, _$identity);
@override
Map<String, dynamic> toJson() {
return _$PhotoModelToJson(this, );
}
@override
bool operator ==(Object other) {
return identical(this, other) || (other.runtimeType == runtimeType&&other is _PhotoModel&&(identical(other.id, id) || other.id == id)&&(identical(other.width, width) || other.width == width)&&(identical(other.height, height) || other.height == height)&&(identical(other.url, url) || other.url == url)&&(identical(other.photographer, photographer) || other.photographer == photographer)&&(identical(other.photographerUrl, photographerUrl) || other.photographerUrl == photographerUrl)&&(identical(other.photographerId, photographerId) || other.photographerId == photographerId)&&(identical(other.avgColor, avgColor) || other.avgColor == avgColor)&&(identical(other.src, src) || other.src == src)&&(identical(other.liked, liked) || other.liked == liked)&&(identical(other.alt, alt) || other.alt == alt));
}
@JsonKey(includeFromJson: false, includeToJson: false)
@override
int get hashCode => Object.hash(runtimeType,id,width,height,url,photographer,photographerUrl,photographerId,avgColor,src,liked,alt);
@override
String toString() {
return 'PhotoModel(id: $id, width: $width, height: $height, url: $url, photographer: $photographer, photographerUrl: $photographerUrl, photographerId: $photographerId, avgColor: $avgColor, src: $src, liked: $liked, alt: $alt)';
}
}
/// @nodoc
abstract mixin class _$PhotoModelCopyWith<$Res> implements $PhotoModelCopyWith<$Res> {
factory _$PhotoModelCopyWith(_PhotoModel value, $Res Function(_PhotoModel) _then) = __$PhotoModelCopyWithImpl;
@override @useResult
$Res call({
int id, int width, int height, String url, String photographer, String photographerUrl, int photographerId, String avgColor, SrcModel src, bool liked, String alt
});
@override $SrcModelCopyWith<$Res> get src;
}
/// @nodoc
class __$PhotoModelCopyWithImpl<$Res>
implements _$PhotoModelCopyWith<$Res> {
__$PhotoModelCopyWithImpl(this._self, this._then);
final _PhotoModel _self;
final $Res Function(_PhotoModel) _then;
/// Create a copy of PhotoModel
/// with the given fields replaced by the non-null parameter values.
@override @pragma('vm:prefer-inline') $Res call({Object? id = null,Object? width = null,Object? height = null,Object? url = null,Object? photographer = null,Object? photographerUrl = null,Object? photographerId = null,Object? avgColor = null,Object? src = null,Object? liked = null,Object? alt = null,}) {
return _then(_PhotoModel(
id: null == id ? _self.id : id // ignore: cast_nullable_to_non_nullable
as int,width: null == width ? _self.width : width // ignore: cast_nullable_to_non_nullable
as int,height: null == height ? _self.height : height // ignore: cast_nullable_to_non_nullable
as int,url: null == url ? _self.url : url // ignore: cast_nullable_to_non_nullable
as String,photographer: null == photographer ? _self.photographer : photographer // ignore: cast_nullable_to_non_nullable
as String,photographerUrl: null == photographerUrl ? _self.photographerUrl : photographerUrl // ignore: cast_nullable_to_non_nullable
as String,photographerId: null == photographerId ? _self.photographerId : photographerId // ignore: cast_nullable_to_non_nullable
as int,avgColor: null == avgColor ? _self.avgColor : avgColor // ignore: cast_nullable_to_non_nullable
as String,src: null == src ? _self.src : src // ignore: cast_nullable_to_non_nullable
as SrcModel,liked: null == liked ? _self.liked : liked // ignore: cast_nullable_to_non_nullable
as bool,alt: null == alt ? _self.alt : alt // ignore: cast_nullable_to_non_nullable
as String,
));
}
/// Create a copy of PhotoModel
/// with the given fields replaced by the non-null parameter values.
@override
@pragma('vm:prefer-inline')
$SrcModelCopyWith<$Res> get src {
return $SrcModelCopyWith<$Res>(_self.src, (value) {
return _then(_self.copyWith(src: value));
});
}
}
/// @nodoc
mixin _$SrcModel {
String get original; String get large2x; String get large; String get medium; String get small; String get portrait; String get landscape; String get tiny;
/// Create a copy of SrcModel
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
@pragma('vm:prefer-inline')
$SrcModelCopyWith<SrcModel> get copyWith => _$SrcModelCopyWithImpl<SrcModel>(this as SrcModel, _$identity);
/// Serializes this SrcModel to a JSON map.
Map<String, dynamic> toJson();
@override
bool operator ==(Object other) {
return identical(this, other) || (other.runtimeType == runtimeType&&other is SrcModel&&(identical(other.original, original) || other.original == original)&&(identical(other.large2x, large2x) || other.large2x == large2x)&&(identical(other.large, large) || other.large == large)&&(identical(other.medium, medium) || other.medium == medium)&&(identical(other.small, small) || other.small == small)&&(identical(other.portrait, portrait) || other.portrait == portrait)&&(identical(other.landscape, landscape) || other.landscape == landscape)&&(identical(other.tiny, tiny) || other.tiny == tiny));
}
@JsonKey(includeFromJson: false, includeToJson: false)
@override
int get hashCode => Object.hash(runtimeType,original,large2x,large,medium,small,portrait,landscape,tiny);
@override
String toString() {
return 'SrcModel(original: $original, large2x: $large2x, large: $large, medium: $medium, small: $small, portrait: $portrait, landscape: $landscape, tiny: $tiny)';
}
}
/// @nodoc
abstract mixin class $SrcModelCopyWith<$Res> {
factory $SrcModelCopyWith(SrcModel value, $Res Function(SrcModel) _then) = _$SrcModelCopyWithImpl;
@useResult
$Res call({
String original, String large2x, String large, String medium, String small, String portrait, String landscape, String tiny
});
}
/// @nodoc
class _$SrcModelCopyWithImpl<$Res>
implements $SrcModelCopyWith<$Res> {
_$SrcModelCopyWithImpl(this._self, this._then);
final SrcModel _self;
final $Res Function(SrcModel) _then;
/// Create a copy of SrcModel
/// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline') @override $Res call({Object? original = null,Object? large2x = null,Object? large = null,Object? medium = null,Object? small = null,Object? portrait = null,Object? landscape = null,Object? tiny = null,}) {
return _then(_self.copyWith(
original: null == original ? _self.original : original // ignore: cast_nullable_to_non_nullable
as String,large2x: null == large2x ? _self.large2x : large2x // ignore: cast_nullable_to_non_nullable
as String,large: null == large ? _self.large : large // ignore: cast_nullable_to_non_nullable
as String,medium: null == medium ? _self.medium : medium // ignore: cast_nullable_to_non_nullable
as String,small: null == small ? _self.small : small // ignore: cast_nullable_to_non_nullable
as String,portrait: null == portrait ? _self.portrait : portrait // ignore: cast_nullable_to_non_nullable
as String,landscape: null == landscape ? _self.landscape : landscape // ignore: cast_nullable_to_non_nullable
as String,tiny: null == tiny ? _self.tiny : tiny // ignore: cast_nullable_to_non_nullable
as String,
));
}
}
/// Adds pattern-matching-related methods to [SrcModel].
extension SrcModelPatterns on SrcModel {
/// 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<TResult extends Object?>(TResult Function( _SrcModel value)? $default,{required TResult orElse(),}){
final _that = this;
switch (_that) {
case _SrcModel() when $default != null:
return $default(_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<TResult extends Object?>(TResult Function( _SrcModel value) $default,){
final _that = this;
switch (_that) {
case _SrcModel():
return $default(_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<TResult extends Object?>(TResult? Function( _SrcModel value)? $default,){
final _that = this;
switch (_that) {
case _SrcModel() when $default != null:
return $default(_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<TResult extends Object?>(TResult Function( String original, String large2x, String large, String medium, String small, String portrait, String landscape, String tiny)? $default,{required TResult orElse(),}) {final _that = this;
switch (_that) {
case _SrcModel() when $default != null:
return $default(_that.original,_that.large2x,_that.large,_that.medium,_that.small,_that.portrait,_that.landscape,_that.tiny);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<TResult extends Object?>(TResult Function( String original, String large2x, String large, String medium, String small, String portrait, String landscape, String tiny) $default,) {final _that = this;
switch (_that) {
case _SrcModel():
return $default(_that.original,_that.large2x,_that.large,_that.medium,_that.small,_that.portrait,_that.landscape,_that.tiny);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<TResult extends Object?>(TResult? Function( String original, String large2x, String large, String medium, String small, String portrait, String landscape, String tiny)? $default,) {final _that = this;
switch (_that) {
case _SrcModel() when $default != null:
return $default(_that.original,_that.large2x,_that.large,_that.medium,_that.small,_that.portrait,_that.landscape,_that.tiny);case _:
return null;
}
}
}
/// @nodoc
@JsonSerializable(fieldRename: FieldRename.snake)
class _SrcModel implements SrcModel {
const _SrcModel({required this.original, required this.large2x, required this.large, required this.medium, required this.small, required this.portrait, required this.landscape, required this.tiny});
factory _SrcModel.fromJson(Map<String, dynamic> json) => _$SrcModelFromJson(json);
@override final String original;
@override final String large2x;
@override final String large;
@override final String medium;
@override final String small;
@override final String portrait;
@override final String landscape;
@override final String tiny;
/// Create a copy of SrcModel
/// with the given fields replaced by the non-null parameter values.
@override @JsonKey(includeFromJson: false, includeToJson: false)
@pragma('vm:prefer-inline')
_$SrcModelCopyWith<_SrcModel> get copyWith => __$SrcModelCopyWithImpl<_SrcModel>(this, _$identity);
@override
Map<String, dynamic> toJson() {
return _$SrcModelToJson(this, );
}
@override
bool operator ==(Object other) {
return identical(this, other) || (other.runtimeType == runtimeType&&other is _SrcModel&&(identical(other.original, original) || other.original == original)&&(identical(other.large2x, large2x) || other.large2x == large2x)&&(identical(other.large, large) || other.large == large)&&(identical(other.medium, medium) || other.medium == medium)&&(identical(other.small, small) || other.small == small)&&(identical(other.portrait, portrait) || other.portrait == portrait)&&(identical(other.landscape, landscape) || other.landscape == landscape)&&(identical(other.tiny, tiny) || other.tiny == tiny));
}
@JsonKey(includeFromJson: false, includeToJson: false)
@override
int get hashCode => Object.hash(runtimeType,original,large2x,large,medium,small,portrait,landscape,tiny);
@override
String toString() {
return 'SrcModel(original: $original, large2x: $large2x, large: $large, medium: $medium, small: $small, portrait: $portrait, landscape: $landscape, tiny: $tiny)';
}
}
/// @nodoc
abstract mixin class _$SrcModelCopyWith<$Res> implements $SrcModelCopyWith<$Res> {
factory _$SrcModelCopyWith(_SrcModel value, $Res Function(_SrcModel) _then) = __$SrcModelCopyWithImpl;
@override @useResult
$Res call({
String original, String large2x, String large, String medium, String small, String portrait, String landscape, String tiny
});
}
/// @nodoc
class __$SrcModelCopyWithImpl<$Res>
implements _$SrcModelCopyWith<$Res> {
__$SrcModelCopyWithImpl(this._self, this._then);
final _SrcModel _self;
final $Res Function(_SrcModel) _then;
/// Create a copy of SrcModel
/// with the given fields replaced by the non-null parameter values.
@override @pragma('vm:prefer-inline') $Res call({Object? original = null,Object? large2x = null,Object? large = null,Object? medium = null,Object? small = null,Object? portrait = null,Object? landscape = null,Object? tiny = null,}) {
return _then(_SrcModel(
original: null == original ? _self.original : original // ignore: cast_nullable_to_non_nullable
as String,large2x: null == large2x ? _self.large2x : large2x // ignore: cast_nullable_to_non_nullable
as String,large: null == large ? _self.large : large // ignore: cast_nullable_to_non_nullable
as String,medium: null == medium ? _self.medium : medium // ignore: cast_nullable_to_non_nullable
as String,small: null == small ? _self.small : small // ignore: cast_nullable_to_non_nullable
as String,portrait: null == portrait ? _self.portrait : portrait // ignore: cast_nullable_to_non_nullable
as String,landscape: null == landscape ? _self.landscape : landscape // ignore: cast_nullable_to_non_nullable
as String,tiny: null == tiny ? _self.tiny : tiny // ignore: cast_nullable_to_non_nullable
as String,
));
}
}
// dart format on

View file

@ -0,0 +1,78 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'photo_list_model.dart';
// **************************************************************************
// JsonSerializableGenerator
// **************************************************************************
_PhotoListModel _$PhotoListModelFromJson(Map<String, dynamic> json) =>
_PhotoListModel(
page: (json['page'] as num).toInt(),
perPage: (json['per_page'] as num).toInt(),
photos: (json['photos'] as List<dynamic>)
.map((e) => PhotoModel.fromJson(e as Map<String, dynamic>))
.toList(),
totalResults: (json['total_results'] as num).toInt(),
nextPage: json['next_page'] as String,
);
Map<String, dynamic> _$PhotoListModelToJson(_PhotoListModel instance) =>
<String, dynamic>{
'page': instance.page,
'per_page': instance.perPage,
'photos': instance.photos,
'total_results': instance.totalResults,
'next_page': instance.nextPage,
};
_PhotoModel _$PhotoModelFromJson(Map<String, dynamic> json) => _PhotoModel(
id: (json['id'] as num).toInt(),
width: (json['width'] as num).toInt(),
height: (json['height'] as num).toInt(),
url: json['url'] as String,
photographer: json['photographer'] as String,
photographerUrl: json['photographer_url'] as String,
photographerId: (json['photographer_id'] as num).toInt(),
avgColor: json['avg_color'] as String,
src: SrcModel.fromJson(json['src'] as Map<String, dynamic>),
liked: json['liked'] as bool,
alt: json['alt'] as String,
);
Map<String, dynamic> _$PhotoModelToJson(_PhotoModel instance) =>
<String, dynamic>{
'id': instance.id,
'width': instance.width,
'height': instance.height,
'url': instance.url,
'photographer': instance.photographer,
'photographer_url': instance.photographerUrl,
'photographer_id': instance.photographerId,
'avg_color': instance.avgColor,
'src': instance.src,
'liked': instance.liked,
'alt': instance.alt,
};
_SrcModel _$SrcModelFromJson(Map<String, dynamic> json) => _SrcModel(
original: json['original'] as String,
large2x: json['large2x'] as String,
large: json['large'] as String,
medium: json['medium'] as String,
small: json['small'] as String,
portrait: json['portrait'] as String,
landscape: json['landscape'] as String,
tiny: json['tiny'] as String,
);
Map<String, dynamic> _$SrcModelToJson(_SrcModel instance) => <String, dynamic>{
'original': instance.original,
'large2x': instance.large2x,
'large': instance.large,
'medium': instance.medium,
'small': instance.small,
'portrait': instance.portrait,
'landscape': instance.landscape,
'tiny': instance.tiny,
};

View file

@ -0,0 +1,17 @@
import 'package:freezed_annotation/freezed_annotation.dart';
part 'quote_model.freezed.dart';
part 'quote_model.g.dart';
@freezed
abstract class QuoteModel with _$QuoteModel {
@JsonSerializable(fieldRename: FieldRename.snake)
const factory QuoteModel({
required int id,
required String text,
required String author,
}) = _QuoteModel;
factory QuoteModel.fromJson(Map<String, dynamic> json) =>
_$QuoteModelFromJson(json);
}

View file

@ -0,0 +1,283 @@
// 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 'quote_model.dart';
// **************************************************************************
// FreezedGenerator
// **************************************************************************
// dart format off
T _$identity<T>(T value) => value;
/// @nodoc
mixin _$QuoteModel {
int get id; String get text; String get author;
/// Create a copy of QuoteModel
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
@pragma('vm:prefer-inline')
$QuoteModelCopyWith<QuoteModel> get copyWith => _$QuoteModelCopyWithImpl<QuoteModel>(this as QuoteModel, _$identity);
/// Serializes this QuoteModel to a JSON map.
Map<String, dynamic> toJson();
@override
bool operator ==(Object other) {
return identical(this, other) || (other.runtimeType == runtimeType&&other is QuoteModel&&(identical(other.id, id) || other.id == id)&&(identical(other.text, text) || other.text == text)&&(identical(other.author, author) || other.author == author));
}
@JsonKey(includeFromJson: false, includeToJson: false)
@override
int get hashCode => Object.hash(runtimeType,id,text,author);
@override
String toString() {
return 'QuoteModel(id: $id, text: $text, author: $author)';
}
}
/// @nodoc
abstract mixin class $QuoteModelCopyWith<$Res> {
factory $QuoteModelCopyWith(QuoteModel value, $Res Function(QuoteModel) _then) = _$QuoteModelCopyWithImpl;
@useResult
$Res call({
int id, String text, String author
});
}
/// @nodoc
class _$QuoteModelCopyWithImpl<$Res>
implements $QuoteModelCopyWith<$Res> {
_$QuoteModelCopyWithImpl(this._self, this._then);
final QuoteModel _self;
final $Res Function(QuoteModel) _then;
/// Create a copy of QuoteModel
/// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline') @override $Res call({Object? id = null,Object? text = null,Object? author = null,}) {
return _then(_self.copyWith(
id: null == id ? _self.id : id // ignore: cast_nullable_to_non_nullable
as int,text: null == text ? _self.text : text // ignore: cast_nullable_to_non_nullable
as String,author: null == author ? _self.author : author // ignore: cast_nullable_to_non_nullable
as String,
));
}
}
/// Adds pattern-matching-related methods to [QuoteModel].
extension QuoteModelPatterns on QuoteModel {
/// 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<TResult extends Object?>(TResult Function( _QuoteModel value)? $default,{required TResult orElse(),}){
final _that = this;
switch (_that) {
case _QuoteModel() when $default != null:
return $default(_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<TResult extends Object?>(TResult Function( _QuoteModel value) $default,){
final _that = this;
switch (_that) {
case _QuoteModel():
return $default(_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<TResult extends Object?>(TResult? Function( _QuoteModel value)? $default,){
final _that = this;
switch (_that) {
case _QuoteModel() when $default != null:
return $default(_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<TResult extends Object?>(TResult Function( int id, String text, String author)? $default,{required TResult orElse(),}) {final _that = this;
switch (_that) {
case _QuoteModel() when $default != null:
return $default(_that.id,_that.text,_that.author);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<TResult extends Object?>(TResult Function( int id, String text, String author) $default,) {final _that = this;
switch (_that) {
case _QuoteModel():
return $default(_that.id,_that.text,_that.author);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<TResult extends Object?>(TResult? Function( int id, String text, String author)? $default,) {final _that = this;
switch (_that) {
case _QuoteModel() when $default != null:
return $default(_that.id,_that.text,_that.author);case _:
return null;
}
}
}
/// @nodoc
@JsonSerializable(fieldRename: FieldRename.snake)
class _QuoteModel implements QuoteModel {
const _QuoteModel({required this.id, required this.text, required this.author});
factory _QuoteModel.fromJson(Map<String, dynamic> json) => _$QuoteModelFromJson(json);
@override final int id;
@override final String text;
@override final String author;
/// Create a copy of QuoteModel
/// with the given fields replaced by the non-null parameter values.
@override @JsonKey(includeFromJson: false, includeToJson: false)
@pragma('vm:prefer-inline')
_$QuoteModelCopyWith<_QuoteModel> get copyWith => __$QuoteModelCopyWithImpl<_QuoteModel>(this, _$identity);
@override
Map<String, dynamic> toJson() {
return _$QuoteModelToJson(this, );
}
@override
bool operator ==(Object other) {
return identical(this, other) || (other.runtimeType == runtimeType&&other is _QuoteModel&&(identical(other.id, id) || other.id == id)&&(identical(other.text, text) || other.text == text)&&(identical(other.author, author) || other.author == author));
}
@JsonKey(includeFromJson: false, includeToJson: false)
@override
int get hashCode => Object.hash(runtimeType,id,text,author);
@override
String toString() {
return 'QuoteModel(id: $id, text: $text, author: $author)';
}
}
/// @nodoc
abstract mixin class _$QuoteModelCopyWith<$Res> implements $QuoteModelCopyWith<$Res> {
factory _$QuoteModelCopyWith(_QuoteModel value, $Res Function(_QuoteModel) _then) = __$QuoteModelCopyWithImpl;
@override @useResult
$Res call({
int id, String text, String author
});
}
/// @nodoc
class __$QuoteModelCopyWithImpl<$Res>
implements _$QuoteModelCopyWith<$Res> {
__$QuoteModelCopyWithImpl(this._self, this._then);
final _QuoteModel _self;
final $Res Function(_QuoteModel) _then;
/// Create a copy of QuoteModel
/// with the given fields replaced by the non-null parameter values.
@override @pragma('vm:prefer-inline') $Res call({Object? id = null,Object? text = null,Object? author = null,}) {
return _then(_QuoteModel(
id: null == id ? _self.id : id // ignore: cast_nullable_to_non_nullable
as int,text: null == text ? _self.text : text // ignore: cast_nullable_to_non_nullable
as String,author: null == author ? _self.author : author // ignore: cast_nullable_to_non_nullable
as String,
));
}
}
// dart format on

View file

@ -0,0 +1,20 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'quote_model.dart';
// **************************************************************************
// JsonSerializableGenerator
// **************************************************************************
_QuoteModel _$QuoteModelFromJson(Map<String, dynamic> json) => _QuoteModel(
id: (json['id'] as num).toInt(),
text: json['text'] as String,
author: json['author'] as String,
);
Map<String, dynamic> _$QuoteModelToJson(_QuoteModel instance) =>
<String, dynamic>{
'id': instance.id,
'text': instance.text,
'author': instance.author,
};

View file

@ -0,0 +1,13 @@
import 'package:freezed_annotation/freezed_annotation.dart';
part 'translation_model.freezed.dart';
part 'translation_model.g.dart';
@freezed
abstract class TranslationModel with _$TranslationModel {
const factory TranslationModel({required String id, required String lang}) =
_TranslationModel;
factory TranslationModel.fromJson(Map<String, dynamic> json) =>
_$TranslationModelFromJson(json);
}

View file

@ -0,0 +1,280 @@
// 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 'translation_model.dart';
// **************************************************************************
// FreezedGenerator
// **************************************************************************
// dart format off
T _$identity<T>(T value) => value;
/// @nodoc
mixin _$TranslationModel {
String get id; String get lang;
/// Create a copy of TranslationModel
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
@pragma('vm:prefer-inline')
$TranslationModelCopyWith<TranslationModel> get copyWith => _$TranslationModelCopyWithImpl<TranslationModel>(this as TranslationModel, _$identity);
/// Serializes this TranslationModel to a JSON map.
Map<String, dynamic> toJson();
@override
bool operator ==(Object other) {
return identical(this, other) || (other.runtimeType == runtimeType&&other is TranslationModel&&(identical(other.id, id) || other.id == id)&&(identical(other.lang, lang) || other.lang == lang));
}
@JsonKey(includeFromJson: false, includeToJson: false)
@override
int get hashCode => Object.hash(runtimeType,id,lang);
@override
String toString() {
return 'TranslationModel(id: $id, lang: $lang)';
}
}
/// @nodoc
abstract mixin class $TranslationModelCopyWith<$Res> {
factory $TranslationModelCopyWith(TranslationModel value, $Res Function(TranslationModel) _then) = _$TranslationModelCopyWithImpl;
@useResult
$Res call({
String id, String lang
});
}
/// @nodoc
class _$TranslationModelCopyWithImpl<$Res>
implements $TranslationModelCopyWith<$Res> {
_$TranslationModelCopyWithImpl(this._self, this._then);
final TranslationModel _self;
final $Res Function(TranslationModel) _then;
/// Create a copy of TranslationModel
/// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline') @override $Res call({Object? id = null,Object? lang = null,}) {
return _then(_self.copyWith(
id: null == id ? _self.id : id // ignore: cast_nullable_to_non_nullable
as String,lang: null == lang ? _self.lang : lang // ignore: cast_nullable_to_non_nullable
as String,
));
}
}
/// Adds pattern-matching-related methods to [TranslationModel].
extension TranslationModelPatterns on TranslationModel {
/// 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<TResult extends Object?>(TResult Function( _TranslationModel value)? $default,{required TResult orElse(),}){
final _that = this;
switch (_that) {
case _TranslationModel() when $default != null:
return $default(_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<TResult extends Object?>(TResult Function( _TranslationModel value) $default,){
final _that = this;
switch (_that) {
case _TranslationModel():
return $default(_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<TResult extends Object?>(TResult? Function( _TranslationModel value)? $default,){
final _that = this;
switch (_that) {
case _TranslationModel() when $default != null:
return $default(_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<TResult extends Object?>(TResult Function( String id, String lang)? $default,{required TResult orElse(),}) {final _that = this;
switch (_that) {
case _TranslationModel() when $default != null:
return $default(_that.id,_that.lang);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<TResult extends Object?>(TResult Function( String id, String lang) $default,) {final _that = this;
switch (_that) {
case _TranslationModel():
return $default(_that.id,_that.lang);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<TResult extends Object?>(TResult? Function( String id, String lang)? $default,) {final _that = this;
switch (_that) {
case _TranslationModel() when $default != null:
return $default(_that.id,_that.lang);case _:
return null;
}
}
}
/// @nodoc
@JsonSerializable()
class _TranslationModel implements TranslationModel {
const _TranslationModel({required this.id, required this.lang});
factory _TranslationModel.fromJson(Map<String, dynamic> json) => _$TranslationModelFromJson(json);
@override final String id;
@override final String lang;
/// Create a copy of TranslationModel
/// with the given fields replaced by the non-null parameter values.
@override @JsonKey(includeFromJson: false, includeToJson: false)
@pragma('vm:prefer-inline')
_$TranslationModelCopyWith<_TranslationModel> get copyWith => __$TranslationModelCopyWithImpl<_TranslationModel>(this, _$identity);
@override
Map<String, dynamic> toJson() {
return _$TranslationModelToJson(this, );
}
@override
bool operator ==(Object other) {
return identical(this, other) || (other.runtimeType == runtimeType&&other is _TranslationModel&&(identical(other.id, id) || other.id == id)&&(identical(other.lang, lang) || other.lang == lang));
}
@JsonKey(includeFromJson: false, includeToJson: false)
@override
int get hashCode => Object.hash(runtimeType,id,lang);
@override
String toString() {
return 'TranslationModel(id: $id, lang: $lang)';
}
}
/// @nodoc
abstract mixin class _$TranslationModelCopyWith<$Res> implements $TranslationModelCopyWith<$Res> {
factory _$TranslationModelCopyWith(_TranslationModel value, $Res Function(_TranslationModel) _then) = __$TranslationModelCopyWithImpl;
@override @useResult
$Res call({
String id, String lang
});
}
/// @nodoc
class __$TranslationModelCopyWithImpl<$Res>
implements _$TranslationModelCopyWith<$Res> {
__$TranslationModelCopyWithImpl(this._self, this._then);
final _TranslationModel _self;
final $Res Function(_TranslationModel) _then;
/// Create a copy of TranslationModel
/// with the given fields replaced by the non-null parameter values.
@override @pragma('vm:prefer-inline') $Res call({Object? id = null,Object? lang = null,}) {
return _then(_TranslationModel(
id: null == id ? _self.id : id // ignore: cast_nullable_to_non_nullable
as String,lang: null == lang ? _self.lang : lang // ignore: cast_nullable_to_non_nullable
as String,
));
}
}
// dart format on

View file

@ -0,0 +1,13 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'translation_model.dart';
// **************************************************************************
// JsonSerializableGenerator
// **************************************************************************
_TranslationModel _$TranslationModelFromJson(Map<String, dynamic> json) =>
_TranslationModel(id: json['id'] as String, lang: json['lang'] as String);
Map<String, dynamic> _$TranslationModelToJson(_TranslationModel instance) =>
<String, dynamic>{'id': instance.id, 'lang': instance.lang};

View file

@ -0,0 +1,89 @@
import 'dart:convert';
import 'package:fpdart/fpdart.dart';
import 'package:http/http.dart';
import 'package:kuwot/core/data/local/translation_target_config.dart';
import 'package:kuwot/core/error/exception.dart';
import 'package:kuwot/core/error/failure.dart';
import 'package:kuwot/core/error/server_error_model.dart';
import 'package:kuwot/features/quote/data/data_sources/remote/kuwot_api_remote_data_source.dart';
import 'package:kuwot/features/quote/domain/entities/background_image.dart';
import 'package:kuwot/features/quote/domain/entities/quote.dart';
import 'package:kuwot/features/quote/domain/entities/translation.dart';
import 'package:kuwot/features/quote/domain/repositories/quote_repository.dart';
class QuoteRepositoryImpl implements QuoteRepository {
final KuwotApiRemoteDataSource quoteDataSource;
QuoteRepositoryImpl({required this.quoteDataSource});
@override
Future<Either<Failure, List<BackgroundImage>>> getBackgroundImages() async {
try {
final images = await quoteDataSource.getRandomImages();
return right(BackgroundImage.fromModels(images));
} on ClientException catch (e) {
return left(
ClientFailure(message: "Failed to connect to the server", cause: e),
);
} on Exception catch (e) {
return left(UnknownFailure(message: e.toString(), cause: e));
}
}
@override
Future<Either<Failure, Quote>> getQuote(
TranslationTarget? translationTarget,
) async {
try {
final query = translationTarget != null
? "lang=${translationTarget.id}"
: null;
final quote = await quoteDataSource.getQuote(query: query);
return right(Quote.fromModel(quote));
} on ClientException catch (e) {
return left(
ClientFailure(message: "Failed to connect to the server", cause: e),
);
} on ServerException catch (e) {
final error = ServerErrorModel.fromJson(jsonDecode(e.message));
return left(
ServerFailure(message: '${error.message} (${error.code})', cause: e),
);
} on Exception catch (e) {
return left(UnknownFailure(message: e.toString(), cause: e));
}
}
@override
Future<Either<Failure, Quote>> getTranslatedQuote(
int id,
TranslationTarget translationTarget,
) async {
try {
final query = "lang=${translationTarget.id}";
final quote = await quoteDataSource.getTranslatedQuote(id, query: query);
return right(Quote.fromModel(quote));
} on ClientException catch (e) {
return left(
ClientFailure(message: "Failed to connect to the server", cause: e),
);
} on Exception catch (e) {
return left(UnknownFailure(message: e.toString(), cause: e));
}
}
@override
Future<Either<Failure, List<Translation>>> getTranslations() async {
try {
final translations = await quoteDataSource.getTranslations();
return right(translations.map((e) => Translation.fromModel(e)).toList());
} on ClientException catch (e) {
return left(
ClientFailure(message: "Failed to connect to the server", cause: e),
);
} on Exception catch (e) {
return left(UnknownFailure(message: e.toString(), cause: e));
}
}
}

View file

@ -0,0 +1,49 @@
import 'package:freezed_annotation/freezed_annotation.dart';
import 'package:kuwot/features/quote/data/models/image_model.dart';
part 'background_image.freezed.dart';
@freezed
abstract class BackgroundImage with _$BackgroundImage {
const BackgroundImage._();
const factory BackgroundImage({
required String id,
required String description,
required String color,
required String blurHash,
required String url,
required String originUrl,
required String authorName,
required String authorProfileImageUrl,
required String authorUrl,
required String authorBio,
required String authorLocation,
required int authorTotalLikes,
required int authorTotalPhotos,
required bool authorIsForHire,
}) = _BackgroundImage;
static List<BackgroundImage> fromModels(List<ImageModel> images) {
return images
.map(
(e) => BackgroundImage(
id: e.id,
description: e.description,
color: e.color,
blurHash: e.blurHash,
url: e.url,
originUrl: e.originUrl,
authorUrl: e.authorUrl,
authorName: e.authorName,
authorProfileImageUrl: e.authorProfileImageUrl,
authorBio: e.authorBio,
authorLocation: e.authorLocation,
authorTotalLikes: e.authorTotalLikes,
authorTotalPhotos: e.authorTotalPhotos,
authorIsForHire: e.authorIsForHire,
),
)
.toList();
}
}

View file

@ -0,0 +1,310 @@
// 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 'background_image.dart';
// **************************************************************************
// FreezedGenerator
// **************************************************************************
// dart format off
T _$identity<T>(T value) => value;
/// @nodoc
mixin _$BackgroundImage {
String get id; String get description; String get color; String get blurHash; String get url; String get originUrl; String get authorName; String get authorProfileImageUrl; String get authorUrl; String get authorBio; String get authorLocation; int get authorTotalLikes; int get authorTotalPhotos; bool get authorIsForHire;
/// Create a copy of BackgroundImage
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
@pragma('vm:prefer-inline')
$BackgroundImageCopyWith<BackgroundImage> get copyWith => _$BackgroundImageCopyWithImpl<BackgroundImage>(this as BackgroundImage, _$identity);
@override
bool operator ==(Object other) {
return identical(this, other) || (other.runtimeType == runtimeType&&other is BackgroundImage&&(identical(other.id, id) || other.id == id)&&(identical(other.description, description) || other.description == description)&&(identical(other.color, color) || other.color == color)&&(identical(other.blurHash, blurHash) || other.blurHash == blurHash)&&(identical(other.url, url) || other.url == url)&&(identical(other.originUrl, originUrl) || other.originUrl == originUrl)&&(identical(other.authorName, authorName) || other.authorName == authorName)&&(identical(other.authorProfileImageUrl, authorProfileImageUrl) || other.authorProfileImageUrl == authorProfileImageUrl)&&(identical(other.authorUrl, authorUrl) || other.authorUrl == authorUrl)&&(identical(other.authorBio, authorBio) || other.authorBio == authorBio)&&(identical(other.authorLocation, authorLocation) || other.authorLocation == authorLocation)&&(identical(other.authorTotalLikes, authorTotalLikes) || other.authorTotalLikes == authorTotalLikes)&&(identical(other.authorTotalPhotos, authorTotalPhotos) || other.authorTotalPhotos == authorTotalPhotos)&&(identical(other.authorIsForHire, authorIsForHire) || other.authorIsForHire == authorIsForHire));
}
@override
int get hashCode => Object.hash(runtimeType,id,description,color,blurHash,url,originUrl,authorName,authorProfileImageUrl,authorUrl,authorBio,authorLocation,authorTotalLikes,authorTotalPhotos,authorIsForHire);
@override
String toString() {
return 'BackgroundImage(id: $id, description: $description, color: $color, blurHash: $blurHash, url: $url, originUrl: $originUrl, authorName: $authorName, authorProfileImageUrl: $authorProfileImageUrl, authorUrl: $authorUrl, authorBio: $authorBio, authorLocation: $authorLocation, authorTotalLikes: $authorTotalLikes, authorTotalPhotos: $authorTotalPhotos, authorIsForHire: $authorIsForHire)';
}
}
/// @nodoc
abstract mixin class $BackgroundImageCopyWith<$Res> {
factory $BackgroundImageCopyWith(BackgroundImage value, $Res Function(BackgroundImage) _then) = _$BackgroundImageCopyWithImpl;
@useResult
$Res call({
String id, String description, String color, String blurHash, String url, String originUrl, String authorName, String authorProfileImageUrl, String authorUrl, String authorBio, String authorLocation, int authorTotalLikes, int authorTotalPhotos, bool authorIsForHire
});
}
/// @nodoc
class _$BackgroundImageCopyWithImpl<$Res>
implements $BackgroundImageCopyWith<$Res> {
_$BackgroundImageCopyWithImpl(this._self, this._then);
final BackgroundImage _self;
final $Res Function(BackgroundImage) _then;
/// Create a copy of BackgroundImage
/// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline') @override $Res call({Object? id = null,Object? description = null,Object? color = null,Object? blurHash = null,Object? url = null,Object? originUrl = null,Object? authorName = null,Object? authorProfileImageUrl = null,Object? authorUrl = null,Object? authorBio = null,Object? authorLocation = null,Object? authorTotalLikes = null,Object? authorTotalPhotos = null,Object? authorIsForHire = null,}) {
return _then(_self.copyWith(
id: null == id ? _self.id : id // ignore: cast_nullable_to_non_nullable
as String,description: null == description ? _self.description : description // ignore: cast_nullable_to_non_nullable
as String,color: null == color ? _self.color : color // ignore: cast_nullable_to_non_nullable
as String,blurHash: null == blurHash ? _self.blurHash : blurHash // ignore: cast_nullable_to_non_nullable
as String,url: null == url ? _self.url : url // ignore: cast_nullable_to_non_nullable
as String,originUrl: null == originUrl ? _self.originUrl : originUrl // ignore: cast_nullable_to_non_nullable
as String,authorName: null == authorName ? _self.authorName : authorName // ignore: cast_nullable_to_non_nullable
as String,authorProfileImageUrl: null == authorProfileImageUrl ? _self.authorProfileImageUrl : authorProfileImageUrl // ignore: cast_nullable_to_non_nullable
as String,authorUrl: null == authorUrl ? _self.authorUrl : authorUrl // ignore: cast_nullable_to_non_nullable
as String,authorBio: null == authorBio ? _self.authorBio : authorBio // ignore: cast_nullable_to_non_nullable
as String,authorLocation: null == authorLocation ? _self.authorLocation : authorLocation // ignore: cast_nullable_to_non_nullable
as String,authorTotalLikes: null == authorTotalLikes ? _self.authorTotalLikes : authorTotalLikes // ignore: cast_nullable_to_non_nullable
as int,authorTotalPhotos: null == authorTotalPhotos ? _self.authorTotalPhotos : authorTotalPhotos // ignore: cast_nullable_to_non_nullable
as int,authorIsForHire: null == authorIsForHire ? _self.authorIsForHire : authorIsForHire // ignore: cast_nullable_to_non_nullable
as bool,
));
}
}
/// Adds pattern-matching-related methods to [BackgroundImage].
extension BackgroundImagePatterns on BackgroundImage {
/// 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<TResult extends Object?>(TResult Function( _BackgroundImage value)? $default,{required TResult orElse(),}){
final _that = this;
switch (_that) {
case _BackgroundImage() when $default != null:
return $default(_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<TResult extends Object?>(TResult Function( _BackgroundImage value) $default,){
final _that = this;
switch (_that) {
case _BackgroundImage():
return $default(_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<TResult extends Object?>(TResult? Function( _BackgroundImage value)? $default,){
final _that = this;
switch (_that) {
case _BackgroundImage() when $default != null:
return $default(_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<TResult extends Object?>(TResult Function( String id, String description, String color, String blurHash, String url, String originUrl, String authorName, String authorProfileImageUrl, String authorUrl, String authorBio, String authorLocation, int authorTotalLikes, int authorTotalPhotos, bool authorIsForHire)? $default,{required TResult orElse(),}) {final _that = this;
switch (_that) {
case _BackgroundImage() when $default != null:
return $default(_that.id,_that.description,_that.color,_that.blurHash,_that.url,_that.originUrl,_that.authorName,_that.authorProfileImageUrl,_that.authorUrl,_that.authorBio,_that.authorLocation,_that.authorTotalLikes,_that.authorTotalPhotos,_that.authorIsForHire);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<TResult extends Object?>(TResult Function( String id, String description, String color, String blurHash, String url, String originUrl, String authorName, String authorProfileImageUrl, String authorUrl, String authorBio, String authorLocation, int authorTotalLikes, int authorTotalPhotos, bool authorIsForHire) $default,) {final _that = this;
switch (_that) {
case _BackgroundImage():
return $default(_that.id,_that.description,_that.color,_that.blurHash,_that.url,_that.originUrl,_that.authorName,_that.authorProfileImageUrl,_that.authorUrl,_that.authorBio,_that.authorLocation,_that.authorTotalLikes,_that.authorTotalPhotos,_that.authorIsForHire);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<TResult extends Object?>(TResult? Function( String id, String description, String color, String blurHash, String url, String originUrl, String authorName, String authorProfileImageUrl, String authorUrl, String authorBio, String authorLocation, int authorTotalLikes, int authorTotalPhotos, bool authorIsForHire)? $default,) {final _that = this;
switch (_that) {
case _BackgroundImage() when $default != null:
return $default(_that.id,_that.description,_that.color,_that.blurHash,_that.url,_that.originUrl,_that.authorName,_that.authorProfileImageUrl,_that.authorUrl,_that.authorBio,_that.authorLocation,_that.authorTotalLikes,_that.authorTotalPhotos,_that.authorIsForHire);case _:
return null;
}
}
}
/// @nodoc
class _BackgroundImage extends BackgroundImage {
const _BackgroundImage({required this.id, required this.description, required this.color, required this.blurHash, required this.url, required this.originUrl, required this.authorName, required this.authorProfileImageUrl, required this.authorUrl, required this.authorBio, required this.authorLocation, required this.authorTotalLikes, required this.authorTotalPhotos, required this.authorIsForHire}): super._();
@override final String id;
@override final String description;
@override final String color;
@override final String blurHash;
@override final String url;
@override final String originUrl;
@override final String authorName;
@override final String authorProfileImageUrl;
@override final String authorUrl;
@override final String authorBio;
@override final String authorLocation;
@override final int authorTotalLikes;
@override final int authorTotalPhotos;
@override final bool authorIsForHire;
/// Create a copy of BackgroundImage
/// with the given fields replaced by the non-null parameter values.
@override @JsonKey(includeFromJson: false, includeToJson: false)
@pragma('vm:prefer-inline')
_$BackgroundImageCopyWith<_BackgroundImage> get copyWith => __$BackgroundImageCopyWithImpl<_BackgroundImage>(this, _$identity);
@override
bool operator ==(Object other) {
return identical(this, other) || (other.runtimeType == runtimeType&&other is _BackgroundImage&&(identical(other.id, id) || other.id == id)&&(identical(other.description, description) || other.description == description)&&(identical(other.color, color) || other.color == color)&&(identical(other.blurHash, blurHash) || other.blurHash == blurHash)&&(identical(other.url, url) || other.url == url)&&(identical(other.originUrl, originUrl) || other.originUrl == originUrl)&&(identical(other.authorName, authorName) || other.authorName == authorName)&&(identical(other.authorProfileImageUrl, authorProfileImageUrl) || other.authorProfileImageUrl == authorProfileImageUrl)&&(identical(other.authorUrl, authorUrl) || other.authorUrl == authorUrl)&&(identical(other.authorBio, authorBio) || other.authorBio == authorBio)&&(identical(other.authorLocation, authorLocation) || other.authorLocation == authorLocation)&&(identical(other.authorTotalLikes, authorTotalLikes) || other.authorTotalLikes == authorTotalLikes)&&(identical(other.authorTotalPhotos, authorTotalPhotos) || other.authorTotalPhotos == authorTotalPhotos)&&(identical(other.authorIsForHire, authorIsForHire) || other.authorIsForHire == authorIsForHire));
}
@override
int get hashCode => Object.hash(runtimeType,id,description,color,blurHash,url,originUrl,authorName,authorProfileImageUrl,authorUrl,authorBio,authorLocation,authorTotalLikes,authorTotalPhotos,authorIsForHire);
@override
String toString() {
return 'BackgroundImage(id: $id, description: $description, color: $color, blurHash: $blurHash, url: $url, originUrl: $originUrl, authorName: $authorName, authorProfileImageUrl: $authorProfileImageUrl, authorUrl: $authorUrl, authorBio: $authorBio, authorLocation: $authorLocation, authorTotalLikes: $authorTotalLikes, authorTotalPhotos: $authorTotalPhotos, authorIsForHire: $authorIsForHire)';
}
}
/// @nodoc
abstract mixin class _$BackgroundImageCopyWith<$Res> implements $BackgroundImageCopyWith<$Res> {
factory _$BackgroundImageCopyWith(_BackgroundImage value, $Res Function(_BackgroundImage) _then) = __$BackgroundImageCopyWithImpl;
@override @useResult
$Res call({
String id, String description, String color, String blurHash, String url, String originUrl, String authorName, String authorProfileImageUrl, String authorUrl, String authorBio, String authorLocation, int authorTotalLikes, int authorTotalPhotos, bool authorIsForHire
});
}
/// @nodoc
class __$BackgroundImageCopyWithImpl<$Res>
implements _$BackgroundImageCopyWith<$Res> {
__$BackgroundImageCopyWithImpl(this._self, this._then);
final _BackgroundImage _self;
final $Res Function(_BackgroundImage) _then;
/// Create a copy of BackgroundImage
/// with the given fields replaced by the non-null parameter values.
@override @pragma('vm:prefer-inline') $Res call({Object? id = null,Object? description = null,Object? color = null,Object? blurHash = null,Object? url = null,Object? originUrl = null,Object? authorName = null,Object? authorProfileImageUrl = null,Object? authorUrl = null,Object? authorBio = null,Object? authorLocation = null,Object? authorTotalLikes = null,Object? authorTotalPhotos = null,Object? authorIsForHire = null,}) {
return _then(_BackgroundImage(
id: null == id ? _self.id : id // ignore: cast_nullable_to_non_nullable
as String,description: null == description ? _self.description : description // ignore: cast_nullable_to_non_nullable
as String,color: null == color ? _self.color : color // ignore: cast_nullable_to_non_nullable
as String,blurHash: null == blurHash ? _self.blurHash : blurHash // ignore: cast_nullable_to_non_nullable
as String,url: null == url ? _self.url : url // ignore: cast_nullable_to_non_nullable
as String,originUrl: null == originUrl ? _self.originUrl : originUrl // ignore: cast_nullable_to_non_nullable
as String,authorName: null == authorName ? _self.authorName : authorName // ignore: cast_nullable_to_non_nullable
as String,authorProfileImageUrl: null == authorProfileImageUrl ? _self.authorProfileImageUrl : authorProfileImageUrl // ignore: cast_nullable_to_non_nullable
as String,authorUrl: null == authorUrl ? _self.authorUrl : authorUrl // ignore: cast_nullable_to_non_nullable
as String,authorBio: null == authorBio ? _self.authorBio : authorBio // ignore: cast_nullable_to_non_nullable
as String,authorLocation: null == authorLocation ? _self.authorLocation : authorLocation // ignore: cast_nullable_to_non_nullable
as String,authorTotalLikes: null == authorTotalLikes ? _self.authorTotalLikes : authorTotalLikes // ignore: cast_nullable_to_non_nullable
as int,authorTotalPhotos: null == authorTotalPhotos ? _self.authorTotalPhotos : authorTotalPhotos // ignore: cast_nullable_to_non_nullable
as int,authorIsForHire: null == authorIsForHire ? _self.authorIsForHire : authorIsForHire // ignore: cast_nullable_to_non_nullable
as bool,
));
}
}
// dart format on

View file

@ -0,0 +1,28 @@
import 'package:freezed_annotation/freezed_annotation.dart';
import 'package:kuwot/features/quote/data/models/photo_list_model.dart';
part 'background_photos.freezed.dart';
@freezed
abstract class BackgroundPhotos with _$BackgroundPhotos {
const factory BackgroundPhotos({required List<Photo> photos}) =
_BackgroundPhotos;
static BackgroundPhotos fromModel(PhotoListModel model) => BackgroundPhotos(
photos: model.photos.map<Photo>((e) {
return Photo(
url: buildPortraitImageUrl(e.src.original),
avgColor: e.avgColor,
);
}).toList(),
);
static String buildPortraitImageUrl(String originalUrl) =>
'$originalUrl?auto=compress&cs=tinysrgb&fit=crop&h=2400&w=1200';
}
@freezed
abstract class Photo with _$Photo {
const factory Photo({required String url, required String avgColor}) =
_BackgroundImage;
}

View file

@ -0,0 +1,537 @@
// 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 'background_photos.dart';
// **************************************************************************
// FreezedGenerator
// **************************************************************************
// dart format off
T _$identity<T>(T value) => value;
/// @nodoc
mixin _$BackgroundPhotos {
List<Photo> get photos;
/// Create a copy of BackgroundPhotos
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
@pragma('vm:prefer-inline')
$BackgroundPhotosCopyWith<BackgroundPhotos> get copyWith => _$BackgroundPhotosCopyWithImpl<BackgroundPhotos>(this as BackgroundPhotos, _$identity);
@override
bool operator ==(Object other) {
return identical(this, other) || (other.runtimeType == runtimeType&&other is BackgroundPhotos&&const DeepCollectionEquality().equals(other.photos, photos));
}
@override
int get hashCode => Object.hash(runtimeType,const DeepCollectionEquality().hash(photos));
@override
String toString() {
return 'BackgroundPhotos(photos: $photos)';
}
}
/// @nodoc
abstract mixin class $BackgroundPhotosCopyWith<$Res> {
factory $BackgroundPhotosCopyWith(BackgroundPhotos value, $Res Function(BackgroundPhotos) _then) = _$BackgroundPhotosCopyWithImpl;
@useResult
$Res call({
List<Photo> photos
});
}
/// @nodoc
class _$BackgroundPhotosCopyWithImpl<$Res>
implements $BackgroundPhotosCopyWith<$Res> {
_$BackgroundPhotosCopyWithImpl(this._self, this._then);
final BackgroundPhotos _self;
final $Res Function(BackgroundPhotos) _then;
/// Create a copy of BackgroundPhotos
/// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline') @override $Res call({Object? photos = null,}) {
return _then(_self.copyWith(
photos: null == photos ? _self.photos : photos // ignore: cast_nullable_to_non_nullable
as List<Photo>,
));
}
}
/// Adds pattern-matching-related methods to [BackgroundPhotos].
extension BackgroundPhotosPatterns on BackgroundPhotos {
/// 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<TResult extends Object?>(TResult Function( _BackgroundPhotos value)? $default,{required TResult orElse(),}){
final _that = this;
switch (_that) {
case _BackgroundPhotos() when $default != null:
return $default(_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<TResult extends Object?>(TResult Function( _BackgroundPhotos value) $default,){
final _that = this;
switch (_that) {
case _BackgroundPhotos():
return $default(_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<TResult extends Object?>(TResult? Function( _BackgroundPhotos value)? $default,){
final _that = this;
switch (_that) {
case _BackgroundPhotos() when $default != null:
return $default(_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<TResult extends Object?>(TResult Function( List<Photo> photos)? $default,{required TResult orElse(),}) {final _that = this;
switch (_that) {
case _BackgroundPhotos() when $default != null:
return $default(_that.photos);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<TResult extends Object?>(TResult Function( List<Photo> photos) $default,) {final _that = this;
switch (_that) {
case _BackgroundPhotos():
return $default(_that.photos);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<TResult extends Object?>(TResult? Function( List<Photo> photos)? $default,) {final _that = this;
switch (_that) {
case _BackgroundPhotos() when $default != null:
return $default(_that.photos);case _:
return null;
}
}
}
/// @nodoc
class _BackgroundPhotos implements BackgroundPhotos {
const _BackgroundPhotos({required final List<Photo> photos}): _photos = photos;
final List<Photo> _photos;
@override List<Photo> get photos {
if (_photos is EqualUnmodifiableListView) return _photos;
// ignore: implicit_dynamic_type
return EqualUnmodifiableListView(_photos);
}
/// Create a copy of BackgroundPhotos
/// with the given fields replaced by the non-null parameter values.
@override @JsonKey(includeFromJson: false, includeToJson: false)
@pragma('vm:prefer-inline')
_$BackgroundPhotosCopyWith<_BackgroundPhotos> get copyWith => __$BackgroundPhotosCopyWithImpl<_BackgroundPhotos>(this, _$identity);
@override
bool operator ==(Object other) {
return identical(this, other) || (other.runtimeType == runtimeType&&other is _BackgroundPhotos&&const DeepCollectionEquality().equals(other._photos, _photos));
}
@override
int get hashCode => Object.hash(runtimeType,const DeepCollectionEquality().hash(_photos));
@override
String toString() {
return 'BackgroundPhotos(photos: $photos)';
}
}
/// @nodoc
abstract mixin class _$BackgroundPhotosCopyWith<$Res> implements $BackgroundPhotosCopyWith<$Res> {
factory _$BackgroundPhotosCopyWith(_BackgroundPhotos value, $Res Function(_BackgroundPhotos) _then) = __$BackgroundPhotosCopyWithImpl;
@override @useResult
$Res call({
List<Photo> photos
});
}
/// @nodoc
class __$BackgroundPhotosCopyWithImpl<$Res>
implements _$BackgroundPhotosCopyWith<$Res> {
__$BackgroundPhotosCopyWithImpl(this._self, this._then);
final _BackgroundPhotos _self;
final $Res Function(_BackgroundPhotos) _then;
/// Create a copy of BackgroundPhotos
/// with the given fields replaced by the non-null parameter values.
@override @pragma('vm:prefer-inline') $Res call({Object? photos = null,}) {
return _then(_BackgroundPhotos(
photos: null == photos ? _self._photos : photos // ignore: cast_nullable_to_non_nullable
as List<Photo>,
));
}
}
/// @nodoc
mixin _$Photo {
String get url; String get avgColor;
/// Create a copy of Photo
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
@pragma('vm:prefer-inline')
$PhotoCopyWith<Photo> get copyWith => _$PhotoCopyWithImpl<Photo>(this as Photo, _$identity);
@override
bool operator ==(Object other) {
return identical(this, other) || (other.runtimeType == runtimeType&&other is Photo&&(identical(other.url, url) || other.url == url)&&(identical(other.avgColor, avgColor) || other.avgColor == avgColor));
}
@override
int get hashCode => Object.hash(runtimeType,url,avgColor);
@override
String toString() {
return 'Photo(url: $url, avgColor: $avgColor)';
}
}
/// @nodoc
abstract mixin class $PhotoCopyWith<$Res> {
factory $PhotoCopyWith(Photo value, $Res Function(Photo) _then) = _$PhotoCopyWithImpl;
@useResult
$Res call({
String url, String avgColor
});
}
/// @nodoc
class _$PhotoCopyWithImpl<$Res>
implements $PhotoCopyWith<$Res> {
_$PhotoCopyWithImpl(this._self, this._then);
final Photo _self;
final $Res Function(Photo) _then;
/// Create a copy of Photo
/// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline') @override $Res call({Object? url = null,Object? avgColor = null,}) {
return _then(_self.copyWith(
url: null == url ? _self.url : url // ignore: cast_nullable_to_non_nullable
as String,avgColor: null == avgColor ? _self.avgColor : avgColor // ignore: cast_nullable_to_non_nullable
as String,
));
}
}
/// Adds pattern-matching-related methods to [Photo].
extension PhotoPatterns on Photo {
/// 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<TResult extends Object?>(TResult Function( _BackgroundImage value)? $default,{required TResult orElse(),}){
final _that = this;
switch (_that) {
case _BackgroundImage() when $default != null:
return $default(_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<TResult extends Object?>(TResult Function( _BackgroundImage value) $default,){
final _that = this;
switch (_that) {
case _BackgroundImage():
return $default(_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<TResult extends Object?>(TResult? Function( _BackgroundImage value)? $default,){
final _that = this;
switch (_that) {
case _BackgroundImage() when $default != null:
return $default(_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<TResult extends Object?>(TResult Function( String url, String avgColor)? $default,{required TResult orElse(),}) {final _that = this;
switch (_that) {
case _BackgroundImage() when $default != null:
return $default(_that.url,_that.avgColor);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<TResult extends Object?>(TResult Function( String url, String avgColor) $default,) {final _that = this;
switch (_that) {
case _BackgroundImage():
return $default(_that.url,_that.avgColor);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<TResult extends Object?>(TResult? Function( String url, String avgColor)? $default,) {final _that = this;
switch (_that) {
case _BackgroundImage() when $default != null:
return $default(_that.url,_that.avgColor);case _:
return null;
}
}
}
/// @nodoc
class _BackgroundImage implements Photo {
const _BackgroundImage({required this.url, required this.avgColor});
@override final String url;
@override final String avgColor;
/// Create a copy of Photo
/// with the given fields replaced by the non-null parameter values.
@override @JsonKey(includeFromJson: false, includeToJson: false)
@pragma('vm:prefer-inline')
_$BackgroundImageCopyWith<_BackgroundImage> get copyWith => __$BackgroundImageCopyWithImpl<_BackgroundImage>(this, _$identity);
@override
bool operator ==(Object other) {
return identical(this, other) || (other.runtimeType == runtimeType&&other is _BackgroundImage&&(identical(other.url, url) || other.url == url)&&(identical(other.avgColor, avgColor) || other.avgColor == avgColor));
}
@override
int get hashCode => Object.hash(runtimeType,url,avgColor);
@override
String toString() {
return 'Photo(url: $url, avgColor: $avgColor)';
}
}
/// @nodoc
abstract mixin class _$BackgroundImageCopyWith<$Res> implements $PhotoCopyWith<$Res> {
factory _$BackgroundImageCopyWith(_BackgroundImage value, $Res Function(_BackgroundImage) _then) = __$BackgroundImageCopyWithImpl;
@override @useResult
$Res call({
String url, String avgColor
});
}
/// @nodoc
class __$BackgroundImageCopyWithImpl<$Res>
implements _$BackgroundImageCopyWith<$Res> {
__$BackgroundImageCopyWithImpl(this._self, this._then);
final _BackgroundImage _self;
final $Res Function(_BackgroundImage) _then;
/// Create a copy of Photo
/// with the given fields replaced by the non-null parameter values.
@override @pragma('vm:prefer-inline') $Res call({Object? url = null,Object? avgColor = null,}) {
return _then(_BackgroundImage(
url: null == url ? _self.url : url // ignore: cast_nullable_to_non_nullable
as String,avgColor: null == avgColor ? _self.avgColor : avgColor // ignore: cast_nullable_to_non_nullable
as String,
));
}
}
// dart format on

View file

@ -0,0 +1,16 @@
import 'package:freezed_annotation/freezed_annotation.dart';
import 'package:kuwot/features/quote/data/models/quote_model.dart';
part 'quote.freezed.dart';
@freezed
abstract class Quote with _$Quote {
const factory Quote({
required int id,
required String author,
required String body,
}) = _Quote;
static Quote fromModel(QuoteModel model) =>
Quote(id: model.id, author: model.author, body: model.text);
}

View file

@ -0,0 +1,277 @@
// 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 'quote.dart';
// **************************************************************************
// FreezedGenerator
// **************************************************************************
// dart format off
T _$identity<T>(T value) => value;
/// @nodoc
mixin _$Quote {
int get id; String get author; String get body;
/// Create a copy of Quote
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
@pragma('vm:prefer-inline')
$QuoteCopyWith<Quote> get copyWith => _$QuoteCopyWithImpl<Quote>(this as Quote, _$identity);
@override
bool operator ==(Object other) {
return identical(this, other) || (other.runtimeType == runtimeType&&other is Quote&&(identical(other.id, id) || other.id == id)&&(identical(other.author, author) || other.author == author)&&(identical(other.body, body) || other.body == body));
}
@override
int get hashCode => Object.hash(runtimeType,id,author,body);
@override
String toString() {
return 'Quote(id: $id, author: $author, body: $body)';
}
}
/// @nodoc
abstract mixin class $QuoteCopyWith<$Res> {
factory $QuoteCopyWith(Quote value, $Res Function(Quote) _then) = _$QuoteCopyWithImpl;
@useResult
$Res call({
int id, String author, String body
});
}
/// @nodoc
class _$QuoteCopyWithImpl<$Res>
implements $QuoteCopyWith<$Res> {
_$QuoteCopyWithImpl(this._self, this._then);
final Quote _self;
final $Res Function(Quote) _then;
/// Create a copy of Quote
/// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline') @override $Res call({Object? id = null,Object? author = null,Object? body = null,}) {
return _then(_self.copyWith(
id: null == id ? _self.id : id // ignore: cast_nullable_to_non_nullable
as int,author: null == author ? _self.author : author // ignore: cast_nullable_to_non_nullable
as String,body: null == body ? _self.body : body // ignore: cast_nullable_to_non_nullable
as String,
));
}
}
/// Adds pattern-matching-related methods to [Quote].
extension QuotePatterns on Quote {
/// 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<TResult extends Object?>(TResult Function( _Quote value)? $default,{required TResult orElse(),}){
final _that = this;
switch (_that) {
case _Quote() when $default != null:
return $default(_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<TResult extends Object?>(TResult Function( _Quote value) $default,){
final _that = this;
switch (_that) {
case _Quote():
return $default(_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<TResult extends Object?>(TResult? Function( _Quote value)? $default,){
final _that = this;
switch (_that) {
case _Quote() when $default != null:
return $default(_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<TResult extends Object?>(TResult Function( int id, String author, String body)? $default,{required TResult orElse(),}) {final _that = this;
switch (_that) {
case _Quote() when $default != null:
return $default(_that.id,_that.author,_that.body);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<TResult extends Object?>(TResult Function( int id, String author, String body) $default,) {final _that = this;
switch (_that) {
case _Quote():
return $default(_that.id,_that.author,_that.body);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<TResult extends Object?>(TResult? Function( int id, String author, String body)? $default,) {final _that = this;
switch (_that) {
case _Quote() when $default != null:
return $default(_that.id,_that.author,_that.body);case _:
return null;
}
}
}
/// @nodoc
class _Quote implements Quote {
const _Quote({required this.id, required this.author, required this.body});
@override final int id;
@override final String author;
@override final String body;
/// Create a copy of Quote
/// with the given fields replaced by the non-null parameter values.
@override @JsonKey(includeFromJson: false, includeToJson: false)
@pragma('vm:prefer-inline')
_$QuoteCopyWith<_Quote> get copyWith => __$QuoteCopyWithImpl<_Quote>(this, _$identity);
@override
bool operator ==(Object other) {
return identical(this, other) || (other.runtimeType == runtimeType&&other is _Quote&&(identical(other.id, id) || other.id == id)&&(identical(other.author, author) || other.author == author)&&(identical(other.body, body) || other.body == body));
}
@override
int get hashCode => Object.hash(runtimeType,id,author,body);
@override
String toString() {
return 'Quote(id: $id, author: $author, body: $body)';
}
}
/// @nodoc
abstract mixin class _$QuoteCopyWith<$Res> implements $QuoteCopyWith<$Res> {
factory _$QuoteCopyWith(_Quote value, $Res Function(_Quote) _then) = __$QuoteCopyWithImpl;
@override @useResult
$Res call({
int id, String author, String body
});
}
/// @nodoc
class __$QuoteCopyWithImpl<$Res>
implements _$QuoteCopyWith<$Res> {
__$QuoteCopyWithImpl(this._self, this._then);
final _Quote _self;
final $Res Function(_Quote) _then;
/// Create a copy of Quote
/// with the given fields replaced by the non-null parameter values.
@override @pragma('vm:prefer-inline') $Res call({Object? id = null,Object? author = null,Object? body = null,}) {
return _then(_Quote(
id: null == id ? _self.id : id // ignore: cast_nullable_to_non_nullable
as int,author: null == author ? _self.author : author // ignore: cast_nullable_to_non_nullable
as String,body: null == body ? _self.body : body // ignore: cast_nullable_to_non_nullable
as String,
));
}
}
// dart format on

View file

@ -0,0 +1,14 @@
import 'package:freezed_annotation/freezed_annotation.dart';
import 'package:kuwot/features/quote/data/models/translation_model.dart';
part 'translation.freezed.dart';
@freezed
abstract class Translation with _$Translation {
const factory Translation({required String id, required String language}) =
_Translation;
static Translation fromModel(TranslationModel model) {
return Translation(id: model.id, language: model.lang);
}
}

View file

@ -0,0 +1,274 @@
// 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 'translation.dart';
// **************************************************************************
// FreezedGenerator
// **************************************************************************
// dart format off
T _$identity<T>(T value) => value;
/// @nodoc
mixin _$Translation {
String get id; String get language;
/// Create a copy of Translation
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
@pragma('vm:prefer-inline')
$TranslationCopyWith<Translation> get copyWith => _$TranslationCopyWithImpl<Translation>(this as Translation, _$identity);
@override
bool operator ==(Object other) {
return identical(this, other) || (other.runtimeType == runtimeType&&other is Translation&&(identical(other.id, id) || other.id == id)&&(identical(other.language, language) || other.language == language));
}
@override
int get hashCode => Object.hash(runtimeType,id,language);
@override
String toString() {
return 'Translation(id: $id, language: $language)';
}
}
/// @nodoc
abstract mixin class $TranslationCopyWith<$Res> {
factory $TranslationCopyWith(Translation value, $Res Function(Translation) _then) = _$TranslationCopyWithImpl;
@useResult
$Res call({
String id, String language
});
}
/// @nodoc
class _$TranslationCopyWithImpl<$Res>
implements $TranslationCopyWith<$Res> {
_$TranslationCopyWithImpl(this._self, this._then);
final Translation _self;
final $Res Function(Translation) _then;
/// Create a copy of Translation
/// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline') @override $Res call({Object? id = null,Object? language = null,}) {
return _then(_self.copyWith(
id: null == id ? _self.id : id // ignore: cast_nullable_to_non_nullable
as String,language: null == language ? _self.language : language // ignore: cast_nullable_to_non_nullable
as String,
));
}
}
/// Adds pattern-matching-related methods to [Translation].
extension TranslationPatterns on Translation {
/// 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<TResult extends Object?>(TResult Function( _Translation value)? $default,{required TResult orElse(),}){
final _that = this;
switch (_that) {
case _Translation() when $default != null:
return $default(_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<TResult extends Object?>(TResult Function( _Translation value) $default,){
final _that = this;
switch (_that) {
case _Translation():
return $default(_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<TResult extends Object?>(TResult? Function( _Translation value)? $default,){
final _that = this;
switch (_that) {
case _Translation() when $default != null:
return $default(_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<TResult extends Object?>(TResult Function( String id, String language)? $default,{required TResult orElse(),}) {final _that = this;
switch (_that) {
case _Translation() when $default != null:
return $default(_that.id,_that.language);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<TResult extends Object?>(TResult Function( String id, String language) $default,) {final _that = this;
switch (_that) {
case _Translation():
return $default(_that.id,_that.language);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<TResult extends Object?>(TResult? Function( String id, String language)? $default,) {final _that = this;
switch (_that) {
case _Translation() when $default != null:
return $default(_that.id,_that.language);case _:
return null;
}
}
}
/// @nodoc
class _Translation implements Translation {
const _Translation({required this.id, required this.language});
@override final String id;
@override final String language;
/// Create a copy of Translation
/// with the given fields replaced by the non-null parameter values.
@override @JsonKey(includeFromJson: false, includeToJson: false)
@pragma('vm:prefer-inline')
_$TranslationCopyWith<_Translation> get copyWith => __$TranslationCopyWithImpl<_Translation>(this, _$identity);
@override
bool operator ==(Object other) {
return identical(this, other) || (other.runtimeType == runtimeType&&other is _Translation&&(identical(other.id, id) || other.id == id)&&(identical(other.language, language) || other.language == language));
}
@override
int get hashCode => Object.hash(runtimeType,id,language);
@override
String toString() {
return 'Translation(id: $id, language: $language)';
}
}
/// @nodoc
abstract mixin class _$TranslationCopyWith<$Res> implements $TranslationCopyWith<$Res> {
factory _$TranslationCopyWith(_Translation value, $Res Function(_Translation) _then) = __$TranslationCopyWithImpl;
@override @useResult
$Res call({
String id, String language
});
}
/// @nodoc
class __$TranslationCopyWithImpl<$Res>
implements _$TranslationCopyWith<$Res> {
__$TranslationCopyWithImpl(this._self, this._then);
final _Translation _self;
final $Res Function(_Translation) _then;
/// Create a copy of Translation
/// with the given fields replaced by the non-null parameter values.
@override @pragma('vm:prefer-inline') $Res call({Object? id = null,Object? language = null,}) {
return _then(_Translation(
id: null == id ? _self.id : id // ignore: cast_nullable_to_non_nullable
as String,language: null == language ? _self.language : language // ignore: cast_nullable_to_non_nullable
as String,
));
}
}
// dart format on

View file

@ -0,0 +1,19 @@
import 'package:fpdart/fpdart.dart';
import 'package:kuwot/core/data/local/translation_target_config.dart';
import 'package:kuwot/core/error/failure.dart';
import 'package:kuwot/features/quote/domain/entities/background_image.dart';
import 'package:kuwot/features/quote/domain/entities/quote.dart';
import 'package:kuwot/features/quote/domain/entities/translation.dart';
abstract class QuoteRepository {
Future<Either<Failure, List<BackgroundImage>>> getBackgroundImages();
Future<Either<Failure, Quote>> getQuote(TranslationTarget? translationTarget);
Future<Either<Failure, Quote>> getTranslatedQuote(
int id,
TranslationTarget translationTarget,
);
Future<Either<Failure, List<Translation>>> getTranslations();
}

View file

@ -0,0 +1,17 @@
import 'package:fpdart/fpdart.dart';
import 'package:kuwot/core/domain/no_params.dart';
import 'package:kuwot/core/domain/use_case.dart';
import 'package:kuwot/core/error/failure.dart';
import 'package:kuwot/features/quote/domain/entities/background_image.dart';
import 'package:kuwot/features/quote/domain/repositories/quote_repository.dart';
class GetBackgroundImages extends UseCase<List<BackgroundImage>, NoParams> {
final QuoteRepository _repository;
GetBackgroundImages(QuoteRepository repository) : _repository = repository;
@override
Future<Either<Failure, List<BackgroundImage>>> call(NoParams params) async {
return await _repository.getBackgroundImages();
}
}

View file

@ -0,0 +1,27 @@
import 'package:equatable/equatable.dart';
import 'package:fpdart/fpdart.dart';
import 'package:kuwot/core/data/local/translation_target_config.dart';
import 'package:kuwot/core/domain/use_case.dart';
import 'package:kuwot/core/error/failure.dart';
import 'package:kuwot/features/quote/domain/entities/quote.dart';
import 'package:kuwot/features/quote/domain/repositories/quote_repository.dart';
class GetQuote extends UseCase<Quote, GetQuoteParams> {
final QuoteRepository _repository;
GetQuote(QuoteRepository repository) : _repository = repository;
@override
Future<Either<Failure, Quote>> call(GetQuoteParams params) {
return _repository.getQuote(params.translationTarget);
}
}
class GetQuoteParams extends Equatable {
final TranslationTarget? translationTarget;
const GetQuoteParams(this.translationTarget);
@override
List<Object?> get props => [translationTarget];
}

View file

@ -0,0 +1,34 @@
import 'package:equatable/equatable.dart';
import 'package:fpdart/fpdart.dart';
import 'package:kuwot/core/data/local/translation_target_config.dart';
import 'package:kuwot/core/domain/use_case.dart';
import 'package:kuwot/core/error/failure.dart';
import 'package:kuwot/features/quote/domain/entities/quote.dart';
import 'package:kuwot/features/quote/domain/repositories/quote_repository.dart';
class GetTranslatedQuote extends UseCase<Quote, GetTranslatedQuoteParams> {
final QuoteRepository _repository;
GetTranslatedQuote(QuoteRepository repository) : _repository = repository;
@override
Future<Either<Failure, Quote>> call(GetTranslatedQuoteParams params) async {
return await _repository.getTranslatedQuote(
params.id,
params.translationTarget,
);
}
}
class GetTranslatedQuoteParams extends Equatable {
final int id;
final TranslationTarget translationTarget;
const GetTranslatedQuoteParams({
required this.id,
required this.translationTarget,
});
@override
List<Object?> get props => [id, translationTarget];
}

View file

@ -0,0 +1,17 @@
import 'package:fpdart/fpdart.dart';
import 'package:kuwot/core/domain/no_params.dart';
import 'package:kuwot/core/domain/use_case.dart';
import 'package:kuwot/core/error/failure.dart';
import 'package:kuwot/features/quote/domain/entities/translation.dart';
import 'package:kuwot/features/quote/domain/repositories/quote_repository.dart';
class GetTranslations extends UseCase<List<Translation>, NoParams> {
final QuoteRepository _repository;
GetTranslations(QuoteRepository repository) : _repository = repository;
@override
Future<Either<Failure, List<Translation>>> call(NoParams params) {
return _repository.getTranslations();
}
}

View file

@ -0,0 +1,37 @@
import 'package:equatable/equatable.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:kuwot/core/domain/no_params.dart';
import 'package:kuwot/core/presentation/bloc/error_state.dart';
import 'package:kuwot/features/quote/domain/entities/background_image.dart';
import 'package:kuwot/features/quote/domain/use_cases/get_background_images.dart';
part 'background_images_events.dart';
part 'background_images_states.dart';
class BackgroundImagesBloc
extends Bloc<BackgroundImagesEvent, BackgroundImagesState> {
final GetBackgroundImages getBackgroundImages;
BackgroundImagesBloc({required this.getBackgroundImages})
: super(const BackgroundImagesInitialState()) {
on<GetBackgroundImagesEvent>(_onGetBackgroundImages);
}
Future<void> _onGetBackgroundImages(
GetBackgroundImagesEvent event,
Emitter<BackgroundImagesState> emit,
) async {
emit(const BackgroundImagesLoadingState());
final result = await getBackgroundImages(const NoParams());
result.fold(
(failure) => emit(
BackgroundImagesErrorState(
message: failure.message,
cause: failure.cause,
),
),
(photos) => emit(BackgroundImagesLoadedState(photos)),
);
}
}

View file

@ -0,0 +1,12 @@
part of 'background_images_bloc.dart';
abstract class BackgroundImagesEvent extends Equatable {
const BackgroundImagesEvent();
}
class GetBackgroundImagesEvent extends BackgroundImagesEvent {
const GetBackgroundImagesEvent();
@override
List<Object> get props => [];
}

View file

@ -0,0 +1,45 @@
part of 'background_images_bloc.dart';
abstract class BackgroundImagesState extends Equatable {
const BackgroundImagesState();
@override
String toString() => runtimeType.toString();
}
class BackgroundImagesInitialState extends BackgroundImagesState {
const BackgroundImagesInitialState();
@override
List<Object> get props => [];
}
class BackgroundImagesLoadingState extends BackgroundImagesState {
const BackgroundImagesLoadingState();
@override
List<Object> get props => [];
}
class BackgroundImagesLoadedState extends BackgroundImagesState {
final List<BackgroundImage> backgroundImages;
const BackgroundImagesLoadedState(this.backgroundImages);
@override
List<Object> get props => [backgroundImages];
}
class BackgroundImagesErrorState extends BackgroundImagesState
implements ErrorState {
@override
final String message;
@override
final Exception? cause;
const BackgroundImagesErrorState({required this.message, this.cause});
@override
List<Object?> get props => [message, cause];
}

View file

@ -0,0 +1,83 @@
import 'package:bloc_concurrency/bloc_concurrency.dart';
import 'package:equatable/equatable.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:kuwot/core/data/local/config.dart';
import 'package:kuwot/core/data/local/translation_target_config.dart';
import 'package:kuwot/core/presentation/bloc/error_state.dart';
import 'package:kuwot/features/quote/domain/entities/quote.dart';
import 'package:kuwot/features/quote/domain/use_cases/get_quote.dart';
import 'package:kuwot/features/quote/domain/use_cases/get_translated_quote.dart';
part 'quote_events.dart';
part 'quote_states.dart';
class QuoteBloc extends Bloc<QuoteEvent, QuoteState> {
final GetQuote getQuote;
final GetTranslatedQuote getTranslatedQuote;
final Config<TranslationTarget> translationTargetConfig;
int _originalQuoteId = -1;
QuoteBloc({
required this.getQuote,
required this.getTranslatedQuote,
required this.translationTargetConfig,
}) : super(const QuoteInitialState()) {
on<GetQuoteEvent>(_onGetQuoteEvent, transformer: droppable());
on<GetTranslatedQuoteEvent>(
_onGetTranslatedQuoteEvent,
transformer: droppable(),
);
}
Future<void> _onGetQuoteEvent(
QuoteEvent event,
Emitter<QuoteState> emit,
) async {
emit(const QuoteLoadingState());
// add delay to limit the number of api calls
await Future.delayed(const Duration(seconds: 1));
// check if need to translate the quote
final translationTarget = await translationTargetConfig.get();
// get quote
final result = await getQuote(GetQuoteParams(translationTarget));
result.fold((failure) => emit(QuoteErrorState(message: failure.message)), (
quote,
) {
_originalQuoteId = quote.id;
emit(QuoteLoadedState(quote: quote));
});
}
Future<void> _onGetTranslatedQuoteEvent(
GetTranslatedQuoteEvent event,
Emitter<QuoteState> emit,
) async {
emit(const QuoteLoadingState());
// add delay to limit the number of api calls
await Future.delayed(const Duration(seconds: 1));
// get translation target
final translationTarget = await translationTargetConfig.get();
if (translationTarget == null) {
emit(const QuoteErrorState(message: 'Translation target is not set'));
return;
}
// get translated quote
final result = await getTranslatedQuote(
GetTranslatedQuoteParams(
id: _originalQuoteId,
translationTarget: translationTarget,
),
);
result.fold(
(failure) => emit(QuoteErrorState(message: failure.message)),
(quote) => emit(QuoteLoadedState(quote: quote)),
);
}
}

View file

@ -0,0 +1,21 @@
part of 'quote_bloc.dart';
abstract class QuoteEvent extends Equatable {
const QuoteEvent();
}
class GetQuoteEvent extends QuoteEvent {
const GetQuoteEvent();
@override
List<Object> get props => [];
}
class GetTranslatedQuoteEvent extends QuoteEvent {
final TranslationTarget translationTarget;
const GetTranslatedQuoteEvent(this.translationTarget);
@override
List<Object> get props => [translationTarget];
}

View file

@ -0,0 +1,44 @@
part of 'quote_bloc.dart';
abstract class QuoteState extends Equatable {
const QuoteState();
@override
String toString() => runtimeType.toString();
}
class QuoteInitialState extends QuoteState {
const QuoteInitialState();
@override
List<Object> get props => [];
}
class QuoteLoadingState extends QuoteState {
const QuoteLoadingState();
@override
List<Object> get props => [];
}
class QuoteLoadedState extends QuoteState {
final Quote quote;
const QuoteLoadedState({required this.quote});
@override
List<Object> get props => [quote];
}
class QuoteErrorState extends QuoteState implements ErrorState {
@override
final String message;
@override
final Exception? cause;
const QuoteErrorState({required this.message, this.cause});
@override
List<Object?> get props => [message, cause];
}

View file

@ -0,0 +1,31 @@
import 'package:equatable/equatable.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:kuwot/core/domain/no_params.dart';
import 'package:kuwot/core/presentation/bloc/error_state.dart';
import 'package:kuwot/features/quote/domain/entities/translation.dart';
import 'package:kuwot/features/quote/domain/use_cases/get_translations.dart';
part 'translations_events.dart';
part 'translations_states.dart';
class TranslationsBloc extends Bloc<TranslationsEvent, TranslationsState> {
final GetTranslations getTranslations;
TranslationsBloc({required this.getTranslations})
: super(const TranslationsInitialState()) {
on<GetTranslationsEvent>(_onGetTranslationEvent);
}
Future<void> _onGetTranslationEvent(
TranslationsEvent event,
Emitter<TranslationsState> emit,
) async {
emit(const TranslationsLoadingState());
final translations = await getTranslations(const NoParams());
translations.fold(
(failure) => emit(TranslationsErrorState(message: failure.message)),
(translations) =>
emit(TranslationsLoadedState(translations: translations)),
);
}
}

View file

@ -0,0 +1,12 @@
part of 'translations_bloc.dart';
abstract class TranslationsEvent extends Equatable {
const TranslationsEvent();
}
class GetTranslationsEvent extends TranslationsEvent {
const GetTranslationsEvent();
@override
List<Object> get props => [];
}

View file

@ -0,0 +1,41 @@
part of 'translations_bloc.dart';
abstract class TranslationsState extends Equatable {
const TranslationsState();
}
class TranslationsInitialState extends TranslationsState {
const TranslationsInitialState();
@override
List<Object> get props => [];
}
class TranslationsLoadingState extends TranslationsState {
const TranslationsLoadingState();
@override
List<Object> get props => [];
}
class TranslationsLoadedState extends TranslationsState {
final List<Translation> translations;
const TranslationsLoadedState({required this.translations});
@override
List<Object> get props => [translations];
}
class TranslationsErrorState extends TranslationsState implements ErrorState {
@override
final String message;
@override
final Exception? cause;
const TranslationsErrorState({required this.message, this.cause});
@override
List<Object?> get props => [message, cause];
}

View file

@ -0,0 +1,209 @@
import 'package:auto_route/auto_route.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flutter_svg/svg.dart';
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
import 'package:google_fonts/google_fonts.dart';
import 'package:kuwot/core/data/local/translation_target_config.dart';
import 'package:kuwot/core/router/app_router.gr.dart';
import 'package:kuwot/features/quote/data/data_sources/remote/kuwot_api_remote_data_source.dart';
import 'package:kuwot/features/quote/presentation/bloc/background_images_bloc.dart';
import 'package:kuwot/features/quote/presentation/bloc/quote_bloc.dart';
import 'package:kuwot/features/quote/presentation/widgets/background_image_widget.dart';
import 'package:kuwot/features/quote/presentation/widgets/quote_widget.dart';
import 'package:kuwot/features/quote/presentation/widgets/translate_target_dialog.dart';
import 'package:screenshot/screenshot.dart';
import 'package:share_plus/share_plus.dart';
@RoutePage()
class QuotePage extends StatefulWidget {
const QuotePage({super.key});
@override
State<QuotePage> createState() => _QuotePageState();
}
class _QuotePageState extends State<QuotePage> {
final _screenshotController = ScreenshotController();
int _backgroundIndex = 0;
bool _isSharingQuote = false;
late QuoteBloc _dailyQuoteBloc;
late BackgroundImagesBloc _backgroundImagesBloc;
@override
void initState() {
super.initState();
_dailyQuoteBloc = context.read<QuoteBloc>();
_backgroundImagesBloc = context.read<BackgroundImagesBloc>();
// get daily quote & background photos
_dailyQuoteBloc.add(const GetQuoteEvent());
_backgroundImagesBloc.add(const GetBackgroundImagesEvent());
}
@override
Widget build(BuildContext context) {
final header = Padding(
padding: const EdgeInsets.fromLTRB(24, 8, 16, 8),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Row(
children: [
Text('Kuwot', style: GoogleFonts.sriracha(fontSize: 30)),
const SizedBox(width: 2),
SvgPicture.asset(
'assets/svgs/chat-quote.svg',
height: 24,
colorFilter: ColorFilter.mode(
Theme.of(context).colorScheme.onSurface,
BlendMode.srcIn,
),
),
],
),
Row(
mainAxisAlignment: MainAxisAlignment.end,
children: [
IconButton(
onPressed: () {
context.router.push(const DonationRoute());
},
icon: const FaIcon(FontAwesomeIcons.mugHot),
),
IconButton(
onPressed: () {
context.router.push(const AppSettingsRoute());
},
icon: const FaIcon(FontAwesomeIcons.sliders),
),
],
),
],
),
);
final body = SafeArea(
child: Stack(
fit: StackFit.expand,
children: [
Align(alignment: Alignment.topLeft, child: header),
Padding(
padding: const EdgeInsets.fromLTRB(24, 70, 24, 24),
child: _buildQuote(),
),
],
),
);
return Scaffold(body: body);
}
Widget _buildQuote() {
final screenshotEnabledQuote = Screenshot(
controller: _screenshotController,
child: Stack(
fit: StackFit.expand,
children: [
BackgroundPhotoWidget(
backgroundIndex: _backgroundIndex,
hideImageInfoButton: _isSharingQuote,
),
const Align(alignment: Alignment.center, child: QuoteWidget()),
],
),
);
final actionButtons = [
Expanded(
child: _buildQuoteActionButton(
onPressed: () {
_dailyQuoteBloc.add(const GetQuoteEvent());
},
icon: const FaIcon(FontAwesomeIcons.quoteRight),
),
),
Expanded(
child: _buildQuoteActionButton(
onPressed: _cycleBackground,
icon: const FaIcon(FontAwesomeIcons.image),
),
),
Expanded(
child: _buildQuoteActionButton(
onPressed: () async {
final result = await showAdaptiveDialog<TranslationTarget?>(
context: context,
barrierDismissible: true,
builder: (context) => const TranslateTargetDialog(),
);
if (result != null) {
_dailyQuoteBloc.add(GetTranslatedQuoteEvent(result));
}
},
icon: const FaIcon(FontAwesomeIcons.language),
),
),
Expanded(
child: _buildQuoteActionButton(
onPressed: _shareQuote,
icon: const FaIcon(FontAwesomeIcons.shareNodes),
),
),
];
return Card(
clipBehavior: Clip.antiAlias,
margin: const EdgeInsets.all(0),
elevation: 12,
child: Column(
children: [
Expanded(child: screenshotEnabledQuote),
Row(children: actionButtons),
],
),
);
}
Widget _buildQuoteActionButton({
required VoidCallback onPressed,
required Widget icon,
}) {
return InkWell(
onTap: onPressed,
child: Container(
height: 50,
alignment: Alignment.center,
padding: const EdgeInsets.all(8),
child: icon,
),
);
}
void _cycleBackground() {
setState(() {
_backgroundIndex = ++_backgroundIndex % imagesPerPage;
});
}
Future<void> _shareQuote() async {
setState(() => _isSharingQuote = true);
final image = await _screenshotController.capture();
setState(() => _isSharingQuote = false);
if (image == null) return;
final shareParams = ShareParams(
files: [
XFile.fromData(
image,
mimeType: 'image/png',
name: 'kuwot_${DateTime.now().millisecondsSinceEpoch}.png',
),
],
);
await SharePlus.instance.share(shareParams);
}
}

View file

@ -0,0 +1,134 @@
import 'package:auto_route/auto_route.dart';
import 'package:cached_network_image/cached_network_image.dart';
import 'package:flutter/material.dart';
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
import 'package:google_fonts/google_fonts.dart';
import 'package:kuwot/features/quote/domain/entities/background_image.dart';
import 'package:url_launcher/url_launcher_string.dart';
class AboutImageDialog extends StatelessWidget {
final BackgroundImage image;
const AboutImageDialog({required this.image, super.key});
@override
Widget build(BuildContext context) {
final header = Padding(
padding: const EdgeInsets.fromLTRB(20, 16, 8, 16),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text('About Image', style: GoogleFonts.sriracha(fontSize: 24)),
IconButton(
onPressed: () {
context.router.maybePop();
},
icon: const FaIcon(FontAwesomeIcons.xmark),
),
],
),
);
final info = Padding(
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
CircleAvatar(
radius: 40,
child: ClipOval(
child: CachedNetworkImage(
imageUrl: image.authorProfileImageUrl,
placeholder: (context, url) => CircularProgressIndicator(
color: Theme.of(context).colorScheme.onSurfaceVariant,
),
),
),
),
const SizedBox(height: 8),
Text(
image.authorName,
style: Theme.of(context).textTheme.titleLarge,
textAlign: TextAlign.center,
),
Text(image.authorBio, textAlign: TextAlign.center),
const SizedBox(height: 4),
Wrap(
alignment: WrapAlignment.center,
children: [
_buildUserInfoChip(
icon: FontAwesomeIcons.locationDot,
text: image.authorLocation,
),
_buildUserInfoChip(
icon: FontAwesomeIcons.cameraRetro,
text: '${image.authorTotalPhotos} photos',
),
_buildUserInfoChip(
icon: FontAwesomeIcons.thumbsUp,
text: '${image.authorTotalLikes} likes',
),
image.authorIsForHire
? _buildUserInfoChip(
icon: FontAwesomeIcons.handshake,
text: 'For hire',
)
: const SizedBox.shrink(),
],
),
const SizedBox(height: 16),
Text(
image.description,
style: Theme.of(
context,
).textTheme.bodyLarge?.copyWith(fontStyle: FontStyle.italic),
),
],
),
);
final footer = Padding(
padding: const EdgeInsets.all(8),
child: Row(
mainAxisAlignment: MainAxisAlignment.end,
children: [
TextButton(
onPressed: () {
launchUrlString(image.authorUrl);
},
child: const Text('Author profile'),
),
TextButton(
onPressed: () {
launchUrlString(image.originUrl);
},
child: const Text('Original image'),
),
],
),
);
return Dialog(
clipBehavior: Clip.antiAlias,
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
mainAxisSize: MainAxisSize.min,
children: [header, const Divider(height: 1), info, footer],
),
);
}
Widget _buildUserInfoChip({required IconData icon, required String text}) {
return Padding(
padding: const EdgeInsets.symmetric(horizontal: 4),
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
FaIcon(icon, size: 16),
const SizedBox(width: 4),
Flexible(child: Text(text)),
],
),
);
}
}

View file

@ -0,0 +1,152 @@
import 'package:cached_network_image/cached_network_image.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
import 'package:kuwot/core/presentation/error_retry_snackbar.dart';
import 'package:kuwot/features/quote/data/data_sources/remote/kuwot_api_remote_data_source.dart';
import 'package:kuwot/features/quote/presentation/bloc/background_images_bloc.dart';
import 'package:kuwot/features/quote/presentation/widgets/about_image_dialog.dart';
import 'package:kuwot/utilities.dart';
import 'package:url_launcher/url_launcher_string.dart';
class BackgroundPhotoWidget extends StatelessWidget {
final int backgroundIndex;
final bool hideImageInfoButton;
const BackgroundPhotoWidget({
required this.backgroundIndex,
this.hideImageInfoButton = false,
super.key,
}) : assert(backgroundIndex < imagesPerPage);
@override
Widget build(BuildContext context) {
return BlocConsumer<BackgroundImagesBloc, BackgroundImagesState>(
listener: (context, state) {
// handle error state
if (state is BackgroundImagesErrorState) {
ErrorRetrySnackbar.show(
context,
errorMessage: state.message,
onRetry: () {
context.read<BackgroundImagesBloc>().add(
const GetBackgroundImagesEvent(),
);
},
);
}
},
builder: (context, state) {
if (state is BackgroundImagesLoadedState) {
const borderRadius = BorderRadius.only(
bottomLeft: Radius.circular(16),
);
final imageInfoButton = Material(
color: Colors.black26,
borderRadius: borderRadius,
clipBehavior: Clip.antiAlias,
child: InkWell(
onTap: () {
showAdaptiveDialog(
context: context,
barrierDismissible: true,
builder: (context) => AboutImageDialog(
image: state.backgroundImages[backgroundIndex],
),
);
},
child: Container(
height: 40,
width: 40,
alignment: Alignment.center,
decoration: const BoxDecoration(borderRadius: borderRadius),
child: const FaIcon(
FontAwesomeIcons.circleInfo,
size: 20,
color: Colors.white54,
),
),
),
);
final imageAttribution = ColoredBox(
color: Colors.black38,
child: Padding(
padding: const EdgeInsets.symmetric(vertical: 2),
child: Wrap(
alignment: WrapAlignment.center,
children: [
Text(
'Photo by ',
style: Theme.of(
context,
).textTheme.bodySmall?.copyWith(color: Colors.white54),
),
GestureDetector(
child: Text(
state.backgroundImages[backgroundIndex].authorName,
style: Theme.of(context).textTheme.bodySmall?.copyWith(
decoration: TextDecoration.underline,
color: Colors.white.withValues(alpha: 0.85),
decorationColor: Colors.white.withValues(alpha: 0.85),
),
),
onTap: () async {
await launchUrlString(
state.backgroundImages[backgroundIndex].authorUrl,
);
},
),
Text(
' on ',
style: Theme.of(
context,
).textTheme.bodySmall?.copyWith(color: Colors.white54),
),
GestureDetector(
child: Text(
'Unsplash',
style: Theme.of(context).textTheme.bodySmall?.copyWith(
decoration: TextDecoration.underline,
color: Colors.white.withValues(alpha: 0.85),
decorationColor: Colors.white.withValues(alpha: 0.85),
),
),
onTap: () async {
await launchUrlString(
"https://unsplash.com?utm_source=kuwot&utm_medium=referral",
);
},
),
],
),
),
);
return Stack(
fit: StackFit.expand,
children: [
CachedNetworkImage(
imageUrl: state.backgroundImages[backgroundIndex].url,
fit: BoxFit.cover,
placeholder: (_, _) {
final avgColor =
state.backgroundImages[backgroundIndex].color;
return Container(color: getColorFromHexString(avgColor));
},
errorWidget: (_, _, _) {
return Container(color: Colors.grey);
},
),
hideImageInfoButton
? const SizedBox()
: Positioned(top: 0, right: 0, child: imageInfoButton),
Positioned(bottom: 0, left: 0, right: 0, child: imageAttribution),
],
);
}
return Container(color: Colors.grey);
},
);
}
}

View file

@ -0,0 +1,111 @@
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flutter_svg/svg.dart';
import 'package:kuwot/core/presentation/error_retry_snackbar.dart';
import 'package:kuwot/features/quote/presentation/bloc/quote_bloc.dart';
class QuoteWidget extends StatefulWidget {
const QuoteWidget({super.key});
@override
State<QuoteWidget> createState() => _QuoteWidgetState();
}
class _QuoteWidgetState extends State<QuoteWidget>
with SingleTickerProviderStateMixin {
late AnimationController _animationController;
String? _quoteBody;
String? _quoteAuthor;
@override
void initState() {
super.initState();
// init animation
_animationController = AnimationController(
vsync: this,
duration: const Duration(milliseconds: 500),
);
_animationController.addListener(() => setState(() {}));
}
@override
Widget build(BuildContext context) {
return BlocConsumer<QuoteBloc, QuoteState>(
listener: (context, state) {
// animate quote loading state
if (state is QuoteLoadingState) {
_animationController.repeat(reverse: true);
} else {
_animationController.forward();
}
// handle error state
if (state is QuoteErrorState) {
ErrorRetrySnackbar.show(
context,
errorMessage: state.message,
onRetry: () {
context.read<QuoteBloc>().add(const GetQuoteEvent());
},
);
}
},
builder: (context, state) {
if (state is QuoteLoadedState) {
_quoteBody = state.quote.body;
_quoteAuthor = state.quote.author;
}
return Container(
decoration: BoxDecoration(
color: Colors.black54,
borderRadius: BorderRadius.circular(16),
),
padding: const EdgeInsets.all(16.0),
margin: const EdgeInsets.all(16.0),
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
Opacity(
opacity: _animationController.value,
child: SvgPicture.asset(
'assets/svgs/chat-quote-fill.svg',
height: 54,
colorFilter: const ColorFilter.mode(
Colors.white54,
BlendMode.srcIn,
),
),
),
const SizedBox(height: 30),
Text(
_quoteBody ?? '...',
style: Theme.of(
context,
).textTheme.headlineSmall?.copyWith(color: Colors.white),
textAlign: TextAlign.center,
),
const SizedBox(height: 30),
Text(
'- ${_quoteAuthor ?? 'Kuwot'}',
style: Theme.of(
context,
).textTheme.bodyLarge?.copyWith(color: Colors.white),
textAlign: TextAlign.right,
),
],
),
);
},
);
}
@override
void dispose() {
_animationController.dispose();
super.dispose();
}
}

View file

@ -0,0 +1,104 @@
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
import 'package:google_fonts/google_fonts.dart';
import 'package:kuwot/core/data/local/translation_target_config.dart';
import 'package:kuwot/core/presentation/bloc/config/translation_target_cubit.dart';
import 'package:kuwot/features/quote/presentation/bloc/translations_bloc.dart';
class TranslateTargetDialog extends StatefulWidget {
const TranslateTargetDialog({super.key});
@override
State<TranslateTargetDialog> createState() => _TranslateTargetDialogState();
}
class _TranslateTargetDialogState extends State<TranslateTargetDialog> {
@override
void initState() {
super.initState();
// load translations
context.read<TranslationsBloc>().add(const GetTranslationsEvent());
}
@override
Widget build(BuildContext context) {
final header = Padding(
padding: const EdgeInsets.fromLTRB(20, 16, 8, 16),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text('Quote Language', style: GoogleFonts.sriracha(fontSize: 24)),
IconButton(
onPressed: () {
Navigator.of(context).pop<TranslationTarget?>(null);
},
icon: const FaIcon(FontAwesomeIcons.xmark),
),
],
),
);
final currentTranslationTarget = context
.read<TranslationTargetCubit>()
.state;
final languageList = SizedBox(
height: MediaQuery.of(context).size.height / 2,
child: BlocBuilder<TranslationsBloc, TranslationsState>(
builder: (context, state) {
if (state is TranslationsLoadedState) {
final translations = state.translations;
return ListView.builder(
padding: EdgeInsets.zero,
itemCount: translations.length,
itemBuilder: (context, index) {
final isSelected =
currentTranslationTarget.id == translations[index].id;
return ListTile(
contentPadding: const EdgeInsets.symmetric(horizontal: 20),
title: Text(
translations[index].language,
style: Theme.of(context).textTheme.bodyLarge?.copyWith(
fontWeight: isSelected
? FontWeight.bold
: FontWeight.normal,
),
),
trailing: isSelected
? const FaIcon(FontAwesomeIcons.check)
: null,
onTap: () {
final translationTarget = TranslationTarget(
id: translations[index].id,
name: translations[index].language,
);
context.read<TranslationTargetCubit>().set(
translationTarget,
);
Navigator.of(
context,
).pop<TranslationTarget?>(translationTarget);
},
);
},
);
}
return const Center(child: CircularProgressIndicator());
},
),
);
return Dialog(
clipBehavior: Clip.antiAlias,
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
mainAxisSize: MainAxisSize.min,
children: [header, const Divider(height: 1), languageList],
),
);
}
}

View file

@ -0,0 +1,159 @@
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:get_it/get_it.dart';
import 'package:in_app_purchase/in_app_purchase.dart';
import 'package:kuwot/core/app_updater.dart';
import 'package:kuwot/core/data/local/config.dart';
import 'package:kuwot/core/data/local/theme_mode_config.dart';
import 'package:kuwot/core/data/local/translation_target_config.dart';
import 'package:kuwot/core/env.dart';
import 'package:kuwot/core/network/network.dart';
import 'package:kuwot/core/presentation/bloc/config/theme_mode_cubit.dart';
import 'package:kuwot/core/presentation/bloc/config/translation_target_cubit.dart';
import 'package:kuwot/core/time.dart';
import 'package:kuwot/features/in_app_purchase/data/data_sources/remote/in_app_purchase_remote_data_source.dart';
import 'package:kuwot/features/in_app_purchase/data/repositories/in_app_purchase_repository_impl.dart';
import 'package:kuwot/features/in_app_purchase/domain/repositories/in_app_purchase_repository.dart';
import 'package:kuwot/features/in_app_purchase/domain/use_case/get_consumable_products.dart';
import 'package:kuwot/features/in_app_purchase/domain/use_case/listen_purchase.dart';
import 'package:kuwot/features/in_app_purchase/domain/use_case/purchase_consumable_product.dart';
import 'package:kuwot/features/in_app_purchase/presentation/bloc/in_app_purchase_bloc.dart';
import 'package:kuwot/features/in_app_purchase/presentation/bloc/purchase_details_cubit.dart';
import 'package:kuwot/features/in_app_update/presentation/bloc/in_app_update_bloc.dart';
import 'package:kuwot/features/quote/data/data_sources/remote/kuwot_api_remote_data_source.dart';
import 'package:kuwot/features/quote/data/repositories/quote_repository_impl.dart';
import 'package:kuwot/features/quote/domain/repositories/quote_repository.dart';
import 'package:kuwot/features/quote/domain/use_cases/get_background_images.dart';
import 'package:kuwot/features/quote/domain/use_cases/get_quote.dart';
import 'package:kuwot/features/quote/domain/use_cases/get_translated_quote.dart';
import 'package:kuwot/features/quote/domain/use_cases/get_translations.dart';
import 'package:kuwot/features/quote/presentation/bloc/background_images_bloc.dart';
import 'package:kuwot/features/quote/presentation/bloc/quote_bloc.dart';
import 'package:kuwot/features/quote/presentation/bloc/translations_bloc.dart';
import 'package:shared_preferences/shared_preferences.dart';
final getIt = GetIt.instance;
void setup() {
// network
getIt.registerLazySingleton<Network>(() => NetworkImpl());
// shared preferences
getIt.registerSingletonAsync<SharedPreferences>(() async {
final prefs = await SharedPreferences.getInstance();
return prefs;
});
// env
getIt.registerLazySingleton<Env>(() => EnvImpl());
// configs
getIt.registerSingletonWithDependencies<Config<ThemeMode>>(
() => ThemeModeConfig(sharedPreferences: getIt()),
dependsOn: [SharedPreferences],
);
getIt.registerSingletonWithDependencies<Config<TranslationTarget>>(
() => TranslationTargetConfig(sharedPreferences: getIt()),
dependsOn: [SharedPreferences],
);
// data sources
getIt.registerLazySingleton<KuwotApiRemoteDataSource>(
() => KuwotApiRemoteApiImpl(env: getIt(), network: getIt()),
);
getIt.registerLazySingleton<InAppPurchaseRemoteDataSource>(
() => InAppPurchaseRemoteDataSourceImpl(iap: getIt()),
);
// repositories
getIt.registerLazySingleton<QuoteRepository>(
() => QuoteRepositoryImpl(quoteDataSource: getIt()),
);
getIt.registerLazySingleton<InAppPurchaseRepository>(
() => InAppPurchaseRepositoryImpl(inAppPurchaseDataSource: getIt()),
);
// use cases
getIt.registerLazySingleton<GetQuote>(() => GetQuote(getIt()));
getIt.registerLazySingleton<GetTranslatedQuote>(
() => GetTranslatedQuote(getIt()),
);
getIt.registerLazySingleton<GetBackgroundImages>(
() => GetBackgroundImages(getIt()),
);
getIt.registerLazySingleton<GetTranslations>(() => GetTranslations(getIt()));
getIt.registerLazySingleton<GetConsumableProducts>(
() => GetConsumableProducts(getIt()),
);
getIt.registerLazySingleton<PurchaseConsumableProduct>(
() => PurchaseConsumableProduct(getIt()),
);
getIt.registerLazySingleton<ListenPurchase>(() => ListenPurchase(getIt()));
// blocs
getIt.registerSingletonAsync<ThemeModeCubit>(() async {
final initialThemeMode = await getIt<Config<ThemeMode>>().get();
return ThemeModeCubit(
themeModeConfig: getIt(),
initialThemeMode: initialThemeMode ?? ThemeMode.system,
);
}, dependsOn: [SharedPreferences, Config<ThemeMode>]);
getIt.registerLazySingleton<InAppUpdateBloc>(
() => InAppUpdateBloc(appUpdater: getIt()),
);
getIt.registerLazySingleton<InAppPurchaseBloc>(
() => InAppPurchaseBloc(
getConsumableProducts: getIt(),
purchaseConsumableProduct: getIt(),
),
);
getIt.registerLazySingleton<PurchaseDetailsCubit>(
() => PurchaseDetailsCubit(getIt()),
);
getIt.registerSingletonAsync<TranslationTargetCubit>(() async {
final initialTranslationTarget = await getIt<Config<TranslationTarget>>()
.get();
return TranslationTargetCubit(
translationTargetConfig: getIt(),
initialTranslationTarget:
initialTranslationTarget ?? defaultTranslationTarget,
);
}, dependsOn: [SharedPreferences, Config<TranslationTarget>]);
getIt.registerFactory<QuoteBloc>(
() => QuoteBloc(
getQuote: getIt(),
getTranslatedQuote: getIt(),
translationTargetConfig: getIt(),
),
);
getIt.registerFactory<BackgroundImagesBloc>(
() => BackgroundImagesBloc(getBackgroundImages: getIt()),
);
getIt.registerFactory<TranslationsBloc>(
() => TranslationsBloc(getTranslations: getIt()),
);
// others
getIt.registerLazySingleton<AppUpdater>(() => AppUpdaterImpl());
getIt.registerLazySingleton<Time>(() => TimeImpl());
getIt.registerLazySingleton<InAppPurchase>(() => InAppPurchase.instance);
getIt.registerLazySingleton<GlobalKey<ScaffoldMessengerState>>(
() => GlobalKey<ScaffoldMessengerState>(),
);
}
MultiBlocProvider getMultiBlocProvider({required Widget child}) {
return MultiBlocProvider(
providers: [
BlocProvider<ThemeModeCubit>(create: (context) => getIt()),
BlocProvider<InAppUpdateBloc>(create: (context) => getIt()),
BlocProvider<InAppPurchaseBloc>(create: (context) => getIt()),
BlocProvider<PurchaseDetailsCubit>(create: (context) => getIt()),
BlocProvider<TranslationTargetCubit>(create: (context) => getIt()),
BlocProvider<QuoteBloc>(create: (context) => getIt()),
BlocProvider<BackgroundImagesBloc>(create: (context) => getIt()),
BlocProvider<TranslationsBloc>(create: (context) => getIt()),
],
child: child,
);
}

76
lib/main.dart Normal file
View file

@ -0,0 +1,76 @@
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:kuwot/core/env.dart';
import 'package:kuwot/core/presentation/bloc/app_bloc_observer.dart';
import 'package:kuwot/core/presentation/bloc/config/theme_mode_cubit.dart';
import 'package:kuwot/core/presentation/theme/app_theme.dart';
import 'package:kuwot/core/router/app_router.dart';
import 'package:kuwot/features/in_app_purchase/presentation/in_app_purchase_listener.dart';
import 'package:sentry_flutter/sentry_flutter.dart';
import 'injection_container.dart' as ic;
Future<void> main() async {
WidgetsFlutterBinding.ensureInitialized();
// lock screen orientation to portrait
await setPortraitMode();
// dependency injection setup
ic.setup();
await ic.getIt.allReady();
// register bloc observer
Bloc.observer = AppBlocObserver();
// sentry setup
final sentryDsn = EnvImpl().sentryDsn;
if (!kDebugMode && sentryDsn.isNotEmpty) {
await SentryFlutter.init((options) {
options.dsn = sentryDsn;
options.tracesSampleRate = 1.0;
options.profilesSampleRate = 1.0;
}, appRunner: () => runApp(KuwotApp()));
} else {
runApp(KuwotApp());
}
}
Future<void> setPortraitMode() async {
if (kIsWeb) return;
await SystemChrome.setPreferredOrientations([
DeviceOrientation.portraitUp,
DeviceOrientation.portraitDown,
]);
}
class KuwotApp extends StatelessWidget {
KuwotApp({super.key});
final _appRouter = AppRouter();
@override
Widget build(BuildContext context) {
return ic.getMultiBlocProvider(
child: InAppPurchaseListener(
child: BlocBuilder<ThemeModeCubit, ThemeMode>(
bloc: ic.getIt(),
builder: (context, state) {
return MaterialApp.router(
scaffoldMessengerKey: ic.getIt(),
debugShowCheckedModeBanner: false,
title: 'Kuwot',
theme: lightTheme,
darkTheme: darkTheme,
themeMode: state,
routerConfig: _appRouter.config(),
);
},
),
),
);
}
}

20
lib/utilities.dart Normal file
View file

@ -0,0 +1,20 @@
import 'package:flutter/material.dart';
import 'package:get_it/get_it.dart';
void showSnackbar(String message) {
final scaffoldMessenger =
GetIt.I<GlobalKey<ScaffoldMessengerState>>().currentState;
scaffoldMessenger?.showSnackBar(SnackBar(content: Text(message)));
}
Color getColorFromHexString(String hexString) {
if (hexString.startsWith('#')) {
hexString = hexString.substring(1);
}
if (hexString.length == 6) {
hexString = 'FF$hexString';
}
return Color(int.parse(hexString, radix: 16) | 0xFF000000);
}