Toggle navigation
Toggle navigation
This project
Loading...
Sign in
OnePoem
/
OnePoem-App
Go to a project
Toggle navigation
Toggle navigation pinning
Projects
Groups
Snippets
Help
Project
Activity
Repository
Pipelines
Graphs
Issues
0
Merge Requests
0
Wiki
Snippets
Network
Create a new issue
Builds
Commits
Issue Boards
Authored by
Chad
2022-11-03 17:55:05 +0800
Browse Files
Options
Browse Files
Download
Email Patches
Plain Diff
Commit
f1baba8a99942df0048e97bb18fa676646cf1ddf
f1baba8a
1 parent
5e41dc2b
替换内购插件,重新实现内购服务
Hide whitespace changes
Inline
Side-by-side
Showing
9 changed files
with
468 additions
and
211 deletions
lib/base/base_state.dart
lib/extension/widget_ext.dart
lib/main.dart
lib/membership/page/membership_page.dart
lib/net/intercept.dart
lib/payment/payment_sdk.dart
lib/payment/payment_service.dart
pubspec.lock
pubspec.yaml
lib/base/base_state.dart
0 → 100644
View file @
f1baba8
import
'package:flutter/material.dart'
;
import
'package:flutter_easyloading/flutter_easyloading.dart'
;
abstract
class
BaseState
<
T
extends
StatefulWidget
>
extends
State
<
T
>
{
bool
_isFirstBuild
=
true
;
@override
Widget
build
(
BuildContext
context
)
{
if
(
_isFirstBuild
)
{
onFirstBuildBody
(
context
);
_isFirstBuild
=
false
;
}
return
buildBody
(
context
);
}
Widget
buildBody
(
BuildContext
context
);
void
onFirstBuildBody
(
BuildContext
context
){
}
showLoading
({
String
text
=
'Loading...'
})
{
EasyLoading
.
show
(
status:
text
);
}
hideLoading
()
{
EasyLoading
.
dismiss
();
}
}
lib/extension/widget_ext.dart
View file @
f1baba8
...
...
@@ -8,4 +8,115 @@ extension WidgetExt on Widget {
SafeArea
safe
()
{
return
SafeArea
(
child:
this
);
}
ClipRRect
round
({
double
?
radius
,
BorderRadius
?
borderRadius
})
{
return
ClipRRect
(
borderRadius:
borderRadius
??
BorderRadius
.
all
(
Radius
.
circular
(
radius
??
5
)),
child:
this
,
);
}
Container
height
(
double
height
,
{
Alignment
?
alignment
})
{
return
Container
(
height:
height
,
alignment:
alignment
,
child:
this
);
}
Container
height48
({
Alignment
?
alignment
})
{
return
Container
(
height:
48
,
alignment:
alignment
,
child:
this
);
}
Container
paddingALL
(
double
padding
)
{
return
Container
(
child:
this
,
padding:
EdgeInsets
.
all
(
padding
));
}
Container
paddingTopBottom
(
double
padding
)
{
return
Container
(
child:
this
,
padding:
EdgeInsets
.
only
(
bottom:
padding
,
top:
padding
));
}
Container
paddingBottom
(
double
padding
)
{
return
Container
(
child:
this
,
padding:
EdgeInsets
.
only
(
bottom:
padding
));
}
Container
paddingTop
(
double
padding
)
{
return
Container
(
child:
this
,
padding:
EdgeInsets
.
only
(
top:
padding
));
}
Widget
paddingLeftRight
(
double
padding
)
{
return
Container
(
child:
this
,
padding:
EdgeInsets
.
only
(
left:
padding
,
right:
padding
));
}
Container
paddingLeft
(
double
padding
)
{
return
Container
(
child:
this
,
padding:
EdgeInsets
.
only
(
left:
padding
));
}
Container
paddingRight
(
double
padding
)
{
return
Container
(
child:
this
,
padding:
EdgeInsets
.
only
(
right:
padding
));
}
Row
rowRight
()
{
return
Row
(
mainAxisAlignment:
MainAxisAlignment
.
end
,
children:
[
this
]);
}
Row
rowLeft
()
{
return
Row
(
mainAxisAlignment:
MainAxisAlignment
.
start
,
children:
[
this
]);
}
Row
rowCenter
()
{
return
Row
(
mainAxisAlignment:
MainAxisAlignment
.
center
,
children:
[
this
]);
}
Widget
click
(
GestureTapCallback
?
onTap
,
{
Color
?
color
,
double
?
radius
,
BorderRadius
?
borderRadius
,
Color
?
bgColor
,
})
{
if
(
color
!=
null
)
{
var
border
=
radius
==
null
?
null
:
BorderRadius
.
all
(
Radius
.
circular
(
radius
));
return
getMaterialInkWell
(
this
,
onTap
,
color
,
borderRadius:
borderRadius
??
border
,
bgColor:
bgColor
,
);
}
return
getWhiteInkWell
(
this
,
onTap
);
}
///当InkWell效果失效时,使用这个套件
Widget
getMaterialInkWell
(
Widget
widget
,
GestureTapCallback
?
onTap
,
Color
?
color
,
{
BorderRadius
?
borderRadius
,
Color
?
splashColor
,
Color
?
bgColor
=
Colors
.
white
,
})
{
return
Material
(
color:
bgColor
,
child:
Ink
(
decoration:
BoxDecoration
(
color:
color
,
borderRadius:
borderRadius
,
),
child:
InkWell
(
onTap:
onTap
,
splashColor:
splashColor
,
borderRadius:
borderRadius
,
child:
widget
,
),
),
);
}
///当InkWell效果失效时,使用这个套件
Widget
getWhiteInkWell
(
Widget
widget
,
GestureTapCallback
?
onTap
,
{
BorderRadius
?
borderRadius
})
{
return
Material
(
child:
Ink
(
color:
Colors
.
white
,
child:
InkWell
(
onTap:
onTap
,
child:
widget
),
),
);
}
}
...
...
lib/main.dart
View file @
f1baba8
...
...
@@ -6,6 +6,7 @@ import 'package:dio/dio.dart';
import
'package:flustars/flustars.dart'
;
import
'package:flutter/material.dart'
;
import
'package:flutter/services.dart'
;
import
'package:flutter_easyloading/flutter_easyloading.dart'
;
import
'package:oktoast/oktoast.dart'
;
import
'package:provider/provider.dart'
;
import
'package:quick_actions/quick_actions.dart'
;
...
...
@@ -60,8 +61,7 @@ Future<void> main() async {
await
SpUtil
.
getInstance
();
WidgetsFlutterBinding
.
ensureInitialized
();
SystemChrome
.
setPreferredOrientations
(
[
DeviceOrientation
.
portraitUp
,
DeviceOrientation
.
portraitDown
]);
SystemChrome
.
setPreferredOrientations
([
DeviceOrientation
.
portraitUp
,
DeviceOrientation
.
portraitDown
]);
/// 1.22 预览功能: 在输入频率与显示刷新率不匹配情况下提供平滑的滚动效果
// GestureBinding.instance?.resamplingEnabled = true;
...
...
@@ -69,8 +69,7 @@ Future<void> main() async {
handleError
(()
=>
runApp
(
MyApp
()));
/// 隐藏状态栏。为启动页、引导页设置。完成后修改回显示状态栏。
SystemChrome
.
setEnabledSystemUIMode
(
SystemUiMode
.
manual
,
overlays:
[
SystemUiOverlay
.
bottom
]);
SystemChrome
.
setEnabledSystemUIMode
(
SystemUiMode
.
manual
,
overlays:
[
SystemUiOverlay
.
bottom
]);
// TODO(weilu): 启动体验不佳。状态栏、导航栏在冷启动开始的一瞬间为黑色,且无法通过隐藏、修改颜色等方式进行处理。。。
// 相关问题跟踪:https://github.com/flutter/flutter/issues/73351
if
(
Platform
.
isAndroid
)
{
...
...
@@ -133,8 +132,7 @@ class MyApp extends StatelessWidget {
}
quickActions
.
setShortcutItems
(<
ShortcutItem
>[
const
ShortcutItem
(
type:
'demo'
,
localizedTitle:
'发一言'
,
icon:
'flutter_dash_black'
),
const
ShortcutItem
(
type:
'demo'
,
localizedTitle:
'发一言'
,
icon:
'flutter_dash_black'
),
]);
}
}
...
...
@@ -149,8 +147,7 @@ class MyApp extends StatelessWidget {
ChangeNotifierProvider
(
create:
(
_
)
=>
MembershipViewProvider
())
],
child:
Consumer2
<
ThemeProvider
,
LocaleProvider
>(
builder:
(
_
,
ThemeProvider
provider
,
LocaleProvider
localeProvider
,
__
)
{
builder:
(
_
,
ThemeProvider
provider
,
LocaleProvider
localeProvider
,
__
)
{
return
_buildMaterialApp
(
provider
,
localeProvider
);
},
),
...
...
@@ -159,15 +156,13 @@ class MyApp extends StatelessWidget {
/// Toast 配置
return
OKToast
(
backgroundColor:
Colors
.
black54
,
textPadding:
const
EdgeInsets
.
symmetric
(
horizontal:
16.0
,
vertical:
10.0
),
textPadding:
const
EdgeInsets
.
symmetric
(
horizontal:
16.0
,
vertical:
10.0
),
radius:
20.0
,
position:
ToastPosition
.
bottom
,
child:
app
);
}
Widget
_buildMaterialApp
(
ThemeProvider
provider
,
LocaleProvider
localeProvider
)
{
Widget
_buildMaterialApp
(
ThemeProvider
provider
,
LocaleProvider
localeProvider
)
{
return
MaterialApp
(
title:
'一言'
,
// showPerformanceOverlay: true, //显示性能标签
...
...
@@ -185,7 +180,7 @@ class MyApp extends StatelessWidget {
supportedLocales:
ParlandoLocalizations
.
supportedLocales
,
locale:
localeProvider
.
locale
,
navigatorKey:
navigatorKey
,
builder:
(
BuildContext
context
,
Widget
?
child
)
{
builder:
EasyLoading
.
init
(
builder:
(
context
,
child
)
{
/// 仅针对安卓
if
(
Device
.
isAndroid
)
{
/// 切换深色模式会触发此方法,这里设置导航栏颜色
...
...
@@ -197,7 +192,7 @@ class MyApp extends StatelessWidget {
data:
MediaQuery
.
of
(
context
).
copyWith
(
textScaleFactor:
1.0
),
child:
child
!,
);
},
}
)
,
/// 因为使用了fluro,这里设置主要针对Web
onUnknownRoute:
(
_
)
{
...
...
@@ -236,4 +231,4 @@ Future<bool> requestLocationPermission() async {
return
false
;
}
}
}
\ No newline at end of file
}
...
...
lib/membership/page/membership_page.dart
View file @
f1baba8
...
...
@@ -2,11 +2,15 @@ import 'dart:async';
import
'dart:ui'
;
import
'package:Parlando/apis/api_response.dart'
;
import
'package:Parlando/base/base_state.dart'
;
import
'package:Parlando/extension/widget_ext.dart'
;
import
'package:Parlando/login/login_router.dart'
;
import
'package:Parlando/membership/models/membership_entity.dart'
;
import
'package:Parlando/membership/view_models/membership_view_model.dart'
;
import
'package:Parlando/payment/payment_sdk.dart'
;
import
'package:Parlando/payment/payment_service.dart'
;
import
'package:Parlando/res/constant.dart'
;
import
'package:Parlando/widgets/my_button.dart'
;
import
'package:cached_network_image/cached_network_image.dart'
;
import
'package:flustars/flustars.dart'
;
import
'package:flutter/material.dart'
;
...
...
@@ -15,6 +19,7 @@ import 'package:Parlando/routers/fluro_navigator.dart';
import
'package:Parlando/extension/int_extension.dart'
;
import
'package:flutter_inapp_purchase/flutter_inapp_purchase.dart'
;
import
'package:getwidget/getwidget.dart'
;
import
'package:in_app_purchase_platform_interface/src/types/product_details.dart'
;
import
'package:provider/provider.dart'
;
class
MembershipPage
extends
StatefulWidget
{
...
...
@@ -24,202 +29,166 @@ class MembershipPage extends StatefulWidget {
MembershipPageState
createState
()
=>
MembershipPageState
();
}
class
MembershipPageState
extends
State
<
MembershipPage
>
with
WidgetsBindingObserver
{
bool
_isLoading
=
false
;
class
MembershipPageState
extends
BaseState
<
MembershipPage
>
with
WidgetsBindingObserver
{
final
List
<
Widget
>
_productWidgets
=
<
Widget
>[];
Function
?
connectionCallBack
;
ApiResponse
?
apiResponse
;
@override
void
initState
()
{
super
.
initState
();
PaymentSdk
.
instance
.
initState
();
PaymentSdk
.
instance
.
queryProducts
().
then
((
value
)
{
for
(
var
element
in
value
)
{
_productWidgets
.
add
(
buildBuyItem
(
element
));
}
setState
(()
{});
});
if
(
SpUtil
.
containsKey
(
Constant
.
userToken
)!)
{
Provider
.
of
<
MembershipViewProvider
>(
context
,
listen:
false
)
.
setSelectedMembership
(
null
);
Provider
.
of
<
MembershipViewProvider
>(
context
,
listen:
false
)
.
fetchMembershipData
(
'0'
);
PaymentService
.
instance
.
initConnection
();
Provider
.
of
<
MembershipViewProvider
>(
context
,
listen:
false
).
setSelectedMembership
(
null
);
Provider
.
of
<
MembershipViewProvider
>(
context
,
listen:
false
).
fetchMembershipData
(
'0'
);
}
else
{
NavigatorUtils
.
push
(
context
,
LoginRouter
.
loginPage
,
replace:
true
);
}
}
@override
Widget
build
(
BuildContext
context
)
{
ApiResponse
apiResponse
=
Provider
.
of
<
MembershipViewProvider
>(
context
).
response
;
switch
(
apiResponse
.
status
)
{
case
Status
.
LOADING
:
return
const
Center
(
child:
CircularProgressIndicator
());
case
Status
.
COMPLETED
:
MembershipData
mb
=
apiResponse
.
data
as
MembershipData
;
return
SafeArea
(
child:
Scaffold
(
backgroundColor:
Colors
.
black
,
body:
Container
(
alignment:
Alignment
.
topCenter
,
decoration:
BoxDecoration
(
image:
DecorationImage
(
image:
CachedNetworkImageProvider
(
mb
.
bgImages
!),
fit:
BoxFit
.
fill
,
),
),
child:
Column
(
crossAxisAlignment:
CrossAxisAlignment
.
center
,
children:
[
Container
(
alignment:
Alignment
.
centerLeft
,
child:
IconButton
(
onPressed:
()
{
NavigatorUtils
.
goBack
(
context
);
},
icon:
const
Icon
(
Icons
.
arrow_back_ios
,
color:
Colors
.
white
,
),
),
),
const
Spacer
(),
Container
(
margin:
EdgeInsets
.
symmetric
(
vertical:
60
.
px
,
horizontal:
20
.
px
),
height:
MediaQuery
.
of
(
context
).
size
.
height
/
2
,
width:
double
.
infinity
,
decoration:
BoxDecoration
(
color:
Colors
.
grey
.
shade200
.
withOpacity
(
0.1
),
border:
Border
.
all
(
color:
Colors
.
grey
.
shade50
,
width:
0.5
,
),
// 边色与边宽度
),
child:
ClipRect
(
child:
BackdropFilter
(
filter:
ImageFilter
.
blur
(
sigmaX:
10.0
,
sigmaY:
10.0
,
),
child:
Container
(
decoration:
BoxDecoration
(
color:
Colors
.
grey
.
shade200
.
withOpacity
(
0.1
),
),
child:
Padding
(
padding:
EdgeInsets
.
all
(
10
.
px
),
child:
Column
(
crossAxisAlignment:
CrossAxisAlignment
.
center
,
children:
[
Text
(
mb
.
title
!,
style:
TextStyle
(
fontSize:
18
.
px
,
color:
Colors
.
white
,
),
),
Gaps
.
vGap24
,
// TODO 如果会员则显示会员详情
SizedBox
(
width:
double
.
infinity
,
height:
100
,
child:
_productWidgets
.
isEmpty
?
Column
(
children:
_productWidgets
,
)
:
const
GFLoader
(),
),
Gaps
.
vGap24
,
Text
(
mb
.
intro
!,
style:
TextStyle
(
fontSize:
14
.
px
,
color:
Colors
.
white
,
),
),
Gaps
.
vGap10
,
Row
(
mainAxisAlignment:
MainAxisAlignment
.
spaceBetween
,
mainAxisSize:
MainAxisSize
.
min
,
crossAxisAlignment:
CrossAxisAlignment
.
center
,
children:
[
TextButton
(
onPressed:
()
{},
child:
Text
(
"服务协议"
,
style:
TextStyle
(
fontSize:
14
.
px
,
color:
Colors
.
white
,
),
),
),
Container
(
width:
0.6
,
height:
15.0
,
color:
Colours
.
line
,
),
TextButton
(
onPressed:
()
{},
child:
Text
(
"隐私政策"
,
style:
TextStyle
(
fontSize:
14
.
px
,
color:
Colors
.
white
,
),
),
),
Container
(
width:
0.6
,
height:
15.0
,
color:
Colours
.
line
,
),
TextButton
(
onPressed:
()
{},
child:
Text
(
"恢复购买"
,
style:
TextStyle
(
fontSize:
14
.
px
,
color:
Colors
.
white
,
),
),
),
],
),
],
),
),
),
),
),
),
],
),
),
),
);
case
Status
.
ERROR
:
return
Center
(
child:
Text
(
'暂时无法获取数据,请稍候再试!
${apiResponse.message}
'
),
);
case
Status
.
INITIAL
:
default
:
return
const
Center
(
child:
Text
(
'正在获取数据....'
),
);
}
void
onFirstBuildBody
(
BuildContext
context
)
{
super
.
onFirstBuildBody
(
context
);
showLoading
();
}
@override
void
didChangeAppLifecycleState
(
AppLifecycleState
state
)
{}
Widget
buildBody
(
BuildContext
context
)
{
apiResponse
=
Provider
.
of
<
MembershipViewProvider
>(
context
).
response
;
if
(
apiResponse
?.
status
!=
Status
.
LOADING
)
{
hideLoading
();
}
var
url
=
"https://api.parlando.ink/storage/images/274d864a62d277b2a29c4db39f92d591.png"
;
return
Scaffold
(
body:
Container
(
decoration:
BoxDecoration
(
image:
DecorationImage
(
image:
CachedNetworkImageProvider
(
url
),
fit:
BoxFit
.
fill
),
),
child:
Column
(
children:
[
initBackBar
(),
Spacer
(),
// buildCompleteWidget(),
],
),
),
);
}
Widget
buildLoading
()
{
return
const
Center
(
child:
CircularProgressIndicator
());
}
Widget
buildCompleteWidget
()
{
MembershipData
?
mb
=
apiResponse
?.
data
;
return
Container
(
height:
MediaQuery
.
of
(
context
).
size
.
height
/
2
,
decoration:
BoxDecoration
(
color:
Colors
.
grey
.
shade200
.
withOpacity
(
0.1
),
border:
Border
.
all
(
color:
Colors
.
grey
.
shade50
,
width:
0.5
),
// 边色与边宽度
),
margin:
const
EdgeInsets
.
all
(
20
),
child:
ClipRect
(
child:
BackdropFilter
(
filter:
ImageFilter
.
blur
(
sigmaX:
10.0
,
sigmaY:
10.0
),
child:
Container
(
decoration:
BoxDecoration
(
color:
Colors
.
grey
.
shade200
.
withOpacity
(
0.1
)),
child:
Padding
(
padding:
EdgeInsets
.
all
(
10
.
px
),
child:
buildMemberContent
(
mb
),
),
),
),
),
);
}
@override
void
dispose
()
{
PaymentS
ervice
.
instance
.
dispose
();
PaymentS
dk
.
instance
.
dispose
();
super
.
dispose
();
}
void
_renderInApps
()
async
{
List
<
IAPItem
>
items
=
await
PaymentService
.
instance
.
products
;
for
(
IAPItem
item
in
items
)
{
_productWidgets
.
add
(
Text
(
item
.
title
!));
}
setState
(()
{});
Widget
buildBuyItem
(
ProductDetails
element
)
{
var
style
=
const
TextStyle
(
color:
Colors
.
white
);
var
button
=
Text
(
"购买"
,
style:
style
).
paddingLeftRight
(
15
).
paddingTopBottom
(
8
).
click
(()
{
PaymentSdk
.
instance
.
buy
(
element
);
},
color:
Colors
.
blue
).
round
(
radius:
10
);
return
Row
(
mainAxisAlignment:
MainAxisAlignment
.
end
,
children:
[
Text
(
element
.
price
,
style:
style
),
button
,
],
).
paddingLeftRight
(
10
).
paddingTopBottom
(
5
);
}
Widget
initOtherEntrance
()
{
return
Row
(
mainAxisAlignment:
MainAxisAlignment
.
spaceBetween
,
mainAxisSize:
MainAxisSize
.
min
,
crossAxisAlignment:
CrossAxisAlignment
.
center
,
children:
[
TextButton
(
onPressed:
()
{},
child:
Text
(
"服务协议"
,
style:
TextStyle
(
fontSize:
14
.
px
,
color:
Colors
.
white
),
),
),
Container
(
width:
0.6
,
height:
15.0
,
color:
Colours
.
line
),
TextButton
(
onPressed:
()
{},
child:
Text
(
"隐私政策"
,
style:
TextStyle
(
fontSize:
14
.
px
,
color:
Colors
.
white
),
),
),
Container
(
width:
0.6
,
height:
15.0
,
color:
Colours
.
line
),
TextButton
(
onPressed:
()
{},
child:
Text
(
"恢复购买"
,
style:
TextStyle
(
fontSize:
14
.
px
,
color:
Colors
.
white
),
),
),
],
);
}
buildMemberContent
(
MembershipData
?
mb
)
{
return
Column
(
crossAxisAlignment:
CrossAxisAlignment
.
center
,
children:
[
Text
(
mb
?.
title
??
"一言会员"
,
style:
TextStyle
(
fontSize:
18
.
px
,
color:
Colors
.
white
)),
Gaps
.
vGap24
,
_productWidgets
.
isNotEmpty
?
Column
(
children:
_productWidgets
)
:
const
GFLoader
(),
Gaps
.
vGap24
,
Text
(
mb
?.
intro
??
"一言介绍"
,
style:
TextStyle
(
fontSize:
14
.
px
,
color:
Colors
.
white
)),
Gaps
.
vGap10
,
initOtherEntrance
(),
],
);
}
initBackBar
()
{
return
Container
(
alignment:
Alignment
.
centerLeft
,
child:
IconButton
(
onPressed:
()
{
NavigatorUtils
.
goBack
(
context
);
},
icon:
const
Icon
(
Icons
.
arrow_back_ios
,
color:
Colors
.
white
),
),
);
}
}
...
...
lib/net/intercept.dart
View file @
f1baba8
...
...
@@ -122,7 +122,7 @@ class LoggingInterceptor extends Interceptor {
Log
.
e
(
'ResponseCode:
${response.statusCode}
'
);
}
// 输出结果
Log
.
json
(
response
.
data
.
toString
());
//
Log.json(response.data.toString());
Log
.
d
(
'----------End:
$duration
毫秒----------'
);
super
.
onResponse
(
response
,
handler
);
}
...
...
lib/payment/payment_sdk.dart
0 → 100644
View file @
f1baba8
import
'dart:async'
;
import
'package:flutter/material.dart'
;
import
'package:in_app_purchase/in_app_purchase.dart'
;
class
PaymentSdk
{
PaymentSdk
.
_privateConstructor
();
static
final
PaymentSdk
_instance
=
PaymentSdk
.
_privateConstructor
();
static
PaymentSdk
get
instance
{
return
_instance
;
}
static
const
Set
<
String
>
_kIds
=
<
String
>{
'yearly_yiyan_vip'
,
'monthly_yiyan_vip'
,
'yearly-default'
};
StreamSubscription
<
List
<
PurchaseDetails
>>?
_subscription
;
List
<
ProductDetails
>
products
=
[];
initState
()
{
final
Stream
<
List
<
PurchaseDetails
>>
purchaseUpdated
=
InAppPurchase
.
instance
.
purchaseStream
;
_subscription
=
purchaseUpdated
.
listen
((
purchaseDetailsList
)
{
_listenToPurchaseUpdated
(
purchaseDetailsList
);
},
onDone:
()
{
_subscription
?.
cancel
();
},
onError:
(
error
)
{
// handle error here.
});
}
Future
<
List
<
ProductDetails
>>
queryProducts
()
async
{
final
bool
available
=
await
InAppPurchase
.
instance
.
isAvailable
();
if
(!
available
)
{
print
(
"####### isAvailable false"
);
return
[];
}
final
ProductDetailsResponse
response
=
await
InAppPurchase
.
instance
.
queryProductDetails
({
'yearly_yiyan_vip'
,
'monthly_yiyan_vip'
,
'yearly-default'
,
'test.yiyan.vip.1.month'
,
});
if
(
response
.
notFoundIDs
.
isNotEmpty
)
{
// Handle the error.
print
(
"####### notFoundIDs"
);
}
else
{
}
print
(
response
.
productDetails
.
length
);
return
products
=
response
.
productDetails
;
}
buy
(
ProductDetails
details
)
{
final
PurchaseParam
purchaseParam
=
PurchaseParam
(
productDetails:
details
);
if
(
_isConsumable
(
details
))
{
InAppPurchase
.
instance
.
buyConsumable
(
purchaseParam:
purchaseParam
);
}
else
{
InAppPurchase
.
instance
.
buyNonConsumable
(
purchaseParam:
purchaseParam
);
}
}
void
_listenToPurchaseUpdated
(
List
<
PurchaseDetails
>
purchaseDetailsList
)
async
{
for
(
var
purchaseDetails
in
purchaseDetailsList
)
{
if
(
purchaseDetails
.
status
==
PurchaseStatus
.
pending
)
{
// _showPendingUI();
}
else
{
if
(
purchaseDetails
.
status
==
PurchaseStatus
.
error
)
{
// _handleError(purchaseDetails.error!);
}
else
if
(
purchaseDetails
.
status
==
PurchaseStatus
.
purchased
||
purchaseDetails
.
status
==
PurchaseStatus
.
restored
)
{
bool
valid
=
await
_verifyPurchase
(
purchaseDetails
);
if
(
valid
)
{
_deliverProduct
(
purchaseDetails
);
}
else
{
_handleInvalidPurchase
(
purchaseDetails
);
}
}
if
(
purchaseDetails
.
pendingCompletePurchase
)
{
await
InAppPurchase
.
instance
.
completePurchase
(
purchaseDetails
);
}
}
}
}
_verifyPurchase
(
PurchaseDetails
purchaseDetails
)
async
{
return
true
;
}
void
_deliverProduct
(
PurchaseDetails
purchaseDetails
)
{}
void
_handleInvalidPurchase
(
PurchaseDetails
purchaseDetails
)
{}
bool
_isConsumable
(
ProductDetails
details
)
{
return
true
;
}
void
dispose
()
{
_subscription
?.
cancel
();
}
}
lib/payment/payment_service.dart
View file @
f1baba8
...
...
@@ -26,7 +26,7 @@ class PaymentService {
late
StreamSubscription
<
PurchaseResult
?>
_purchaseErrorSubscription
;
/// List of product ids you want to fetch
final
List
<
String
>
_productIds
=
[
'
test.yiyan.vip.1.month
'
];
final
List
<
String
>
_productIds
=
[
'
yearly_yiyan_vip'
,
'monthly_yiyan_vip
'
];
/// All available products will be store in this list
late
List
<
IAPItem
>
_products
;
...
...
@@ -36,12 +36,12 @@ class PaymentService {
/// view of the app will subscribe to this to get notified
/// when premium status of the user changes
final
ObserverList
<
Function
>
_proStatusChangedListeners
=
ObserverList
<
Function
>();
final
ObserverList
<
Function
>
_proStatusChangedListeners
=
ObserverList
<
Function
>();
/// view of the app will subscribe to this to get errors of the purchase
final
ObserverList
<
Function
(
String
)>
_errorListeners
=
ObserverList
<
Function
(
String
)>();
final
ObserverList
<
Function
(
String
)>
_errorListeners
=
ObserverList
<
Function
(
String
)>();
final
ObserverList
<
Function
>
_connectListeners
=
ObserverList
<
Function
>();
/// logged in user's premium status
bool
_isProUser
=
false
;
...
...
@@ -68,6 +68,20 @@ class PaymentService {
_errorListeners
.
remove
(
callback
);
}
addConnectListener
(
Function
?
callback
)
{
if
(
callback
==
null
)
{
return
;
}
_connectListeners
.
add
(
callback
);
}
removeConnectListener
(
Function
?
callback
)
{
if
(
callback
==
null
)
{
return
;
}
_connectListeners
.
remove
(
callback
);
}
/// Call this method to notify all the subsctibers of _proStatusChangedListeners
void
_callProStatusChangedListeners
()
{
for
(
var
callback
in
_proStatusChangedListeners
)
{
...
...
@@ -84,18 +98,19 @@ class PaymentService {
/// Call this method at the startup of you app to initialize connection
/// with billing server and get all the necessary data
void
initConnection
()
{
var
result
=
FlutterInappPurchase
.
instance
.
initialize
();
void
initConnection
()
async
{
var
result
=
await
FlutterInappPurchase
.
instance
.
initialize
();
print
(
"___________________________"
);
print
(
"result:
$result
"
);
_connectionSubscription
=
FlutterInappPurchase
.
connectionUpdated
.
listen
((
connected
)
{});
_connectionSubscription
=
FlutterInappPurchase
.
connectionUpdated
.
listen
((
connected
)
{
for
(
var
value
in
_connectListeners
)
{
value
.
call
();
}
});
_purchaseUpdatedSubscription
=
FlutterInappPurchase
.
purchaseUpdated
.
listen
(
_handlePurchaseUpdate
);
_purchaseUpdatedSubscription
=
FlutterInappPurchase
.
purchaseUpdated
.
listen
(
_handlePurchaseUpdate
);
_purchaseErrorSubscription
=
FlutterInappPurchase
.
purchaseError
.
listen
(
_handlePurchaseError
);
_purchaseErrorSubscription
=
FlutterInappPurchase
.
purchaseError
.
listen
(
_handlePurchaseError
);
_getItems
();
_getPastPurchases
();
...
...
@@ -191,14 +206,14 @@ class PaymentService {
}
Future
<
void
>
_getItems
()
async
{
List
<
IAPItem
>
items
=
await
FlutterInappPurchase
.
instance
.
getSubscriptions
(
_productIds
);
List
<
IAPItem
>
items
=
await
FlutterInappPurchase
.
instance
.
getSubscriptions
(
_productIds
);
print
(
"############
${items.length}
"
);
_products
=
[];
for
(
var
item
in
items
)
{
_products
.
add
(
item
);
}
print
(
"############"
);
print
(
_products
);
print
(
"############
${_products}
"
);
}
void
_getPastPurchases
()
async
{
...
...
@@ -206,8 +221,7 @@ class PaymentService {
if
(
Platform
.
isIOS
)
{
return
;
}
List
<
PurchasedItem
>?
purchasedItems
=
await
FlutterInappPurchase
.
instance
.
getAvailablePurchases
();
List
<
PurchasedItem
>?
purchasedItems
=
await
FlutterInappPurchase
.
instance
.
getAvailablePurchases
();
for
(
var
purchasedItem
in
purchasedItems
!)
{
bool
isValid
=
false
;
...
...
@@ -236,8 +250,7 @@ class PaymentService {
Future
<
void
>
buyProduct
(
IAPItem
item
)
async
{
try
{
await
FlutterInappPurchase
.
instance
.
requestSubscription
(
item
.
productId
.
toString
());
await
FlutterInappPurchase
.
instance
.
requestSubscription
(
item
.
productId
.
toString
());
}
catch
(
error
)
{
Toast
.
show
(
"购买失败!"
);
}
...
...
pubspec.lock
View file @
f1baba8
...
...
@@ -417,6 +417,13 @@ packages:
url: "https://pub.flutter-io.cn"
source: hosted
version: "1.1.2"
flutter_easyloading:
dependency: "direct main"
description:
name: flutter_easyloading
url: "https://pub.flutter-io.cn"
source: hosted
version: "3.0.5"
flutter_facebook_auth:
dependency: "direct main"
description:
...
...
@@ -710,6 +717,34 @@ packages:
url: "https://pub.flutter-io.cn"
source: hosted
version: "2.6.2"
in_app_purchase:
dependency: "direct main"
description:
name: in_app_purchase
url: "https://pub.flutter-io.cn"
source: hosted
version: "3.0.8"
in_app_purchase_android:
dependency: transitive
description:
name: in_app_purchase_android
url: "https://pub.flutter-io.cn"
source: hosted
version: "0.2.3+6"
in_app_purchase_platform_interface:
dependency: transitive
description:
name: in_app_purchase_platform_interface
url: "https://pub.flutter-io.cn"
source: hosted
version: "1.3.2"
in_app_purchase_storekit:
dependency: transitive
description:
name: in_app_purchase_storekit
url: "https://pub.flutter-io.cn"
source: hosted
version: "0.3.3"
integration_test:
dependency: "direct dev"
description: flutter
...
...
pubspec.yaml
View file @
f1baba8
...
...
@@ -101,6 +101,8 @@ dependencies:
# A Dart timer that can be paused, resumed and reset.
pausable_timer
:
^1.0.0+3
flutter_easyloading
:
^3.0.0
email_validator
:
^2.0.1
getwidget
:
^2.0.5
...
...
@@ -114,7 +116,10 @@ dependencies:
animated_radial_menu
:
path
:
plugins/animated_radial
# 非官方库 暂时不删
flutter_inapp_purchase
:
^5.3.0
# Flutter官方支付支持库
in_app_purchase
:
^3.0.8
jpush_flutter
:
^2.2.9
share_plus
:
^4.0.10
...
...
@@ -125,6 +130,7 @@ dependencies:
google_fonts
:
^3.0.1
wakelock
:
^0.6.1+2
location
:
^4.4.0
# GoogleMap支持库
google_maps_flutter
:
^2.2.1
http
:
^0.13.5
...
...
Please
register
or
login
to post a comment