Jonny Borges
Committed by GitHub

Merge pull request #2048 from parmarravi/master

Added Circular reveal Transition
No preview for this file type
... ... @@ -328,7 +328,7 @@ Text(controller.textFromApi);
### 종속성 관리에 대한 자세한 내용
**종속성 관리에 대한 더 제사한 사항은 [여기](./documentation/kr_KO/dependency_management.md)에 있습니다.**
**종속성 관리에 대한 더 자세한 사항은 [여기](./documentation/kr_KO/dependency_management.md)에 있습니다.**
# 기능들
... ... @@ -1092,6 +1092,73 @@ class SettingsService extends GetxService {
따라서 앱 실행중 절대로 유지되어야 하는 클래스 인스턴스가 필요하면
`GetxService`를 사용하세요.
### 테스트
당신은 당신의 컨트롤러들을 생성주기를 포함하여 다른 어떤 클래스처럼 테스트할 수 있습니다 :
```dart
class Controller extends GetxController {
@override
void onInit() {
super.onInit();
//name2로 값 변경
name.value = 'name2';
}
@override
void onClose() {
name.value = '';
super.onClose();
}
final name = 'name1'.obs;
void changeName() => name.value = 'name3';
}
void main() {
test('''
Test the state of the reactive variable "name" across all of its lifecycles''',
() {
/// 당신은 생성주기를 제외하고 컨트롤러를 테스트할 수 있습니다,
/// 그러나 당신이 사용하지 않는다면 추천되지 않습니다
/// GetX 종속성 주입
final controller = Controller();
expect(controller.name.value, 'name1');
/// 당신이 그것을 사용한다면, 당신은 모든 것을 테스트할 수 있습니다,
/// 각각의 생성주기 이후 어플리케이션의 상태를 포함하여.
Get.put(controller); // onInit was called
expect(controller.name.value, 'name2');
/// 당신의 함수를 테스트하세요
controller.changeName();
expect(controller.name.value, 'name3');
/// onClose 호출됨
Get.delete<Controller>();
expect(controller.name.value, '');
});
}
```
#### 팁들
##### Mockito 또는 mocktail
당신이 당신의 GetxController/GetxService를 모킹하려고 한다면, 당신은 GetxController를 extend 하고, Mock과 mixin 하라, 그렇게 되면
```dart
class NotificationServiceMock extends GetxService with Mock implements NotificationService {}
```
##### Get.reset() 사용하기
당신이 위젯 또는 테스트 그룹을 테스트하고 있다면, 당신의 테스트의 마지막 또는 해제 때 당신의 이전 테스트에서 모든 설정을 리셋하기 위해 Get.rest을 사용하십시오
##### Get.testMode
당신이 당신의 컨트롤러에서 당신의 네비게이션을 사용하고 있다면, 당신의 메인의 시작에 `Get.testMode = true` 를 사용하십시오.
# 2.0의 주요 변경점
1- Rx 타입들:
... ...
... ... @@ -115,6 +115,7 @@ class GetConnect extends GetConnectInterface {
Decoder? defaultDecoder;
Duration timeout;
List<TrustedCertificate>? trustedCertificates;
String Function(Uri url)? findProxy;
GetHttpClient? _httpClient;
List<GetSocket>? _sockets;
bool withCredentials;
... ... @@ -134,6 +135,7 @@ class GetConnect extends GetConnectInterface {
baseUrl: baseUrl,
trustedCertificates: trustedCertificates,
withCredentials: withCredentials,
findProxy: findProxy
);
@override
... ...
... ... @@ -39,6 +39,8 @@ class GetHttpClient {
final GetModifier _modifier;
String Function(Uri url)? findProxy;
GetHttpClient({
this.userAgent = 'getx-client',
this.timeout = const Duration(seconds: 8),
... ... @@ -50,10 +52,12 @@ class GetHttpClient {
this.baseUrl,
List<TrustedCertificate>? trustedCertificates,
bool withCredentials = false,
String Function(Uri url)? findProxy,
}) : _httpClient = HttpRequestImpl(
allowAutoSignedCert: allowAutoSignedCert,
trustedCertificates: trustedCertificates,
withCredentials: withCredentials,
findProxy: findProxy,
),
_modifier = GetModifier();
... ... @@ -195,7 +199,6 @@ class GetHttpClient {
int requestNumber = 1,
Map<String, String>? headers,
}) async {
try {
var request = await handler();
headers?.forEach((key, value) {
... ... @@ -206,6 +209,7 @@ class GetHttpClient {
final newRequest = await _modifier.modifyRequest<T>(request);
_httpClient.timeout = timeout;
try {
var response = await _httpClient.send<T>(newRequest);
final newResponse =
... ... @@ -242,7 +246,7 @@ class GetHttpClient {
throw GetHttpException(err.toString());
} else {
return Response<T>(
request: null,
request: newRequest,
headers: null,
statusCode: null,
body: null,
... ... @@ -268,6 +272,8 @@ class GetHttpClient {
headers: headers,
decoder: decoder ?? (defaultDecoder as Decoder<T>?),
contentLength: 0,
followRedirects: followRedirects,
maxRedirects: maxRedirects,
));
}
... ...
... ... @@ -17,6 +17,7 @@ class HttpRequestImpl extends HttpRequestBase {
bool allowAutoSignedCert = true,
List<TrustedCertificate>? trustedCertificates,
bool withCredentials = false,
String Function(Uri url)? findProxy,
}) {
_httpClient = io.HttpClient();
if (trustedCertificates != null) {
... ... @@ -29,6 +30,7 @@ class HttpRequestImpl extends HttpRequestBase {
_httpClient = io.HttpClient(context: _securityContext);
_httpClient!.badCertificateCallback = (_, __, ___) => allowAutoSignedCert;
_httpClient!.findProxy = findProxy;
}
@override
... ...
... ... @@ -8,6 +8,7 @@ class HttpRequestImpl extends HttpRequestBase {
bool allowAutoSignedCert = true,
List<TrustedCertificate>? trustedCertificates,
bool withCredentials = false,
String Function(Uri url)? findProxy,
});
@override
void close() {}
... ...
... ... @@ -239,7 +239,7 @@ class GetInstance {
final newKey = key ?? _getKey(S, tag);
if (_singl.containsKey(newKey)) {
final dep = _singl[newKey];
if (dep != null) {
if (dep != null && !dep.permanent) {
dep.isDirty = true;
}
}
... ...
import 'package:flutter/cupertino.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import '../../../get_core/get_core.dart';
import '../../../get_instance/get_instance.dart';
import '../../../get_state_manager/get_state_manager.dart';
... ... @@ -9,6 +10,59 @@ import '../../get_navigation.dart';
import 'root_controller.dart';
class GetCupertinoApp extends StatelessWidget {
final GlobalKey<NavigatorState>? navigatorKey;
final Widget? home;
final Map<String, WidgetBuilder>? routes;
final String? initialRoute;
final RouteFactory? onGenerateRoute;
final InitialRouteListFactory? onGenerateInitialRoutes;
final RouteFactory? onUnknownRoute;
final List<NavigatorObserver>? navigatorObservers;
final TransitionBuilder? builder;
final String title;
final GenerateAppTitle? onGenerateTitle;
final CustomTransition? customTransition;
final Color? color;
final Map<String, Map<String, String>>? translationsKeys;
final Translations? translations;
final TextDirection? textDirection;
final Locale? locale;
final Locale? fallbackLocale;
final Iterable<LocalizationsDelegate<dynamic>>? localizationsDelegates;
final LocaleListResolutionCallback? localeListResolutionCallback;
final LocaleResolutionCallback? localeResolutionCallback;
final Iterable<Locale> supportedLocales;
final bool showPerformanceOverlay;
final bool checkerboardRasterCacheImages;
final bool checkerboardOffscreenLayers;
final bool showSemanticsDebugger;
final bool debugShowCheckedModeBanner;
final Map<LogicalKeySet, Intent>? shortcuts;
final ThemeData? highContrastTheme;
final ThemeData? highContrastDarkTheme;
final Map<Type, Action<Intent>>? actions;
final Function(Routing?)? routingCallback;
final Transition? defaultTransition;
final bool? opaqueRoute;
final VoidCallback? onInit;
final VoidCallback? onReady;
final VoidCallback? onDispose;
final bool? enableLog;
final LogWriterCallback? logWriterCallback;
final bool? popGesture;
final SmartManagement smartManagement;
final Bindings? initialBinding;
final Duration? transitionDuration;
final bool? defaultGlobalState;
final List<GetPage>? getPages;
final GetPage? unknownRoute;
final RouteInformationProvider? routeInformationProvider;
final RouteInformationParser<Object>? routeInformationParser;
final RouterDelegate<Object>? routerDelegate;
final BackButtonDispatcher? backButtonDispatcher;
final CupertinoThemeData? theme;
final bool useInheritedMediaQuery;
const GetCupertinoApp({
Key? key,
this.theme,
... ... @@ -46,6 +100,7 @@ class GetCupertinoApp extends StatelessWidget {
this.shortcuts,
this.smartManagement = SmartManagement.full,
this.initialBinding,
this.useInheritedMediaQuery = false,
this.unknownRoute,
this.routingCallback,
this.defaultTransition,
... ... @@ -66,58 +121,6 @@ class GetCupertinoApp extends StatelessWidget {
backButtonDispatcher = null,
super(key: key);
final GlobalKey<NavigatorState>? navigatorKey;
final Widget? home;
final Map<String, WidgetBuilder>? routes;
final String? initialRoute;
final RouteFactory? onGenerateRoute;
final InitialRouteListFactory? onGenerateInitialRoutes;
final RouteFactory? onUnknownRoute;
final List<NavigatorObserver>? navigatorObservers;
final TransitionBuilder? builder;
final String title;
final GenerateAppTitle? onGenerateTitle;
final CustomTransition? customTransition;
final Color? color;
final Map<String, Map<String, String>>? translationsKeys;
final Translations? translations;
final TextDirection? textDirection;
final Locale? locale;
final Locale? fallbackLocale;
final Iterable<LocalizationsDelegate<dynamic>>? localizationsDelegates;
final LocaleListResolutionCallback? localeListResolutionCallback;
final LocaleResolutionCallback? localeResolutionCallback;
final Iterable<Locale> supportedLocales;
final bool showPerformanceOverlay;
final bool checkerboardRasterCacheImages;
final bool checkerboardOffscreenLayers;
final bool showSemanticsDebugger;
final bool debugShowCheckedModeBanner;
final Map<LogicalKeySet, Intent>? shortcuts;
final ThemeData? highContrastTheme;
final ThemeData? highContrastDarkTheme;
final Map<Type, Action<Intent>>? actions;
final Function(Routing?)? routingCallback;
final Transition? defaultTransition;
final bool? opaqueRoute;
final VoidCallback? onInit;
final VoidCallback? onReady;
final VoidCallback? onDispose;
final bool? enableLog;
final LogWriterCallback? logWriterCallback;
final bool? popGesture;
final SmartManagement smartManagement;
final Bindings? initialBinding;
final Duration? transitionDuration;
final bool? defaultGlobalState;
final List<GetPage>? getPages;
final GetPage? unknownRoute;
final RouteInformationProvider? routeInformationProvider;
final RouteInformationParser<Object>? routeInformationParser;
final RouterDelegate<Object>? routerDelegate;
final BackButtonDispatcher? backButtonDispatcher;
final CupertinoThemeData? theme;
GetCupertinoApp.router({
Key? key,
this.theme,
... ... @@ -128,6 +131,7 @@ class GetCupertinoApp extends StatelessWidget {
this.builder,
this.title = '',
this.onGenerateTitle,
this.useInheritedMediaQuery = false,
this.color,
this.highContrastTheme,
this.highContrastDarkTheme,
... ... @@ -183,31 +187,6 @@ class GetCupertinoApp extends StatelessWidget {
Get.routeInformationParser = routeInformationParser;
}
Route<dynamic> generator(RouteSettings settings) {
return PageRedirect(settings: settings, unknownRoute: unknownRoute).page();
}
List<Route<dynamic>> initialRoutesGenerate(String name) {
return [
PageRedirect(
settings: RouteSettings(name: name),
unknownRoute: unknownRoute,
).page()
];
}
Widget defaultBuilder(BuildContext context, Widget? child) {
return Directionality(
textDirection: textDirection ??
(rtlLanguages.contains(Get.locale?.languageCode)
? TextDirection.rtl
: TextDirection.ltr),
child: builder == null
? (child ?? Material())
: builder!(context, child ?? Material()),
);
}
@override
Widget build(BuildContext context) => GetBuilder<GetMaterialController>(
init: Get.rootController,
... ... @@ -271,6 +250,7 @@ class GetCupertinoApp extends StatelessWidget {
showSemanticsDebugger: showSemanticsDebugger,
debugShowCheckedModeBanner: debugShowCheckedModeBanner,
shortcuts: shortcuts,
useInheritedMediaQuery: useInheritedMediaQuery,
)
: CupertinoApp(
key: _.unikey,
... ... @@ -310,7 +290,33 @@ class GetCupertinoApp extends StatelessWidget {
showSemanticsDebugger: showSemanticsDebugger,
debugShowCheckedModeBanner: debugShowCheckedModeBanner,
shortcuts: shortcuts,
useInheritedMediaQuery: useInheritedMediaQuery,
// actions: actions,
),
);
Widget defaultBuilder(BuildContext context, Widget? child) {
return Directionality(
textDirection: textDirection ??
(rtlLanguages.contains(Get.locale?.languageCode)
? TextDirection.rtl
: TextDirection.ltr),
child: builder == null
? (child ?? Material())
: builder!(context, child ?? Material()),
);
}
Route<dynamic> generator(RouteSettings settings) {
return PageRedirect(settings: settings, unknownRoute: unknownRoute).page();
}
List<Route<dynamic>> initialRoutesGenerate(String name) {
return [
PageRedirect(
settings: RouteSettings(name: name),
unknownRoute: unknownRoute,
).page()
];
}
}
... ...
... ... @@ -67,6 +67,7 @@ class GetMaterialApp extends StatelessWidget {
final RouteInformationParser<Object>? routeInformationParser;
final RouterDelegate<Object>? routerDelegate;
final BackButtonDispatcher? backButtonDispatcher;
final bool useInheritedMediaQuery;
const GetMaterialApp({
Key? key,
this.navigatorKey,
... ... @@ -78,6 +79,7 @@ class GetMaterialApp extends StatelessWidget {
this.onGenerateRoute,
this.onGenerateInitialRoutes,
this.onUnknownRoute,
this.useInheritedMediaQuery = false,
List<NavigatorObserver> this.navigatorObservers =
const <NavigatorObserver>[],
this.builder,
... ... @@ -142,6 +144,7 @@ class GetMaterialApp extends StatelessWidget {
this.color,
this.theme,
this.darkTheme,
this.useInheritedMediaQuery = false,
this.highContrastTheme,
this.highContrastDarkTheme,
this.themeMode = ThemeMode.system,
... ... @@ -271,6 +274,7 @@ class GetMaterialApp extends StatelessWidget {
debugShowCheckedModeBanner: debugShowCheckedModeBanner,
shortcuts: shortcuts,
scrollBehavior: scrollBehavior,
useInheritedMediaQuery: useInheritedMediaQuery,
)
: MaterialApp(
key: _.unikey,
... ... @@ -317,6 +321,7 @@ class GetMaterialApp extends StatelessWidget {
debugShowCheckedModeBanner: debugShowCheckedModeBanner,
shortcuts: shortcuts,
scrollBehavior: scrollBehavior,
useInheritedMediaQuery: useInheritedMediaQuery,
// actions: actions,
),
);
... ...
import 'dart:math' show sqrt, max;
import 'dart:ui' show lerpDouble;
import 'package:flutter/material.dart';
class CircularRevealClipper extends CustomClipper<Path> {
final double fraction;
final Alignment? centerAlignment;
final Offset? centerOffset;
final double? minRadius;
final double? maxRadius;
CircularRevealClipper({
required this.fraction,
this.centerAlignment,
this.centerOffset,
this.minRadius,
this.maxRadius,
});
@override
Path getClip(Size size) {
final Offset center = this.centerAlignment?.alongSize(size) ??
this.centerOffset ??
Offset(size.width / 2, size.height / 2);
final minRadius = this.minRadius ?? 0;
final maxRadius = this.maxRadius ?? calcMaxRadius(size, center);
return Path()
..addOval(
Rect.fromCircle(
center: center,
radius: lerpDouble(minRadius, maxRadius, fraction)!,
),
);
}
@override
bool shouldReclip(CustomClipper<Path> oldClipper) => true;
static double calcMaxRadius(Size size, Offset center) {
final w = max(center.dx, size.width - center.dx);
final h = max(center.dy, size.height - center.dy);
return sqrt(w * w + h * h);
}
}
... ...
... ... @@ -184,3 +184,26 @@ class SizeTransitions {
);
}
}
class CircularRevealTransition {
Widget buildTransitions(
BuildContext context,
Curve? curve,
Alignment? alignment,
Animation<double> animation,
Animation<double> secondaryAnimation,
Widget child) {
return ClipPath(
clipper: CircularRevealClipper(
fraction: animation.value,
centerAlignment: Alignment.center,
centerOffset: Offset.zero,
minRadius: 0,
maxRadius: 800,
),
child: child,
);
}
}
... ...
... ... @@ -619,6 +619,22 @@ Cannot read the previousTitle for a route that has not yet been installed''',
onStartPopGesture: () => _startPopGesture<T>(route),
child: child)
: child);
case Transition.ciruclarReveal:
return CircularRevealTransition().buildTransitions(
context,
route.curve,
route.alignment,
animation,
secondaryAnimation,
route.popGesture ?? Get.defaultPopGesture
? CupertinoBackGestureDetector<T>(
gestureWidth: route.gestureWidth?.call(context) ??
_kBackGestureWidth,
enabledCallback: () => _isPopGestureEnabled<T>(route),
onStartPopGesture: () => _startPopGesture<T>(route),
child: child)
: child);
default:
if (Get.customTransition != null) {
... ...
... ... @@ -67,6 +67,7 @@ mixin RxObjectMixin<T> on NotifyManager<T> {
}
bool firstRebuild = true;
bool sentToStream = false;
/// Same as `toString()` but using a getter.
String get string => value.toString();
... ... @@ -96,10 +97,11 @@ mixin RxObjectMixin<T> on NotifyManager<T> {
/// Widget, only if it's different from the previous value.
set value(T val) {
if (subject.isClosed) return;
sentToStream = false;
if (_value == val && !firstRebuild) return;
firstRebuild = false;
_value = val;
sentToStream = true;
subject.add(_value);
}
... ... @@ -254,7 +256,7 @@ abstract class _RxImpl<T> extends RxNotifier<T> with RxObjectMixin<T> {
value = v;
// If it's not the first rebuild, the listeners have been called already
// So we won't call them again.
if (!firstRebuild) {
if (!firstRebuild && !sentToStream) {
subject.add(v);
}
}
... ...
... ... @@ -36,13 +36,13 @@ mixin GetSingleTickerProviderStateMixin on GetxController
if (_ticker == null) return true;
throw FlutterError.fromParts(<DiagnosticsNode>[
ErrorSummary(
'$runtimeType is a SingleTickerProviderStateMixin but multiple tickers were created.'),
'$runtimeType is a GetSingleTickerProviderStateMixin but multiple tickers were created.'),
ErrorDescription(
'A SingleTickerProviderStateMixin can only be used as a TickerProvider once.'),
'A GetSingleTickerProviderStateMixin can only be used as a TickerProvider once.'),
ErrorHint(
'If a State is used for multiple AnimationController objects, or if it is passed to other '
'objects and those objects might use it more than one time in total, then instead of '
'mixing in a SingleTickerProviderStateMixin, use a regular TickerProviderStateMixin.',
'mixing in a GetSingleTickerProviderStateMixin, use a regular GetTickerProviderStateMixin.',
),
]);
}());
... ... @@ -66,7 +66,7 @@ mixin GetSingleTickerProviderStateMixin on GetxController
throw FlutterError.fromParts(<DiagnosticsNode>[
ErrorSummary('$this was disposed with an active Ticker.'),
ErrorDescription(
'$runtimeType created a Ticker via its SingleTickerProviderStateMixin, but at the time '
'$runtimeType created a Ticker via its GetSingleTickerProviderStateMixin, but at the time '
'dispose() was called on the mixin, that Ticker was still active. The Ticker must '
'be disposed before calling super.dispose().',
),
... ... @@ -82,6 +82,99 @@ mixin GetSingleTickerProviderStateMixin on GetxController
}
}
/// Used like `TickerProviderMixin` but only with Get Controllers.
/// Simplifies multiple AnimationController creation inside GetxController.
///
/// Example:
///```
///class SplashController extends GetxController with
/// GetTickerProviderStateMixin {
/// AnimationController first_controller;
/// AnimationController second_controller;
///
/// @override
/// void onInit() {
/// final duration = const Duration(seconds: 2);
/// first_controller =
/// AnimationController.unbounded(duration: duration, vsync: this);
/// second_controller =
/// AnimationController.unbounded(duration: duration, vsync: this);
/// first_controller.repeat();
/// first_controller.addListener(() =>
/// print("Animation Controller value: ${first_controller.value}"));
/// second_controller.addListener(() =>
/// print("Animation Controller value: ${second_controller.value}"));
/// }
/// ...
/// ```
mixin GetTickerProviderStateMixin on GetxController implements TickerProvider {
Set<Ticker>? _tickers;
@override
Ticker createTicker(TickerCallback onTick) {
_tickers ??= <_WidgetTicker>{};
final result = _WidgetTicker(onTick, this, debugLabel: kDebugMode ? 'created by ${describeIdentity(this)}' : null);
_tickers!.add(result);
return result;
}
void _removeTicker(_WidgetTicker ticker) {
assert(_tickers != null);
assert(_tickers!.contains(ticker));
_tickers!.remove(ticker);
}
void didChangeDependencies(BuildContext context) {
final muted = !TickerMode.of(context);
if (_tickers != null) {
for (final ticker in _tickers!) {
ticker.muted = muted;
}
}
}
@override
void onClose() {
assert(() {
if (_tickers != null) {
for (final ticker in _tickers!) {
if (ticker.isActive) {
throw FlutterError.fromParts(<DiagnosticsNode>[
ErrorSummary('$this was disposed with an active Ticker.'),
ErrorDescription(
'$runtimeType created a Ticker via its GetTickerProviderStateMixin, but at the time '
'dispose() was called on the mixin, that Ticker was still active. All Tickers must '
'be disposed before calling super.dispose().',
),
ErrorHint(
'Tickers used by AnimationControllers '
'should be disposed by calling dispose() on the AnimationController itself. '
'Otherwise, the ticker will leak.',
),
ticker.describeForError('The offending ticker was'),
]);
}
}
}
return true;
}());
super.onClose();
}
}
class _WidgetTicker extends Ticker {
_WidgetTicker(TickerCallback onTick, this._creator, { String? debugLabel }) : super(onTick, debugLabel: debugLabel);
final GetTickerProviderStateMixin _creator;
@override
void dispose() {
_creator._removeTicker(this);
super.dispose();
}
}
@Deprecated('use GetSingleTickerProviderStateMixin')
/// Used like `SingleTickerProviderMixin` but only with Get Controllers.
... ...
... ... @@ -69,13 +69,13 @@ extension Trans on String {
final translationsWithNoCountry = Get.translations
.map((key, value) => MapEntry(key.split("_").first, value));
final containsKey =
translationsWithNoCountry.containsKey(Get.locale!.languageCode);
translationsWithNoCountry.containsKey(Get.locale!.languageCode.split("_").first);
if (!containsKey) {
return null;
}
return translationsWithNoCountry[Get.locale!.languageCode];
return translationsWithNoCountry[Get.locale!.languageCode.split("_").first];
}
String get tr {
... ...
... ... @@ -125,7 +125,7 @@ void main() {
reactiveInteger.trigger(3);
// then repeat twice
reactiveInteger.trigger(3);
reactiveInteger.trigger(3);
reactiveInteger.trigger(1);
await Future.delayed(Duration(milliseconds: 100));
expect(3, timesCalled);
... ...