init
This commit is contained in:
commit
8e54cfffc5
91 changed files with 2686 additions and 0 deletions
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,
|
||||
);
|
||||
}
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue