init
This commit is contained in:
commit
8e54cfffc5
91 changed files with 2686 additions and 0 deletions
23
lib/app.dart
Normal file
23
lib/app.dart
Normal file
|
@ -0,0 +1,23 @@
|
|||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'features/core/abstracts/app_setup.dart';
|
||||
import 'features/core/abstracts/router/app_router.dart';
|
||||
import 'features/core/data/constants/const_colors.dart';
|
||||
|
||||
class McgApp extends StatelessWidget {
|
||||
const McgApp({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final appRouter = McgRouter.locate.router;
|
||||
return MaterialApp.router(
|
||||
title: 'MC Gallery App',
|
||||
theme: ConstThemes.materialLightTheme,
|
||||
darkTheme: ConstThemes.materialDarkTheme,
|
||||
supportedLocales: AppSetup.supportedLocales,
|
||||
routeInformationParser: appRouter.routeInformationParser,
|
||||
routerDelegate: appRouter.routerDelegate,
|
||||
routeInformationProvider: appRouter.routeInformationProvider,
|
||||
);
|
||||
}
|
||||
}
|
49
lib/features/core/abstracts/app_setup.dart
Normal file
49
lib/features/core/abstracts/app_setup.dart
Normal file
|
@ -0,0 +1,49 @@
|
|||
import 'dart:async';
|
||||
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
|
||||
import '/l10n/generated/l10n.dart';
|
||||
import '/locator.dart';
|
||||
|
||||
abstract class AppSetup {
|
||||
// TODO: When locator is properly refactored we should not have to use these stub methods for testing
|
||||
static Future<void> initialise() async {
|
||||
WidgetsFlutterBinding.ensureInitialized();
|
||||
|
||||
await SystemChrome.setPreferredOrientations([
|
||||
DeviceOrientation.portraitUp,
|
||||
DeviceOrientation.portraitDown,
|
||||
]);
|
||||
|
||||
await Locator.setup();
|
||||
}
|
||||
|
||||
static final List<Locale> supportedLocales = kReleaseMode
|
||||
? <Locale>[
|
||||
const Locale.fromSubtags(languageCode: 'en'),
|
||||
]
|
||||
: Strings.delegate.supportedLocales;
|
||||
|
||||
static Locale resolveLocale(List<Locale>? preferredLocales, Iterable<Locale> supportedLocales) {
|
||||
for (final locale in preferredLocales ?? const <Locale>[]) {
|
||||
// Check if the current device locale is supported
|
||||
for (final supportedLocale in supportedLocales) {
|
||||
if (supportedLocale.languageCode == locale.languageCode) {
|
||||
return supportedLocale;
|
||||
}
|
||||
}
|
||||
}
|
||||
return supportedLocales.first;
|
||||
}
|
||||
|
||||
static Future<void> setupStrings() async {
|
||||
await Strings.load(resolveLocale(WidgetsBinding.instance.window.locales, supportedLocales));
|
||||
}
|
||||
|
||||
static void Function(Object error, StackTrace stackTrace) get onUncaughtException => (
|
||||
error,
|
||||
stackTrace,
|
||||
) {};
|
||||
}
|
73
lib/features/core/abstracts/base_view_model.dart
Normal file
73
lib/features/core/abstracts/base_view_model.dart
Normal file
|
@ -0,0 +1,73 @@
|
|||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/widgets.dart';
|
||||
import 'package:mc_gallery/features/core/services/logging_service.dart';
|
||||
import 'package:mc_gallery/l10n/generated/l10n.dart';
|
||||
|
||||
import '../data/enums/view_model_state.dart';
|
||||
|
||||
abstract class BaseViewModel<E extends Object?> extends ChangeNotifier {
|
||||
final ValueNotifier<bool> _isInitialised = ValueNotifier(false);
|
||||
ValueListenable<bool> get isInitialised => _isInitialised;
|
||||
|
||||
final ValueNotifier<bool> _isBusy = ValueNotifier(false);
|
||||
ValueListenable<bool> get isBusy => _isBusy;
|
||||
|
||||
final ValueNotifier<bool> _hasError = ValueNotifier(false);
|
||||
ValueListenable<bool> get hasError => _hasError;
|
||||
final ValueNotifier<ViewModelState> _state = ValueNotifier(ViewModelState.isInitialising);
|
||||
ValueListenable<ViewModelState> get state => _state;
|
||||
|
||||
final LoggingService _loggingService = LoggingService.locate;
|
||||
|
||||
String? _errorMessage;
|
||||
String get errorMessage => _errorMessage ?? strings.somethingWentWrong;
|
||||
|
||||
@mustCallSuper
|
||||
void initialise(DisposableBuildContext disposableBuildContext, bool Function() mounted,
|
||||
[E? arguments]) {
|
||||
_mounted = mounted;
|
||||
_isInitialised.value = true;
|
||||
_state.value = ViewModelState.isInitialised;
|
||||
_loggingService.successfulInit(location: runtimeType.runtimeType.toString());
|
||||
}
|
||||
|
||||
void setBusy(bool isBusy) {
|
||||
_isBusy.value = isBusy;
|
||||
if (isBusy) {
|
||||
_state.value = ViewModelState.isBusy;
|
||||
} else {
|
||||
_state.value = ViewModelState.isInitialised;
|
||||
}
|
||||
}
|
||||
|
||||
void setError(
|
||||
bool hasError, {
|
||||
String? message,
|
||||
}) {
|
||||
_errorMessage = hasError ? message : null;
|
||||
_hasError.value = hasError;
|
||||
if (hasError) {
|
||||
_state.value = ViewModelState.hasError;
|
||||
} else {
|
||||
_state.value = ViewModelState.isInitialised;
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
super.dispose();
|
||||
_loggingService.successfulDispose(location: runtimeType.runtimeType.toString());
|
||||
}
|
||||
|
||||
late final bool Function() _mounted;
|
||||
void ifMounted(VoidCallback voidCallback) {
|
||||
if (_mounted()) {
|
||||
voidCallback();
|
||||
}
|
||||
}
|
||||
|
||||
final Strings strings = Strings.current;
|
||||
|
||||
double width(BuildContext context) => MediaQuery.of(context).size.width;
|
||||
double height(BuildContext context) => MediaQuery.of(context).size.height;
|
||||
}
|
28
lib/features/core/abstracts/router/app_router.dart
Normal file
28
lib/features/core/abstracts/router/app_router.dart
Normal file
|
@ -0,0 +1,28 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
import 'package:mc_gallery/features/home/views/gallery_view.dart';
|
||||
|
||||
import '../../views/error_page_view.dart';
|
||||
import 'routes.dart';
|
||||
|
||||
class McgRouter {
|
||||
static final _mcgRouter = McgRouter();
|
||||
final router = GoRouter(
|
||||
initialLocation: Routes.home.routePath,
|
||||
debugLogDiagnostics: true,
|
||||
errorPageBuilder: (context, state) => MaterialPage<void>(
|
||||
key: state.pageKey,
|
||||
child: ErrorPageView(error: state.error),
|
||||
),
|
||||
// TODO Add Redirect
|
||||
routes: [
|
||||
GoRoute(
|
||||
path: Routes.home.routePath,
|
||||
name: Routes.home.routeName,
|
||||
builder: (context, _) => const GalleryView(),
|
||||
),
|
||||
],
|
||||
);
|
||||
|
||||
static McgRouter get locate => _mcgRouter;
|
||||
}
|
27
lib/features/core/abstracts/router/routes.dart
Normal file
27
lib/features/core/abstracts/router/routes.dart
Normal file
|
@ -0,0 +1,27 @@
|
|||
enum Routes {
|
||||
home(RoutesInfo(
|
||||
routePath: '/gallery',
|
||||
routeName: 'Home',
|
||||
)),
|
||||
imageCarousel(RoutesInfo(
|
||||
routePath: '/image_carousel',
|
||||
routeName: 'Image Carousel',
|
||||
));
|
||||
|
||||
final RoutesInfo _routeInfo;
|
||||
|
||||
String get routePath => _routeInfo.routePath;
|
||||
String get routeName => _routeInfo.routeName;
|
||||
|
||||
const Routes(this._routeInfo);
|
||||
}
|
||||
|
||||
class RoutesInfo {
|
||||
final String routePath;
|
||||
final String routeName;
|
||||
|
||||
const RoutesInfo({
|
||||
required this.routePath,
|
||||
required this.routeName,
|
||||
});
|
||||
}
|
35
lib/features/core/data/constants/const_colors.dart
Normal file
35
lib/features/core/data/constants/const_colors.dart
Normal file
|
@ -0,0 +1,35 @@
|
|||
import 'package:flutter/cupertino.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
abstract class ConstColours {
|
||||
/// Smoke Gray => a neutral grey with neutral warmth/cold
|
||||
static const imageBackgroundColour = Color.fromRGBO(127, 127, 125, 1.0);
|
||||
|
||||
static const white = Colors.white;
|
||||
static const black = Colors.black;
|
||||
}
|
||||
|
||||
abstract class ConstThemes {
|
||||
static ThemeData get materialLightTheme => ThemeData(
|
||||
useMaterial3: true,
|
||||
brightness: Brightness.light,
|
||||
colorSchemeSeed: ConstColours.white,
|
||||
);
|
||||
|
||||
static ThemeData get materialDarkTheme => ThemeData(
|
||||
useMaterial3: true,
|
||||
brightness: Brightness.dark,
|
||||
colorSchemeSeed: ConstColours.white,
|
||||
);
|
||||
|
||||
static CupertinoThemeData get cupertinoLightTheme => const CupertinoThemeData(
|
||||
brightness: Brightness.light,
|
||||
);
|
||||
static CupertinoThemeData get cupertinoDarkTheme => const CupertinoThemeData(
|
||||
brightness: Brightness.dark,
|
||||
);
|
||||
static ThemeData get cupertinoThemeLightHack =>
|
||||
materialLightTheme.copyWith(cupertinoOverrideTheme: cupertinoLightTheme);
|
||||
static ThemeData get cupertinoThemeDarkHack =>
|
||||
materialDarkTheme.copyWith(cupertinoOverrideTheme: cupertinoDarkTheme);
|
||||
}
|
0
lib/features/core/data/constants/const_text.dart
Normal file
0
lib/features/core/data/constants/const_text.dart
Normal file
0
lib/features/core/data/constants/const_values.dart
Normal file
0
lib/features/core/data/constants/const_values.dart
Normal file
6
lib/features/core/data/enums/view_model_state.dart
Normal file
6
lib/features/core/data/enums/view_model_state.dart
Normal file
|
@ -0,0 +1,6 @@
|
|||
enum ViewModelState {
|
||||
isInitialising,
|
||||
isInitialised,
|
||||
isBusy,
|
||||
hasError;
|
||||
}
|
13
lib/features/core/data/extensions/context_extensions.dart
Normal file
13
lib/features/core/data/extensions/context_extensions.dart
Normal file
|
@ -0,0 +1,13 @@
|
|||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/widgets.dart';
|
||||
|
||||
extension DarkMode on BuildContext {
|
||||
/// is dark mode currently enabled?
|
||||
///
|
||||
/// Using the context version in release throws exception.
|
||||
bool get isDarkMode =>
|
||||
WidgetsBinding.instance.platformDispatcher.platformBrightness == Brightness.dark;
|
||||
|
||||
double get width => MediaQuery.of(this).size.width;
|
||||
double get height => MediaQuery.of(this).size.height;
|
||||
}
|
6
lib/features/core/data/extensions/dio_extensions.dart
Normal file
6
lib/features/core/data/extensions/dio_extensions.dart
Normal file
|
@ -0,0 +1,6 @@
|
|||
import 'package:dio/dio.dart';
|
||||
|
||||
extension ResponseExtensions on Response {
|
||||
/// Shorthand for getting response's successful state.
|
||||
bool get isSuccessful => (data as Map)['response'];
|
||||
}
|
3
lib/features/core/data/extensions/list_extensions.dart
Normal file
3
lib/features/core/data/extensions/list_extensions.dart
Normal file
|
@ -0,0 +1,3 @@
|
|||
extension ListExtensions<T> on List<T> {
|
||||
List<T> get deepCopy => [...this];
|
||||
}
|
3
lib/features/core/data/extensions/map_extensions.dart
Normal file
3
lib/features/core/data/extensions/map_extensions.dart
Normal file
|
@ -0,0 +1,3 @@
|
|||
extension MapExtensions<A, B> on Map<A, B> {
|
||||
Map<A, B> get deepCopy => {...this};
|
||||
}
|
37
lib/features/core/data/extensions/stream_extensions.dart
Normal file
37
lib/features/core/data/extensions/stream_extensions.dart
Normal file
|
@ -0,0 +1,37 @@
|
|||
import 'package:flutter/foundation.dart';
|
||||
|
||||
extension StreamExtensions<T> on Stream<T> {
|
||||
ValueListenable<T> toValueNotifier(
|
||||
T initialValue, {
|
||||
bool Function(T previous, T current)? notifyWhen,
|
||||
}) {
|
||||
final notifier = ValueNotifier<T>(initialValue);
|
||||
listen((value) {
|
||||
if (notifyWhen == null || notifyWhen(notifier.value, value)) {
|
||||
notifier.value = value;
|
||||
}
|
||||
});
|
||||
return notifier;
|
||||
}
|
||||
|
||||
ValueListenable<T?> toNullableValueNotifier({
|
||||
bool Function(T? previous, T? current)? notifyWhen,
|
||||
}) {
|
||||
final notifier = ValueNotifier<T?>(null);
|
||||
listen((value) {
|
||||
if (notifyWhen == null || notifyWhen(notifier.value, value)) {
|
||||
notifier.value = value;
|
||||
}
|
||||
});
|
||||
return notifier;
|
||||
}
|
||||
|
||||
Listenable toListenable() {
|
||||
final notifier = ChangeNotifier();
|
||||
listen((_) {
|
||||
// ignore: invalid_use_of_protected_member, invalid_use_of_visible_for_testing_member
|
||||
notifier.notifyListeners();
|
||||
});
|
||||
return notifier;
|
||||
}
|
||||
}
|
0
lib/features/core/services/connection_service.dart
Normal file
0
lib/features/core/services/connection_service.dart
Normal file
66
lib/features/core/services/logging_service.dart
Normal file
66
lib/features/core/services/logging_service.dart
Normal file
|
@ -0,0 +1,66 @@
|
|||
import 'dart:io';
|
||||
|
||||
import 'package:dio/dio.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:talker/talker.dart';
|
||||
import 'package:talker_dio_logger/talker_dio_logger_interceptor.dart';
|
||||
import 'package:talker_dio_logger/talker_dio_logger_settings.dart';
|
||||
|
||||
import '/locator.dart';
|
||||
|
||||
class LoggingService {
|
||||
final Talker _talker = Talker(
|
||||
settings: TalkerSettings(
|
||||
useHistory: false,
|
||||
),
|
||||
logger: TalkerLogger(formater: const ColoredLoggerFormatter()),
|
||||
loggerSettings: TalkerLoggerSettings(
|
||||
enableColors: !Platform.isIOS,
|
||||
),
|
||||
loggerOutput: debugPrint,
|
||||
);
|
||||
|
||||
void Function(dynamic msg, [Object? exception, StackTrace? stackTrace]) get fine => _talker.fine;
|
||||
void Function(dynamic msg, [Object? exception, StackTrace? stackTrace]) get good => _talker.good;
|
||||
|
||||
void Function(dynamic msg, [Object? exception, StackTrace? stackTrace]) get info => _talker.info;
|
||||
void Function(dynamic msg, [Object exception, StackTrace stackTrace]) get warning =>
|
||||
_talker.warning;
|
||||
|
||||
void Function(dynamic msg, [Object? exception, StackTrace? stackTrace]) get error =>
|
||||
_talker.error;
|
||||
void Function(Object exception, [StackTrace stackTrace, dynamic msg]) get handle =>
|
||||
_talker.handle;
|
||||
void Function(Error error, [StackTrace stackTrace, dynamic msg]) get handleError =>
|
||||
_talker.handleError;
|
||||
void Function(Exception exception, [StackTrace? stackTrace, dynamic msg]) get handleException =>
|
||||
_talker.handleException;
|
||||
|
||||
void successfulInit({required String location}) => _talker.good('[$location] I am initialized');
|
||||
void successfulDispose({required String location}) => _talker.good('[$location] I am disposed');
|
||||
|
||||
/// Adds logging to dio calls
|
||||
void addLoggingInterceptor({required Dio dio}) {
|
||||
dio.interceptors.add(
|
||||
TalkerDioLogger(
|
||||
talker: Talker(
|
||||
logger: TalkerLogger(formater: const ColoredLoggerFormatter()),
|
||||
settings: TalkerSettings(
|
||||
useConsoleLogs: true,
|
||||
useHistory: false,
|
||||
),
|
||||
loggerSettings: TalkerLoggerSettings(
|
||||
enableColors: !Platform.isIOS,
|
||||
),
|
||||
),
|
||||
settings: const TalkerDioLoggerSettings(
|
||||
printRequestHeaders: true,
|
||||
printResponseHeaders: true,
|
||||
printResponseMessage: true,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
static LoggingService get locate => Locator.locate();
|
||||
}
|
21
lib/features/core/services/navigation_service.dart
Normal file
21
lib/features/core/services/navigation_service.dart
Normal file
|
@ -0,0 +1,21 @@
|
|||
import 'package:flutter/widgets.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
|
||||
import '../abstracts/router/app_router.dart';
|
||||
import '../abstracts/router/routes.dart';
|
||||
|
||||
class NavigationService {
|
||||
const NavigationService({
|
||||
required McgRouter mcgRouter,
|
||||
}) : _mcgRouter = mcgRouter;
|
||||
|
||||
final McgRouter _mcgRouter;
|
||||
|
||||
void pushImageCarouselView(BuildContext context) =>
|
||||
context.pushNamed(Routes.imageCarousel.routeName);
|
||||
|
||||
void backToGallery(BuildContext context) => context.pop();
|
||||
|
||||
void previous() {}
|
||||
void next() {}
|
||||
}
|
25
lib/features/core/views/error_page_view.dart
Normal file
25
lib/features/core/views/error_page_view.dart
Normal file
|
@ -0,0 +1,25 @@
|
|||
import 'package:flutter/widgets.dart';
|
||||
|
||||
import '../widgets/gap.dart';
|
||||
|
||||
class ErrorPageView extends StatelessWidget {
|
||||
const ErrorPageView({
|
||||
required this.error,
|
||||
super.key,
|
||||
});
|
||||
|
||||
final Exception? error;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Center(
|
||||
child: Column(
|
||||
children: [
|
||||
const Text('Oopsie, there has been an error. Hang tight till we do something about it.'),
|
||||
const Gap(16),
|
||||
Text('what happened: $error'),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
202
lib/features/core/widgets/gap.dart
Normal file
202
lib/features/core/widgets/gap.dart
Normal file
|
@ -0,0 +1,202 @@
|
|||
import 'package:flutter/rendering.dart';
|
||||
import 'package:flutter/widgets.dart';
|
||||
|
||||
class Gap extends LeafRenderObjectWidget {
|
||||
const Gap(this.size, {Key? key}) : super(key: key);
|
||||
|
||||
final double size;
|
||||
|
||||
@override
|
||||
RenderGap createRenderObject(BuildContext context) => RenderGap(size);
|
||||
|
||||
@override
|
||||
void updateRenderObject(BuildContext context, covariant RenderGap renderObject) {
|
||||
renderObject.gap = size;
|
||||
super.updateRenderObject(context, renderObject);
|
||||
}
|
||||
|
||||
static const size4 = Gap(4);
|
||||
static const size8 = Gap(8);
|
||||
static const size16 = Gap(16);
|
||||
static const size24 = Gap(24);
|
||||
static const size32 = Gap(32);
|
||||
static const size64 = Gap(64);
|
||||
}
|
||||
|
||||
class RenderGap extends RenderBox {
|
||||
RenderGap(this._gap) : super() {
|
||||
markNeedsLayout();
|
||||
}
|
||||
|
||||
double _gap;
|
||||
double get gap => _gap;
|
||||
|
||||
set gap(double value) {
|
||||
if (_gap != value) {
|
||||
_gap = value;
|
||||
markNeedsLayout();
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void performLayout() {
|
||||
final parent = this.parent;
|
||||
Size newSize;
|
||||
if (parent is RenderFlex) {
|
||||
switch (parent.direction) {
|
||||
case Axis.vertical:
|
||||
newSize = Size(0, gap);
|
||||
break;
|
||||
case Axis.horizontal:
|
||||
newSize = Size(gap, 0);
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
newSize = Size.square(gap);
|
||||
}
|
||||
size = constraints.constrain(newSize);
|
||||
}
|
||||
}
|
||||
|
||||
class AnimatedGap extends StatefulWidget {
|
||||
const AnimatedGap(
|
||||
this.gap, {
|
||||
Key? key,
|
||||
this.duration = const Duration(milliseconds: 200),
|
||||
this.curve = Curves.easeInOut,
|
||||
}) : super(key: key);
|
||||
|
||||
final Duration duration;
|
||||
final double gap;
|
||||
final Curve curve;
|
||||
|
||||
@override
|
||||
State<AnimatedGap> createState() => _AnimatedGapState();
|
||||
}
|
||||
|
||||
class _AnimatedGapState extends State<AnimatedGap> with SingleTickerProviderStateMixin {
|
||||
late final _controller = AnimationController(
|
||||
vsync: this,
|
||||
value: widget.gap,
|
||||
upperBound: double.infinity,
|
||||
);
|
||||
|
||||
@override
|
||||
void didUpdateWidget(covariant AnimatedGap oldWidget) {
|
||||
if (oldWidget.gap != widget.gap) {
|
||||
_controller.animateTo(
|
||||
widget.gap,
|
||||
curve: widget.curve,
|
||||
duration: widget.duration,
|
||||
);
|
||||
}
|
||||
super.didUpdateWidget(oldWidget);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return ValueListenableBuilder<double>(
|
||||
valueListenable: _controller,
|
||||
builder: (context, gap, _) {
|
||||
return Gap(gap);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class AnimatedSliverGap extends StatefulWidget {
|
||||
const AnimatedSliverGap(
|
||||
this.gap, {
|
||||
Key? key,
|
||||
this.duration = const Duration(milliseconds: 200),
|
||||
this.curve = Curves.easeInOut,
|
||||
}) : super(key: key);
|
||||
|
||||
final Duration duration;
|
||||
final double gap;
|
||||
final Curve curve;
|
||||
|
||||
@override
|
||||
State<AnimatedSliverGap> createState() => _AnimatedSliverGapState();
|
||||
}
|
||||
|
||||
class _AnimatedSliverGapState extends State<AnimatedSliverGap> with SingleTickerProviderStateMixin {
|
||||
late final _controller = AnimationController(
|
||||
vsync: this,
|
||||
value: widget.gap,
|
||||
upperBound: double.infinity,
|
||||
);
|
||||
|
||||
@override
|
||||
void didUpdateWidget(covariant AnimatedSliverGap oldWidget) {
|
||||
if (oldWidget.gap != widget.gap) {
|
||||
_controller.animateTo(
|
||||
widget.gap,
|
||||
curve: widget.curve,
|
||||
duration: widget.duration,
|
||||
);
|
||||
}
|
||||
super.didUpdateWidget(oldWidget);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return ValueListenableBuilder<double>(
|
||||
valueListenable: _controller,
|
||||
builder: (context, gap, _) {
|
||||
return SliverGap(gap);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class SliverGap extends LeafRenderObjectWidget {
|
||||
const SliverGap(this.gap, {Key? key}) : super(key: key);
|
||||
|
||||
final double gap;
|
||||
|
||||
static const size4 = SliverGap(4);
|
||||
static const size8 = SliverGap(8);
|
||||
static const size16 = SliverGap(16);
|
||||
static const size24 = SliverGap(24);
|
||||
static const size32 = SliverGap(32);
|
||||
static const size64 = SliverGap(64);
|
||||
|
||||
@override
|
||||
RenderSliverGap createRenderObject(BuildContext context) => RenderSliverGap(gap);
|
||||
|
||||
@override
|
||||
void updateRenderObject(BuildContext context, covariant RenderSliverGap renderObject) {
|
||||
renderObject.gap = gap;
|
||||
super.updateRenderObject(context, renderObject);
|
||||
}
|
||||
}
|
||||
|
||||
class RenderSliverGap extends RenderSliver {
|
||||
RenderSliverGap(this._gap) : super() {
|
||||
markNeedsLayout();
|
||||
}
|
||||
|
||||
double _gap;
|
||||
double get gap => _gap;
|
||||
|
||||
set gap(double value) {
|
||||
if (_gap != value) {
|
||||
_gap = value;
|
||||
markNeedsLayout();
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void performLayout() {
|
||||
final cacheExtent = calculateCacheOffset(constraints, from: 0, to: gap);
|
||||
final paintExtent = calculatePaintOffset(constraints, from: 0, to: gap);
|
||||
geometry = SliverGeometry(
|
||||
paintExtent: paintExtent,
|
||||
scrollExtent: gap,
|
||||
visible: false,
|
||||
cacheExtent: cacheExtent,
|
||||
maxPaintExtent: gap,
|
||||
);
|
||||
}
|
||||
}
|
10
lib/features/home/views/gallery_view.dart
Normal file
10
lib/features/home/views/gallery_view.dart
Normal file
|
@ -0,0 +1,10 @@
|
|||
import 'package:flutter/widgets.dart';
|
||||
|
||||
class GalleryView extends StatelessWidget {
|
||||
const GalleryView({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return const Placeholder();
|
||||
}
|
||||
}
|
0
lib/features/home/views/image_carousel_view.dart
Normal file
0
lib/features/home/views/image_carousel_view.dart
Normal file
63
lib/l10n/generated/intl/messages_all.dart
Normal file
63
lib/l10n/generated/intl/messages_all.dart
Normal file
|
@ -0,0 +1,63 @@
|
|||
// DO NOT EDIT. This is code generated via package:intl/generate_localized.dart
|
||||
// This is a library that looks up messages for specific locales by
|
||||
// delegating to the appropriate library.
|
||||
|
||||
// Ignore issues from commonly used lints in this file.
|
||||
// ignore_for_file:implementation_imports, file_names, unnecessary_new
|
||||
// ignore_for_file:unnecessary_brace_in_string_interps, directives_ordering
|
||||
// ignore_for_file:argument_type_not_assignable, invalid_assignment
|
||||
// ignore_for_file:prefer_single_quotes, prefer_generic_function_type_aliases
|
||||
// ignore_for_file:comment_references
|
||||
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:intl/intl.dart';
|
||||
import 'package:intl/message_lookup_by_library.dart';
|
||||
import 'package:intl/src/intl_helpers.dart';
|
||||
|
||||
import 'messages_en.dart' as messages_en;
|
||||
|
||||
typedef Future<dynamic> LibraryLoader();
|
||||
Map<String, LibraryLoader> _deferredLibraries = {
|
||||
'en': () => new SynchronousFuture(null),
|
||||
};
|
||||
|
||||
MessageLookupByLibrary? _findExact(String localeName) {
|
||||
switch (localeName) {
|
||||
case 'en':
|
||||
return messages_en.messages;
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/// User programs should call this before using [localeName] for messages.
|
||||
Future<bool> initializeMessages(String localeName) {
|
||||
var availableLocale = Intl.verifiedLocale(
|
||||
localeName, (locale) => _deferredLibraries[locale] != null,
|
||||
onFailure: (_) => null);
|
||||
if (availableLocale == null) {
|
||||
return new SynchronousFuture(false);
|
||||
}
|
||||
var lib = _deferredLibraries[availableLocale];
|
||||
lib == null ? new SynchronousFuture(false) : lib();
|
||||
initializeInternalMessageLookup(() => new CompositeMessageLookup());
|
||||
messageLookup.addLocale(availableLocale, _findGeneratedMessagesFor);
|
||||
return new SynchronousFuture(true);
|
||||
}
|
||||
|
||||
bool _messagesExistFor(String locale) {
|
||||
try {
|
||||
return _findExact(locale) != null;
|
||||
} catch (e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
MessageLookupByLibrary? _findGeneratedMessagesFor(String locale) {
|
||||
var actualLocale =
|
||||
Intl.verifiedLocale(locale, _messagesExistFor, onFailure: (_) => null);
|
||||
if (actualLocale == null) return null;
|
||||
return _findExact(actualLocale);
|
||||
}
|
28
lib/l10n/generated/intl/messages_en.dart
Normal file
28
lib/l10n/generated/intl/messages_en.dart
Normal file
|
@ -0,0 +1,28 @@
|
|||
// DO NOT EDIT. This is code generated via package:intl/generate_localized.dart
|
||||
// This is a library that provides messages for a en locale. All the
|
||||
// messages from the main program should be duplicated here with the same
|
||||
// function name.
|
||||
|
||||
// Ignore issues from commonly used lints in this file.
|
||||
// ignore_for_file:unnecessary_brace_in_string_interps, unnecessary_new
|
||||
// ignore_for_file:prefer_single_quotes,comment_references, directives_ordering
|
||||
// ignore_for_file:annotate_overrides,prefer_generic_function_type_aliases
|
||||
// ignore_for_file:unused_import, file_names, avoid_escaping_inner_quotes
|
||||
// ignore_for_file:unnecessary_string_interpolations, unnecessary_string_escapes
|
||||
|
||||
import 'package:intl/intl.dart';
|
||||
import 'package:intl/message_lookup_by_library.dart';
|
||||
|
||||
final messages = new MessageLookup();
|
||||
|
||||
typedef String MessageIfAbsent(String messageStr, List<dynamic> args);
|
||||
|
||||
class MessageLookup extends MessageLookupByLibrary {
|
||||
String get localeName => 'en';
|
||||
|
||||
final messages = _notInlinedMessages(_notInlinedMessages);
|
||||
static Map<String, Function> _notInlinedMessages(_) => <String, Function>{
|
||||
"somethingWentWrong":
|
||||
MessageLookupByLibrary.simpleMessage("Something went wrong")
|
||||
};
|
||||
}
|
88
lib/l10n/generated/l10n.dart
Normal file
88
lib/l10n/generated/l10n.dart
Normal file
|
@ -0,0 +1,88 @@
|
|||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:intl/intl.dart';
|
||||
import 'intl/messages_all.dart';
|
||||
|
||||
// **************************************************************************
|
||||
// Generator: Flutter Intl IDE plugin
|
||||
// Made by Localizely
|
||||
// **************************************************************************
|
||||
|
||||
// ignore_for_file: non_constant_identifier_names, lines_longer_than_80_chars
|
||||
// ignore_for_file: join_return_with_assignment, prefer_final_in_for_each
|
||||
// ignore_for_file: avoid_redundant_argument_values, avoid_escaping_inner_quotes
|
||||
|
||||
class Strings {
|
||||
Strings();
|
||||
|
||||
static Strings? _current;
|
||||
|
||||
static Strings get current {
|
||||
assert(_current != null,
|
||||
'No instance of Strings was loaded. Try to initialize the Strings delegate before accessing Strings.current.');
|
||||
return _current!;
|
||||
}
|
||||
|
||||
static const AppLocalizationDelegate delegate = AppLocalizationDelegate();
|
||||
|
||||
static Future<Strings> load(Locale locale) {
|
||||
final name = (locale.countryCode?.isEmpty ?? false)
|
||||
? locale.languageCode
|
||||
: locale.toString();
|
||||
final localeName = Intl.canonicalizedLocale(name);
|
||||
return initializeMessages(localeName).then((_) {
|
||||
Intl.defaultLocale = localeName;
|
||||
final instance = Strings();
|
||||
Strings._current = instance;
|
||||
|
||||
return instance;
|
||||
});
|
||||
}
|
||||
|
||||
static Strings of(BuildContext context) {
|
||||
final instance = Strings.maybeOf(context);
|
||||
assert(instance != null,
|
||||
'No instance of Strings present in the widget tree. Did you add Strings.delegate in localizationsDelegates?');
|
||||
return instance!;
|
||||
}
|
||||
|
||||
static Strings? maybeOf(BuildContext context) {
|
||||
return Localizations.of<Strings>(context, Strings);
|
||||
}
|
||||
|
||||
/// `Something went wrong`
|
||||
String get somethingWentWrong {
|
||||
return Intl.message(
|
||||
'Something went wrong',
|
||||
name: 'somethingWentWrong',
|
||||
desc: '',
|
||||
args: [],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class AppLocalizationDelegate extends LocalizationsDelegate<Strings> {
|
||||
const AppLocalizationDelegate();
|
||||
|
||||
List<Locale> get supportedLocales {
|
||||
return const <Locale>[
|
||||
Locale.fromSubtags(languageCode: 'en'),
|
||||
];
|
||||
}
|
||||
|
||||
@override
|
||||
bool isSupported(Locale locale) => _isSupported(locale);
|
||||
@override
|
||||
Future<Strings> load(Locale locale) => Strings.load(locale);
|
||||
@override
|
||||
bool shouldReload(AppLocalizationDelegate old) => false;
|
||||
|
||||
bool _isSupported(Locale locale) {
|
||||
for (var supportedLocale in supportedLocales) {
|
||||
if (supportedLocale.languageCode == locale.languageCode) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
4
lib/l10n/intl_en.arb
Normal file
4
lib/l10n/intl_en.arb
Normal file
|
@ -0,0 +1,4 @@
|
|||
{
|
||||
"@@locale": "en",
|
||||
"somethingWentWrong": "Something went wrong"
|
||||
}
|
48
lib/locator.dart
Normal file
48
lib/locator.dart
Normal file
|
@ -0,0 +1,48 @@
|
|||
import 'package:get_it/get_it.dart';
|
||||
import 'package:mc_gallery/features/core/abstracts/router/app_router.dart';
|
||||
import 'package:mc_gallery/features/core/services/logging_service.dart';
|
||||
import 'package:mc_gallery/features/core/services/navigation_service.dart';
|
||||
|
||||
GetIt get locate => Locator.instance();
|
||||
|
||||
class Locator {
|
||||
static GetIt instance() => GetIt.instance;
|
||||
static T locate<T extends Object>() => instance().get<T>();
|
||||
|
||||
static Future<void> setup() async {
|
||||
final locator = instance();
|
||||
|
||||
_registerAPIs();
|
||||
_registerViewModels();
|
||||
_registerLazySingletons();
|
||||
_registerFactories();
|
||||
|
||||
await _registerServices(locator);
|
||||
|
||||
await _registerRepos(locator);
|
||||
_registerSingletons();
|
||||
}
|
||||
|
||||
static void _registerAPIs() {}
|
||||
|
||||
static void _registerViewModels() {}
|
||||
|
||||
static void _registerLazySingletons() {}
|
||||
|
||||
static void _registerFactories() {}
|
||||
|
||||
static _registerServices(GetIt it) {
|
||||
it.registerLazySingleton(
|
||||
() => NavigationService(
|
||||
mcgRouter: McgRouter.locate,
|
||||
),
|
||||
);
|
||||
it.registerFactory(
|
||||
() => LoggingService(),
|
||||
);
|
||||
}
|
||||
|
||||
static _registerRepos(GetIt locator) {}
|
||||
|
||||
static void _registerSingletons() {}
|
||||
}
|
16
lib/main.dart
Normal file
16
lib/main.dart
Normal file
|
@ -0,0 +1,16 @@
|
|||
import 'dart:async';
|
||||
|
||||
import 'package:flutter/widgets.dart';
|
||||
|
||||
import 'app.dart';
|
||||
import 'features/core/abstracts/app_setup.dart';
|
||||
|
||||
Future<void> main() async {
|
||||
await runZonedGuarded(
|
||||
() async {
|
||||
await AppSetup.initialise();
|
||||
runApp(const McgApp());
|
||||
},
|
||||
AppSetup.onUncaughtException,
|
||||
);
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue