Showing
9 changed files
with
429 additions
and
172 deletions
lib/base/base_state.dart
0 → 100644
| 1 | +import 'package:flutter/material.dart'; | ||
| 2 | +import 'package:flutter_easyloading/flutter_easyloading.dart'; | ||
| 3 | + | ||
| 4 | +abstract class BaseState<T extends StatefulWidget> extends State<T> { | ||
| 5 | + bool _isFirstBuild = true; | ||
| 6 | + | ||
| 7 | + @override | ||
| 8 | + Widget build(BuildContext context) { | ||
| 9 | + if (_isFirstBuild) { | ||
| 10 | + onFirstBuildBody(context); | ||
| 11 | + _isFirstBuild = false; | ||
| 12 | + } | ||
| 13 | + return buildBody(context); | ||
| 14 | + } | ||
| 15 | + | ||
| 16 | + Widget buildBody(BuildContext context); | ||
| 17 | + | ||
| 18 | + void onFirstBuildBody(BuildContext context){ | ||
| 19 | + | ||
| 20 | + } | ||
| 21 | + | ||
| 22 | + | ||
| 23 | + showLoading({String text = 'Loading...'}) { | ||
| 24 | + EasyLoading.show(status: text); | ||
| 25 | + } | ||
| 26 | + | ||
| 27 | + hideLoading() { | ||
| 28 | + EasyLoading.dismiss(); | ||
| 29 | + } | ||
| 30 | +} |
| ... | @@ -8,4 +8,115 @@ extension WidgetExt on Widget { | ... | @@ -8,4 +8,115 @@ extension WidgetExt on Widget { |
| 8 | SafeArea safe() { | 8 | SafeArea safe() { |
| 9 | return SafeArea(child: this); | 9 | return SafeArea(child: this); |
| 10 | } | 10 | } |
| 11 | + | ||
| 12 | + ClipRRect round({double? radius, BorderRadius? borderRadius}) { | ||
| 13 | + return ClipRRect( | ||
| 14 | + borderRadius: borderRadius ?? BorderRadius.all(Radius.circular(radius ?? 5)), | ||
| 15 | + child: this, | ||
| 16 | + ); | ||
| 17 | + } | ||
| 18 | + | ||
| 19 | + Container height(double height, {Alignment? alignment}) { | ||
| 20 | + return Container(height: height, alignment: alignment, child: this); | ||
| 21 | + } | ||
| 22 | + | ||
| 23 | + Container height48({Alignment? alignment}) { | ||
| 24 | + return Container(height: 48, alignment: alignment, child: this); | ||
| 25 | + } | ||
| 26 | + | ||
| 27 | + Container paddingALL(double padding) { | ||
| 28 | + return Container(child: this, padding: EdgeInsets.all(padding)); | ||
| 29 | + } | ||
| 30 | + | ||
| 31 | + Container paddingTopBottom(double padding) { | ||
| 32 | + return Container(child: this, padding: EdgeInsets.only(bottom: padding, top: padding)); | ||
| 33 | + } | ||
| 34 | + | ||
| 35 | + Container paddingBottom(double padding) { | ||
| 36 | + return Container(child: this, padding: EdgeInsets.only(bottom: padding)); | ||
| 37 | + } | ||
| 38 | + | ||
| 39 | + Container paddingTop(double padding) { | ||
| 40 | + return Container(child: this, padding: EdgeInsets.only(top: padding)); | ||
| 41 | + } | ||
| 42 | + | ||
| 43 | + Widget paddingLeftRight(double padding) { | ||
| 44 | + return Container(child: this, padding: EdgeInsets.only(left: padding, right: padding)); | ||
| 45 | + } | ||
| 46 | + | ||
| 47 | + Container paddingLeft(double padding) { | ||
| 48 | + return Container(child: this, padding: EdgeInsets.only(left: padding)); | ||
| 49 | + } | ||
| 50 | + | ||
| 51 | + Container paddingRight(double padding) { | ||
| 52 | + return Container(child: this, padding: EdgeInsets.only(right: padding)); | ||
| 53 | + } | ||
| 54 | + | ||
| 55 | + Row rowRight() { | ||
| 56 | + return Row(mainAxisAlignment: MainAxisAlignment.end, children: [this]); | ||
| 57 | + } | ||
| 58 | + | ||
| 59 | + Row rowLeft() { | ||
| 60 | + return Row(mainAxisAlignment: MainAxisAlignment.start, children: [this]); | ||
| 61 | + } | ||
| 62 | + | ||
| 63 | + Row rowCenter() { | ||
| 64 | + return Row(mainAxisAlignment: MainAxisAlignment.center, children: [this]); | ||
| 65 | + } | ||
| 66 | + | ||
| 67 | + Widget click( | ||
| 68 | + GestureTapCallback? onTap, { | ||
| 69 | + Color? color, | ||
| 70 | + double? radius, | ||
| 71 | + BorderRadius? borderRadius, | ||
| 72 | + Color? bgColor, | ||
| 73 | + }) { | ||
| 74 | + if (color != null) { | ||
| 75 | + var border = radius == null ? null : BorderRadius.all(Radius.circular(radius)); | ||
| 76 | + return getMaterialInkWell( | ||
| 77 | + this, | ||
| 78 | + onTap, | ||
| 79 | + color, | ||
| 80 | + borderRadius: borderRadius ?? border, | ||
| 81 | + bgColor: bgColor, | ||
| 82 | + ); | ||
| 83 | + } | ||
| 84 | + return getWhiteInkWell(this, onTap); | ||
| 85 | + } | ||
| 86 | + | ||
| 87 | + ///当InkWell效果失效时,使用这个套件 | ||
| 88 | + Widget getMaterialInkWell( | ||
| 89 | + Widget widget, | ||
| 90 | + GestureTapCallback? onTap, | ||
| 91 | + Color? color, { | ||
| 92 | + BorderRadius? borderRadius, | ||
| 93 | + Color? splashColor, | ||
| 94 | + Color? bgColor = Colors.white, | ||
| 95 | + }) { | ||
| 96 | + return Material( | ||
| 97 | + color: bgColor, | ||
| 98 | + child: Ink( | ||
| 99 | + decoration: BoxDecoration( | ||
| 100 | + color: color, | ||
| 101 | + borderRadius: borderRadius, | ||
| 102 | + ), | ||
| 103 | + child: InkWell( | ||
| 104 | + onTap: onTap, | ||
| 105 | + splashColor: splashColor, | ||
| 106 | + borderRadius: borderRadius, | ||
| 107 | + child: widget, | ||
| 108 | + ), | ||
| 109 | + ), | ||
| 110 | + ); | ||
| 111 | + } | ||
| 112 | + | ||
| 113 | + ///当InkWell效果失效时,使用这个套件 | ||
| 114 | + Widget getWhiteInkWell(Widget widget, GestureTapCallback? onTap, {BorderRadius? borderRadius}) { | ||
| 115 | + return Material( | ||
| 116 | + child: Ink( | ||
| 117 | + color: Colors.white, | ||
| 118 | + child: InkWell(onTap: onTap, child: widget), | ||
| 119 | + ), | ||
| 120 | + ); | ||
| 121 | + } | ||
| 11 | } | 122 | } | ... | ... |
| ... | @@ -6,6 +6,7 @@ import 'package:dio/dio.dart'; | ... | @@ -6,6 +6,7 @@ import 'package:dio/dio.dart'; |
| 6 | import 'package:flustars/flustars.dart'; | 6 | import 'package:flustars/flustars.dart'; |
| 7 | import 'package:flutter/material.dart'; | 7 | import 'package:flutter/material.dart'; |
| 8 | import 'package:flutter/services.dart'; | 8 | import 'package:flutter/services.dart'; |
| 9 | +import 'package:flutter_easyloading/flutter_easyloading.dart'; | ||
| 9 | import 'package:oktoast/oktoast.dart'; | 10 | import 'package:oktoast/oktoast.dart'; |
| 10 | import 'package:provider/provider.dart'; | 11 | import 'package:provider/provider.dart'; |
| 11 | import 'package:quick_actions/quick_actions.dart'; | 12 | import 'package:quick_actions/quick_actions.dart'; |
| ... | @@ -60,8 +61,7 @@ Future<void> main() async { | ... | @@ -60,8 +61,7 @@ Future<void> main() async { |
| 60 | await SpUtil.getInstance(); | 61 | await SpUtil.getInstance(); |
| 61 | 62 | ||
| 62 | WidgetsFlutterBinding.ensureInitialized(); | 63 | WidgetsFlutterBinding.ensureInitialized(); |
| 63 | - SystemChrome.setPreferredOrientations( | 64 | + SystemChrome.setPreferredOrientations([DeviceOrientation.portraitUp, DeviceOrientation.portraitDown]); |
| 64 | - [DeviceOrientation.portraitUp, DeviceOrientation.portraitDown]); | ||
| 65 | 65 | ||
| 66 | /// 1.22 预览功能: 在输入频率与显示刷新率不匹配情况下提供平滑的滚动效果 | 66 | /// 1.22 预览功能: 在输入频率与显示刷新率不匹配情况下提供平滑的滚动效果 |
| 67 | // GestureBinding.instance?.resamplingEnabled = true; | 67 | // GestureBinding.instance?.resamplingEnabled = true; |
| ... | @@ -69,8 +69,7 @@ Future<void> main() async { | ... | @@ -69,8 +69,7 @@ Future<void> main() async { |
| 69 | handleError(() => runApp(MyApp())); | 69 | handleError(() => runApp(MyApp())); |
| 70 | 70 | ||
| 71 | /// 隐藏状态栏。为启动页、引导页设置。完成后修改回显示状态栏。 | 71 | /// 隐藏状态栏。为启动页、引导页设置。完成后修改回显示状态栏。 |
| 72 | - SystemChrome.setEnabledSystemUIMode(SystemUiMode.manual, | 72 | + SystemChrome.setEnabledSystemUIMode(SystemUiMode.manual, overlays: [SystemUiOverlay.bottom]); |
| 73 | - overlays: [SystemUiOverlay.bottom]); | ||
| 74 | // TODO(weilu): 启动体验不佳。状态栏、导航栏在冷启动开始的一瞬间为黑色,且无法通过隐藏、修改颜色等方式进行处理。。。 | 73 | // TODO(weilu): 启动体验不佳。状态栏、导航栏在冷启动开始的一瞬间为黑色,且无法通过隐藏、修改颜色等方式进行处理。。。 |
| 75 | // 相关问题跟踪:https://github.com/flutter/flutter/issues/73351 | 74 | // 相关问题跟踪:https://github.com/flutter/flutter/issues/73351 |
| 76 | if (Platform.isAndroid) { | 75 | if (Platform.isAndroid) { |
| ... | @@ -133,8 +132,7 @@ class MyApp extends StatelessWidget { | ... | @@ -133,8 +132,7 @@ class MyApp extends StatelessWidget { |
| 133 | } | 132 | } |
| 134 | 133 | ||
| 135 | quickActions.setShortcutItems(<ShortcutItem>[ | 134 | quickActions.setShortcutItems(<ShortcutItem>[ |
| 136 | - const ShortcutItem( | 135 | + const ShortcutItem(type: 'demo', localizedTitle: '发一言', icon: 'flutter_dash_black'), |
| 137 | - type: 'demo', localizedTitle: '发一言', icon: 'flutter_dash_black'), | ||
| 138 | ]); | 136 | ]); |
| 139 | } | 137 | } |
| 140 | } | 138 | } |
| ... | @@ -149,8 +147,7 @@ class MyApp extends StatelessWidget { | ... | @@ -149,8 +147,7 @@ class MyApp extends StatelessWidget { |
| 149 | ChangeNotifierProvider(create: (_) => MembershipViewProvider()) | 147 | ChangeNotifierProvider(create: (_) => MembershipViewProvider()) |
| 150 | ], | 148 | ], |
| 151 | child: Consumer2<ThemeProvider, LocaleProvider>( | 149 | child: Consumer2<ThemeProvider, LocaleProvider>( |
| 152 | - builder: | 150 | + builder: (_, ThemeProvider provider, LocaleProvider localeProvider, __) { |
| 153 | - (_, ThemeProvider provider, LocaleProvider localeProvider, __) { | ||
| 154 | return _buildMaterialApp(provider, localeProvider); | 151 | return _buildMaterialApp(provider, localeProvider); |
| 155 | }, | 152 | }, |
| 156 | ), | 153 | ), |
| ... | @@ -159,15 +156,13 @@ class MyApp extends StatelessWidget { | ... | @@ -159,15 +156,13 @@ class MyApp extends StatelessWidget { |
| 159 | /// Toast 配置 | 156 | /// Toast 配置 |
| 160 | return OKToast( | 157 | return OKToast( |
| 161 | backgroundColor: Colors.black54, | 158 | backgroundColor: Colors.black54, |
| 162 | - textPadding: | 159 | + textPadding: const EdgeInsets.symmetric(horizontal: 16.0, vertical: 10.0), |
| 163 | - const EdgeInsets.symmetric(horizontal: 16.0, vertical: 10.0), | ||
| 164 | radius: 20.0, | 160 | radius: 20.0, |
| 165 | position: ToastPosition.bottom, | 161 | position: ToastPosition.bottom, |
| 166 | child: app); | 162 | child: app); |
| 167 | } | 163 | } |
| 168 | 164 | ||
| 169 | - Widget _buildMaterialApp( | 165 | + Widget _buildMaterialApp(ThemeProvider provider, LocaleProvider localeProvider) { |
| 170 | - ThemeProvider provider, LocaleProvider localeProvider) { | ||
| 171 | return MaterialApp( | 166 | return MaterialApp( |
| 172 | title: '一言', | 167 | title: '一言', |
| 173 | // showPerformanceOverlay: true, //显示性能标签 | 168 | // showPerformanceOverlay: true, //显示性能标签 |
| ... | @@ -185,7 +180,7 @@ class MyApp extends StatelessWidget { | ... | @@ -185,7 +180,7 @@ class MyApp extends StatelessWidget { |
| 185 | supportedLocales: ParlandoLocalizations.supportedLocales, | 180 | supportedLocales: ParlandoLocalizations.supportedLocales, |
| 186 | locale: localeProvider.locale, | 181 | locale: localeProvider.locale, |
| 187 | navigatorKey: navigatorKey, | 182 | navigatorKey: navigatorKey, |
| 188 | - builder: (BuildContext context, Widget? child) { | 183 | + builder: EasyLoading.init(builder: (context, child) { |
| 189 | /// 仅针对安卓 | 184 | /// 仅针对安卓 |
| 190 | if (Device.isAndroid) { | 185 | if (Device.isAndroid) { |
| 191 | /// 切换深色模式会触发此方法,这里设置导航栏颜色 | 186 | /// 切换深色模式会触发此方法,这里设置导航栏颜色 |
| ... | @@ -197,7 +192,7 @@ class MyApp extends StatelessWidget { | ... | @@ -197,7 +192,7 @@ class MyApp extends StatelessWidget { |
| 197 | data: MediaQuery.of(context).copyWith(textScaleFactor: 1.0), | 192 | data: MediaQuery.of(context).copyWith(textScaleFactor: 1.0), |
| 198 | child: child!, | 193 | child: child!, |
| 199 | ); | 194 | ); |
| 200 | - }, | 195 | + }), |
| 201 | 196 | ||
| 202 | /// 因为使用了fluro,这里设置主要针对Web | 197 | /// 因为使用了fluro,这里设置主要针对Web |
| 203 | onUnknownRoute: (_) { | 198 | onUnknownRoute: (_) { | ... | ... |
| ... | @@ -2,11 +2,15 @@ import 'dart:async'; | ... | @@ -2,11 +2,15 @@ import 'dart:async'; |
| 2 | import 'dart:ui'; | 2 | import 'dart:ui'; |
| 3 | 3 | ||
| 4 | import 'package:Parlando/apis/api_response.dart'; | 4 | import 'package:Parlando/apis/api_response.dart'; |
| 5 | +import 'package:Parlando/base/base_state.dart'; | ||
| 6 | +import 'package:Parlando/extension/widget_ext.dart'; | ||
| 5 | import 'package:Parlando/login/login_router.dart'; | 7 | import 'package:Parlando/login/login_router.dart'; |
| 6 | import 'package:Parlando/membership/models/membership_entity.dart'; | 8 | import 'package:Parlando/membership/models/membership_entity.dart'; |
| 7 | import 'package:Parlando/membership/view_models/membership_view_model.dart'; | 9 | import 'package:Parlando/membership/view_models/membership_view_model.dart'; |
| 10 | +import 'package:Parlando/payment/payment_sdk.dart'; | ||
| 8 | import 'package:Parlando/payment/payment_service.dart'; | 11 | import 'package:Parlando/payment/payment_service.dart'; |
| 9 | import 'package:Parlando/res/constant.dart'; | 12 | import 'package:Parlando/res/constant.dart'; |
| 13 | +import 'package:Parlando/widgets/my_button.dart'; | ||
| 10 | import 'package:cached_network_image/cached_network_image.dart'; | 14 | import 'package:cached_network_image/cached_network_image.dart'; |
| 11 | import 'package:flustars/flustars.dart'; | 15 | import 'package:flustars/flustars.dart'; |
| 12 | import 'package:flutter/material.dart'; | 16 | import 'package:flutter/material.dart'; |
| ... | @@ -15,6 +19,7 @@ import 'package:Parlando/routers/fluro_navigator.dart'; | ... | @@ -15,6 +19,7 @@ import 'package:Parlando/routers/fluro_navigator.dart'; |
| 15 | import 'package:Parlando/extension/int_extension.dart'; | 19 | import 'package:Parlando/extension/int_extension.dart'; |
| 16 | import 'package:flutter_inapp_purchase/flutter_inapp_purchase.dart'; | 20 | import 'package:flutter_inapp_purchase/flutter_inapp_purchase.dart'; |
| 17 | import 'package:getwidget/getwidget.dart'; | 21 | import 'package:getwidget/getwidget.dart'; |
| 22 | +import 'package:in_app_purchase_platform_interface/src/types/product_details.dart'; | ||
| 18 | import 'package:provider/provider.dart'; | 23 | import 'package:provider/provider.dart'; |
| 19 | 24 | ||
| 20 | class MembershipPage extends StatefulWidget { | 25 | class MembershipPage extends StatefulWidget { |
| ... | @@ -24,119 +29,112 @@ class MembershipPage extends StatefulWidget { | ... | @@ -24,119 +29,112 @@ class MembershipPage extends StatefulWidget { |
| 24 | MembershipPageState createState() => MembershipPageState(); | 29 | MembershipPageState createState() => MembershipPageState(); |
| 25 | } | 30 | } |
| 26 | 31 | ||
| 27 | -class MembershipPageState extends State<MembershipPage> | 32 | +class MembershipPageState extends BaseState<MembershipPage> with WidgetsBindingObserver { |
| 28 | - with WidgetsBindingObserver { | ||
| 29 | - bool _isLoading = false; | ||
| 30 | 33 | ||
| 31 | final List<Widget> _productWidgets = <Widget>[]; | 34 | final List<Widget> _productWidgets = <Widget>[]; |
| 32 | 35 | ||
| 36 | + Function? connectionCallBack; | ||
| 37 | + | ||
| 38 | + ApiResponse? apiResponse; | ||
| 39 | + | ||
| 33 | @override | 40 | @override |
| 34 | void initState() { | 41 | void initState() { |
| 35 | super.initState(); | 42 | super.initState(); |
| 43 | + PaymentSdk.instance.initState(); | ||
| 44 | + PaymentSdk.instance.queryProducts().then((value) { | ||
| 45 | + for (var element in value) { | ||
| 46 | + _productWidgets.add(buildBuyItem(element)); | ||
| 47 | + } | ||
| 48 | + setState(() {}); | ||
| 49 | + }); | ||
| 36 | if (SpUtil.containsKey(Constant.userToken)!) { | 50 | if (SpUtil.containsKey(Constant.userToken)!) { |
| 37 | - Provider.of<MembershipViewProvider>(context, listen: false) | 51 | + Provider.of<MembershipViewProvider>(context, listen: false).setSelectedMembership(null); |
| 38 | - .setSelectedMembership(null); | 52 | + Provider.of<MembershipViewProvider>(context, listen: false).fetchMembershipData('0'); |
| 39 | - Provider.of<MembershipViewProvider>(context, listen: false) | ||
| 40 | - .fetchMembershipData('0'); | ||
| 41 | - PaymentService.instance.initConnection(); | ||
| 42 | } else { | 53 | } else { |
| 43 | NavigatorUtils.push(context, LoginRouter.loginPage, replace: true); | 54 | NavigatorUtils.push(context, LoginRouter.loginPage, replace: true); |
| 44 | } | 55 | } |
| 45 | } | 56 | } |
| 46 | 57 | ||
| 47 | @override | 58 | @override |
| 48 | - Widget build(BuildContext context) { | 59 | + void onFirstBuildBody(BuildContext context) { |
| 49 | - ApiResponse apiResponse = | 60 | + super.onFirstBuildBody(context); |
| 50 | - Provider.of<MembershipViewProvider>(context).response; | 61 | + showLoading(); |
| 51 | - switch (apiResponse.status) { | 62 | + } |
| 52 | - case Status.LOADING: | 63 | + |
| 53 | - return const Center(child: CircularProgressIndicator()); | 64 | + @override |
| 54 | - case Status.COMPLETED: | 65 | + Widget buildBody(BuildContext context) { |
| 55 | - MembershipData mb = apiResponse.data as MembershipData; | 66 | + apiResponse = Provider.of<MembershipViewProvider>(context).response; |
| 56 | - return SafeArea( | 67 | + if (apiResponse?.status != Status.LOADING) { |
| 57 | - child: Scaffold( | 68 | + hideLoading(); |
| 58 | - backgroundColor: Colors.black, | 69 | + } |
| 70 | + var url = "https://api.parlando.ink/storage/images/274d864a62d277b2a29c4db39f92d591.png"; | ||
| 71 | + return Scaffold( | ||
| 59 | body: Container( | 72 | body: Container( |
| 60 | - alignment: Alignment.topCenter, | ||
| 61 | decoration: BoxDecoration( | 73 | decoration: BoxDecoration( |
| 62 | - image: DecorationImage( | 74 | + image: DecorationImage(image: CachedNetworkImageProvider(url), fit: BoxFit.fill), |
| 63 | - image: CachedNetworkImageProvider(mb.bgImages!), | ||
| 64 | - fit: BoxFit.fill, | ||
| 65 | - ), | ||
| 66 | ), | 75 | ), |
| 67 | child: Column( | 76 | child: Column( |
| 68 | - crossAxisAlignment: CrossAxisAlignment.center, | ||
| 69 | children: [ | 77 | children: [ |
| 70 | - Container( | 78 | + initBackBar(), |
| 71 | - alignment: Alignment.centerLeft, | 79 | + Spacer(), |
| 72 | - child: IconButton( | 80 | + // buildCompleteWidget(), |
| 73 | - onPressed: () { | 81 | + ], |
| 74 | - NavigatorUtils.goBack(context); | ||
| 75 | - }, | ||
| 76 | - icon: const Icon( | ||
| 77 | - Icons.arrow_back_ios, | ||
| 78 | - color: Colors.white, | ||
| 79 | - ), | ||
| 80 | ), | 82 | ), |
| 81 | ), | 83 | ), |
| 82 | - const Spacer(), | 84 | + ); |
| 83 | - Container( | 85 | + } |
| 84 | - margin: EdgeInsets.symmetric( | 86 | + |
| 85 | - vertical: 60.px, horizontal: 20.px), | 87 | + Widget buildLoading() { |
| 88 | + return const Center(child: CircularProgressIndicator()); | ||
| 89 | + } | ||
| 90 | + | ||
| 91 | + Widget buildCompleteWidget() { | ||
| 92 | + MembershipData? mb = apiResponse?.data; | ||
| 93 | + return Container( | ||
| 86 | height: MediaQuery.of(context).size.height / 2, | 94 | height: MediaQuery.of(context).size.height / 2, |
| 87 | - width: double.infinity, | ||
| 88 | decoration: BoxDecoration( | 95 | decoration: BoxDecoration( |
| 89 | color: Colors.grey.shade200.withOpacity(0.1), | 96 | color: Colors.grey.shade200.withOpacity(0.1), |
| 90 | - border: Border.all( | 97 | + border: Border.all(color: Colors.grey.shade50, width: 0.5), // 边色与边宽度 |
| 91 | - color: Colors.grey.shade50, | ||
| 92 | - width: 0.5, | ||
| 93 | - ), // 边色与边宽度 | ||
| 94 | ), | 98 | ), |
| 99 | + margin: const EdgeInsets.all(20), | ||
| 95 | child: ClipRect( | 100 | child: ClipRect( |
| 96 | child: BackdropFilter( | 101 | child: BackdropFilter( |
| 97 | - filter: ImageFilter.blur( | 102 | + filter: ImageFilter.blur(sigmaX: 10.0, sigmaY: 10.0), |
| 98 | - sigmaX: 10.0, | ||
| 99 | - sigmaY: 10.0, | ||
| 100 | - ), | ||
| 101 | child: Container( | 103 | child: Container( |
| 102 | - decoration: BoxDecoration( | 104 | + decoration: BoxDecoration(color: Colors.grey.shade200.withOpacity(0.1)), |
| 103 | - color: Colors.grey.shade200.withOpacity(0.1), | ||
| 104 | - ), | ||
| 105 | child: Padding( | 105 | child: Padding( |
| 106 | padding: EdgeInsets.all(10.px), | 106 | padding: EdgeInsets.all(10.px), |
| 107 | - child: Column( | 107 | + child: buildMemberContent(mb), |
| 108 | - crossAxisAlignment: CrossAxisAlignment.center, | ||
| 109 | - children: [ | ||
| 110 | - Text( | ||
| 111 | - mb.title!, | ||
| 112 | - style: TextStyle( | ||
| 113 | - fontSize: 18.px, | ||
| 114 | - color: Colors.white, | ||
| 115 | ), | 108 | ), |
| 116 | ), | 109 | ), |
| 117 | - Gaps.vGap24, | ||
| 118 | - // TODO 如果会员则显示会员详情 | ||
| 119 | - SizedBox( | ||
| 120 | - width: double.infinity, | ||
| 121 | - height: 100, | ||
| 122 | - child: _productWidgets.isEmpty | ||
| 123 | - ? Column( | ||
| 124 | - children: _productWidgets, | ||
| 125 | - ) | ||
| 126 | - : const GFLoader(), | ||
| 127 | - ), | ||
| 128 | - Gaps.vGap24, | ||
| 129 | - Text( | ||
| 130 | - mb.intro!, | ||
| 131 | - style: TextStyle( | ||
| 132 | - fontSize: 14.px, | ||
| 133 | - color: Colors.white, | ||
| 134 | ), | 110 | ), |
| 135 | ), | 111 | ), |
| 136 | - Gaps.vGap10, | 112 | + ); |
| 137 | - Row( | 113 | + } |
| 138 | - mainAxisAlignment: | 114 | + |
| 139 | - MainAxisAlignment.spaceBetween, | 115 | + @override |
| 116 | + void dispose() { | ||
| 117 | + PaymentSdk.instance.dispose(); | ||
| 118 | + super.dispose(); | ||
| 119 | + } | ||
| 120 | + | ||
| 121 | + Widget buildBuyItem(ProductDetails element) { | ||
| 122 | + var style = const TextStyle(color: Colors.white); | ||
| 123 | + var button = Text("购买", style: style).paddingLeftRight(15).paddingTopBottom(8).click(() { | ||
| 124 | + PaymentSdk.instance.buy(element); | ||
| 125 | + }, color: Colors.blue).round(radius: 10); | ||
| 126 | + return Row( | ||
| 127 | + mainAxisAlignment: MainAxisAlignment.end, | ||
| 128 | + children: [ | ||
| 129 | + Text(element.price, style: style), | ||
| 130 | + button, | ||
| 131 | + ], | ||
| 132 | + ).paddingLeftRight(10).paddingTopBottom(5); | ||
| 133 | + } | ||
| 134 | + | ||
| 135 | + Widget initOtherEntrance() { | ||
| 136 | + return Row( | ||
| 137 | + mainAxisAlignment: MainAxisAlignment.spaceBetween, | ||
| 140 | mainAxisSize: MainAxisSize.min, | 138 | mainAxisSize: MainAxisSize.min, |
| 141 | crossAxisAlignment: CrossAxisAlignment.center, | 139 | crossAxisAlignment: CrossAxisAlignment.center, |
| 142 | children: [ | 140 | children: [ |
| ... | @@ -144,82 +142,53 @@ class MembershipPageState extends State<MembershipPage> | ... | @@ -144,82 +142,53 @@ class MembershipPageState extends State<MembershipPage> |
| 144 | onPressed: () {}, | 142 | onPressed: () {}, |
| 145 | child: Text( | 143 | child: Text( |
| 146 | "服务协议", | 144 | "服务协议", |
| 147 | - style: TextStyle( | 145 | + style: TextStyle(fontSize: 14.px, color: Colors.white), |
| 148 | - fontSize: 14.px, | ||
| 149 | - color: Colors.white, | ||
| 150 | ), | 146 | ), |
| 151 | ), | 147 | ), |
| 152 | - ), | 148 | + Container(width: 0.6, height: 15.0, color: Colours.line), |
| 153 | - Container( | ||
| 154 | - width: 0.6, | ||
| 155 | - height: 15.0, | ||
| 156 | - color: Colours.line, | ||
| 157 | - ), | ||
| 158 | TextButton( | 149 | TextButton( |
| 159 | onPressed: () {}, | 150 | onPressed: () {}, |
| 160 | child: Text( | 151 | child: Text( |
| 161 | "隐私政策", | 152 | "隐私政策", |
| 162 | - style: TextStyle( | 153 | + style: TextStyle(fontSize: 14.px, color: Colors.white), |
| 163 | - fontSize: 14.px, | ||
| 164 | - color: Colors.white, | ||
| 165 | - ), | ||
| 166 | ), | 154 | ), |
| 167 | ), | 155 | ), |
| 168 | - Container( | 156 | + Container(width: 0.6, height: 15.0, color: Colours.line), |
| 169 | - width: 0.6, | ||
| 170 | - height: 15.0, | ||
| 171 | - color: Colours.line, | ||
| 172 | - ), | ||
| 173 | TextButton( | 157 | TextButton( |
| 174 | onPressed: () {}, | 158 | onPressed: () {}, |
| 175 | child: Text( | 159 | child: Text( |
| 176 | "恢复购买", | 160 | "恢复购买", |
| 177 | - style: TextStyle( | 161 | + style: TextStyle(fontSize: 14.px, color: Colors.white), |
| 178 | - fontSize: 14.px, | ||
| 179 | - color: Colors.white, | ||
| 180 | - ), | ||
| 181 | - ), | ||
| 182 | - ), | ||
| 183 | - ], | ||
| 184 | - ), | ||
| 185 | - ], | ||
| 186 | - ), | ||
| 187 | - ), | ||
| 188 | - ), | ||
| 189 | - ), | ||
| 190 | ), | 162 | ), |
| 191 | ), | 163 | ), |
| 192 | ], | 164 | ], |
| 193 | - ), | ||
| 194 | - ), | ||
| 195 | - ), | ||
| 196 | - ); | ||
| 197 | - case Status.ERROR: | ||
| 198 | - return Center( | ||
| 199 | - child: Text('暂时无法获取数据,请稍候再试!${apiResponse.message}'), | ||
| 200 | - ); | ||
| 201 | - case Status.INITIAL: | ||
| 202 | - default: | ||
| 203 | - return const Center( | ||
| 204 | - child: Text('正在获取数据....'), | ||
| 205 | ); | 165 | ); |
| 206 | } | 166 | } |
| 207 | - } | ||
| 208 | - | ||
| 209 | - @override | ||
| 210 | - void didChangeAppLifecycleState(AppLifecycleState state) {} | ||
| 211 | 167 | ||
| 212 | - @override | 168 | + buildMemberContent(MembershipData? mb) { |
| 213 | - void dispose() { | 169 | + return Column( |
| 214 | - PaymentService.instance.dispose(); | 170 | + crossAxisAlignment: CrossAxisAlignment.center, |
| 215 | - super.dispose(); | 171 | + children: [ |
| 172 | + Text(mb?.title ?? "一言会员", style: TextStyle(fontSize: 18.px, color: Colors.white)), | ||
| 173 | + Gaps.vGap24, | ||
| 174 | + _productWidgets.isNotEmpty ? Column(children: _productWidgets) : const GFLoader(), | ||
| 175 | + Gaps.vGap24, | ||
| 176 | + Text(mb?.intro ?? "一言介绍", style: TextStyle(fontSize: 14.px, color: Colors.white)), | ||
| 177 | + Gaps.vGap10, | ||
| 178 | + initOtherEntrance(), | ||
| 179 | + ], | ||
| 180 | + ); | ||
| 216 | } | 181 | } |
| 217 | 182 | ||
| 218 | - void _renderInApps() async { | 183 | + initBackBar() { |
| 219 | - List<IAPItem> items = await PaymentService.instance.products; | 184 | + return Container( |
| 220 | - for (IAPItem item in items) { | 185 | + alignment: Alignment.centerLeft, |
| 221 | - _productWidgets.add(Text(item.title!)); | 186 | + child: IconButton( |
| 222 | - } | 187 | + onPressed: () { |
| 223 | - setState(() {}); | 188 | + NavigatorUtils.goBack(context); |
| 189 | + }, | ||
| 190 | + icon: const Icon(Icons.arrow_back_ios, color: Colors.white), | ||
| 191 | + ), | ||
| 192 | + ); | ||
| 224 | } | 193 | } |
| 225 | } | 194 | } | ... | ... |
| ... | @@ -122,7 +122,7 @@ class LoggingInterceptor extends Interceptor { | ... | @@ -122,7 +122,7 @@ class LoggingInterceptor extends Interceptor { |
| 122 | Log.e('ResponseCode: ${response.statusCode}'); | 122 | Log.e('ResponseCode: ${response.statusCode}'); |
| 123 | } | 123 | } |
| 124 | // 输出结果 | 124 | // 输出结果 |
| 125 | - Log.json(response.data.toString()); | 125 | + // Log.json(response.data.toString()); |
| 126 | Log.d('----------End: $duration 毫秒----------'); | 126 | Log.d('----------End: $duration 毫秒----------'); |
| 127 | super.onResponse(response, handler); | 127 | super.onResponse(response, handler); |
| 128 | } | 128 | } | ... | ... |
lib/payment/payment_sdk.dart
0 → 100644
| 1 | +import 'dart:async'; | ||
| 2 | + | ||
| 3 | +import 'package:flutter/material.dart'; | ||
| 4 | +import 'package:in_app_purchase/in_app_purchase.dart'; | ||
| 5 | + | ||
| 6 | +class PaymentSdk { | ||
| 7 | + PaymentSdk._privateConstructor(); | ||
| 8 | + | ||
| 9 | + static final PaymentSdk _instance = PaymentSdk._privateConstructor(); | ||
| 10 | + | ||
| 11 | + static PaymentSdk get instance { | ||
| 12 | + return _instance; | ||
| 13 | + } | ||
| 14 | + | ||
| 15 | + static const Set<String> _kIds = <String>{'yearly_yiyan_vip', 'monthly_yiyan_vip', 'yearly-default'}; | ||
| 16 | + | ||
| 17 | + StreamSubscription<List<PurchaseDetails>>? _subscription; | ||
| 18 | + List<ProductDetails> products = []; | ||
| 19 | + | ||
| 20 | + initState() { | ||
| 21 | + final Stream<List<PurchaseDetails>> purchaseUpdated = InAppPurchase.instance.purchaseStream; | ||
| 22 | + _subscription = purchaseUpdated.listen((purchaseDetailsList) { | ||
| 23 | + _listenToPurchaseUpdated(purchaseDetailsList); | ||
| 24 | + }, onDone: () { | ||
| 25 | + _subscription?.cancel(); | ||
| 26 | + }, onError: (error) { | ||
| 27 | + // handle error here. | ||
| 28 | + }); | ||
| 29 | + } | ||
| 30 | + | ||
| 31 | + Future<List<ProductDetails>> queryProducts() async { | ||
| 32 | + final bool available = await InAppPurchase.instance.isAvailable(); | ||
| 33 | + if (!available) { | ||
| 34 | + print("####### isAvailable false"); | ||
| 35 | + return []; | ||
| 36 | + } | ||
| 37 | + final ProductDetailsResponse response = await InAppPurchase.instance.queryProductDetails({ | ||
| 38 | + 'yearly_yiyan_vip', | ||
| 39 | + 'monthly_yiyan_vip', | ||
| 40 | + 'yearly-default', | ||
| 41 | + 'test.yiyan.vip.1.month', | ||
| 42 | + }); | ||
| 43 | + if (response.notFoundIDs.isNotEmpty) { | ||
| 44 | + // Handle the error. | ||
| 45 | + print("####### notFoundIDs"); | ||
| 46 | + } else { | ||
| 47 | + } | ||
| 48 | + print(response.productDetails.length); | ||
| 49 | + return products = response.productDetails; | ||
| 50 | + } | ||
| 51 | + | ||
| 52 | + buy(ProductDetails details) { | ||
| 53 | + final PurchaseParam purchaseParam = PurchaseParam(productDetails: details); | ||
| 54 | + if (_isConsumable(details)) { | ||
| 55 | + InAppPurchase.instance.buyConsumable(purchaseParam: purchaseParam); | ||
| 56 | + } else { | ||
| 57 | + InAppPurchase.instance.buyNonConsumable(purchaseParam: purchaseParam); | ||
| 58 | + } | ||
| 59 | + } | ||
| 60 | + | ||
| 61 | + void _listenToPurchaseUpdated(List<PurchaseDetails> purchaseDetailsList) async { | ||
| 62 | + for (var purchaseDetails in purchaseDetailsList) { | ||
| 63 | + if (purchaseDetails.status == PurchaseStatus.pending) { | ||
| 64 | + // _showPendingUI(); | ||
| 65 | + } else { | ||
| 66 | + if (purchaseDetails.status == PurchaseStatus.error) { | ||
| 67 | + // _handleError(purchaseDetails.error!); | ||
| 68 | + } else if (purchaseDetails.status == PurchaseStatus.purchased || purchaseDetails.status == PurchaseStatus.restored) { | ||
| 69 | + bool valid = await _verifyPurchase(purchaseDetails); | ||
| 70 | + if (valid) { | ||
| 71 | + _deliverProduct(purchaseDetails); | ||
| 72 | + } else { | ||
| 73 | + _handleInvalidPurchase(purchaseDetails); | ||
| 74 | + } | ||
| 75 | + } | ||
| 76 | + if (purchaseDetails.pendingCompletePurchase) { | ||
| 77 | + await InAppPurchase.instance.completePurchase(purchaseDetails); | ||
| 78 | + } | ||
| 79 | + } | ||
| 80 | + } | ||
| 81 | + } | ||
| 82 | + | ||
| 83 | + _verifyPurchase(PurchaseDetails purchaseDetails) async { | ||
| 84 | + return true; | ||
| 85 | + } | ||
| 86 | + | ||
| 87 | + void _deliverProduct(PurchaseDetails purchaseDetails) {} | ||
| 88 | + | ||
| 89 | + void _handleInvalidPurchase(PurchaseDetails purchaseDetails) {} | ||
| 90 | + | ||
| 91 | + bool _isConsumable(ProductDetails details) { | ||
| 92 | + return true; | ||
| 93 | + } | ||
| 94 | + | ||
| 95 | + void dispose() { | ||
| 96 | + _subscription?.cancel(); | ||
| 97 | + } | ||
| 98 | +} |
| ... | @@ -26,7 +26,7 @@ class PaymentService { | ... | @@ -26,7 +26,7 @@ class PaymentService { |
| 26 | late StreamSubscription<PurchaseResult?> _purchaseErrorSubscription; | 26 | late StreamSubscription<PurchaseResult?> _purchaseErrorSubscription; |
| 27 | 27 | ||
| 28 | /// List of product ids you want to fetch | 28 | /// List of product ids you want to fetch |
| 29 | - final List<String> _productIds = ['test.yiyan.vip.1.month']; | 29 | + final List<String> _productIds = ['yearly_yiyan_vip', 'monthly_yiyan_vip']; |
| 30 | 30 | ||
| 31 | /// All available products will be store in this list | 31 | /// All available products will be store in this list |
| 32 | late List<IAPItem> _products; | 32 | late List<IAPItem> _products; |
| ... | @@ -36,12 +36,12 @@ class PaymentService { | ... | @@ -36,12 +36,12 @@ class PaymentService { |
| 36 | 36 | ||
| 37 | /// view of the app will subscribe to this to get notified | 37 | /// view of the app will subscribe to this to get notified |
| 38 | /// when premium status of the user changes | 38 | /// when premium status of the user changes |
| 39 | - final ObserverList<Function> _proStatusChangedListeners = | 39 | + final ObserverList<Function> _proStatusChangedListeners = ObserverList<Function>(); |
| 40 | - ObserverList<Function>(); | ||
| 41 | 40 | ||
| 42 | /// view of the app will subscribe to this to get errors of the purchase | 41 | /// view of the app will subscribe to this to get errors of the purchase |
| 43 | - final ObserverList<Function(String)> _errorListeners = | 42 | + final ObserverList<Function(String)> _errorListeners = ObserverList<Function(String)>(); |
| 44 | - ObserverList<Function(String)>(); | 43 | + |
| 44 | + final ObserverList<Function> _connectListeners = ObserverList<Function>(); | ||
| 45 | 45 | ||
| 46 | /// logged in user's premium status | 46 | /// logged in user's premium status |
| 47 | bool _isProUser = false; | 47 | bool _isProUser = false; |
| ... | @@ -68,6 +68,20 @@ class PaymentService { | ... | @@ -68,6 +68,20 @@ class PaymentService { |
| 68 | _errorListeners.remove(callback); | 68 | _errorListeners.remove(callback); |
| 69 | } | 69 | } |
| 70 | 70 | ||
| 71 | + addConnectListener(Function? callback) { | ||
| 72 | + if (callback == null) { | ||
| 73 | + return; | ||
| 74 | + } | ||
| 75 | + _connectListeners.add(callback); | ||
| 76 | + } | ||
| 77 | + | ||
| 78 | + removeConnectListener(Function? callback) { | ||
| 79 | + if (callback == null) { | ||
| 80 | + return; | ||
| 81 | + } | ||
| 82 | + _connectListeners.remove(callback); | ||
| 83 | + } | ||
| 84 | + | ||
| 71 | /// Call this method to notify all the subsctibers of _proStatusChangedListeners | 85 | /// Call this method to notify all the subsctibers of _proStatusChangedListeners |
| 72 | void _callProStatusChangedListeners() { | 86 | void _callProStatusChangedListeners() { |
| 73 | for (var callback in _proStatusChangedListeners) { | 87 | for (var callback in _proStatusChangedListeners) { |
| ... | @@ -84,18 +98,19 @@ class PaymentService { | ... | @@ -84,18 +98,19 @@ class PaymentService { |
| 84 | 98 | ||
| 85 | /// Call this method at the startup of you app to initialize connection | 99 | /// Call this method at the startup of you app to initialize connection |
| 86 | /// with billing server and get all the necessary data | 100 | /// with billing server and get all the necessary data |
| 87 | - void initConnection() { | 101 | + void initConnection() async { |
| 88 | - var result = FlutterInappPurchase.instance.initialize(); | 102 | + var result = await FlutterInappPurchase.instance.initialize(); |
| 89 | print("___________________________"); | 103 | print("___________________________"); |
| 90 | print("result:$result"); | 104 | print("result:$result"); |
| 91 | - _connectionSubscription = | 105 | + _connectionSubscription = FlutterInappPurchase.connectionUpdated.listen((connected) { |
| 92 | - FlutterInappPurchase.connectionUpdated.listen((connected) {}); | 106 | + for (var value in _connectListeners) { |
| 107 | + value.call(); | ||
| 108 | + } | ||
| 109 | + }); | ||
| 93 | 110 | ||
| 94 | - _purchaseUpdatedSubscription = | 111 | + _purchaseUpdatedSubscription = FlutterInappPurchase.purchaseUpdated.listen(_handlePurchaseUpdate); |
| 95 | - FlutterInappPurchase.purchaseUpdated.listen(_handlePurchaseUpdate); | ||
| 96 | 112 | ||
| 97 | - _purchaseErrorSubscription = | 113 | + _purchaseErrorSubscription = FlutterInappPurchase.purchaseError.listen(_handlePurchaseError); |
| 98 | - FlutterInappPurchase.purchaseError.listen(_handlePurchaseError); | ||
| 99 | 114 | ||
| 100 | _getItems(); | 115 | _getItems(); |
| 101 | _getPastPurchases(); | 116 | _getPastPurchases(); |
| ... | @@ -191,14 +206,14 @@ class PaymentService { | ... | @@ -191,14 +206,14 @@ class PaymentService { |
| 191 | } | 206 | } |
| 192 | 207 | ||
| 193 | Future<void> _getItems() async { | 208 | Future<void> _getItems() async { |
| 194 | - List<IAPItem> items = | 209 | + List<IAPItem> items = await FlutterInappPurchase.instance.getSubscriptions(_productIds); |
| 195 | - await FlutterInappPurchase.instance.getSubscriptions(_productIds); | 210 | + print("############${items.length}"); |
| 196 | _products = []; | 211 | _products = []; |
| 197 | for (var item in items) { | 212 | for (var item in items) { |
| 198 | _products.add(item); | 213 | _products.add(item); |
| 199 | } | 214 | } |
| 200 | print("############"); | 215 | print("############"); |
| 201 | - print(_products); | 216 | + print("############${_products}"); |
| 202 | } | 217 | } |
| 203 | 218 | ||
| 204 | void _getPastPurchases() async { | 219 | void _getPastPurchases() async { |
| ... | @@ -206,8 +221,7 @@ class PaymentService { | ... | @@ -206,8 +221,7 @@ class PaymentService { |
| 206 | if (Platform.isIOS) { | 221 | if (Platform.isIOS) { |
| 207 | return; | 222 | return; |
| 208 | } | 223 | } |
| 209 | - List<PurchasedItem>? purchasedItems = | 224 | + List<PurchasedItem>? purchasedItems = await FlutterInappPurchase.instance.getAvailablePurchases(); |
| 210 | - await FlutterInappPurchase.instance.getAvailablePurchases(); | ||
| 211 | 225 | ||
| 212 | for (var purchasedItem in purchasedItems!) { | 226 | for (var purchasedItem in purchasedItems!) { |
| 213 | bool isValid = false; | 227 | bool isValid = false; |
| ... | @@ -236,8 +250,7 @@ class PaymentService { | ... | @@ -236,8 +250,7 @@ class PaymentService { |
| 236 | 250 | ||
| 237 | Future<void> buyProduct(IAPItem item) async { | 251 | Future<void> buyProduct(IAPItem item) async { |
| 238 | try { | 252 | try { |
| 239 | - await FlutterInappPurchase.instance | 253 | + await FlutterInappPurchase.instance.requestSubscription(item.productId.toString()); |
| 240 | - .requestSubscription(item.productId.toString()); | ||
| 241 | } catch (error) { | 254 | } catch (error) { |
| 242 | Toast.show("购买失败!"); | 255 | Toast.show("购买失败!"); |
| 243 | } | 256 | } | ... | ... |
| ... | @@ -417,6 +417,13 @@ packages: | ... | @@ -417,6 +417,13 @@ packages: |
| 417 | url: "https://pub.flutter-io.cn" | 417 | url: "https://pub.flutter-io.cn" |
| 418 | source: hosted | 418 | source: hosted |
| 419 | version: "1.1.2" | 419 | version: "1.1.2" |
| 420 | + flutter_easyloading: | ||
| 421 | + dependency: "direct main" | ||
| 422 | + description: | ||
| 423 | + name: flutter_easyloading | ||
| 424 | + url: "https://pub.flutter-io.cn" | ||
| 425 | + source: hosted | ||
| 426 | + version: "3.0.5" | ||
| 420 | flutter_facebook_auth: | 427 | flutter_facebook_auth: |
| 421 | dependency: "direct main" | 428 | dependency: "direct main" |
| 422 | description: | 429 | description: |
| ... | @@ -710,6 +717,34 @@ packages: | ... | @@ -710,6 +717,34 @@ packages: |
| 710 | url: "https://pub.flutter-io.cn" | 717 | url: "https://pub.flutter-io.cn" |
| 711 | source: hosted | 718 | source: hosted |
| 712 | version: "2.6.2" | 719 | version: "2.6.2" |
| 720 | + in_app_purchase: | ||
| 721 | + dependency: "direct main" | ||
| 722 | + description: | ||
| 723 | + name: in_app_purchase | ||
| 724 | + url: "https://pub.flutter-io.cn" | ||
| 725 | + source: hosted | ||
| 726 | + version: "3.0.8" | ||
| 727 | + in_app_purchase_android: | ||
| 728 | + dependency: transitive | ||
| 729 | + description: | ||
| 730 | + name: in_app_purchase_android | ||
| 731 | + url: "https://pub.flutter-io.cn" | ||
| 732 | + source: hosted | ||
| 733 | + version: "0.2.3+6" | ||
| 734 | + in_app_purchase_platform_interface: | ||
| 735 | + dependency: transitive | ||
| 736 | + description: | ||
| 737 | + name: in_app_purchase_platform_interface | ||
| 738 | + url: "https://pub.flutter-io.cn" | ||
| 739 | + source: hosted | ||
| 740 | + version: "1.3.2" | ||
| 741 | + in_app_purchase_storekit: | ||
| 742 | + dependency: transitive | ||
| 743 | + description: | ||
| 744 | + name: in_app_purchase_storekit | ||
| 745 | + url: "https://pub.flutter-io.cn" | ||
| 746 | + source: hosted | ||
| 747 | + version: "0.3.3" | ||
| 713 | integration_test: | 748 | integration_test: |
| 714 | dependency: "direct dev" | 749 | dependency: "direct dev" |
| 715 | description: flutter | 750 | description: flutter | ... | ... |
| ... | @@ -101,6 +101,8 @@ dependencies: | ... | @@ -101,6 +101,8 @@ dependencies: |
| 101 | 101 | ||
| 102 | # A Dart timer that can be paused, resumed and reset. | 102 | # A Dart timer that can be paused, resumed and reset. |
| 103 | pausable_timer: ^1.0.0+3 | 103 | pausable_timer: ^1.0.0+3 |
| 104 | + | ||
| 105 | + flutter_easyloading: ^3.0.0 | ||
| 104 | email_validator: ^2.0.1 | 106 | email_validator: ^2.0.1 |
| 105 | 107 | ||
| 106 | getwidget: ^2.0.5 | 108 | getwidget: ^2.0.5 |
| ... | @@ -114,7 +116,10 @@ dependencies: | ... | @@ -114,7 +116,10 @@ dependencies: |
| 114 | animated_radial_menu: | 116 | animated_radial_menu: |
| 115 | path: plugins/animated_radial | 117 | path: plugins/animated_radial |
| 116 | 118 | ||
| 119 | + # 非官方库 暂时不删 | ||
| 117 | flutter_inapp_purchase: ^5.3.0 | 120 | flutter_inapp_purchase: ^5.3.0 |
| 121 | + # Flutter官方支付支持库 | ||
| 122 | + in_app_purchase: ^3.0.8 | ||
| 118 | 123 | ||
| 119 | jpush_flutter: ^2.2.9 | 124 | jpush_flutter: ^2.2.9 |
| 120 | share_plus: ^4.0.10 | 125 | share_plus: ^4.0.10 |
| ... | @@ -125,6 +130,7 @@ dependencies: | ... | @@ -125,6 +130,7 @@ dependencies: |
| 125 | google_fonts: ^3.0.1 | 130 | google_fonts: ^3.0.1 |
| 126 | wakelock: ^0.6.1+2 | 131 | wakelock: ^0.6.1+2 |
| 127 | location: ^4.4.0 | 132 | location: ^4.4.0 |
| 133 | + # GoogleMap支持库 | ||
| 128 | google_maps_flutter: ^2.2.1 | 134 | google_maps_flutter: ^2.2.1 |
| 129 | http: ^0.13.5 | 135 | http: ^0.13.5 |
| 130 | 136 | ... | ... |
-
Please register or login to post a comment