Compare commits
145 Commits
gong_dev_a
...
新改版_poyo_0
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7433369c1a | ||
|
|
6ab70b4fcb | ||
|
|
9817fb1ff7 | ||
|
|
54eb9ea099 | ||
|
|
98cb22a706 | ||
|
|
767bc41820 | ||
|
|
ef99178d33 | ||
|
|
24fa740e05 | ||
|
|
d662d5c3ea | ||
|
|
b9a6d9b966 | ||
|
|
0586360408 | ||
|
|
9d8bef398a | ||
|
|
602417f45d | ||
|
|
86902a8f9b | ||
|
|
7cb89035d8 | ||
|
|
f39f2b483e | ||
|
|
15f19e39a7 | ||
|
|
10d1d22b8a | ||
|
|
96b302f0ee | ||
|
|
99d8df2ec3 | ||
|
|
4e42a4b04c | ||
|
|
c582376cc4 | ||
|
|
e58ce7932f | ||
|
|
9a72e54ca3 | ||
|
|
6de4523d27 | ||
|
|
40966e0b33 | ||
|
|
935a5a3ec5 | ||
|
|
34aaa61f11 | ||
|
|
5373f6b5bc | ||
|
|
c84fc9e8f1 | ||
|
|
b68b334e00 | ||
|
|
bfb172c1e8 | ||
|
|
277684b8e0 | ||
|
|
d181e0e743 | ||
|
|
361d23b9ef | ||
|
|
745702e36c | ||
|
|
0b62d4ddc4 | ||
|
|
23d3c9a915 | ||
|
|
8a23e76788 | ||
|
|
6f2de3a4d7 | ||
|
|
68f444b3fc | ||
|
|
f616fb1192 | ||
|
|
cd4d0c13c1 | ||
|
|
a8f92a1e38 | ||
|
|
a1b59733b3 | ||
|
|
f1670965b9 | ||
|
|
1985eebfb1 | ||
|
|
d299e579d6 | ||
|
|
02ee96e369 | ||
|
|
ed595ec1f4 | ||
|
|
5dc2b15bd8 | ||
|
|
08bc59f80e | ||
|
|
25a926e507 | ||
|
|
abfb8e74be | ||
|
|
6e0e195c20 | ||
|
|
0854d931d4 | ||
|
|
98a0009019 | ||
|
|
b9503b91f5 | ||
|
|
dee916ea7d | ||
|
|
2e1eb3c7b4 | ||
|
|
2102a3bfc2 | ||
|
|
1576a75c08 | ||
|
|
89e4373d81 | ||
|
|
59271ce709 | ||
| 528647a0bc | |||
| 238dfcc2d3 | |||
| 61b1101c3f | |||
| daf881f6a7 | |||
| 38f3b721e0 | |||
| 04ae7eeacc | |||
| d863c0af5a | |||
| 99c1037a15 | |||
|
|
3440b71229 | ||
| 50686957dc | |||
|
|
214e6f5d89 | ||
| 2d9e1cd685 | |||
| 60485deed5 | |||
| 7196ebd16e | |||
| 6eee7b9ede | |||
| e0405e9c13 | |||
| e8e0fc32f9 | |||
| 3d4ad99c99 | |||
| a25e22b142 | |||
| 3aeabfa32b | |||
| 47da21351e | |||
| 451a875526 | |||
| dc7b987eda | |||
| fe28d3508b | |||
|
|
cb87974320 | ||
|
|
dbd684a6e2 | ||
| abc37aa486 | |||
| 3349b2d7df | |||
| 97d636ddec | |||
| 3b8aedaa17 | |||
| 40ba4b8aa8 | |||
| f7db0b0768 | |||
| ed642f0137 | |||
| d1512bc256 | |||
| 9f90040168 | |||
| 9ae6fedd8d | |||
| 516a068c25 | |||
| 63b7a18c0b | |||
|
|
6af48002bb | ||
| 79c730c1b7 | |||
| c20a9804e9 | |||
|
|
96a6b05d03 | ||
| ba8090eec7 | |||
| e1fd4949b5 | |||
|
|
08c46a7684 | ||
| 1cde41f2d0 | |||
| 092d4cb914 | |||
| 21ae621343 | |||
| 048c66736f | |||
| d3055d8fb2 | |||
|
|
b9164b6a08 | ||
|
|
67e5e4e02f | ||
|
|
2351618e5a | ||
|
|
b2e2ca7303 | ||
| 714e51b621 | |||
| efa4c25c4b | |||
|
|
854cad8ec6 | ||
| 46fba9429d | |||
| c67ed4b736 | |||
|
|
4a91aafb4b | ||
|
|
2b20782def | ||
|
|
152848a04f | ||
|
|
fe827482c9 | ||
| 7fc1c2e712 | |||
| 7a6dfe5a3d | |||
|
|
96c45820b0 | ||
| ac18150503 | |||
|
|
22b208bcd9 | ||
|
|
484069dac7 | ||
| ade837e85c | |||
| 12a1f24101 | |||
| 250d4832a3 | |||
|
|
93db808f8b | ||
|
|
59f0fa4acb | ||
| 8fe6130c81 | |||
| 9292e6f5c5 | |||
| 845b217c4d | |||
|
|
7edd75e223 | ||
|
|
eb9f615f70 | ||
| 9c38f40098 | |||
| 8cae89e7cd |
@@ -334,4 +334,7 @@
|
|||||||
<string name="home_function_name_fine_sticker">Exquisite sticker</string>
|
<string name="home_function_name_fine_sticker">Exquisite sticker</string>
|
||||||
<string name="dialog_reset">Reset</string>
|
<string name="dialog_reset">Reset</string>
|
||||||
<string name="menu_diy">Custom</string>
|
<string name="menu_diy">Custom</string>
|
||||||
|
|
||||||
|
<string name="toast_not_detect_face">No face tracking</string>
|
||||||
|
<string name="toast_not_detect_face_or_body">No face or body tracking</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|||||||
@@ -31,4 +31,6 @@ repositories {
|
|||||||
}
|
}
|
||||||
dependencies {
|
dependencies {
|
||||||
implementation fileTree(dir: 'libs', include: ['*.jar', '*.aar'])
|
implementation fileTree(dir: 'libs', include: ['*.jar', '*.aar'])
|
||||||
|
implementation 'com.google.code.gson:gson:2.8.6'
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,134 @@
|
|||||||
|
package com.samsung.android.sdk.iap.lib;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.util.Log;
|
||||||
|
|
||||||
|
import com.google.gson.Gson;
|
||||||
|
import com.samsung.android.sdk.iap.lib.constants.HelperDefine;
|
||||||
|
import com.samsung.android.sdk.iap.lib.helper.IapHelper;
|
||||||
|
import com.samsung.android.sdk.iap.lib.listener.OnConsumePurchasedItemsListener;
|
||||||
|
import com.samsung.android.sdk.iap.lib.listener.OnGetOwnedListListener;
|
||||||
|
import com.samsung.android.sdk.iap.lib.vo.ConsumeVo;
|
||||||
|
import com.samsung.android.sdk.iap.lib.vo.ErrorVo;
|
||||||
|
import com.samsung.android.sdk.iap.lib.vo.OwnedProductVo;
|
||||||
|
import com.samsung.android.sdk.iap.lib.vo.PurchaseVo;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
|
||||||
|
public class SamsungUtil {
|
||||||
|
|
||||||
|
private Context mContext;
|
||||||
|
|
||||||
|
IapHelper iapHelper;
|
||||||
|
|
||||||
|
private static SamsungUtil samsungUtil;
|
||||||
|
|
||||||
|
public static SamsungUtil newInstance(Context context) {
|
||||||
|
if (samsungUtil == null) {
|
||||||
|
samsungUtil = new SamsungUtil(context);
|
||||||
|
}
|
||||||
|
return samsungUtil;
|
||||||
|
}
|
||||||
|
|
||||||
|
public SamsungUtil(Context mContext) {
|
||||||
|
this.mContext = mContext;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 初始化
|
||||||
|
*/
|
||||||
|
public void init() {
|
||||||
|
iapHelper = IapHelper.getInstance(mContext);
|
||||||
|
//设置支付模式 OPERATION_MODE_PRODUCTION 正式模式 OPERATION_MODE_TEST 测试模式
|
||||||
|
iapHelper.setOperationMode(HelperDefine.OperationMode.OPERATION_MODE_PRODUCTION);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void dispose() {
|
||||||
|
if (iapHelper != null) {
|
||||||
|
iapHelper.dispose();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 购买
|
||||||
|
*
|
||||||
|
* @param skuId
|
||||||
|
*/
|
||||||
|
public void buy(String skuId, OnPaymentListener onPaymentListener) {
|
||||||
|
//购买
|
||||||
|
iapHelper.startPayment(skuId, "", (errorVo, purchaseVo) -> {
|
||||||
|
if (purchaseVo != null) {
|
||||||
|
onPaymentListener.onPaymentSuccess(purchaseVo.getPurchaseId());
|
||||||
|
} else {
|
||||||
|
if (errorVo.getErrorCode() == HelperDefine.IAP_PAYMENT_IS_CANCELED) {
|
||||||
|
onPaymentListener.onPaymentFailed(mContext.getString(R.string.pay_cancel));
|
||||||
|
} else {
|
||||||
|
onPaymentListener.onPaymentFailed(errorVo.getErrorString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public interface OnPaymentListener {
|
||||||
|
void onPaymentSuccess(String purchaseVo);
|
||||||
|
|
||||||
|
void onPaymentFailed(String errorVo);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 消耗指定商品
|
||||||
|
*
|
||||||
|
* @param skuId
|
||||||
|
*/
|
||||||
|
public void consume(String skuId) {
|
||||||
|
//消耗
|
||||||
|
iapHelper.consumePurchasedItems(skuId, new OnConsumePurchasedItemsListener() {
|
||||||
|
@Override
|
||||||
|
public void onConsumePurchasedItems(ErrorVo _errorVO, ArrayList<ConsumeVo> _consumeList) {
|
||||||
|
if (_consumeList != null) {
|
||||||
|
Log.e("samsung","消耗:" + new Gson().toJson(_consumeList));
|
||||||
|
Log.e("samsung","ErrorVo:" + _errorVO.dump());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public void consumeAll(ArrayList<OwnedProductVo> _ownedList) {
|
||||||
|
if (_ownedList.size() > 0) {
|
||||||
|
iapHelper.consumePurchasedItems(_ownedList.get(0).getPurchaseId(), new OnConsumePurchasedItemsListener() {
|
||||||
|
@Override
|
||||||
|
public void onConsumePurchasedItems(ErrorVo _errorVO, ArrayList<ConsumeVo> _consumeList1) {
|
||||||
|
if (_errorVO.getErrorCode() == IapHelper.IAP_ERROR_NONE) {
|
||||||
|
Log.e("samsung","消耗:" + new Gson().toJson(_consumeList1));
|
||||||
|
Log.e("samsung","ErrorVo:" + _errorVO.dump());
|
||||||
|
_ownedList.remove(0);
|
||||||
|
consumeAll(_ownedList);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 消耗所有未消耗的消耗品
|
||||||
|
*/
|
||||||
|
public void query() {
|
||||||
|
//查询商品 PRODUCT_TYPE_ITEM 消耗品&非消耗品
|
||||||
|
iapHelper.getOwnedList(HelperDefine.PRODUCT_TYPE_ITEM, new OnGetOwnedListListener() {
|
||||||
|
@Override
|
||||||
|
public void onGetOwnedProducts(ErrorVo _errorVo, ArrayList<OwnedProductVo> _ownedList) {
|
||||||
|
if (_errorVo != null) {
|
||||||
|
if (_errorVo.getErrorCode() == IapHelper.IAP_ERROR_NONE) {
|
||||||
|
if (_ownedList != null) {
|
||||||
|
consumeAll(_ownedList);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,85 +0,0 @@
|
|||||||
package com.samsung.android.sdk.iap.lib.activity;
|
|
||||||
|
|
||||||
import android.app.Activity;
|
|
||||||
import android.content.Intent;
|
|
||||||
import android.os.Bundle;
|
|
||||||
import android.util.Log;
|
|
||||||
|
|
||||||
import com.samsung.android.sdk.iap.lib.helper.HelperDefine;
|
|
||||||
import com.samsung.android.sdk.iap.lib.helper.HelperUtil;
|
|
||||||
import com.samsung.android.sdk.iap.lib.helper.IapHelper;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Created by sangbum7.kim on 2018-03-06.
|
|
||||||
*/
|
|
||||||
|
|
||||||
public class AccountActivity extends Activity {
|
|
||||||
private static final String TAG = AccountActivity.class.getSimpleName();
|
|
||||||
|
|
||||||
IapHelper mIapHelper = null;
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void onCreate(Bundle savedInstanceState) {
|
|
||||||
super.onCreate(savedInstanceState);
|
|
||||||
|
|
||||||
mIapHelper = IapHelper.getInstance(this);
|
|
||||||
// ====================================================================
|
|
||||||
// 1. If IAP package is installed and valid, start SamsungAccount
|
|
||||||
// authentication activity to start purchase.
|
|
||||||
// ====================================================================
|
|
||||||
Log.i(TAG, "Samsung Account Login...");
|
|
||||||
HelperUtil.startAccountActivity(this);
|
|
||||||
// ====================================================================
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void onDestroy() {
|
|
||||||
super.onDestroy();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void onActivityResult(int _requestCode, int _resultCode, Intent intent) {
|
|
||||||
Log.i(TAG, "onActivityResult>> requestCode : " + _requestCode + ", resultCode : " + _resultCode);
|
|
||||||
switch (_requestCode) {
|
|
||||||
case HelperDefine.REQUEST_CODE_IS_ACCOUNT_CERTIFICATION:
|
|
||||||
Log.i(TAG, "REQUEST_CODE_IS_ACCOUNT_CERTIFICATION Result : " + _resultCode);
|
|
||||||
// 1) If SamsungAccount authentication is succeed
|
|
||||||
// ------------------------------------------------------------
|
|
||||||
if (RESULT_OK == _resultCode) {
|
|
||||||
// bind to IAPService
|
|
||||||
// --------------------------------------------------------
|
|
||||||
Runnable runProcess = new Runnable() {
|
|
||||||
@Override
|
|
||||||
public void run() {
|
|
||||||
mIapHelper.bindIapService();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
runProcess.run();
|
|
||||||
|
|
||||||
finish();
|
|
||||||
overridePendingTransition(0, 0);
|
|
||||||
// --------------------------------------------------------
|
|
||||||
}
|
|
||||||
// ------------------------------------------------------------
|
|
||||||
// 2) If SamsungAccount authentication is cancelled
|
|
||||||
// ------------------------------------------------------------
|
|
||||||
else {
|
|
||||||
// Runnable runProcess = new Runnable() {
|
|
||||||
// @Override
|
|
||||||
// public void run() {
|
|
||||||
// if(mIapHelper.getServiceProcess() != null)
|
|
||||||
// mIapHelper.getServiceProcess().onEndProcess();
|
|
||||||
// else
|
|
||||||
// mIapHelper.dispose();
|
|
||||||
// }
|
|
||||||
// };
|
|
||||||
// if(runProcess!=null)
|
|
||||||
// runProcess.run();
|
|
||||||
// else
|
|
||||||
mIapHelper.dispose();
|
|
||||||
finish();
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,233 +0,0 @@
|
|||||||
package com.samsung.android.sdk.iap.lib.activity;
|
|
||||||
|
|
||||||
import android.app.Activity;
|
|
||||||
import android.app.Dialog;
|
|
||||||
import android.content.Intent;
|
|
||||||
import android.os.Bundle;
|
|
||||||
import android.util.Log;
|
|
||||||
import android.widget.Toast;
|
|
||||||
|
|
||||||
import com.samsung.android.sdk.iap.lib.R;
|
|
||||||
import com.samsung.android.sdk.iap.lib.dialog.BaseDialogFragment;
|
|
||||||
import com.samsung.android.sdk.iap.lib.helper.HelperDefine;
|
|
||||||
import com.samsung.android.sdk.iap.lib.helper.HelperUtil;
|
|
||||||
import com.samsung.android.sdk.iap.lib.helper.IapHelper;
|
|
||||||
import com.samsung.android.sdk.iap.lib.vo.ErrorVo;
|
|
||||||
import com.samsung.android.sdk.iap.lib.vo.PurchaseVo;
|
|
||||||
|
|
||||||
|
|
||||||
public abstract class BaseActivity extends Activity {
|
|
||||||
private static final String TAG = BaseActivity.class.getSimpleName();
|
|
||||||
|
|
||||||
protected ErrorVo mErrorVo = new ErrorVo();
|
|
||||||
private Dialog mProgressDialog = null;
|
|
||||||
protected PurchaseVo mPurchaseVo = null;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Helper Class between IAPService and 3rd Party Application
|
|
||||||
*/
|
|
||||||
IapHelper mIapHelper = null;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Flag value to show successful pop-up. Error pop-up appears whenever it fails or not.
|
|
||||||
*/
|
|
||||||
protected boolean mShowErrorDialog = true;
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void onCreate(Bundle savedInstanceState) {
|
|
||||||
// 1. IapHelper Instance creation
|
|
||||||
// To test on development, set mode to test mode using
|
|
||||||
// use HelperDefine.OperationMode.OPERATION_MODE_TEST or
|
|
||||||
// HelperDefine.OperationMode.OPERATION_MODE_TEST_FAILURE constants.
|
|
||||||
// ====================================================================
|
|
||||||
mIapHelper = IapHelper.getInstance(this);
|
|
||||||
// ====================================================================
|
|
||||||
|
|
||||||
// 2. This activity is invisible excepting progress bar as default.
|
|
||||||
// ====================================================================
|
|
||||||
try {
|
|
||||||
Toast.makeText(this,
|
|
||||||
R.string.dream_sapps_body_authenticating_ing,
|
|
||||||
Toast.LENGTH_LONG).show();
|
|
||||||
} catch (Exception e) {
|
|
||||||
e.printStackTrace();
|
|
||||||
}
|
|
||||||
// ====================================================================
|
|
||||||
|
|
||||||
super.onCreate(savedInstanceState);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setErrorVo(ErrorVo _errorVo) {
|
|
||||||
mErrorVo = _errorVo;
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean checkAppsPackage(Activity _activity) {
|
|
||||||
// 1. If Galaxy Store is installed
|
|
||||||
// ====================================================================
|
|
||||||
if (HelperUtil.isInstalledAppsPackage(this)) {
|
|
||||||
// 1) If Galaxy Store is enabled
|
|
||||||
// ================================================================
|
|
||||||
if (!HelperUtil.isEnabledAppsPackage(this)) {
|
|
||||||
HelperUtil.showEnableGalaxyStoreDialog(_activity);
|
|
||||||
// ================================================================
|
|
||||||
// 2) If Galaxy Store is valid
|
|
||||||
// ================================================================
|
|
||||||
} else if (HelperUtil.isValidAppsPackage(this)) {
|
|
||||||
return true;
|
|
||||||
} else {
|
|
||||||
// Set error to notify result to third-party application
|
|
||||||
// ------------------------------------------------------------
|
|
||||||
final String ERROR_ISSUER_IAP_CLIENT = "IC";
|
|
||||||
final int ERROR_CODE_INVALID_GALAXY_STORE = 10002;
|
|
||||||
String errorString = String.format(
|
|
||||||
getString(
|
|
||||||
R.string.dream_ph_body_contact_p1sscustomer_servicep2ss_for_more_information_n_nerror_code_c_p3ss),
|
|
||||||
"", "", ERROR_ISSUER_IAP_CLIENT + ERROR_CODE_INVALID_GALAXY_STORE);
|
|
||||||
mErrorVo.setError(HelperDefine.IAP_PAYMENT_IS_CANCELED, errorString);
|
|
||||||
HelperUtil.showInvalidGalaxyStoreDialog(this);
|
|
||||||
}
|
|
||||||
// ================================================================
|
|
||||||
|
|
||||||
// ====================================================================
|
|
||||||
// 2. If Galaxy Store is not installed
|
|
||||||
// ====================================================================
|
|
||||||
} else {
|
|
||||||
HelperUtil.installAppsPackage(this);
|
|
||||||
}
|
|
||||||
// ====================================================================
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* dispose IapHelper {@link PaymentActivity} To do that, preDestory must be invoked at first in onDestory of each child activity
|
|
||||||
*/
|
|
||||||
protected void preDestory() {
|
|
||||||
// 1. Invoke dispose Method to unbind service and release inprogress flag
|
|
||||||
// ====================================================================
|
|
||||||
if (mIapHelper != null) {
|
|
||||||
mIapHelper.dispose();
|
|
||||||
mIapHelper = null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void onDestroy() {
|
|
||||||
// 1. dismiss ProgressDialog
|
|
||||||
// ====================================================================
|
|
||||||
try {
|
|
||||||
if (mProgressDialog != null) {
|
|
||||||
mProgressDialog.dismiss();
|
|
||||||
mProgressDialog = null;
|
|
||||||
}
|
|
||||||
} catch (Exception e) {
|
|
||||||
e.printStackTrace();
|
|
||||||
}
|
|
||||||
// ====================================================================
|
|
||||||
|
|
||||||
super.onDestroy();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void finish() {
|
|
||||||
super.finish();
|
|
||||||
overridePendingTransition(0, 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected void finishPurchase(Intent _intent) {
|
|
||||||
// 1. If there is bundle passed from IAP
|
|
||||||
// ====================================================================
|
|
||||||
if (_intent != null && _intent.getExtras() != null) {
|
|
||||||
Bundle extras = _intent.getExtras();
|
|
||||||
mErrorVo.setError(
|
|
||||||
extras.getInt(HelperDefine.KEY_NAME_STATUS_CODE),
|
|
||||||
extras.getString(HelperDefine.KEY_NAME_ERROR_STRING),
|
|
||||||
extras.getString(HelperDefine.KEY_NAME_ERROR_DETAILS, ""));
|
|
||||||
|
|
||||||
// 1) If the purchase is successful,
|
|
||||||
// ----------------------------------------------------------------
|
|
||||||
if (mErrorVo.getErrorCode() == HelperDefine.IAP_ERROR_NONE) {
|
|
||||||
mPurchaseVo = new PurchaseVo(extras.getString(
|
|
||||||
HelperDefine.KEY_NAME_RESULT_OBJECT));
|
|
||||||
|
|
||||||
mErrorVo.setError(
|
|
||||||
HelperDefine.IAP_ERROR_NONE,
|
|
||||||
getString(R.string.dream_sapps_body_your_purchase_is_complete));
|
|
||||||
|
|
||||||
finish();
|
|
||||||
}
|
|
||||||
// ----------------------------------------------------------------
|
|
||||||
// 2) If the purchase is failed
|
|
||||||
// ----------------------------------------------------------------
|
|
||||||
else {
|
|
||||||
Log.e(TAG, "finishPurchase: " + mErrorVo.dump());
|
|
||||||
if (mShowErrorDialog) {
|
|
||||||
HelperUtil.showIapErrorDialog(
|
|
||||||
this,
|
|
||||||
getString(R.string.dream_ph_pheader_couldnt_complete_purchase),
|
|
||||||
mErrorVo.getErrorString(),
|
|
||||||
mErrorVo.getErrorDetailsString(),
|
|
||||||
new BaseDialogFragment.OnClickListener() {
|
|
||||||
@Override
|
|
||||||
public void onClick() {
|
|
||||||
finish();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
null);
|
|
||||||
} else {
|
|
||||||
finish();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// ----------------------------------------------------------------
|
|
||||||
}
|
|
||||||
// ====================================================================
|
|
||||||
// 2. If there is no bundle passed from IAP
|
|
||||||
// ====================================================================
|
|
||||||
else {
|
|
||||||
mErrorVo.setError(
|
|
||||||
HelperDefine.IAP_ERROR_COMMON,
|
|
||||||
getString(R.string.mids_sapps_pop_unknown_error_occurred));
|
|
||||||
|
|
||||||
if (mShowErrorDialog) {
|
|
||||||
HelperUtil.showIapErrorDialog(
|
|
||||||
this,
|
|
||||||
getString(R.string.dream_ph_pheader_couldnt_complete_purchase),
|
|
||||||
getString(R.string.mids_sapps_pop_unknown_error_occurred),
|
|
||||||
new BaseDialogFragment.OnClickListener() {
|
|
||||||
@Override
|
|
||||||
public void onClick() {
|
|
||||||
finish();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
null);
|
|
||||||
} else {
|
|
||||||
finish();
|
|
||||||
}
|
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
// ====================================================================
|
|
||||||
}
|
|
||||||
|
|
||||||
protected void cancelPurchase(Intent intent) {
|
|
||||||
if (intent != null) {
|
|
||||||
Bundle extras = intent.getExtras();
|
|
||||||
if (extras != null) {
|
|
||||||
mErrorVo.setError(
|
|
||||||
extras.getInt(
|
|
||||||
HelperDefine.KEY_NAME_STATUS_CODE,
|
|
||||||
HelperDefine.IAP_PAYMENT_IS_CANCELED),
|
|
||||||
extras.getString(
|
|
||||||
HelperDefine.KEY_NAME_ERROR_STRING,
|
|
||||||
getString(R.string.mids_sapps_pop_payment_canceled)),
|
|
||||||
extras.getString(HelperDefine.KEY_NAME_ERROR_DETAILS, ""));
|
|
||||||
finish();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
mErrorVo.setError(
|
|
||||||
HelperDefine.IAP_PAYMENT_IS_CANCELED,
|
|
||||||
getString(R.string.mids_sapps_pop_payment_canceled));
|
|
||||||
finish();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,57 +0,0 @@
|
|||||||
package com.samsung.android.sdk.iap.lib.activity;
|
|
||||||
|
|
||||||
import android.app.Activity;
|
|
||||||
import android.content.Intent;
|
|
||||||
import android.os.Bundle;
|
|
||||||
|
|
||||||
import com.samsung.android.sdk.iap.lib.helper.HelperDefine;
|
|
||||||
import com.samsung.android.sdk.iap.lib.helper.HelperUtil;
|
|
||||||
import com.samsung.android.sdk.iap.lib.helper.IapHelper;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Created by sangbum7.kim on 2018-03-07.
|
|
||||||
*/
|
|
||||||
|
|
||||||
public class CheckPackageActivity extends Activity {
|
|
||||||
private static final String TAG = CheckPackageActivity.class.getSimpleName();
|
|
||||||
private boolean mFinishFlag = true;
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void onCreate(Bundle savedInstanceState) {
|
|
||||||
super.onCreate(savedInstanceState);
|
|
||||||
mFinishFlag = true;
|
|
||||||
Intent intent = getIntent();
|
|
||||||
if (intent != null) {
|
|
||||||
Bundle extras = intent.getExtras();
|
|
||||||
if (extras != null) {
|
|
||||||
int DialogType = extras.getInt("DialogType");
|
|
||||||
switch (DialogType) {
|
|
||||||
case HelperDefine.DIALOG_TYPE_INVALID_PACKAGE: {
|
|
||||||
HelperUtil.showInvalidGalaxyStoreDialog(this);
|
|
||||||
mFinishFlag = false;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case HelperDefine.DIALOG_TYPE_DISABLE_APPLICATION: {
|
|
||||||
HelperUtil.showEnableGalaxyStoreDialog(this);
|
|
||||||
mFinishFlag = false;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case HelperDefine.DIALOG_TYPE_APPS_DETAIL: {
|
|
||||||
HelperUtil.showUpdateGalaxyStoreDialog(this);
|
|
||||||
mFinishFlag = false;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (mFinishFlag) {
|
|
||||||
finish();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void onDestroy() {
|
|
||||||
super.onDestroy();
|
|
||||||
IapHelper.getInstance(getApplicationContext()).dispose();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,53 +0,0 @@
|
|||||||
package com.samsung.android.sdk.iap.lib.activity;
|
|
||||||
|
|
||||||
import android.app.Activity;
|
|
||||||
import android.content.ActivityNotFoundException;
|
|
||||||
import android.content.Intent;
|
|
||||||
import android.net.Uri;
|
|
||||||
import android.os.Bundle;
|
|
||||||
import android.text.TextUtils;
|
|
||||||
|
|
||||||
import com.samsung.android.sdk.iap.lib.R;
|
|
||||||
import com.samsung.android.sdk.iap.lib.dialog.BaseDialogFragment;
|
|
||||||
import com.samsung.android.sdk.iap.lib.helper.HelperDefine;
|
|
||||||
import com.samsung.android.sdk.iap.lib.helper.HelperUtil;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Created by sangbum7.kim on 2018-03-05.
|
|
||||||
*/
|
|
||||||
|
|
||||||
public class DialogActivity extends Activity {
|
|
||||||
private static final String TAG = DialogActivity.class.getSimpleName();
|
|
||||||
private String mExtraString = "";
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void onCreate(Bundle savedInstanceState) {
|
|
||||||
super.onCreate(savedInstanceState);
|
|
||||||
Intent intent = getIntent();
|
|
||||||
if (intent != null) {
|
|
||||||
Bundle extras = intent.getExtras();
|
|
||||||
if (extras != null) {
|
|
||||||
if (HelperDefine.DIALOG_TYPE_NOTIFICATION == extras.getInt("DialogType")) {
|
|
||||||
String title = extras.getString("Title");
|
|
||||||
String message = extras.getString("Message");
|
|
||||||
HelperUtil.showIapErrorDialog(
|
|
||||||
this,
|
|
||||||
title,
|
|
||||||
message,
|
|
||||||
new BaseDialogFragment.OnClickListener() {
|
|
||||||
@Override
|
|
||||||
public void onClick() {
|
|
||||||
finish();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
null);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void onDestroy() {
|
|
||||||
super.onDestroy();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,127 +0,0 @@
|
|||||||
package com.samsung.android.sdk.iap.lib.activity;
|
|
||||||
|
|
||||||
import android.content.ComponentName;
|
|
||||||
import android.content.Context;
|
|
||||||
import android.content.Intent;
|
|
||||||
import android.content.res.Configuration;
|
|
||||||
import android.os.Bundle;
|
|
||||||
import android.util.Log;
|
|
||||||
import android.widget.Toast;
|
|
||||||
|
|
||||||
import com.samsung.android.sdk.iap.lib.R;
|
|
||||||
import com.samsung.android.sdk.iap.lib.helper.HelperDefine;
|
|
||||||
import com.samsung.android.sdk.iap.lib.helper.HelperListenerManager;
|
|
||||||
import com.samsung.android.sdk.iap.lib.listener.OnPaymentListener;
|
|
||||||
|
|
||||||
public class PaymentActivity extends BaseActivity {
|
|
||||||
private static final String TAG = PaymentActivity.class.getSimpleName();
|
|
||||||
|
|
||||||
private String mItemId = null;
|
|
||||||
private String mPassThroughParam = "";
|
|
||||||
private int mMode;
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void onCreate(Bundle savedInstanceState) {
|
|
||||||
super.onCreate(savedInstanceState);
|
|
||||||
|
|
||||||
Intent intent = getIntent();
|
|
||||||
if (intent != null && intent.getExtras() != null
|
|
||||||
&& intent.getExtras().containsKey("ItemId")) {
|
|
||||||
Bundle extras = intent.getExtras();
|
|
||||||
mItemId = extras.getString("ItemId");
|
|
||||||
mPassThroughParam = extras.getString("PassThroughParam");
|
|
||||||
mShowErrorDialog = extras.getBoolean("ShowErrorDialog", true);
|
|
||||||
mMode = extras.getInt("OperationMode", HelperDefine.OperationMode.OPERATION_MODE_PRODUCTION.getValue());
|
|
||||||
} else {
|
|
||||||
Toast.makeText(this,
|
|
||||||
R.string.mids_sapps_pop_an_invalid_value_has_been_provided_for_samsung_in_app_purchase,
|
|
||||||
Toast.LENGTH_LONG).show();
|
|
||||||
|
|
||||||
// Set error to pass result to third-party application
|
|
||||||
// ----------------------------------------------------------------
|
|
||||||
mErrorVo.setError(HelperDefine.IAP_ERROR_COMMON,
|
|
||||||
getString(R.string.mids_sapps_pop_an_invalid_value_has_been_provided_for_samsung_in_app_purchase));
|
|
||||||
// ----------------------------------------------------------------
|
|
||||||
|
|
||||||
finish();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (checkAppsPackage(this)) {
|
|
||||||
startPaymentActivity();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void onDestroy() {
|
|
||||||
super.preDestory();
|
|
||||||
if (isFinishing()) {
|
|
||||||
OnPaymentListener onPaymentListener =
|
|
||||||
HelperListenerManager.getInstance().getOnPaymentListener();
|
|
||||||
HelperListenerManager.getInstance().setOnPaymentListener(null);
|
|
||||||
if (null != onPaymentListener) {
|
|
||||||
onPaymentListener.onPayment(mErrorVo, mPurchaseVo);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
super.onDestroy();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onConfigurationChanged(Configuration newConfig) {
|
|
||||||
super.onConfigurationChanged(newConfig);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void onActivityResult(int _requestCode, int _resultCode, Intent _intent) {
|
|
||||||
switch (_requestCode) {
|
|
||||||
case HelperDefine.REQUEST_CODE_IS_IAP_PAYMENT: {
|
|
||||||
if (_resultCode == RESULT_OK) {
|
|
||||||
finishPurchase(_intent);
|
|
||||||
}
|
|
||||||
else if (_resultCode == RESULT_CANCELED) {
|
|
||||||
Log.e(TAG, "Payment is canceled.");
|
|
||||||
cancelPurchase(_intent);
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case HelperDefine.REQUEST_CODE_IS_ENABLE_APPS: {
|
|
||||||
if (checkAppsPackage(this)) {
|
|
||||||
startPaymentActivity();
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void startPaymentActivity() {
|
|
||||||
if (mIapHelper == null) {
|
|
||||||
Log.e(TAG, "Fail to get IAP Helper instance");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
Context context = this.getApplicationContext();
|
|
||||||
Bundle bundle = new Bundle();
|
|
||||||
bundle.putString(HelperDefine.KEY_NAME_THIRD_PARTY_NAME, context.getPackageName());
|
|
||||||
bundle.putString(HelperDefine.KEY_NAME_ITEM_ID, mItemId);
|
|
||||||
if (mPassThroughParam != null) {
|
|
||||||
bundle.putString(HelperDefine.KEY_NAME_PASSTHROUGH_ID, mPassThroughParam);
|
|
||||||
}
|
|
||||||
bundle.putInt(HelperDefine.KEY_NAME_OPERATION_MODE, mMode);
|
|
||||||
bundle.putString(HelperDefine.KEY_NAME_VERSION_CODE, HelperDefine.HELPER_VERSION);
|
|
||||||
|
|
||||||
ComponentName com = new ComponentName(HelperDefine.GALAXY_PACKAGE_NAME,
|
|
||||||
HelperDefine.IAP_PACKAGE_NAME + ".activity.PaymentMethodListActivity");
|
|
||||||
|
|
||||||
Intent intent = new Intent(Intent.ACTION_MAIN);
|
|
||||||
intent.addCategory(Intent.CATEGORY_LAUNCHER);
|
|
||||||
intent.setComponent(com);
|
|
||||||
|
|
||||||
intent.putExtras(bundle);
|
|
||||||
|
|
||||||
if (intent.resolveActivity(context.getPackageManager()) != null) {
|
|
||||||
startActivityForResult(intent, HelperDefine.REQUEST_CODE_IS_IAP_PAYMENT);
|
|
||||||
}
|
|
||||||
} catch (Exception e) {
|
|
||||||
e.printStackTrace();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,213 +0,0 @@
|
|||||||
package com.samsung.android.sdk.iap.lib.dialog;
|
|
||||||
|
|
||||||
import android.app.ActionBar;
|
|
||||||
import android.app.Dialog;
|
|
||||||
import android.app.DialogFragment;
|
|
||||||
import android.content.res.Configuration;
|
|
||||||
import android.os.Build;
|
|
||||||
import android.os.Bundle;
|
|
||||||
import android.text.TextUtils;
|
|
||||||
import android.text.method.LinkMovementMethod;
|
|
||||||
import android.util.Log;
|
|
||||||
import android.util.TypedValue;
|
|
||||||
import android.view.Gravity;
|
|
||||||
import android.view.View;
|
|
||||||
import android.view.WindowManager;
|
|
||||||
import android.widget.Button;
|
|
||||||
import android.widget.TextView;
|
|
||||||
|
|
||||||
import com.samsung.android.sdk.iap.lib.R;
|
|
||||||
|
|
||||||
public class BaseDialogFragment extends DialogFragment implements View.OnClickListener {
|
|
||||||
private static final String TAG = BaseDialogFragment.class.getSimpleName();
|
|
||||||
|
|
||||||
private int dialogWidth;
|
|
||||||
private String title;
|
|
||||||
private CharSequence message;
|
|
||||||
private String messageExtra = "";
|
|
||||||
private String positiveBtnText;
|
|
||||||
private String negativeBtnText;
|
|
||||||
private OnClickListener positiveButtonListener = null;
|
|
||||||
private OnClickListener negativeButtonListener = null;
|
|
||||||
|
|
||||||
public static final String IAP_DIALOG_TAG = "IAP_dialog";
|
|
||||||
|
|
||||||
public interface OnClickListener {
|
|
||||||
void onClick();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onCreate(Bundle savedInstanceState) {
|
|
||||||
super.onCreate(savedInstanceState);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onResume() {
|
|
||||||
super.onResume();
|
|
||||||
dialogWidth = getDialogWidth();
|
|
||||||
getDialog().getWindow().setLayout(dialogWidth, ActionBar.LayoutParams.WRAP_CONTENT);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onDestroyView() {
|
|
||||||
if (getDialog() != null && getRetainInstance()) {
|
|
||||||
getDialog().setDismissMessage(null);
|
|
||||||
}
|
|
||||||
super.onDestroyView();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Dialog onCreateDialog(Bundle savedInstanceState) {
|
|
||||||
View view;
|
|
||||||
if (isDarkMode()) {
|
|
||||||
view = getActivity().getLayoutInflater().inflate(R.layout.dialog_dark, null);
|
|
||||||
} else {
|
|
||||||
view = getActivity().getLayoutInflater().inflate(R.layout.dialog_light, null);
|
|
||||||
}
|
|
||||||
|
|
||||||
((TextView) view.findViewById(R.id.dialog_title)).setText(title);
|
|
||||||
((TextView) view.findViewById(R.id.dialog_message)).setText(message);
|
|
||||||
((TextView) view.findViewById(R.id.dialog_message)).setLinksClickable(true);
|
|
||||||
((TextView) view.findViewById(R.id.dialog_message)).setMovementMethod(LinkMovementMethod.getInstance());
|
|
||||||
|
|
||||||
if (messageExtra == null || messageExtra.isEmpty()) {
|
|
||||||
view.findViewById(R.id.dialog_message_extra).setVisibility(View.GONE);
|
|
||||||
} else {
|
|
||||||
((TextView) view.findViewById(R.id.dialog_message_extra))
|
|
||||||
.setText(getString(R.string.ids_com_body_error_code_c) + " " + messageExtra);
|
|
||||||
view.findViewById(R.id.dialog_message_extra).setVisibility(View.VISIBLE);
|
|
||||||
}
|
|
||||||
|
|
||||||
((Button) view.findViewById(R.id.dialog_ok_btn)).setText(positiveBtnText);
|
|
||||||
view.findViewById(R.id.dialog_ok_btn).setOnClickListener(this);
|
|
||||||
|
|
||||||
if (negativeButtonListener == null) {
|
|
||||||
view.findViewById(R.id.dialog_cancel_btn).setVisibility(View.GONE);
|
|
||||||
view.findViewById(R.id.dialog_btn_padding).setVisibility(View.GONE);
|
|
||||||
} else {
|
|
||||||
((Button) view.findViewById(R.id.dialog_cancel_btn)).setText(negativeBtnText);
|
|
||||||
view.findViewById(R.id.dialog_cancel_btn).setVisibility(View.VISIBLE);
|
|
||||||
view.findViewById(R.id.dialog_cancel_btn).setOnClickListener(this);
|
|
||||||
view.findViewById(R.id.dialog_btn_padding).setVisibility(View.VISIBLE);
|
|
||||||
}
|
|
||||||
|
|
||||||
Dialog dialog = new Dialog(getActivity(), R.style.Theme_DialogTransparent);
|
|
||||||
dialog.setContentView(view);
|
|
||||||
dialog.setCancelable(false);
|
|
||||||
dialog.setCanceledOnTouchOutside(false);
|
|
||||||
|
|
||||||
dialog.getWindow().setGravity(Gravity.BOTTOM);
|
|
||||||
dialog.getWindow().addFlags(WindowManager.LayoutParams.FLAG_DIM_BEHIND);
|
|
||||||
TypedValue dimValue = new TypedValue();
|
|
||||||
if (isDarkMode()) {
|
|
||||||
getResources().getValue(R.integer.dim_dark, dimValue, true);
|
|
||||||
} else {
|
|
||||||
getResources().getValue(R.integer.dim_light, dimValue, true);
|
|
||||||
}
|
|
||||||
dialog.getWindow().setDimAmount(dimValue.getFloat());
|
|
||||||
|
|
||||||
return dialog;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onConfigurationChanged(Configuration newConfig) {
|
|
||||||
super.onConfigurationChanged(newConfig);
|
|
||||||
dialogWidth = getDialogWidth();
|
|
||||||
getDialog().getWindow().setLayout(dialogWidth, ActionBar.LayoutParams.WRAP_CONTENT);
|
|
||||||
}
|
|
||||||
|
|
||||||
public BaseDialogFragment setDialogTitle(String _title) {
|
|
||||||
if (!TextUtils.isEmpty(_title)) {
|
|
||||||
this.title = _title;
|
|
||||||
}
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public BaseDialogFragment setDialogMessageText(CharSequence _message) {
|
|
||||||
if (!TextUtils.isEmpty(_message)) {
|
|
||||||
this.message = _message;
|
|
||||||
}
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public BaseDialogFragment setDialogMessageExtra(String _extra) {
|
|
||||||
if (!TextUtils.isEmpty(_extra)) {
|
|
||||||
this.messageExtra = _extra;
|
|
||||||
}
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public BaseDialogFragment setDialogPositiveButton(String _positiveBtnText, final OnClickListener listener) {
|
|
||||||
if (!TextUtils.isEmpty(_positiveBtnText)) {
|
|
||||||
positiveBtnText = _positiveBtnText;
|
|
||||||
} else {
|
|
||||||
positiveBtnText = (String) getText(android.R.string.ok);
|
|
||||||
}
|
|
||||||
if (listener != null) {
|
|
||||||
positiveButtonListener = listener;
|
|
||||||
}
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public BaseDialogFragment setDialogNegativeButton(String _negativeBtnText, final OnClickListener listener) {
|
|
||||||
if (!TextUtils.isEmpty(_negativeBtnText)) {
|
|
||||||
negativeBtnText = _negativeBtnText;
|
|
||||||
}
|
|
||||||
if (listener != null) {
|
|
||||||
negativeButtonListener = listener;
|
|
||||||
}
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onClick(View v) {
|
|
||||||
if (v.getId() == R.id.dialog_ok_btn) {
|
|
||||||
Runnable OkBtnRunnable = new Runnable() {
|
|
||||||
@Override
|
|
||||||
public void run() {
|
|
||||||
positiveButtonListener.onClick();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
OkBtnRunnable.run();
|
|
||||||
} else if (v.getId() == R.id.dialog_cancel_btn) {
|
|
||||||
Runnable CancelBtnRunnable = new Runnable() {
|
|
||||||
@Override
|
|
||||||
public void run() {
|
|
||||||
negativeButtonListener.onClick();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
CancelBtnRunnable.run();
|
|
||||||
}
|
|
||||||
dismiss();
|
|
||||||
}
|
|
||||||
|
|
||||||
private int getDialogWidth() {
|
|
||||||
TypedValue outValue = new TypedValue();
|
|
||||||
getResources().getValue(R.integer.dialog_width_percentage, outValue, true);
|
|
||||||
float ratio = outValue.getFloat();
|
|
||||||
int width = (int) (getResources().getDisplayMetrics().widthPixels * ratio);
|
|
||||||
return width;
|
|
||||||
}
|
|
||||||
|
|
||||||
private boolean isDarkMode() {
|
|
||||||
try {
|
|
||||||
// getContext() requires M OS or higher version
|
|
||||||
int nightModeFlags = 0;
|
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
|
||||||
nightModeFlags = getContext().getResources().getConfiguration().uiMode &
|
|
||||||
Configuration.UI_MODE_NIGHT_MASK;
|
|
||||||
}
|
|
||||||
switch (nightModeFlags) {
|
|
||||||
case Configuration.UI_MODE_NIGHT_YES:
|
|
||||||
return true;
|
|
||||||
case Configuration.UI_MODE_NIGHT_NO:
|
|
||||||
case Configuration.UI_MODE_NIGHT_UNDEFINED:
|
|
||||||
default:
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
} catch (Exception e) {
|
|
||||||
e.printStackTrace();
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,192 +0,0 @@
|
|||||||
package com.samsung.android.sdk.iap.lib.helper;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Created by sangbum7.kim on 2017-07-17.
|
|
||||||
*/
|
|
||||||
|
|
||||||
public class HelperDefine {
|
|
||||||
public static final String HELPER_VERSION = "6.1.0.004";
|
|
||||||
// IAP Signature HashCode - Used to validate IAP package
|
|
||||||
// ========================================================================
|
|
||||||
public static final int APPS_SIGNATURE_HASHCODE = 0x79998D13;
|
|
||||||
public static final int APPS_PACKAGE_VERSION = 450301000;
|
|
||||||
public static final int APPS_PACKAGE_VERSION_GO = 510130000;
|
|
||||||
public static final int APPS_PACKAGE_VERSION_INDIA = 660107000;
|
|
||||||
|
|
||||||
// ========================================================================
|
|
||||||
|
|
||||||
// Name of IAP Package and Service
|
|
||||||
// ========================================================================
|
|
||||||
public static final String GALAXY_PACKAGE_NAME = "com.sec.android.app.samsungapps";
|
|
||||||
public static final String IAP_PACKAGE_NAME = "com.samsung.android.iap";
|
|
||||||
public static final String IAP_SERVICE_NAME =
|
|
||||||
"com.samsung.android.iap.service.IAPService";
|
|
||||||
// ========================================================================
|
|
||||||
|
|
||||||
// result code for binding to IAPService
|
|
||||||
// ========================================================================
|
|
||||||
public static final int IAP_RESPONSE_RESULT_OK = 0;
|
|
||||||
public static final int IAP_RESPONSE_RESULT_UNAVAILABLE = 2;
|
|
||||||
// ========================================================================
|
|
||||||
|
|
||||||
// BUNDLE KEY
|
|
||||||
// ========================================================================
|
|
||||||
public static final String KEY_NAME_THIRD_PARTY_NAME = "THIRD_PARTY_NAME";
|
|
||||||
public static final String KEY_NAME_STATUS_CODE = "STATUS_CODE";
|
|
||||||
public static final String KEY_NAME_ERROR_STRING = "ERROR_STRING";
|
|
||||||
public static final String KEY_NAME_ERROR_DETAILS = "ERROR_DETAILS";
|
|
||||||
public static final String KEY_NAME_ITEM_ID = "ITEM_ID";
|
|
||||||
public static final String KEY_NAME_PASSTHROUGH_ID = "PASSTHROUGH_ID";
|
|
||||||
public static final String KEY_NAME_RESULT_LIST = "RESULT_LIST";
|
|
||||||
public static final String KEY_NAME_OPERATION_MODE = "OPERATION_MODE";
|
|
||||||
public static final String KEY_NAME_RESULT_OBJECT = "RESULT_OBJECT";
|
|
||||||
public static final String KEY_NAME_VERSION_CODE = "VERSION_CODE";
|
|
||||||
public static final String NEXT_PAGING_INDEX = "NEXT_PAGING_INDEX";
|
|
||||||
// ========================================================================
|
|
||||||
|
|
||||||
// Item Type
|
|
||||||
// ========================================================================
|
|
||||||
public static final String PRODUCT_TYPE_ITEM = "item";
|
|
||||||
public static final String PRODUCT_TYPE_SUBSCRIPTION = "subscription";
|
|
||||||
public static final String PRODUCT_TYPE_ALL = "all";
|
|
||||||
|
|
||||||
// Define request code to IAPService.
|
|
||||||
// ========================================================================
|
|
||||||
public static final int REQUEST_CODE_IS_IAP_PAYMENT = 1;
|
|
||||||
public static final int REQUEST_CODE_IS_ACCOUNT_CERTIFICATION = 2;
|
|
||||||
public static final int REQUEST_CODE_IS_ENABLE_APPS = 3;
|
|
||||||
// ========================================================================
|
|
||||||
|
|
||||||
// Define dialog type to DialogActivity
|
|
||||||
public static final int DIALOG_TYPE_NONE = 0;
|
|
||||||
public static final int DIALOG_TYPE_NOTIFICATION = 1;
|
|
||||||
public static final int DIALOG_TYPE_INVALID_PACKAGE = 2;
|
|
||||||
public static final int DIALOG_TYPE_DISABLE_APPLICATION = 3;
|
|
||||||
public static final int DIALOG_TYPE_APPS_DETAIL = 4;
|
|
||||||
|
|
||||||
// Define request parameter to IAPService
|
|
||||||
// ========================================================================
|
|
||||||
public static final int PASSTHROGUH_MAX_LENGTH = 255;
|
|
||||||
// ========================================================================
|
|
||||||
|
|
||||||
// Define status code notify to 3rd-party application
|
|
||||||
// ========================================================================
|
|
||||||
/**
|
|
||||||
* Success
|
|
||||||
*/
|
|
||||||
final public static int IAP_ERROR_NONE = 0;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Payment is cancelled
|
|
||||||
*/
|
|
||||||
final public static int IAP_PAYMENT_IS_CANCELED = 1;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* IAP initialization error
|
|
||||||
*/
|
|
||||||
final public static int IAP_ERROR_INITIALIZATION = -1000;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* IAP need to be upgraded
|
|
||||||
*/
|
|
||||||
final public static int IAP_ERROR_NEED_APP_UPGRADE = -1001;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Common error
|
|
||||||
*/
|
|
||||||
final public static int IAP_ERROR_COMMON = -1002;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Repurchase NON-CONSUMABLE item
|
|
||||||
*/
|
|
||||||
final public static int IAP_ERROR_ALREADY_PURCHASED = -1003;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* When PaymentMethodList Activity is called without Bundle data
|
|
||||||
*/
|
|
||||||
final public static int IAP_ERROR_WHILE_RUNNING = -1004;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* does not exist item or item group id
|
|
||||||
*/
|
|
||||||
final public static int IAP_ERROR_PRODUCT_DOES_NOT_EXIST = -1005;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* After purchase request not received the results can not be determined whether to buy. So, the confirmation of purchase list is needed.
|
|
||||||
*/
|
|
||||||
final public static int IAP_ERROR_CONFIRM_INBOX = -1006;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Error when item group id does not exist
|
|
||||||
*/
|
|
||||||
public static final int IAP_ERROR_ITEM_GROUP_DOES_NOT_EXIST = -1007;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Error when network is not available
|
|
||||||
*/
|
|
||||||
public static final int IAP_ERROR_NETWORK_NOT_AVAILABLE = -1008;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* IOException
|
|
||||||
*/
|
|
||||||
public static final int IAP_ERROR_IOEXCEPTION_ERROR = -1009;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* SocketTimeoutException
|
|
||||||
*/
|
|
||||||
public static final int IAP_ERROR_SOCKET_TIMEOUT = -1010;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* ConnectTimeoutException
|
|
||||||
*/
|
|
||||||
public static final int IAP_ERROR_CONNECT_TIMEOUT = -1011;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The Item is not for sale in the country
|
|
||||||
*/
|
|
||||||
public static final int IAP_ERROR_NOT_EXIST_LOCAL_PRICE = -1012;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* IAP is not serviced in the country
|
|
||||||
*/
|
|
||||||
public static final int IAP_ERROR_NOT_AVAILABLE_SHOP = -1013;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* SA not logged in
|
|
||||||
*/
|
|
||||||
public static final int IAP_ERROR_NEED_SA_LOGIN = -1014;
|
|
||||||
// ========================================================================
|
|
||||||
|
|
||||||
/**
|
|
||||||
* initial state
|
|
||||||
*/
|
|
||||||
protected static final int STATE_TERM = 0;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* state of bound to IAPService successfully
|
|
||||||
*/
|
|
||||||
protected static final int STATE_BINDING = 1;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* state of InitIapTask successfully finished
|
|
||||||
*/
|
|
||||||
protected static final int STATE_READY = 2;
|
|
||||||
// ========================================================================
|
|
||||||
|
|
||||||
// IAP Operation Mode
|
|
||||||
// ========================================================================
|
|
||||||
public enum OperationMode {
|
|
||||||
OPERATION_MODE_TEST_FAILURE(-1),
|
|
||||||
OPERATION_MODE_PRODUCTION(0),
|
|
||||||
OPERATION_MODE_TEST(1);
|
|
||||||
|
|
||||||
final private int value;
|
|
||||||
OperationMode(int value) {
|
|
||||||
this.value = value;
|
|
||||||
}
|
|
||||||
public int getValue() {
|
|
||||||
return value;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// ========================================================================
|
|
||||||
}
|
|
||||||
@@ -1,102 +0,0 @@
|
|||||||
package com.samsung.android.sdk.iap.lib.helper;
|
|
||||||
|
|
||||||
import com.samsung.android.sdk.iap.lib.helper.task.ConsumePurchasedItemsTask;
|
|
||||||
import com.samsung.android.sdk.iap.lib.helper.task.GetOwnedListTask;
|
|
||||||
import com.samsung.android.sdk.iap.lib.helper.task.GetProductsDetailsTask;
|
|
||||||
import com.samsung.android.sdk.iap.lib.listener.OnConsumePurchasedItemsListener;
|
|
||||||
import com.samsung.android.sdk.iap.lib.listener.OnGetOwnedListListener;
|
|
||||||
import com.samsung.android.sdk.iap.lib.listener.OnGetProductsDetailsListener;
|
|
||||||
import com.samsung.android.sdk.iap.lib.listener.OnPaymentListener;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Created by sangbum7.kim on 2017-08-29.
|
|
||||||
*/
|
|
||||||
|
|
||||||
public class HelperListenerManager {
|
|
||||||
private static HelperListenerManager mInstance = null;
|
|
||||||
|
|
||||||
private OnGetProductsDetailsListener mOnGetProductsDetailsListener = null;
|
|
||||||
private OnGetOwnedListListener mOnGetOwnedListListener = null;
|
|
||||||
private OnConsumePurchasedItemsListener mOnConsumePurchasedItemsListener = null;
|
|
||||||
private OnPaymentListener mOnPaymentListener = null;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* HelperListenerManager singleton reference method
|
|
||||||
*/
|
|
||||||
public static HelperListenerManager getInstance() {
|
|
||||||
if (mInstance == null) {
|
|
||||||
mInstance = new HelperListenerManager();
|
|
||||||
}
|
|
||||||
return mInstance;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void destroy() {
|
|
||||||
mInstance = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* HelperListenerManager constructor
|
|
||||||
*/
|
|
||||||
private HelperListenerManager() {
|
|
||||||
mOnGetProductsDetailsListener = null;
|
|
||||||
mOnGetOwnedListListener = null;
|
|
||||||
mOnConsumePurchasedItemsListener = null;
|
|
||||||
mOnPaymentListener = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Register {@link OnGetProductsDetailsListener} callback interface to be invoked when {@link GetProductsDetailsTask} has been finished.
|
|
||||||
*
|
|
||||||
* @param _onGetProductsDetailsListener
|
|
||||||
*/
|
|
||||||
public void setOnGetProductsDetailsListener(OnGetProductsDetailsListener _onGetProductsDetailsListener) {
|
|
||||||
mOnGetProductsDetailsListener = _onGetProductsDetailsListener;
|
|
||||||
}
|
|
||||||
|
|
||||||
public OnGetProductsDetailsListener getOnGetProductsDetailsListener() {
|
|
||||||
return mOnGetProductsDetailsListener;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Register {@link OnGetOwnedListListener} callback interface to be invoked when {@link GetOwnedListTask} has been finished.
|
|
||||||
*
|
|
||||||
* @param _onGetOwnedListListener
|
|
||||||
*/
|
|
||||||
public void setOnGetOwnedListListener(OnGetOwnedListListener _onGetOwnedListListener) {
|
|
||||||
mOnGetOwnedListListener = _onGetOwnedListListener;
|
|
||||||
}
|
|
||||||
|
|
||||||
public OnGetOwnedListListener getOnGetOwnedListListener() {
|
|
||||||
return mOnGetOwnedListListener;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Register {@link OnConsumePurchasedItemsListener} callback interface to be invoked when {@link ConsumePurchasedItemsTask} has been finished.
|
|
||||||
*
|
|
||||||
* @param _onConsumePurchasedItemsListener
|
|
||||||
*/
|
|
||||||
public void setOnConsumePurchasedItemsListener(OnConsumePurchasedItemsListener _onConsumePurchasedItemsListener) {
|
|
||||||
mOnConsumePurchasedItemsListener = _onConsumePurchasedItemsListener;
|
|
||||||
}
|
|
||||||
|
|
||||||
public OnConsumePurchasedItemsListener getOnConsumePurchasedItemsListener() {
|
|
||||||
return mOnConsumePurchasedItemsListener;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Register a callback interface to be invoked when Purchase Process has been finished.
|
|
||||||
*
|
|
||||||
* @param _onPaymentListener
|
|
||||||
*/
|
|
||||||
public void setOnPaymentListener(OnPaymentListener _onPaymentListener) {
|
|
||||||
mOnPaymentListener = _onPaymentListener;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public OnPaymentListener getOnPaymentListener() {
|
|
||||||
return mOnPaymentListener;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,335 +0,0 @@
|
|||||||
package com.samsung.android.sdk.iap.lib.helper;
|
|
||||||
|
|
||||||
import android.app.Activity;
|
|
||||||
import android.content.ComponentName;
|
|
||||||
import android.content.Context;
|
|
||||||
import android.content.Intent;
|
|
||||||
import android.content.pm.PackageInfo;
|
|
||||||
import android.content.pm.PackageManager;
|
|
||||||
import android.content.pm.Signature;
|
|
||||||
import android.net.Uri;
|
|
||||||
import android.os.Build;
|
|
||||||
import android.provider.Settings;
|
|
||||||
import android.text.Html;
|
|
||||||
import android.util.Log;
|
|
||||||
|
|
||||||
import com.samsung.android.sdk.iap.lib.R;
|
|
||||||
import com.samsung.android.sdk.iap.lib.activity.BaseActivity;
|
|
||||||
import com.samsung.android.sdk.iap.lib.dialog.BaseDialogFragment;
|
|
||||||
import com.samsung.android.sdk.iap.lib.vo.ErrorVo;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Created by sangbum7.kim on 2017-08-17.
|
|
||||||
*/
|
|
||||||
|
|
||||||
public class HelperUtil {
|
|
||||||
private static final String TAG = HelperUtil.class.getSimpleName();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* show a dialog
|
|
||||||
*
|
|
||||||
* @param activity The activity adding the fragment that displays a dialog
|
|
||||||
* @param title The title to display
|
|
||||||
* @param message The message to display
|
|
||||||
* @param positiveListener The listener to be invoked when the positive button of the dialog is pressed
|
|
||||||
* @param negativeListener The listener to be invoked when the negative button of the dialog is pressed
|
|
||||||
*/
|
|
||||||
public static void showIapErrorDialog(
|
|
||||||
final Activity activity,
|
|
||||||
String title,
|
|
||||||
String message,
|
|
||||||
final BaseDialogFragment.OnClickListener positiveListener,
|
|
||||||
final BaseDialogFragment.OnClickListener negativeListener) {
|
|
||||||
showIapErrorDialog(activity, title, message, "", positiveListener, negativeListener);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* show a dialog
|
|
||||||
*
|
|
||||||
* @param activity The activity adding the fragment that displays a dialog
|
|
||||||
* @param title The title to display
|
|
||||||
* @param message The message to display
|
|
||||||
* @param messageExtra The extra message to display
|
|
||||||
* @param positiveListener The listener to be invoked when the positive button of the dialog is pressed
|
|
||||||
* @param negativeListener The listener to be invoked when the negative button of the dialog is pressed
|
|
||||||
*/
|
|
||||||
public static void showIapErrorDialog(
|
|
||||||
final Activity activity,
|
|
||||||
String title,
|
|
||||||
String message,
|
|
||||||
String messageExtra,
|
|
||||||
final BaseDialogFragment.OnClickListener positiveListener,
|
|
||||||
final BaseDialogFragment.OnClickListener negativeListener) {
|
|
||||||
new BaseDialogFragment()
|
|
||||||
.setDialogTitle(title)
|
|
||||||
.setDialogMessageText(message)
|
|
||||||
.setDialogMessageExtra(messageExtra)
|
|
||||||
.setDialogPositiveButton(activity.getString(android.R.string.ok), positiveListener)
|
|
||||||
.setDialogNegativeButton(activity.getString(android.R.string.cancel), negativeListener)
|
|
||||||
.show(activity.getFragmentManager(), BaseDialogFragment.IAP_DIALOG_TAG);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* show a dialog to update the Galaxy Store
|
|
||||||
*
|
|
||||||
* @param activity The activity adding the fragment that displays a dialog
|
|
||||||
*/
|
|
||||||
public static void showUpdateGalaxyStoreDialog(final Activity activity) {
|
|
||||||
// TODO: both title and message will be changed as UX Guide
|
|
||||||
new BaseDialogFragment()
|
|
||||||
.setDialogTitle(activity.getString(R.string.dream_ph_pheader_couldnt_complete_purchase))
|
|
||||||
.setDialogMessageText(activity.getString(
|
|
||||||
R.string.dream_ph_body_to_complete_this_purchase_you_need_to_update_the_galaxy_store))
|
|
||||||
.setDialogPositiveButton(
|
|
||||||
activity.getString(android.R.string.ok),
|
|
||||||
new BaseDialogFragment.OnClickListener() {
|
|
||||||
@Override
|
|
||||||
public void onClick() {
|
|
||||||
goGalaxyStoreDetailPage(activity.getApplicationContext());
|
|
||||||
activity.finish();
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.setDialogNegativeButton(
|
|
||||||
activity.getString(android.R.string.cancel),
|
|
||||||
new BaseDialogFragment.OnClickListener() {
|
|
||||||
@Override
|
|
||||||
public void onClick() {
|
|
||||||
activity.finish();
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.show(activity.getFragmentManager(), BaseDialogFragment.IAP_DIALOG_TAG);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* show a dialog to enable the Galaxy Store
|
|
||||||
*
|
|
||||||
* @param activity The activity adding the fragment that displays a dialog
|
|
||||||
*/
|
|
||||||
public static void showEnableGalaxyStoreDialog(final Activity activity) {
|
|
||||||
// TODO: both title and message will be changed as UX Guide
|
|
||||||
new BaseDialogFragment()
|
|
||||||
.setDialogTitle(activity.getString(R.string.dream_ph_pheader_couldnt_complete_purchase))
|
|
||||||
.setDialogMessageText(
|
|
||||||
activity.getString(R.string.dream_ph_body_to_complete_this_purchase_you_need_to_enable_the_galaxy_store_in_settings))
|
|
||||||
.setDialogPositiveButton(
|
|
||||||
activity.getString(android.R.string.ok),
|
|
||||||
new BaseDialogFragment.OnClickListener() {
|
|
||||||
@Override
|
|
||||||
public void onClick() {
|
|
||||||
Intent intent = new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS);
|
|
||||||
intent.setData(Uri.parse("package:" + HelperDefine.GALAXY_PACKAGE_NAME));
|
|
||||||
activity.startActivityForResult(intent, HelperDefine.REQUEST_CODE_IS_ENABLE_APPS);
|
|
||||||
|
|
||||||
activity.finish();
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.setDialogNegativeButton(
|
|
||||||
activity.getString(android.R.string.cancel),
|
|
||||||
new BaseDialogFragment.OnClickListener() {
|
|
||||||
@Override
|
|
||||||
public void onClick() {
|
|
||||||
activity.finish();
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.show(activity.getFragmentManager(), BaseDialogFragment.IAP_DIALOG_TAG);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* show a dialog to notice that the Galaxy Store is invalid
|
|
||||||
*
|
|
||||||
* @param activity The activity adding the fragment that displays a dialog
|
|
||||||
*/
|
|
||||||
public static void showInvalidGalaxyStoreDialog(final Activity activity) {
|
|
||||||
final String ERROR_ISSUER_IAP_CLIENT = "IC";
|
|
||||||
final int ERROR_CODE_INVALID_GALAXY_STORE = 10002;
|
|
||||||
|
|
||||||
String source = String.format(
|
|
||||||
activity.getString(
|
|
||||||
R.string.dream_ph_body_contact_p1sscustomer_servicep2ss_for_more_information_n_nerror_code_c_p3ss),
|
|
||||||
"<a href=\"http://help.content.samsung.com\">", "</a>",
|
|
||||||
ERROR_ISSUER_IAP_CLIENT + ERROR_CODE_INVALID_GALAXY_STORE);
|
|
||||||
|
|
||||||
CharSequence errorMessage;
|
|
||||||
// fromHtml(String) was deprecated in N OS
|
|
||||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.N) {
|
|
||||||
errorMessage = Html.fromHtml(source);
|
|
||||||
} else {
|
|
||||||
errorMessage = Html.fromHtml(source, Html.FROM_HTML_MODE_LEGACY);
|
|
||||||
}
|
|
||||||
new BaseDialogFragment()
|
|
||||||
.setDialogTitle(activity.getString(R.string.dream_ph_pheader_couldnt_complete_purchase))
|
|
||||||
.setDialogMessageText(errorMessage)
|
|
||||||
.setDialogMessageExtra(ERROR_ISSUER_IAP_CLIENT + ERROR_CODE_INVALID_GALAXY_STORE)
|
|
||||||
.setDialogPositiveButton(
|
|
||||||
activity.getString(android.R.string.ok),
|
|
||||||
new BaseDialogFragment.OnClickListener() {
|
|
||||||
@Override
|
|
||||||
public void onClick() {
|
|
||||||
activity.finish();
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.show(activity.getFragmentManager(), BaseDialogFragment.IAP_DIALOG_TAG);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void goGalaxyStoreDetailPage(Context context) {
|
|
||||||
// Link of Galaxy Store for IAP install
|
|
||||||
// ------------------------------------------------------------
|
|
||||||
Uri appsDeepLink = Uri.parse("samsungapps://StoreVersionInfo/");
|
|
||||||
// ------------------------------------------------------------
|
|
||||||
|
|
||||||
Intent intent = new Intent();
|
|
||||||
intent.setData(appsDeepLink);
|
|
||||||
|
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB_MR1) {
|
|
||||||
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK |
|
|
||||||
Intent.FLAG_ACTIVITY_CLEAR_TOP |
|
|
||||||
Intent.FLAG_INCLUDE_STOPPED_PACKAGES);
|
|
||||||
} else {
|
|
||||||
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK |
|
|
||||||
Intent.FLAG_ACTIVITY_CLEAR_TOP);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (intent.resolveActivity(context.getPackageManager()) != null) {
|
|
||||||
context.startActivity(intent);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Check that Galaxy Store is installed
|
|
||||||
*
|
|
||||||
* @param _context Context
|
|
||||||
* @return If it is true Galaxy Store is installed. otherwise, not installed.
|
|
||||||
*/
|
|
||||||
static public boolean isInstalledAppsPackage(Context _context) {
|
|
||||||
PackageManager pm = _context.getPackageManager();
|
|
||||||
try {
|
|
||||||
PackageInfo packageInfo = pm.getPackageInfo(HelperDefine.GALAXY_PACKAGE_NAME, PackageManager.GET_META_DATA);
|
|
||||||
int versionType = packageInfo.versionCode / 100000000;
|
|
||||||
Log.i(TAG, "isInstalledAppsPackage : " + packageInfo.versionCode + ", " + versionType);
|
|
||||||
switch (versionType) {
|
|
||||||
case 4: {
|
|
||||||
return packageInfo.versionCode >= HelperDefine.APPS_PACKAGE_VERSION;
|
|
||||||
}
|
|
||||||
case 5: {
|
|
||||||
return true;
|
|
||||||
// return packageInfo.versionCode >= HelperDefine.APPS_PACKAGE_VERSION_GO;
|
|
||||||
}
|
|
||||||
case 6: {
|
|
||||||
return packageInfo.versionCode >= HelperDefine.APPS_PACKAGE_VERSION_INDIA;
|
|
||||||
}
|
|
||||||
// Unverified version
|
|
||||||
default:
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
} catch (PackageManager.NameNotFoundException e) {
|
|
||||||
e.printStackTrace();
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static public boolean isEnabledAppsPackage(Context context) {
|
|
||||||
//// TODO: 2017-08-16 Make sure the status is normal
|
|
||||||
int status = context.getPackageManager().getApplicationEnabledSetting(HelperDefine.GALAXY_PACKAGE_NAME);
|
|
||||||
Log.i(TAG, "isEnabledAppsPackage: status " + status);
|
|
||||||
return !((status == PackageManager.COMPONENT_ENABLED_STATE_DISABLED) || (status == PackageManager.COMPONENT_ENABLED_STATE_DISABLED_USER));
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* check validation of installed Galaxy Store in your device
|
|
||||||
*
|
|
||||||
* @param _context
|
|
||||||
* @return If it is true Galaxy Store is valid. otherwise, is not valid.
|
|
||||||
*/
|
|
||||||
static public boolean isValidAppsPackage(Context _context) {
|
|
||||||
boolean result = true;
|
|
||||||
try {
|
|
||||||
Signature[] sigs = _context.getPackageManager().getPackageInfo(
|
|
||||||
HelperDefine.GALAXY_PACKAGE_NAME,
|
|
||||||
PackageManager.GET_SIGNATURES).signatures;
|
|
||||||
if (sigs[0].hashCode() != HelperDefine.APPS_SIGNATURE_HASHCODE) {
|
|
||||||
result = false;
|
|
||||||
}
|
|
||||||
} catch (Exception e) {
|
|
||||||
e.printStackTrace();
|
|
||||||
result = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* SamsungAccount authentication
|
|
||||||
*
|
|
||||||
* @param _activity
|
|
||||||
*/
|
|
||||||
static public boolean startAccountActivity(final Activity _activity) {
|
|
||||||
ComponentName com = new ComponentName(HelperDefine.GALAXY_PACKAGE_NAME,
|
|
||||||
HelperDefine.IAP_PACKAGE_NAME + ".activity.AccountActivity");
|
|
||||||
Context context = _activity.getApplicationContext();
|
|
||||||
|
|
||||||
Intent intent = new Intent();
|
|
||||||
intent.setComponent(com);
|
|
||||||
|
|
||||||
if (intent.resolveActivity(context.getPackageManager()) != null) {
|
|
||||||
_activity.startActivityForResult(intent,
|
|
||||||
HelperDefine.REQUEST_CODE_IS_ACCOUNT_CERTIFICATION);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* go to about page of Galaxy Store in order to install IAP package.
|
|
||||||
*/
|
|
||||||
static public void installAppsPackage(final BaseActivity _activity) {
|
|
||||||
// Set error in order to notify result to third-party application.
|
|
||||||
// ====================================================================
|
|
||||||
ErrorVo errorVo = new ErrorVo();
|
|
||||||
_activity.setErrorVo(errorVo);
|
|
||||||
|
|
||||||
errorVo.setError(
|
|
||||||
HelperDefine.IAP_PAYMENT_IS_CANCELED,
|
|
||||||
_activity.getString(R.string.mids_sapps_pop_payment_canceled));
|
|
||||||
// ====================================================================
|
|
||||||
|
|
||||||
// Show information dialog
|
|
||||||
// ====================================================================
|
|
||||||
showUpdateGalaxyStoreDialog(_activity);
|
|
||||||
// ====================================================================
|
|
||||||
}
|
|
||||||
|
|
||||||
static public int checkAppsPackage(Context _context) {
|
|
||||||
// 1. If Galaxy Store is installed
|
|
||||||
// ====================================================================
|
|
||||||
if (HelperUtil.isInstalledAppsPackage(_context)) {
|
|
||||||
// 1) If Galaxy Store is enabled
|
|
||||||
// ================================================================
|
|
||||||
if (!HelperUtil.isEnabledAppsPackage(_context)) {
|
|
||||||
return HelperDefine.DIALOG_TYPE_DISABLE_APPLICATION;
|
|
||||||
// ================================================================
|
|
||||||
// 2) If Galaxy Store is valid
|
|
||||||
// ================================================================
|
|
||||||
} else if (HelperUtil.isValidAppsPackage(_context)) {
|
|
||||||
return HelperDefine.DIALOG_TYPE_NONE;
|
|
||||||
} else {
|
|
||||||
// ------------------------------------------------------------
|
|
||||||
// show alert dialog if Galaxy Store is invalid
|
|
||||||
// ------------------------------------------------------------
|
|
||||||
return HelperDefine.DIALOG_TYPE_INVALID_PACKAGE;
|
|
||||||
// ------------------------------------------------------------
|
|
||||||
}
|
|
||||||
// ================================================================
|
|
||||||
|
|
||||||
// ====================================================================
|
|
||||||
// 2. If Galaxy Store is not installed
|
|
||||||
// ====================================================================
|
|
||||||
} else {
|
|
||||||
// When user click the OK button on the dialog,
|
|
||||||
// go to Galaxy Store Detail page
|
|
||||||
// ====================================================================
|
|
||||||
return HelperDefine.DIALOG_TYPE_APPS_DETAIL;
|
|
||||||
}
|
|
||||||
// ====================================================================
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,642 +0,0 @@
|
|||||||
package com.samsung.android.sdk.iap.lib.helper;
|
|
||||||
|
|
||||||
import android.content.ComponentName;
|
|
||||||
import android.content.Context;
|
|
||||||
import android.content.Intent;
|
|
||||||
import android.content.ServiceConnection;
|
|
||||||
import android.os.AsyncTask.Status;
|
|
||||||
import android.os.IBinder;
|
|
||||||
import android.text.TextUtils;
|
|
||||||
import android.util.Base64;
|
|
||||||
import android.util.Log;
|
|
||||||
|
|
||||||
import com.samsung.android.iap.IAPConnector;
|
|
||||||
import com.samsung.android.sdk.iap.lib.R;
|
|
||||||
import com.samsung.android.sdk.iap.lib.activity.CheckPackageActivity;
|
|
||||||
import com.samsung.android.sdk.iap.lib.activity.PaymentActivity;
|
|
||||||
import com.samsung.android.sdk.iap.lib.helper.HelperDefine;
|
|
||||||
import com.samsung.android.sdk.iap.lib.helper.task.ConsumePurchasedItemsTask;
|
|
||||||
import com.samsung.android.sdk.iap.lib.helper.task.GetOwnedListTask;
|
|
||||||
import com.samsung.android.sdk.iap.lib.helper.task.GetProductsDetailsTask;
|
|
||||||
import com.samsung.android.sdk.iap.lib.listener.OnConsumePurchasedItemsListener;
|
|
||||||
import com.samsung.android.sdk.iap.lib.listener.OnGetOwnedListListener;
|
|
||||||
import com.samsung.android.sdk.iap.lib.listener.OnGetProductsDetailsListener;
|
|
||||||
import com.samsung.android.sdk.iap.lib.listener.OnPaymentListener;
|
|
||||||
import com.samsung.android.sdk.iap.lib.service.BaseService;
|
|
||||||
import com.samsung.android.sdk.iap.lib.service.ConsumePurchasedItems;
|
|
||||||
import com.samsung.android.sdk.iap.lib.service.OwnedProduct;
|
|
||||||
import com.samsung.android.sdk.iap.lib.service.ProductsDetails;
|
|
||||||
import com.samsung.android.sdk.iap.lib.vo.ErrorVo;
|
|
||||||
|
|
||||||
import java.util.ArrayList;
|
|
||||||
|
|
||||||
public class IapHelper extends HelperDefine {
|
|
||||||
private static final String TAG = IapHelper.class.getSimpleName();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Please double-check this mode before release.
|
|
||||||
*/
|
|
||||||
private int mMode = HelperDefine.OperationMode.OPERATION_MODE_PRODUCTION.getValue();
|
|
||||||
// ========================================================================
|
|
||||||
|
|
||||||
private Context mContext = null;
|
|
||||||
|
|
||||||
private IAPConnector mIapConnector = null;
|
|
||||||
private ServiceConnection mServiceConn = null;
|
|
||||||
|
|
||||||
// AsyncTask for API
|
|
||||||
// ========================================================================
|
|
||||||
private com.samsung.android.sdk.iap.lib.helper.task.GetProductsDetailsTask mGetProductsDetailsTask = null;
|
|
||||||
private com.samsung.android.sdk.iap.lib.helper.task.GetOwnedListTask mGetOwnedListTask = null;
|
|
||||||
private com.samsung.android.sdk.iap.lib.helper.task.ConsumePurchasedItemsTask mConsumePurchasedItemsTask = null;
|
|
||||||
// ========================================================================
|
|
||||||
|
|
||||||
private ArrayList<BaseService> mServiceQueue = new ArrayList<BaseService>();
|
|
||||||
private BaseService mCurrentService = null;
|
|
||||||
|
|
||||||
// API listener
|
|
||||||
private HelperListenerManager mListenerInstance = null;
|
|
||||||
|
|
||||||
private static IapHelper mInstance = null;
|
|
||||||
|
|
||||||
// State of IAP Service
|
|
||||||
// ========================================================================
|
|
||||||
private int mState = HelperDefine.STATE_TERM;
|
|
||||||
private final static Object mOperationLock = new Object();
|
|
||||||
static boolean mOperationRunningFlag = false;
|
|
||||||
private boolean mShowErrorDialog = true;
|
|
||||||
|
|
||||||
// ########################################################################
|
|
||||||
// ########################################################################
|
|
||||||
// 1. SamsungIAPHeler object create and reference
|
|
||||||
// ########################################################################
|
|
||||||
// ########################################################################
|
|
||||||
|
|
||||||
/**
|
|
||||||
* IapHelper constructor
|
|
||||||
*
|
|
||||||
* @param _context
|
|
||||||
*/
|
|
||||||
private IapHelper(Context _context) {
|
|
||||||
_setContextAndMode(_context);
|
|
||||||
_setListenerInstance();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* IapHelper singleton reference method
|
|
||||||
*
|
|
||||||
* @param _context Context
|
|
||||||
*/
|
|
||||||
public static IapHelper getInstance(Context _context) {
|
|
||||||
Log.i(TAG, "IAP Helper version : " + HelperDefine.HELPER_VERSION);
|
|
||||||
if (null == mInstance) {
|
|
||||||
mInstance = new IapHelper(_context);
|
|
||||||
} else {
|
|
||||||
mInstance._setContextAndMode(_context);
|
|
||||||
}
|
|
||||||
|
|
||||||
return mInstance;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setOperationMode(OperationMode _mode) {
|
|
||||||
mMode = _mode.getValue();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void _setContextAndMode(Context _context) {
|
|
||||||
mContext = _context.getApplicationContext();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void _setListenerInstance() {
|
|
||||||
if (mListenerInstance != null) {
|
|
||||||
mListenerInstance.destroy();
|
|
||||||
mListenerInstance = null;
|
|
||||||
}
|
|
||||||
mListenerInstance = HelperListenerManager.getInstance();
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// ########################################################################
|
|
||||||
// ########################################################################
|
|
||||||
// 2. Binding for IAPService
|
|
||||||
// ########################################################################
|
|
||||||
// ########################################################################
|
|
||||||
|
|
||||||
/**
|
|
||||||
* bind to IAPService
|
|
||||||
*/
|
|
||||||
public void bindIapService() {
|
|
||||||
Log.i(TAG, "bindIapService()");
|
|
||||||
// exit If already bound
|
|
||||||
// ====================================================================
|
|
||||||
if (mState >= HelperDefine.STATE_BINDING) {
|
|
||||||
onBindIapFinished(HelperDefine.IAP_RESPONSE_RESULT_OK);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
// ====================================================================
|
|
||||||
|
|
||||||
// Connection to IAP service
|
|
||||||
// ====================================================================
|
|
||||||
mServiceConn = new ServiceConnection() {
|
|
||||||
@Override
|
|
||||||
public void onServiceDisconnected(ComponentName _name) {
|
|
||||||
Log.i(TAG, "IAP Service Disconnected...");
|
|
||||||
|
|
||||||
mState = HelperDefine.STATE_TERM;
|
|
||||||
mIapConnector = null;
|
|
||||||
mServiceConn = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onServiceConnected
|
|
||||||
(
|
|
||||||
ComponentName _name,
|
|
||||||
IBinder _service
|
|
||||||
) {
|
|
||||||
Log.i(TAG, "IAP Service Connected...");
|
|
||||||
mIapConnector = IAPConnector.Stub.asInterface(_service);
|
|
||||||
|
|
||||||
if (mIapConnector != null) {
|
|
||||||
mState = HelperDefine.STATE_BINDING;
|
|
||||||
onBindIapFinished(HelperDefine.IAP_RESPONSE_RESULT_OK);
|
|
||||||
} else {
|
|
||||||
mState = HelperDefine.STATE_TERM;
|
|
||||||
onBindIapFinished(HelperDefine.IAP_RESPONSE_RESULT_UNAVAILABLE);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
// ====================================================================
|
|
||||||
Intent serviceIntent = new Intent();
|
|
||||||
serviceIntent.setComponent(new ComponentName(HelperDefine.GALAXY_PACKAGE_NAME, HelperDefine.IAP_SERVICE_NAME));
|
|
||||||
|
|
||||||
// bind to IAPService
|
|
||||||
// ====================================================================
|
|
||||||
try {
|
|
||||||
if (mContext == null || mContext.bindService(serviceIntent,
|
|
||||||
mServiceConn,
|
|
||||||
Context.BIND_AUTO_CREATE) == false) {
|
|
||||||
mState = HelperDefine.STATE_TERM;
|
|
||||||
onBindIapFinished(HelperDefine.IAP_RESPONSE_RESULT_UNAVAILABLE);
|
|
||||||
}
|
|
||||||
} catch (SecurityException e) {
|
|
||||||
Log.e(TAG, "SecurityException : " + e);
|
|
||||||
onBindIapFinished(HelperDefine.IAP_RESPONSE_RESULT_UNAVAILABLE);
|
|
||||||
}
|
|
||||||
// ====================================================================
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
protected void onBindIapFinished(int _result) {
|
|
||||||
Log.i(TAG, "onBindIapFinished");
|
|
||||||
if (_result == HelperDefine.IAP_RESPONSE_RESULT_OK) {
|
|
||||||
if (getServiceProcess() != null) {
|
|
||||||
getServiceProcess().runServiceProcess();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// ============================================================
|
|
||||||
// 2) If IAPService is not bound.
|
|
||||||
// ============================================================
|
|
||||||
else {
|
|
||||||
if (getServiceProcess() != null) {
|
|
||||||
ErrorVo errorVo = new ErrorVo();
|
|
||||||
errorVo.setError(HelperDefine.IAP_ERROR_INITIALIZATION,
|
|
||||||
mContext.getString(R.string.mids_sapps_pop_unknown_error_occurred) + "[Lib_Bind]");
|
|
||||||
errorVo.setShowDialog(mShowErrorDialog);
|
|
||||||
getServiceProcess().setErrorVo(errorVo);
|
|
||||||
getServiceProcess().onEndProcess();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/* ########################################################################
|
|
||||||
* ########################################################################
|
|
||||||
* 3. IAP APIs.
|
|
||||||
* ########################################################################
|
|
||||||
* ##################################################################### */
|
|
||||||
///////////////////////////////////////////////////////////////////////////
|
|
||||||
// 3.1) getProductsDetails ///////////////////////////////////////////////////
|
|
||||||
///////////////////////////////////////////////////////////////////////////
|
|
||||||
|
|
||||||
/**
|
|
||||||
* <PRE>
|
|
||||||
* This load item list by starting productActivity in this library, and the result will be sent to {@link OnGetProductsDetailsListener} Callback
|
|
||||||
* interface.
|
|
||||||
*
|
|
||||||
* </PRE>
|
|
||||||
*
|
|
||||||
* @param _productIds
|
|
||||||
* @param _onGetProductsDetailsListener
|
|
||||||
*/
|
|
||||||
public void getProductsDetails
|
|
||||||
(
|
|
||||||
String _productIds,
|
|
||||||
OnGetProductsDetailsListener _onGetProductsDetailsListener
|
|
||||||
) {
|
|
||||||
try {
|
|
||||||
if (_onGetProductsDetailsListener == null) {
|
|
||||||
throw new Exception("_onGetProductsDetailsListener is null");
|
|
||||||
}
|
|
||||||
|
|
||||||
ProductsDetails productsDetails = new ProductsDetails(mInstance, mContext, _onGetProductsDetailsListener);
|
|
||||||
productsDetails.setProductId(_productIds);
|
|
||||||
setServiceProcess(productsDetails);
|
|
||||||
|
|
||||||
IapStartInProgressFlag();
|
|
||||||
checkAppsPackage();
|
|
||||||
} catch (IapInProgressException e) {
|
|
||||||
e.printStackTrace();
|
|
||||||
} catch (Exception e) {
|
|
||||||
e.printStackTrace();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* execute GetProductsDetailsTask
|
|
||||||
*/
|
|
||||||
public boolean safeGetProductsDetails
|
|
||||||
(
|
|
||||||
ProductsDetails _baseService,
|
|
||||||
String _productIDs,
|
|
||||||
boolean _showErrorDialog
|
|
||||||
) {
|
|
||||||
try {
|
|
||||||
if (mGetProductsDetailsTask != null &&
|
|
||||||
mGetProductsDetailsTask.getStatus() != Status.FINISHED) {
|
|
||||||
mGetProductsDetailsTask.cancel(true);
|
|
||||||
}
|
|
||||||
if (mIapConnector == null || mContext == null) {
|
|
||||||
return false;
|
|
||||||
} else {
|
|
||||||
mGetProductsDetailsTask = new com.samsung.android.sdk.iap.lib.helper.task.GetProductsDetailsTask(_baseService,
|
|
||||||
mIapConnector,
|
|
||||||
mContext,
|
|
||||||
_productIDs,
|
|
||||||
_showErrorDialog,
|
|
||||||
mMode);
|
|
||||||
mGetProductsDetailsTask.execute();
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
} catch (Exception e) {
|
|
||||||
e.printStackTrace();
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
///////////////////////////////////////////////////////////////////////////
|
|
||||||
// 3.2) getOwnedList //////////////////////////////////////////////////////
|
|
||||||
///////////////////////////////////////////////////////////////////////////
|
|
||||||
|
|
||||||
/**
|
|
||||||
* <PRE>
|
|
||||||
* This load owned product list by starting OwnedListActivity in this library, and the result will be sent to {@link OnGetOwnedListListener}
|
|
||||||
* Callback interface.
|
|
||||||
*
|
|
||||||
* </PRE>
|
|
||||||
*
|
|
||||||
* @param _productType
|
|
||||||
* @param _onGetOwnedListListener
|
|
||||||
*/
|
|
||||||
public boolean getOwnedList
|
|
||||||
(
|
|
||||||
String _productType,
|
|
||||||
OnGetOwnedListListener _onGetOwnedListListener
|
|
||||||
) {
|
|
||||||
Log.i(TAG, "getOwnedList");
|
|
||||||
try {
|
|
||||||
if (_onGetOwnedListListener == null) {
|
|
||||||
throw new Exception("_onGetOwnedListListener is null");
|
|
||||||
}
|
|
||||||
if (TextUtils.isEmpty(_productType)) {
|
|
||||||
throw new Exception("_productType is null or empty");
|
|
||||||
}
|
|
||||||
|
|
||||||
OwnedProduct ownedProduct = new OwnedProduct(mInstance, mContext, _onGetOwnedListListener);
|
|
||||||
ownedProduct.setProductType(_productType);
|
|
||||||
setServiceProcess(ownedProduct);
|
|
||||||
|
|
||||||
IapStartInProgressFlag();
|
|
||||||
checkAppsPackage();
|
|
||||||
} catch (IapInProgressException e) {
|
|
||||||
e.printStackTrace();
|
|
||||||
} catch (Exception e) {
|
|
||||||
e.printStackTrace();
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* execute GetOwnedListTask
|
|
||||||
*/
|
|
||||||
public boolean safeGetOwnedList
|
|
||||||
(
|
|
||||||
OwnedProduct _baseService,
|
|
||||||
String _productType,
|
|
||||||
boolean _showErrorDialog
|
|
||||||
) {
|
|
||||||
try {
|
|
||||||
if (mGetOwnedListTask != null &&
|
|
||||||
mGetOwnedListTask.getStatus() != Status.FINISHED) {
|
|
||||||
mGetOwnedListTask.cancel(true);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (mIapConnector == null || mContext == null) {
|
|
||||||
return false;
|
|
||||||
} else {
|
|
||||||
mGetOwnedListTask = new com.samsung.android.sdk.iap.lib.helper.task.GetOwnedListTask(_baseService,
|
|
||||||
mIapConnector,
|
|
||||||
mContext,
|
|
||||||
_productType,
|
|
||||||
_showErrorDialog,
|
|
||||||
mMode);
|
|
||||||
|
|
||||||
mGetOwnedListTask.execute();
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
} catch (Exception e) {
|
|
||||||
e.printStackTrace();
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
///////////////////////////////////////////////////////////////////////////
|
|
||||||
// 3.3) consumePurchasedItems /////////////////////////////////////////////
|
|
||||||
///////////////////////////////////////////////////////////////////////////
|
|
||||||
|
|
||||||
/**
|
|
||||||
* <PRE>
|
|
||||||
* This load item list by starting OwnedListActivity in this library, and the result will be sent to {@link OnConsumePurchasedItemsListener}
|
|
||||||
* Callback interface.
|
|
||||||
*
|
|
||||||
* </PRE>
|
|
||||||
*
|
|
||||||
* @param _purchaseIds
|
|
||||||
* @param _onConsumePurchasedItemsListener
|
|
||||||
*/
|
|
||||||
public boolean consumePurchasedItems
|
|
||||||
(
|
|
||||||
String _purchaseIds,
|
|
||||||
OnConsumePurchasedItemsListener _onConsumePurchasedItemsListener
|
|
||||||
) {
|
|
||||||
try {
|
|
||||||
if (_onConsumePurchasedItemsListener == null) {
|
|
||||||
throw new Exception("_onConsumePurchasedItemsListener is null");
|
|
||||||
}
|
|
||||||
if (TextUtils.isEmpty(_purchaseIds)) {
|
|
||||||
throw new Exception("_purchaseIds is null or empty");
|
|
||||||
}
|
|
||||||
|
|
||||||
ConsumePurchasedItems consumePurchasedItems = new ConsumePurchasedItems(mInstance, mContext, _onConsumePurchasedItemsListener);
|
|
||||||
consumePurchasedItems.setPurchaseIds(_purchaseIds);
|
|
||||||
setServiceProcess(consumePurchasedItems);
|
|
||||||
|
|
||||||
IapStartInProgressFlag();
|
|
||||||
checkAppsPackage();
|
|
||||||
} catch (IapInProgressException e) {
|
|
||||||
e.printStackTrace();
|
|
||||||
} catch (Exception e) {
|
|
||||||
e.printStackTrace();
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* execute ConsumePurchasedItemsTask
|
|
||||||
*/
|
|
||||||
public boolean safeConsumePurchasedItems
|
|
||||||
(
|
|
||||||
ConsumePurchasedItems _baseService,
|
|
||||||
String _purchaseIds,
|
|
||||||
boolean _showErrorDialog
|
|
||||||
) {
|
|
||||||
try {
|
|
||||||
if (mConsumePurchasedItemsTask != null &&
|
|
||||||
mConsumePurchasedItemsTask.getStatus() != Status.FINISHED) {
|
|
||||||
mConsumePurchasedItemsTask.cancel(true);
|
|
||||||
}
|
|
||||||
|
|
||||||
mConsumePurchasedItemsTask = new com.samsung.android.sdk.iap.lib.helper.task.ConsumePurchasedItemsTask(_baseService,
|
|
||||||
mIapConnector,
|
|
||||||
mContext,
|
|
||||||
_purchaseIds,
|
|
||||||
_showErrorDialog,
|
|
||||||
mMode);
|
|
||||||
mConsumePurchasedItemsTask.execute();
|
|
||||||
return true;
|
|
||||||
} catch (Exception e) {
|
|
||||||
e.printStackTrace();
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
///////////////////////////////////////////////////////////////////////////
|
|
||||||
// 3.4) startPurchase / ///////////////////////////////////////////////////
|
|
||||||
///////////////////////////////////////////////////////////////////////////
|
|
||||||
|
|
||||||
/**
|
|
||||||
* <PRE>
|
|
||||||
* Start payment process by starting {@link PaymentActivity} in this library, and result will be sent to {@link OnPaymentListener} interface. To
|
|
||||||
* do that, PaymentActivity must be described in AndroidManifest.xml of third-party application as below.
|
|
||||||
* <p>
|
|
||||||
* <activity android:name="com.sec.android.iap.lib.activity.PaymentActivity" android:theme="@style/Theme.Empty"
|
|
||||||
* android:configChanges="orientation|screenSize"/>
|
|
||||||
* </PRE>
|
|
||||||
*
|
|
||||||
* @param _itemId
|
|
||||||
* @param _passThroughParam
|
|
||||||
* @param _onPaymentListener
|
|
||||||
*/
|
|
||||||
public boolean startPayment
|
|
||||||
(
|
|
||||||
String _itemId,
|
|
||||||
String _passThroughParam,
|
|
||||||
OnPaymentListener _onPaymentListener
|
|
||||||
) {
|
|
||||||
try {
|
|
||||||
if (_onPaymentListener == null) {
|
|
||||||
throw new Exception("OnPaymentListener is null");
|
|
||||||
}
|
|
||||||
if (TextUtils.isEmpty(_itemId)) {
|
|
||||||
throw new Exception("_itemId is null or empty");
|
|
||||||
}
|
|
||||||
if (_passThroughParam != null && _passThroughParam.getBytes("UTF-8").length > HelperDefine.PASSTHROGUH_MAX_LENGTH) {
|
|
||||||
throw new Exception("PassThroughParam length exceeded (MAX " + HelperDefine.PASSTHROGUH_MAX_LENGTH + ")");
|
|
||||||
}
|
|
||||||
|
|
||||||
IapStartInProgressFlag();
|
|
||||||
mListenerInstance.setOnPaymentListener(_onPaymentListener);
|
|
||||||
|
|
||||||
Intent intent = new Intent(mContext, PaymentActivity.class);
|
|
||||||
intent.putExtra("ItemId", _itemId);
|
|
||||||
String encodedPassThroughParam = "";
|
|
||||||
if (_passThroughParam != null) {
|
|
||||||
encodedPassThroughParam = Base64.encodeToString(_passThroughParam.getBytes("UTF-8"), 0);
|
|
||||||
}
|
|
||||||
intent.putExtra("PassThroughParam", encodedPassThroughParam);
|
|
||||||
intent.putExtra("ShowErrorDialog", mShowErrorDialog);
|
|
||||||
intent.putExtra("OperationMode", mMode);
|
|
||||||
Log.i(TAG, "startPayment: " + mMode);
|
|
||||||
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
|
|
||||||
|
|
||||||
mContext.startActivity(intent);
|
|
||||||
} catch (IapInProgressException e) {
|
|
||||||
e.printStackTrace();
|
|
||||||
return false;
|
|
||||||
} catch (Exception e) {
|
|
||||||
e.printStackTrace();
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* <PRE>
|
|
||||||
* Start payment process by starting {@link PaymentActivity} in this library, and result will be sent to {@link OnPaymentListener} interface. To
|
|
||||||
* do that, PaymentActivity must be described in AndroidManifest.xml of third-party application as below.
|
|
||||||
* <p>
|
|
||||||
* <activity android:name="com.sec.android.iap.lib.activity.PaymentActivity" android:theme="@style/Theme.Empty"
|
|
||||||
* android:configChanges="orientation|screenSize"/>
|
|
||||||
* </PRE>
|
|
||||||
*
|
|
||||||
* @param _itemId
|
|
||||||
* @param _passThroughParam
|
|
||||||
* @param _showSuccessDialog Unused parameter.
|
|
||||||
* @param _onPaymentListener
|
|
||||||
* @deprecated
|
|
||||||
*/
|
|
||||||
public boolean startPayment
|
|
||||||
(
|
|
||||||
String _itemId,
|
|
||||||
String _passThroughParam,
|
|
||||||
boolean _showSuccessDialog,
|
|
||||||
OnPaymentListener _onPaymentListener
|
|
||||||
) {
|
|
||||||
return startPayment(_itemId, _passThroughParam, _onPaymentListener);
|
|
||||||
}
|
|
||||||
|
|
||||||
// ########################################################################
|
|
||||||
// ########################################################################
|
|
||||||
// 4. etc
|
|
||||||
// ########################################################################
|
|
||||||
// ########################################################################
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Stop running task, {@link GetProductsDetailsTask}, {@link ConsumePurchasedItemsTask} or {@link GetOwnedListTask} } before dispose().
|
|
||||||
*/
|
|
||||||
private void stopTasksIfNotFinished() {
|
|
||||||
if (mGetProductsDetailsTask != null) {
|
|
||||||
if (mGetProductsDetailsTask.getStatus() != Status.FINISHED) {
|
|
||||||
Log.e(TAG, "stopTasksIfNotFinished: mGetProductsDetailsTask Status > " + mGetProductsDetailsTask.getStatus());
|
|
||||||
mGetProductsDetailsTask.cancel(true);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (mGetOwnedListTask != null) {
|
|
||||||
if (mGetOwnedListTask.getStatus() != Status.FINISHED) {
|
|
||||||
Log.e(TAG, "stopTasksIfNotFinished: mGetOwnedListTask Status > " + mGetOwnedListTask.getStatus());
|
|
||||||
mGetOwnedListTask.cancel(true);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (mConsumePurchasedItemsTask != null) {
|
|
||||||
if (mConsumePurchasedItemsTask.getStatus() != Status.FINISHED) {
|
|
||||||
Log.e(TAG, "stopTasksIfNotFinished: mConsumePurchasedItemsTask Status > " + mConsumePurchasedItemsTask.getStatus());
|
|
||||||
mConsumePurchasedItemsTask.cancel(true);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Unbind from IAPService and release used resources.
|
|
||||||
*/
|
|
||||||
public void dispose() {
|
|
||||||
stopTasksIfNotFinished();
|
|
||||||
|
|
||||||
if (mContext != null && mServiceConn != null) {
|
|
||||||
mContext.unbindService(mServiceConn);
|
|
||||||
}
|
|
||||||
|
|
||||||
mState = HelperDefine.STATE_TERM;
|
|
||||||
mServiceConn = null;
|
|
||||||
mIapConnector = null;
|
|
||||||
clearServiceProcess();
|
|
||||||
IapEndInProgressFlag();
|
|
||||||
}
|
|
||||||
|
|
||||||
void IapStartInProgressFlag() throws IapInProgressException {
|
|
||||||
Log.i(TAG, "IapStartInProgressFlag");
|
|
||||||
synchronized (mOperationLock) {
|
|
||||||
if (mOperationRunningFlag) {
|
|
||||||
throw new IapInProgressException("another operation is running");
|
|
||||||
}
|
|
||||||
mOperationRunningFlag = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void IapEndInProgressFlag() {
|
|
||||||
Log.i(TAG, "IapEndInProgressFlag");
|
|
||||||
synchronized (mOperationLock) {
|
|
||||||
mOperationRunningFlag = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
protected static class IapInProgressException extends Exception {
|
|
||||||
public IapInProgressException(String message) {
|
|
||||||
super(message);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void checkAppsPackage() {
|
|
||||||
int checkResult = HelperUtil.checkAppsPackage(mContext);
|
|
||||||
if (checkResult == HelperDefine.DIALOG_TYPE_NONE) {
|
|
||||||
bindIapService();
|
|
||||||
} else {
|
|
||||||
Intent intent = new Intent(mContext, CheckPackageActivity.class);
|
|
||||||
intent.putExtra("DialogType", checkResult);
|
|
||||||
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
|
|
||||||
mContext.startActivity(intent);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sets whether error popup is displayed when payment is finished.
|
|
||||||
*/
|
|
||||||
public void setShowErrorDialog(boolean _showErrorDialog) {
|
|
||||||
this.mShowErrorDialog = _showErrorDialog;
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean getShowErrorDialog() {
|
|
||||||
return this.mShowErrorDialog;
|
|
||||||
}
|
|
||||||
|
|
||||||
public BaseService getServiceProcess() {
|
|
||||||
return getServiceProcess(false);
|
|
||||||
}
|
|
||||||
|
|
||||||
public BaseService getServiceProcess(boolean _nextProcess) {
|
|
||||||
if (mCurrentService == null || _nextProcess) {
|
|
||||||
mCurrentService = null;
|
|
||||||
if (mServiceQueue.size() > 0) {
|
|
||||||
mCurrentService = mServiceQueue.get(0);
|
|
||||||
mServiceQueue.remove(0);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return mCurrentService;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void setServiceProcess(BaseService _baseService) {
|
|
||||||
mServiceQueue.add(_baseService);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void clearServiceProcess() {
|
|
||||||
do {
|
|
||||||
if (mCurrentService != null) {
|
|
||||||
mCurrentService.releaseProcess();
|
|
||||||
}
|
|
||||||
mCurrentService = getServiceProcess(true);
|
|
||||||
} while (mCurrentService != null);
|
|
||||||
mServiceQueue.clear();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,65 +0,0 @@
|
|||||||
package com.samsung.android.sdk.iap.lib.helper.task;
|
|
||||||
|
|
||||||
import android.content.Context;
|
|
||||||
import android.os.AsyncTask;
|
|
||||||
import android.util.Log;
|
|
||||||
|
|
||||||
import com.samsung.android.iap.IAPConnector;
|
|
||||||
import com.samsung.android.sdk.iap.lib.R;
|
|
||||||
import com.samsung.android.sdk.iap.lib.helper.HelperDefine;
|
|
||||||
import com.samsung.android.sdk.iap.lib.service.BaseService;
|
|
||||||
import com.samsung.android.sdk.iap.lib.vo.ErrorVo;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Created by sangbum7.kim on 2017-09-01.
|
|
||||||
*/
|
|
||||||
|
|
||||||
public class BaseTask extends AsyncTask<String, Object, Boolean> {
|
|
||||||
private static final String TAG = BaseTask.class.getSimpleName();
|
|
||||||
|
|
||||||
protected BaseService mBaseService;
|
|
||||||
protected IAPConnector mIapConnector;
|
|
||||||
protected Context mContext;
|
|
||||||
protected int mMode;
|
|
||||||
protected String mPackageName = "";
|
|
||||||
|
|
||||||
protected ErrorVo mErrorVo = new ErrorVo();
|
|
||||||
|
|
||||||
public BaseTask(BaseService _baseService,
|
|
||||||
IAPConnector _iapConnector,
|
|
||||||
Context _context,
|
|
||||||
boolean _showErrorDialog,
|
|
||||||
int _mode) {
|
|
||||||
|
|
||||||
mBaseService = _baseService;
|
|
||||||
mIapConnector = _iapConnector;
|
|
||||||
mContext = _context;
|
|
||||||
if (mContext != null) {
|
|
||||||
mPackageName = mContext.getPackageName();
|
|
||||||
}
|
|
||||||
mMode = _mode;
|
|
||||||
mErrorVo.setShowDialog(_showErrorDialog);
|
|
||||||
mBaseService.setErrorVo(mErrorVo);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected Boolean doInBackground(String... params) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void onPostExecute(Boolean _result) {
|
|
||||||
// ================================================================
|
|
||||||
if (!_result) {
|
|
||||||
mErrorVo.setError(mErrorVo.getErrorCode(), mContext.getString(R.string.mids_sapps_pop_unknown_error_occurred));
|
|
||||||
}
|
|
||||||
// ================================================================
|
|
||||||
|
|
||||||
mBaseService.onEndProcess();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void onCancelled() {
|
|
||||||
Log.e(TAG, "onCancelled: task cancelled");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,105 +0,0 @@
|
|||||||
package com.samsung.android.sdk.iap.lib.helper.task;
|
|
||||||
|
|
||||||
import android.content.Context;
|
|
||||||
import android.os.Bundle;
|
|
||||||
import android.util.Log;
|
|
||||||
|
|
||||||
import com.samsung.android.iap.IAPConnector;
|
|
||||||
import com.samsung.android.sdk.iap.lib.R;
|
|
||||||
import com.samsung.android.sdk.iap.lib.helper.HelperDefine;
|
|
||||||
import com.samsung.android.sdk.iap.lib.service.ConsumePurchasedItems;
|
|
||||||
import com.samsung.android.sdk.iap.lib.vo.ConsumeVo;
|
|
||||||
|
|
||||||
import java.util.ArrayList;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Asynchronized Task to load a list of items
|
|
||||||
*/
|
|
||||||
public class ConsumePurchasedItemsTask extends BaseTask {
|
|
||||||
private static final String TAG = GetOwnedListTask.class.getSimpleName();
|
|
||||||
private String mPurchaseIds = "";
|
|
||||||
|
|
||||||
ArrayList<ConsumeVo> mConsumeList = new ArrayList<ConsumeVo>();
|
|
||||||
|
|
||||||
public ConsumePurchasedItemsTask
|
|
||||||
(
|
|
||||||
ConsumePurchasedItems _baseService,
|
|
||||||
IAPConnector _iapConnector,
|
|
||||||
Context _context,
|
|
||||||
String _purchaseIds,
|
|
||||||
boolean _showErrorDialog,
|
|
||||||
int _mode
|
|
||||||
) {
|
|
||||||
super(_baseService, _iapConnector, _context, _showErrorDialog, _mode);
|
|
||||||
mPurchaseIds = _purchaseIds;
|
|
||||||
|
|
||||||
_baseService.setConsumeList(mConsumeList);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected Boolean doInBackground(String... params) {
|
|
||||||
try {
|
|
||||||
// 1) call getItemList() method of IAPService
|
|
||||||
// ============================================================
|
|
||||||
Bundle bundle = mIapConnector.consumePurchasedItems(
|
|
||||||
mPackageName,
|
|
||||||
mPurchaseIds,
|
|
||||||
mMode);
|
|
||||||
// ============================================================
|
|
||||||
|
|
||||||
// 2) save status code, error string and extra String.
|
|
||||||
// ============================================================
|
|
||||||
if (bundle != null) {
|
|
||||||
mErrorVo.setError(bundle.getInt(HelperDefine.KEY_NAME_STATUS_CODE),
|
|
||||||
bundle.getString(HelperDefine.KEY_NAME_ERROR_STRING));
|
|
||||||
} else {
|
|
||||||
mErrorVo.setError(
|
|
||||||
HelperDefine.IAP_ERROR_COMMON,
|
|
||||||
mContext.getString(
|
|
||||||
R.string.mids_sapps_pop_unknown_error_occurred));
|
|
||||||
}
|
|
||||||
// ============================================================
|
|
||||||
|
|
||||||
// 3) If item list is loaded successfully,
|
|
||||||
// make item list by Bundle data
|
|
||||||
// ============================================================
|
|
||||||
// ============================================================
|
|
||||||
|
|
||||||
// 3) If item list is loaded successfully,
|
|
||||||
// make item list by Bundle data
|
|
||||||
// ============================================================
|
|
||||||
if (mErrorVo.getErrorCode() == HelperDefine.IAP_ERROR_NONE) {
|
|
||||||
if (bundle != null) {
|
|
||||||
ArrayList<String> consumePurchasedItemsStringList =
|
|
||||||
bundle.getStringArrayList(HelperDefine.KEY_NAME_RESULT_LIST);
|
|
||||||
|
|
||||||
if (consumePurchasedItemsStringList != null) {
|
|
||||||
for (String consumePurchasedItemString : consumePurchasedItemsStringList) {
|
|
||||||
ConsumeVo consumeVo = new ConsumeVo(consumePurchasedItemString);
|
|
||||||
mConsumeList.add(consumeVo);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
Log.i(TAG, "Bundle Value 'RESULT_LIST' is null.");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// ============================================================
|
|
||||||
// 4) If failed, print log.
|
|
||||||
// ============================================================
|
|
||||||
else {
|
|
||||||
Log.e(TAG, mErrorVo.getErrorString());
|
|
||||||
}
|
|
||||||
// ============================================================
|
|
||||||
} catch (Exception e) {
|
|
||||||
mErrorVo.setError(
|
|
||||||
HelperDefine.IAP_ERROR_COMMON,
|
|
||||||
mContext.getString(
|
|
||||||
R.string.mids_sapps_pop_unknown_error_occurred));
|
|
||||||
|
|
||||||
e.printStackTrace();
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,112 +0,0 @@
|
|||||||
package com.samsung.android.sdk.iap.lib.helper.task;
|
|
||||||
|
|
||||||
import android.content.Context;
|
|
||||||
import android.os.Bundle;
|
|
||||||
import android.util.Log;
|
|
||||||
|
|
||||||
import com.samsung.android.iap.IAPConnector;
|
|
||||||
import com.samsung.android.sdk.iap.lib.R;
|
|
||||||
import com.samsung.android.sdk.iap.lib.helper.HelperDefine;
|
|
||||||
import com.samsung.android.sdk.iap.lib.service.OwnedProduct;
|
|
||||||
import com.samsung.android.sdk.iap.lib.vo.OwnedProductVo;
|
|
||||||
|
|
||||||
import java.util.ArrayList;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Asynchronized Task to load a list of items
|
|
||||||
*/
|
|
||||||
public class GetOwnedListTask extends BaseTask {
|
|
||||||
private static final String TAG = GetOwnedListTask.class.getSimpleName();
|
|
||||||
private String mProductType = "";
|
|
||||||
ArrayList<OwnedProductVo> mOwnedList = new ArrayList<OwnedProductVo>();
|
|
||||||
|
|
||||||
public GetOwnedListTask
|
|
||||||
(
|
|
||||||
OwnedProduct _baseService,
|
|
||||||
IAPConnector _iapConnector,
|
|
||||||
Context _context,
|
|
||||||
String _productType,
|
|
||||||
boolean _showErrorDialog,
|
|
||||||
int _mode
|
|
||||||
) {
|
|
||||||
super(_baseService, _iapConnector, _context, _showErrorDialog, _mode);
|
|
||||||
mProductType = _productType;
|
|
||||||
_baseService.setOwnedList(mOwnedList);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected Boolean doInBackground(String... params) {
|
|
||||||
Log.i(TAG, "doInBackground: start");
|
|
||||||
try {
|
|
||||||
int pagingIndex = 1;
|
|
||||||
do {
|
|
||||||
Log.i(TAG, "doInBackground: pagingIndex = " + pagingIndex);
|
|
||||||
// 1) call getItemList() method of IAPService
|
|
||||||
// ============================================================
|
|
||||||
Bundle bundle = mIapConnector.getOwnedList(
|
|
||||||
mPackageName,
|
|
||||||
mProductType,
|
|
||||||
pagingIndex,
|
|
||||||
mMode);
|
|
||||||
// ============================================================
|
|
||||||
|
|
||||||
// 2) save status code, error string and extra String.
|
|
||||||
// ============================================================
|
|
||||||
if (bundle != null) {
|
|
||||||
mErrorVo.setError(bundle.getInt(HelperDefine.KEY_NAME_STATUS_CODE),
|
|
||||||
bundle.getString(HelperDefine.KEY_NAME_ERROR_STRING));
|
|
||||||
} else {
|
|
||||||
mErrorVo.setError(
|
|
||||||
HelperDefine.IAP_ERROR_COMMON,
|
|
||||||
mContext.getString(
|
|
||||||
R.string.mids_sapps_pop_unknown_error_occurred));
|
|
||||||
}
|
|
||||||
// ============================================================
|
|
||||||
|
|
||||||
// 3) If item list is loaded successfully,
|
|
||||||
// make item list by Bundle data
|
|
||||||
// ============================================================
|
|
||||||
if (mErrorVo.getErrorCode() == HelperDefine.IAP_ERROR_NONE) {
|
|
||||||
if (bundle != null) {
|
|
||||||
String nextPagingIndex = bundle.getString(HelperDefine.NEXT_PAGING_INDEX);
|
|
||||||
if (nextPagingIndex != null && nextPagingIndex.length() > 0) {
|
|
||||||
pagingIndex = Integer.parseInt(nextPagingIndex);
|
|
||||||
} else {
|
|
||||||
pagingIndex = -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
ArrayList<String> ownedProductStringList =
|
|
||||||
bundle.getStringArrayList(HelperDefine.KEY_NAME_RESULT_LIST);
|
|
||||||
|
|
||||||
if (ownedProductStringList != null) {
|
|
||||||
for (String ownedProductString : ownedProductStringList) {
|
|
||||||
OwnedProductVo ownedPrroductVo = new OwnedProductVo(ownedProductString);
|
|
||||||
mOwnedList.add(ownedPrroductVo);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
Log.i(TAG, "Bundle Value 'RESULT_LIST' is null.");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// ============================================================
|
|
||||||
// 4) If failed, print log.
|
|
||||||
// ============================================================
|
|
||||||
else {
|
|
||||||
Log.e(TAG, mErrorVo.getErrorString());
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
// ============================================================
|
|
||||||
} while (pagingIndex > 0);
|
|
||||||
} catch (Exception e) {
|
|
||||||
mErrorVo.setError(
|
|
||||||
HelperDefine.IAP_ERROR_COMMON,
|
|
||||||
mContext.getString(
|
|
||||||
R.string.mids_sapps_pop_unknown_error_occurred));
|
|
||||||
|
|
||||||
e.printStackTrace();
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,115 +0,0 @@
|
|||||||
package com.samsung.android.sdk.iap.lib.helper.task;
|
|
||||||
|
|
||||||
import android.content.Context;
|
|
||||||
import android.os.Bundle;
|
|
||||||
import android.util.Log;
|
|
||||||
|
|
||||||
import com.samsung.android.iap.IAPConnector;
|
|
||||||
import com.samsung.android.sdk.iap.lib.R;
|
|
||||||
import com.samsung.android.sdk.iap.lib.helper.HelperDefine;
|
|
||||||
import com.samsung.android.sdk.iap.lib.service.ProductsDetails;
|
|
||||||
import com.samsung.android.sdk.iap.lib.vo.ProductVo;
|
|
||||||
|
|
||||||
import java.util.ArrayList;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Asynchronized Task to load a list of items
|
|
||||||
*/
|
|
||||||
public class GetProductsDetailsTask extends BaseTask {
|
|
||||||
private static final String TAG = GetProductsDetailsTask.class.getSimpleName();
|
|
||||||
private String mProductIds = "";
|
|
||||||
ArrayList<ProductVo> mProductsDetails = new ArrayList<ProductVo>();
|
|
||||||
|
|
||||||
public GetProductsDetailsTask
|
|
||||||
(
|
|
||||||
ProductsDetails _baseService,
|
|
||||||
IAPConnector _iapConnector,
|
|
||||||
Context _context,
|
|
||||||
String _productIDs,
|
|
||||||
boolean _showErrorDialog,
|
|
||||||
int _mode
|
|
||||||
) {
|
|
||||||
super(_baseService, _iapConnector, _context, _showErrorDialog, _mode);
|
|
||||||
mProductIds = _productIDs;
|
|
||||||
|
|
||||||
_baseService.setProductsDetails(mProductsDetails);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected Boolean doInBackground(String... params) {
|
|
||||||
try {
|
|
||||||
int pagingIndex = 1;
|
|
||||||
do {
|
|
||||||
// 1) call getProductsDetails() method of IAPService
|
|
||||||
// ---- Order Priority ----
|
|
||||||
// 1. if productIds is not empty, the infomations abouts products included in the productIds are returned
|
|
||||||
// 2. if productIds is empty, the infomations about all products in this package are returned on a page by page
|
|
||||||
// ============================================================
|
|
||||||
Bundle bundle = mIapConnector.getProductsDetails(
|
|
||||||
mPackageName,
|
|
||||||
mProductIds,
|
|
||||||
pagingIndex,
|
|
||||||
mMode);
|
|
||||||
// ============================================================
|
|
||||||
|
|
||||||
// 2) save status code, error string and extra String.
|
|
||||||
// ============================================================
|
|
||||||
if (bundle != null) {
|
|
||||||
mErrorVo.setError(bundle.getInt(HelperDefine.KEY_NAME_STATUS_CODE),
|
|
||||||
bundle.getString(HelperDefine.KEY_NAME_ERROR_STRING));
|
|
||||||
} else {
|
|
||||||
mErrorVo.setError(
|
|
||||||
HelperDefine.IAP_ERROR_COMMON,
|
|
||||||
mContext.getString(
|
|
||||||
R.string.mids_sapps_pop_unknown_error_occurred));
|
|
||||||
}
|
|
||||||
// ============================================================
|
|
||||||
|
|
||||||
// 3) If item list is loaded successfully,
|
|
||||||
// make item list by Bundle data
|
|
||||||
// ============================================================
|
|
||||||
if (mErrorVo.getErrorCode() == HelperDefine.IAP_ERROR_NONE) {
|
|
||||||
if (bundle != null) {
|
|
||||||
String nextPagingIndex = bundle.getString(HelperDefine.NEXT_PAGING_INDEX);
|
|
||||||
if (nextPagingIndex != null && nextPagingIndex.length() > 0) {
|
|
||||||
pagingIndex = Integer.parseInt(nextPagingIndex);
|
|
||||||
Log.i(TAG, "PagingIndex = " + nextPagingIndex);
|
|
||||||
} else {
|
|
||||||
pagingIndex = -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
ArrayList<String> productStringList =
|
|
||||||
bundle.getStringArrayList(HelperDefine.KEY_NAME_RESULT_LIST);
|
|
||||||
|
|
||||||
if (productStringList != null) {
|
|
||||||
for (String productString : productStringList) {
|
|
||||||
ProductVo productVo = new ProductVo(productString);
|
|
||||||
mProductsDetails.add(productVo);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
Log.i(TAG, "Bundle Value 'RESULT_LIST' is null.");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// ============================================================
|
|
||||||
// 4) If failed, print log.
|
|
||||||
// ============================================================
|
|
||||||
else {
|
|
||||||
Log.e(TAG, mErrorVo.getErrorString());
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
// ============================================================
|
|
||||||
} while (pagingIndex > 0);
|
|
||||||
} catch (Exception e) {
|
|
||||||
mErrorVo.setError(
|
|
||||||
HelperDefine.IAP_ERROR_COMMON,
|
|
||||||
mContext.getString(
|
|
||||||
R.string.mids_sapps_pop_unknown_error_occurred));
|
|
||||||
|
|
||||||
e.printStackTrace();
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,20 +0,0 @@
|
|||||||
package com.samsung.android.sdk.iap.lib.listener;
|
|
||||||
|
|
||||||
import com.samsung.android.sdk.iap.lib.helper.task.GetOwnedListTask;
|
|
||||||
import com.samsung.android.sdk.iap.lib.vo.ConsumeVo;
|
|
||||||
import com.samsung.android.sdk.iap.lib.vo.ErrorVo;
|
|
||||||
|
|
||||||
import java.util.ArrayList;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Callback Interface used with {@link GetOwnedListTask}
|
|
||||||
*/
|
|
||||||
public interface OnConsumePurchasedItemsListener {
|
|
||||||
/**
|
|
||||||
* Callback method to be invoked when {@link GetOwnedListTask} has been finished.
|
|
||||||
*
|
|
||||||
* @param _errorVO
|
|
||||||
* @param _consumeList
|
|
||||||
*/
|
|
||||||
void onConsumePurchasedItems(ErrorVo _errorVO, ArrayList<ConsumeVo> _consumeList);
|
|
||||||
}
|
|
||||||
@@ -1,20 +0,0 @@
|
|||||||
package com.samsung.android.sdk.iap.lib.listener;
|
|
||||||
|
|
||||||
import com.samsung.android.sdk.iap.lib.helper.task.GetOwnedListTask;
|
|
||||||
import com.samsung.android.sdk.iap.lib.vo.ErrorVo;
|
|
||||||
import com.samsung.android.sdk.iap.lib.vo.OwnedProductVo;
|
|
||||||
|
|
||||||
import java.util.ArrayList;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Callback Interface used with {@link GetOwnedListTask}
|
|
||||||
*/
|
|
||||||
public interface OnGetOwnedListListener {
|
|
||||||
/**
|
|
||||||
* Callback method to be invoked when {@link GetOwnedListTask} has been finished.
|
|
||||||
*
|
|
||||||
* @param _errorVO
|
|
||||||
* @param _ownedList
|
|
||||||
*/
|
|
||||||
void onGetOwnedProducts(ErrorVo _errorVO, ArrayList<OwnedProductVo> _ownedList);
|
|
||||||
}
|
|
||||||
@@ -1,20 +0,0 @@
|
|||||||
package com.samsung.android.sdk.iap.lib.listener;
|
|
||||||
|
|
||||||
import com.samsung.android.sdk.iap.lib.helper.task.GetProductsDetailsTask;
|
|
||||||
import com.samsung.android.sdk.iap.lib.vo.ErrorVo;
|
|
||||||
import com.samsung.android.sdk.iap.lib.vo.ProductVo;
|
|
||||||
|
|
||||||
import java.util.ArrayList;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Callback Interface used with {@link GetProductsDetailsTask}
|
|
||||||
*/
|
|
||||||
public interface OnGetProductsDetailsListener {
|
|
||||||
/**
|
|
||||||
* Callback method to be invoked when {@link GetProductsDetailsTask} has been finished.
|
|
||||||
*
|
|
||||||
* @param _errorVO
|
|
||||||
* @param _productList
|
|
||||||
*/
|
|
||||||
void onGetProducts(ErrorVo _errorVO, ArrayList<ProductVo> _productList);
|
|
||||||
}
|
|
||||||
@@ -1,13 +0,0 @@
|
|||||||
package com.samsung.android.sdk.iap.lib.listener;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Callback Interface to be invoked when bind to IAPService has been finished.
|
|
||||||
*/
|
|
||||||
public interface OnIapBindListener {
|
|
||||||
/**
|
|
||||||
* Callback method to be invoked after binding to IAP service successfully.
|
|
||||||
*
|
|
||||||
* @param result
|
|
||||||
*/
|
|
||||||
public void onBindIapFinished(int result);
|
|
||||||
}
|
|
||||||
@@ -1,15 +0,0 @@
|
|||||||
package com.samsung.android.sdk.iap.lib.listener;
|
|
||||||
|
|
||||||
import com.samsung.android.sdk.iap.lib.vo.ErrorVo;
|
|
||||||
import com.samsung.android.sdk.iap.lib.vo.PurchaseVo;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Callback Interface to be invoked when payment has been finished.
|
|
||||||
*/
|
|
||||||
public interface OnPaymentListener {
|
|
||||||
/**
|
|
||||||
* Callback method to be invoked when payment has been finished. There is return data for result of financial transaction whenever it was
|
|
||||||
* successful or failed.
|
|
||||||
*/
|
|
||||||
void onPayment(ErrorVo _errorVO, PurchaseVo _purchaseVO);
|
|
||||||
}
|
|
||||||
@@ -1,8 +0,0 @@
|
|||||||
package com.samsung.android.sdk.iap.lib.listener;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Created by sangbum7.kim on 2018-02-28.
|
|
||||||
*/
|
|
||||||
|
|
||||||
public interface OnSucceedBind {
|
|
||||||
}
|
|
||||||
@@ -1,76 +0,0 @@
|
|||||||
package com.samsung.android.sdk.iap.lib.service;
|
|
||||||
|
|
||||||
import android.content.Context;
|
|
||||||
import android.content.Intent;
|
|
||||||
import android.util.Log;
|
|
||||||
|
|
||||||
import com.samsung.android.sdk.iap.lib.R;
|
|
||||||
import com.samsung.android.sdk.iap.lib.activity.AccountActivity;
|
|
||||||
import com.samsung.android.sdk.iap.lib.activity.DialogActivity;
|
|
||||||
import com.samsung.android.sdk.iap.lib.helper.HelperDefine;
|
|
||||||
import com.samsung.android.sdk.iap.lib.helper.IapHelper;
|
|
||||||
import com.samsung.android.sdk.iap.lib.vo.ErrorVo;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Created by sangbum7.kim on 2018-02-28.
|
|
||||||
*/
|
|
||||||
|
|
||||||
public abstract class BaseService {
|
|
||||||
private static final String TAG = BaseService.class.getSimpleName();
|
|
||||||
|
|
||||||
protected ErrorVo mErrorVo = new ErrorVo();
|
|
||||||
protected IapHelper mIapHelper = null;
|
|
||||||
protected Context mContext = null;
|
|
||||||
|
|
||||||
public BaseService(IapHelper _iapHelper, Context _context) {
|
|
||||||
mIapHelper = _iapHelper;
|
|
||||||
mContext = _context;
|
|
||||||
mErrorVo.setError(HelperDefine.IAP_ERROR_INITIALIZATION, mContext.getString(R.string.mids_sapps_pop_unknown_error_occurred));
|
|
||||||
}
|
|
||||||
|
|
||||||
public ErrorVo getErrorVo() {
|
|
||||||
return mErrorVo;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setErrorVo(ErrorVo mErrorVo) {
|
|
||||||
this.mErrorVo = mErrorVo;
|
|
||||||
}
|
|
||||||
|
|
||||||
public abstract void runServiceProcess();
|
|
||||||
|
|
||||||
public void onEndProcess() {
|
|
||||||
Log.i(TAG, "BaseService.onEndProcess");
|
|
||||||
|
|
||||||
if (mErrorVo.getErrorCode() == HelperDefine.IAP_ERROR_NEED_SA_LOGIN) {
|
|
||||||
Intent intent = new Intent(mContext, AccountActivity.class);
|
|
||||||
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
|
|
||||||
mContext.startActivity(intent);
|
|
||||||
return;
|
|
||||||
} else if (mErrorVo.getErrorCode() != HelperDefine.IAP_ERROR_NONE) {
|
|
||||||
if (mErrorVo.getErrorCode() != HelperDefine.IAP_ERROR_NETWORK_NOT_AVAILABLE && mErrorVo.isShowDialog()) {
|
|
||||||
Intent intent = new Intent(mContext, DialogActivity.class);
|
|
||||||
intent.putExtra("Title", mContext.getString(R.string.dream_ph_pheader_couldnt_complete_purchase));
|
|
||||||
intent.putExtra("Message", mErrorVo.getErrorString());
|
|
||||||
intent.putExtra("DialogType", HelperDefine.DIALOG_TYPE_NOTIFICATION);
|
|
||||||
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
|
|
||||||
mContext.startActivity(intent);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (mIapHelper != null) {
|
|
||||||
BaseService baseService = mIapHelper.getServiceProcess(true);
|
|
||||||
if (baseService != null) {
|
|
||||||
baseService.runServiceProcess();
|
|
||||||
} else {
|
|
||||||
mIapHelper.dispose();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
onReleaseProcess();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void releaseProcess() {
|
|
||||||
onReleaseProcess();
|
|
||||||
}
|
|
||||||
|
|
||||||
abstract void onReleaseProcess();
|
|
||||||
}
|
|
||||||
@@ -1,62 +0,0 @@
|
|||||||
package com.samsung.android.sdk.iap.lib.service;
|
|
||||||
|
|
||||||
import android.content.Context;
|
|
||||||
import android.util.Log;
|
|
||||||
|
|
||||||
import com.samsung.android.sdk.iap.lib.R;
|
|
||||||
import com.samsung.android.sdk.iap.lib.helper.HelperDefine;
|
|
||||||
import com.samsung.android.sdk.iap.lib.helper.IapHelper;
|
|
||||||
import com.samsung.android.sdk.iap.lib.listener.OnConsumePurchasedItemsListener;
|
|
||||||
import com.samsung.android.sdk.iap.lib.vo.ConsumeVo;
|
|
||||||
|
|
||||||
import java.util.ArrayList;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Created by sangbum7.kim on 2018-02-28.
|
|
||||||
*/
|
|
||||||
|
|
||||||
public class ConsumePurchasedItems extends BaseService {
|
|
||||||
private static final String TAG = ConsumePurchasedItems.class.getSimpleName();
|
|
||||||
|
|
||||||
private OnConsumePurchasedItemsListener mOnConsumePurchasedItemsListener = null;
|
|
||||||
private static String mPurchaseIds = "";
|
|
||||||
protected ArrayList<ConsumeVo> mConsumeList = null;
|
|
||||||
|
|
||||||
public ConsumePurchasedItems(IapHelper _iapHelper, Context _context, OnConsumePurchasedItemsListener _onConsumePurchasedItemsListener) {
|
|
||||||
super(_iapHelper, _context);
|
|
||||||
mOnConsumePurchasedItemsListener = _onConsumePurchasedItemsListener;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void setPurchaseIds(String _purchaseIds) {
|
|
||||||
mPurchaseIds = _purchaseIds;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setConsumeList(ArrayList<ConsumeVo> _consumeList) {
|
|
||||||
this.mConsumeList = _consumeList;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void runServiceProcess() {
|
|
||||||
if (mIapHelper != null) {
|
|
||||||
if (mIapHelper.safeConsumePurchasedItems(ConsumePurchasedItems.this,
|
|
||||||
mPurchaseIds,
|
|
||||||
mIapHelper.getShowErrorDialog()) == true) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
mErrorVo.setError(HelperDefine.IAP_ERROR_INITIALIZATION, mContext.getString(R.string.mids_sapps_pop_unknown_error_occurred));
|
|
||||||
onEndProcess();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onReleaseProcess() {
|
|
||||||
Log.i(TAG, "ConsumePurchasedItems.onReleaseProcess");
|
|
||||||
try {
|
|
||||||
if (mOnConsumePurchasedItemsListener != null) {
|
|
||||||
mOnConsumePurchasedItemsListener.onConsumePurchasedItems(mErrorVo, mConsumeList);
|
|
||||||
}
|
|
||||||
} catch (Exception e) {
|
|
||||||
Log.e(TAG, e.toString());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,63 +0,0 @@
|
|||||||
package com.samsung.android.sdk.iap.lib.service;
|
|
||||||
|
|
||||||
import android.content.Context;
|
|
||||||
import android.util.Log;
|
|
||||||
|
|
||||||
import com.samsung.android.sdk.iap.lib.R;
|
|
||||||
import com.samsung.android.sdk.iap.lib.helper.HelperDefine;
|
|
||||||
import com.samsung.android.sdk.iap.lib.helper.IapHelper;
|
|
||||||
import com.samsung.android.sdk.iap.lib.listener.OnGetOwnedListListener;
|
|
||||||
import com.samsung.android.sdk.iap.lib.vo.OwnedProductVo;
|
|
||||||
|
|
||||||
import java.util.ArrayList;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Created by sangbum7.kim on 2018-02-28.
|
|
||||||
*/
|
|
||||||
|
|
||||||
public class OwnedProduct extends BaseService {
|
|
||||||
private static final String TAG = OwnedProduct.class.getSimpleName();
|
|
||||||
|
|
||||||
private OnGetOwnedListListener mOnGetOwnedListListener = null;
|
|
||||||
private static String mProductType = "";
|
|
||||||
protected ArrayList<OwnedProductVo> mOwnedList = null;
|
|
||||||
|
|
||||||
public OwnedProduct(IapHelper _iapHelper, Context _context, OnGetOwnedListListener _onGetOwnedListListener) {
|
|
||||||
super(_iapHelper, _context);
|
|
||||||
mOnGetOwnedListListener = _onGetOwnedListListener;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void setProductType(String _productType) {
|
|
||||||
mProductType = _productType;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setOwnedList(ArrayList<OwnedProductVo> _ownedList) {
|
|
||||||
this.mOwnedList = _ownedList;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void runServiceProcess() {
|
|
||||||
Log.i(TAG, "runServiceProcess");
|
|
||||||
if (mIapHelper != null) {
|
|
||||||
if (mIapHelper.safeGetOwnedList(OwnedProduct.this,
|
|
||||||
mProductType,
|
|
||||||
mIapHelper.getShowErrorDialog()) == true) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
mErrorVo.setError(HelperDefine.IAP_ERROR_INITIALIZATION, mContext.getString(R.string.mids_sapps_pop_unknown_error_occurred));
|
|
||||||
onEndProcess();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onReleaseProcess() {
|
|
||||||
Log.i(TAG, "OwnedProduct.onReleaseProcess");
|
|
||||||
try {
|
|
||||||
if (mOnGetOwnedListListener != null) {
|
|
||||||
mOnGetOwnedListListener.onGetOwnedProducts(mErrorVo, mOwnedList);
|
|
||||||
}
|
|
||||||
} catch (Exception e) {
|
|
||||||
Log.e(TAG, e.toString());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,63 +0,0 @@
|
|||||||
package com.samsung.android.sdk.iap.lib.service;
|
|
||||||
|
|
||||||
import android.content.Context;
|
|
||||||
import android.util.Log;
|
|
||||||
|
|
||||||
import com.samsung.android.sdk.iap.lib.R;
|
|
||||||
import com.samsung.android.sdk.iap.lib.helper.HelperDefine;
|
|
||||||
import com.samsung.android.sdk.iap.lib.helper.IapHelper;
|
|
||||||
import com.samsung.android.sdk.iap.lib.listener.OnGetProductsDetailsListener;
|
|
||||||
import com.samsung.android.sdk.iap.lib.vo.ProductVo;
|
|
||||||
|
|
||||||
import java.util.ArrayList;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Created by sangbum7.kim on 2018-02-28.
|
|
||||||
*/
|
|
||||||
|
|
||||||
public class ProductsDetails extends BaseService {
|
|
||||||
private static final String TAG = ProductsDetails.class.getSimpleName();
|
|
||||||
|
|
||||||
private OnGetProductsDetailsListener mOnGetProductsDetailsListener = null;
|
|
||||||
private static String mProductIds = "";
|
|
||||||
protected ArrayList<ProductVo> mProductsDetails = null;
|
|
||||||
|
|
||||||
public ProductsDetails(IapHelper _iapHelper, Context _context, OnGetProductsDetailsListener _onGetProductsDetailsListener) {
|
|
||||||
super(_iapHelper, _context);
|
|
||||||
mOnGetProductsDetailsListener = _onGetProductsDetailsListener;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void setProductId(String _productIds) {
|
|
||||||
mProductIds = _productIds;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setProductsDetails(ArrayList<ProductVo> _ProductsDetails) {
|
|
||||||
this.mProductsDetails = _ProductsDetails;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void runServiceProcess() {
|
|
||||||
Log.i(TAG, "succeedBind");
|
|
||||||
if (mIapHelper != null) {
|
|
||||||
if (mIapHelper.safeGetProductsDetails(ProductsDetails.this,
|
|
||||||
mProductIds,
|
|
||||||
mIapHelper.getShowErrorDialog()) == true) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
mErrorVo.setError(HelperDefine.IAP_ERROR_INITIALIZATION, mContext.getString(R.string.mids_sapps_pop_unknown_error_occurred));
|
|
||||||
onEndProcess();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onReleaseProcess() {
|
|
||||||
Log.i(TAG, "OwnedProduct.onEndProcess");
|
|
||||||
try {
|
|
||||||
if (mOnGetProductsDetailsListener != null) {
|
|
||||||
mOnGetProductsDetailsListener.onGetProducts(mErrorVo, mProductsDetails);
|
|
||||||
}
|
|
||||||
} catch (Exception e) {
|
|
||||||
Log.e(TAG, e.toString());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,146 +0,0 @@
|
|||||||
package com.samsung.android.sdk.iap.lib.vo;
|
|
||||||
|
|
||||||
import android.text.format.DateFormat;
|
|
||||||
|
|
||||||
import org.json.JSONException;
|
|
||||||
import org.json.JSONObject;
|
|
||||||
|
|
||||||
public class BaseVo {
|
|
||||||
private String mItemId;
|
|
||||||
private String mItemName;
|
|
||||||
private Double mItemPrice;
|
|
||||||
private String mItemPriceString;
|
|
||||||
private String mCurrencyUnit;
|
|
||||||
private String mCurrencyCode;
|
|
||||||
private String mItemDesc;
|
|
||||||
private String mType;
|
|
||||||
private Boolean mIsConsumable;
|
|
||||||
|
|
||||||
public BaseVo() {
|
|
||||||
}
|
|
||||||
|
|
||||||
public BaseVo(String _jsonString) {
|
|
||||||
try {
|
|
||||||
JSONObject jObject = new JSONObject(_jsonString);
|
|
||||||
|
|
||||||
setItemId(jObject.optString("mItemId"));
|
|
||||||
setItemName(jObject.optString("mItemName"));
|
|
||||||
setItemPrice(jObject.optDouble("mItemPrice"));
|
|
||||||
setItemPriceString(jObject.optString("mItemPriceString"));
|
|
||||||
setCurrencyUnit(jObject.optString("mCurrencyUnit"));
|
|
||||||
setCurrencyCode(jObject.optString("mCurrencyCode"));
|
|
||||||
setItemDesc(jObject.optString("mItemDesc"));
|
|
||||||
setType(jObject.optString("mType"));
|
|
||||||
Boolean isConsumable = false;
|
|
||||||
if (jObject.optString("mConsumableYN") != null && jObject.optString("mConsumableYN").equals("Y")) {
|
|
||||||
isConsumable = true;
|
|
||||||
}
|
|
||||||
setIsConsumable(isConsumable);
|
|
||||||
} catch (JSONException e) {
|
|
||||||
e.printStackTrace();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getItemId() {
|
|
||||||
return mItemId;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setItemId(String _itemId) {
|
|
||||||
mItemId = _itemId;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getItemName() {
|
|
||||||
return mItemName;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setItemName(String _itemName) {
|
|
||||||
mItemName = _itemName;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Double getItemPrice() {
|
|
||||||
return mItemPrice;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setItemPrice(Double _itemPrice) {
|
|
||||||
mItemPrice = _itemPrice;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getItemPriceString() {
|
|
||||||
return mItemPriceString;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setItemPriceString(String _itemPriceString) {
|
|
||||||
mItemPriceString = _itemPriceString;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getCurrencyUnit() {
|
|
||||||
return mCurrencyUnit;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setCurrencyUnit(String _currencyUnit) {
|
|
||||||
mCurrencyUnit = _currencyUnit;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getCurrencyCode() {
|
|
||||||
return mCurrencyCode;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setCurrencyCode(String _currencyCode) {
|
|
||||||
mCurrencyCode = _currencyCode;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getItemDesc() {
|
|
||||||
return mItemDesc;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setItemDesc(String _itemDesc) {
|
|
||||||
mItemDesc = _itemDesc;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getType() {
|
|
||||||
return mType;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setType(String _itemDesc) {
|
|
||||||
mType = _itemDesc;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Boolean getIsConsumable() {
|
|
||||||
return mIsConsumable;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setIsConsumable(Boolean _consumableYN) {
|
|
||||||
mIsConsumable = _consumableYN;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public String dump() {
|
|
||||||
String dump = null;
|
|
||||||
|
|
||||||
dump = "ItemId : " + getItemId() + "\n" +
|
|
||||||
"ItemName : " + getItemName() + "\n" +
|
|
||||||
"ItemPrice : " + getItemPrice() + "\n" +
|
|
||||||
"ItemPriceString : " + getItemPriceString() + "\n" +
|
|
||||||
"ItemDesc : " + getItemDesc() + "\n" +
|
|
||||||
"CurrencyUnit : " + getCurrencyUnit() + "\n" +
|
|
||||||
"CurrencyCode : " + getCurrencyCode() + "\n" +
|
|
||||||
"IsConsumable : " + getIsConsumable() + "\n" +
|
|
||||||
"Type : " + getType();
|
|
||||||
|
|
||||||
return dump;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected String getDateString(long _timeMills) {
|
|
||||||
String result = "";
|
|
||||||
String dateFormat = "yyyy-MM-dd HH:mm:ss";
|
|
||||||
|
|
||||||
try {
|
|
||||||
result = DateFormat.format(dateFormat, _timeMills).toString();
|
|
||||||
} catch (Exception e) {
|
|
||||||
e.printStackTrace();
|
|
||||||
result = "";
|
|
||||||
}
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,75 +0,0 @@
|
|||||||
package com.samsung.android.sdk.iap.lib.vo;
|
|
||||||
|
|
||||||
import android.util.Log;
|
|
||||||
|
|
||||||
import org.json.JSONException;
|
|
||||||
import org.json.JSONObject;
|
|
||||||
|
|
||||||
public class ConsumeVo {
|
|
||||||
private static final String TAG = ConsumeVo.class.getSimpleName();
|
|
||||||
|
|
||||||
private String mPurchaseId;
|
|
||||||
private String mStatusString;
|
|
||||||
private int mStatusCode;
|
|
||||||
|
|
||||||
private String mJsonString = "";
|
|
||||||
|
|
||||||
public ConsumeVo(String _jsonString) {
|
|
||||||
setJsonString(_jsonString);
|
|
||||||
try {
|
|
||||||
JSONObject jObject = new JSONObject(_jsonString);
|
|
||||||
|
|
||||||
Log.i(TAG, jObject.toString(4));
|
|
||||||
|
|
||||||
setPurchaseId(jObject.optString("mPurchaseId"));
|
|
||||||
setStatusString(jObject.optString("mStatusString"));
|
|
||||||
setStatusCode(jObject.optInt("mStatusCode"));
|
|
||||||
} catch (JSONException e) {
|
|
||||||
e.printStackTrace();
|
|
||||||
} catch (Exception e) {
|
|
||||||
e.printStackTrace();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getPurchaseId() {
|
|
||||||
return mPurchaseId;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setPurchaseId(String _paymentId) {
|
|
||||||
mPurchaseId = _paymentId;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getStatusString() {
|
|
||||||
return mStatusString;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setStatusString(String _statusString) {
|
|
||||||
mStatusString = _statusString;
|
|
||||||
}
|
|
||||||
|
|
||||||
public int getStatusCode() {
|
|
||||||
return mStatusCode;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setStatusCode(int _statusCode) {
|
|
||||||
mStatusCode = _statusCode;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getJsonString() {
|
|
||||||
return mJsonString;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setJsonString(String _jsonString) {
|
|
||||||
mJsonString = _jsonString;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String dump() {
|
|
||||||
String dump = null;
|
|
||||||
|
|
||||||
dump = "PurchaseId : " + getPurchaseId() + "\n" +
|
|
||||||
"StatusString : " + getStatusString() + "\n" +
|
|
||||||
"StatusCode : " + getStatusCode();
|
|
||||||
|
|
||||||
return dump;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,59 +0,0 @@
|
|||||||
package com.samsung.android.sdk.iap.lib.vo;
|
|
||||||
|
|
||||||
import com.samsung.android.sdk.iap.lib.helper.HelperDefine;
|
|
||||||
|
|
||||||
public class ErrorVo {
|
|
||||||
private int mErrorCode = HelperDefine.IAP_PAYMENT_IS_CANCELED;
|
|
||||||
private String mErrorString = "";
|
|
||||||
private String mErrorDetailsString = "";
|
|
||||||
private String mExtraString = "";
|
|
||||||
private boolean mShowDialog = false;
|
|
||||||
|
|
||||||
public void setError(int _errorCode, String _errorString) {
|
|
||||||
mErrorCode = _errorCode;
|
|
||||||
mErrorString = _errorString;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setError(int _errorCode, String _errorString, String _errorDetails) {
|
|
||||||
mErrorCode = _errorCode;
|
|
||||||
mErrorString = _errorString;
|
|
||||||
mErrorDetailsString = _errorDetails;
|
|
||||||
}
|
|
||||||
|
|
||||||
public int getErrorCode() {
|
|
||||||
return mErrorCode;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getErrorString() {
|
|
||||||
return mErrorString;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getErrorDetailsString() {
|
|
||||||
return mErrorDetailsString;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getExtraString() {
|
|
||||||
return mExtraString;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setExtraString(String _extraString) {
|
|
||||||
mExtraString = _extraString;
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean isShowDialog() {
|
|
||||||
return mShowDialog;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setShowDialog(boolean _showDialog) {
|
|
||||||
mShowDialog = _showDialog;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String dump() {
|
|
||||||
String dump =
|
|
||||||
"ErrorCode : " + getErrorCode() + "\n" +
|
|
||||||
"ErrorString : " + getErrorString() + "\n" +
|
|
||||||
"ErrorDetailsString : " + getErrorDetailsString() + "\n" +
|
|
||||||
"ExtraString : " + getExtraString();
|
|
||||||
return dump;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,115 +0,0 @@
|
|||||||
package com.samsung.android.sdk.iap.lib.vo;
|
|
||||||
|
|
||||||
import android.util.Base64;
|
|
||||||
|
|
||||||
import org.json.JSONException;
|
|
||||||
import org.json.JSONObject;
|
|
||||||
|
|
||||||
import java.io.UnsupportedEncodingException;
|
|
||||||
|
|
||||||
public class OwnedProductVo extends BaseVo {
|
|
||||||
private static final String TAG = OwnedProductVo.class.getSimpleName();
|
|
||||||
|
|
||||||
private String mPaymentId;
|
|
||||||
private String mPurchaseId;
|
|
||||||
private String mPurchaseDate;
|
|
||||||
private String mPassThroughParam;
|
|
||||||
|
|
||||||
// Expiration date for a item which is "subscription" type
|
|
||||||
// ========================================================================
|
|
||||||
private String mSubscriptionEndDate = "";
|
|
||||||
// ========================================================================
|
|
||||||
|
|
||||||
private String mJsonString = "";
|
|
||||||
|
|
||||||
public OwnedProductVo() {
|
|
||||||
}
|
|
||||||
|
|
||||||
public OwnedProductVo(String _jsonString) {
|
|
||||||
super(_jsonString);
|
|
||||||
setJsonString(_jsonString);
|
|
||||||
|
|
||||||
try {
|
|
||||||
JSONObject jObject = new JSONObject(_jsonString);
|
|
||||||
setPaymentId(jObject.optString("mPaymentId"));
|
|
||||||
setPurchaseId(jObject.optString("mPurchaseId"));
|
|
||||||
setPurchaseDate(getDateString(jObject.optLong("mPurchaseDate")));
|
|
||||||
jObject.remove("mPurchaseDate");
|
|
||||||
jObject.put("mPurchaseDate", getPurchaseDate());
|
|
||||||
|
|
||||||
String decodedPassThroughParam = new String(Base64.decode(jObject.optString("mPassThroughParam"), 0), "UTF-8");
|
|
||||||
setPassThroughParam(decodedPassThroughParam);
|
|
||||||
|
|
||||||
if (jObject.optLong("mSubscriptionEndDate") != 0) {
|
|
||||||
setSubscriptionEndDate(getDateString(jObject.optLong("mSubscriptionEndDate")));
|
|
||||||
}
|
|
||||||
jObject.remove("mSubscriptionEndDate");
|
|
||||||
jObject.put("mSubscriptionEndDate", getSubscriptionEndDate());
|
|
||||||
setJsonString(jObject.toString());
|
|
||||||
} catch (JSONException e) {
|
|
||||||
e.printStackTrace();
|
|
||||||
} catch (UnsupportedEncodingException e) {
|
|
||||||
e.printStackTrace();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getPaymentId() {
|
|
||||||
return mPaymentId;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setPaymentId(String _paymentId) {
|
|
||||||
mPaymentId = _paymentId;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getPurchaseId() {
|
|
||||||
return mPurchaseId;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setPurchaseId(String _purchaseId) {
|
|
||||||
mPurchaseId = _purchaseId;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getPurchaseDate() {
|
|
||||||
return mPurchaseDate;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setPurchaseDate(String _purchaseDate) {
|
|
||||||
mPurchaseDate = _purchaseDate;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getSubscriptionEndDate() {
|
|
||||||
return mSubscriptionEndDate;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setSubscriptionEndDate(String _subscriptionEndDate) {
|
|
||||||
mSubscriptionEndDate = _subscriptionEndDate;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getPassThroughParam() {
|
|
||||||
return mPassThroughParam;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setPassThroughParam(String _passThroughParam) {
|
|
||||||
mPassThroughParam = _passThroughParam;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getJsonString() {
|
|
||||||
return mJsonString;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setJsonString(String _jsonString) {
|
|
||||||
mJsonString = _jsonString;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String dump() {
|
|
||||||
String dump = super.dump() + "\n";
|
|
||||||
|
|
||||||
dump += "PaymentID : " + getPaymentId() + "\n" +
|
|
||||||
"PurchaseID : " + getPurchaseId() + "\n" +
|
|
||||||
"PurchaseDate : " + getPurchaseDate() + "\n" +
|
|
||||||
"PassThroughParam : " + getPassThroughParam() + "\n" +
|
|
||||||
"SubscriptionEndDate : " + getSubscriptionEndDate();
|
|
||||||
|
|
||||||
return dump;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,223 +0,0 @@
|
|||||||
package com.samsung.android.sdk.iap.lib.vo;
|
|
||||||
|
|
||||||
import org.json.JSONException;
|
|
||||||
import org.json.JSONObject;
|
|
||||||
|
|
||||||
public class ProductVo extends BaseVo {
|
|
||||||
private static final String TAG = ProductVo.class.getSimpleName();
|
|
||||||
|
|
||||||
//Subscription data
|
|
||||||
private String mSubscriptionDurationUnit;
|
|
||||||
private String mSubscriptionDurationMultiplier;
|
|
||||||
|
|
||||||
// Tiered Subscription data
|
|
||||||
private String mTieredPrice = "";
|
|
||||||
private String mTieredPriceString = "";
|
|
||||||
private String mTieredSubscriptionYN = "";
|
|
||||||
private String mTieredSubscriptionDurationUnit = "";
|
|
||||||
private String mTieredSubscriptionDurationMultiplier = "";
|
|
||||||
private String mTieredSubscriptionCount = "";
|
|
||||||
private String mShowStartDate = "";
|
|
||||||
private String mShowEndDate = "";
|
|
||||||
|
|
||||||
private String mItemImageUrl;
|
|
||||||
private String mItemDownloadUrl;
|
|
||||||
private String mReserved1;
|
|
||||||
private String mReserved2;
|
|
||||||
private String mFreeTrialPeriod;
|
|
||||||
|
|
||||||
private String mJsonString;
|
|
||||||
|
|
||||||
public ProductVo() {
|
|
||||||
}
|
|
||||||
|
|
||||||
public ProductVo(String _jsonString) {
|
|
||||||
super(_jsonString);
|
|
||||||
setJsonString(_jsonString);
|
|
||||||
|
|
||||||
try {
|
|
||||||
JSONObject jObject = new JSONObject(_jsonString);
|
|
||||||
|
|
||||||
setSubscriptionDurationUnit(jObject.optString("mSubscriptionDurationUnit"));
|
|
||||||
setSubscriptionDurationMultiplier(jObject.optString("mSubscriptionDurationMultiplier"));
|
|
||||||
|
|
||||||
setTieredSubscriptionYN(jObject.optString("mTieredSubscriptionYN"));
|
|
||||||
setTieredSubscriptionDurationUnit(jObject.optString("mTieredSubscriptionDurationUnit"));
|
|
||||||
setTieredSubscriptionDurationMultiplier(jObject.optString("mTieredSubscriptionDurationMultiplier"));
|
|
||||||
setTieredSubscriptionCount(jObject.optString("mTieredSubscriptionCount"));
|
|
||||||
setTieredPrice(jObject.optString("mTieredPrice"));
|
|
||||||
setTieredPriceString(jObject.optString("mTieredPriceString"));
|
|
||||||
setShowStartDate(getDateString(jObject.optLong("mShowStartDate")));
|
|
||||||
setShowEndDate(getDateString(jObject.optLong("mShowEndDate")));
|
|
||||||
|
|
||||||
setItemImageUrl(jObject.optString("mItemImageUrl"));
|
|
||||||
setItemDownloadUrl(jObject.optString("mItemDownloadUrl"));
|
|
||||||
setReserved1(jObject.optString("mReserved1"));
|
|
||||||
setReserved2(jObject.optString("mReserved2"));
|
|
||||||
setFreeTrialPeriod(jObject.optString("mFreeTrialPeriod"));
|
|
||||||
} catch (JSONException e) {
|
|
||||||
e.printStackTrace();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getSubscriptionDurationUnit() {
|
|
||||||
return mSubscriptionDurationUnit;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setSubscriptionDurationUnit(String _subscriptionDurationUnit) {
|
|
||||||
mSubscriptionDurationUnit = _subscriptionDurationUnit;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getSubscriptionDurationMultiplier() {
|
|
||||||
return mSubscriptionDurationMultiplier;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setSubscriptionDurationMultiplier(
|
|
||||||
String _subscriptionDurationMultiplier) {
|
|
||||||
mSubscriptionDurationMultiplier = _subscriptionDurationMultiplier;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getTieredSubscriptionYN() {
|
|
||||||
return mTieredSubscriptionYN;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setTieredSubscriptionYN(String _tieredSubscriptionYN) {
|
|
||||||
this.mTieredSubscriptionYN = _tieredSubscriptionYN;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getTieredPrice() {
|
|
||||||
return mTieredPrice;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setTieredPrice(String _tieredPrice) {
|
|
||||||
this.mTieredPrice = _tieredPrice;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getTieredPriceString() {
|
|
||||||
return mTieredPriceString;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setTieredPriceString(String _tieredPriceString) {
|
|
||||||
this.mTieredPriceString = _tieredPriceString;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getTieredSubscriptionDurationUnit() {
|
|
||||||
return mTieredSubscriptionDurationUnit;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setTieredSubscriptionDurationUnit(String _tieredSubscriptionDurationUnit) {
|
|
||||||
this.mTieredSubscriptionDurationUnit = _tieredSubscriptionDurationUnit;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getTieredSubscriptionDurationMultiplier() {
|
|
||||||
return mTieredSubscriptionDurationMultiplier;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setTieredSubscriptionDurationMultiplier(String _tieredSubscriptionDurationMultiplier) {
|
|
||||||
this.mTieredSubscriptionDurationMultiplier = _tieredSubscriptionDurationMultiplier;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getTieredSubscriptionCount() {
|
|
||||||
return mTieredSubscriptionCount;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setTieredSubscriptionCount(String _tieredSubscriptionCount) {
|
|
||||||
this.mTieredSubscriptionCount = _tieredSubscriptionCount;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getShowStartDate() {
|
|
||||||
return mShowStartDate;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setShowStartDate(String showStartDate) {
|
|
||||||
this.mShowStartDate = showStartDate;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getShowEndDate() {
|
|
||||||
return mShowEndDate;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setShowEndDate(String showEndDate) {
|
|
||||||
this.mShowEndDate = showEndDate;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getItemImageUrl() {
|
|
||||||
return mItemImageUrl;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setItemImageUrl(String _itemImageUrl) {
|
|
||||||
mItemImageUrl = _itemImageUrl;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getItemDownloadUrl() {
|
|
||||||
return mItemDownloadUrl;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setItemDownloadUrl(String _itemDownloadUrl) {
|
|
||||||
mItemDownloadUrl = _itemDownloadUrl;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getReserved1() {
|
|
||||||
return mReserved1;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setReserved1(String _reserved1) {
|
|
||||||
mReserved1 = _reserved1;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getReserved2() {
|
|
||||||
return mReserved2;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setReserved2(String _reserved2) {
|
|
||||||
mReserved2 = _reserved2;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getFreeTrialPeriod() {
|
|
||||||
return mFreeTrialPeriod;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setFreeTrialPeriod(String _freeTrialPeriod) {
|
|
||||||
mFreeTrialPeriod = _freeTrialPeriod;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getJsonString() {
|
|
||||||
return mJsonString;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setJsonString(String _jsonString) {
|
|
||||||
mJsonString = _jsonString;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String tieredDump() {
|
|
||||||
String dump = "";
|
|
||||||
if (getTieredSubscriptionYN().equals("Y") == true) {
|
|
||||||
dump = "TieredSubscriptionYN : " + getTieredSubscriptionYN() + "\n" +
|
|
||||||
"TieredPrice : " + getTieredPrice() + "\n" +
|
|
||||||
"TieredPriceString : " + getTieredPriceString() + "\n" +
|
|
||||||
"TieredSubscriptionCount : " + getTieredSubscriptionCount() + "\n" +
|
|
||||||
"TieredSubscriptionDurationUnit : " + getTieredSubscriptionDurationUnit() + "\n" +
|
|
||||||
"TieredSubscriptionDurationMultiplier : " + getTieredSubscriptionDurationMultiplier() + "\n" +
|
|
||||||
"ShowStartDate : " + getShowStartDate() + "\n" +
|
|
||||||
"ShowEndDate : " + getShowEndDate();
|
|
||||||
|
|
||||||
}
|
|
||||||
return dump;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String dump() {
|
|
||||||
String dump = super.dump() + "\n";
|
|
||||||
|
|
||||||
dump += "SubscriptionDurationUnit : "
|
|
||||||
+ getSubscriptionDurationUnit() + "\n" +
|
|
||||||
"SubscriptionDurationMultiplier : " +
|
|
||||||
getSubscriptionDurationMultiplier() + "\n" +
|
|
||||||
"ItemImageUrl : " + getItemImageUrl() + "\n" +
|
|
||||||
"ItemDownloadUrl : " + getItemDownloadUrl() + "\n" +
|
|
||||||
"Reserved1 : " + getReserved1() + "\n" +
|
|
||||||
"Reserved2 : " + getReserved2() + "\n" +
|
|
||||||
"FreeTrialPeriod : " + getFreeTrialPeriod() + "\n" +
|
|
||||||
tieredDump();
|
|
||||||
return dump;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,172 +0,0 @@
|
|||||||
package com.samsung.android.sdk.iap.lib.vo;
|
|
||||||
|
|
||||||
import android.util.Base64;
|
|
||||||
|
|
||||||
import org.json.JSONException;
|
|
||||||
import org.json.JSONObject;
|
|
||||||
|
|
||||||
import java.io.UnsupportedEncodingException;
|
|
||||||
|
|
||||||
public class PurchaseVo extends BaseVo {
|
|
||||||
private static final String TAG = PurchaseVo.class.getSimpleName();
|
|
||||||
|
|
||||||
private String mPaymentId;
|
|
||||||
private String mPurchaseId;
|
|
||||||
private String mPurchaseDate;
|
|
||||||
private String mVerifyUrl;
|
|
||||||
private String mPassThroughParam;
|
|
||||||
|
|
||||||
private String mItemImageUrl;
|
|
||||||
private String mItemDownloadUrl;
|
|
||||||
private String mReserved1;
|
|
||||||
private String mReserved2;
|
|
||||||
private String mOrderId;
|
|
||||||
private String mUdpSignature;
|
|
||||||
|
|
||||||
private String mJsonString;
|
|
||||||
|
|
||||||
public PurchaseVo(String _jsonString) {
|
|
||||||
super(_jsonString);
|
|
||||||
setJsonString(_jsonString);
|
|
||||||
|
|
||||||
try {
|
|
||||||
JSONObject jObject = new JSONObject(_jsonString);
|
|
||||||
|
|
||||||
setPaymentId(jObject.optString("mPaymentId"));
|
|
||||||
setPurchaseId(jObject.optString("mPurchaseId"));
|
|
||||||
setPurchaseDate(getDateString(jObject.optLong("mPurchaseDate")));
|
|
||||||
jObject.remove("mPurchaseDate");
|
|
||||||
jObject.put("mPurchaseDate", getPurchaseDate());
|
|
||||||
String decodedPassThroughParam = new String(Base64.decode(jObject.optString("mPassThroughParam"), 0), "UTF-8");
|
|
||||||
setPassThroughParam(decodedPassThroughParam);
|
|
||||||
|
|
||||||
setItemImageUrl(jObject.optString("mItemImageUrl"));
|
|
||||||
setItemDownloadUrl(jObject.optString("mItemDownloadUrl"));
|
|
||||||
setReserved1(jObject.optString("mReserved1"));
|
|
||||||
setReserved2(jObject.optString("mReserved2"));
|
|
||||||
setOrderId(jObject.optString("mOrderId"));
|
|
||||||
|
|
||||||
setVerifyUrl(jObject.optString("mVerifyUrl"));
|
|
||||||
setUdpSignature(jObject.optString("mUdpSignature"));
|
|
||||||
|
|
||||||
setJsonString(jObject.toString());
|
|
||||||
} catch (JSONException e) {
|
|
||||||
e.printStackTrace();
|
|
||||||
} catch (UnsupportedEncodingException e) {
|
|
||||||
e.printStackTrace();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getPaymentId() {
|
|
||||||
return mPaymentId;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setPaymentId(String _paymentId) {
|
|
||||||
mPaymentId = _paymentId;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getPurchaseId() {
|
|
||||||
return mPurchaseId;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setPurchaseId(String _purchaseId) {
|
|
||||||
mPurchaseId = _purchaseId;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getPurchaseDate() {
|
|
||||||
return mPurchaseDate;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setPurchaseDate(String _purchaseDate) {
|
|
||||||
mPurchaseDate = _purchaseDate;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getVerifyUrl() {
|
|
||||||
return mVerifyUrl;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setVerifyUrl(String _verifyUrl) {
|
|
||||||
mVerifyUrl = _verifyUrl;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getPassThroughParam() {
|
|
||||||
return mPassThroughParam;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setPassThroughParam(String _passThroughParam) {
|
|
||||||
mPassThroughParam = _passThroughParam;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getItemImageUrl() {
|
|
||||||
return mItemImageUrl;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setItemImageUrl(String _itemImageUrl) {
|
|
||||||
mItemImageUrl = _itemImageUrl;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getItemDownloadUrl() {
|
|
||||||
return mItemDownloadUrl;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setItemDownloadUrl(String _itemDownloadUrl) {
|
|
||||||
mItemDownloadUrl = _itemDownloadUrl;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getReserved1() {
|
|
||||||
return mReserved1;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setReserved1(String _reserved1) {
|
|
||||||
mReserved1 = _reserved1;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getReserved2() {
|
|
||||||
return mReserved2;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setReserved2(String _reserved2) {
|
|
||||||
mReserved2 = _reserved2;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getOrderId() {
|
|
||||||
return mOrderId;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setOrderId(String orderId) {
|
|
||||||
this.mOrderId = orderId;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getUdpSignature() {
|
|
||||||
return mUdpSignature;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setUdpSignature(String udpSignature) {
|
|
||||||
this.mUdpSignature = udpSignature;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getJsonString() {
|
|
||||||
return mJsonString;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setJsonString(String _jsonString) {
|
|
||||||
mJsonString = _jsonString;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String dump() {
|
|
||||||
String dump = super.dump() + "\n";
|
|
||||||
|
|
||||||
dump += "PaymentID : " + getPaymentId() + "\n" +
|
|
||||||
"PurchaseId : " + getPurchaseId() + "\n" +
|
|
||||||
"PurchaseDate : " + getPurchaseDate() + "\n" +
|
|
||||||
"PassThroughParam : " + getPassThroughParam() + "\n" +
|
|
||||||
"VerifyUrl : " + getVerifyUrl() + "\n" +
|
|
||||||
"ItemImageUrl : " + getItemImageUrl() + "\n" +
|
|
||||||
"ItemDownloadUrl : " + getItemDownloadUrl() + "\n" +
|
|
||||||
"Reserved1 : " + getReserved1() + "\n" +
|
|
||||||
"Reserved2 : " + getReserved2() + "\n" +
|
|
||||||
"UdpSignature : " + getUdpSignature();
|
|
||||||
|
|
||||||
return dump;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -52,4 +52,8 @@
|
|||||||
<string name="mids_sapps_header_update_galaxy_apps">Update Galaxy Apps</string>
|
<string name="mids_sapps_header_update_galaxy_apps">Update Galaxy Apps</string>
|
||||||
<!-- MIDS_SAPPS_POP_A_NEW_VERSION_IS_AVAILABLE_GALAXY_APPS_WILL_BE_UPDATED_TO_THE_LATEST_VERSION_TO_COMPLETE_THIS_PURCHASE -->
|
<!-- MIDS_SAPPS_POP_A_NEW_VERSION_IS_AVAILABLE_GALAXY_APPS_WILL_BE_UPDATED_TO_THE_LATEST_VERSION_TO_COMPLETE_THIS_PURCHASE -->
|
||||||
<string name="mids_sapps_pop_a_new_version_is_available_galaxy_apps_will_be_updated_to_the_latest_version_to_complete_this_purchase">A new version is available. Galaxy Apps will be updated to the latest version to complete this purchase.</string>
|
<string name="mids_sapps_pop_a_new_version_is_available_galaxy_apps_will_be_updated_to_the_latest_version_to_complete_this_purchase">A new version is available. Galaxy Apps will be updated to the latest version to complete this purchase.</string>
|
||||||
|
|
||||||
|
<string name="pay_cancel">Payment cancellation</string>
|
||||||
|
<string name="pay_suc">Payment successful</string>
|
||||||
|
<string name="pay_fail">Payment failed</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|||||||
@@ -52,4 +52,8 @@
|
|||||||
<string name="mids_sapps_header_update_galaxy_apps">更新三星应用商店</string>
|
<string name="mids_sapps_header_update_galaxy_apps">更新三星应用商店</string>
|
||||||
<!-- MIDS_SAPPS_POP_A_NEW_VERSION_IS_AVAILABLE_GALAXY_APPS_WILL_BE_UPDATED_TO_THE_LATEST_VERSION_TO_COMPLETE_THIS_PURCHASE -->
|
<!-- MIDS_SAPPS_POP_A_NEW_VERSION_IS_AVAILABLE_GALAXY_APPS_WILL_BE_UPDATED_TO_THE_LATEST_VERSION_TO_COMPLETE_THIS_PURCHASE -->
|
||||||
<string name="mids_sapps_pop_a_new_version_is_available_galaxy_apps_will_be_updated_to_the_latest_version_to_complete_this_purchase">有新版本可用。三星应用商店将更新至最新版本以完成本次购物。</string>
|
<string name="mids_sapps_pop_a_new_version_is_available_galaxy_apps_will_be_updated_to_the_latest_version_to_complete_this_purchase">有新版本可用。三星应用商店将更新至最新版本以完成本次购物。</string>
|
||||||
|
|
||||||
|
<string name="pay_cancel">支付取消</string>
|
||||||
|
<string name="pay_suc">支付成功</string>
|
||||||
|
<string name="pay_fail">支付失敗</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|||||||
@@ -52,4 +52,8 @@
|
|||||||
<string name="mids_sapps_header_update_galaxy_apps">更新 Galaxy Apps</string>
|
<string name="mids_sapps_header_update_galaxy_apps">更新 Galaxy Apps</string>
|
||||||
<!-- MIDS_SAPPS_POP_A_NEW_VERSION_IS_AVAILABLE_GALAXY_APPS_WILL_BE_UPDATED_TO_THE_LATEST_VERSION_TO_COMPLETE_THIS_PURCHASE -->
|
<!-- MIDS_SAPPS_POP_A_NEW_VERSION_IS_AVAILABLE_GALAXY_APPS_WILL_BE_UPDATED_TO_THE_LATEST_VERSION_TO_COMPLETE_THIS_PURCHASE -->
|
||||||
<string name="mids_sapps_pop_a_new_version_is_available_galaxy_apps_will_be_updated_to_the_latest_version_to_complete_this_purchase">已有新版本可用。Galaxy Apps 將更新至最新版本以完成此購買。</string>
|
<string name="mids_sapps_pop_a_new_version_is_available_galaxy_apps_will_be_updated_to_the_latest_version_to_complete_this_purchase">已有新版本可用。Galaxy Apps 將更新至最新版本以完成此購買。</string>
|
||||||
|
|
||||||
|
<string name="pay_cancel">支付取消</string>
|
||||||
|
<string name="pay_suc">支付成功</string>
|
||||||
|
<string name="pay_fail">支付失敗</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|||||||
@@ -52,4 +52,8 @@
|
|||||||
<string name="mids_sapps_header_update_galaxy_apps">更新 Galaxy Apps</string>
|
<string name="mids_sapps_header_update_galaxy_apps">更新 Galaxy Apps</string>
|
||||||
<!-- MIDS_SAPPS_POP_A_NEW_VERSION_IS_AVAILABLE_GALAXY_APPS_WILL_BE_UPDATED_TO_THE_LATEST_VERSION_TO_COMPLETE_THIS_PURCHASE -->
|
<!-- MIDS_SAPPS_POP_A_NEW_VERSION_IS_AVAILABLE_GALAXY_APPS_WILL_BE_UPDATED_TO_THE_LATEST_VERSION_TO_COMPLETE_THIS_PURCHASE -->
|
||||||
<string name="mids_sapps_pop_a_new_version_is_available_galaxy_apps_will_be_updated_to_the_latest_version_to_complete_this_purchase">有可用的新版本。Galaxy Apps 將更新至最新版本以完成此筆購買。</string>
|
<string name="mids_sapps_pop_a_new_version_is_available_galaxy_apps_will_be_updated_to_the_latest_version_to_complete_this_purchase">有可用的新版本。Galaxy Apps 將更新至最新版本以完成此筆購買。</string>
|
||||||
|
|
||||||
|
<string name="pay_cancel">支付取消</string>
|
||||||
|
<string name="pay_suc">支付成功</string>
|
||||||
|
<string name="pay_fail">支付失敗</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|||||||
@@ -20,5 +20,7 @@
|
|||||||
<string name="ids_com_body_error_code_c" did="IDS_COM_BODY_ERROR_CODE_C">Error code:</string>
|
<string name="ids_com_body_error_code_c" did="IDS_COM_BODY_ERROR_CODE_C">Error code:</string>
|
||||||
<!-- unused -->
|
<!-- unused -->
|
||||||
<string name="mids_sapps_pop_to_purchase_items_you_need_to_install_samsung_in_app_purchase_install_q" did="MIDS_SAPPS_POP_TO_PURCHASE_ITEMS_YOU_NEED_TO_INSTALL_SAMSUNG_IN_APP_PURCHASE_INSTALL_Q">To purchase items, you need to install Samsung In-App Purchase. Install?</string>
|
<string name="mids_sapps_pop_to_purchase_items_you_need_to_install_samsung_in_app_purchase_install_q" did="MIDS_SAPPS_POP_TO_PURCHASE_ITEMS_YOU_NEED_TO_INSTALL_SAMSUNG_IN_APP_PURCHASE_INSTALL_Q">To purchase items, you need to install Samsung In-App Purchase. Install?</string>
|
||||||
|
<string name="pay_cancel">Payment cancellation</string>
|
||||||
|
<string name="pay_suc">Payment successful</string>
|
||||||
|
<string name="pay_fail">Payment failed</string>
|
||||||
</resources>
|
</resources>
|
||||||
2
SVGAlibrary/.gitignore
vendored
Normal file
2
SVGAlibrary/.gitignore
vendored
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
/build
|
||||||
|
*.iml
|
||||||
37
SVGAlibrary/build.gradle
Normal file
37
SVGAlibrary/build.gradle
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
apply plugin: 'com.android.library'
|
||||||
|
apply plugin: 'kotlin-android'
|
||||||
|
|
||||||
|
|
||||||
|
android {
|
||||||
|
compileSdkVersion 28
|
||||||
|
defaultConfig {
|
||||||
|
minSdkVersion 14
|
||||||
|
targetSdkVersion 28
|
||||||
|
}
|
||||||
|
compileOptions {
|
||||||
|
kotlinOptions.freeCompilerArgs += ['-module-name', "com.opensource.svgaplayer"]
|
||||||
|
}
|
||||||
|
buildTypes {
|
||||||
|
release {
|
||||||
|
minifyEnabled false
|
||||||
|
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
compileOptions {
|
||||||
|
sourceCompatibility JavaVersion.VERSION_1_8
|
||||||
|
targetCompatibility JavaVersion.VERSION_1_8
|
||||||
|
}
|
||||||
|
packagingOptions {
|
||||||
|
exclude 'META-INF/ASL2.0'
|
||||||
|
exclude 'META-INF/LICENSE'
|
||||||
|
exclude 'META-INF/NOTICE'
|
||||||
|
exclude 'META-INF/NOTICE.txt'
|
||||||
|
exclude 'META-INF/LICENSE.txt'
|
||||||
|
exclude 'META-INF/MANIFEST.MF'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
dependencies {
|
||||||
|
implementation 'com.squareup.wire:wire-runtime:4.4.1'
|
||||||
|
}
|
||||||
17
SVGAlibrary/proguard-rules.pro
vendored
Normal file
17
SVGAlibrary/proguard-rules.pro
vendored
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
# Add project specific ProGuard rules here.
|
||||||
|
# By default, the flags in this file are appended to flags specified
|
||||||
|
# in /Users/PonyCui_Home/Library/Android/sdk/tools/proguard/proguard-android.txt
|
||||||
|
# You can edit the include path and order by changing the proguardFiles
|
||||||
|
# directive in build.gradle.
|
||||||
|
#
|
||||||
|
# For more details, see
|
||||||
|
# http://developer.android.com/guide/developing/tools/proguard.html
|
||||||
|
|
||||||
|
# Add any project specific keep options here:
|
||||||
|
|
||||||
|
# If your project uses WebView with JS, uncomment the following
|
||||||
|
# and specify the fully qualified class name to the JavaScript interface
|
||||||
|
# class:
|
||||||
|
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
|
||||||
|
# public *;
|
||||||
|
#}
|
||||||
8
SVGAlibrary/src/main/AndroidManifest.xml
Normal file
8
SVGAlibrary/src/main/AndroidManifest.xml
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
package="com.opensource.svgaplayer">
|
||||||
|
|
||||||
|
<application android:allowBackup="true" android:label="@string/app_name">
|
||||||
|
|
||||||
|
</application>
|
||||||
|
|
||||||
|
</manifest>
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
package com.opensource.svgaplayer
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Created by miaojun on 2019/6/21.
|
||||||
|
* mail:1290846731@qq.com
|
||||||
|
*/
|
||||||
|
interface IClickAreaListener{
|
||||||
|
fun onResponseArea(key : String,x0 : Int, y0 : Int, x1 : Int, y1 : Int)
|
||||||
|
}
|
||||||
119
SVGAlibrary/src/main/java/com/opensource/svgaplayer/SVGACache.kt
Normal file
119
SVGAlibrary/src/main/java/com/opensource/svgaplayer/SVGACache.kt
Normal file
@@ -0,0 +1,119 @@
|
|||||||
|
package com.opensource.svgaplayer
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import com.opensource.svgaplayer.utils.log.LogUtils
|
||||||
|
import java.io.File
|
||||||
|
import java.net.URL
|
||||||
|
import java.security.MessageDigest
|
||||||
|
|
||||||
|
/**
|
||||||
|
* SVGA 缓存管理
|
||||||
|
*/
|
||||||
|
object SVGACache {
|
||||||
|
enum class Type {
|
||||||
|
DEFAULT,
|
||||||
|
FILE
|
||||||
|
}
|
||||||
|
|
||||||
|
private const val TAG = "SVGACache"
|
||||||
|
private var type: Type = Type.DEFAULT
|
||||||
|
private var cacheDir: String = "/"
|
||||||
|
get() {
|
||||||
|
if (field != "/") {
|
||||||
|
val dir = File(field)
|
||||||
|
if (!dir.exists()) {
|
||||||
|
dir.mkdirs()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return field
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
fun onCreate(context: Context?) {
|
||||||
|
onCreate(context, Type.DEFAULT)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun onCreate(context: Context?, type: Type) {
|
||||||
|
if (isInitialized()) return
|
||||||
|
context ?: return
|
||||||
|
cacheDir = "${context.cacheDir.absolutePath}/svga/"
|
||||||
|
File(cacheDir).takeIf { !it.exists() }?.mkdirs()
|
||||||
|
this.type = type
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 清理缓存
|
||||||
|
*/
|
||||||
|
fun clearCache() {
|
||||||
|
if (!isInitialized()) {
|
||||||
|
LogUtils.error(TAG, "SVGACache is not init!")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
SVGAParser.threadPoolExecutor.execute {
|
||||||
|
clearDir(cacheDir)
|
||||||
|
LogUtils.info(TAG, "Clear svga cache done!")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 清除目录下的所有文件
|
||||||
|
internal fun clearDir(path: String) {
|
||||||
|
try {
|
||||||
|
val dir = File(path)
|
||||||
|
dir.takeIf { it.exists() }?.let { parentDir ->
|
||||||
|
parentDir.listFiles()?.forEach { file ->
|
||||||
|
if (!file.exists()) {
|
||||||
|
return@forEach
|
||||||
|
}
|
||||||
|
if (file.isDirectory) {
|
||||||
|
clearDir(file.absolutePath)
|
||||||
|
}
|
||||||
|
file.delete()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (e: Exception) {
|
||||||
|
LogUtils.error(TAG, "Clear svga cache path: $path fail", e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun isInitialized(): Boolean {
|
||||||
|
return "/" != cacheDir && File(cacheDir).exists()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun isDefaultCache(): Boolean = type == Type.DEFAULT
|
||||||
|
|
||||||
|
fun isCached(cacheKey: String): Boolean {
|
||||||
|
return if (isDefaultCache()) {
|
||||||
|
buildCacheDir(cacheKey)
|
||||||
|
} else {
|
||||||
|
buildSvgaFile(
|
||||||
|
cacheKey
|
||||||
|
)
|
||||||
|
}.exists()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun buildCacheKey(str: String): String {
|
||||||
|
val messageDigest = MessageDigest.getInstance("MD5")
|
||||||
|
messageDigest.update(str.toByteArray(charset("UTF-8")))
|
||||||
|
val digest = messageDigest.digest()
|
||||||
|
var sb = ""
|
||||||
|
for (b in digest) {
|
||||||
|
sb += String.format("%02x", b)
|
||||||
|
}
|
||||||
|
return sb
|
||||||
|
}
|
||||||
|
|
||||||
|
fun buildCacheKey(url: URL): String = buildCacheKey(url.toString())
|
||||||
|
|
||||||
|
fun buildCacheDir(cacheKey: String): File {
|
||||||
|
return File("$cacheDir$cacheKey/")
|
||||||
|
}
|
||||||
|
|
||||||
|
fun buildSvgaFile(cacheKey: String): File {
|
||||||
|
return File("$cacheDir$cacheKey.svga")
|
||||||
|
}
|
||||||
|
|
||||||
|
fun buildAudioFile(audio: String): File {
|
||||||
|
return File("$cacheDir$audio.mp3")
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
package com.opensource.svgaplayer
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Created by cuiminghui on 2017/3/30.
|
||||||
|
*/
|
||||||
|
interface SVGACallback {
|
||||||
|
|
||||||
|
fun onPause()
|
||||||
|
fun onFinished()
|
||||||
|
fun onRepeat()
|
||||||
|
fun onStep(frame: Int, percentage: Double)
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
package com.opensource.svgaplayer
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Created by miaojun on 2019/6/21.
|
||||||
|
* mail:1290846731@qq.com
|
||||||
|
*/
|
||||||
|
interface SVGAClickAreaListener{
|
||||||
|
fun onClick(clickKey : String)
|
||||||
|
}
|
||||||
@@ -0,0 +1,106 @@
|
|||||||
|
package com.opensource.svgaplayer
|
||||||
|
|
||||||
|
import android.graphics.Canvas
|
||||||
|
import android.graphics.ColorFilter
|
||||||
|
import android.graphics.PixelFormat
|
||||||
|
import android.graphics.drawable.Drawable
|
||||||
|
import android.widget.ImageView
|
||||||
|
import com.opensource.svgaplayer.drawer.SVGACanvasDrawer
|
||||||
|
|
||||||
|
class SVGADrawable(val videoItem: SVGAVideoEntity, val dynamicItem: SVGADynamicEntity): Drawable() {
|
||||||
|
|
||||||
|
constructor(videoItem: SVGAVideoEntity): this(videoItem, SVGADynamicEntity())
|
||||||
|
|
||||||
|
var cleared = true
|
||||||
|
internal set (value) {
|
||||||
|
if (field == value) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
field = value
|
||||||
|
invalidateSelf()
|
||||||
|
}
|
||||||
|
|
||||||
|
var currentFrame = 0
|
||||||
|
internal set (value) {
|
||||||
|
if (field == value) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
field = value
|
||||||
|
invalidateSelf()
|
||||||
|
}
|
||||||
|
|
||||||
|
var scaleType: ImageView.ScaleType = ImageView.ScaleType.MATRIX
|
||||||
|
|
||||||
|
private val drawer = SVGACanvasDrawer(videoItem, dynamicItem)
|
||||||
|
|
||||||
|
override fun draw(canvas: Canvas?) {
|
||||||
|
if (cleared) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
canvas?.let {
|
||||||
|
drawer.drawFrame(it,currentFrame, scaleType)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun setAlpha(alpha: Int) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getOpacity(): Int {
|
||||||
|
return PixelFormat.TRANSPARENT
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun setColorFilter(colorFilter: ColorFilter?) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
fun resume() {
|
||||||
|
videoItem.audioList.forEach { audio ->
|
||||||
|
audio.playID?.let {
|
||||||
|
if (SVGASoundManager.isInit()){
|
||||||
|
SVGASoundManager.resume(it)
|
||||||
|
}else{
|
||||||
|
videoItem.soundPool?.resume(it)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun pause() {
|
||||||
|
videoItem.audioList.forEach { audio ->
|
||||||
|
audio.playID?.let {
|
||||||
|
if (SVGASoundManager.isInit()){
|
||||||
|
SVGASoundManager.pause(it)
|
||||||
|
}else{
|
||||||
|
videoItem.soundPool?.pause(it)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun stop() {
|
||||||
|
videoItem.audioList.forEach { audio ->
|
||||||
|
audio.playID?.let {
|
||||||
|
if (SVGASoundManager.isInit()){
|
||||||
|
SVGASoundManager.stop(it)
|
||||||
|
}else{
|
||||||
|
videoItem.soundPool?.stop(it)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun clear() {
|
||||||
|
videoItem.audioList.forEach { audio ->
|
||||||
|
audio.playID?.let {
|
||||||
|
if (SVGASoundManager.isInit()){
|
||||||
|
SVGASoundManager.stop(it)
|
||||||
|
}else{
|
||||||
|
videoItem.soundPool?.stop(it)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
audio.playID = null
|
||||||
|
}
|
||||||
|
videoItem.clear()
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,153 @@
|
|||||||
|
package com.opensource.svgaplayer
|
||||||
|
|
||||||
|
import android.graphics.Bitmap
|
||||||
|
import android.graphics.BitmapFactory
|
||||||
|
import android.graphics.Canvas
|
||||||
|
import android.text.BoringLayout
|
||||||
|
import android.text.StaticLayout
|
||||||
|
import android.text.TextPaint
|
||||||
|
import java.net.HttpURLConnection
|
||||||
|
import java.net.URL
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Created by cuiminghui on 2017/3/30.
|
||||||
|
*/
|
||||||
|
class SVGADynamicEntity {
|
||||||
|
|
||||||
|
internal var dynamicHidden: HashMap<String, Boolean> = hashMapOf()
|
||||||
|
|
||||||
|
internal var dynamicImage: HashMap<String, Bitmap> = hashMapOf()
|
||||||
|
|
||||||
|
internal var dynamicText: HashMap<String, String> = hashMapOf()
|
||||||
|
|
||||||
|
internal var dynamicTextPaint: HashMap<String, TextPaint> = hashMapOf()
|
||||||
|
|
||||||
|
internal var dynamicStaticLayoutText: HashMap<String, StaticLayout> = hashMapOf()
|
||||||
|
|
||||||
|
internal var dynamicBoringLayoutText: HashMap<String, BoringLayout> = hashMapOf()
|
||||||
|
|
||||||
|
internal var dynamicDrawer: HashMap<String, (canvas: Canvas, frameIndex: Int) -> Boolean> = hashMapOf()
|
||||||
|
|
||||||
|
//点击事件回调map
|
||||||
|
internal var mClickMap : HashMap<String, IntArray> = hashMapOf()
|
||||||
|
internal var dynamicIClickArea: HashMap<String, IClickAreaListener> = hashMapOf()
|
||||||
|
|
||||||
|
internal var dynamicDrawerSized: HashMap<String, (canvas: Canvas, frameIndex: Int, width: Int, height: Int) -> Boolean> = hashMapOf()
|
||||||
|
|
||||||
|
|
||||||
|
internal var isTextDirty = false
|
||||||
|
|
||||||
|
fun setHidden(value: Boolean, forKey: String) {
|
||||||
|
this.dynamicHidden.put(forKey, value)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun setDynamicImage(bitmap: Bitmap, forKey: String) {
|
||||||
|
this.dynamicImage.put(forKey, bitmap)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun setDynamicImage(url: String, forKey: String) {
|
||||||
|
val handler = android.os.Handler()
|
||||||
|
SVGAParser.threadPoolExecutor.execute {
|
||||||
|
(URL(url).openConnection() as? HttpURLConnection)?.let {
|
||||||
|
try {
|
||||||
|
it.connectTimeout = 20 * 1000
|
||||||
|
it.requestMethod = "GET"
|
||||||
|
it.connect()
|
||||||
|
it.inputStream.use { stream ->
|
||||||
|
BitmapFactory.decodeStream(stream)?.let {
|
||||||
|
handler.post { setDynamicImage(it, forKey) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (e: Exception) {
|
||||||
|
e.printStackTrace()
|
||||||
|
} finally {
|
||||||
|
try {
|
||||||
|
it.disconnect()
|
||||||
|
} catch (disconnectException: Throwable) {
|
||||||
|
// ignored here
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun setDynamicText(text: String, textPaint: TextPaint, forKey: String) {
|
||||||
|
this.isTextDirty = true
|
||||||
|
this.dynamicText.put(forKey, text)
|
||||||
|
this.dynamicTextPaint.put(forKey, textPaint)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun setDynamicText(layoutText: StaticLayout, forKey: String) {
|
||||||
|
this.isTextDirty = true
|
||||||
|
this.dynamicStaticLayoutText.put(forKey, layoutText)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun setDynamicText(layoutText: BoringLayout, forKey: String) {
|
||||||
|
this.isTextDirty = true
|
||||||
|
BoringLayout.isBoring(layoutText.text,layoutText.paint)?.let {
|
||||||
|
this.dynamicBoringLayoutText.put(forKey,layoutText)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun setDynamicDrawer(drawer: (canvas: Canvas, frameIndex: Int) -> Boolean, forKey: String) {
|
||||||
|
this.dynamicDrawer.put(forKey, drawer)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun setClickArea(clickKey: List<String>) {
|
||||||
|
for(itemKey in clickKey){
|
||||||
|
dynamicIClickArea.put(itemKey,object : IClickAreaListener {
|
||||||
|
override fun onResponseArea(key: String, x0: Int, y0: Int, x1: Int, y1: Int) {
|
||||||
|
mClickMap.let {
|
||||||
|
if(it.get(key) == null){
|
||||||
|
it.put(key, intArrayOf(x0,y0,x1,y1))
|
||||||
|
}else{
|
||||||
|
it.get(key)?.let {
|
||||||
|
it[0] = x0
|
||||||
|
it[1] = y0
|
||||||
|
it[2] = x1
|
||||||
|
it[3] = y1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun setClickArea(clickKey: String) {
|
||||||
|
dynamicIClickArea.put(clickKey, object : IClickAreaListener {
|
||||||
|
override fun onResponseArea(key: String, x0: Int, y0: Int, x1: Int, y1: Int) {
|
||||||
|
mClickMap.let {
|
||||||
|
if (it.get(key) == null) {
|
||||||
|
it.put(key, intArrayOf(x0, y0, x1, y1))
|
||||||
|
} else {
|
||||||
|
it.get(key)?.let {
|
||||||
|
it[0] = x0
|
||||||
|
it[1] = y0
|
||||||
|
it[2] = x1
|
||||||
|
it[3] = y1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fun setDynamicDrawerSized(drawer: (canvas: Canvas, frameIndex: Int, width: Int, height: Int) -> Boolean, forKey: String) {
|
||||||
|
this.dynamicDrawerSized.put(forKey, drawer)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun clearDynamicObjects() {
|
||||||
|
this.isTextDirty = true
|
||||||
|
this.dynamicHidden.clear()
|
||||||
|
this.dynamicImage.clear()
|
||||||
|
this.dynamicText.clear()
|
||||||
|
this.dynamicTextPaint.clear()
|
||||||
|
this.dynamicStaticLayoutText.clear()
|
||||||
|
this.dynamicBoringLayoutText.clear()
|
||||||
|
this.dynamicDrawer.clear()
|
||||||
|
this.dynamicIClickArea.clear()
|
||||||
|
this.mClickMap.clear()
|
||||||
|
this.dynamicDrawerSized.clear()
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,329 @@
|
|||||||
|
package com.opensource.svgaplayer
|
||||||
|
|
||||||
|
import android.animation.Animator
|
||||||
|
import android.animation.ValueAnimator
|
||||||
|
import android.annotation.SuppressLint
|
||||||
|
import android.content.Context
|
||||||
|
import android.os.Build
|
||||||
|
import android.util.AttributeSet
|
||||||
|
import android.view.MotionEvent
|
||||||
|
import android.view.View
|
||||||
|
import android.view.animation.LinearInterpolator
|
||||||
|
import android.widget.ImageView
|
||||||
|
import com.opensource.svgaplayer.utils.SVGARange
|
||||||
|
import com.opensource.svgaplayer.utils.log.LogUtils
|
||||||
|
import java.lang.ref.WeakReference
|
||||||
|
import java.net.URL
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Created by PonyCui on 2017/3/29.
|
||||||
|
*/
|
||||||
|
open class SVGAImageView @JvmOverloads constructor(
|
||||||
|
context: Context,
|
||||||
|
attrs: AttributeSet? = null,
|
||||||
|
defStyleAttr: Int = 0
|
||||||
|
) : ImageView(context, attrs, defStyleAttr) {
|
||||||
|
|
||||||
|
private val TAG = "SVGAImageView"
|
||||||
|
|
||||||
|
enum class FillMode {
|
||||||
|
Backward,
|
||||||
|
Forward,
|
||||||
|
Clear,
|
||||||
|
}
|
||||||
|
|
||||||
|
var isAnimating = false
|
||||||
|
private set
|
||||||
|
|
||||||
|
var loops = 0
|
||||||
|
|
||||||
|
@Deprecated(
|
||||||
|
"It is recommended to use clearAfterDetached, or manually call to SVGAVideoEntity#clear." +
|
||||||
|
"If you just consider cleaning up the canvas after playing, you can use FillMode#Clear.",
|
||||||
|
level = DeprecationLevel.WARNING
|
||||||
|
)
|
||||||
|
var clearsAfterStop = false
|
||||||
|
var clearsAfterDetached = false
|
||||||
|
var fillMode: FillMode = FillMode.Forward
|
||||||
|
var callback: SVGACallback? = null
|
||||||
|
|
||||||
|
private var mAnimator: ValueAnimator? = null
|
||||||
|
private var mItemClickAreaListener: SVGAClickAreaListener? = null
|
||||||
|
private var mAntiAlias = true
|
||||||
|
private var mAutoPlay = true
|
||||||
|
private val mAnimatorListener = AnimatorListener(this)
|
||||||
|
private val mAnimatorUpdateListener = AnimatorUpdateListener(this)
|
||||||
|
private var mStartFrame = 0
|
||||||
|
private var mEndFrame = 0
|
||||||
|
|
||||||
|
init {
|
||||||
|
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN_MR2) {
|
||||||
|
this.setLayerType(View.LAYER_TYPE_SOFTWARE, null)
|
||||||
|
}
|
||||||
|
attrs?.let { loadAttrs(it) }
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun loadAttrs(attrs: AttributeSet) {
|
||||||
|
val typedArray = context.theme.obtainStyledAttributes(attrs, R.styleable.SVGAImageView, 0, 0)
|
||||||
|
loops = typedArray.getInt(R.styleable.SVGAImageView_loopCount, 0)
|
||||||
|
clearsAfterStop = typedArray.getBoolean(R.styleable.SVGAImageView_clearsAfterStop, false)
|
||||||
|
clearsAfterDetached = typedArray.getBoolean(R.styleable.SVGAImageView_clearsAfterDetached, false)
|
||||||
|
mAntiAlias = typedArray.getBoolean(R.styleable.SVGAImageView_antiAlias, true)
|
||||||
|
mAutoPlay = typedArray.getBoolean(R.styleable.SVGAImageView_autoPlay, true)
|
||||||
|
typedArray.getString(R.styleable.SVGAImageView_fillMode)?.let {
|
||||||
|
when (it) {
|
||||||
|
"0" -> {
|
||||||
|
fillMode = FillMode.Backward
|
||||||
|
}
|
||||||
|
"1" -> {
|
||||||
|
fillMode = FillMode.Forward
|
||||||
|
}
|
||||||
|
"2" -> {
|
||||||
|
fillMode = FillMode.Clear
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
typedArray.getString(R.styleable.SVGAImageView_source)?.let {
|
||||||
|
parserSource(it)
|
||||||
|
}
|
||||||
|
typedArray.recycle()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun parserSource(source: String) {
|
||||||
|
val refImgView = WeakReference<SVGAImageView>(this)
|
||||||
|
val parser = SVGAParser(context)
|
||||||
|
if (source.startsWith("http://") || source.startsWith("https://")) {
|
||||||
|
parser.decodeFromURL(URL(source), createParseCompletion(refImgView))
|
||||||
|
} else {
|
||||||
|
parser.decodeFromAssets(source, createParseCompletion(refImgView))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun createParseCompletion(ref: WeakReference<SVGAImageView>): SVGAParser.ParseCompletion {
|
||||||
|
return object : SVGAParser.ParseCompletion {
|
||||||
|
override fun onComplete(videoItem: SVGAVideoEntity) {
|
||||||
|
ref.get()?.startAnimation(videoItem)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onError() {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun startAnimation(videoItem: SVGAVideoEntity) {
|
||||||
|
this@SVGAImageView.post {
|
||||||
|
videoItem.antiAlias = mAntiAlias
|
||||||
|
setVideoItem(videoItem)
|
||||||
|
getSVGADrawable()?.scaleType = scaleType
|
||||||
|
if (mAutoPlay) {
|
||||||
|
startAnimation()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun startAnimation() {
|
||||||
|
startAnimation(null, false)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun startAnimation(range: SVGARange?, reverse: Boolean = false) {
|
||||||
|
stopAnimation(false)
|
||||||
|
play(range, reverse)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun play(range: SVGARange?, reverse: Boolean) {
|
||||||
|
LogUtils.info(TAG, "================ start animation ================")
|
||||||
|
val drawable = getSVGADrawable() ?: return
|
||||||
|
setupDrawable()
|
||||||
|
mStartFrame = Math.max(0, range?.location ?: 0)
|
||||||
|
val videoItem = drawable.videoItem
|
||||||
|
mEndFrame = Math.min(videoItem.frames - 1, ((range?.location ?: 0) + (range?.length ?: Int.MAX_VALUE) - 1))
|
||||||
|
val animator = ValueAnimator.ofInt(mStartFrame, mEndFrame)
|
||||||
|
animator.interpolator = LinearInterpolator()
|
||||||
|
animator.duration = ((mEndFrame - mStartFrame + 1) * (1000 / videoItem.FPS) / generateScale()).toLong()
|
||||||
|
animator.repeatCount = if (loops <= 0) 99999 else loops - 1
|
||||||
|
animator.addUpdateListener(mAnimatorUpdateListener)
|
||||||
|
animator.addListener(mAnimatorListener)
|
||||||
|
if (reverse) {
|
||||||
|
animator.reverse()
|
||||||
|
} else {
|
||||||
|
animator.start()
|
||||||
|
}
|
||||||
|
mAnimator = animator
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun setupDrawable() {
|
||||||
|
val drawable = getSVGADrawable() ?: return
|
||||||
|
drawable.cleared = false
|
||||||
|
drawable.scaleType = scaleType
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun getSVGADrawable(): SVGADrawable? {
|
||||||
|
return drawable as? SVGADrawable
|
||||||
|
}
|
||||||
|
|
||||||
|
@Suppress("UNNECESSARY_SAFE_CALL")
|
||||||
|
private fun generateScale(): Double {
|
||||||
|
var scale = 1.0
|
||||||
|
try {
|
||||||
|
val animatorClass = Class.forName("android.animation.ValueAnimator") ?: return scale
|
||||||
|
val getMethod = animatorClass.getDeclaredMethod("getDurationScale") ?: return scale
|
||||||
|
scale = (getMethod.invoke(animatorClass) as Float).toDouble()
|
||||||
|
if (scale == 0.0) {
|
||||||
|
val setMethod = animatorClass.getDeclaredMethod("setDurationScale",Float::class.java) ?: return scale
|
||||||
|
setMethod.isAccessible = true
|
||||||
|
setMethod.invoke(animatorClass,1.0f)
|
||||||
|
scale = 1.0
|
||||||
|
LogUtils.info(TAG,
|
||||||
|
"The animation duration scale has been reset to" +
|
||||||
|
" 1.0x, because you closed it on developer options.")
|
||||||
|
}
|
||||||
|
} catch (ignore: Exception) {
|
||||||
|
ignore.printStackTrace()
|
||||||
|
}
|
||||||
|
return scale
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun onAnimatorUpdate(animator: ValueAnimator?) {
|
||||||
|
val drawable = getSVGADrawable() ?: return
|
||||||
|
drawable.currentFrame = animator?.animatedValue as Int
|
||||||
|
val percentage = (drawable.currentFrame + 1).toDouble() / drawable.videoItem.frames.toDouble()
|
||||||
|
callback?.onStep(drawable.currentFrame, percentage)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun onAnimationEnd(animation: Animator?) {
|
||||||
|
isAnimating = false
|
||||||
|
stopAnimation()
|
||||||
|
val drawable = getSVGADrawable()
|
||||||
|
if (drawable != null) {
|
||||||
|
when (fillMode) {
|
||||||
|
FillMode.Backward -> {
|
||||||
|
drawable.currentFrame = mStartFrame
|
||||||
|
}
|
||||||
|
FillMode.Forward -> {
|
||||||
|
drawable.currentFrame = mEndFrame
|
||||||
|
}
|
||||||
|
FillMode.Clear -> {
|
||||||
|
drawable.cleared = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
callback?.onFinished()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun clear() {
|
||||||
|
getSVGADrawable()?.cleared = true
|
||||||
|
getSVGADrawable()?.clear()
|
||||||
|
// 清除对 drawable 的引用
|
||||||
|
setImageDrawable(null)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun pauseAnimation() {
|
||||||
|
stopAnimation(false)
|
||||||
|
callback?.onPause()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun stopAnimation() {
|
||||||
|
stopAnimation(clear = clearsAfterStop)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun stopAnimation(clear: Boolean) {
|
||||||
|
mAnimator?.cancel()
|
||||||
|
mAnimator?.removeAllListeners()
|
||||||
|
mAnimator?.removeAllUpdateListeners()
|
||||||
|
getSVGADrawable()?.stop()
|
||||||
|
getSVGADrawable()?.cleared = clear
|
||||||
|
}
|
||||||
|
|
||||||
|
fun setVideoItem(videoItem: SVGAVideoEntity?) {
|
||||||
|
setVideoItem(videoItem, SVGADynamicEntity())
|
||||||
|
}
|
||||||
|
|
||||||
|
fun setVideoItem(videoItem: SVGAVideoEntity?, dynamicItem: SVGADynamicEntity?) {
|
||||||
|
if (videoItem == null) {
|
||||||
|
setImageDrawable(null)
|
||||||
|
} else {
|
||||||
|
val drawable = SVGADrawable(videoItem, dynamicItem ?: SVGADynamicEntity())
|
||||||
|
drawable.cleared = true
|
||||||
|
setImageDrawable(drawable)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun stepToFrame(frame: Int, andPlay: Boolean) {
|
||||||
|
pauseAnimation()
|
||||||
|
val drawable = getSVGADrawable() ?: return
|
||||||
|
drawable.currentFrame = frame
|
||||||
|
if (andPlay) {
|
||||||
|
startAnimation()
|
||||||
|
mAnimator?.let {
|
||||||
|
it.currentPlayTime = (Math.max(0.0f, Math.min(1.0f, (frame.toFloat() / drawable.videoItem.frames.toFloat()))) * it.duration).toLong()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun stepToPercentage(percentage: Double, andPlay: Boolean) {
|
||||||
|
val drawable = drawable as? SVGADrawable ?: return
|
||||||
|
var frame = (drawable.videoItem.frames * percentage).toInt()
|
||||||
|
if (frame >= drawable.videoItem.frames && frame > 0) {
|
||||||
|
frame = drawable.videoItem.frames - 1
|
||||||
|
}
|
||||||
|
stepToFrame(frame, andPlay)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun setOnAnimKeyClickListener(clickListener : SVGAClickAreaListener){
|
||||||
|
mItemClickAreaListener = clickListener
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressLint("ClickableViewAccessibility")
|
||||||
|
override fun onTouchEvent(event: MotionEvent?): Boolean {
|
||||||
|
if (event?.action != MotionEvent.ACTION_DOWN) {
|
||||||
|
return super.onTouchEvent(event)
|
||||||
|
}
|
||||||
|
val drawable = getSVGADrawable() ?: return super.onTouchEvent(event)
|
||||||
|
for ((key, value) in drawable.dynamicItem.mClickMap) {
|
||||||
|
if (event.x >= value[0] && event.x <= value[2] && event.y >= value[1] && event.y <= value[3]) {
|
||||||
|
mItemClickAreaListener?.let {
|
||||||
|
it.onClick(key)
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return super.onTouchEvent(event)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onDetachedFromWindow() {
|
||||||
|
super.onDetachedFromWindow()
|
||||||
|
stopAnimation(clearsAfterDetached)
|
||||||
|
if (clearsAfterDetached) {
|
||||||
|
clear()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private class AnimatorListener(view: SVGAImageView) : Animator.AnimatorListener {
|
||||||
|
private val weakReference = WeakReference<SVGAImageView>(view)
|
||||||
|
|
||||||
|
override fun onAnimationRepeat(animation: Animator?) {
|
||||||
|
weakReference.get()?.callback?.onRepeat()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onAnimationEnd(animation: Animator?) {
|
||||||
|
weakReference.get()?.onAnimationEnd(animation)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onAnimationCancel(animation: Animator?) {
|
||||||
|
weakReference.get()?.isAnimating = false
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onAnimationStart(animation: Animator?) {
|
||||||
|
weakReference.get()?.isAnimating = true
|
||||||
|
}
|
||||||
|
} // end of AnimatorListener
|
||||||
|
|
||||||
|
|
||||||
|
private class AnimatorUpdateListener(view: SVGAImageView) : ValueAnimator.AnimatorUpdateListener {
|
||||||
|
private val weakReference = WeakReference<SVGAImageView>(view)
|
||||||
|
|
||||||
|
override fun onAnimationUpdate(animation: ValueAnimator?) {
|
||||||
|
weakReference.get()?.onAnimatorUpdate(animation)
|
||||||
|
}
|
||||||
|
} // end of AnimatorUpdateListener
|
||||||
|
}
|
||||||
@@ -0,0 +1,565 @@
|
|||||||
|
package com.opensource.svgaplayer
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import android.net.http.HttpResponseCache
|
||||||
|
import android.os.Handler
|
||||||
|
import android.os.Looper
|
||||||
|
import com.opensource.svgaplayer.proto.MovieEntity
|
||||||
|
import com.opensource.svgaplayer.utils.log.LogUtils
|
||||||
|
import org.json.JSONObject
|
||||||
|
import java.io.*
|
||||||
|
import java.net.HttpURLConnection
|
||||||
|
import java.net.URL
|
||||||
|
import java.util.concurrent.Executors
|
||||||
|
import java.util.concurrent.ThreadPoolExecutor
|
||||||
|
import java.util.concurrent.atomic.AtomicInteger
|
||||||
|
import java.util.zip.Inflater
|
||||||
|
import java.util.zip.ZipInputStream
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Created by PonyCui 16/6/18.
|
||||||
|
*/
|
||||||
|
private var fileLock: Int = 0
|
||||||
|
private var isUnzipping = false
|
||||||
|
|
||||||
|
class SVGAParser(context: Context?) {
|
||||||
|
private var mContext = context?.applicationContext
|
||||||
|
|
||||||
|
init {
|
||||||
|
SVGACache.onCreate(context)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Volatile
|
||||||
|
private var mFrameWidth: Int = 0
|
||||||
|
|
||||||
|
@Volatile
|
||||||
|
private var mFrameHeight: Int = 0
|
||||||
|
|
||||||
|
interface ParseCompletion {
|
||||||
|
fun onComplete(videoItem: SVGAVideoEntity)
|
||||||
|
fun onError()
|
||||||
|
}
|
||||||
|
|
||||||
|
interface PlayCallback{
|
||||||
|
fun onPlay(file: List<File>)
|
||||||
|
}
|
||||||
|
|
||||||
|
open class FileDownloader {
|
||||||
|
|
||||||
|
var noCache = false
|
||||||
|
|
||||||
|
open fun resume(url: URL, complete: (inputStream: InputStream) -> Unit, failure: (e: Exception) -> Unit): () -> Unit {
|
||||||
|
var cancelled = false
|
||||||
|
val cancelBlock = {
|
||||||
|
cancelled = true
|
||||||
|
}
|
||||||
|
threadPoolExecutor.execute {
|
||||||
|
try {
|
||||||
|
LogUtils.info(TAG, "================ svga file download start ================")
|
||||||
|
if (HttpResponseCache.getInstalled() == null && !noCache) {
|
||||||
|
LogUtils.error(TAG, "SVGAParser can not handle cache before install HttpResponseCache. see https://github.com/yyued/SVGAPlayer-Android#cache")
|
||||||
|
LogUtils.error(TAG, "在配置 HttpResponseCache 前 SVGAParser 无法缓存. 查看 https://github.com/yyued/SVGAPlayer-Android#cache ")
|
||||||
|
}
|
||||||
|
(url.openConnection() as? HttpURLConnection)?.let {
|
||||||
|
it.connectTimeout = 20 * 1000
|
||||||
|
it.requestMethod = "GET"
|
||||||
|
it.setRequestProperty("Connection", "close")
|
||||||
|
it.connect()
|
||||||
|
it.inputStream.use { inputStream ->
|
||||||
|
ByteArrayOutputStream().use { outputStream ->
|
||||||
|
val buffer = ByteArray(4096)
|
||||||
|
var count: Int
|
||||||
|
while (true) {
|
||||||
|
if (cancelled) {
|
||||||
|
LogUtils.warn(TAG, "================ svga file download canceled ================")
|
||||||
|
break
|
||||||
|
}
|
||||||
|
count = inputStream.read(buffer, 0, 4096)
|
||||||
|
if (count == -1) {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
outputStream.write(buffer, 0, count)
|
||||||
|
}
|
||||||
|
if (cancelled) {
|
||||||
|
LogUtils.warn(TAG, "================ svga file download canceled ================")
|
||||||
|
return@execute
|
||||||
|
}
|
||||||
|
ByteArrayInputStream(outputStream.toByteArray()).use {
|
||||||
|
LogUtils.info(TAG, "================ svga file download complete ================")
|
||||||
|
complete(it)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (e: Exception) {
|
||||||
|
LogUtils.error(TAG, "================ svga file download fail ================")
|
||||||
|
LogUtils.error(TAG, "error: ${e.message}")
|
||||||
|
e.printStackTrace()
|
||||||
|
failure(e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return cancelBlock
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var fileDownloader = FileDownloader()
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
private const val TAG = "SVGAParser"
|
||||||
|
|
||||||
|
private val threadNum = AtomicInteger(0)
|
||||||
|
private var mShareParser = SVGAParser(null)
|
||||||
|
|
||||||
|
internal var threadPoolExecutor = Executors.newCachedThreadPool { r ->
|
||||||
|
Thread(r, "SVGAParser-Thread-${threadNum.getAndIncrement()}")
|
||||||
|
}
|
||||||
|
|
||||||
|
fun setThreadPoolExecutor(executor: ThreadPoolExecutor) {
|
||||||
|
threadPoolExecutor = executor
|
||||||
|
}
|
||||||
|
|
||||||
|
fun shareParser(): SVGAParser {
|
||||||
|
return mShareParser
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun init(context: Context) {
|
||||||
|
mContext = context.applicationContext
|
||||||
|
SVGACache.onCreate(mContext)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun setFrameSize(frameWidth: Int, frameHeight: Int) {
|
||||||
|
mFrameWidth = frameWidth
|
||||||
|
mFrameHeight = frameHeight
|
||||||
|
}
|
||||||
|
|
||||||
|
fun decodeFromAssets(
|
||||||
|
name: String,
|
||||||
|
callback: ParseCompletion?,
|
||||||
|
playCallback: PlayCallback? = null
|
||||||
|
) {
|
||||||
|
if (mContext == null) {
|
||||||
|
LogUtils.error(TAG, "在配置 SVGAParser context 前, 无法解析 SVGA 文件。")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
LogUtils.info(TAG, "================ decode $name from assets ================")
|
||||||
|
threadPoolExecutor.execute {
|
||||||
|
try {
|
||||||
|
mContext?.assets?.open(name)?.let {
|
||||||
|
this.decodeFromInputStream(
|
||||||
|
it,
|
||||||
|
SVGACache.buildCacheKey("file:///assets/$name"),
|
||||||
|
callback,
|
||||||
|
true,
|
||||||
|
playCallback,
|
||||||
|
alias = name
|
||||||
|
)
|
||||||
|
}
|
||||||
|
} catch (e: Exception) {
|
||||||
|
this.invokeErrorCallback(e, callback, name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
fun decodeFromURL(
|
||||||
|
url: URL,
|
||||||
|
callback: ParseCompletion?,
|
||||||
|
playCallback: PlayCallback? = null
|
||||||
|
): (() -> Unit)? {
|
||||||
|
if (mContext == null) {
|
||||||
|
LogUtils.error(TAG, "在配置 SVGAParser context 前, 无法解析 SVGA 文件。")
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
val urlPath = url.toString()
|
||||||
|
LogUtils.info(TAG, "================ decode from url: $urlPath ================")
|
||||||
|
val cacheKey = SVGACache.buildCacheKey(url);
|
||||||
|
return if (SVGACache.isCached(cacheKey)) {
|
||||||
|
LogUtils.info(TAG, "this url cached")
|
||||||
|
threadPoolExecutor.execute {
|
||||||
|
if (SVGACache.isDefaultCache()) {
|
||||||
|
this.decodeFromCacheKey(cacheKey, callback, alias = urlPath)
|
||||||
|
} else {
|
||||||
|
this.decodeFromSVGAFileCacheKey(cacheKey, callback, playCallback, alias = urlPath)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null
|
||||||
|
} else {
|
||||||
|
LogUtils.info(TAG, "no cached, prepare to download")
|
||||||
|
fileDownloader.resume(url, {
|
||||||
|
this.decodeFromInputStream(
|
||||||
|
it,
|
||||||
|
cacheKey,
|
||||||
|
callback,
|
||||||
|
false,
|
||||||
|
playCallback,
|
||||||
|
alias = urlPath
|
||||||
|
)
|
||||||
|
}, {
|
||||||
|
LogUtils.error(
|
||||||
|
TAG,
|
||||||
|
"================ svga file: $url download fail ================"
|
||||||
|
)
|
||||||
|
this.invokeErrorCallback(it, callback, alias = urlPath)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 读取解析本地缓存的 svga 文件.
|
||||||
|
*/
|
||||||
|
fun decodeFromSVGAFileCacheKey(
|
||||||
|
cacheKey: String,
|
||||||
|
callback: ParseCompletion?,
|
||||||
|
playCallback: PlayCallback?,
|
||||||
|
alias: String? = null
|
||||||
|
) {
|
||||||
|
threadPoolExecutor.execute {
|
||||||
|
try {
|
||||||
|
LogUtils.info(TAG, "================ decode $alias from svga cachel file to entity ================")
|
||||||
|
FileInputStream(SVGACache.buildSvgaFile(cacheKey)).use { inputStream ->
|
||||||
|
readAsBytes(inputStream)?.let { bytes ->
|
||||||
|
if (isZipFile(bytes)) {
|
||||||
|
this.decodeFromCacheKey(cacheKey, callback, alias)
|
||||||
|
} else {
|
||||||
|
LogUtils.info(TAG, "inflate start")
|
||||||
|
inflate(bytes)?.let {
|
||||||
|
LogUtils.info(TAG, "inflate complete")
|
||||||
|
val videoItem = SVGAVideoEntity(
|
||||||
|
MovieEntity.ADAPTER.decode(it),
|
||||||
|
File(cacheKey),
|
||||||
|
mFrameWidth,
|
||||||
|
mFrameHeight
|
||||||
|
)
|
||||||
|
LogUtils.info(TAG, "SVGAVideoEntity prepare start")
|
||||||
|
videoItem.prepare({
|
||||||
|
LogUtils.info(TAG, "SVGAVideoEntity prepare success")
|
||||||
|
this.invokeCompleteCallback(videoItem, callback, alias)
|
||||||
|
},playCallback)
|
||||||
|
|
||||||
|
} ?: this.invokeErrorCallback(
|
||||||
|
Exception("inflate(bytes) cause exception"),
|
||||||
|
callback,
|
||||||
|
alias
|
||||||
|
)
|
||||||
|
}
|
||||||
|
} ?: this.invokeErrorCallback(
|
||||||
|
Exception("readAsBytes(inputStream) cause exception"),
|
||||||
|
callback,
|
||||||
|
alias
|
||||||
|
)
|
||||||
|
}
|
||||||
|
} catch (e: java.lang.Exception) {
|
||||||
|
this.invokeErrorCallback(e, callback, alias)
|
||||||
|
} finally {
|
||||||
|
LogUtils.info(TAG, "================ decode $alias from svga cachel file to entity end ================")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun decodeFromInputStream(
|
||||||
|
inputStream: InputStream,
|
||||||
|
cacheKey: String,
|
||||||
|
callback: ParseCompletion?,
|
||||||
|
closeInputStream: Boolean = false,
|
||||||
|
playCallback: PlayCallback? = null,
|
||||||
|
alias: String? = null
|
||||||
|
) {
|
||||||
|
if (mContext == null) {
|
||||||
|
LogUtils.error(TAG, "在配置 SVGAParser context 前, 无法解析 SVGA 文件。")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
LogUtils.info(TAG, "================ decode $alias from input stream ================")
|
||||||
|
threadPoolExecutor.execute {
|
||||||
|
try {
|
||||||
|
readAsBytes(inputStream)?.let { bytes ->
|
||||||
|
if (isZipFile(bytes)) {
|
||||||
|
LogUtils.info(TAG, "decode from zip file")
|
||||||
|
if (!SVGACache.buildCacheDir(cacheKey).exists() || isUnzipping) {
|
||||||
|
synchronized(fileLock) {
|
||||||
|
if (!SVGACache.buildCacheDir(cacheKey).exists()) {
|
||||||
|
isUnzipping = true
|
||||||
|
LogUtils.info(TAG, "no cached, prepare to unzip")
|
||||||
|
ByteArrayInputStream(bytes).use {
|
||||||
|
unzip(it, cacheKey)
|
||||||
|
isUnzipping = false
|
||||||
|
LogUtils.info(TAG, "unzip success")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this.decodeFromCacheKey(cacheKey, callback, alias)
|
||||||
|
} else {
|
||||||
|
if (!SVGACache.isDefaultCache()) {
|
||||||
|
// 如果 SVGACache 设置类型为 FILE
|
||||||
|
threadPoolExecutor.execute {
|
||||||
|
SVGACache.buildSvgaFile(cacheKey).let { cacheFile ->
|
||||||
|
try {
|
||||||
|
cacheFile.takeIf { !it.exists() }?.createNewFile()
|
||||||
|
FileOutputStream(cacheFile).write(bytes)
|
||||||
|
} catch (e: Exception) {
|
||||||
|
LogUtils.error(TAG, "create cache file fail.", e)
|
||||||
|
cacheFile.delete()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
LogUtils.info(TAG, "inflate start")
|
||||||
|
inflate(bytes)?.let {
|
||||||
|
LogUtils.info(TAG, "inflate complete")
|
||||||
|
val videoItem = SVGAVideoEntity(
|
||||||
|
MovieEntity.ADAPTER.decode(it),
|
||||||
|
File(cacheKey),
|
||||||
|
mFrameWidth,
|
||||||
|
mFrameHeight
|
||||||
|
)
|
||||||
|
LogUtils.info(TAG, "SVGAVideoEntity prepare start")
|
||||||
|
videoItem.prepare({
|
||||||
|
LogUtils.info(TAG, "SVGAVideoEntity prepare success")
|
||||||
|
this.invokeCompleteCallback(videoItem, callback, alias)
|
||||||
|
},playCallback)
|
||||||
|
|
||||||
|
} ?: this.invokeErrorCallback(
|
||||||
|
Exception("inflate(bytes) cause exception"),
|
||||||
|
callback,
|
||||||
|
alias
|
||||||
|
)
|
||||||
|
}
|
||||||
|
} ?: this.invokeErrorCallback(
|
||||||
|
Exception("readAsBytes(inputStream) cause exception"),
|
||||||
|
callback,
|
||||||
|
alias
|
||||||
|
)
|
||||||
|
} catch (e: java.lang.Exception) {
|
||||||
|
this.invokeErrorCallback(e, callback, alias)
|
||||||
|
} finally {
|
||||||
|
if (closeInputStream) {
|
||||||
|
inputStream.close()
|
||||||
|
}
|
||||||
|
LogUtils.info(TAG, "================ decode $alias from input stream end ================")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @deprecated from 2.4.0
|
||||||
|
*/
|
||||||
|
@Deprecated("This method has been deprecated from 2.4.0.", ReplaceWith("this.decodeFromAssets(assetsName, callback)"))
|
||||||
|
fun parse(assetsName: String, callback: ParseCompletion?) {
|
||||||
|
this.decodeFromAssets(assetsName, callback,null)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @deprecated from 2.4.0
|
||||||
|
*/
|
||||||
|
@Deprecated("This method has been deprecated from 2.4.0.", ReplaceWith("this.decodeFromURL(url, callback)"))
|
||||||
|
fun parse(url: URL, callback: ParseCompletion?) {
|
||||||
|
this.decodeFromURL(url, callback,null)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @deprecated from 2.4.0
|
||||||
|
*/
|
||||||
|
@Deprecated("This method has been deprecated from 2.4.0.", ReplaceWith("this.decodeFromInputStream(inputStream, cacheKey, callback, closeInputStream)"))
|
||||||
|
fun parse(
|
||||||
|
inputStream: InputStream,
|
||||||
|
cacheKey: String,
|
||||||
|
callback: ParseCompletion?,
|
||||||
|
closeInputStream: Boolean = false
|
||||||
|
) {
|
||||||
|
this.decodeFromInputStream(inputStream, cacheKey, callback, closeInputStream,null)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun invokeCompleteCallback(
|
||||||
|
videoItem: SVGAVideoEntity,
|
||||||
|
callback: ParseCompletion?,
|
||||||
|
alias: String?
|
||||||
|
) {
|
||||||
|
Handler(Looper.getMainLooper()).post {
|
||||||
|
LogUtils.info(TAG, "================ $alias parser complete ================")
|
||||||
|
callback?.onComplete(videoItem)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun invokeErrorCallback(
|
||||||
|
e: Exception,
|
||||||
|
callback: ParseCompletion?,
|
||||||
|
alias: String?
|
||||||
|
) {
|
||||||
|
e.printStackTrace()
|
||||||
|
LogUtils.error(TAG, "================ $alias parser error ================")
|
||||||
|
LogUtils.error(TAG, "$alias parse error", e)
|
||||||
|
Handler(Looper.getMainLooper()).post {
|
||||||
|
callback?.onError()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun decodeFromCacheKey(
|
||||||
|
cacheKey: String,
|
||||||
|
callback: ParseCompletion?,
|
||||||
|
alias: String?
|
||||||
|
) {
|
||||||
|
LogUtils.info(TAG, "================ decode $alias from cache ================")
|
||||||
|
LogUtils.debug(TAG, "decodeFromCacheKey called with cacheKey : $cacheKey")
|
||||||
|
if (mContext == null) {
|
||||||
|
LogUtils.error(TAG, "在配置 SVGAParser context 前, 无法解析 SVGA 文件。")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
val cacheDir = SVGACache.buildCacheDir(cacheKey)
|
||||||
|
File(cacheDir, "movie.binary").takeIf { it.isFile }?.let { binaryFile ->
|
||||||
|
try {
|
||||||
|
LogUtils.info(TAG, "binary change to entity")
|
||||||
|
FileInputStream(binaryFile).use {
|
||||||
|
LogUtils.info(TAG, "binary change to entity success")
|
||||||
|
this.invokeCompleteCallback(
|
||||||
|
SVGAVideoEntity(
|
||||||
|
MovieEntity.ADAPTER.decode(it),
|
||||||
|
cacheDir,
|
||||||
|
mFrameWidth,
|
||||||
|
mFrameHeight
|
||||||
|
),
|
||||||
|
callback,
|
||||||
|
alias
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch (e: Exception) {
|
||||||
|
LogUtils.error(TAG, "binary change to entity fail", e)
|
||||||
|
cacheDir.delete()
|
||||||
|
binaryFile.delete()
|
||||||
|
throw e
|
||||||
|
}
|
||||||
|
}
|
||||||
|
File(cacheDir, "movie.spec").takeIf { it.isFile }?.let { jsonFile ->
|
||||||
|
try {
|
||||||
|
LogUtils.info(TAG, "spec change to entity")
|
||||||
|
FileInputStream(jsonFile).use { fileInputStream ->
|
||||||
|
ByteArrayOutputStream().use { byteArrayOutputStream ->
|
||||||
|
val buffer = ByteArray(2048)
|
||||||
|
while (true) {
|
||||||
|
val size = fileInputStream.read(buffer, 0, buffer.size)
|
||||||
|
if (size == -1) {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
byteArrayOutputStream.write(buffer, 0, size)
|
||||||
|
}
|
||||||
|
byteArrayOutputStream.toString().let {
|
||||||
|
JSONObject(it).let {
|
||||||
|
LogUtils.info(TAG, "spec change to entity success")
|
||||||
|
this.invokeCompleteCallback(
|
||||||
|
SVGAVideoEntity(
|
||||||
|
it,
|
||||||
|
cacheDir,
|
||||||
|
mFrameWidth,
|
||||||
|
mFrameHeight
|
||||||
|
),
|
||||||
|
callback,
|
||||||
|
alias
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (e: Exception) {
|
||||||
|
LogUtils.error(TAG, "$alias movie.spec change to entity fail", e)
|
||||||
|
cacheDir.delete()
|
||||||
|
jsonFile.delete()
|
||||||
|
throw e
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (e: Exception) {
|
||||||
|
this.invokeErrorCallback(e, callback, alias)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun readAsBytes(inputStream: InputStream): ByteArray? {
|
||||||
|
ByteArrayOutputStream().use { byteArrayOutputStream ->
|
||||||
|
val byteArray = ByteArray(2048)
|
||||||
|
while (true) {
|
||||||
|
val count = inputStream.read(byteArray, 0, 2048)
|
||||||
|
if (count <= 0) {
|
||||||
|
break
|
||||||
|
} else {
|
||||||
|
byteArrayOutputStream.write(byteArray, 0, count)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return byteArrayOutputStream.toByteArray()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun inflate(byteArray: ByteArray): ByteArray? {
|
||||||
|
val inflater = Inflater()
|
||||||
|
inflater.setInput(byteArray, 0, byteArray.size)
|
||||||
|
val inflatedBytes = ByteArray(2048)
|
||||||
|
ByteArrayOutputStream().use { inflatedOutputStream ->
|
||||||
|
while (true) {
|
||||||
|
val count = inflater.inflate(inflatedBytes, 0, 2048)
|
||||||
|
if (count <= 0) {
|
||||||
|
break
|
||||||
|
} else {
|
||||||
|
inflatedOutputStream.write(inflatedBytes, 0, count)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
inflater.end()
|
||||||
|
return inflatedOutputStream.toByteArray()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 是否是 zip 文件
|
||||||
|
private fun isZipFile(bytes: ByteArray): Boolean {
|
||||||
|
return bytes.size > 4 && bytes[0].toInt() == 80 && bytes[1].toInt() == 75 && bytes[2].toInt() == 3 && bytes[3].toInt() == 4
|
||||||
|
}
|
||||||
|
|
||||||
|
// 解压
|
||||||
|
private fun unzip(inputStream: InputStream, cacheKey: String) {
|
||||||
|
LogUtils.info(TAG, "================ unzip prepare ================")
|
||||||
|
val cacheDir = SVGACache.buildCacheDir(cacheKey)
|
||||||
|
cacheDir.mkdirs()
|
||||||
|
try {
|
||||||
|
BufferedInputStream(inputStream).use {
|
||||||
|
ZipInputStream(it).use { zipInputStream ->
|
||||||
|
while (true) {
|
||||||
|
val zipItem = zipInputStream.nextEntry ?: break
|
||||||
|
if (zipItem.name.contains("../")) {
|
||||||
|
// 解压路径存在路径穿越问题,直接过滤
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if (zipItem.name.contains("/")) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
val file = File(cacheDir, zipItem.name)
|
||||||
|
ensureUnzipSafety(file, cacheDir.absolutePath)
|
||||||
|
FileOutputStream(file).use { fileOutputStream ->
|
||||||
|
val buff = ByteArray(2048)
|
||||||
|
while (true) {
|
||||||
|
val readBytes = zipInputStream.read(buff)
|
||||||
|
if (readBytes <= 0) {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
fileOutputStream.write(buff, 0, readBytes)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
LogUtils.error(TAG, "================ unzip complete ================")
|
||||||
|
zipInputStream.closeEntry()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (e: Exception) {
|
||||||
|
LogUtils.error(TAG, "================ unzip error ================")
|
||||||
|
LogUtils.error(TAG, "error", e)
|
||||||
|
SVGACache.clearDir(cacheDir.absolutePath)
|
||||||
|
cacheDir.delete()
|
||||||
|
throw e
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 检查 zip 路径穿透
|
||||||
|
private fun ensureUnzipSafety(outputFile: File, dstDirPath: String) {
|
||||||
|
val dstDirCanonicalPath = File(dstDirPath).canonicalPath
|
||||||
|
val outputFileCanonicalPath = outputFile.canonicalPath
|
||||||
|
if (!outputFileCanonicalPath.startsWith(dstDirCanonicalPath)) {
|
||||||
|
throw IOException("Found Zip Path Traversal Vulnerability with $dstDirCanonicalPath")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
package com.opensource.svgaplayer
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import android.util.AttributeSet
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Created by cuiminghui on 2017/3/30.
|
||||||
|
* @deprecated from 2.4.0
|
||||||
|
*/
|
||||||
|
@Deprecated("This class has been deprecated from 2.4.0. We don't recommend you to use it.")
|
||||||
|
class SVGAPlayer: SVGAImageView {
|
||||||
|
|
||||||
|
constructor(context: Context) : super(context) {}
|
||||||
|
|
||||||
|
constructor(context: Context, attrs: AttributeSet) : super(context, attrs) {}
|
||||||
|
|
||||||
|
constructor(context: Context, attrs: AttributeSet, defStyleAttr: Int) : super(context, attrs, defStyleAttr) {}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,194 @@
|
|||||||
|
package com.opensource.svgaplayer
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Devin
|
||||||
|
*
|
||||||
|
* Created on 2/24/21.
|
||||||
|
*/
|
||||||
|
import android.media.AudioAttributes
|
||||||
|
import android.media.AudioManager
|
||||||
|
import android.media.SoundPool
|
||||||
|
import android.os.Build
|
||||||
|
import com.opensource.svgaplayer.utils.log.LogUtils
|
||||||
|
import java.io.FileDescriptor
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Author : llk
|
||||||
|
* Time : 2020/10/24
|
||||||
|
* Description : svga 音频加载管理类
|
||||||
|
* 将 SoundPool 抽取到单例里边,规避 load 资源之后不回调 onLoadComplete 的问题。
|
||||||
|
*
|
||||||
|
* 需要对 SVGASoundManager 进行初始化
|
||||||
|
*
|
||||||
|
* 相关文章:Android SoundPool 崩溃问题研究
|
||||||
|
* https://zhuanlan.zhihu.com/p/29985198
|
||||||
|
*/
|
||||||
|
object SVGASoundManager {
|
||||||
|
|
||||||
|
private val TAG = SVGASoundManager::class.java.simpleName
|
||||||
|
|
||||||
|
private var soundPool: SoundPool? = null
|
||||||
|
|
||||||
|
private val soundCallBackMap: MutableMap<Int, SVGASoundCallBack> = mutableMapOf()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 音量设置,范围在 [0, 1] 之间
|
||||||
|
*/
|
||||||
|
private var volume: Float = 1f
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 音频回调
|
||||||
|
*/
|
||||||
|
internal interface SVGASoundCallBack {
|
||||||
|
|
||||||
|
// 音量发生变化
|
||||||
|
fun onVolumeChange(value: Float)
|
||||||
|
|
||||||
|
// 音频加载完成
|
||||||
|
fun onComplete()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun init() {
|
||||||
|
init(20)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun init(maxStreams: Int) {
|
||||||
|
LogUtils.debug(TAG, "**************** init **************** $maxStreams")
|
||||||
|
if (soundPool != null) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
soundPool = getSoundPool(maxStreams)
|
||||||
|
soundPool?.setOnLoadCompleteListener { _, soundId, status ->
|
||||||
|
LogUtils.debug(TAG, "SoundPool onLoadComplete soundId=$soundId status=$status")
|
||||||
|
if (status == 0) { //加载该声音成功
|
||||||
|
if (soundCallBackMap.containsKey(soundId)) {
|
||||||
|
soundCallBackMap[soundId]?.onComplete()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun release() {
|
||||||
|
LogUtils.debug(TAG, "**************** release ****************")
|
||||||
|
if (soundCallBackMap.isNotEmpty()) {
|
||||||
|
soundCallBackMap.clear()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据当前播放实体,设置音量
|
||||||
|
*
|
||||||
|
* @param volume 范围在 [0, 1]
|
||||||
|
* @param entity 根据需要控制对应 entity 音量大小,若为空则控制所有正在播放的音频音量
|
||||||
|
*/
|
||||||
|
fun setVolume(volume: Float, entity: SVGAVideoEntity? = null) {
|
||||||
|
if (!checkInit()) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (volume < 0f || volume > 1f) {
|
||||||
|
LogUtils.error(TAG, "The volume level is in the range of 0 to 1 ")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (entity == null) {
|
||||||
|
this.volume = volume
|
||||||
|
val iterator = soundCallBackMap.entries.iterator()
|
||||||
|
while (iterator.hasNext()) {
|
||||||
|
val e = iterator.next()
|
||||||
|
e.value.onVolumeChange(volume)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
val soundPool = soundPool ?: return
|
||||||
|
|
||||||
|
entity.audioList.forEach { audio ->
|
||||||
|
val streamId = audio.playID ?: return
|
||||||
|
soundPool.setVolume(streamId, volume, volume)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 是否初始化
|
||||||
|
* 如果没有初始化,就使用原来SvgaPlayer库的音频加载逻辑。
|
||||||
|
* @return true 则已初始化, 否则为 false
|
||||||
|
*/
|
||||||
|
internal fun isInit(): Boolean {
|
||||||
|
return soundPool != null
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun checkInit(): Boolean {
|
||||||
|
val isInit = isInit()
|
||||||
|
if (!isInit) {
|
||||||
|
LogUtils.error(TAG, "soundPool is null, you need call init() !!!")
|
||||||
|
}
|
||||||
|
return isInit
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun getSoundPool(maxStreams: Int) = if (Build.VERSION.SDK_INT >= 21) {
|
||||||
|
val attributes = AudioAttributes.Builder()
|
||||||
|
.setUsage(AudioAttributes.USAGE_MEDIA)
|
||||||
|
.build()
|
||||||
|
SoundPool.Builder().setAudioAttributes(attributes)
|
||||||
|
.setMaxStreams(maxStreams)
|
||||||
|
.build()
|
||||||
|
} else {
|
||||||
|
SoundPool(maxStreams, AudioManager.STREAM_MUSIC, 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
internal fun load(callBack: SVGASoundCallBack?,
|
||||||
|
fd: FileDescriptor?,
|
||||||
|
offset: Long,
|
||||||
|
length: Long,
|
||||||
|
priority: Int): Int {
|
||||||
|
if (!checkInit()) return -1
|
||||||
|
|
||||||
|
val soundId = soundPool!!.load(fd, offset, length, priority)
|
||||||
|
|
||||||
|
LogUtils.debug(TAG, "load soundId=$soundId callBack=$callBack")
|
||||||
|
|
||||||
|
if (callBack != null && !soundCallBackMap.containsKey(soundId)) {
|
||||||
|
soundCallBackMap[soundId] = callBack
|
||||||
|
}
|
||||||
|
return soundId
|
||||||
|
}
|
||||||
|
|
||||||
|
internal fun unload(soundId: Int) {
|
||||||
|
if (!checkInit()) return
|
||||||
|
|
||||||
|
LogUtils.debug(TAG, "unload soundId=$soundId")
|
||||||
|
|
||||||
|
soundPool!!.unload(soundId)
|
||||||
|
|
||||||
|
soundCallBackMap.remove(soundId)
|
||||||
|
}
|
||||||
|
|
||||||
|
internal fun play(soundId: Int): Int {
|
||||||
|
if (!checkInit()) return -1
|
||||||
|
|
||||||
|
LogUtils.debug(TAG, "play soundId=$soundId")
|
||||||
|
return soundPool!!.play(soundId, volume, volume, 1, 0, 1.0f)
|
||||||
|
}
|
||||||
|
|
||||||
|
internal fun stop(soundId: Int) {
|
||||||
|
if (!checkInit()) return
|
||||||
|
|
||||||
|
LogUtils.debug(TAG, "stop soundId=$soundId")
|
||||||
|
soundPool!!.stop(soundId)
|
||||||
|
}
|
||||||
|
|
||||||
|
internal fun resume(soundId: Int) {
|
||||||
|
if (!checkInit()) return
|
||||||
|
|
||||||
|
LogUtils.debug(TAG, "stop soundId=$soundId")
|
||||||
|
soundPool!!.resume(soundId)
|
||||||
|
}
|
||||||
|
|
||||||
|
internal fun pause(soundId: Int) {
|
||||||
|
if (!checkInit()) return
|
||||||
|
|
||||||
|
LogUtils.debug(TAG, "pause soundId=$soundId")
|
||||||
|
soundPool!!.pause(soundId)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,347 @@
|
|||||||
|
package com.opensource.svgaplayer
|
||||||
|
|
||||||
|
import android.graphics.Bitmap
|
||||||
|
import android.media.AudioAttributes
|
||||||
|
import android.media.AudioManager
|
||||||
|
import android.media.SoundPool
|
||||||
|
import android.os.Build
|
||||||
|
import com.opensource.svgaplayer.bitmap.SVGABitmapByteArrayDecoder
|
||||||
|
import com.opensource.svgaplayer.bitmap.SVGABitmapFileDecoder
|
||||||
|
import com.opensource.svgaplayer.entities.SVGAAudioEntity
|
||||||
|
import com.opensource.svgaplayer.entities.SVGAVideoSpriteEntity
|
||||||
|
import com.opensource.svgaplayer.proto.AudioEntity
|
||||||
|
import com.opensource.svgaplayer.proto.MovieEntity
|
||||||
|
import com.opensource.svgaplayer.proto.MovieParams
|
||||||
|
import com.opensource.svgaplayer.utils.SVGARect
|
||||||
|
import com.opensource.svgaplayer.utils.log.LogUtils
|
||||||
|
import org.json.JSONObject
|
||||||
|
import java.io.File
|
||||||
|
import java.io.FileInputStream
|
||||||
|
import java.io.FileOutputStream
|
||||||
|
import java.util.*
|
||||||
|
import kotlin.collections.ArrayList
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Created by PonyCui on 16/6/18.
|
||||||
|
*/
|
||||||
|
class SVGAVideoEntity {
|
||||||
|
|
||||||
|
private val TAG = "SVGAVideoEntity"
|
||||||
|
|
||||||
|
var antiAlias = true
|
||||||
|
var movieItem: MovieEntity? = null
|
||||||
|
|
||||||
|
var videoSize = SVGARect(0.0, 0.0, 0.0, 0.0)
|
||||||
|
private set
|
||||||
|
|
||||||
|
var FPS = 15
|
||||||
|
private set
|
||||||
|
|
||||||
|
var frames: Int = 0
|
||||||
|
private set
|
||||||
|
|
||||||
|
internal var spriteList: List<SVGAVideoSpriteEntity> = emptyList()
|
||||||
|
internal var audioList: List<SVGAAudioEntity> = emptyList()
|
||||||
|
internal var soundPool: SoundPool? = null
|
||||||
|
private var soundCallback: SVGASoundManager.SVGASoundCallBack? = null
|
||||||
|
internal var imageMap = HashMap<String, Bitmap>()
|
||||||
|
private var mCacheDir: File
|
||||||
|
private var mFrameHeight = 0
|
||||||
|
private var mFrameWidth = 0
|
||||||
|
private var mPlayCallback: SVGAParser.PlayCallback?=null
|
||||||
|
private lateinit var mCallback: () -> Unit
|
||||||
|
|
||||||
|
constructor(json: JSONObject, cacheDir: File) : this(json, cacheDir, 0, 0)
|
||||||
|
|
||||||
|
constructor(json: JSONObject, cacheDir: File, frameWidth: Int, frameHeight: Int) {
|
||||||
|
mFrameWidth = frameWidth
|
||||||
|
mFrameHeight = frameHeight
|
||||||
|
mCacheDir = cacheDir
|
||||||
|
val movieJsonObject = json.optJSONObject("movie") ?: return
|
||||||
|
setupByJson(movieJsonObject)
|
||||||
|
try {
|
||||||
|
parserImages(json)
|
||||||
|
} catch (e: Exception) {
|
||||||
|
e.printStackTrace()
|
||||||
|
} catch (e: OutOfMemoryError) {
|
||||||
|
e.printStackTrace()
|
||||||
|
}
|
||||||
|
resetSprites(json)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun setupByJson(movieObject: JSONObject) {
|
||||||
|
movieObject.optJSONObject("viewBox")?.let { viewBoxObject ->
|
||||||
|
val width = viewBoxObject.optDouble("width", 0.0)
|
||||||
|
val height = viewBoxObject.optDouble("height", 0.0)
|
||||||
|
videoSize = SVGARect(0.0, 0.0, width, height)
|
||||||
|
}
|
||||||
|
FPS = movieObject.optInt("fps", 20)
|
||||||
|
frames = movieObject.optInt("frames", 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
constructor(entity: MovieEntity, cacheDir: File) : this(entity, cacheDir, 0, 0)
|
||||||
|
|
||||||
|
constructor(entity: MovieEntity, cacheDir: File, frameWidth: Int, frameHeight: Int) {
|
||||||
|
this.mFrameWidth = frameWidth
|
||||||
|
this.mFrameHeight = frameHeight
|
||||||
|
this.mCacheDir = cacheDir
|
||||||
|
this.movieItem = entity
|
||||||
|
entity.params?.let(this::setupByMovie)
|
||||||
|
try {
|
||||||
|
parserImages(entity)
|
||||||
|
} catch (e: Exception) {
|
||||||
|
e.printStackTrace()
|
||||||
|
} catch (e: OutOfMemoryError) {
|
||||||
|
e.printStackTrace()
|
||||||
|
}
|
||||||
|
resetSprites(entity)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun setupByMovie(movieParams: MovieParams) {
|
||||||
|
val width = (movieParams.viewBoxWidth ?: 0.0f).toDouble()
|
||||||
|
val height = (movieParams.viewBoxHeight ?: 0.0f).toDouble()
|
||||||
|
videoSize = SVGARect(0.0, 0.0, width, height)
|
||||||
|
FPS = movieParams.fps ?: 20
|
||||||
|
frames = movieParams.frames ?: 0
|
||||||
|
}
|
||||||
|
|
||||||
|
internal fun prepare(callback: () -> Unit, playCallback: SVGAParser.PlayCallback?) {
|
||||||
|
mCallback = callback
|
||||||
|
mPlayCallback = playCallback
|
||||||
|
if (movieItem == null) {
|
||||||
|
mCallback()
|
||||||
|
} else {
|
||||||
|
setupAudios(movieItem!!) {
|
||||||
|
mCallback()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun parserImages(json: JSONObject) {
|
||||||
|
val imgJson = json.optJSONObject("images") ?: return
|
||||||
|
imgJson.keys().forEach { imgKey ->
|
||||||
|
val filePath = generateBitmapFilePath(imgJson[imgKey].toString(), imgKey)
|
||||||
|
if (filePath.isEmpty()) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
val bitmapKey = imgKey.replace(".matte", "")
|
||||||
|
val bitmap = createBitmap(filePath)
|
||||||
|
if (bitmap != null) {
|
||||||
|
imageMap[bitmapKey] = bitmap
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun generateBitmapFilePath(imgName: String, imgKey: String): String {
|
||||||
|
val path = mCacheDir.absolutePath + "/" + imgName
|
||||||
|
val path1 = "$path.png"
|
||||||
|
val path2 = mCacheDir.absolutePath + "/" + imgKey + ".png"
|
||||||
|
|
||||||
|
return when {
|
||||||
|
File(path).exists() -> path
|
||||||
|
File(path1).exists() -> path1
|
||||||
|
File(path2).exists() -> path2
|
||||||
|
else -> ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun createBitmap(filePath: String): Bitmap? {
|
||||||
|
return SVGABitmapFileDecoder.decodeBitmapFrom(filePath, mFrameWidth, mFrameHeight)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun parserImages(obj: MovieEntity) {
|
||||||
|
obj.images?.entries?.forEach { entry ->
|
||||||
|
val byteArray = entry.value.toByteArray()
|
||||||
|
if (byteArray.count() < 4) {
|
||||||
|
return@forEach
|
||||||
|
}
|
||||||
|
val fileTag = byteArray.slice(IntRange(0, 3))
|
||||||
|
if (fileTag[0].toInt() == 73 && fileTag[1].toInt() == 68 && fileTag[2].toInt() == 51) {
|
||||||
|
return@forEach
|
||||||
|
}
|
||||||
|
val filePath = generateBitmapFilePath(entry.value.utf8(), entry.key)
|
||||||
|
createBitmap(byteArray, filePath)?.let { bitmap ->
|
||||||
|
imageMap[entry.key] = bitmap
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun createBitmap(byteArray: ByteArray, filePath: String): Bitmap? {
|
||||||
|
val bitmap = SVGABitmapByteArrayDecoder.decodeBitmapFrom(byteArray, mFrameWidth, mFrameHeight)
|
||||||
|
return bitmap ?: createBitmap(filePath)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun resetSprites(json: JSONObject) {
|
||||||
|
val mutableList: MutableList<SVGAVideoSpriteEntity> = mutableListOf()
|
||||||
|
json.optJSONArray("sprites")?.let { item ->
|
||||||
|
for (i in 0 until item.length()) {
|
||||||
|
item.optJSONObject(i)?.let { entryJson ->
|
||||||
|
mutableList.add(SVGAVideoSpriteEntity(entryJson))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
spriteList = mutableList.toList()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun resetSprites(entity: MovieEntity) {
|
||||||
|
spriteList = entity.sprites?.map {
|
||||||
|
return@map SVGAVideoSpriteEntity(it)
|
||||||
|
} ?: listOf()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun setupAudios(entity: MovieEntity, completionBlock: () -> Unit) {
|
||||||
|
if (entity.audios == null || entity.audios.isEmpty()) {
|
||||||
|
run(completionBlock)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
setupSoundPool(entity, completionBlock)
|
||||||
|
val audiosFileMap = generateAudioFileMap(entity)
|
||||||
|
//repair when audioEntity error can not callback
|
||||||
|
//如果audiosFileMap为空 soundPool?.load 不会走 导致 setOnLoadCompleteListener 不会回调 导致外层prepare不回调卡住
|
||||||
|
if (audiosFileMap.size == 0) {
|
||||||
|
run(completionBlock)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
this.audioList = entity.audios.map { audio ->
|
||||||
|
return@map createSvgaAudioEntity(audio, audiosFileMap)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun createSvgaAudioEntity(audio: AudioEntity, audiosFileMap: HashMap<String, File>): SVGAAudioEntity {
|
||||||
|
val item = SVGAAudioEntity(audio)
|
||||||
|
val startTime = (audio.startTime ?: 0).toDouble()
|
||||||
|
val totalTime = (audio.totalTime ?: 0).toDouble()
|
||||||
|
if (totalTime.toInt() == 0) {
|
||||||
|
// 除数不能为 0
|
||||||
|
return item
|
||||||
|
}
|
||||||
|
// 直接回调文件,后续播放都不走
|
||||||
|
mPlayCallback?.let {
|
||||||
|
val fileList: MutableList<File> = ArrayList()
|
||||||
|
audiosFileMap.forEach { entity ->
|
||||||
|
fileList.add(entity.value)
|
||||||
|
}
|
||||||
|
it.onPlay(fileList)
|
||||||
|
mCallback()
|
||||||
|
return item
|
||||||
|
}
|
||||||
|
|
||||||
|
audiosFileMap[audio.audioKey]?.let { file ->
|
||||||
|
FileInputStream(file).use {
|
||||||
|
val length = it.available().toDouble()
|
||||||
|
val offset = ((startTime / totalTime) * length).toLong()
|
||||||
|
if (SVGASoundManager.isInit()) {
|
||||||
|
item.soundID = SVGASoundManager.load(soundCallback,
|
||||||
|
it.fd,
|
||||||
|
offset,
|
||||||
|
length.toLong(),
|
||||||
|
1)
|
||||||
|
} else {
|
||||||
|
item.soundID = soundPool?.load(it.fd, offset, length.toLong(), 1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return item
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun generateAudioFile(audioCache: File, value: ByteArray): File {
|
||||||
|
audioCache.createNewFile()
|
||||||
|
FileOutputStream(audioCache).write(value)
|
||||||
|
return audioCache
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun generateAudioFileMap(entity: MovieEntity): HashMap<String, File> {
|
||||||
|
val audiosDataMap = generateAudioMap(entity)
|
||||||
|
val audiosFileMap = HashMap<String, File>()
|
||||||
|
if (audiosDataMap.count() > 0) {
|
||||||
|
audiosDataMap.forEach {
|
||||||
|
val audioCache = SVGACache.buildAudioFile(it.key)
|
||||||
|
audiosFileMap[it.key] =
|
||||||
|
audioCache.takeIf { file -> file.exists() } ?: generateAudioFile(
|
||||||
|
audioCache,
|
||||||
|
it.value
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return audiosFileMap
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun generateAudioMap(entity: MovieEntity): HashMap<String, ByteArray> {
|
||||||
|
val audiosDataMap = HashMap<String, ByteArray>()
|
||||||
|
entity.images?.entries?.forEach {
|
||||||
|
val imageKey = it.key
|
||||||
|
val byteArray = it.value.toByteArray()
|
||||||
|
if (byteArray.count() < 4) {
|
||||||
|
return@forEach
|
||||||
|
}
|
||||||
|
val fileTag = byteArray.slice(IntRange(0, 3))
|
||||||
|
if (fileTag[0].toInt() == 73 && fileTag[1].toInt() == 68 && fileTag[2].toInt() == 51) {
|
||||||
|
audiosDataMap[imageKey] = byteArray
|
||||||
|
}else if(fileTag[0].toInt() == -1 && fileTag[1].toInt() == -5 && fileTag[2].toInt() == -108){
|
||||||
|
audiosDataMap[imageKey] = byteArray
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return audiosDataMap
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun setupSoundPool(entity: MovieEntity, completionBlock: () -> Unit) {
|
||||||
|
var soundLoaded = 0
|
||||||
|
if (SVGASoundManager.isInit()) {
|
||||||
|
soundCallback = object : SVGASoundManager.SVGASoundCallBack {
|
||||||
|
override fun onVolumeChange(value: Float) {
|
||||||
|
SVGASoundManager.setVolume(value, this@SVGAVideoEntity)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onComplete() {
|
||||||
|
soundLoaded++
|
||||||
|
if (soundLoaded >= entity.audios.count()) {
|
||||||
|
completionBlock()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
soundPool = generateSoundPool(entity)
|
||||||
|
LogUtils.info("SVGAParser", "pool_start")
|
||||||
|
soundPool?.setOnLoadCompleteListener { _, _, _ ->
|
||||||
|
LogUtils.info("SVGAParser", "pool_complete")
|
||||||
|
soundLoaded++
|
||||||
|
if (soundLoaded >= entity.audios.count()) {
|
||||||
|
completionBlock()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun generateSoundPool(entity: MovieEntity): SoundPool? {
|
||||||
|
return try {
|
||||||
|
if (Build.VERSION.SDK_INT >= 21) {
|
||||||
|
val attributes = AudioAttributes.Builder()
|
||||||
|
.setUsage(AudioAttributes.USAGE_MEDIA)
|
||||||
|
.build()
|
||||||
|
SoundPool.Builder().setAudioAttributes(attributes)
|
||||||
|
.setMaxStreams(12.coerceAtMost(entity.audios.count()))
|
||||||
|
.build()
|
||||||
|
} else {
|
||||||
|
SoundPool(12.coerceAtMost(entity.audios.count()), AudioManager.STREAM_MUSIC, 0)
|
||||||
|
}
|
||||||
|
} catch (e: Exception) {
|
||||||
|
LogUtils.error(TAG, e)
|
||||||
|
null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun clear() {
|
||||||
|
if (SVGASoundManager.isInit()) {
|
||||||
|
this.audioList.forEach {
|
||||||
|
it.soundID?.let { id -> SVGASoundManager.unload(id) }
|
||||||
|
}
|
||||||
|
soundCallback = null
|
||||||
|
}
|
||||||
|
soundPool?.release()
|
||||||
|
soundPool = null
|
||||||
|
audioList = emptyList()
|
||||||
|
spriteList = emptyList()
|
||||||
|
imageMap.clear()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@@ -0,0 +1,33 @@
|
|||||||
|
package com.opensource.svgaplayer.bitmap
|
||||||
|
|
||||||
|
import android.graphics.BitmapFactory
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* Create by im_dsd 2020/7/7 17:59
|
||||||
|
*/
|
||||||
|
internal object BitmapSampleSizeCalculator {
|
||||||
|
|
||||||
|
fun calculate(options: BitmapFactory.Options, reqWidth: Int, reqHeight: Int): Int {
|
||||||
|
// Raw height and width of image
|
||||||
|
val (height: Int, width: Int) = options.run { outHeight to outWidth }
|
||||||
|
var inSampleSize = 1
|
||||||
|
|
||||||
|
if (reqHeight <= 0 || reqWidth <= 0) {
|
||||||
|
return inSampleSize
|
||||||
|
}
|
||||||
|
if (height > reqHeight || width > reqWidth) {
|
||||||
|
|
||||||
|
val halfHeight: Int = height / 2
|
||||||
|
val halfWidth: Int = width / 2
|
||||||
|
|
||||||
|
// Calculate the largest inSampleSize value that is a power of 2 and keeps both
|
||||||
|
// height and width larger than the requested height and width.
|
||||||
|
while (halfHeight / inSampleSize >= reqHeight && halfWidth / inSampleSize >= reqWidth) {
|
||||||
|
inSampleSize *= 2
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return inSampleSize
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
package com.opensource.svgaplayer.bitmap
|
||||||
|
|
||||||
|
import android.graphics.Bitmap
|
||||||
|
import android.graphics.BitmapFactory
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 通过字节码解码 Bitmap
|
||||||
|
*
|
||||||
|
* Create by im_dsd 2020/7/7 17:50
|
||||||
|
*/
|
||||||
|
internal object SVGABitmapByteArrayDecoder : SVGABitmapDecoder<ByteArray>() {
|
||||||
|
|
||||||
|
override fun onDecode(data: ByteArray, ops: BitmapFactory.Options): Bitmap? {
|
||||||
|
return BitmapFactory.decodeByteArray(data, 0, data.count(), ops)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,35 @@
|
|||||||
|
package com.opensource.svgaplayer.bitmap
|
||||||
|
|
||||||
|
import android.graphics.Bitmap
|
||||||
|
import android.graphics.BitmapFactory
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Bitmap 解码器
|
||||||
|
*
|
||||||
|
* <T> 需要加载的数据类型
|
||||||
|
*
|
||||||
|
* Create by im_dsd 2020/7/7 17:39
|
||||||
|
*/
|
||||||
|
internal abstract class SVGABitmapDecoder<T> {
|
||||||
|
|
||||||
|
fun decodeBitmapFrom(data: T, reqWidth: Int, reqHeight: Int): Bitmap? {
|
||||||
|
return BitmapFactory.Options().run {
|
||||||
|
// 如果期望的宽高是合法的, 则开启检测尺寸模式
|
||||||
|
inJustDecodeBounds = (reqWidth > 0 && reqHeight > 0)
|
||||||
|
inPreferredConfig = Bitmap.Config.RGB_565
|
||||||
|
|
||||||
|
val bitmap = onDecode(data, this)
|
||||||
|
if (!inJustDecodeBounds) {
|
||||||
|
return bitmap
|
||||||
|
}
|
||||||
|
|
||||||
|
// Calculate inSampleSize
|
||||||
|
inSampleSize = BitmapSampleSizeCalculator.calculate(this, reqWidth, reqHeight)
|
||||||
|
// Decode bitmap with inSampleSize set
|
||||||
|
inJustDecodeBounds = false
|
||||||
|
onDecode(data, this)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
abstract fun onDecode(data: T, ops: BitmapFactory.Options): Bitmap?
|
||||||
|
}
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
package com.opensource.svgaplayer.bitmap
|
||||||
|
|
||||||
|
import android.graphics.Bitmap
|
||||||
|
import android.graphics.BitmapFactory
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 通过文件解码 Bitmap
|
||||||
|
*
|
||||||
|
* Create by im_dsd 2020/7/7 17:50
|
||||||
|
*/
|
||||||
|
internal object SVGABitmapFileDecoder : SVGABitmapDecoder<String>() {
|
||||||
|
|
||||||
|
override fun onDecode(data: String, ops: BitmapFactory.Options): Bitmap? {
|
||||||
|
return BitmapFactory.decodeFile(data, ops)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,53 @@
|
|||||||
|
package com.opensource.svgaplayer.drawer
|
||||||
|
|
||||||
|
import android.graphics.Canvas
|
||||||
|
import android.widget.ImageView
|
||||||
|
import com.opensource.svgaplayer.SVGAVideoEntity
|
||||||
|
import com.opensource.svgaplayer.entities.SVGAVideoSpriteFrameEntity
|
||||||
|
import com.opensource.svgaplayer.utils.Pools
|
||||||
|
import com.opensource.svgaplayer.utils.SVGAScaleInfo
|
||||||
|
import kotlin.math.max
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Created by cuiminghui on 2017/3/29.
|
||||||
|
*/
|
||||||
|
|
||||||
|
open internal class SGVADrawer(val videoItem: SVGAVideoEntity) {
|
||||||
|
|
||||||
|
val scaleInfo = SVGAScaleInfo()
|
||||||
|
|
||||||
|
private val spritePool = Pools.SimplePool<SVGADrawerSprite>(max(1, videoItem.spriteList.size))
|
||||||
|
|
||||||
|
inner class SVGADrawerSprite(var _matteKey: String? = null, var _imageKey: String? = null, var _frameEntity: SVGAVideoSpriteFrameEntity? = null) {
|
||||||
|
val matteKey get() = _matteKey
|
||||||
|
val imageKey get() = _imageKey
|
||||||
|
val frameEntity get() = _frameEntity!!
|
||||||
|
}
|
||||||
|
|
||||||
|
internal fun requestFrameSprites(frameIndex: Int): List<SVGADrawerSprite> {
|
||||||
|
return videoItem.spriteList.mapNotNull {
|
||||||
|
if (frameIndex >= 0 && frameIndex < it.frames.size) {
|
||||||
|
it.imageKey?.let { imageKey ->
|
||||||
|
if (!imageKey.endsWith(".matte") && it.frames[frameIndex].alpha <= 0.0) {
|
||||||
|
return@mapNotNull null
|
||||||
|
}
|
||||||
|
return@mapNotNull (spritePool.acquire() ?: SVGADrawerSprite()).apply {
|
||||||
|
_matteKey = it.matteKey
|
||||||
|
_imageKey = it.imageKey
|
||||||
|
_frameEntity = it.frames[frameIndex]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return@mapNotNull null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal fun releaseFrameSprites(sprites: List<SVGADrawerSprite>) {
|
||||||
|
sprites.forEach { spritePool.release(it) }
|
||||||
|
}
|
||||||
|
|
||||||
|
open fun drawFrame(canvas : Canvas, frameIndex: Int, scaleType: ImageView.ScaleType) {
|
||||||
|
scaleInfo.performScaleType(canvas.width.toFloat(),canvas.height.toFloat(), videoItem.videoSize.width.toFloat(), videoItem.videoSize.height.toFloat(), scaleType)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,559 @@
|
|||||||
|
package com.opensource.svgaplayer.drawer
|
||||||
|
|
||||||
|
import android.graphics.*
|
||||||
|
import android.os.Build
|
||||||
|
import android.text.StaticLayout
|
||||||
|
import android.text.TextUtils
|
||||||
|
import android.widget.ImageView
|
||||||
|
import com.opensource.svgaplayer.SVGADynamicEntity
|
||||||
|
import com.opensource.svgaplayer.SVGASoundManager
|
||||||
|
import com.opensource.svgaplayer.SVGAVideoEntity
|
||||||
|
import com.opensource.svgaplayer.entities.SVGAVideoShapeEntity
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Created by cuiminghui on 2017/3/29.
|
||||||
|
*/
|
||||||
|
|
||||||
|
internal class SVGACanvasDrawer(videoItem: SVGAVideoEntity, val dynamicItem: SVGADynamicEntity) : SGVADrawer(videoItem) {
|
||||||
|
|
||||||
|
private val sharedValues = ShareValues()
|
||||||
|
private val drawTextCache: HashMap<String, Bitmap> = hashMapOf()
|
||||||
|
private val pathCache = PathCache()
|
||||||
|
|
||||||
|
private var beginIndexList: Array<Boolean>? = null
|
||||||
|
private var endIndexList: Array<Boolean>? = null
|
||||||
|
|
||||||
|
override fun drawFrame(canvas: Canvas, frameIndex: Int, scaleType: ImageView.ScaleType) {
|
||||||
|
super.drawFrame(canvas, frameIndex, scaleType)
|
||||||
|
playAudio(frameIndex)
|
||||||
|
this.pathCache.onSizeChanged(canvas)
|
||||||
|
val sprites = requestFrameSprites(frameIndex)
|
||||||
|
// Filter null sprites
|
||||||
|
if (sprites.count() <= 0) return
|
||||||
|
val matteSprites = mutableMapOf<String, SVGADrawerSprite>()
|
||||||
|
var saveID = -1
|
||||||
|
beginIndexList = null
|
||||||
|
endIndexList = null
|
||||||
|
|
||||||
|
// Filter no matte layer
|
||||||
|
var hasMatteLayer = false
|
||||||
|
sprites.get(0).imageKey?.let {
|
||||||
|
if (it.endsWith(".matte")) {
|
||||||
|
hasMatteLayer = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
sprites.forEachIndexed { index, svgaDrawerSprite ->
|
||||||
|
|
||||||
|
// Save matte sprite
|
||||||
|
svgaDrawerSprite.imageKey?.let {
|
||||||
|
/// No matte layer included or VERSION Unsopport matte
|
||||||
|
if (!hasMatteLayer || Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) {
|
||||||
|
// Normal sprite
|
||||||
|
drawSprite(svgaDrawerSprite, canvas, frameIndex)
|
||||||
|
// Continue
|
||||||
|
return@forEachIndexed
|
||||||
|
}
|
||||||
|
/// Cache matte sprite
|
||||||
|
if (it.endsWith(".matte")) {
|
||||||
|
matteSprites.put(it, svgaDrawerSprite)
|
||||||
|
// Continue
|
||||||
|
return@forEachIndexed
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/// Is matte begin
|
||||||
|
if (isMatteBegin(index, sprites)) {
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
|
||||||
|
saveID = canvas.saveLayer(0f, 0f, canvas.width.toFloat(), canvas.height.toFloat(), null)
|
||||||
|
} else {
|
||||||
|
canvas.save()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/// Normal matte
|
||||||
|
drawSprite(svgaDrawerSprite, canvas, frameIndex)
|
||||||
|
|
||||||
|
/// Is matte end
|
||||||
|
if (isMatteEnd(index, sprites)) {
|
||||||
|
matteSprites.get(svgaDrawerSprite.matteKey)?.let {
|
||||||
|
drawSprite(it, this.sharedValues.shareMatteCanvas(canvas.width, canvas.height), frameIndex)
|
||||||
|
canvas.drawBitmap(this.sharedValues.sharedMatteBitmap(), 0f, 0f, this.sharedValues.shareMattePaint())
|
||||||
|
if (saveID != -1) {
|
||||||
|
canvas.restoreToCount(saveID)
|
||||||
|
} else {
|
||||||
|
canvas.restore()
|
||||||
|
}
|
||||||
|
// Continue
|
||||||
|
return@forEachIndexed
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
releaseFrameSprites(sprites)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun isMatteBegin(spriteIndex: Int, sprites: List<SVGADrawerSprite>): Boolean {
|
||||||
|
if (beginIndexList == null) {
|
||||||
|
val boolArray = Array(sprites.count()) { false }
|
||||||
|
sprites.forEachIndexed { index, svgaDrawerSprite ->
|
||||||
|
svgaDrawerSprite.imageKey?.let {
|
||||||
|
/// Filter matte sprite
|
||||||
|
if (it.endsWith(".matte")) {
|
||||||
|
// Continue
|
||||||
|
return@forEachIndexed
|
||||||
|
}
|
||||||
|
}
|
||||||
|
svgaDrawerSprite.matteKey?.let {
|
||||||
|
if (it.length > 0) {
|
||||||
|
sprites.get(index - 1)?.let { lastSprite ->
|
||||||
|
if (lastSprite.matteKey.isNullOrEmpty()) {
|
||||||
|
boolArray[index] = true
|
||||||
|
} else {
|
||||||
|
if (lastSprite.matteKey != svgaDrawerSprite.matteKey) {
|
||||||
|
boolArray[index] = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
beginIndexList = boolArray
|
||||||
|
}
|
||||||
|
return beginIndexList?.get(spriteIndex) ?: false
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun isMatteEnd(spriteIndex: Int, sprites: List<SVGADrawerSprite>): Boolean {
|
||||||
|
if (endIndexList == null) {
|
||||||
|
val boolArray = Array(sprites.count()) { false }
|
||||||
|
sprites.forEachIndexed { index, svgaDrawerSprite ->
|
||||||
|
svgaDrawerSprite.imageKey?.let {
|
||||||
|
/// Filter matte sprite
|
||||||
|
if (it.endsWith(".matte")) {
|
||||||
|
// Continue
|
||||||
|
return@forEachIndexed
|
||||||
|
}
|
||||||
|
}
|
||||||
|
svgaDrawerSprite.matteKey?.let {
|
||||||
|
if (it.length > 0) {
|
||||||
|
// Last one
|
||||||
|
if (index == sprites.count() - 1) {
|
||||||
|
boolArray[index] = true
|
||||||
|
} else {
|
||||||
|
sprites.get(index + 1)?.let { nextSprite ->
|
||||||
|
if (nextSprite.matteKey.isNullOrEmpty()) {
|
||||||
|
boolArray[index] = true
|
||||||
|
} else {
|
||||||
|
if (nextSprite.matteKey != svgaDrawerSprite.matteKey) {
|
||||||
|
boolArray[index] = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
endIndexList = boolArray
|
||||||
|
}
|
||||||
|
return endIndexList?.get(spriteIndex) ?: false
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun playAudio(frameIndex: Int) {
|
||||||
|
this.videoItem.audioList.forEach { audio ->
|
||||||
|
if (audio.startFrame == frameIndex) {
|
||||||
|
if (SVGASoundManager.isInit()) {
|
||||||
|
audio.soundID?.let { soundID ->
|
||||||
|
audio.playID = SVGASoundManager.play(soundID)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
this.videoItem.soundPool?.let { soundPool ->
|
||||||
|
audio.soundID?.let { soundID ->
|
||||||
|
audio.playID = soundPool.play(soundID, 1.0f, 1.0f, 1, 0, 1.0f)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
if (audio.endFrame <= frameIndex) {
|
||||||
|
audio.playID?.let {
|
||||||
|
if (SVGASoundManager.isInit()) {
|
||||||
|
SVGASoundManager.stop(it)
|
||||||
|
} else {
|
||||||
|
this.videoItem.soundPool?.stop(it)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
audio.playID = null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun shareFrameMatrix(transform: Matrix): Matrix {
|
||||||
|
val matrix = this.sharedValues.sharedMatrix()
|
||||||
|
matrix.postScale(scaleInfo.scaleFx, scaleInfo.scaleFy)
|
||||||
|
matrix.postTranslate(scaleInfo.tranFx, scaleInfo.tranFy)
|
||||||
|
matrix.preConcat(transform)
|
||||||
|
return matrix
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun drawSprite(sprite: SVGADrawerSprite, canvas: Canvas, frameIndex: Int) {
|
||||||
|
drawImage(sprite, canvas)
|
||||||
|
drawShape(sprite, canvas)
|
||||||
|
drawDynamic(sprite, canvas, frameIndex)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun drawImage(sprite: SVGADrawerSprite, canvas: Canvas) {
|
||||||
|
val imageKey = sprite.imageKey ?: return
|
||||||
|
val isHidden = dynamicItem.dynamicHidden[imageKey] == true
|
||||||
|
if (isHidden) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
val bitmapKey = if (imageKey.endsWith(".matte")) imageKey.substring(0, imageKey.length - 6) else imageKey
|
||||||
|
val drawingBitmap = (dynamicItem.dynamicImage[bitmapKey] ?: videoItem.imageMap[bitmapKey])
|
||||||
|
?: return
|
||||||
|
val frameMatrix = shareFrameMatrix(sprite.frameEntity.transform)
|
||||||
|
val paint = this.sharedValues.sharedPaint()
|
||||||
|
paint.isAntiAlias = videoItem.antiAlias
|
||||||
|
paint.isFilterBitmap = videoItem.antiAlias
|
||||||
|
paint.alpha = (sprite.frameEntity.alpha * 255).toInt()
|
||||||
|
if (sprite.frameEntity.maskPath != null) {
|
||||||
|
val maskPath = sprite.frameEntity.maskPath ?: return
|
||||||
|
canvas.save()
|
||||||
|
val path = this.sharedValues.sharedPath()
|
||||||
|
maskPath.buildPath(path)
|
||||||
|
path.transform(frameMatrix)
|
||||||
|
canvas.clipPath(path)
|
||||||
|
frameMatrix.preScale((sprite.frameEntity.layout.width / drawingBitmap.width).toFloat(), (sprite.frameEntity.layout.height / drawingBitmap.height).toFloat())
|
||||||
|
if (!drawingBitmap.isRecycled) {
|
||||||
|
canvas.drawBitmap(drawingBitmap, frameMatrix, paint)
|
||||||
|
}
|
||||||
|
canvas.restore()
|
||||||
|
} else {
|
||||||
|
frameMatrix.preScale((sprite.frameEntity.layout.width / drawingBitmap.width).toFloat(), (sprite.frameEntity.layout.height / drawingBitmap.height).toFloat())
|
||||||
|
if (!drawingBitmap.isRecycled) {
|
||||||
|
canvas.drawBitmap(drawingBitmap, frameMatrix, paint)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
dynamicItem.dynamicIClickArea.let {
|
||||||
|
it.get(imageKey)?.let { listener ->
|
||||||
|
val matrixArray = floatArrayOf(0f, 0f, 0f, 0f, 0f, 0f, 0f, 0f, 0f)
|
||||||
|
frameMatrix.getValues(matrixArray)
|
||||||
|
listener.onResponseArea(imageKey, matrixArray[2].toInt()
|
||||||
|
, matrixArray[5].toInt()
|
||||||
|
, (drawingBitmap.width * matrixArray[0] + matrixArray[2]).toInt()
|
||||||
|
, (drawingBitmap.height * matrixArray[4] + matrixArray[5]).toInt())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
drawTextOnBitmap(canvas, drawingBitmap, sprite, frameMatrix)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun drawTextOnBitmap(canvas: Canvas, drawingBitmap: Bitmap, sprite: SVGADrawerSprite, frameMatrix: Matrix) {
|
||||||
|
if (dynamicItem.isTextDirty) {
|
||||||
|
this.drawTextCache.clear()
|
||||||
|
dynamicItem.isTextDirty = false
|
||||||
|
}
|
||||||
|
val imageKey = sprite.imageKey ?: return
|
||||||
|
var textBitmap: Bitmap? = null
|
||||||
|
dynamicItem.dynamicText[imageKey]?.let { drawingText ->
|
||||||
|
dynamicItem.dynamicTextPaint[imageKey]?.let { drawingTextPaint ->
|
||||||
|
drawTextCache[imageKey]?.let {
|
||||||
|
textBitmap = it
|
||||||
|
} ?: kotlin.run {
|
||||||
|
textBitmap = Bitmap.createBitmap(drawingBitmap.width, drawingBitmap.height, Bitmap.Config.ARGB_8888)
|
||||||
|
val drawRect = Rect(0, 0, drawingBitmap.width, drawingBitmap.height)
|
||||||
|
val textCanvas = Canvas(textBitmap)
|
||||||
|
drawingTextPaint.isAntiAlias = true
|
||||||
|
val fontMetrics = drawingTextPaint.getFontMetrics();
|
||||||
|
val top = fontMetrics.top
|
||||||
|
val bottom = fontMetrics.bottom
|
||||||
|
val baseLineY = drawRect.centerY() - top / 2 - bottom / 2
|
||||||
|
textCanvas.drawText(drawingText, drawRect.centerX().toFloat(), baseLineY, drawingTextPaint);
|
||||||
|
drawTextCache.put(imageKey, textBitmap as Bitmap)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
dynamicItem.dynamicBoringLayoutText[imageKey]?.let {
|
||||||
|
drawTextCache[imageKey]?.let {
|
||||||
|
textBitmap = it
|
||||||
|
} ?: kotlin.run {
|
||||||
|
it.paint.isAntiAlias = true
|
||||||
|
|
||||||
|
textBitmap = Bitmap.createBitmap(drawingBitmap.width, drawingBitmap.height, Bitmap.Config.ARGB_8888)
|
||||||
|
val textCanvas = Canvas(textBitmap)
|
||||||
|
textCanvas.translate(0f, ((drawingBitmap.height - it.height) / 2).toFloat())
|
||||||
|
it.draw(textCanvas)
|
||||||
|
drawTextCache.put(imageKey, textBitmap as Bitmap)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
dynamicItem.dynamicStaticLayoutText[imageKey]?.let {
|
||||||
|
drawTextCache[imageKey]?.let {
|
||||||
|
textBitmap = it
|
||||||
|
} ?: kotlin.run {
|
||||||
|
it.paint.isAntiAlias = true
|
||||||
|
var layout = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
||||||
|
var lineMax = try {
|
||||||
|
val field = StaticLayout::class.java.getDeclaredField("mMaximumVisibleLineCount")
|
||||||
|
field.isAccessible = true
|
||||||
|
field.getInt(it)
|
||||||
|
} catch (e: Exception) {
|
||||||
|
Int.MAX_VALUE
|
||||||
|
}
|
||||||
|
StaticLayout.Builder
|
||||||
|
.obtain(it.text, 0, it.text.length, it.paint, drawingBitmap.width)
|
||||||
|
.setAlignment(it.alignment)
|
||||||
|
.setMaxLines(lineMax)
|
||||||
|
.setEllipsize(TextUtils.TruncateAt.END)
|
||||||
|
.build()
|
||||||
|
} else {
|
||||||
|
StaticLayout(it.text, 0, it.text.length, it.paint, drawingBitmap.width, it.alignment, it.spacingMultiplier, it.spacingAdd, false)
|
||||||
|
}
|
||||||
|
textBitmap = Bitmap.createBitmap(drawingBitmap.width, drawingBitmap.height, Bitmap.Config.ARGB_8888)
|
||||||
|
val textCanvas = Canvas(textBitmap)
|
||||||
|
textCanvas.translate(0f, ((drawingBitmap.height - layout.height) / 2).toFloat())
|
||||||
|
layout.draw(textCanvas)
|
||||||
|
drawTextCache.put(imageKey, textBitmap as Bitmap)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
textBitmap?.let { textBitmap ->
|
||||||
|
val paint = this.sharedValues.sharedPaint()
|
||||||
|
paint.isAntiAlias = videoItem.antiAlias
|
||||||
|
paint.alpha = (sprite.frameEntity.alpha * 255).toInt()
|
||||||
|
if (sprite.frameEntity.maskPath != null) {
|
||||||
|
val maskPath = sprite.frameEntity.maskPath ?: return@let
|
||||||
|
canvas.save()
|
||||||
|
canvas.concat(frameMatrix)
|
||||||
|
canvas.clipRect(0, 0, drawingBitmap.width, drawingBitmap.height)
|
||||||
|
val bitmapShader = BitmapShader(textBitmap, Shader.TileMode.REPEAT, Shader.TileMode.REPEAT)
|
||||||
|
paint.shader = bitmapShader
|
||||||
|
val path = this.sharedValues.sharedPath()
|
||||||
|
maskPath.buildPath(path)
|
||||||
|
canvas.drawPath(path, paint)
|
||||||
|
canvas.restore()
|
||||||
|
} else {
|
||||||
|
paint.isFilterBitmap = videoItem.antiAlias
|
||||||
|
canvas.drawBitmap(textBitmap, frameMatrix, paint)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun drawShape(sprite: SVGADrawerSprite, canvas: Canvas) {
|
||||||
|
val frameMatrix = shareFrameMatrix(sprite.frameEntity.transform)
|
||||||
|
sprite.frameEntity.shapes.forEach { shape ->
|
||||||
|
shape.buildPath()
|
||||||
|
shape.shapePath?.let {
|
||||||
|
val paint = this.sharedValues.sharedPaint()
|
||||||
|
paint.reset()
|
||||||
|
paint.isAntiAlias = videoItem.antiAlias
|
||||||
|
paint.alpha = (sprite.frameEntity.alpha * 255).toInt()
|
||||||
|
val path = this.sharedValues.sharedPath()
|
||||||
|
path.reset()
|
||||||
|
path.addPath(this.pathCache.buildPath(shape))
|
||||||
|
val shapeMatrix = this.sharedValues.sharedMatrix2()
|
||||||
|
shapeMatrix.reset()
|
||||||
|
shape.transform?.let {
|
||||||
|
shapeMatrix.postConcat(it)
|
||||||
|
}
|
||||||
|
shapeMatrix.postConcat(frameMatrix)
|
||||||
|
path.transform(shapeMatrix)
|
||||||
|
shape.styles?.fill?.let {
|
||||||
|
if (it != 0x00000000) {
|
||||||
|
paint.style = Paint.Style.FILL
|
||||||
|
paint.color = it
|
||||||
|
val alpha = Math.min(255, Math.max(0, (sprite.frameEntity.alpha * 255).toInt()))
|
||||||
|
if (alpha != 255) {
|
||||||
|
paint.alpha = alpha
|
||||||
|
}
|
||||||
|
if (sprite.frameEntity.maskPath !== null) canvas.save()
|
||||||
|
sprite.frameEntity.maskPath?.let { maskPath ->
|
||||||
|
val path2 = this.sharedValues.sharedPath2()
|
||||||
|
maskPath.buildPath(path2)
|
||||||
|
path2.transform(frameMatrix)
|
||||||
|
canvas.clipPath(path2)
|
||||||
|
}
|
||||||
|
canvas.drawPath(path, paint)
|
||||||
|
if (sprite.frameEntity.maskPath !== null) canvas.restore()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
shape.styles?.strokeWidth?.let {
|
||||||
|
if (it > 0) {
|
||||||
|
paint.alpha = (sprite.frameEntity.alpha * 255).toInt()
|
||||||
|
paint.style = Paint.Style.STROKE
|
||||||
|
shape.styles?.stroke?.let {
|
||||||
|
paint.color = it
|
||||||
|
val alpha = Math.min(255, Math.max(0, (sprite.frameEntity.alpha * 255).toInt()))
|
||||||
|
if (alpha != 255) {
|
||||||
|
paint.alpha = alpha
|
||||||
|
}
|
||||||
|
}
|
||||||
|
val scale = matrixScale(frameMatrix)
|
||||||
|
shape.styles?.strokeWidth?.let {
|
||||||
|
paint.strokeWidth = it * scale
|
||||||
|
}
|
||||||
|
shape.styles?.lineCap?.let {
|
||||||
|
when {
|
||||||
|
it.equals("butt", true) -> paint.strokeCap = Paint.Cap.BUTT
|
||||||
|
it.equals("round", true) -> paint.strokeCap = Paint.Cap.ROUND
|
||||||
|
it.equals("square", true) -> paint.strokeCap = Paint.Cap.SQUARE
|
||||||
|
}
|
||||||
|
}
|
||||||
|
shape.styles?.lineJoin?.let {
|
||||||
|
when {
|
||||||
|
it.equals("miter", true) -> paint.strokeJoin = Paint.Join.MITER
|
||||||
|
it.equals("round", true) -> paint.strokeJoin = Paint.Join.ROUND
|
||||||
|
it.equals("bevel", true) -> paint.strokeJoin = Paint.Join.BEVEL
|
||||||
|
}
|
||||||
|
}
|
||||||
|
shape.styles?.miterLimit?.let {
|
||||||
|
paint.strokeMiter = it.toFloat() * scale
|
||||||
|
}
|
||||||
|
shape.styles?.lineDash?.let {
|
||||||
|
if (it.size == 3 && (it[0] > 0 || it[1] > 0)) {
|
||||||
|
paint.pathEffect = DashPathEffect(floatArrayOf(
|
||||||
|
(if (it[0] < 1.0f) 1.0f else it[0]) * scale,
|
||||||
|
(if (it[1] < 0.1f) 0.1f else it[1]) * scale
|
||||||
|
), it[2] * scale)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (sprite.frameEntity.maskPath !== null) canvas.save()
|
||||||
|
sprite.frameEntity.maskPath?.let { maskPath ->
|
||||||
|
val path2 = this.sharedValues.sharedPath2()
|
||||||
|
maskPath.buildPath(path2)
|
||||||
|
path2.transform(frameMatrix)
|
||||||
|
canvas.clipPath(path2)
|
||||||
|
}
|
||||||
|
canvas.drawPath(path, paint)
|
||||||
|
if (sprite.frameEntity.maskPath !== null) canvas.restore()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private val matrixScaleTempValues = FloatArray(16)
|
||||||
|
|
||||||
|
private fun matrixScale(matrix: Matrix): Float {
|
||||||
|
matrix.getValues(matrixScaleTempValues)
|
||||||
|
if (matrixScaleTempValues[0] == 0f) {
|
||||||
|
return 0f
|
||||||
|
}
|
||||||
|
var A = matrixScaleTempValues[0].toDouble()
|
||||||
|
var B = matrixScaleTempValues[3].toDouble()
|
||||||
|
var C = matrixScaleTempValues[1].toDouble()
|
||||||
|
var D = matrixScaleTempValues[4].toDouble()
|
||||||
|
if (A * D == B * C) return 0f
|
||||||
|
var scaleX = Math.sqrt(A * A + B * B)
|
||||||
|
A /= scaleX
|
||||||
|
B /= scaleX
|
||||||
|
var skew = A * C + B * D
|
||||||
|
C -= A * skew
|
||||||
|
D -= B * skew
|
||||||
|
var scaleY = Math.sqrt(C * C + D * D)
|
||||||
|
C /= scaleY
|
||||||
|
D /= scaleY
|
||||||
|
skew /= scaleY
|
||||||
|
if (A * D < B * C) {
|
||||||
|
scaleX = -scaleX
|
||||||
|
}
|
||||||
|
return if (scaleInfo.ratioX) Math.abs(scaleX.toFloat()) else Math.abs(scaleY.toFloat())
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun drawDynamic(sprite: SVGADrawerSprite, canvas: Canvas, frameIndex: Int) {
|
||||||
|
val imageKey = sprite.imageKey ?: return
|
||||||
|
dynamicItem.dynamicDrawer[imageKey]?.let {
|
||||||
|
val frameMatrix = shareFrameMatrix(sprite.frameEntity.transform)
|
||||||
|
canvas.save()
|
||||||
|
canvas.concat(frameMatrix)
|
||||||
|
it.invoke(canvas, frameIndex)
|
||||||
|
canvas.restore()
|
||||||
|
}
|
||||||
|
dynamicItem.dynamicDrawerSized[imageKey]?.let {
|
||||||
|
val frameMatrix = shareFrameMatrix(sprite.frameEntity.transform)
|
||||||
|
canvas.save()
|
||||||
|
canvas.concat(frameMatrix)
|
||||||
|
it.invoke(canvas, frameIndex, sprite.frameEntity.layout.width.toInt(), sprite.frameEntity.layout.height.toInt())
|
||||||
|
canvas.restore()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class ShareValues {
|
||||||
|
|
||||||
|
private val sharedPaint = Paint()
|
||||||
|
private val sharedPath = Path()
|
||||||
|
private val sharedPath2 = Path()
|
||||||
|
private val sharedMatrix = Matrix()
|
||||||
|
private val sharedMatrix2 = Matrix()
|
||||||
|
|
||||||
|
private val shareMattePaint = Paint()
|
||||||
|
private var shareMatteCanvas: Canvas? = null
|
||||||
|
private var sharedMatteBitmap: Bitmap? = null
|
||||||
|
|
||||||
|
fun sharedPaint(): Paint {
|
||||||
|
sharedPaint.reset()
|
||||||
|
return sharedPaint
|
||||||
|
}
|
||||||
|
|
||||||
|
fun sharedPath(): Path {
|
||||||
|
sharedPath.reset()
|
||||||
|
return sharedPath
|
||||||
|
}
|
||||||
|
|
||||||
|
fun sharedPath2(): Path {
|
||||||
|
sharedPath2.reset()
|
||||||
|
return sharedPath2
|
||||||
|
}
|
||||||
|
|
||||||
|
fun sharedMatrix(): Matrix {
|
||||||
|
sharedMatrix.reset()
|
||||||
|
return sharedMatrix
|
||||||
|
}
|
||||||
|
|
||||||
|
fun sharedMatrix2(): Matrix {
|
||||||
|
sharedMatrix2.reset()
|
||||||
|
return sharedMatrix2
|
||||||
|
}
|
||||||
|
|
||||||
|
fun shareMattePaint(): Paint {
|
||||||
|
shareMattePaint.setXfermode(PorterDuffXfermode(PorterDuff.Mode.DST_IN))
|
||||||
|
return shareMattePaint
|
||||||
|
}
|
||||||
|
|
||||||
|
fun sharedMatteBitmap(): Bitmap {
|
||||||
|
return sharedMatteBitmap as Bitmap
|
||||||
|
}
|
||||||
|
|
||||||
|
fun shareMatteCanvas(width: Int, height: Int): Canvas {
|
||||||
|
if (shareMatteCanvas == null) {
|
||||||
|
sharedMatteBitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ALPHA_8)
|
||||||
|
// shareMatteCanvas = Canvas(sharedMatteBitmap)
|
||||||
|
}
|
||||||
|
// val matteCanvas = shareMatteCanvas as Canvas
|
||||||
|
// matteCanvas.drawColor(Color.TRANSPARENT, PorterDuff.Mode.CLEAR)
|
||||||
|
// return matteCanvas
|
||||||
|
return Canvas(sharedMatteBitmap)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class PathCache {
|
||||||
|
|
||||||
|
private var canvasWidth: Int = 0
|
||||||
|
private var canvasHeight: Int = 0
|
||||||
|
private val cache = HashMap<SVGAVideoShapeEntity, Path>()
|
||||||
|
|
||||||
|
fun onSizeChanged(canvas: Canvas) {
|
||||||
|
if (this.canvasWidth != canvas.width || this.canvasHeight != canvas.height) {
|
||||||
|
this.cache.clear()
|
||||||
|
}
|
||||||
|
this.canvasWidth = canvas.width
|
||||||
|
this.canvasHeight = canvas.height
|
||||||
|
}
|
||||||
|
|
||||||
|
fun buildPath(shape: SVGAVideoShapeEntity): Path {
|
||||||
|
if (!this.cache.containsKey(shape)) {
|
||||||
|
val path = Path()
|
||||||
|
path.set(shape.shapePath)
|
||||||
|
this.cache[shape] = path
|
||||||
|
}
|
||||||
|
return this.cache[shape]!!
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
package com.opensource.svgaplayer.entities
|
||||||
|
|
||||||
|
import com.opensource.svgaplayer.proto.AudioEntity
|
||||||
|
import java.io.FileInputStream
|
||||||
|
|
||||||
|
internal class SVGAAudioEntity {
|
||||||
|
|
||||||
|
val audioKey: String?
|
||||||
|
val startFrame: Int
|
||||||
|
val endFrame: Int
|
||||||
|
val startTime: Int
|
||||||
|
val totalTime: Int
|
||||||
|
var soundID: Int? = null
|
||||||
|
var playID: Int? = null
|
||||||
|
|
||||||
|
constructor(audioItem: AudioEntity) {
|
||||||
|
this.audioKey = audioItem.audioKey
|
||||||
|
this.startFrame = audioItem.startFrame ?: 0
|
||||||
|
this.endFrame = audioItem.endFrame ?: 0
|
||||||
|
this.startTime = audioItem.startTime ?: 0
|
||||||
|
this.totalTime = audioItem.totalTime ?: 0
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,100 @@
|
|||||||
|
package com.opensource.svgaplayer.entities
|
||||||
|
|
||||||
|
import android.graphics.Path
|
||||||
|
import com.opensource.svgaplayer.utils.SVGAPoint
|
||||||
|
import java.util.*
|
||||||
|
|
||||||
|
private val VALID_METHODS: Set<String> = setOf("M", "L", "H", "V", "C", "S", "Q", "R", "A", "Z", "m", "l", "h", "v", "c", "s", "q", "r", "a", "z")
|
||||||
|
|
||||||
|
class SVGAPathEntity(originValue: String) {
|
||||||
|
|
||||||
|
private val replacedValue: String = if (originValue.contains(",")) originValue.replace(",", " ") else originValue
|
||||||
|
|
||||||
|
private var cachedPath: Path? = null
|
||||||
|
|
||||||
|
fun buildPath(toPath: Path) {
|
||||||
|
cachedPath?.let {
|
||||||
|
toPath.set(it)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
val cachedPath = Path()
|
||||||
|
val segments = StringTokenizer(this.replacedValue, "MLHVCSQRAZmlhvcsqraz", true)
|
||||||
|
var currentMethod = ""
|
||||||
|
while (segments.hasMoreTokens()) {
|
||||||
|
val segment = segments.nextToken()
|
||||||
|
if (segment.isEmpty()) { continue }
|
||||||
|
if (VALID_METHODS.contains(segment)) {
|
||||||
|
currentMethod = segment
|
||||||
|
if (currentMethod == "Z" || currentMethod == "z") { operate(cachedPath, currentMethod, StringTokenizer("", "")) }
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
operate(cachedPath, currentMethod, StringTokenizer(segment, " "))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this.cachedPath = cachedPath
|
||||||
|
toPath.set(cachedPath)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun operate(finalPath: Path, method: String, args: StringTokenizer) {
|
||||||
|
var x0 = 0.0f
|
||||||
|
var y0 = 0.0f
|
||||||
|
var x1 = 0.0f
|
||||||
|
var y1 = 0.0f
|
||||||
|
var x2 = 0.0f
|
||||||
|
var y2 = 0.0f
|
||||||
|
try {
|
||||||
|
var index = 0
|
||||||
|
while (args.hasMoreTokens()) {
|
||||||
|
val s = args.nextToken()
|
||||||
|
if (s.isEmpty()) {continue}
|
||||||
|
if (index == 0) { x0 = s.toFloat() }
|
||||||
|
if (index == 1) { y0 = s.toFloat() }
|
||||||
|
if (index == 2) { x1 = s.toFloat() }
|
||||||
|
if (index == 3) { y1 = s.toFloat() }
|
||||||
|
if (index == 4) { x2 = s.toFloat() }
|
||||||
|
if (index == 5) { y2 = s.toFloat() }
|
||||||
|
index++
|
||||||
|
}
|
||||||
|
} catch (e: Exception) {}
|
||||||
|
var currentPoint = SVGAPoint(0.0f, 0.0f, 0.0f)
|
||||||
|
if (method == "M") {
|
||||||
|
finalPath.moveTo(x0, y0)
|
||||||
|
currentPoint = SVGAPoint(x0, y0, 0.0f)
|
||||||
|
} else if (method == "m") {
|
||||||
|
finalPath.rMoveTo(x0, y0)
|
||||||
|
currentPoint = SVGAPoint(currentPoint.x + x0, currentPoint.y + y0, 0.0f)
|
||||||
|
}
|
||||||
|
if (method == "L") {
|
||||||
|
finalPath.lineTo(x0, y0)
|
||||||
|
} else if (method == "l") {
|
||||||
|
finalPath.rLineTo(x0, y0)
|
||||||
|
}
|
||||||
|
if (method == "C") {
|
||||||
|
finalPath.cubicTo(x0, y0, x1, y1, x2, y2)
|
||||||
|
} else if (method == "c") {
|
||||||
|
finalPath.rCubicTo(x0, y0, x1, y1, x2, y2)
|
||||||
|
}
|
||||||
|
if (method == "Q") {
|
||||||
|
finalPath.quadTo(x0, y0, x1, y1)
|
||||||
|
} else if (method == "q") {
|
||||||
|
finalPath.rQuadTo(x0, y0, x1, y1)
|
||||||
|
}
|
||||||
|
if (method == "H") {
|
||||||
|
finalPath.lineTo(x0, currentPoint.y)
|
||||||
|
} else if (method == "h") {
|
||||||
|
finalPath.rLineTo(x0, 0f)
|
||||||
|
}
|
||||||
|
if (method == "V") {
|
||||||
|
finalPath.lineTo(currentPoint.x, x0)
|
||||||
|
} else if (method == "v") {
|
||||||
|
finalPath.rLineTo(0f, x0)
|
||||||
|
}
|
||||||
|
if (method == "Z") {
|
||||||
|
finalPath.close()
|
||||||
|
}
|
||||||
|
else if (method == "z") {
|
||||||
|
finalPath.close()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,356 @@
|
|||||||
|
package com.opensource.svgaplayer.entities
|
||||||
|
|
||||||
|
import android.graphics.Color
|
||||||
|
import android.graphics.Matrix
|
||||||
|
import android.graphics.Path
|
||||||
|
import android.graphics.RectF
|
||||||
|
import com.opensource.svgaplayer.proto.ShapeEntity
|
||||||
|
import org.json.JSONArray
|
||||||
|
import org.json.JSONObject
|
||||||
|
import java.util.*
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Created by cuiminghui on 2017/2/22.
|
||||||
|
*/
|
||||||
|
|
||||||
|
val sharedPath = Path()
|
||||||
|
|
||||||
|
internal class SVGAVideoShapeEntity {
|
||||||
|
|
||||||
|
enum class Type {
|
||||||
|
shape,
|
||||||
|
rect,
|
||||||
|
ellipse,
|
||||||
|
keep
|
||||||
|
}
|
||||||
|
|
||||||
|
class Styles {
|
||||||
|
|
||||||
|
var fill = 0x00000000
|
||||||
|
internal set
|
||||||
|
|
||||||
|
var stroke = 0x00000000
|
||||||
|
internal set
|
||||||
|
|
||||||
|
var strokeWidth = 0.0f
|
||||||
|
internal set
|
||||||
|
|
||||||
|
var lineCap = "butt"
|
||||||
|
internal set
|
||||||
|
|
||||||
|
var lineJoin = "miter"
|
||||||
|
internal set
|
||||||
|
|
||||||
|
var miterLimit = 0
|
||||||
|
internal set
|
||||||
|
|
||||||
|
var lineDash = FloatArray(0)
|
||||||
|
internal set
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
var type = Type.shape
|
||||||
|
private set
|
||||||
|
|
||||||
|
var args: Map<String, Any>? = null
|
||||||
|
private set
|
||||||
|
|
||||||
|
var styles: Styles? = null
|
||||||
|
private set
|
||||||
|
|
||||||
|
var transform: Matrix? = null
|
||||||
|
private set
|
||||||
|
|
||||||
|
constructor(obj: JSONObject) {
|
||||||
|
parseType(obj)
|
||||||
|
parseArgs(obj)
|
||||||
|
parseStyles(obj)
|
||||||
|
parseTransform(obj)
|
||||||
|
}
|
||||||
|
|
||||||
|
constructor(obj: ShapeEntity) {
|
||||||
|
parseType(obj)
|
||||||
|
parseArgs(obj)
|
||||||
|
parseStyles(obj)
|
||||||
|
parseTransform(obj)
|
||||||
|
}
|
||||||
|
|
||||||
|
val isKeep: Boolean
|
||||||
|
get() = type == Type.keep
|
||||||
|
|
||||||
|
var shapePath: Path? = null
|
||||||
|
|
||||||
|
private fun parseType(obj: JSONObject) {
|
||||||
|
obj.optString("type")?.let {
|
||||||
|
when {
|
||||||
|
it.equals("shape", ignoreCase = true) -> type = Type.shape
|
||||||
|
it.equals("rect", ignoreCase = true) -> type = Type.rect
|
||||||
|
it.equals("ellipse", ignoreCase = true) -> type = Type.ellipse
|
||||||
|
it.equals("keep", ignoreCase = true) -> type = Type.keep
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun parseType(obj: ShapeEntity) {
|
||||||
|
obj.type?.let {
|
||||||
|
type = when (it) {
|
||||||
|
ShapeEntity.ShapeType.SHAPE -> Type.shape
|
||||||
|
ShapeEntity.ShapeType.RECT -> Type.rect
|
||||||
|
ShapeEntity.ShapeType.ELLIPSE -> Type.ellipse
|
||||||
|
ShapeEntity.ShapeType.KEEP -> Type.keep
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun parseArgs(obj: JSONObject) {
|
||||||
|
val args = HashMap<String, Any>()
|
||||||
|
obj.optJSONObject("args")?.let { values ->
|
||||||
|
values.keys().forEach { key ->
|
||||||
|
values.get(key)?.let {
|
||||||
|
args.put(key, it)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this.args = args
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun parseArgs(obj: ShapeEntity) {
|
||||||
|
val args = HashMap<String, Any>()
|
||||||
|
obj.shape?.let {
|
||||||
|
it.d?.let { args.put("d", it) }
|
||||||
|
}
|
||||||
|
obj.ellipse?.let {
|
||||||
|
args.put("x", it.x ?: 0.0f)
|
||||||
|
args.put("y", it.y ?: 0.0f)
|
||||||
|
args.put("radiusX", it.radiusX ?: 0.0f)
|
||||||
|
args.put("radiusY", it.radiusY ?: 0.0f)
|
||||||
|
}
|
||||||
|
obj.rect?.let {
|
||||||
|
args.put("x", it.x ?: 0.0f)
|
||||||
|
args.put("y", it.y ?: 0.0f)
|
||||||
|
args.put("width", it.width ?: 0.0f)
|
||||||
|
args.put("height", it.height ?: 0.0f)
|
||||||
|
args.put("cornerRadius", it.cornerRadius ?: 0.0f)
|
||||||
|
}
|
||||||
|
this.args = args
|
||||||
|
}
|
||||||
|
|
||||||
|
// 检查色域范围是否是 [0f, 1f],或者是 [0f, 255f]
|
||||||
|
private fun checkValueRange(obj: JSONArray): Float {
|
||||||
|
return if (
|
||||||
|
obj.optDouble(0) <= 1 &&
|
||||||
|
obj.optDouble(1) <= 1 &&
|
||||||
|
obj.optDouble(2) <= 1
|
||||||
|
) {
|
||||||
|
255f
|
||||||
|
} else {
|
||||||
|
1f
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 检查 alpha 的范围是否是 [0f, 1f],或者是 [0f, 255f]
|
||||||
|
private fun checkAlphaValueRange(obj: JSONArray): Float {
|
||||||
|
return if (obj.optDouble(3) <= 1) {
|
||||||
|
255f
|
||||||
|
} else {
|
||||||
|
1f
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun parseStyles(obj: JSONObject) {
|
||||||
|
obj.optJSONObject("styles")?.let {
|
||||||
|
val styles = Styles()
|
||||||
|
it.optJSONArray("fill")?.let {
|
||||||
|
if (it.length() == 4) {
|
||||||
|
val mulValue = checkValueRange(it)
|
||||||
|
val alphaRangeValue = checkAlphaValueRange(it)
|
||||||
|
styles.fill = Color.argb(
|
||||||
|
(it.optDouble(3) * alphaRangeValue).toInt(),
|
||||||
|
(it.optDouble(0) * mulValue).toInt(),
|
||||||
|
(it.optDouble(1) * mulValue).toInt(),
|
||||||
|
(it.optDouble(2) * mulValue).toInt()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
it.optJSONArray("stroke")?.let {
|
||||||
|
if (it.length() == 4) {
|
||||||
|
val mulValue = checkValueRange(it)
|
||||||
|
val alphaRangeValue = checkAlphaValueRange(it)
|
||||||
|
styles.stroke = Color.argb(
|
||||||
|
(it.optDouble(3) * alphaRangeValue).toInt(),
|
||||||
|
(it.optDouble(0) * mulValue).toInt(),
|
||||||
|
(it.optDouble(1) * mulValue).toInt(),
|
||||||
|
(it.optDouble(2) * mulValue).toInt()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
styles.strokeWidth = it.optDouble("strokeWidth", 0.0).toFloat()
|
||||||
|
styles.lineCap = it.optString("lineCap", "butt")
|
||||||
|
styles.lineJoin = it.optString("lineJoin", "miter")
|
||||||
|
styles.miterLimit = it.optInt("miterLimit", 0)
|
||||||
|
it.optJSONArray("lineDash")?.let {
|
||||||
|
styles.lineDash = FloatArray(it.length())
|
||||||
|
for (i in 0 until it.length()) {
|
||||||
|
styles.lineDash[i] = it.optDouble(i, 0.0).toFloat()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this.styles = styles
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 检查色域范围是否是 [0f, 1f],或者是 [0f, 255f]
|
||||||
|
private fun checkValueRange(color: ShapeEntity.ShapeStyle.RGBAColor): Float {
|
||||||
|
return if (
|
||||||
|
(color.r ?: 0f) <= 1 &&
|
||||||
|
(color.g ?: 0f) <= 1 &&
|
||||||
|
(color.b ?: 0f) <= 1
|
||||||
|
) {
|
||||||
|
255f
|
||||||
|
} else {
|
||||||
|
1f
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 检查 alpha 范围是否是 [0f, 1f],有可能是 [0f, 255f]
|
||||||
|
private fun checkAlphaValueRange(color: ShapeEntity.ShapeStyle.RGBAColor): Float {
|
||||||
|
return if (color.a <= 1f) {
|
||||||
|
255f
|
||||||
|
} else {
|
||||||
|
1f
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun parseStyles(obj: ShapeEntity) {
|
||||||
|
obj.styles?.let {
|
||||||
|
val styles = Styles()
|
||||||
|
it.fill?.let {
|
||||||
|
val mulValue = checkValueRange(it)
|
||||||
|
val alphaRangeValue = checkAlphaValueRange(it)
|
||||||
|
styles.fill = Color.argb(
|
||||||
|
((it.a ?: 0f) * alphaRangeValue).toInt(),
|
||||||
|
((it.r ?: 0f) * mulValue).toInt(),
|
||||||
|
((it.g ?: 0f) * mulValue).toInt(),
|
||||||
|
((it.b ?: 0f) * mulValue).toInt()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
it.stroke?.let {
|
||||||
|
val mulValue = checkValueRange(it)
|
||||||
|
val alphaRangeValue = checkAlphaValueRange(it)
|
||||||
|
styles.stroke = Color.argb(
|
||||||
|
((it.a ?: 0f) * alphaRangeValue).toInt(),
|
||||||
|
((it.r ?: 0f) * mulValue).toInt(),
|
||||||
|
((it.g ?: 0f) * mulValue).toInt(),
|
||||||
|
((it.b ?: 0f) * mulValue).toInt()
|
||||||
|
)
|
||||||
|
|
||||||
|
}
|
||||||
|
styles.strokeWidth = it.strokeWidth ?: 0.0f
|
||||||
|
it.lineCap?.let {
|
||||||
|
when (it) {
|
||||||
|
ShapeEntity.ShapeStyle.LineCap.LineCap_BUTT -> styles.lineCap = "butt"
|
||||||
|
ShapeEntity.ShapeStyle.LineCap.LineCap_ROUND -> styles.lineCap = "round"
|
||||||
|
ShapeEntity.ShapeStyle.LineCap.LineCap_SQUARE -> styles.lineCap = "square"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
it.lineJoin?.let {
|
||||||
|
when (it) {
|
||||||
|
ShapeEntity.ShapeStyle.LineJoin.LineJoin_BEVEL -> styles.lineJoin = "bevel"
|
||||||
|
ShapeEntity.ShapeStyle.LineJoin.LineJoin_MITER -> styles.lineJoin = "miter"
|
||||||
|
ShapeEntity.ShapeStyle.LineJoin.LineJoin_ROUND -> styles.lineJoin = "round"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
styles.miterLimit = (it.miterLimit ?: 0.0f).toInt()
|
||||||
|
styles.lineDash = kotlin.FloatArray(3)
|
||||||
|
it.lineDashI?.let { styles.lineDash[0] = it }
|
||||||
|
it.lineDashII?.let { styles.lineDash[1] = it }
|
||||||
|
it.lineDashIII?.let { styles.lineDash[2] = it }
|
||||||
|
this.styles = styles
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun parseTransform(obj: JSONObject) {
|
||||||
|
obj.optJSONObject("transform")?.let {
|
||||||
|
val transform = Matrix()
|
||||||
|
val arr = FloatArray(9)
|
||||||
|
val a = it.optDouble("a", 1.0)
|
||||||
|
val b = it.optDouble("b", 0.0)
|
||||||
|
val c = it.optDouble("c", 0.0)
|
||||||
|
val d = it.optDouble("d", 1.0)
|
||||||
|
val tx = it.optDouble("tx", 0.0)
|
||||||
|
val ty = it.optDouble("ty", 0.0)
|
||||||
|
arr[0] = a.toFloat() // a
|
||||||
|
arr[1] = c.toFloat() // c
|
||||||
|
arr[2] = tx.toFloat() // tx
|
||||||
|
arr[3] = b.toFloat() // b
|
||||||
|
arr[4] = d.toFloat() // d
|
||||||
|
arr[5] = ty.toFloat() // ty
|
||||||
|
arr[6] = 0.0.toFloat()
|
||||||
|
arr[7] = 0.0.toFloat()
|
||||||
|
arr[8] = 1.0.toFloat()
|
||||||
|
transform.setValues(arr)
|
||||||
|
this.transform = transform
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun parseTransform(obj: ShapeEntity) {
|
||||||
|
obj.transform?.let {
|
||||||
|
val transform = Matrix()
|
||||||
|
val arr = FloatArray(9)
|
||||||
|
val a = it.a ?: 1.0f
|
||||||
|
val b = it.b ?: 0.0f
|
||||||
|
val c = it.c ?: 0.0f
|
||||||
|
val d = it.d ?: 1.0f
|
||||||
|
val tx = it.tx ?: 0.0f
|
||||||
|
val ty = it.ty ?: 0.0f
|
||||||
|
arr[0] = a
|
||||||
|
arr[1] = c
|
||||||
|
arr[2] = tx
|
||||||
|
arr[3] = b
|
||||||
|
arr[4] = d
|
||||||
|
arr[5] = ty
|
||||||
|
arr[6] = 0.0f
|
||||||
|
arr[7] = 0.0f
|
||||||
|
arr[8] = 1.0f
|
||||||
|
transform.setValues(arr)
|
||||||
|
this.transform = transform
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
fun buildPath() {
|
||||||
|
if (this.shapePath != null) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
sharedPath.reset()
|
||||||
|
if (this.type == Type.shape) {
|
||||||
|
(this.args?.get("d") as? String)?.let {
|
||||||
|
SVGAPathEntity(it).buildPath(sharedPath)
|
||||||
|
}
|
||||||
|
} else if (this.type == Type.ellipse) {
|
||||||
|
val xv = this.args?.get("x") as? Number ?: return
|
||||||
|
val yv = this.args?.get("y") as? Number ?: return
|
||||||
|
val rxv = this.args?.get("radiusX") as? Number ?: return
|
||||||
|
val ryv = this.args?.get("radiusY") as? Number ?: return
|
||||||
|
val x = xv.toFloat()
|
||||||
|
val y = yv.toFloat()
|
||||||
|
val rx = rxv.toFloat()
|
||||||
|
val ry = ryv.toFloat()
|
||||||
|
sharedPath.addOval(RectF(x - rx, y - ry, x + rx, y + ry), Path.Direction.CW)
|
||||||
|
} else if (this.type == Type.rect) {
|
||||||
|
val xv = this.args?.get("x") as? Number ?: return
|
||||||
|
val yv = this.args?.get("y") as? Number ?: return
|
||||||
|
val wv = this.args?.get("width") as? Number ?: return
|
||||||
|
val hv = this.args?.get("height") as? Number ?: return
|
||||||
|
val crv = this.args?.get("cornerRadius") as? Number ?: return
|
||||||
|
val x = xv.toFloat()
|
||||||
|
val y = yv.toFloat()
|
||||||
|
val width = wv.toFloat()
|
||||||
|
val height = hv.toFloat()
|
||||||
|
val cornerRadius = crv.toFloat()
|
||||||
|
sharedPath.addRoundRect(RectF(x, y, x + width, y + height), cornerRadius, cornerRadius, Path.Direction.CW)
|
||||||
|
}
|
||||||
|
this.shapePath = Path()
|
||||||
|
this.shapePath?.set(sharedPath)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,60 @@
|
|||||||
|
package com.opensource.svgaplayer.entities
|
||||||
|
|
||||||
|
import com.opensource.svgaplayer.proto.SpriteEntity
|
||||||
|
import org.json.JSONObject
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Created by cuiminghui on 2016/10/17.
|
||||||
|
*/
|
||||||
|
internal class SVGAVideoSpriteEntity {
|
||||||
|
|
||||||
|
val imageKey: String?
|
||||||
|
|
||||||
|
val matteKey: String?
|
||||||
|
|
||||||
|
val frames: List<SVGAVideoSpriteFrameEntity>
|
||||||
|
|
||||||
|
constructor(obj: JSONObject) {
|
||||||
|
this.imageKey = obj.optString("imageKey")
|
||||||
|
this.matteKey = obj.optString("matteKey")
|
||||||
|
val mutableFrames: MutableList<SVGAVideoSpriteFrameEntity> = mutableListOf()
|
||||||
|
obj.optJSONArray("frames")?.let {
|
||||||
|
for (i in 0 until it.length()) {
|
||||||
|
it.optJSONObject(i)?.let {
|
||||||
|
val frameItem = SVGAVideoSpriteFrameEntity(it)
|
||||||
|
if (frameItem.shapes.isNotEmpty()) {
|
||||||
|
frameItem.shapes.first().let {
|
||||||
|
if (it.isKeep && mutableFrames.size > 0) {
|
||||||
|
frameItem.shapes = mutableFrames.last().shapes
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
mutableFrames.add(frameItem)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
frames = mutableFrames.toList()
|
||||||
|
}
|
||||||
|
|
||||||
|
constructor(obj: SpriteEntity) {
|
||||||
|
this.imageKey = obj.imageKey
|
||||||
|
this.matteKey = obj.matteKey
|
||||||
|
var lastFrame: SVGAVideoSpriteFrameEntity? = null
|
||||||
|
frames = obj.frames?.map {
|
||||||
|
val frameItem = SVGAVideoSpriteFrameEntity(it)
|
||||||
|
if (frameItem.shapes.isNotEmpty()) {
|
||||||
|
frameItem.shapes.first().let {
|
||||||
|
if (it.isKeep) {
|
||||||
|
lastFrame?.let {
|
||||||
|
frameItem.shapes = it.shapes
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
lastFrame = frameItem
|
||||||
|
return@map frameItem
|
||||||
|
} ?: listOf()
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,94 @@
|
|||||||
|
package com.opensource.svgaplayer.entities
|
||||||
|
|
||||||
|
import android.graphics.Matrix
|
||||||
|
import com.opensource.svgaplayer.proto.FrameEntity
|
||||||
|
import com.opensource.svgaplayer.utils.SVGARect
|
||||||
|
|
||||||
|
import org.json.JSONObject
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Created by cuiminghui on 2016/10/17.
|
||||||
|
*/
|
||||||
|
internal class SVGAVideoSpriteFrameEntity {
|
||||||
|
|
||||||
|
var alpha: Double
|
||||||
|
var layout = SVGARect(0.0, 0.0, 0.0, 0.0)
|
||||||
|
var transform = Matrix()
|
||||||
|
var maskPath: SVGAPathEntity? = null
|
||||||
|
var shapes: List<SVGAVideoShapeEntity> = listOf()
|
||||||
|
|
||||||
|
constructor(obj: JSONObject) {
|
||||||
|
this.alpha = obj.optDouble("alpha", 0.0)
|
||||||
|
obj.optJSONObject("layout")?.let {
|
||||||
|
layout = SVGARect(it.optDouble("x", 0.0), it.optDouble("y", 0.0), it.optDouble("width", 0.0), it.optDouble("height", 0.0))
|
||||||
|
}
|
||||||
|
obj.optJSONObject("transform")?.let {
|
||||||
|
val arr = FloatArray(9)
|
||||||
|
val a = it.optDouble("a", 1.0)
|
||||||
|
val b = it.optDouble("b", 0.0)
|
||||||
|
val c = it.optDouble("c", 0.0)
|
||||||
|
val d = it.optDouble("d", 1.0)
|
||||||
|
val tx = it.optDouble("tx", 0.0)
|
||||||
|
val ty = it.optDouble("ty", 0.0)
|
||||||
|
arr[0] = a.toFloat()
|
||||||
|
arr[1] = c.toFloat()
|
||||||
|
arr[2] = tx.toFloat()
|
||||||
|
arr[3] = b.toFloat()
|
||||||
|
arr[4] = d.toFloat()
|
||||||
|
arr[5] = ty.toFloat()
|
||||||
|
arr[6] = 0.0.toFloat()
|
||||||
|
arr[7] = 0.0.toFloat()
|
||||||
|
arr[8] = 1.0.toFloat()
|
||||||
|
transform.setValues(arr)
|
||||||
|
}
|
||||||
|
obj.optString("clipPath")?.let { d ->
|
||||||
|
if (d.isNotEmpty()) {
|
||||||
|
maskPath = SVGAPathEntity(d)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
obj.optJSONArray("shapes")?.let {
|
||||||
|
val mutableList: MutableList<SVGAVideoShapeEntity> = mutableListOf()
|
||||||
|
for (i in 0 until it.length()) {
|
||||||
|
it.optJSONObject(i)?.let {
|
||||||
|
mutableList.add(SVGAVideoShapeEntity(it))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
shapes = mutableList.toList()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
constructor(obj: FrameEntity) {
|
||||||
|
this.alpha = (obj.alpha ?: 0.0f).toDouble()
|
||||||
|
obj.layout?.let {
|
||||||
|
this.layout = SVGARect((it.x ?: 0.0f).toDouble(), (it.y
|
||||||
|
?: 0.0f).toDouble(), (it.width ?: 0.0f).toDouble(), (it.height
|
||||||
|
?: 0.0f).toDouble())
|
||||||
|
}
|
||||||
|
obj.transform?.let {
|
||||||
|
val arr = FloatArray(9)
|
||||||
|
val a = it.a ?: 1.0f
|
||||||
|
val b = it.b ?: 0.0f
|
||||||
|
val c = it.c ?: 0.0f
|
||||||
|
val d = it.d ?: 1.0f
|
||||||
|
val tx = it.tx ?: 0.0f
|
||||||
|
val ty = it.ty ?: 0.0f
|
||||||
|
arr[0] = a
|
||||||
|
arr[1] = c
|
||||||
|
arr[2] = tx
|
||||||
|
arr[3] = b
|
||||||
|
arr[4] = d
|
||||||
|
arr[5] = ty
|
||||||
|
arr[6] = 0.0f
|
||||||
|
arr[7] = 0.0f
|
||||||
|
arr[8] = 1.0f
|
||||||
|
transform.setValues(arr)
|
||||||
|
}
|
||||||
|
obj.clipPath?.takeIf { it.isNotEmpty() }?.let {
|
||||||
|
maskPath = SVGAPathEntity(it)
|
||||||
|
}
|
||||||
|
this.shapes = obj.shapes.map {
|
||||||
|
return@map SVGAVideoShapeEntity(it)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,258 @@
|
|||||||
|
// Code generated by Wire protocol buffer compiler, do not edit.
|
||||||
|
// Source file: svga.proto at 19:1
|
||||||
|
package com.opensource.svgaplayer.proto;
|
||||||
|
|
||||||
|
import com.squareup.wire.FieldEncoding;
|
||||||
|
import com.squareup.wire.Message;
|
||||||
|
import com.squareup.wire.ProtoAdapter;
|
||||||
|
import com.squareup.wire.ProtoReader;
|
||||||
|
import com.squareup.wire.ProtoWriter;
|
||||||
|
import com.squareup.wire.WireField;
|
||||||
|
import com.squareup.wire.internal.Internal;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.lang.Integer;
|
||||||
|
import java.lang.Object;
|
||||||
|
import java.lang.Override;
|
||||||
|
import java.lang.String;
|
||||||
|
import java.lang.StringBuilder;
|
||||||
|
import okio.ByteString;
|
||||||
|
|
||||||
|
public final class AudioEntity extends Message<AudioEntity, AudioEntity.Builder> {
|
||||||
|
public static final ProtoAdapter<AudioEntity> ADAPTER = new ProtoAdapter_AudioEntity();
|
||||||
|
|
||||||
|
private static final long serialVersionUID = 0L;
|
||||||
|
|
||||||
|
public static final String DEFAULT_AUDIOKEY = "";
|
||||||
|
|
||||||
|
public static final Integer DEFAULT_STARTFRAME = 0;
|
||||||
|
|
||||||
|
public static final Integer DEFAULT_ENDFRAME = 0;
|
||||||
|
|
||||||
|
public static final Integer DEFAULT_STARTTIME = 0;
|
||||||
|
|
||||||
|
public static final Integer DEFAULT_TOTALTIME = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 音频文件名
|
||||||
|
*/
|
||||||
|
@WireField(
|
||||||
|
tag = 1,
|
||||||
|
adapter = "com.squareup.wire.ProtoAdapter#STRING"
|
||||||
|
)
|
||||||
|
public final String audioKey;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 音频播放起始帧
|
||||||
|
*/
|
||||||
|
@WireField(
|
||||||
|
tag = 2,
|
||||||
|
adapter = "com.squareup.wire.ProtoAdapter#INT32"
|
||||||
|
)
|
||||||
|
public final Integer startFrame;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 音频播放结束帧
|
||||||
|
*/
|
||||||
|
@WireField(
|
||||||
|
tag = 3,
|
||||||
|
adapter = "com.squareup.wire.ProtoAdapter#INT32"
|
||||||
|
)
|
||||||
|
public final Integer endFrame;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 音频播放起始时间(相对音频长度)
|
||||||
|
*/
|
||||||
|
@WireField(
|
||||||
|
tag = 4,
|
||||||
|
adapter = "com.squareup.wire.ProtoAdapter#INT32"
|
||||||
|
)
|
||||||
|
public final Integer startTime;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 音频总长度
|
||||||
|
*/
|
||||||
|
@WireField(
|
||||||
|
tag = 5,
|
||||||
|
adapter = "com.squareup.wire.ProtoAdapter#INT32"
|
||||||
|
)
|
||||||
|
public final Integer totalTime;
|
||||||
|
|
||||||
|
public AudioEntity(String audioKey, Integer startFrame, Integer endFrame, Integer startTime, Integer totalTime) {
|
||||||
|
this(audioKey, startFrame, endFrame, startTime, totalTime, ByteString.EMPTY);
|
||||||
|
}
|
||||||
|
|
||||||
|
public AudioEntity(String audioKey, Integer startFrame, Integer endFrame, Integer startTime, Integer totalTime, ByteString unknownFields) {
|
||||||
|
super(ADAPTER, unknownFields);
|
||||||
|
this.audioKey = audioKey;
|
||||||
|
this.startFrame = startFrame;
|
||||||
|
this.endFrame = endFrame;
|
||||||
|
this.startTime = startTime;
|
||||||
|
this.totalTime = totalTime;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Builder newBuilder() {
|
||||||
|
Builder builder = new Builder();
|
||||||
|
builder.audioKey = audioKey;
|
||||||
|
builder.startFrame = startFrame;
|
||||||
|
builder.endFrame = endFrame;
|
||||||
|
builder.startTime = startTime;
|
||||||
|
builder.totalTime = totalTime;
|
||||||
|
builder.addUnknownFields(unknownFields());
|
||||||
|
return builder;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object other) {
|
||||||
|
if (other == this) return true;
|
||||||
|
if (!(other instanceof AudioEntity)) return false;
|
||||||
|
AudioEntity o = (AudioEntity) other;
|
||||||
|
return unknownFields().equals(o.unknownFields())
|
||||||
|
&& Internal.equals(audioKey, o.audioKey)
|
||||||
|
&& Internal.equals(startFrame, o.startFrame)
|
||||||
|
&& Internal.equals(endFrame, o.endFrame)
|
||||||
|
&& Internal.equals(startTime, o.startTime)
|
||||||
|
&& Internal.equals(totalTime, o.totalTime);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int hashCode() {
|
||||||
|
int result = super.hashCode;
|
||||||
|
if (result == 0) {
|
||||||
|
result = unknownFields().hashCode();
|
||||||
|
result = result * 37 + (audioKey != null ? audioKey.hashCode() : 0);
|
||||||
|
result = result * 37 + (startFrame != null ? startFrame.hashCode() : 0);
|
||||||
|
result = result * 37 + (endFrame != null ? endFrame.hashCode() : 0);
|
||||||
|
result = result * 37 + (startTime != null ? startTime.hashCode() : 0);
|
||||||
|
result = result * 37 + (totalTime != null ? totalTime.hashCode() : 0);
|
||||||
|
super.hashCode = result;
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
StringBuilder builder = new StringBuilder();
|
||||||
|
if (audioKey != null) builder.append(", audioKey=").append(audioKey);
|
||||||
|
if (startFrame != null) builder.append(", startFrame=").append(startFrame);
|
||||||
|
if (endFrame != null) builder.append(", endFrame=").append(endFrame);
|
||||||
|
if (startTime != null) builder.append(", startTime=").append(startTime);
|
||||||
|
if (totalTime != null) builder.append(", totalTime=").append(totalTime);
|
||||||
|
return builder.replace(0, 2, "AudioEntity{").append('}').toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static final class Builder extends Message.Builder<AudioEntity, Builder> {
|
||||||
|
public String audioKey;
|
||||||
|
|
||||||
|
public Integer startFrame;
|
||||||
|
|
||||||
|
public Integer endFrame;
|
||||||
|
|
||||||
|
public Integer startTime;
|
||||||
|
|
||||||
|
public Integer totalTime;
|
||||||
|
|
||||||
|
public Builder() {
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 音频文件名
|
||||||
|
*/
|
||||||
|
public Builder audioKey(String audioKey) {
|
||||||
|
this.audioKey = audioKey;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 音频播放起始帧
|
||||||
|
*/
|
||||||
|
public Builder startFrame(Integer startFrame) {
|
||||||
|
this.startFrame = startFrame;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 音频播放结束帧
|
||||||
|
*/
|
||||||
|
public Builder endFrame(Integer endFrame) {
|
||||||
|
this.endFrame = endFrame;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 音频播放起始时间(相对音频长度)
|
||||||
|
*/
|
||||||
|
public Builder startTime(Integer startTime) {
|
||||||
|
this.startTime = startTime;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 音频总长度
|
||||||
|
*/
|
||||||
|
public Builder totalTime(Integer totalTime) {
|
||||||
|
this.totalTime = totalTime;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public AudioEntity build() {
|
||||||
|
return new AudioEntity(audioKey, startFrame, endFrame, startTime, totalTime, super.buildUnknownFields());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static final class ProtoAdapter_AudioEntity extends ProtoAdapter<AudioEntity> {
|
||||||
|
ProtoAdapter_AudioEntity() {
|
||||||
|
super(FieldEncoding.LENGTH_DELIMITED, AudioEntity.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int encodedSize(AudioEntity value) {
|
||||||
|
return (value.audioKey != null ? ProtoAdapter.STRING.encodedSizeWithTag(1, value.audioKey) : 0)
|
||||||
|
+ (value.startFrame != null ? ProtoAdapter.INT32.encodedSizeWithTag(2, value.startFrame) : 0)
|
||||||
|
+ (value.endFrame != null ? ProtoAdapter.INT32.encodedSizeWithTag(3, value.endFrame) : 0)
|
||||||
|
+ (value.startTime != null ? ProtoAdapter.INT32.encodedSizeWithTag(4, value.startTime) : 0)
|
||||||
|
+ (value.totalTime != null ? ProtoAdapter.INT32.encodedSizeWithTag(5, value.totalTime) : 0)
|
||||||
|
+ value.unknownFields().size();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void encode(ProtoWriter writer, AudioEntity value) throws IOException {
|
||||||
|
if (value.audioKey != null) ProtoAdapter.STRING.encodeWithTag(writer, 1, value.audioKey);
|
||||||
|
if (value.startFrame != null) ProtoAdapter.INT32.encodeWithTag(writer, 2, value.startFrame);
|
||||||
|
if (value.endFrame != null) ProtoAdapter.INT32.encodeWithTag(writer, 3, value.endFrame);
|
||||||
|
if (value.startTime != null) ProtoAdapter.INT32.encodeWithTag(writer, 4, value.startTime);
|
||||||
|
if (value.totalTime != null) ProtoAdapter.INT32.encodeWithTag(writer, 5, value.totalTime);
|
||||||
|
writer.writeBytes(value.unknownFields());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public AudioEntity decode(ProtoReader reader) throws IOException {
|
||||||
|
Builder builder = new Builder();
|
||||||
|
long token = reader.beginMessage();
|
||||||
|
for (int tag; (tag = reader.nextTag()) != -1;) {
|
||||||
|
switch (tag) {
|
||||||
|
case 1: builder.audioKey(ProtoAdapter.STRING.decode(reader)); break;
|
||||||
|
case 2: builder.startFrame(ProtoAdapter.INT32.decode(reader)); break;
|
||||||
|
case 3: builder.endFrame(ProtoAdapter.INT32.decode(reader)); break;
|
||||||
|
case 4: builder.startTime(ProtoAdapter.INT32.decode(reader)); break;
|
||||||
|
case 5: builder.totalTime(ProtoAdapter.INT32.decode(reader)); break;
|
||||||
|
default: {
|
||||||
|
FieldEncoding fieldEncoding = reader.peekFieldEncoding();
|
||||||
|
Object value = fieldEncoding.rawProtoAdapter().decode(reader);
|
||||||
|
builder.addUnknownField(tag, fieldEncoding, value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
reader.endMessage(token);
|
||||||
|
return builder.build();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public AudioEntity redact(AudioEntity value) {
|
||||||
|
Builder builder = value.newBuilder();
|
||||||
|
builder.clearUnknownFields();
|
||||||
|
return builder.build();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,259 @@
|
|||||||
|
// Code generated by Wire protocol buffer compiler, do not edit.
|
||||||
|
// Source file: svga.proto at 115:1
|
||||||
|
package com.opensource.svgaplayer.proto;
|
||||||
|
|
||||||
|
import com.squareup.wire.FieldEncoding;
|
||||||
|
import com.squareup.wire.Message;
|
||||||
|
import com.squareup.wire.ProtoAdapter;
|
||||||
|
import com.squareup.wire.ProtoReader;
|
||||||
|
import com.squareup.wire.ProtoWriter;
|
||||||
|
import com.squareup.wire.WireField;
|
||||||
|
import com.squareup.wire.internal.Internal;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.lang.Float;
|
||||||
|
import java.lang.Object;
|
||||||
|
import java.lang.Override;
|
||||||
|
import java.lang.String;
|
||||||
|
import java.lang.StringBuilder;
|
||||||
|
import java.util.List;
|
||||||
|
import okio.ByteString;
|
||||||
|
|
||||||
|
public final class FrameEntity extends Message<FrameEntity, FrameEntity.Builder> {
|
||||||
|
public static final ProtoAdapter<FrameEntity> ADAPTER = new ProtoAdapter_FrameEntity();
|
||||||
|
|
||||||
|
private static final long serialVersionUID = 0L;
|
||||||
|
|
||||||
|
public static final Float DEFAULT_ALPHA = 0.0f;
|
||||||
|
|
||||||
|
public static final String DEFAULT_CLIPPATH = "";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 透明度
|
||||||
|
*/
|
||||||
|
@WireField(
|
||||||
|
tag = 1,
|
||||||
|
adapter = "com.squareup.wire.ProtoAdapter#FLOAT"
|
||||||
|
)
|
||||||
|
public final Float alpha;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 初始约束大小
|
||||||
|
*/
|
||||||
|
@WireField(
|
||||||
|
tag = 2,
|
||||||
|
adapter = "com.opensource.svgaplayer.proto.Layout#ADAPTER"
|
||||||
|
)
|
||||||
|
public final Layout layout;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 2D 变换矩阵
|
||||||
|
*/
|
||||||
|
@WireField(
|
||||||
|
tag = 3,
|
||||||
|
adapter = "com.opensource.svgaplayer.proto.Transform#ADAPTER"
|
||||||
|
)
|
||||||
|
public final Transform transform;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 遮罩路径,使用 SVG 标准 Path 绘制图案进行 Mask 遮罩。
|
||||||
|
*/
|
||||||
|
@WireField(
|
||||||
|
tag = 4,
|
||||||
|
adapter = "com.squareup.wire.ProtoAdapter#STRING"
|
||||||
|
)
|
||||||
|
public final String clipPath;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 矢量元素列表
|
||||||
|
*/
|
||||||
|
@WireField(
|
||||||
|
tag = 5,
|
||||||
|
adapter = "com.opensource.svgaplayer.proto.ShapeEntity#ADAPTER",
|
||||||
|
label = WireField.Label.REPEATED
|
||||||
|
)
|
||||||
|
public final List<ShapeEntity> shapes;
|
||||||
|
|
||||||
|
public FrameEntity(Float alpha, Layout layout, Transform transform, String clipPath, List<ShapeEntity> shapes) {
|
||||||
|
this(alpha, layout, transform, clipPath, shapes, ByteString.EMPTY);
|
||||||
|
}
|
||||||
|
|
||||||
|
public FrameEntity(Float alpha, Layout layout, Transform transform, String clipPath, List<ShapeEntity> shapes, ByteString unknownFields) {
|
||||||
|
super(ADAPTER, unknownFields);
|
||||||
|
this.alpha = alpha;
|
||||||
|
this.layout = layout;
|
||||||
|
this.transform = transform;
|
||||||
|
this.clipPath = clipPath;
|
||||||
|
this.shapes = Internal.immutableCopyOf("shapes", shapes);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Builder newBuilder() {
|
||||||
|
Builder builder = new Builder();
|
||||||
|
builder.alpha = alpha;
|
||||||
|
builder.layout = layout;
|
||||||
|
builder.transform = transform;
|
||||||
|
builder.clipPath = clipPath;
|
||||||
|
builder.shapes = Internal.copyOf("shapes", shapes);
|
||||||
|
builder.addUnknownFields(unknownFields());
|
||||||
|
return builder;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object other) {
|
||||||
|
if (other == this) return true;
|
||||||
|
if (!(other instanceof FrameEntity)) return false;
|
||||||
|
FrameEntity o = (FrameEntity) other;
|
||||||
|
return unknownFields().equals(o.unknownFields())
|
||||||
|
&& Internal.equals(alpha, o.alpha)
|
||||||
|
&& Internal.equals(layout, o.layout)
|
||||||
|
&& Internal.equals(transform, o.transform)
|
||||||
|
&& Internal.equals(clipPath, o.clipPath)
|
||||||
|
&& shapes.equals(o.shapes);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int hashCode() {
|
||||||
|
int result = super.hashCode;
|
||||||
|
if (result == 0) {
|
||||||
|
result = unknownFields().hashCode();
|
||||||
|
result = result * 37 + (alpha != null ? alpha.hashCode() : 0);
|
||||||
|
result = result * 37 + (layout != null ? layout.hashCode() : 0);
|
||||||
|
result = result * 37 + (transform != null ? transform.hashCode() : 0);
|
||||||
|
result = result * 37 + (clipPath != null ? clipPath.hashCode() : 0);
|
||||||
|
result = result * 37 + shapes.hashCode();
|
||||||
|
super.hashCode = result;
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
StringBuilder builder = new StringBuilder();
|
||||||
|
if (alpha != null) builder.append(", alpha=").append(alpha);
|
||||||
|
if (layout != null) builder.append(", layout=").append(layout);
|
||||||
|
if (transform != null) builder.append(", transform=").append(transform);
|
||||||
|
if (clipPath != null) builder.append(", clipPath=").append(clipPath);
|
||||||
|
if (!shapes.isEmpty()) builder.append(", shapes=").append(shapes);
|
||||||
|
return builder.replace(0, 2, "FrameEntity{").append('}').toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static final class Builder extends Message.Builder<FrameEntity, Builder> {
|
||||||
|
public Float alpha;
|
||||||
|
|
||||||
|
public Layout layout;
|
||||||
|
|
||||||
|
public Transform transform;
|
||||||
|
|
||||||
|
public String clipPath;
|
||||||
|
|
||||||
|
public List<ShapeEntity> shapes;
|
||||||
|
|
||||||
|
public Builder() {
|
||||||
|
shapes = Internal.newMutableList();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 透明度
|
||||||
|
*/
|
||||||
|
public Builder alpha(Float alpha) {
|
||||||
|
this.alpha = alpha;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 初始约束大小
|
||||||
|
*/
|
||||||
|
public Builder layout(Layout layout) {
|
||||||
|
this.layout = layout;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 2D 变换矩阵
|
||||||
|
*/
|
||||||
|
public Builder transform(Transform transform) {
|
||||||
|
this.transform = transform;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 遮罩路径,使用 SVG 标准 Path 绘制图案进行 Mask 遮罩。
|
||||||
|
*/
|
||||||
|
public Builder clipPath(String clipPath) {
|
||||||
|
this.clipPath = clipPath;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 矢量元素列表
|
||||||
|
*/
|
||||||
|
public Builder shapes(List<ShapeEntity> shapes) {
|
||||||
|
Internal.checkElementsNotNull(shapes);
|
||||||
|
this.shapes = shapes;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public FrameEntity build() {
|
||||||
|
return new FrameEntity(alpha, layout, transform, clipPath, shapes, super.buildUnknownFields());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static final class ProtoAdapter_FrameEntity extends ProtoAdapter<FrameEntity> {
|
||||||
|
ProtoAdapter_FrameEntity() {
|
||||||
|
super(FieldEncoding.LENGTH_DELIMITED, FrameEntity.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int encodedSize(FrameEntity value) {
|
||||||
|
return (value.alpha != null ? ProtoAdapter.FLOAT.encodedSizeWithTag(1, value.alpha) : 0)
|
||||||
|
+ (value.layout != null ? Layout.ADAPTER.encodedSizeWithTag(2, value.layout) : 0)
|
||||||
|
+ (value.transform != null ? Transform.ADAPTER.encodedSizeWithTag(3, value.transform) : 0)
|
||||||
|
+ (value.clipPath != null ? ProtoAdapter.STRING.encodedSizeWithTag(4, value.clipPath) : 0)
|
||||||
|
+ ShapeEntity.ADAPTER.asRepeated().encodedSizeWithTag(5, value.shapes)
|
||||||
|
+ value.unknownFields().size();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void encode(ProtoWriter writer, FrameEntity value) throws IOException {
|
||||||
|
if (value.alpha != null) ProtoAdapter.FLOAT.encodeWithTag(writer, 1, value.alpha);
|
||||||
|
if (value.layout != null) Layout.ADAPTER.encodeWithTag(writer, 2, value.layout);
|
||||||
|
if (value.transform != null) Transform.ADAPTER.encodeWithTag(writer, 3, value.transform);
|
||||||
|
if (value.clipPath != null) ProtoAdapter.STRING.encodeWithTag(writer, 4, value.clipPath);
|
||||||
|
ShapeEntity.ADAPTER.asRepeated().encodeWithTag(writer, 5, value.shapes);
|
||||||
|
writer.writeBytes(value.unknownFields());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public FrameEntity decode(ProtoReader reader) throws IOException {
|
||||||
|
Builder builder = new Builder();
|
||||||
|
long token = reader.beginMessage();
|
||||||
|
for (int tag; (tag = reader.nextTag()) != -1;) {
|
||||||
|
switch (tag) {
|
||||||
|
case 1: builder.alpha(ProtoAdapter.FLOAT.decode(reader)); break;
|
||||||
|
case 2: builder.layout(Layout.ADAPTER.decode(reader)); break;
|
||||||
|
case 3: builder.transform(Transform.ADAPTER.decode(reader)); break;
|
||||||
|
case 4: builder.clipPath(ProtoAdapter.STRING.decode(reader)); break;
|
||||||
|
case 5: builder.shapes.add(ShapeEntity.ADAPTER.decode(reader)); break;
|
||||||
|
default: {
|
||||||
|
FieldEncoding fieldEncoding = reader.peekFieldEncoding();
|
||||||
|
Object value = fieldEncoding.rawProtoAdapter().decode(reader);
|
||||||
|
builder.addUnknownField(tag, fieldEncoding, value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
reader.endMessage(token);
|
||||||
|
return builder.build();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public FrameEntity redact(FrameEntity value) {
|
||||||
|
Builder builder = value.newBuilder();
|
||||||
|
if (builder.layout != null) builder.layout = Layout.ADAPTER.redact(builder.layout);
|
||||||
|
if (builder.transform != null) builder.transform = Transform.ADAPTER.redact(builder.transform);
|
||||||
|
Internal.redactElements(builder.shapes, ShapeEntity.ADAPTER);
|
||||||
|
builder.clearUnknownFields();
|
||||||
|
return builder.build();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,205 @@
|
|||||||
|
// Code generated by Wire protocol buffer compiler, do not edit.
|
||||||
|
// Source file: svga.proto at 27:1
|
||||||
|
package com.opensource.svgaplayer.proto;
|
||||||
|
|
||||||
|
import com.squareup.wire.FieldEncoding;
|
||||||
|
import com.squareup.wire.Message;
|
||||||
|
import com.squareup.wire.ProtoAdapter;
|
||||||
|
import com.squareup.wire.ProtoReader;
|
||||||
|
import com.squareup.wire.ProtoWriter;
|
||||||
|
import com.squareup.wire.WireField;
|
||||||
|
import com.squareup.wire.internal.Internal;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.lang.Float;
|
||||||
|
import java.lang.Object;
|
||||||
|
import java.lang.Override;
|
||||||
|
import java.lang.String;
|
||||||
|
import java.lang.StringBuilder;
|
||||||
|
import okio.ByteString;
|
||||||
|
|
||||||
|
public final class Layout extends Message<Layout, Layout.Builder> {
|
||||||
|
public static final ProtoAdapter<Layout> ADAPTER = new ProtoAdapter_Layout();
|
||||||
|
|
||||||
|
private static final long serialVersionUID = 0L;
|
||||||
|
|
||||||
|
public static final Float DEFAULT_X = 0.0f;
|
||||||
|
|
||||||
|
public static final Float DEFAULT_Y = 0.0f;
|
||||||
|
|
||||||
|
public static final Float DEFAULT_WIDTH = 0.0f;
|
||||||
|
|
||||||
|
public static final Float DEFAULT_HEIGHT = 0.0f;
|
||||||
|
|
||||||
|
@WireField(
|
||||||
|
tag = 1,
|
||||||
|
adapter = "com.squareup.wire.ProtoAdapter#FLOAT"
|
||||||
|
)
|
||||||
|
public final Float x;
|
||||||
|
|
||||||
|
@WireField(
|
||||||
|
tag = 2,
|
||||||
|
adapter = "com.squareup.wire.ProtoAdapter#FLOAT"
|
||||||
|
)
|
||||||
|
public final Float y;
|
||||||
|
|
||||||
|
@WireField(
|
||||||
|
tag = 3,
|
||||||
|
adapter = "com.squareup.wire.ProtoAdapter#FLOAT"
|
||||||
|
)
|
||||||
|
public final Float width;
|
||||||
|
|
||||||
|
@WireField(
|
||||||
|
tag = 4,
|
||||||
|
adapter = "com.squareup.wire.ProtoAdapter#FLOAT"
|
||||||
|
)
|
||||||
|
public final Float height;
|
||||||
|
|
||||||
|
public Layout(Float x, Float y, Float width, Float height) {
|
||||||
|
this(x, y, width, height, ByteString.EMPTY);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Layout(Float x, Float y, Float width, Float height, ByteString unknownFields) {
|
||||||
|
super(ADAPTER, unknownFields);
|
||||||
|
this.x = x;
|
||||||
|
this.y = y;
|
||||||
|
this.width = width;
|
||||||
|
this.height = height;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Builder newBuilder() {
|
||||||
|
Builder builder = new Builder();
|
||||||
|
builder.x = x;
|
||||||
|
builder.y = y;
|
||||||
|
builder.width = width;
|
||||||
|
builder.height = height;
|
||||||
|
builder.addUnknownFields(unknownFields());
|
||||||
|
return builder;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object other) {
|
||||||
|
if (other == this) return true;
|
||||||
|
if (!(other instanceof Layout)) return false;
|
||||||
|
Layout o = (Layout) other;
|
||||||
|
return unknownFields().equals(o.unknownFields())
|
||||||
|
&& Internal.equals(x, o.x)
|
||||||
|
&& Internal.equals(y, o.y)
|
||||||
|
&& Internal.equals(width, o.width)
|
||||||
|
&& Internal.equals(height, o.height);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int hashCode() {
|
||||||
|
int result = super.hashCode;
|
||||||
|
if (result == 0) {
|
||||||
|
result = unknownFields().hashCode();
|
||||||
|
result = result * 37 + (x != null ? x.hashCode() : 0);
|
||||||
|
result = result * 37 + (y != null ? y.hashCode() : 0);
|
||||||
|
result = result * 37 + (width != null ? width.hashCode() : 0);
|
||||||
|
result = result * 37 + (height != null ? height.hashCode() : 0);
|
||||||
|
super.hashCode = result;
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
StringBuilder builder = new StringBuilder();
|
||||||
|
if (x != null) builder.append(", x=").append(x);
|
||||||
|
if (y != null) builder.append(", y=").append(y);
|
||||||
|
if (width != null) builder.append(", width=").append(width);
|
||||||
|
if (height != null) builder.append(", height=").append(height);
|
||||||
|
return builder.replace(0, 2, "Layout{").append('}').toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static final class Builder extends Message.Builder<Layout, Builder> {
|
||||||
|
public Float x;
|
||||||
|
|
||||||
|
public Float y;
|
||||||
|
|
||||||
|
public Float width;
|
||||||
|
|
||||||
|
public Float height;
|
||||||
|
|
||||||
|
public Builder() {
|
||||||
|
}
|
||||||
|
|
||||||
|
public Builder x(Float x) {
|
||||||
|
this.x = x;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Builder y(Float y) {
|
||||||
|
this.y = y;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Builder width(Float width) {
|
||||||
|
this.width = width;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Builder height(Float height) {
|
||||||
|
this.height = height;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Layout build() {
|
||||||
|
return new Layout(x, y, width, height, super.buildUnknownFields());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static final class ProtoAdapter_Layout extends ProtoAdapter<Layout> {
|
||||||
|
ProtoAdapter_Layout() {
|
||||||
|
super(FieldEncoding.LENGTH_DELIMITED, Layout.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int encodedSize(Layout value) {
|
||||||
|
return (value.x != null ? ProtoAdapter.FLOAT.encodedSizeWithTag(1, value.x) : 0)
|
||||||
|
+ (value.y != null ? ProtoAdapter.FLOAT.encodedSizeWithTag(2, value.y) : 0)
|
||||||
|
+ (value.width != null ? ProtoAdapter.FLOAT.encodedSizeWithTag(3, value.width) : 0)
|
||||||
|
+ (value.height != null ? ProtoAdapter.FLOAT.encodedSizeWithTag(4, value.height) : 0)
|
||||||
|
+ value.unknownFields().size();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void encode(ProtoWriter writer, Layout value) throws IOException {
|
||||||
|
if (value.x != null) ProtoAdapter.FLOAT.encodeWithTag(writer, 1, value.x);
|
||||||
|
if (value.y != null) ProtoAdapter.FLOAT.encodeWithTag(writer, 2, value.y);
|
||||||
|
if (value.width != null) ProtoAdapter.FLOAT.encodeWithTag(writer, 3, value.width);
|
||||||
|
if (value.height != null) ProtoAdapter.FLOAT.encodeWithTag(writer, 4, value.height);
|
||||||
|
writer.writeBytes(value.unknownFields());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Layout decode(ProtoReader reader) throws IOException {
|
||||||
|
Builder builder = new Builder();
|
||||||
|
long token = reader.beginMessage();
|
||||||
|
for (int tag; (tag = reader.nextTag()) != -1;) {
|
||||||
|
switch (tag) {
|
||||||
|
case 1: builder.x(ProtoAdapter.FLOAT.decode(reader)); break;
|
||||||
|
case 2: builder.y(ProtoAdapter.FLOAT.decode(reader)); break;
|
||||||
|
case 3: builder.width(ProtoAdapter.FLOAT.decode(reader)); break;
|
||||||
|
case 4: builder.height(ProtoAdapter.FLOAT.decode(reader)); break;
|
||||||
|
default: {
|
||||||
|
FieldEncoding fieldEncoding = reader.peekFieldEncoding();
|
||||||
|
Object value = fieldEncoding.rawProtoAdapter().decode(reader);
|
||||||
|
builder.addUnknownField(tag, fieldEncoding, value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
reader.endMessage(token);
|
||||||
|
return builder.build();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Layout redact(Layout value) {
|
||||||
|
Builder builder = value.newBuilder();
|
||||||
|
builder.clearUnknownFields();
|
||||||
|
return builder.build();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,265 @@
|
|||||||
|
// Code generated by Wire protocol buffer compiler, do not edit.
|
||||||
|
// Source file: svga.proto at 123:1
|
||||||
|
package com.opensource.svgaplayer.proto;
|
||||||
|
|
||||||
|
import com.squareup.wire.FieldEncoding;
|
||||||
|
import com.squareup.wire.Message;
|
||||||
|
import com.squareup.wire.ProtoAdapter;
|
||||||
|
import com.squareup.wire.ProtoReader;
|
||||||
|
import com.squareup.wire.ProtoWriter;
|
||||||
|
import com.squareup.wire.WireField;
|
||||||
|
import com.squareup.wire.internal.Internal;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.lang.Object;
|
||||||
|
import java.lang.Override;
|
||||||
|
import java.lang.String;
|
||||||
|
import java.lang.StringBuilder;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import okio.ByteString;
|
||||||
|
|
||||||
|
public final class MovieEntity extends Message<MovieEntity, MovieEntity.Builder> {
|
||||||
|
public static final ProtoAdapter<MovieEntity> ADAPTER = new ProtoAdapter_MovieEntity();
|
||||||
|
|
||||||
|
private static final long serialVersionUID = 0L;
|
||||||
|
|
||||||
|
public static final String DEFAULT_VERSION = "";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* SVGA 格式版本号
|
||||||
|
*/
|
||||||
|
@WireField(
|
||||||
|
tag = 1,
|
||||||
|
adapter = "com.squareup.wire.ProtoAdapter#STRING"
|
||||||
|
)
|
||||||
|
public final String version;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 动画参数
|
||||||
|
*/
|
||||||
|
@WireField(
|
||||||
|
tag = 2,
|
||||||
|
adapter = "com.opensource.svgaplayer.proto.MovieParams#ADAPTER"
|
||||||
|
)
|
||||||
|
public final MovieParams params;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Key 是位图键名,Value 是位图文件名或二进制 PNG 数据。
|
||||||
|
*/
|
||||||
|
@WireField(
|
||||||
|
tag = 3,
|
||||||
|
keyAdapter = "com.squareup.wire.ProtoAdapter#STRING",
|
||||||
|
adapter = "com.squareup.wire.ProtoAdapter#BYTES"
|
||||||
|
)
|
||||||
|
public final Map<String, ByteString> images;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 元素列表
|
||||||
|
*/
|
||||||
|
@WireField(
|
||||||
|
tag = 4,
|
||||||
|
adapter = "com.opensource.svgaplayer.proto.SpriteEntity#ADAPTER",
|
||||||
|
label = WireField.Label.REPEATED
|
||||||
|
)
|
||||||
|
public final List<SpriteEntity> sprites;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 音频列表
|
||||||
|
*/
|
||||||
|
@WireField(
|
||||||
|
tag = 5,
|
||||||
|
adapter = "com.opensource.svgaplayer.proto.AudioEntity#ADAPTER",
|
||||||
|
label = WireField.Label.REPEATED
|
||||||
|
)
|
||||||
|
public final List<AudioEntity> audios;
|
||||||
|
|
||||||
|
public MovieEntity(String version, MovieParams params, Map<String, ByteString> images, List<SpriteEntity> sprites, List<AudioEntity> audios) {
|
||||||
|
this(version, params, images, sprites, audios, ByteString.EMPTY);
|
||||||
|
}
|
||||||
|
|
||||||
|
public MovieEntity(String version, MovieParams params, Map<String, ByteString> images, List<SpriteEntity> sprites, List<AudioEntity> audios, ByteString unknownFields) {
|
||||||
|
super(ADAPTER, unknownFields);
|
||||||
|
this.version = version;
|
||||||
|
this.params = params;
|
||||||
|
this.images = Internal.immutableCopyOf("images", images);
|
||||||
|
this.sprites = Internal.immutableCopyOf("sprites", sprites);
|
||||||
|
this.audios = Internal.immutableCopyOf("audios", audios);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Builder newBuilder() {
|
||||||
|
Builder builder = new Builder();
|
||||||
|
builder.version = version;
|
||||||
|
builder.params = params;
|
||||||
|
builder.images = Internal.copyOf("images", images);
|
||||||
|
builder.sprites = Internal.copyOf("sprites", sprites);
|
||||||
|
builder.audios = Internal.copyOf("audios", audios);
|
||||||
|
builder.addUnknownFields(unknownFields());
|
||||||
|
return builder;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object other) {
|
||||||
|
if (other == this) return true;
|
||||||
|
if (!(other instanceof MovieEntity)) return false;
|
||||||
|
MovieEntity o = (MovieEntity) other;
|
||||||
|
return unknownFields().equals(o.unknownFields())
|
||||||
|
&& Internal.equals(version, o.version)
|
||||||
|
&& Internal.equals(params, o.params)
|
||||||
|
&& images.equals(o.images)
|
||||||
|
&& sprites.equals(o.sprites)
|
||||||
|
&& audios.equals(o.audios);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int hashCode() {
|
||||||
|
int result = super.hashCode;
|
||||||
|
if (result == 0) {
|
||||||
|
result = unknownFields().hashCode();
|
||||||
|
result = result * 37 + (version != null ? version.hashCode() : 0);
|
||||||
|
result = result * 37 + (params != null ? params.hashCode() : 0);
|
||||||
|
result = result * 37 + images.hashCode();
|
||||||
|
result = result * 37 + sprites.hashCode();
|
||||||
|
result = result * 37 + audios.hashCode();
|
||||||
|
super.hashCode = result;
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
StringBuilder builder = new StringBuilder();
|
||||||
|
if (version != null) builder.append(", version=").append(version);
|
||||||
|
if (params != null) builder.append(", params=").append(params);
|
||||||
|
if (!images.isEmpty()) builder.append(", images=").append(images);
|
||||||
|
if (!sprites.isEmpty()) builder.append(", sprites=").append(sprites);
|
||||||
|
if (!audios.isEmpty()) builder.append(", audios=").append(audios);
|
||||||
|
return builder.replace(0, 2, "MovieEntity{").append('}').toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static final class Builder extends Message.Builder<MovieEntity, Builder> {
|
||||||
|
public String version;
|
||||||
|
|
||||||
|
public MovieParams params;
|
||||||
|
|
||||||
|
public Map<String, ByteString> images;
|
||||||
|
|
||||||
|
public List<SpriteEntity> sprites;
|
||||||
|
|
||||||
|
public List<AudioEntity> audios;
|
||||||
|
|
||||||
|
public Builder() {
|
||||||
|
images = Internal.newMutableMap();
|
||||||
|
sprites = Internal.newMutableList();
|
||||||
|
audios = Internal.newMutableList();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* SVGA 格式版本号
|
||||||
|
*/
|
||||||
|
public Builder version(String version) {
|
||||||
|
this.version = version;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 动画参数
|
||||||
|
*/
|
||||||
|
public Builder params(MovieParams params) {
|
||||||
|
this.params = params;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Key 是位图键名,Value 是位图文件名或二进制 PNG 数据。
|
||||||
|
*/
|
||||||
|
public Builder images(Map<String, ByteString> images) {
|
||||||
|
Internal.checkElementsNotNull(images);
|
||||||
|
this.images = images;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 元素列表
|
||||||
|
*/
|
||||||
|
public Builder sprites(List<SpriteEntity> sprites) {
|
||||||
|
Internal.checkElementsNotNull(sprites);
|
||||||
|
this.sprites = sprites;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 音频列表
|
||||||
|
*/
|
||||||
|
public Builder audios(List<AudioEntity> audios) {
|
||||||
|
Internal.checkElementsNotNull(audios);
|
||||||
|
this.audios = audios;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public MovieEntity build() {
|
||||||
|
return new MovieEntity(version, params, images, sprites, audios, super.buildUnknownFields());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static final class ProtoAdapter_MovieEntity extends ProtoAdapter<MovieEntity> {
|
||||||
|
private final ProtoAdapter<Map<String, ByteString>> images = ProtoAdapter.newMapAdapter(ProtoAdapter.STRING, ProtoAdapter.BYTES);
|
||||||
|
|
||||||
|
ProtoAdapter_MovieEntity() {
|
||||||
|
super(FieldEncoding.LENGTH_DELIMITED, MovieEntity.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int encodedSize(MovieEntity value) {
|
||||||
|
return (value.version != null ? ProtoAdapter.STRING.encodedSizeWithTag(1, value.version) : 0)
|
||||||
|
+ (value.params != null ? MovieParams.ADAPTER.encodedSizeWithTag(2, value.params) : 0)
|
||||||
|
+ images.encodedSizeWithTag(3, value.images)
|
||||||
|
+ SpriteEntity.ADAPTER.asRepeated().encodedSizeWithTag(4, value.sprites)
|
||||||
|
+ AudioEntity.ADAPTER.asRepeated().encodedSizeWithTag(5, value.audios)
|
||||||
|
+ value.unknownFields().size();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void encode(ProtoWriter writer, MovieEntity value) throws IOException {
|
||||||
|
if (value.version != null) ProtoAdapter.STRING.encodeWithTag(writer, 1, value.version);
|
||||||
|
if (value.params != null) MovieParams.ADAPTER.encodeWithTag(writer, 2, value.params);
|
||||||
|
images.encodeWithTag(writer, 3, value.images);
|
||||||
|
SpriteEntity.ADAPTER.asRepeated().encodeWithTag(writer, 4, value.sprites);
|
||||||
|
AudioEntity.ADAPTER.asRepeated().encodeWithTag(writer, 5, value.audios);
|
||||||
|
writer.writeBytes(value.unknownFields());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public MovieEntity decode(ProtoReader reader) throws IOException {
|
||||||
|
Builder builder = new Builder();
|
||||||
|
long token = reader.beginMessage();
|
||||||
|
for (int tag; (tag = reader.nextTag()) != -1;) {
|
||||||
|
switch (tag) {
|
||||||
|
case 1: builder.version(ProtoAdapter.STRING.decode(reader)); break;
|
||||||
|
case 2: builder.params(MovieParams.ADAPTER.decode(reader)); break;
|
||||||
|
case 3: builder.images.putAll(images.decode(reader)); break;
|
||||||
|
case 4: builder.sprites.add(SpriteEntity.ADAPTER.decode(reader)); break;
|
||||||
|
case 5: builder.audios.add(AudioEntity.ADAPTER.decode(reader)); break;
|
||||||
|
default: {
|
||||||
|
FieldEncoding fieldEncoding = reader.peekFieldEncoding();
|
||||||
|
Object value = fieldEncoding.rawProtoAdapter().decode(reader);
|
||||||
|
builder.addUnknownField(tag, fieldEncoding, value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
reader.endMessage(token);
|
||||||
|
return builder.build();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public MovieEntity redact(MovieEntity value) {
|
||||||
|
Builder builder = value.newBuilder();
|
||||||
|
if (builder.params != null) builder.params = MovieParams.ADAPTER.redact(builder.params);
|
||||||
|
Internal.redactElements(builder.sprites, SpriteEntity.ADAPTER);
|
||||||
|
Internal.redactElements(builder.audios, AudioEntity.ADAPTER);
|
||||||
|
builder.clearUnknownFields();
|
||||||
|
return builder.build();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,230 @@
|
|||||||
|
// Code generated by Wire protocol buffer compiler, do not edit.
|
||||||
|
// Source file: svga.proto at 6:1
|
||||||
|
package com.opensource.svgaplayer.proto;
|
||||||
|
|
||||||
|
import com.squareup.wire.FieldEncoding;
|
||||||
|
import com.squareup.wire.Message;
|
||||||
|
import com.squareup.wire.ProtoAdapter;
|
||||||
|
import com.squareup.wire.ProtoReader;
|
||||||
|
import com.squareup.wire.ProtoWriter;
|
||||||
|
import com.squareup.wire.WireField;
|
||||||
|
import com.squareup.wire.internal.Internal;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.lang.Float;
|
||||||
|
import java.lang.Integer;
|
||||||
|
import java.lang.Object;
|
||||||
|
import java.lang.Override;
|
||||||
|
import java.lang.String;
|
||||||
|
import java.lang.StringBuilder;
|
||||||
|
import okio.ByteString;
|
||||||
|
|
||||||
|
public final class MovieParams extends Message<MovieParams, MovieParams.Builder> {
|
||||||
|
public static final ProtoAdapter<MovieParams> ADAPTER = new ProtoAdapter_MovieParams();
|
||||||
|
|
||||||
|
private static final long serialVersionUID = 0L;
|
||||||
|
|
||||||
|
public static final Float DEFAULT_VIEWBOXWIDTH = 0.0f;
|
||||||
|
|
||||||
|
public static final Float DEFAULT_VIEWBOXHEIGHT = 0.0f;
|
||||||
|
|
||||||
|
public static final Integer DEFAULT_FPS = 0;
|
||||||
|
|
||||||
|
public static final Integer DEFAULT_FRAMES = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 画布宽
|
||||||
|
*/
|
||||||
|
@WireField(
|
||||||
|
tag = 1,
|
||||||
|
adapter = "com.squareup.wire.ProtoAdapter#FLOAT"
|
||||||
|
)
|
||||||
|
public final Float viewBoxWidth;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 画布高
|
||||||
|
*/
|
||||||
|
@WireField(
|
||||||
|
tag = 2,
|
||||||
|
adapter = "com.squareup.wire.ProtoAdapter#FLOAT"
|
||||||
|
)
|
||||||
|
public final Float viewBoxHeight;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 动画每秒播放帧数,合法值是 [1, 2, 3, 5, 6, 10, 12, 15, 20, 30, 60] 中的任意一个。
|
||||||
|
*/
|
||||||
|
@WireField(
|
||||||
|
tag = 3,
|
||||||
|
adapter = "com.squareup.wire.ProtoAdapter#INT32"
|
||||||
|
)
|
||||||
|
public final Integer fps;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 动画总帧数
|
||||||
|
*/
|
||||||
|
@WireField(
|
||||||
|
tag = 4,
|
||||||
|
adapter = "com.squareup.wire.ProtoAdapter#INT32"
|
||||||
|
)
|
||||||
|
public final Integer frames;
|
||||||
|
|
||||||
|
public MovieParams(Float viewBoxWidth, Float viewBoxHeight, Integer fps, Integer frames) {
|
||||||
|
this(viewBoxWidth, viewBoxHeight, fps, frames, ByteString.EMPTY);
|
||||||
|
}
|
||||||
|
|
||||||
|
public MovieParams(Float viewBoxWidth, Float viewBoxHeight, Integer fps, Integer frames, ByteString unknownFields) {
|
||||||
|
super(ADAPTER, unknownFields);
|
||||||
|
this.viewBoxWidth = viewBoxWidth;
|
||||||
|
this.viewBoxHeight = viewBoxHeight;
|
||||||
|
this.fps = fps;
|
||||||
|
this.frames = frames;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Builder newBuilder() {
|
||||||
|
Builder builder = new Builder();
|
||||||
|
builder.viewBoxWidth = viewBoxWidth;
|
||||||
|
builder.viewBoxHeight = viewBoxHeight;
|
||||||
|
builder.fps = fps;
|
||||||
|
builder.frames = frames;
|
||||||
|
builder.addUnknownFields(unknownFields());
|
||||||
|
return builder;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object other) {
|
||||||
|
if (other == this) return true;
|
||||||
|
if (!(other instanceof MovieParams)) return false;
|
||||||
|
MovieParams o = (MovieParams) other;
|
||||||
|
return unknownFields().equals(o.unknownFields())
|
||||||
|
&& Internal.equals(viewBoxWidth, o.viewBoxWidth)
|
||||||
|
&& Internal.equals(viewBoxHeight, o.viewBoxHeight)
|
||||||
|
&& Internal.equals(fps, o.fps)
|
||||||
|
&& Internal.equals(frames, o.frames);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int hashCode() {
|
||||||
|
int result = super.hashCode;
|
||||||
|
if (result == 0) {
|
||||||
|
result = unknownFields().hashCode();
|
||||||
|
result = result * 37 + (viewBoxWidth != null ? viewBoxWidth.hashCode() : 0);
|
||||||
|
result = result * 37 + (viewBoxHeight != null ? viewBoxHeight.hashCode() : 0);
|
||||||
|
result = result * 37 + (fps != null ? fps.hashCode() : 0);
|
||||||
|
result = result * 37 + (frames != null ? frames.hashCode() : 0);
|
||||||
|
super.hashCode = result;
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
StringBuilder builder = new StringBuilder();
|
||||||
|
if (viewBoxWidth != null) builder.append(", viewBoxWidth=").append(viewBoxWidth);
|
||||||
|
if (viewBoxHeight != null) builder.append(", viewBoxHeight=").append(viewBoxHeight);
|
||||||
|
if (fps != null) builder.append(", fps=").append(fps);
|
||||||
|
if (frames != null) builder.append(", frames=").append(frames);
|
||||||
|
return builder.replace(0, 2, "MovieParams{").append('}').toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static final class Builder extends Message.Builder<MovieParams, Builder> {
|
||||||
|
public Float viewBoxWidth;
|
||||||
|
|
||||||
|
public Float viewBoxHeight;
|
||||||
|
|
||||||
|
public Integer fps;
|
||||||
|
|
||||||
|
public Integer frames;
|
||||||
|
|
||||||
|
public Builder() {
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 画布宽
|
||||||
|
*/
|
||||||
|
public Builder viewBoxWidth(Float viewBoxWidth) {
|
||||||
|
this.viewBoxWidth = viewBoxWidth;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 画布高
|
||||||
|
*/
|
||||||
|
public Builder viewBoxHeight(Float viewBoxHeight) {
|
||||||
|
this.viewBoxHeight = viewBoxHeight;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 动画每秒播放帧数,合法值是 [1, 2, 3, 5, 6, 10, 12, 15, 20, 30, 60] 中的任意一个。
|
||||||
|
*/
|
||||||
|
public Builder fps(Integer fps) {
|
||||||
|
this.fps = fps;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 动画总帧数
|
||||||
|
*/
|
||||||
|
public Builder frames(Integer frames) {
|
||||||
|
this.frames = frames;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public MovieParams build() {
|
||||||
|
return new MovieParams(viewBoxWidth, viewBoxHeight, fps, frames, super.buildUnknownFields());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static final class ProtoAdapter_MovieParams extends ProtoAdapter<MovieParams> {
|
||||||
|
ProtoAdapter_MovieParams() {
|
||||||
|
super(FieldEncoding.LENGTH_DELIMITED, MovieParams.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int encodedSize(MovieParams value) {
|
||||||
|
return (value.viewBoxWidth != null ? ProtoAdapter.FLOAT.encodedSizeWithTag(1, value.viewBoxWidth) : 0)
|
||||||
|
+ (value.viewBoxHeight != null ? ProtoAdapter.FLOAT.encodedSizeWithTag(2, value.viewBoxHeight) : 0)
|
||||||
|
+ (value.fps != null ? ProtoAdapter.INT32.encodedSizeWithTag(3, value.fps) : 0)
|
||||||
|
+ (value.frames != null ? ProtoAdapter.INT32.encodedSizeWithTag(4, value.frames) : 0)
|
||||||
|
+ value.unknownFields().size();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void encode(ProtoWriter writer, MovieParams value) throws IOException {
|
||||||
|
if (value.viewBoxWidth != null) ProtoAdapter.FLOAT.encodeWithTag(writer, 1, value.viewBoxWidth);
|
||||||
|
if (value.viewBoxHeight != null) ProtoAdapter.FLOAT.encodeWithTag(writer, 2, value.viewBoxHeight);
|
||||||
|
if (value.fps != null) ProtoAdapter.INT32.encodeWithTag(writer, 3, value.fps);
|
||||||
|
if (value.frames != null) ProtoAdapter.INT32.encodeWithTag(writer, 4, value.frames);
|
||||||
|
writer.writeBytes(value.unknownFields());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public MovieParams decode(ProtoReader reader) throws IOException {
|
||||||
|
Builder builder = new Builder();
|
||||||
|
long token = reader.beginMessage();
|
||||||
|
for (int tag; (tag = reader.nextTag()) != -1;) {
|
||||||
|
switch (tag) {
|
||||||
|
case 1: builder.viewBoxWidth(ProtoAdapter.FLOAT.decode(reader)); break;
|
||||||
|
case 2: builder.viewBoxHeight(ProtoAdapter.FLOAT.decode(reader)); break;
|
||||||
|
case 3: builder.fps(ProtoAdapter.INT32.decode(reader)); break;
|
||||||
|
case 4: builder.frames(ProtoAdapter.INT32.decode(reader)); break;
|
||||||
|
default: {
|
||||||
|
FieldEncoding fieldEncoding = reader.peekFieldEncoding();
|
||||||
|
Object value = fieldEncoding.rawProtoAdapter().decode(reader);
|
||||||
|
builder.addUnknownField(tag, fieldEncoding, value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
reader.endMessage(token);
|
||||||
|
return builder.build();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public MovieParams redact(MovieParams value) {
|
||||||
|
Builder builder = value.newBuilder();
|
||||||
|
builder.clearUnknownFields();
|
||||||
|
return builder.build();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,202 @@
|
|||||||
|
// Code generated by Wire protocol buffer compiler, do not edit.
|
||||||
|
// Source file: svga.proto at 13:1
|
||||||
|
package com.opensource.svgaplayer.proto;
|
||||||
|
|
||||||
|
import com.squareup.wire.FieldEncoding;
|
||||||
|
import com.squareup.wire.Message;
|
||||||
|
import com.squareup.wire.ProtoAdapter;
|
||||||
|
import com.squareup.wire.ProtoReader;
|
||||||
|
import com.squareup.wire.ProtoWriter;
|
||||||
|
import com.squareup.wire.WireField;
|
||||||
|
import com.squareup.wire.internal.Internal;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.lang.Object;
|
||||||
|
import java.lang.Override;
|
||||||
|
import java.lang.String;
|
||||||
|
import java.lang.StringBuilder;
|
||||||
|
import java.util.List;
|
||||||
|
import okio.ByteString;
|
||||||
|
|
||||||
|
public final class SpriteEntity extends Message<SpriteEntity, SpriteEntity.Builder> {
|
||||||
|
public static final ProtoAdapter<SpriteEntity> ADAPTER = new ProtoAdapter_SpriteEntity();
|
||||||
|
|
||||||
|
private static final long serialVersionUID = 0L;
|
||||||
|
|
||||||
|
public static final String DEFAULT_IMAGEKEY = "";
|
||||||
|
|
||||||
|
public static final String DEFAULT_MATTEKEY = "";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 元件所对应的位图键名, 如果 imageKey 含有 .vector 后缀,该 sprite 为矢量图层 含有 .matte 后缀,该 sprite 为遮罩图层。
|
||||||
|
*/
|
||||||
|
@WireField(
|
||||||
|
tag = 1,
|
||||||
|
adapter = "com.squareup.wire.ProtoAdapter#STRING"
|
||||||
|
)
|
||||||
|
public final String imageKey;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 帧列表
|
||||||
|
*/
|
||||||
|
@WireField(
|
||||||
|
tag = 2,
|
||||||
|
adapter = "com.opensource.svgaplayer.proto.FrameEntity#ADAPTER",
|
||||||
|
label = WireField.Label.REPEATED
|
||||||
|
)
|
||||||
|
public final List<FrameEntity> frames;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 被遮罩图层的 matteKey 对应的是其遮罩图层的 imageKey.
|
||||||
|
*/
|
||||||
|
@WireField(
|
||||||
|
tag = 3,
|
||||||
|
adapter = "com.squareup.wire.ProtoAdapter#STRING"
|
||||||
|
)
|
||||||
|
public final String matteKey;
|
||||||
|
|
||||||
|
public SpriteEntity(String imageKey, List<FrameEntity> frames, String matteKey) {
|
||||||
|
this(imageKey, frames, matteKey, ByteString.EMPTY);
|
||||||
|
}
|
||||||
|
|
||||||
|
public SpriteEntity(String imageKey, List<FrameEntity> frames, String matteKey, ByteString unknownFields) {
|
||||||
|
super(ADAPTER, unknownFields);
|
||||||
|
this.imageKey = imageKey;
|
||||||
|
this.frames = Internal.immutableCopyOf("frames", frames);
|
||||||
|
this.matteKey = matteKey;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Builder newBuilder() {
|
||||||
|
Builder builder = new Builder();
|
||||||
|
builder.imageKey = imageKey;
|
||||||
|
builder.frames = Internal.copyOf("frames", frames);
|
||||||
|
builder.matteKey = matteKey;
|
||||||
|
builder.addUnknownFields(unknownFields());
|
||||||
|
return builder;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object other) {
|
||||||
|
if (other == this) return true;
|
||||||
|
if (!(other instanceof SpriteEntity)) return false;
|
||||||
|
SpriteEntity o = (SpriteEntity) other;
|
||||||
|
return unknownFields().equals(o.unknownFields())
|
||||||
|
&& Internal.equals(imageKey, o.imageKey)
|
||||||
|
&& frames.equals(o.frames)
|
||||||
|
&& Internal.equals(matteKey, o.matteKey);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int hashCode() {
|
||||||
|
int result = super.hashCode;
|
||||||
|
if (result == 0) {
|
||||||
|
result = unknownFields().hashCode();
|
||||||
|
result = result * 37 + (imageKey != null ? imageKey.hashCode() : 0);
|
||||||
|
result = result * 37 + frames.hashCode();
|
||||||
|
result = result * 37 + (matteKey != null ? matteKey.hashCode() : 0);
|
||||||
|
super.hashCode = result;
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
StringBuilder builder = new StringBuilder();
|
||||||
|
if (imageKey != null) builder.append(", imageKey=").append(imageKey);
|
||||||
|
if (!frames.isEmpty()) builder.append(", frames=").append(frames);
|
||||||
|
if (matteKey != null) builder.append(", matteKey=").append(matteKey);
|
||||||
|
return builder.replace(0, 2, "SpriteEntity{").append('}').toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static final class Builder extends Message.Builder<SpriteEntity, Builder> {
|
||||||
|
public String imageKey;
|
||||||
|
|
||||||
|
public List<FrameEntity> frames;
|
||||||
|
|
||||||
|
public String matteKey;
|
||||||
|
|
||||||
|
public Builder() {
|
||||||
|
frames = Internal.newMutableList();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 元件所对应的位图键名, 如果 imageKey 含有 .vector 后缀,该 sprite 为矢量图层 含有 .matte 后缀,该 sprite 为遮罩图层。
|
||||||
|
*/
|
||||||
|
public Builder imageKey(String imageKey) {
|
||||||
|
this.imageKey = imageKey;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 帧列表
|
||||||
|
*/
|
||||||
|
public Builder frames(List<FrameEntity> frames) {
|
||||||
|
Internal.checkElementsNotNull(frames);
|
||||||
|
this.frames = frames;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 被遮罩图层的 matteKey 对应的是其遮罩图层的 imageKey.
|
||||||
|
*/
|
||||||
|
public Builder matteKey(String matteKey) {
|
||||||
|
this.matteKey = matteKey;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public SpriteEntity build() {
|
||||||
|
return new SpriteEntity(imageKey, frames, matteKey, super.buildUnknownFields());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static final class ProtoAdapter_SpriteEntity extends ProtoAdapter<SpriteEntity> {
|
||||||
|
ProtoAdapter_SpriteEntity() {
|
||||||
|
super(FieldEncoding.LENGTH_DELIMITED, SpriteEntity.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int encodedSize(SpriteEntity value) {
|
||||||
|
return (value.imageKey != null ? ProtoAdapter.STRING.encodedSizeWithTag(1, value.imageKey) : 0)
|
||||||
|
+ FrameEntity.ADAPTER.asRepeated().encodedSizeWithTag(2, value.frames)
|
||||||
|
+ (value.matteKey != null ? ProtoAdapter.STRING.encodedSizeWithTag(3, value.matteKey) : 0)
|
||||||
|
+ value.unknownFields().size();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void encode(ProtoWriter writer, SpriteEntity value) throws IOException {
|
||||||
|
if (value.imageKey != null) ProtoAdapter.STRING.encodeWithTag(writer, 1, value.imageKey);
|
||||||
|
FrameEntity.ADAPTER.asRepeated().encodeWithTag(writer, 2, value.frames);
|
||||||
|
if (value.matteKey != null) ProtoAdapter.STRING.encodeWithTag(writer, 3, value.matteKey);
|
||||||
|
writer.writeBytes(value.unknownFields());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public SpriteEntity decode(ProtoReader reader) throws IOException {
|
||||||
|
Builder builder = new Builder();
|
||||||
|
long token = reader.beginMessage();
|
||||||
|
for (int tag; (tag = reader.nextTag()) != -1;) {
|
||||||
|
switch (tag) {
|
||||||
|
case 1: builder.imageKey(ProtoAdapter.STRING.decode(reader)); break;
|
||||||
|
case 2: builder.frames.add(FrameEntity.ADAPTER.decode(reader)); break;
|
||||||
|
case 3: builder.matteKey(ProtoAdapter.STRING.decode(reader)); break;
|
||||||
|
default: {
|
||||||
|
FieldEncoding fieldEncoding = reader.peekFieldEncoding();
|
||||||
|
Object value = fieldEncoding.rawProtoAdapter().decode(reader);
|
||||||
|
builder.addUnknownField(tag, fieldEncoding, value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
reader.endMessage(token);
|
||||||
|
return builder.build();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public SpriteEntity redact(SpriteEntity value) {
|
||||||
|
Builder builder = value.newBuilder();
|
||||||
|
Internal.redactElements(builder.frames, FrameEntity.ADAPTER);
|
||||||
|
builder.clearUnknownFields();
|
||||||
|
return builder.build();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,251 @@
|
|||||||
|
// Code generated by Wire protocol buffer compiler, do not edit.
|
||||||
|
// Source file: svga.proto at 34:1
|
||||||
|
package com.opensource.svgaplayer.proto;
|
||||||
|
|
||||||
|
import com.squareup.wire.FieldEncoding;
|
||||||
|
import com.squareup.wire.Message;
|
||||||
|
import com.squareup.wire.ProtoAdapter;
|
||||||
|
import com.squareup.wire.ProtoReader;
|
||||||
|
import com.squareup.wire.ProtoWriter;
|
||||||
|
import com.squareup.wire.WireField;
|
||||||
|
import com.squareup.wire.internal.Internal;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.lang.Float;
|
||||||
|
import java.lang.Object;
|
||||||
|
import java.lang.Override;
|
||||||
|
import java.lang.String;
|
||||||
|
import java.lang.StringBuilder;
|
||||||
|
import okio.ByteString;
|
||||||
|
|
||||||
|
public final class Transform extends Message<Transform, Transform.Builder> {
|
||||||
|
public static final ProtoAdapter<Transform> ADAPTER = new ProtoAdapter_Transform();
|
||||||
|
|
||||||
|
private static final long serialVersionUID = 0L;
|
||||||
|
|
||||||
|
public static final Float DEFAULT_A = 0.0f;
|
||||||
|
|
||||||
|
public static final Float DEFAULT_B = 0.0f;
|
||||||
|
|
||||||
|
public static final Float DEFAULT_C = 0.0f;
|
||||||
|
|
||||||
|
public static final Float DEFAULT_D = 0.0f;
|
||||||
|
|
||||||
|
public static final Float DEFAULT_TX = 0.0f;
|
||||||
|
|
||||||
|
public static final Float DEFAULT_TY = 0.0f;
|
||||||
|
|
||||||
|
@WireField(
|
||||||
|
tag = 1,
|
||||||
|
adapter = "com.squareup.wire.ProtoAdapter#FLOAT"
|
||||||
|
)
|
||||||
|
public final Float a;
|
||||||
|
|
||||||
|
@WireField(
|
||||||
|
tag = 2,
|
||||||
|
adapter = "com.squareup.wire.ProtoAdapter#FLOAT"
|
||||||
|
)
|
||||||
|
public final Float b;
|
||||||
|
|
||||||
|
@WireField(
|
||||||
|
tag = 3,
|
||||||
|
adapter = "com.squareup.wire.ProtoAdapter#FLOAT"
|
||||||
|
)
|
||||||
|
public final Float c;
|
||||||
|
|
||||||
|
@WireField(
|
||||||
|
tag = 4,
|
||||||
|
adapter = "com.squareup.wire.ProtoAdapter#FLOAT"
|
||||||
|
)
|
||||||
|
public final Float d;
|
||||||
|
|
||||||
|
@WireField(
|
||||||
|
tag = 5,
|
||||||
|
adapter = "com.squareup.wire.ProtoAdapter#FLOAT"
|
||||||
|
)
|
||||||
|
public final Float tx;
|
||||||
|
|
||||||
|
@WireField(
|
||||||
|
tag = 6,
|
||||||
|
adapter = "com.squareup.wire.ProtoAdapter#FLOAT"
|
||||||
|
)
|
||||||
|
public final Float ty;
|
||||||
|
|
||||||
|
public Transform(Float a, Float b, Float c, Float d, Float tx, Float ty) {
|
||||||
|
this(a, b, c, d, tx, ty, ByteString.EMPTY);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Transform(Float a, Float b, Float c, Float d, Float tx, Float ty, ByteString unknownFields) {
|
||||||
|
super(ADAPTER, unknownFields);
|
||||||
|
this.a = a;
|
||||||
|
this.b = b;
|
||||||
|
this.c = c;
|
||||||
|
this.d = d;
|
||||||
|
this.tx = tx;
|
||||||
|
this.ty = ty;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Builder newBuilder() {
|
||||||
|
Builder builder = new Builder();
|
||||||
|
builder.a = a;
|
||||||
|
builder.b = b;
|
||||||
|
builder.c = c;
|
||||||
|
builder.d = d;
|
||||||
|
builder.tx = tx;
|
||||||
|
builder.ty = ty;
|
||||||
|
builder.addUnknownFields(unknownFields());
|
||||||
|
return builder;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object other) {
|
||||||
|
if (other == this) return true;
|
||||||
|
if (!(other instanceof Transform)) return false;
|
||||||
|
Transform o = (Transform) other;
|
||||||
|
return unknownFields().equals(o.unknownFields())
|
||||||
|
&& Internal.equals(a, o.a)
|
||||||
|
&& Internal.equals(b, o.b)
|
||||||
|
&& Internal.equals(c, o.c)
|
||||||
|
&& Internal.equals(d, o.d)
|
||||||
|
&& Internal.equals(tx, o.tx)
|
||||||
|
&& Internal.equals(ty, o.ty);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int hashCode() {
|
||||||
|
int result = super.hashCode;
|
||||||
|
if (result == 0) {
|
||||||
|
result = unknownFields().hashCode();
|
||||||
|
result = result * 37 + (a != null ? a.hashCode() : 0);
|
||||||
|
result = result * 37 + (b != null ? b.hashCode() : 0);
|
||||||
|
result = result * 37 + (c != null ? c.hashCode() : 0);
|
||||||
|
result = result * 37 + (d != null ? d.hashCode() : 0);
|
||||||
|
result = result * 37 + (tx != null ? tx.hashCode() : 0);
|
||||||
|
result = result * 37 + (ty != null ? ty.hashCode() : 0);
|
||||||
|
super.hashCode = result;
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
StringBuilder builder = new StringBuilder();
|
||||||
|
if (a != null) builder.append(", a=").append(a);
|
||||||
|
if (b != null) builder.append(", b=").append(b);
|
||||||
|
if (c != null) builder.append(", c=").append(c);
|
||||||
|
if (d != null) builder.append(", d=").append(d);
|
||||||
|
if (tx != null) builder.append(", tx=").append(tx);
|
||||||
|
if (ty != null) builder.append(", ty=").append(ty);
|
||||||
|
return builder.replace(0, 2, "Transform{").append('}').toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static final class Builder extends Message.Builder<Transform, Builder> {
|
||||||
|
public Float a;
|
||||||
|
|
||||||
|
public Float b;
|
||||||
|
|
||||||
|
public Float c;
|
||||||
|
|
||||||
|
public Float d;
|
||||||
|
|
||||||
|
public Float tx;
|
||||||
|
|
||||||
|
public Float ty;
|
||||||
|
|
||||||
|
public Builder() {
|
||||||
|
}
|
||||||
|
|
||||||
|
public Builder a(Float a) {
|
||||||
|
this.a = a;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Builder b(Float b) {
|
||||||
|
this.b = b;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Builder c(Float c) {
|
||||||
|
this.c = c;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Builder d(Float d) {
|
||||||
|
this.d = d;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Builder tx(Float tx) {
|
||||||
|
this.tx = tx;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Builder ty(Float ty) {
|
||||||
|
this.ty = ty;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Transform build() {
|
||||||
|
return new Transform(a, b, c, d, tx, ty, super.buildUnknownFields());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static final class ProtoAdapter_Transform extends ProtoAdapter<Transform> {
|
||||||
|
ProtoAdapter_Transform() {
|
||||||
|
super(FieldEncoding.LENGTH_DELIMITED, Transform.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int encodedSize(Transform value) {
|
||||||
|
return (value.a != null ? ProtoAdapter.FLOAT.encodedSizeWithTag(1, value.a) : 0)
|
||||||
|
+ (value.b != null ? ProtoAdapter.FLOAT.encodedSizeWithTag(2, value.b) : 0)
|
||||||
|
+ (value.c != null ? ProtoAdapter.FLOAT.encodedSizeWithTag(3, value.c) : 0)
|
||||||
|
+ (value.d != null ? ProtoAdapter.FLOAT.encodedSizeWithTag(4, value.d) : 0)
|
||||||
|
+ (value.tx != null ? ProtoAdapter.FLOAT.encodedSizeWithTag(5, value.tx) : 0)
|
||||||
|
+ (value.ty != null ? ProtoAdapter.FLOAT.encodedSizeWithTag(6, value.ty) : 0)
|
||||||
|
+ value.unknownFields().size();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void encode(ProtoWriter writer, Transform value) throws IOException {
|
||||||
|
if (value.a != null) ProtoAdapter.FLOAT.encodeWithTag(writer, 1, value.a);
|
||||||
|
if (value.b != null) ProtoAdapter.FLOAT.encodeWithTag(writer, 2, value.b);
|
||||||
|
if (value.c != null) ProtoAdapter.FLOAT.encodeWithTag(writer, 3, value.c);
|
||||||
|
if (value.d != null) ProtoAdapter.FLOAT.encodeWithTag(writer, 4, value.d);
|
||||||
|
if (value.tx != null) ProtoAdapter.FLOAT.encodeWithTag(writer, 5, value.tx);
|
||||||
|
if (value.ty != null) ProtoAdapter.FLOAT.encodeWithTag(writer, 6, value.ty);
|
||||||
|
writer.writeBytes(value.unknownFields());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Transform decode(ProtoReader reader) throws IOException {
|
||||||
|
Builder builder = new Builder();
|
||||||
|
long token = reader.beginMessage();
|
||||||
|
for (int tag; (tag = reader.nextTag()) != -1;) {
|
||||||
|
switch (tag) {
|
||||||
|
case 1: builder.a(ProtoAdapter.FLOAT.decode(reader)); break;
|
||||||
|
case 2: builder.b(ProtoAdapter.FLOAT.decode(reader)); break;
|
||||||
|
case 3: builder.c(ProtoAdapter.FLOAT.decode(reader)); break;
|
||||||
|
case 4: builder.d(ProtoAdapter.FLOAT.decode(reader)); break;
|
||||||
|
case 5: builder.tx(ProtoAdapter.FLOAT.decode(reader)); break;
|
||||||
|
case 6: builder.ty(ProtoAdapter.FLOAT.decode(reader)); break;
|
||||||
|
default: {
|
||||||
|
FieldEncoding fieldEncoding = reader.peekFieldEncoding();
|
||||||
|
Object value = fieldEncoding.rawProtoAdapter().decode(reader);
|
||||||
|
builder.addUnknownField(tag, fieldEncoding, value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
reader.endMessage(token);
|
||||||
|
return builder.build();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Transform redact(Transform value) {
|
||||||
|
Builder builder = value.newBuilder();
|
||||||
|
builder.clearUnknownFields();
|
||||||
|
return builder.build();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,102 @@
|
|||||||
|
package com.opensource.svgaplayer.utils
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Helper class for creating pools of objects. An example use looks like this:
|
||||||
|
* <pre>
|
||||||
|
* public class MyPooledClass {
|
||||||
|
*
|
||||||
|
* private static final SynchronizedPool<MyPooledClass> sPool =
|
||||||
|
* new SynchronizedPool<MyPooledClass>(10);
|
||||||
|
*
|
||||||
|
* public static MyPooledClass obtain() {
|
||||||
|
* MyPooledClass instance = sPool.acquire();
|
||||||
|
* return (instance != null) ? instance : new MyPooledClass();
|
||||||
|
* }
|
||||||
|
*
|
||||||
|
* public void recycle() {
|
||||||
|
* // Clear state if needed.
|
||||||
|
* sPool.release(this);
|
||||||
|
* }
|
||||||
|
*
|
||||||
|
* . . .
|
||||||
|
* }
|
||||||
|
* </pre>
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
class Pools private constructor() {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Interface for managing a pool of objects.
|
||||||
|
*
|
||||||
|
* @param <T> The pooled type.
|
||||||
|
*/
|
||||||
|
interface Pool<T> {
|
||||||
|
/**
|
||||||
|
* @return An instance from the pool if such, null otherwise.
|
||||||
|
*/
|
||||||
|
fun acquire(): T?
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Release an instance to the pool.
|
||||||
|
*
|
||||||
|
* @param instance The instance to release.
|
||||||
|
* @return Whether the instance was put in the pool.
|
||||||
|
*
|
||||||
|
* @throws IllegalStateException If the instance is already in the pool.
|
||||||
|
*/
|
||||||
|
fun release(instance: T): Boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Simple (non-synchronized) pool of objects.
|
||||||
|
*
|
||||||
|
* @param maxPoolSize The max pool size.
|
||||||
|
*
|
||||||
|
* @throws IllegalArgumentException If the max pool size is less than zero.
|
||||||
|
*
|
||||||
|
* @param <T> The pooled type.
|
||||||
|
*/
|
||||||
|
open class SimplePool<T>(maxPoolSize: Int) : Pool<T> {
|
||||||
|
private val mPool: Array<Any?>
|
||||||
|
private var mPoolSize = 0
|
||||||
|
|
||||||
|
init {
|
||||||
|
require(maxPoolSize > 0) { "The max pool size must be > 0" }
|
||||||
|
mPool = arrayOfNulls(maxPoolSize)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Suppress("UNCHECKED_CAST")
|
||||||
|
override fun acquire(): T? {
|
||||||
|
if (mPoolSize > 0) {
|
||||||
|
val lastPooledIndex = mPoolSize - 1
|
||||||
|
val instance = mPool[lastPooledIndex] as T?
|
||||||
|
mPool[lastPooledIndex] = null
|
||||||
|
mPoolSize--
|
||||||
|
return instance
|
||||||
|
}
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun release(instance: T): Boolean {
|
||||||
|
check(!isInPool(instance)) { "Already in the pool!" }
|
||||||
|
if (mPoolSize < mPool.size) {
|
||||||
|
mPool[mPoolSize] = instance
|
||||||
|
mPoolSize++
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun isInPool(instance: T): Boolean {
|
||||||
|
for (i in 0 until mPoolSize) {
|
||||||
|
if (mPool[i] === instance) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,146 @@
|
|||||||
|
package com.opensource.svgaplayer.utils
|
||||||
|
|
||||||
|
import android.widget.ImageView
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Created by ubt on 2018/1/19.
|
||||||
|
*/
|
||||||
|
class SVGAScaleInfo {
|
||||||
|
|
||||||
|
var tranFx : Float = 0.0f
|
||||||
|
var tranFy : Float = 0.0f
|
||||||
|
var scaleFx : Float = 1.0f
|
||||||
|
var scaleFy : Float = 1.0f
|
||||||
|
var ratio = 1.0f
|
||||||
|
var ratioX = false
|
||||||
|
|
||||||
|
private fun resetVar(){
|
||||||
|
tranFx = 0.0f
|
||||||
|
tranFy = 0.0f
|
||||||
|
scaleFx = 1.0f
|
||||||
|
scaleFy = 1.0f
|
||||||
|
ratio = 1.0f
|
||||||
|
ratioX = false
|
||||||
|
}
|
||||||
|
|
||||||
|
fun performScaleType(canvasWidth : Float, canvasHeight: Float, videoWidth : Float, videoHeight : Float, scaleType: ImageView.ScaleType) {
|
||||||
|
if (canvasWidth == 0.0f || canvasHeight == 0.0f || videoWidth == 0.0f || videoHeight == 0.0f) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
resetVar()
|
||||||
|
val canW_vidW_f = (canvasWidth - videoWidth) / 2.0f
|
||||||
|
val canH_vidH_f = (canvasHeight - videoHeight) / 2.0f
|
||||||
|
|
||||||
|
val videoRatio = videoWidth / videoHeight
|
||||||
|
val canvasRatio = canvasWidth / canvasHeight
|
||||||
|
|
||||||
|
val canH_d_vidH = canvasHeight / videoHeight
|
||||||
|
val canW_d_vidW = canvasWidth / videoWidth
|
||||||
|
|
||||||
|
when (scaleType) {
|
||||||
|
ImageView.ScaleType.CENTER -> {
|
||||||
|
tranFx = canW_vidW_f
|
||||||
|
tranFy = canH_vidH_f
|
||||||
|
}
|
||||||
|
ImageView.ScaleType.CENTER_CROP -> {
|
||||||
|
if (videoRatio > canvasRatio) {
|
||||||
|
ratio = canH_d_vidH
|
||||||
|
ratioX = false
|
||||||
|
scaleFx = canH_d_vidH
|
||||||
|
scaleFy = canH_d_vidH
|
||||||
|
tranFx = (canvasWidth - videoWidth * (canH_d_vidH)) / 2.0f
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
ratio = canW_d_vidW
|
||||||
|
ratioX = true
|
||||||
|
scaleFx = canW_d_vidW
|
||||||
|
scaleFy = canW_d_vidW
|
||||||
|
tranFy = (canvasHeight - videoHeight * (canW_d_vidW)) / 2.0f
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ImageView.ScaleType.CENTER_INSIDE -> {
|
||||||
|
if (videoWidth < canvasWidth && videoHeight < canvasHeight) {
|
||||||
|
tranFx = canW_vidW_f
|
||||||
|
tranFy = canH_vidH_f
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
if (videoRatio > canvasRatio) {
|
||||||
|
ratio = canW_d_vidW
|
||||||
|
ratioX = true
|
||||||
|
scaleFx = canW_d_vidW
|
||||||
|
scaleFy = canW_d_vidW
|
||||||
|
tranFy = (canvasHeight - videoHeight * (canW_d_vidW)) / 2.0f
|
||||||
|
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
ratio = canH_d_vidH
|
||||||
|
ratioX = false
|
||||||
|
scaleFx = canH_d_vidH
|
||||||
|
scaleFy = canH_d_vidH
|
||||||
|
tranFx = (canvasWidth - videoWidth * (canH_d_vidH)) / 2.0f
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ImageView.ScaleType.FIT_CENTER -> {
|
||||||
|
if (videoRatio > canvasRatio) {
|
||||||
|
ratio = canW_d_vidW
|
||||||
|
ratioX = true
|
||||||
|
scaleFx = canW_d_vidW
|
||||||
|
scaleFy = canW_d_vidW
|
||||||
|
tranFy = (canvasHeight - videoHeight * (canW_d_vidW)) / 2.0f
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
ratio = canH_d_vidH
|
||||||
|
ratioX = false
|
||||||
|
scaleFx = canH_d_vidH
|
||||||
|
scaleFy = canH_d_vidH
|
||||||
|
tranFx = (canvasWidth - videoWidth * (canH_d_vidH)) / 2.0f
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ImageView.ScaleType.FIT_START -> {
|
||||||
|
if (videoRatio > canvasRatio) {
|
||||||
|
ratio = canW_d_vidW
|
||||||
|
ratioX = true
|
||||||
|
scaleFx = canW_d_vidW
|
||||||
|
scaleFy = canW_d_vidW
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
ratio = canH_d_vidH
|
||||||
|
ratioX = false
|
||||||
|
scaleFx = canH_d_vidH
|
||||||
|
scaleFy = canH_d_vidH
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ImageView.ScaleType.FIT_END -> {
|
||||||
|
if (videoRatio > canvasRatio) {
|
||||||
|
ratio = canW_d_vidW
|
||||||
|
ratioX = true
|
||||||
|
scaleFx = canW_d_vidW
|
||||||
|
scaleFy = canW_d_vidW
|
||||||
|
tranFy= canvasHeight - videoHeight * (canW_d_vidW)
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
ratio = canH_d_vidH
|
||||||
|
ratioX = false
|
||||||
|
scaleFx = canH_d_vidH
|
||||||
|
scaleFy = canH_d_vidH
|
||||||
|
tranFx = canvasWidth - videoWidth * (canH_d_vidH)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ImageView.ScaleType.FIT_XY -> {
|
||||||
|
ratio = Math.max(canW_d_vidW, canH_d_vidH)
|
||||||
|
ratioX = canW_d_vidW > canH_d_vidH
|
||||||
|
scaleFx = canW_d_vidW
|
||||||
|
scaleFy = canH_d_vidH
|
||||||
|
}
|
||||||
|
else -> {
|
||||||
|
ratio = canW_d_vidW
|
||||||
|
ratioX = true
|
||||||
|
scaleFx = canW_d_vidW
|
||||||
|
scaleFy = canW_d_vidW
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
package com.opensource.svgaplayer.utils
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Created by cuiminghui on 2017/3/29.
|
||||||
|
*/
|
||||||
|
|
||||||
|
class SVGAPoint(val x: Float, val y: Float, val value: Float)
|
||||||
|
|
||||||
|
class SVGARect(val x: Double, val y: Double, val width: Double, val height: Double)
|
||||||
|
|
||||||
|
class SVGARange(val location: Int, val length: Int)
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
package com.opensource.svgaplayer.utils.log
|
||||||
|
|
||||||
|
import android.util.Log
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 内部默认 ILogger 接口实现
|
||||||
|
*/
|
||||||
|
class DefaultLogCat : ILogger {
|
||||||
|
override fun verbose(tag: String, msg: String) {
|
||||||
|
Log.v(tag, msg)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun info(tag: String, msg: String) {
|
||||||
|
Log.i(tag, msg)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun debug(tag: String, msg: String) {
|
||||||
|
Log.d(tag, msg)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun warn(tag: String, msg: String) {
|
||||||
|
Log.w(tag, msg)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun error(tag: String, msg: String?, error: Throwable?) {
|
||||||
|
Log.e(tag, msg, error)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
package com.opensource.svgaplayer.utils.log
|
||||||
|
|
||||||
|
/**
|
||||||
|
* log 外部接管接口
|
||||||
|
**/
|
||||||
|
interface ILogger {
|
||||||
|
fun verbose(tag: String, msg: String)
|
||||||
|
fun info(tag: String, msg: String)
|
||||||
|
fun debug(tag: String, msg: String)
|
||||||
|
fun warn(tag: String, msg: String)
|
||||||
|
fun error(tag: String, msg: String?, error: Throwable?)
|
||||||
|
}
|
||||||
@@ -0,0 +1,57 @@
|
|||||||
|
package com.opensource.svgaplayer.utils.log
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 日志输出
|
||||||
|
*/
|
||||||
|
internal object LogUtils {
|
||||||
|
private const val TAG = "SVGALog"
|
||||||
|
|
||||||
|
fun verbose(tag: String = TAG, msg: String) {
|
||||||
|
if (!SVGALogger.isLogEnabled()) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
SVGALogger.getSVGALogger()?.verbose(tag, msg)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun info(tag: String = TAG, msg: String) {
|
||||||
|
if (!SVGALogger.isLogEnabled()) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
SVGALogger.getSVGALogger()?.info(tag, msg)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun debug(tag: String = TAG, msg: String) {
|
||||||
|
if (!SVGALogger.isLogEnabled()) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
SVGALogger.getSVGALogger()?.debug(tag, msg)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun warn(tag: String = TAG, msg: String) {
|
||||||
|
if (!SVGALogger.isLogEnabled()) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
SVGALogger.getSVGALogger()?.warn(tag, msg)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun error(tag: String = TAG, msg: String) {
|
||||||
|
if (!SVGALogger.isLogEnabled()) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
SVGALogger.getSVGALogger()?.error(tag, msg, null)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun error(tag: String, error: Throwable) {
|
||||||
|
if (!SVGALogger.isLogEnabled()) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
SVGALogger.getSVGALogger()?.error(tag, error.message, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun error(tag: String = TAG, msg: String, error: Throwable) {
|
||||||
|
if (!SVGALogger.isLogEnabled()) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
SVGALogger.getSVGALogger()?.error(tag, msg, error)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,40 @@
|
|||||||
|
package com.opensource.svgaplayer.utils.log
|
||||||
|
|
||||||
|
/**
|
||||||
|
* SVGA logger 配置管理
|
||||||
|
**/
|
||||||
|
object SVGALogger {
|
||||||
|
|
||||||
|
private var mLogger: ILogger? = DefaultLogCat()
|
||||||
|
private var isLogEnabled = false
|
||||||
|
|
||||||
|
/**
|
||||||
|
* log 接管注入
|
||||||
|
*/
|
||||||
|
fun injectSVGALoggerImp(logImp: ILogger): SVGALogger {
|
||||||
|
mLogger = logImp
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 设置是否开启 log
|
||||||
|
*/
|
||||||
|
fun setLogEnabled(isEnabled: Boolean): SVGALogger {
|
||||||
|
isLogEnabled = isEnabled
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取当前 ILogger 实现类
|
||||||
|
*/
|
||||||
|
fun getSVGALogger(): ILogger? {
|
||||||
|
return mLogger
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 是否开启 log
|
||||||
|
*/
|
||||||
|
fun isLogEnabled(): Boolean {
|
||||||
|
return isLogEnabled
|
||||||
|
}
|
||||||
|
}
|
||||||
16
SVGAlibrary/src/main/res/values/attrs.xml
Normal file
16
SVGAlibrary/src/main/res/values/attrs.xml
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<resources>
|
||||||
|
<declare-styleable name="SVGAImageView">
|
||||||
|
<attr name="source" format="string" />
|
||||||
|
<attr name="autoPlay" format="boolean" />
|
||||||
|
<attr name="antiAlias" format="boolean" />
|
||||||
|
<attr name="loopCount" format="integer" />
|
||||||
|
<attr name="clearsAfterStop" format="boolean" />
|
||||||
|
<attr name="clearsAfterDetached" format="boolean" />
|
||||||
|
<attr name="fillMode" format="enum">
|
||||||
|
<enum name="Backward" value="0" />
|
||||||
|
<enum name="Forward" value="1" />
|
||||||
|
<enum name="Clear" value="2"/>
|
||||||
|
</attr>
|
||||||
|
</declare-styleable>
|
||||||
|
</resources>
|
||||||
3
SVGAlibrary/src/main/res/values/strings.xml
Normal file
3
SVGAlibrary/src/main/res/values/strings.xml
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
<resources>
|
||||||
|
<string name="app_name">SVGAPlayer</string>
|
||||||
|
</resources>
|
||||||
@@ -5,7 +5,7 @@ apply plugin: 'kotlin-parcelize'
|
|||||||
apply from: "../package_config.gradle"
|
apply from: "../package_config.gradle"
|
||||||
|
|
||||||
android {
|
android {
|
||||||
namespace "com.pdlive.shayu"
|
namespace "com.newpdlive.sy"
|
||||||
compileSdkVersion rootProject.ext.android.compileSdkVersion
|
compileSdkVersion rootProject.ext.android.compileSdkVersion
|
||||||
buildToolsVersion rootProject.ext.android.buildToolsVersion
|
buildToolsVersion rootProject.ext.android.buildToolsVersion
|
||||||
packagingOptions {
|
packagingOptions {
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
xmlns:tools="http://schemas.android.com/tools">
|
xmlns:tools="http://schemas.android.com/tools">
|
||||||
|
|
||||||
<queries>
|
<queries>
|
||||||
<package android:name="com.pdlive.shayu"/>
|
<package android:name="com.newpdlive.sy"/>
|
||||||
<package android:name="com.facebook.orca"/>
|
<package android:name="com.facebook.orca"/>
|
||||||
|
|
||||||
<package
|
<package
|
||||||
@@ -27,7 +27,7 @@
|
|||||||
|
|
||||||
<provider
|
<provider
|
||||||
android:name="com.facebook.FacebookContentProvider"
|
android:name="com.facebook.FacebookContentProvider"
|
||||||
android:authorities="com.facebook.app.FacebookContentProvider2011402032399020"
|
android:authorities="com.facebook.app.FacebookContentProvider2011402032399022"
|
||||||
android:exported="true" />
|
android:exported="true" />
|
||||||
<receiver
|
<receiver
|
||||||
android:name="com.yunbao.share.receiver.TwitterResultReceiver"
|
android:name="com.yunbao.share.receiver.TwitterResultReceiver"
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ import androidx.annotation.NonNull;
|
|||||||
import androidx.annotation.StringRes;
|
import androidx.annotation.StringRes;
|
||||||
import androidx.recyclerview.widget.RecyclerView;
|
import androidx.recyclerview.widget.RecyclerView;
|
||||||
|
|
||||||
import com.pdlive.shayu.R;
|
import com.newpdlive.sy.R;
|
||||||
import com.yunbao.share.ICallback;
|
import com.yunbao.share.ICallback;
|
||||||
import com.yunbao.share.bean.ShareBuilder;
|
import com.yunbao.share.bean.ShareBuilder;
|
||||||
import com.yunbao.share.platform.FacebookShare;
|
import com.yunbao.share.platform.FacebookShare;
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ import androidx.recyclerview.widget.RecyclerView;
|
|||||||
|
|
||||||
import com.lxj.xpopup.XPopup;
|
import com.lxj.xpopup.XPopup;
|
||||||
import com.makeramen.roundedimageview.RoundedImageView;
|
import com.makeramen.roundedimageview.RoundedImageView;
|
||||||
import com.pdlive.shayu.R;
|
import com.newpdlive.sy.R;
|
||||||
import com.yunbao.common.CommonAppConfig;
|
import com.yunbao.common.CommonAppConfig;
|
||||||
import com.yunbao.common.dialog.AbsDialogPopupWindow;
|
import com.yunbao.common.dialog.AbsDialogPopupWindow;
|
||||||
import com.yunbao.common.utils.DialogUitl;
|
import com.yunbao.common.utils.DialogUitl;
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ import androidx.recyclerview.widget.RecyclerView;
|
|||||||
|
|
||||||
import com.lxj.xpopup.XPopup;
|
import com.lxj.xpopup.XPopup;
|
||||||
import com.makeramen.roundedimageview.RoundedImageView;
|
import com.makeramen.roundedimageview.RoundedImageView;
|
||||||
import com.pdlive.shayu.R;
|
import com.newpdlive.sy.R;
|
||||||
import com.yunbao.common.dialog.AbsDialogPopupWindow;
|
import com.yunbao.common.dialog.AbsDialogPopupWindow;
|
||||||
import com.yunbao.common.glide.ImgLoader;
|
import com.yunbao.common.glide.ImgLoader;
|
||||||
import com.yunbao.common.utils.StringUtil;
|
import com.yunbao.common.utils.StringUtil;
|
||||||
|
|||||||
@@ -1,8 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<resources>
|
|
||||||
<string name="dialog_share_title">Share</string>
|
|
||||||
<string name="dialog_share_info">Come and watch %s live on PDLIVE and meet more interesting people!</string>
|
|
||||||
<string name="dialog_invite_title">Invite Friends</string>
|
|
||||||
<string name="dialog_invite_info">Come to PDLIVE to discover more and better live streams.</string>
|
|
||||||
<string name="dialog_share_copy">Copy</string>
|
|
||||||
</resources>
|
|
||||||
18
Share/src/main/res/values-zh/strings.xml
Normal file
18
Share/src/main/res/values-zh/strings.xml
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<resources>
|
||||||
|
<string name="com.twitter.sdk.android.CONSUMER_KEY" >ZWRrZnRUNlBlcHVxMXpsMzVmb2k6MTpjaQ</string>
|
||||||
|
<string name="com.twitter.sdk.android.CONSUMER_SECRET">aq0eV4R1pqMK_AAeKRWnjPr7ErGMGgTPGgZJdm73WeRY-Kluws</string>
|
||||||
|
|
||||||
|
<string name="dialog_share_title">分享</string>
|
||||||
|
<string name="dialog_share_info">快來 POYO觀看%s直播,認識更多有趣的朋友吧!</string>
|
||||||
|
<string name="dialog_share_app_facebook" >Facebook</string>
|
||||||
|
<string name="dialog_share_app_line" >Line</string>
|
||||||
|
<string name="dialog_share_app_twitter">Twitter</string>
|
||||||
|
<string name="dialog_share_app_whatsapp" >WhatsApp</string>
|
||||||
|
<string name="dialog_share_app_messenger">Messenger</string>
|
||||||
|
<string name="dialog_share_app_instagram" >Instagram</string>
|
||||||
|
|
||||||
|
<string name="dialog_invite_title">邀請好友</string>
|
||||||
|
<string name="dialog_invite_info">快來 POYO觀看直播,認識更多有趣的朋友吧!</string>
|
||||||
|
<string name="dialog_share_copy">複製</string>
|
||||||
|
</resources>
|
||||||
@@ -1,17 +1,16 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<resources>
|
<resources>
|
||||||
<string name="com.twitter.sdk.android.CONSUMER_KEY" translatable="false">ZWRrZnRUNlBlcHVxMXpsMzVmb2k6MTpjaQ</string>
|
<string name="com.twitter.sdk.android.CONSUMER_KEY" >ZWRrZnRUNlBlcHVxMXpsMzVmb2k6MTpjaQ</string>
|
||||||
<string name="com.twitter.sdk.android.CONSUMER_SECRET" translatable="false">aq0eV4R1pqMK_AAeKRWnjPr7ErGMGgTPGgZJdm73WeRY-Kluws</string>
|
<string name="com.twitter.sdk.android.CONSUMER_SECRET" >aq0eV4R1pqMK_AAeKRWnjPr7ErGMGgTPGgZJdm73WeRY-Kluws</string>
|
||||||
|
<string name="dialog_share_app_facebook" >Facebook</string>
|
||||||
<string name="dialog_share_title">分享</string>
|
<string name="dialog_share_app_line" >Line</string>
|
||||||
<string name="dialog_share_info">快來 PDLIVE觀看%s直播,認識更多有趣的朋友吧!</string>
|
<string name="dialog_share_app_twitter">Twitter</string>
|
||||||
<string name="dialog_share_app_facebook" translatable="false">Facebook</string>
|
<string name="dialog_share_app_whatsapp" >WhatsApp</string>
|
||||||
<string name="dialog_share_app_line" translatable="false">Line</string>
|
<string name="dialog_share_app_messenger">Messenger</string>
|
||||||
<string name="dialog_share_app_twitter" translatable="false">Twitter</string>
|
<string name="dialog_share_app_instagram" >Instagram</string>
|
||||||
<string name="dialog_share_app_whatsapp" translatable="false">WhatsApp</string>
|
<string name="dialog_share_title">Share</string>
|
||||||
<string name="dialog_share_app_messenger" translatable="false">Messenger</string>
|
<string name="dialog_share_info">Come and watch %s live on PoYo and meet more interesting people!</string>
|
||||||
<string name="dialog_share_app_instagram" translatable="false">Instagram</string>
|
<string name="dialog_invite_title">Invite Friends</string>
|
||||||
|
<string name="dialog_invite_info">Come to PoYo to discover more and better live streams.</string>
|
||||||
<string name="dialog_invite_title">邀請好友</string>
|
<string name="dialog_share_copy">Copy</string>
|
||||||
<string name="dialog_invite_info">快來 PDLIVE觀看直播,認識更多有趣的朋友吧!</string>
|
|
||||||
<string name="dialog_share_copy">複製</string>
|
|
||||||
</resources>
|
</resources>
|
||||||
1
TabLayout/.gitignore
vendored
Normal file
1
TabLayout/.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
/build
|
||||||
37
TabLayout/build.gradle
Normal file
37
TabLayout/build.gradle
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
apply plugin: 'com.android.library'
|
||||||
|
apply plugin: 'kotlin-android'
|
||||||
|
|
||||||
|
android {
|
||||||
|
|
||||||
|
namespace "com.angcyo.tablayout"
|
||||||
|
compileSdk rootProject.ext.android.compileSdkVersion
|
||||||
|
|
||||||
|
|
||||||
|
defaultConfig {
|
||||||
|
minSdkVersion rootProject.ext.android.minSdkVersion
|
||||||
|
targetSdkVersion rootProject.ext.android.targetSdkVersion
|
||||||
|
versionCode rootProject.ext.android.versionCode
|
||||||
|
versionName rootProject.ext.android.versionName
|
||||||
|
manifestPlaceholders = rootProject.ext.manifestPlaceholders
|
||||||
|
|
||||||
|
consumerProguardFiles 'consumer-rules.pro'
|
||||||
|
}
|
||||||
|
|
||||||
|
buildTypes {
|
||||||
|
release {
|
||||||
|
minifyEnabled false
|
||||||
|
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
compileOptions {
|
||||||
|
sourceCompatibility JavaVersion.VERSION_18
|
||||||
|
targetCompatibility JavaVersion.VERSION_18
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
dependencies {
|
||||||
|
implementation 'androidx.core:core-ktx:1.10.1'
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
//apply from: "$gradleHost/master/publish.gradle"
|
||||||
21
TabLayout/proguard-rules.pro
vendored
Normal file
21
TabLayout/proguard-rules.pro
vendored
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
# Add project specific ProGuard rules here.
|
||||||
|
# You can control the set of applied configuration files using the
|
||||||
|
# proguardFiles setting in build.gradle.
|
||||||
|
#
|
||||||
|
# For more details, see
|
||||||
|
# http://developer.android.com/guide/developing/tools/proguard.html
|
||||||
|
|
||||||
|
# If your project uses WebView with JS, uncomment the following
|
||||||
|
# and specify the fully qualified class name to the JavaScript interface
|
||||||
|
# class:
|
||||||
|
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
|
||||||
|
# public *;
|
||||||
|
#}
|
||||||
|
|
||||||
|
# Uncomment this to preserve the line number information for
|
||||||
|
# debugging stack traces.
|
||||||
|
#-keepattributes SourceFile,LineNumberTable
|
||||||
|
|
||||||
|
# If you keep the line number information, uncomment this to
|
||||||
|
# hide the original source file name.
|
||||||
|
#-renamesourcefileattribute SourceFile
|
||||||
2
TabLayout/src/main/AndroidManifest.xml
Normal file
2
TabLayout/src/main/AndroidManifest.xml
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
/>
|
||||||
200
TabLayout/src/main/java/com/angcyo/tablayout/AbsDslDrawable.kt
Normal file
200
TabLayout/src/main/java/com/angcyo/tablayout/AbsDslDrawable.kt
Normal file
@@ -0,0 +1,200 @@
|
|||||||
|
package com.angcyo.tablayout
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import android.content.res.ColorStateList
|
||||||
|
import android.graphics.*
|
||||||
|
import android.graphics.drawable.Drawable
|
||||||
|
import android.text.TextPaint
|
||||||
|
import android.util.AttributeSet
|
||||||
|
import android.view.View
|
||||||
|
import androidx.core.view.ViewCompat
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 基础自绘Drawable
|
||||||
|
* Email:angcyo@126.com
|
||||||
|
* @author angcyo
|
||||||
|
* @date 2019/11/25
|
||||||
|
* Copyright (c) 2019 ShenZhen O&M Cloud Co., Ltd. All rights reserved.
|
||||||
|
*/
|
||||||
|
|
||||||
|
abstract class AbsDslDrawable : Drawable() {
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
/**不绘制*/
|
||||||
|
const val DRAW_TYPE_DRAW_NONE = 0x00
|
||||||
|
|
||||||
|
/**[android.view.View.draw]*/
|
||||||
|
const val DRAW_TYPE_DRAW_AFTER = 0x01
|
||||||
|
const val DRAW_TYPE_DRAW_BEFORE = 0x02
|
||||||
|
|
||||||
|
/**[android.view.View.onDraw]*/
|
||||||
|
const val DRAW_TYPE_ON_DRAW_AFTER = 0x04
|
||||||
|
const val DRAW_TYPE_ON_DRAW_BEFORE = 0x08
|
||||||
|
}
|
||||||
|
|
||||||
|
/**画笔*/
|
||||||
|
val textPaint: TextPaint by lazy {
|
||||||
|
TextPaint(Paint.ANTI_ALIAS_FLAG).apply {
|
||||||
|
isFilterBitmap = true
|
||||||
|
style = Paint.Style.FILL
|
||||||
|
textSize = 12 * dp
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val drawRect = Rect()
|
||||||
|
val drawRectF = RectF()
|
||||||
|
|
||||||
|
/**需要在那个方法中触发绘制*/
|
||||||
|
var drawType = DRAW_TYPE_ON_DRAW_AFTER
|
||||||
|
|
||||||
|
/**xml属性读取*/
|
||||||
|
open fun initAttribute(
|
||||||
|
context: Context,
|
||||||
|
attributeSet: AttributeSet? = null
|
||||||
|
) {
|
||||||
|
//val typedArray = context.obtainStyledAttributes(attributeSet, R.styleable.xxx)
|
||||||
|
//typedArray.recycle()
|
||||||
|
}
|
||||||
|
|
||||||
|
//<editor-fold desc="View相关方法">
|
||||||
|
|
||||||
|
/**附着的[View]*/
|
||||||
|
val attachView: View?
|
||||||
|
get() = if (callback is View) callback as? View else null
|
||||||
|
|
||||||
|
val isInEditMode: Boolean
|
||||||
|
get() = attachView?.isInEditMode ?: false
|
||||||
|
val paddingLeft: Int
|
||||||
|
get() = attachView?.paddingLeft ?: 0
|
||||||
|
val paddingRight: Int
|
||||||
|
get() = attachView?.paddingRight ?: 0
|
||||||
|
val paddingTop: Int
|
||||||
|
get() = attachView?.paddingTop ?: 0
|
||||||
|
val paddingBottom: Int
|
||||||
|
get() = attachView?.paddingBottom ?: 0
|
||||||
|
val viewHeight: Int
|
||||||
|
get() = attachView?.measuredHeight ?: 0
|
||||||
|
val viewWidth: Int
|
||||||
|
get() = attachView?.measuredWidth ?: 0
|
||||||
|
val viewDrawHeight: Int
|
||||||
|
get() = viewHeight - paddingTop - paddingBottom
|
||||||
|
val viewDrawWidth: Int
|
||||||
|
get() = viewWidth - paddingLeft - paddingRight
|
||||||
|
|
||||||
|
val isViewRtl: Boolean
|
||||||
|
get() = attachView != null && ViewCompat.getLayoutDirection(attachView!!) == ViewCompat.LAYOUT_DIRECTION_RTL
|
||||||
|
|
||||||
|
//</editor-fold desc="View相关方法">
|
||||||
|
|
||||||
|
//<editor-fold desc="基类方法">
|
||||||
|
|
||||||
|
/**核心方法, 绘制*/
|
||||||
|
override fun draw(canvas: Canvas) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getIntrinsicWidth(): Int {
|
||||||
|
return super.getIntrinsicWidth()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getMinimumWidth(): Int {
|
||||||
|
return super.getMinimumWidth()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getIntrinsicHeight(): Int {
|
||||||
|
return super.getIntrinsicHeight()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getMinimumHeight(): Int {
|
||||||
|
return super.getMinimumHeight()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun setAlpha(alpha: Int) {
|
||||||
|
if (textPaint.alpha != alpha) {
|
||||||
|
textPaint.alpha = alpha
|
||||||
|
invalidateSelf()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getAlpha(): Int {
|
||||||
|
return textPaint.alpha
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun setBounds(left: Int, top: Int, right: Int, bottom: Int) {
|
||||||
|
super.setBounds(left, top, right, bottom)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun setBounds(bounds: Rect) {
|
||||||
|
super.setBounds(bounds)
|
||||||
|
}
|
||||||
|
|
||||||
|
//不透明度
|
||||||
|
override fun getOpacity(): Int {
|
||||||
|
return if (alpha < 255) PixelFormat.TRANSLUCENT else PixelFormat.OPAQUE
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getColorFilter(): ColorFilter? {
|
||||||
|
return textPaint.colorFilter
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun setColorFilter(colorFilter: ColorFilter?) {
|
||||||
|
textPaint.colorFilter = colorFilter
|
||||||
|
invalidateSelf()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun mutate(): Drawable {
|
||||||
|
return super.mutate()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun setDither(dither: Boolean) {
|
||||||
|
textPaint.isDither = dither
|
||||||
|
invalidateSelf()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun setFilterBitmap(filter: Boolean) {
|
||||||
|
textPaint.isFilterBitmap = filter
|
||||||
|
invalidateSelf()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun isFilterBitmap(): Boolean {
|
||||||
|
return textPaint.isFilterBitmap
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onBoundsChange(bounds: Rect) {
|
||||||
|
super.onBoundsChange(bounds)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onLevelChange(level: Int): Boolean {
|
||||||
|
return super.onLevelChange(level)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onStateChange(state: IntArray): Boolean {
|
||||||
|
return super.onStateChange(state)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun setTintList(tint: ColorStateList?) {
|
||||||
|
super.setTintList(tint)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun setTintMode(tintMode: PorterDuff.Mode?) {
|
||||||
|
super.setTintMode(tintMode)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun setTintBlendMode(blendMode: BlendMode?) {
|
||||||
|
super.setTintBlendMode(blendMode)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun setHotspot(x: Float, y: Float) {
|
||||||
|
super.setHotspot(x, y)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun setHotspotBounds(left: Int, top: Int, right: Int, bottom: Int) {
|
||||||
|
super.setHotspotBounds(left, top, right, bottom)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getHotspotBounds(outRect: Rect) {
|
||||||
|
super.getHotspotBounds(outRect)
|
||||||
|
}
|
||||||
|
|
||||||
|
//</editor-fold desc="基类方法">
|
||||||
|
}
|
||||||
275
TabLayout/src/main/java/com/angcyo/tablayout/DslBadgeDrawable.kt
Normal file
275
TabLayout/src/main/java/com/angcyo/tablayout/DslBadgeDrawable.kt
Normal file
@@ -0,0 +1,275 @@
|
|||||||
|
package com.angcyo.tablayout
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import android.graphics.Canvas
|
||||||
|
import android.graphics.Color
|
||||||
|
import android.graphics.Paint
|
||||||
|
import android.text.TextUtils
|
||||||
|
import android.util.AttributeSet
|
||||||
|
import android.view.Gravity
|
||||||
|
import kotlin.math.max
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 未读数, 未读小红点, 角标绘制Drawable
|
||||||
|
* Email:angcyo@126.com
|
||||||
|
* @author angcyo
|
||||||
|
* @date 2019/12/13
|
||||||
|
* Copyright (c) 2019 ShenZhen O&M Cloud Co., Ltd. All rights reserved.
|
||||||
|
*/
|
||||||
|
open class DslBadgeDrawable : DslGradientDrawable() {
|
||||||
|
|
||||||
|
val dslGravity = DslGravity()
|
||||||
|
|
||||||
|
/**重力*/
|
||||||
|
var badgeGravity: Int = Gravity.CENTER //Gravity.TOP or Gravity.RIGHT
|
||||||
|
|
||||||
|
/**角标文本颜色*/
|
||||||
|
var badgeTextColor = Color.WHITE
|
||||||
|
|
||||||
|
/**角标的文本(默认居中绘制文本,暂不支持修改), 空字符串会绘制成小圆点
|
||||||
|
* null 不绘制角标
|
||||||
|
* "" 空字符绘制圆点
|
||||||
|
* 其他 正常绘制
|
||||||
|
* */
|
||||||
|
var badgeText: String? = null
|
||||||
|
|
||||||
|
/**角标的文本大小*/
|
||||||
|
var badgeTextSize: Float = 12 * dp
|
||||||
|
set(value) {
|
||||||
|
field = value
|
||||||
|
textPaint.textSize = field
|
||||||
|
}
|
||||||
|
|
||||||
|
/**当[badgeText]只有1个字符时, 使用圆形背景*/
|
||||||
|
var badgeAutoCircle: Boolean = true
|
||||||
|
|
||||||
|
/**圆点状态时的半径大小*/
|
||||||
|
var badgeCircleRadius = 4 * dpi
|
||||||
|
|
||||||
|
/**原点状态下, 单独配置的偏移*/
|
||||||
|
var badgeCircleOffsetX: Int = 0
|
||||||
|
var badgeCircleOffsetY: Int = 0
|
||||||
|
|
||||||
|
/**额外偏移距离, 会根据[Gravity]自动取负值*/
|
||||||
|
var badgeOffsetX: Int = 0
|
||||||
|
var badgeOffsetY: Int = 0
|
||||||
|
|
||||||
|
/**文本偏移*/
|
||||||
|
var badgeTextOffsetX: Int = 0
|
||||||
|
var badgeTextOffsetY: Int = 0
|
||||||
|
|
||||||
|
/**圆点状态时无效*/
|
||||||
|
var badgePaddingLeft = 0
|
||||||
|
var badgePaddingRight = 0
|
||||||
|
var badgePaddingTop = 0
|
||||||
|
var badgePaddingBottom = 0
|
||||||
|
|
||||||
|
/**最小的高度大小, px. 大于0生效; 圆点时属性无效*/
|
||||||
|
var badgeMinHeight = -2
|
||||||
|
|
||||||
|
/**最小的宽度大小, px. 大于0生效; 圆点时属性无效;
|
||||||
|
* -1 表示使用使用计算出来的高度值*/
|
||||||
|
var badgeMinWidth = -2
|
||||||
|
|
||||||
|
//计算属性
|
||||||
|
val textWidth: Float
|
||||||
|
get() = textPaint.textWidth(badgeText)
|
||||||
|
|
||||||
|
//最大的宽度
|
||||||
|
val maxWidth: Int
|
||||||
|
get() = max(
|
||||||
|
textWidth.toInt(),
|
||||||
|
originDrawable?.minimumWidth ?: 0
|
||||||
|
) + badgePaddingLeft + badgePaddingRight
|
||||||
|
|
||||||
|
//最大的高度
|
||||||
|
val maxHeight: Int
|
||||||
|
get() = max(
|
||||||
|
textHeight.toInt(),
|
||||||
|
originDrawable?.minimumHeight ?: 0
|
||||||
|
) + badgePaddingTop + badgePaddingBottom
|
||||||
|
|
||||||
|
val textHeight: Float
|
||||||
|
get() = textPaint.textHeight()
|
||||||
|
|
||||||
|
//原型状态
|
||||||
|
val isCircle: Boolean
|
||||||
|
get() = TextUtils.isEmpty(badgeText)
|
||||||
|
|
||||||
|
override fun initAttribute(context: Context, attributeSet: AttributeSet?) {
|
||||||
|
super.initAttribute(context, attributeSet)
|
||||||
|
updateOriginDrawable()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun draw(canvas: Canvas) {
|
||||||
|
//super.draw(canvas)
|
||||||
|
|
||||||
|
if (badgeText == null) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
with(dslGravity) {
|
||||||
|
gravity = if (isViewRtl) {
|
||||||
|
when (badgeGravity) {
|
||||||
|
Gravity.RIGHT -> {
|
||||||
|
Gravity.LEFT
|
||||||
|
}
|
||||||
|
Gravity.LEFT -> {
|
||||||
|
Gravity.RIGHT
|
||||||
|
}
|
||||||
|
else -> {
|
||||||
|
badgeGravity
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
badgeGravity
|
||||||
|
}
|
||||||
|
|
||||||
|
setGravityBounds(bounds)
|
||||||
|
|
||||||
|
if (isCircle) {
|
||||||
|
gravityOffsetX = badgeCircleOffsetX
|
||||||
|
gravityOffsetY = badgeCircleOffsetY
|
||||||
|
} else {
|
||||||
|
gravityOffsetX = badgeOffsetX
|
||||||
|
gravityOffsetY = badgeOffsetY
|
||||||
|
}
|
||||||
|
|
||||||
|
val textWidth = textPaint.textWidth(badgeText)
|
||||||
|
val textHeight = textPaint.textHeight()
|
||||||
|
|
||||||
|
val drawHeight = if (isCircle) {
|
||||||
|
badgeCircleRadius.toFloat()
|
||||||
|
} else {
|
||||||
|
val height = textHeight + badgePaddingTop + badgePaddingBottom
|
||||||
|
if (badgeMinHeight > 0) {
|
||||||
|
max(height, badgeMinHeight.toFloat())
|
||||||
|
} else {
|
||||||
|
height
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val drawWidth = if (isCircle) {
|
||||||
|
badgeCircleRadius.toFloat()
|
||||||
|
} else {
|
||||||
|
val width = textWidth + badgePaddingLeft + badgePaddingRight
|
||||||
|
if (badgeMinWidth == -1) {
|
||||||
|
max(width, drawHeight)
|
||||||
|
} else if (badgeMinWidth > 0) {
|
||||||
|
max(width, badgeMinWidth.toFloat())
|
||||||
|
} else {
|
||||||
|
width
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
applyGravity(drawWidth, drawHeight) { centerX, centerY ->
|
||||||
|
|
||||||
|
if (isCircle) {
|
||||||
|
textPaint.color = gradientSolidColor
|
||||||
|
|
||||||
|
//圆心计算
|
||||||
|
val cx: Float
|
||||||
|
val cy: Float
|
||||||
|
if (gravity.isCenter()) {
|
||||||
|
cx = centerX.toFloat()
|
||||||
|
cy = centerY.toFloat()
|
||||||
|
} else {
|
||||||
|
cx = centerX.toFloat() + _gravityOffsetX
|
||||||
|
cy = centerY.toFloat() + _gravityOffsetY
|
||||||
|
}
|
||||||
|
|
||||||
|
//绘制圆
|
||||||
|
textPaint.color = gradientSolidColor
|
||||||
|
canvas.drawCircle(
|
||||||
|
cx,
|
||||||
|
cy,
|
||||||
|
badgeCircleRadius.toFloat(),
|
||||||
|
textPaint
|
||||||
|
)
|
||||||
|
|
||||||
|
//圆的描边
|
||||||
|
if (gradientStrokeWidth > 0 && gradientStrokeColor != Color.TRANSPARENT) {
|
||||||
|
val oldWidth = textPaint.strokeWidth
|
||||||
|
val oldStyle = textPaint.style
|
||||||
|
|
||||||
|
textPaint.color = gradientStrokeColor
|
||||||
|
textPaint.strokeWidth = gradientStrokeWidth.toFloat()
|
||||||
|
textPaint.style = Paint.Style.STROKE
|
||||||
|
|
||||||
|
canvas.drawCircle(
|
||||||
|
cx,
|
||||||
|
cy,
|
||||||
|
badgeCircleRadius.toFloat(),
|
||||||
|
textPaint
|
||||||
|
)
|
||||||
|
|
||||||
|
textPaint.strokeWidth = oldWidth
|
||||||
|
textPaint.style = oldStyle
|
||||||
|
}
|
||||||
|
|
||||||
|
} else {
|
||||||
|
textPaint.color = badgeTextColor
|
||||||
|
|
||||||
|
val textDrawX: Float = centerX - textWidth / 2
|
||||||
|
val textDrawY: Float = centerY + textHeight / 2
|
||||||
|
|
||||||
|
val bgLeft = _gravityLeft
|
||||||
|
val bgTop = _gravityTop
|
||||||
|
|
||||||
|
//绘制背景
|
||||||
|
if (badgeAutoCircle && badgeText?.length == 1) {
|
||||||
|
if (gradientSolidColor != Color.TRANSPARENT) {
|
||||||
|
textPaint.color = gradientSolidColor
|
||||||
|
canvas.drawCircle(
|
||||||
|
centerX.toFloat(),
|
||||||
|
centerY.toFloat(),
|
||||||
|
max(maxWidth, maxHeight).toFloat() / 2,
|
||||||
|
textPaint
|
||||||
|
)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
originDrawable?.apply {
|
||||||
|
setBounds(
|
||||||
|
bgLeft, bgTop,
|
||||||
|
(bgLeft + drawWidth).toInt(),
|
||||||
|
(bgTop + drawHeight).toInt()
|
||||||
|
)
|
||||||
|
draw(canvas)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//绘制文本
|
||||||
|
textPaint.color = badgeTextColor
|
||||||
|
canvas.drawText(
|
||||||
|
badgeText!!,
|
||||||
|
textDrawX + badgeTextOffsetX,
|
||||||
|
textDrawY - textPaint.descent() + badgeTextOffsetY,
|
||||||
|
textPaint
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getIntrinsicWidth(): Int {
|
||||||
|
val width = if (isCircle) {
|
||||||
|
badgeCircleRadius * 2
|
||||||
|
} else if (badgeAutoCircle && badgeText?.length == 1) {
|
||||||
|
max(maxWidth, maxHeight)
|
||||||
|
} else {
|
||||||
|
maxWidth
|
||||||
|
}
|
||||||
|
return max(badgeMinWidth, width)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getIntrinsicHeight(): Int {
|
||||||
|
val height = if (isCircle) {
|
||||||
|
badgeCircleRadius * 2
|
||||||
|
} else if (badgeAutoCircle && badgeText?.length == 1) {
|
||||||
|
max(maxWidth, maxHeight)
|
||||||
|
} else {
|
||||||
|
maxHeight
|
||||||
|
}
|
||||||
|
return max(badgeMinHeight, height)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,306 @@
|
|||||||
|
package com.angcyo.tablayout
|
||||||
|
|
||||||
|
import android.content.res.ColorStateList
|
||||||
|
import android.content.res.Resources
|
||||||
|
import android.graphics.Canvas
|
||||||
|
import android.graphics.Color
|
||||||
|
import android.graphics.ColorFilter
|
||||||
|
import android.graphics.drawable.Drawable
|
||||||
|
import android.graphics.drawable.GradientDrawable
|
||||||
|
import android.os.Build
|
||||||
|
import androidx.annotation.IntDef
|
||||||
|
import java.util.*
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 用来构建GradientDrawable
|
||||||
|
* Email:angcyo@126.com
|
||||||
|
* @author angcyo
|
||||||
|
* @date 2019/11/27
|
||||||
|
* Copyright (c) 2019 ShenZhen O&M Cloud Co., Ltd. All rights reserved.
|
||||||
|
*/
|
||||||
|
open class DslGradientDrawable : AbsDslDrawable() {
|
||||||
|
|
||||||
|
/**形状*/
|
||||||
|
@Shape
|
||||||
|
var gradientShape = GradientDrawable.RECTANGLE
|
||||||
|
|
||||||
|
/**填充的颜色*/
|
||||||
|
var gradientSolidColor = Color.TRANSPARENT
|
||||||
|
|
||||||
|
/**边框的颜色*/
|
||||||
|
var gradientStrokeColor = Color.TRANSPARENT
|
||||||
|
|
||||||
|
/**边框的宽度*/
|
||||||
|
var gradientStrokeWidth = 0
|
||||||
|
|
||||||
|
/**蚂蚁线的宽度*/
|
||||||
|
var gradientDashWidth = 0f
|
||||||
|
|
||||||
|
/**蚂蚁线之间的间距*/
|
||||||
|
var gradientDashGap = 0f
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 四个角, 8个设置点的圆角信息
|
||||||
|
* 从 左上y轴->左上x轴->右上x轴->右上y轴..., 开始设置.
|
||||||
|
*/
|
||||||
|
var gradientRadii = floatArrayOf(0f, 0f, 0f, 0f, 0f, 0f, 0f, 0f)
|
||||||
|
|
||||||
|
/**颜色渐变*/
|
||||||
|
var gradientColors: IntArray? = null
|
||||||
|
var gradientColorsOffsets: FloatArray? = null
|
||||||
|
|
||||||
|
/**渐变中心点坐标*/
|
||||||
|
var gradientCenterX = 0.5f
|
||||||
|
var gradientCenterY = 0.5f
|
||||||
|
|
||||||
|
/**渐变半径, 非比例值, 是px值. [GradientDrawable.RADIAL_GRADIENT]类型才有效*/
|
||||||
|
var gradientRadius = 0.5f
|
||||||
|
|
||||||
|
/** 渐变方向, 默认从左到右 */
|
||||||
|
var gradientOrientation = GradientDrawable.Orientation.LEFT_RIGHT
|
||||||
|
|
||||||
|
/** 渐变类型 */
|
||||||
|
@GradientType
|
||||||
|
var gradientType = GradientDrawable.LINEAR_GRADIENT
|
||||||
|
|
||||||
|
/**真正绘制的[Drawable]*/
|
||||||
|
var originDrawable: Drawable? = null
|
||||||
|
|
||||||
|
/**宽度补偿*/
|
||||||
|
var gradientWidthOffset: Int = 0
|
||||||
|
|
||||||
|
/**高度补偿*/
|
||||||
|
var gradientHeightOffset: Int = 0
|
||||||
|
|
||||||
|
/**当前的配置, 是否能生成有效的[GradientDrawable]*/
|
||||||
|
open fun isValidConfig(): Boolean {
|
||||||
|
return gradientSolidColor != Color.TRANSPARENT ||
|
||||||
|
gradientStrokeColor != Color.TRANSPARENT ||
|
||||||
|
gradientColors != null
|
||||||
|
}
|
||||||
|
|
||||||
|
fun _fillRadii(array: FloatArray, radii: String?) {
|
||||||
|
if (radii.isNullOrEmpty()) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
val split = radii.split(",")
|
||||||
|
if (split.size != 8) {
|
||||||
|
throw IllegalArgumentException("radii 需要8个值.")
|
||||||
|
} else {
|
||||||
|
val dp = Resources.getSystem().displayMetrics.density
|
||||||
|
for (i in split.indices) {
|
||||||
|
array[i] = split[i].toFloat() * dp
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun fillRadii(radius: Float) {
|
||||||
|
Arrays.fill(gradientRadii, radius)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun fillRadii(radius: Int) {
|
||||||
|
_fillRadii(gradientRadii, radius.toFloat())
|
||||||
|
}
|
||||||
|
|
||||||
|
fun _fillRadii(array: FloatArray, radius: Float) {
|
||||||
|
Arrays.fill(array, radius)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun _fillRadii(array: FloatArray, radius: Int) {
|
||||||
|
_fillRadii(array, radius.toFloat())
|
||||||
|
}
|
||||||
|
|
||||||
|
fun _fillColor(colors: String?): IntArray? {
|
||||||
|
if (colors.isNullOrEmpty()) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
val split = colors.split(",")
|
||||||
|
|
||||||
|
return IntArray(split.size) {
|
||||||
|
val str = split[it]
|
||||||
|
if (str.startsWith("#")) {
|
||||||
|
Color.parseColor(str)
|
||||||
|
} else {
|
||||||
|
str.toInt()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**构建或者更新[originDrawable]*/
|
||||||
|
open fun updateOriginDrawable(): GradientDrawable? {
|
||||||
|
val drawable: GradientDrawable? = when (originDrawable) {
|
||||||
|
null -> GradientDrawable()
|
||||||
|
is GradientDrawable -> originDrawable as GradientDrawable
|
||||||
|
else -> {
|
||||||
|
null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
drawable?.apply {
|
||||||
|
bounds = this@DslGradientDrawable.bounds
|
||||||
|
|
||||||
|
shape = gradientShape
|
||||||
|
setStroke(
|
||||||
|
gradientStrokeWidth,
|
||||||
|
gradientStrokeColor,
|
||||||
|
gradientDashWidth,
|
||||||
|
gradientDashGap
|
||||||
|
)
|
||||||
|
setColor(gradientSolidColor)
|
||||||
|
cornerRadii = gradientRadii
|
||||||
|
|
||||||
|
if (gradientColors != null) {
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
|
||||||
|
setGradientCenter(
|
||||||
|
this@DslGradientDrawable.gradientCenterX,
|
||||||
|
this@DslGradientDrawable.gradientCenterY
|
||||||
|
)
|
||||||
|
}
|
||||||
|
gradientRadius = this@DslGradientDrawable.gradientRadius
|
||||||
|
gradientType = this@DslGradientDrawable.gradientType
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
|
||||||
|
orientation = gradientOrientation
|
||||||
|
}
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
|
||||||
|
setColors(gradientColors, gradientColorsOffsets)
|
||||||
|
} else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
|
||||||
|
colors = gradientColors
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
originDrawable = this
|
||||||
|
invalidateSelf()
|
||||||
|
}
|
||||||
|
|
||||||
|
return drawable
|
||||||
|
}
|
||||||
|
|
||||||
|
open fun configDrawable(config: DslGradientDrawable.() -> Unit): DslGradientDrawable {
|
||||||
|
this.config()
|
||||||
|
updateOriginDrawable()
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun draw(canvas: Canvas) {
|
||||||
|
super.draw(canvas)
|
||||||
|
originDrawable?.apply {
|
||||||
|
setBounds(
|
||||||
|
this@DslGradientDrawable.bounds.left - gradientWidthOffset / 2,
|
||||||
|
this@DslGradientDrawable.bounds.top - gradientHeightOffset / 2,
|
||||||
|
this@DslGradientDrawable.bounds.right + gradientWidthOffset / 2,
|
||||||
|
this@DslGradientDrawable.bounds.bottom + gradientHeightOffset / 2
|
||||||
|
)
|
||||||
|
draw(canvas)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//<editor-fold desc="圆角相关配置">
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 4个角, 8个点 圆角配置
|
||||||
|
*/
|
||||||
|
fun cornerRadii(radii: FloatArray) {
|
||||||
|
gradientRadii = radii
|
||||||
|
}
|
||||||
|
|
||||||
|
fun cornerRadius(radii: Float) {
|
||||||
|
Arrays.fill(gradientRadii, radii)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun cornerRadius(
|
||||||
|
leftTop: Float = 0f,
|
||||||
|
rightTop: Float = 0f,
|
||||||
|
rightBottom: Float = 0f,
|
||||||
|
leftBottom: Float = 0f
|
||||||
|
) {
|
||||||
|
gradientRadii[0] = leftTop
|
||||||
|
gradientRadii[1] = leftTop
|
||||||
|
gradientRadii[2] = rightTop
|
||||||
|
gradientRadii[3] = rightTop
|
||||||
|
gradientRadii[4] = rightBottom
|
||||||
|
gradientRadii[5] = rightBottom
|
||||||
|
gradientRadii[6] = leftBottom
|
||||||
|
gradientRadii[7] = leftBottom
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 只配置左边的圆角
|
||||||
|
*/
|
||||||
|
fun cornerRadiiLeft(radii: Float) {
|
||||||
|
gradientRadii[0] = radii
|
||||||
|
gradientRadii[1] = radii
|
||||||
|
gradientRadii[6] = radii
|
||||||
|
gradientRadii[7] = radii
|
||||||
|
}
|
||||||
|
|
||||||
|
fun cornerRadiiRight(radii: Float) {
|
||||||
|
gradientRadii[2] = radii
|
||||||
|
gradientRadii[3] = radii
|
||||||
|
gradientRadii[4] = radii
|
||||||
|
gradientRadii[5] = radii
|
||||||
|
}
|
||||||
|
|
||||||
|
fun cornerRadiiTop(radii: Float) {
|
||||||
|
gradientRadii[0] = radii
|
||||||
|
gradientRadii[1] = radii
|
||||||
|
gradientRadii[2] = radii
|
||||||
|
gradientRadii[3] = radii
|
||||||
|
}
|
||||||
|
|
||||||
|
fun cornerRadiiBottom(radii: Float) {
|
||||||
|
gradientRadii[4] = radii
|
||||||
|
gradientRadii[5] = radii
|
||||||
|
gradientRadii[6] = radii
|
||||||
|
gradientRadii[7] = radii
|
||||||
|
}
|
||||||
|
|
||||||
|
//</editor-fold desc="圆角相关配置">
|
||||||
|
|
||||||
|
//<editor-fold desc="传递属性">
|
||||||
|
override fun setColorFilter(colorFilter: ColorFilter?) {
|
||||||
|
super.setColorFilter(colorFilter)
|
||||||
|
originDrawable?.colorFilter = colorFilter
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun setTintList(tint: ColorStateList?) {
|
||||||
|
super.setTintList(tint)
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
|
||||||
|
originDrawable?.setTintList(tint)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun setState(stateSet: IntArray): Boolean {
|
||||||
|
return originDrawable?.setState(stateSet) ?: super.setState(stateSet)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getState(): IntArray {
|
||||||
|
return originDrawable?.state ?: super.getState()
|
||||||
|
}
|
||||||
|
|
||||||
|
//</editor-fold desc="传递属性">
|
||||||
|
}
|
||||||
|
|
||||||
|
@IntDef(
|
||||||
|
GradientDrawable.RECTANGLE,
|
||||||
|
GradientDrawable.OVAL,
|
||||||
|
GradientDrawable.LINE,
|
||||||
|
GradientDrawable.RING
|
||||||
|
)
|
||||||
|
@kotlin.annotation.Retention(AnnotationRetention.SOURCE)
|
||||||
|
annotation class Shape
|
||||||
|
|
||||||
|
@IntDef(
|
||||||
|
GradientDrawable.LINEAR_GRADIENT,
|
||||||
|
GradientDrawable.RADIAL_GRADIENT,
|
||||||
|
GradientDrawable.SWEEP_GRADIENT
|
||||||
|
)
|
||||||
|
@kotlin.annotation.Retention(AnnotationRetention.SOURCE)
|
||||||
|
annotation class GradientType
|
||||||
|
|
||||||
|
/**快速创建[GradientDrawable]*/
|
||||||
|
fun dslGradientDrawable(action: DslGradientDrawable.() -> Unit): GradientDrawable {
|
||||||
|
return DslGradientDrawable().run {
|
||||||
|
action()
|
||||||
|
updateOriginDrawable()!!
|
||||||
|
}
|
||||||
|
}
|
||||||
215
TabLayout/src/main/java/com/angcyo/tablayout/DslGravity.kt
Normal file
215
TabLayout/src/main/java/com/angcyo/tablayout/DslGravity.kt
Normal file
@@ -0,0 +1,215 @@
|
|||||||
|
package com.angcyo.tablayout
|
||||||
|
|
||||||
|
import android.graphics.Rect
|
||||||
|
import android.graphics.RectF
|
||||||
|
import android.view.Gravity
|
||||||
|
|
||||||
|
/**
|
||||||
|
* [android.view.Gravity] 辅助计算类
|
||||||
|
* Email:angcyo@126.com
|
||||||
|
* @author angcyo
|
||||||
|
* @date 2019/12/13
|
||||||
|
* Copyright (c) 2019 ShenZhen O&M Cloud Co., Ltd. All rights reserved.
|
||||||
|
*/
|
||||||
|
class DslGravity {
|
||||||
|
|
||||||
|
/**束缚范围*/
|
||||||
|
val gravityBounds: RectF = RectF()
|
||||||
|
|
||||||
|
/**束缚重力*/
|
||||||
|
var gravity: Int = Gravity.LEFT or Gravity.TOP
|
||||||
|
|
||||||
|
/**使用中心坐标作为定位参考, 否则就是四条边*/
|
||||||
|
var gravityRelativeCenter: Boolean = true
|
||||||
|
|
||||||
|
/**额外偏移距离, 会根据[Gravity]自动取负值*/
|
||||||
|
var gravityOffsetX: Int = 0
|
||||||
|
var gravityOffsetY: Int = 0
|
||||||
|
|
||||||
|
fun setGravityBounds(rectF: RectF) {
|
||||||
|
gravityBounds.set(rectF)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun setGravityBounds(rect: Rect) {
|
||||||
|
gravityBounds.set(rect)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun setGravityBounds(
|
||||||
|
left: Int,
|
||||||
|
top: Int,
|
||||||
|
right: Int,
|
||||||
|
bottom: Int
|
||||||
|
) {
|
||||||
|
gravityBounds.set(left.toFloat(), top.toFloat(), right.toFloat(), bottom.toFloat())
|
||||||
|
}
|
||||||
|
|
||||||
|
fun setGravityBounds(
|
||||||
|
left: Float,
|
||||||
|
top: Float,
|
||||||
|
right: Float,
|
||||||
|
bottom: Float
|
||||||
|
) {
|
||||||
|
gravityBounds.set(left, top, right, bottom)
|
||||||
|
}
|
||||||
|
|
||||||
|
//计算后的属性
|
||||||
|
var _horizontalGravity: Int = Gravity.LEFT
|
||||||
|
var _verticalGravity: Int = Gravity.TOP
|
||||||
|
var _isCenterGravity: Boolean = false
|
||||||
|
var _targetWidth = 0f
|
||||||
|
var _targetHeight = 0f
|
||||||
|
var _gravityLeft = 0
|
||||||
|
var _gravityTop = 0
|
||||||
|
var _gravityRight = 0
|
||||||
|
var _gravityBottom = 0
|
||||||
|
|
||||||
|
//根据gravity调整后的offset
|
||||||
|
var _gravityOffsetX = 0
|
||||||
|
var _gravityOffsetY = 0
|
||||||
|
|
||||||
|
/**根据[gravity]返回在[gravityBounds]中的[left] [top]位置*/
|
||||||
|
fun applyGravity(
|
||||||
|
width: Float = _targetWidth,
|
||||||
|
height: Float = _targetHeight,
|
||||||
|
callback: (centerX: Int, centerY: Int) -> Unit
|
||||||
|
) {
|
||||||
|
|
||||||
|
_targetWidth = width
|
||||||
|
_targetHeight = height
|
||||||
|
|
||||||
|
val layoutDirection = 0
|
||||||
|
val absoluteGravity = Gravity.getAbsoluteGravity(gravity, layoutDirection)
|
||||||
|
val verticalGravity = gravity and Gravity.VERTICAL_GRAVITY_MASK
|
||||||
|
val horizontalGravity = absoluteGravity and Gravity.HORIZONTAL_GRAVITY_MASK
|
||||||
|
|
||||||
|
//调整offset
|
||||||
|
_gravityOffsetX = when (horizontalGravity) {
|
||||||
|
Gravity.RIGHT -> -gravityOffsetX
|
||||||
|
Gravity.END -> -gravityOffsetX
|
||||||
|
else -> gravityOffsetX
|
||||||
|
}
|
||||||
|
_gravityOffsetY = when (verticalGravity) {
|
||||||
|
Gravity.BOTTOM -> -gravityOffsetY
|
||||||
|
else -> gravityOffsetY
|
||||||
|
}
|
||||||
|
|
||||||
|
//计算居中的坐标
|
||||||
|
val centerX = when (horizontalGravity) {
|
||||||
|
Gravity.CENTER_HORIZONTAL -> (gravityBounds.left + gravityBounds.width() / 2 + _gravityOffsetX).toInt()
|
||||||
|
Gravity.RIGHT -> (gravityBounds.right + _gravityOffsetX - if (gravityRelativeCenter) 0f else _targetWidth / 2).toInt()
|
||||||
|
Gravity.END -> (gravityBounds.right + _gravityOffsetX - if (gravityRelativeCenter) 0f else _targetWidth / 2).toInt()
|
||||||
|
else -> (gravityBounds.left + _gravityOffsetX + if (gravityRelativeCenter) 0f else _targetWidth / 2).toInt()
|
||||||
|
}
|
||||||
|
|
||||||
|
val centerY = when (verticalGravity) {
|
||||||
|
Gravity.CENTER_VERTICAL -> (gravityBounds.top + gravityBounds.height() / 2 + _gravityOffsetY).toInt()
|
||||||
|
Gravity.BOTTOM -> (gravityBounds.bottom + _gravityOffsetY - if (gravityRelativeCenter) 0f else _targetHeight / 2).toInt()
|
||||||
|
else -> (gravityBounds.top + _gravityOffsetY + if (gravityRelativeCenter) 0f else _targetHeight / 2).toInt()
|
||||||
|
}
|
||||||
|
|
||||||
|
//缓存
|
||||||
|
_horizontalGravity = horizontalGravity
|
||||||
|
_verticalGravity = verticalGravity
|
||||||
|
_isCenterGravity = horizontalGravity == Gravity.CENTER_HORIZONTAL &&
|
||||||
|
verticalGravity == Gravity.CENTER_VERTICAL
|
||||||
|
|
||||||
|
_gravityLeft = (centerX - _targetWidth / 2).toInt()
|
||||||
|
_gravityRight = (centerX + _targetWidth / 2).toInt()
|
||||||
|
_gravityTop = (centerY - _targetHeight / 2).toInt()
|
||||||
|
_gravityBottom = (centerY + _targetHeight / 2).toInt()
|
||||||
|
|
||||||
|
//回调
|
||||||
|
callback(centerX, centerY)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 默认计算出的是目标中心点坐标参考距离
|
||||||
|
* [com.angcyo.drawable.DslGravity.getGravityRelativeCenter]
|
||||||
|
* */
|
||||||
|
fun dslGravity(
|
||||||
|
rect: RectF, //计算的矩形
|
||||||
|
gravity: Int, //重力
|
||||||
|
width: Float, //放置目标的宽度
|
||||||
|
height: Float, //放置目标的高度
|
||||||
|
offsetX: Int = 0, //额外的偏移,会根据[gravity]进行左右|上下取反
|
||||||
|
offsetY: Int = 0,
|
||||||
|
callback: (dslGravity: DslGravity, centerX: Int, centerY: Int) -> Unit
|
||||||
|
): DslGravity {
|
||||||
|
val _dslGravity = DslGravity()
|
||||||
|
_dslGravity.setGravityBounds(rect)
|
||||||
|
_config(_dslGravity, gravity, width, height, offsetX, offsetY, callback)
|
||||||
|
return _dslGravity
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 默认计算出的是目标中心点坐标参考距离
|
||||||
|
* [com.angcyo.drawable.DslGravity.getGravityRelativeCenter]
|
||||||
|
* */
|
||||||
|
fun dslGravity(
|
||||||
|
rect: Rect, //计算的矩形
|
||||||
|
gravity: Int, //重力
|
||||||
|
width: Float, //放置目标的宽度
|
||||||
|
height: Float, //放置目标的高度
|
||||||
|
offsetX: Int = 0, //额外的偏移,会根据[gravity]进行左右|上下取反
|
||||||
|
offsetY: Int = 0,
|
||||||
|
callback: (dslGravity: DslGravity, centerX: Int, centerY: Int) -> Unit
|
||||||
|
): DslGravity {
|
||||||
|
val _dslGravity = DslGravity()
|
||||||
|
_dslGravity.setGravityBounds(rect)
|
||||||
|
_config(_dslGravity, gravity, width, height, offsetX, offsetY, callback)
|
||||||
|
return _dslGravity
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun _config(
|
||||||
|
_dslGravity: DslGravity,
|
||||||
|
gravity: Int, //重力
|
||||||
|
width: Float, //放置目标的宽度
|
||||||
|
height: Float, //放置目标的高度
|
||||||
|
offsetX: Int = 0, //额外的偏移,会根据[gravity]进行左右|上下取反
|
||||||
|
offsetY: Int = 0,
|
||||||
|
callback: (dslGravity: DslGravity, centerX: Int, centerY: Int) -> Unit
|
||||||
|
) {
|
||||||
|
_dslGravity.gravity = gravity
|
||||||
|
_dslGravity.gravityOffsetX = offsetX
|
||||||
|
_dslGravity.gravityOffsetY = offsetY
|
||||||
|
_dslGravity.applyGravity(width, height) { centerX, centerY ->
|
||||||
|
callback(_dslGravity, centerX, centerY)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**居中*/
|
||||||
|
fun Int.isCenter(): Boolean {
|
||||||
|
val layoutDirection = 0
|
||||||
|
val absoluteGravity = Gravity.getAbsoluteGravity(this, layoutDirection)
|
||||||
|
val verticalGravity = this and Gravity.VERTICAL_GRAVITY_MASK
|
||||||
|
val horizontalGravity = absoluteGravity and Gravity.HORIZONTAL_GRAVITY_MASK
|
||||||
|
|
||||||
|
return verticalGravity == Gravity.CENTER_VERTICAL && horizontalGravity == Gravity.CENTER_HORIZONTAL
|
||||||
|
}
|
||||||
|
|
||||||
|
fun Int.isLeft(): Boolean {
|
||||||
|
val layoutDirection = 0
|
||||||
|
val absoluteGravity = Gravity.getAbsoluteGravity(this, layoutDirection)
|
||||||
|
val horizontalGravity = absoluteGravity and Gravity.HORIZONTAL_GRAVITY_MASK
|
||||||
|
|
||||||
|
return horizontalGravity == Gravity.LEFT
|
||||||
|
}
|
||||||
|
|
||||||
|
fun Int.isRight(): Boolean {
|
||||||
|
val layoutDirection = 0
|
||||||
|
val absoluteGravity = Gravity.getAbsoluteGravity(this, layoutDirection)
|
||||||
|
val horizontalGravity = absoluteGravity and Gravity.HORIZONTAL_GRAVITY_MASK
|
||||||
|
|
||||||
|
return horizontalGravity == Gravity.RIGHT
|
||||||
|
}
|
||||||
|
|
||||||
|
fun Int.isTop(): Boolean {
|
||||||
|
val verticalGravity = this and Gravity.VERTICAL_GRAVITY_MASK
|
||||||
|
return verticalGravity == Gravity.TOP
|
||||||
|
}
|
||||||
|
|
||||||
|
fun Int.isBottom(): Boolean {
|
||||||
|
val verticalGravity = this and Gravity.VERTICAL_GRAVITY_MASK
|
||||||
|
return verticalGravity == Gravity.BOTTOM
|
||||||
|
}
|
||||||
438
TabLayout/src/main/java/com/angcyo/tablayout/DslSelector.kt
Normal file
438
TabLayout/src/main/java/com/angcyo/tablayout/DslSelector.kt
Normal file
@@ -0,0 +1,438 @@
|
|||||||
|
package com.angcyo.tablayout
|
||||||
|
|
||||||
|
import android.view.View
|
||||||
|
import android.view.ViewGroup
|
||||||
|
import android.widget.CompoundButton
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 用来操作[ViewGroup]中的[child], 支持单选, 多选, 拦截.
|
||||||
|
* 操作的都是可见性为[VISIBLE]的[View]
|
||||||
|
*
|
||||||
|
* Email:angcyo@126.com
|
||||||
|
* @author angcyo
|
||||||
|
* @date 2019/11/24
|
||||||
|
*/
|
||||||
|
|
||||||
|
open class DslSelector {
|
||||||
|
|
||||||
|
var parent: ViewGroup? = null
|
||||||
|
var dslSelectorConfig: DslSelectorConfig = DslSelectorConfig()
|
||||||
|
|
||||||
|
//可见view列表
|
||||||
|
val visibleViewList: MutableList<View> = mutableListOf()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 选中的索引列表
|
||||||
|
* */
|
||||||
|
val selectorIndexList: MutableList<Int> = mutableListOf()
|
||||||
|
get() {
|
||||||
|
field.clear()
|
||||||
|
visibleViewList.forEachIndexed { index, view ->
|
||||||
|
if (view.isSe()) {
|
||||||
|
field.add(index)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return field
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 选中的View列表
|
||||||
|
* */
|
||||||
|
val selectorViewList: MutableList<View> = mutableListOf()
|
||||||
|
get() {
|
||||||
|
field.clear()
|
||||||
|
visibleViewList.forEachIndexed { index, view ->
|
||||||
|
if (view.isSe() || index == dslSelectIndex) {
|
||||||
|
field.add(view)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return field
|
||||||
|
}
|
||||||
|
|
||||||
|
//child 点击事件处理
|
||||||
|
val _onChildClickListener = View.OnClickListener {
|
||||||
|
val index = visibleViewList.indexOf(it)
|
||||||
|
val select =
|
||||||
|
if (dslSelectorConfig.dslMultiMode || dslSelectorConfig.dslMinSelectLimit < 1) {
|
||||||
|
!it.isSe()
|
||||||
|
} else {
|
||||||
|
true
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!interceptSelector(index, select, true)) {
|
||||||
|
selector(
|
||||||
|
visibleViewList.indexOf(it),
|
||||||
|
select,
|
||||||
|
notify = true,
|
||||||
|
fromUser = true,
|
||||||
|
forceNotify = it is CompoundButton && dslSelectorConfig.dslMultiMode
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**兼容[CompoundButton]*/
|
||||||
|
val _onCheckedChangeListener = CompoundButton.OnCheckedChangeListener { buttonView, isChecked ->
|
||||||
|
buttonView.isChecked = buttonView.isSelected //恢复状态 不做任何处理, 在[OnClickListener]中处理
|
||||||
|
/*val index = visibleViewList.indexOf(buttonView)
|
||||||
|
|
||||||
|
if (interceptSelector(index, isChecked, false)) {
|
||||||
|
//拦截了此操作
|
||||||
|
buttonView.isChecked = !isChecked //恢复状态
|
||||||
|
}
|
||||||
|
|
||||||
|
val selectorViewList = selectorViewList
|
||||||
|
val sum = selectorViewList.size
|
||||||
|
//Limit 过滤
|
||||||
|
if (buttonView.isChecked) {
|
||||||
|
if (sum > dslSelectorConfig.dslMaxSelectLimit) {
|
||||||
|
//不允许选择
|
||||||
|
buttonView.isChecked = false //恢复状态
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
//取消选择, 检查是否达到了 limit
|
||||||
|
if (sum < dslSelectorConfig.dslMinSelectLimit) {
|
||||||
|
//不允许取消选择
|
||||||
|
buttonView.isChecked = true //恢复状态
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isChecked) {
|
||||||
|
//已经选中了控件
|
||||||
|
} else {
|
||||||
|
//已经取消了控件
|
||||||
|
}*/
|
||||||
|
}
|
||||||
|
|
||||||
|
/**当前选中的索引*/
|
||||||
|
var dslSelectIndex = -1
|
||||||
|
|
||||||
|
/**安装*/
|
||||||
|
fun install(viewGroup: ViewGroup, config: DslSelectorConfig.() -> Unit = {}): DslSelector {
|
||||||
|
dslSelectIndex = -1
|
||||||
|
parent = viewGroup
|
||||||
|
updateVisibleList()
|
||||||
|
dslSelectorConfig.config()
|
||||||
|
|
||||||
|
updateStyle()
|
||||||
|
updateClickListener()
|
||||||
|
|
||||||
|
if (dslSelectIndex in 0 until visibleViewList.size) {
|
||||||
|
selector(dslSelectIndex)
|
||||||
|
}
|
||||||
|
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
|
||||||
|
/**更新样式*/
|
||||||
|
fun updateStyle() {
|
||||||
|
visibleViewList.forEachIndexed { index, view ->
|
||||||
|
val selector = dslSelectIndex == index || view.isSe()
|
||||||
|
dslSelectorConfig.onStyleItemView(view, index, selector)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**更新child的点击事件*/
|
||||||
|
fun updateClickListener() {
|
||||||
|
parent?.apply {
|
||||||
|
for (i in 0 until childCount) {
|
||||||
|
getChildAt(i)?.let {
|
||||||
|
it.setOnClickListener(_onChildClickListener)
|
||||||
|
if (it is CompoundButton) {
|
||||||
|
it.setOnCheckedChangeListener(_onCheckedChangeListener)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**更新可见视图列表*/
|
||||||
|
fun updateVisibleList(): List<View> {
|
||||||
|
visibleViewList.clear()
|
||||||
|
parent?.apply {
|
||||||
|
for (i in 0 until childCount) {
|
||||||
|
getChildAt(i)?.let {
|
||||||
|
if (it.visibility == View.VISIBLE) {
|
||||||
|
visibleViewList.add(it)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (dslSelectIndex in visibleViewList.indices) {
|
||||||
|
if (!visibleViewList[dslSelectIndex].isSe()) {
|
||||||
|
visibleViewList[dslSelectIndex].setSe(true)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
//如果当前选中的索引, 不在可见视图列表中
|
||||||
|
dslSelectIndex = -1
|
||||||
|
}
|
||||||
|
return visibleViewList
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 操作单个
|
||||||
|
* @param index 操作目标的索引值
|
||||||
|
* @param select 选中 or 取消选中
|
||||||
|
* @param notify 是否需要通知事件
|
||||||
|
* @param forceNotify 是否强制通知事件.child使用[CompoundButton]时, 推荐使用
|
||||||
|
* */
|
||||||
|
fun selector(
|
||||||
|
index: Int,
|
||||||
|
select: Boolean = true,
|
||||||
|
notify: Boolean = true,
|
||||||
|
fromUser: Boolean = false,
|
||||||
|
forceNotify: Boolean = false
|
||||||
|
) {
|
||||||
|
val oldSelectorList = selectorIndexList.toList()
|
||||||
|
val lastSelectorIndex: Int? = oldSelectorList.lastOrNull()
|
||||||
|
val reselect = !dslSelectorConfig.dslMultiMode &&
|
||||||
|
oldSelectorList.isNotEmpty() &&
|
||||||
|
oldSelectorList.contains(index)
|
||||||
|
|
||||||
|
var needNotify = _selector(index, select, fromUser) || forceNotify
|
||||||
|
|
||||||
|
if (!oldSelectorList.isChange(selectorIndexList)) {
|
||||||
|
//选中项, 未改变时不通知
|
||||||
|
needNotify = false
|
||||||
|
}
|
||||||
|
|
||||||
|
if (needNotify || reselect) {
|
||||||
|
dslSelectIndex = selectorIndexList.lastOrNull() ?: -1
|
||||||
|
if (notify) {
|
||||||
|
notifySelectChange(lastSelectorIndex ?: -1, reselect, fromUser)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**选择所有
|
||||||
|
* [select] true:选择所有, false:取消所有*/
|
||||||
|
fun selectorAll(
|
||||||
|
select: Boolean = true,
|
||||||
|
notify: Boolean = true,
|
||||||
|
fromUser: Boolean = true
|
||||||
|
) {
|
||||||
|
val indexList = visibleViewList.mapIndexedTo(mutableListOf()) { index, _ ->
|
||||||
|
index
|
||||||
|
}
|
||||||
|
selector(indexList, select, notify, fromUser)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 操作多个
|
||||||
|
* @param select 选中 or 取消选中
|
||||||
|
* [selector]
|
||||||
|
* */
|
||||||
|
fun selector(
|
||||||
|
indexList: MutableList<Int>,
|
||||||
|
select: Boolean = true,
|
||||||
|
notify: Boolean = true,
|
||||||
|
fromUser: Boolean = false
|
||||||
|
) {
|
||||||
|
val oldSelectorIndexList = selectorIndexList
|
||||||
|
val lastSelectorIndex: Int? = oldSelectorIndexList.lastOrNull()
|
||||||
|
|
||||||
|
var result = false
|
||||||
|
|
||||||
|
indexList.forEach {
|
||||||
|
result = _selector(it, select, fromUser) || result
|
||||||
|
}
|
||||||
|
|
||||||
|
if (result) {
|
||||||
|
dslSelectIndex = selectorIndexList.lastOrNull() ?: -1
|
||||||
|
if (notify) {
|
||||||
|
notifySelectChange(lastSelectorIndex ?: -1, false, fromUser)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**通知事件*/
|
||||||
|
fun notifySelectChange(lastSelectorIndex: Int, reselect: Boolean, fromUser: Boolean) {
|
||||||
|
val indexSelectorList = selectorIndexList
|
||||||
|
dslSelectorConfig.onSelectViewChange(
|
||||||
|
visibleViewList.getOrNull(lastSelectorIndex),
|
||||||
|
selectorViewList,
|
||||||
|
reselect,
|
||||||
|
fromUser
|
||||||
|
)
|
||||||
|
dslSelectorConfig.onSelectIndexChange(
|
||||||
|
lastSelectorIndex,
|
||||||
|
indexSelectorList,
|
||||||
|
reselect,
|
||||||
|
fromUser
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**当前的操作是否被拦截*/
|
||||||
|
fun interceptSelector(index: Int, select: Boolean, fromUser: Boolean): Boolean {
|
||||||
|
val visibleViewList = visibleViewList
|
||||||
|
if (index !in 0 until visibleViewList.size) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return dslSelectorConfig.onSelectItemView(visibleViewList[index], index, select, fromUser)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**@return 是否发生过改变*/
|
||||||
|
fun _selector(index: Int, select: Boolean, fromUser: Boolean): Boolean {
|
||||||
|
val visibleViewList = visibleViewList
|
||||||
|
//超范围过滤
|
||||||
|
if (index !in 0 until visibleViewList.size) {
|
||||||
|
"index out of list.".logi()
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
val selectorIndexList = selectorIndexList
|
||||||
|
val selectorViewList = selectorViewList
|
||||||
|
|
||||||
|
if (selectorIndexList.isNotEmpty()) {
|
||||||
|
if (select) {
|
||||||
|
//需要选中某项
|
||||||
|
|
||||||
|
if (dslSelectorConfig.dslMultiMode) {
|
||||||
|
//多选模式
|
||||||
|
if (selectorIndexList.contains(index)) {
|
||||||
|
//已经选中
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
//单选模式
|
||||||
|
|
||||||
|
//取消之前选中
|
||||||
|
selectorIndexList.forEach {
|
||||||
|
if (it != index) {
|
||||||
|
visibleViewList[it].setSe(false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (selectorIndexList.contains(index)) {
|
||||||
|
//已经选中
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
} else {
|
||||||
|
//需要取消选中
|
||||||
|
if (!selectorIndexList.contains(index)) {
|
||||||
|
//目标已经是未选中
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//Limit 过滤
|
||||||
|
if (select) {
|
||||||
|
val sum = selectorViewList.size + 1
|
||||||
|
if (sum > dslSelectorConfig.dslMaxSelectLimit) {
|
||||||
|
//不允许选择
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
//取消选择, 检查是否达到了 limit
|
||||||
|
val sum = selectorViewList.size - 1
|
||||||
|
if (sum < dslSelectorConfig.dslMinSelectLimit) {
|
||||||
|
//不允许取消选择
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val selectorView = visibleViewList[index]
|
||||||
|
|
||||||
|
//更新选中样式
|
||||||
|
selectorView.setSe(select)
|
||||||
|
|
||||||
|
if (dslSelectorConfig.dslMultiMode) {
|
||||||
|
//多选
|
||||||
|
} else {
|
||||||
|
//单选
|
||||||
|
|
||||||
|
//取消之前
|
||||||
|
selectorViewList.forEach { view ->
|
||||||
|
//更新样式
|
||||||
|
val indexOf = visibleViewList.indexOf(view)
|
||||||
|
if (indexOf != index &&
|
||||||
|
!dslSelectorConfig.onSelectItemView(view, indexOf, false, fromUser)
|
||||||
|
) {
|
||||||
|
view.setSe(false)
|
||||||
|
dslSelectorConfig.onStyleItemView(view, indexOf, false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
dslSelectorConfig.onStyleItemView(selectorView, index, select)
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
/**是否选中状态*/
|
||||||
|
fun View.isSe(): Boolean {
|
||||||
|
return isSelected || if (this is CompoundButton) isChecked else false
|
||||||
|
}
|
||||||
|
|
||||||
|
fun View.setSe(se: Boolean) {
|
||||||
|
isSelected = se
|
||||||
|
if (this is CompoundButton) isChecked = se
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Dsl配置项
|
||||||
|
* */
|
||||||
|
open class DslSelectorConfig {
|
||||||
|
|
||||||
|
/**取消选择时, 最小需要保持多个选中. 可以决定单选时, 是否允许取消所有选中*/
|
||||||
|
var dslMinSelectLimit = 1
|
||||||
|
|
||||||
|
/**多选时, 最大允许多个选中*/
|
||||||
|
var dslMaxSelectLimit = Int.MAX_VALUE
|
||||||
|
|
||||||
|
/**是否是多选模式*/
|
||||||
|
var dslMultiMode: Boolean = false
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 用来初始化[itemView]的样式
|
||||||
|
* [onSelectItemView]
|
||||||
|
* */
|
||||||
|
var onStyleItemView: (itemView: View, index: Int, select: Boolean) -> Unit =
|
||||||
|
{ _, _, _ ->
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 选中[View]改变回调, 优先于[onSelectIndexChange]触发, 区别在于参数类型不一样
|
||||||
|
* @param fromView 单选模式下有效, 表示之前选中的[View]
|
||||||
|
* @param reselect 是否是重复选择, 只在单选模式下有效
|
||||||
|
* @param fromUser 是否是用户产生的回调, 而非代码设置
|
||||||
|
* */
|
||||||
|
var onSelectViewChange: (fromView: View?, selectViewList: List<View>, reselect: Boolean, fromUser: Boolean) -> Unit =
|
||||||
|
{ _, _, _, _ ->
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 选中改变回调
|
||||||
|
* [onSelectViewChange]
|
||||||
|
* @param fromIndex 单选模式下有效, 表示之前选中的[View], 在可见性[child]列表中的索引
|
||||||
|
* */
|
||||||
|
var onSelectIndexChange: (fromIndex: Int, selectIndexList: List<Int>, reselect: Boolean, fromUser: Boolean) -> Unit =
|
||||||
|
{ fromIndex, selectList, reselect, fromUser ->
|
||||||
|
"选择:[$fromIndex]->${selectList} reselect:$reselect fromUser:$fromUser".logi()
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 当需要选中[itemView]时回调, 返回[true]表示拦截默认处理
|
||||||
|
* @param itemView 操作的[View]
|
||||||
|
* @param index [itemView]在可见性view列表中的索引. 非ViewGroup中的索引
|
||||||
|
* @param select 选中 or 取消选中
|
||||||
|
* @return true 表示拦截默认处理
|
||||||
|
* */
|
||||||
|
var onSelectItemView: (itemView: View, index: Int, select: Boolean, fromUser: Boolean) -> Boolean =
|
||||||
|
{ _, _, _, _ ->
|
||||||
|
false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**[DslSelector]组件*/
|
||||||
|
fun dslSelector(viewGroup: ViewGroup, config: DslSelectorConfig.() -> Unit = {}): DslSelector {
|
||||||
|
return DslSelector().apply {
|
||||||
|
install(viewGroup, config)
|
||||||
|
}
|
||||||
|
}
|
||||||
222
TabLayout/src/main/java/com/angcyo/tablayout/DslTabBadge.kt
Normal file
222
TabLayout/src/main/java/com/angcyo/tablayout/DslTabBadge.kt
Normal file
@@ -0,0 +1,222 @@
|
|||||||
|
package com.angcyo.tablayout
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import android.graphics.Color
|
||||||
|
import android.util.AttributeSet
|
||||||
|
import android.view.Gravity
|
||||||
|
import androidx.annotation.Px
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 角标
|
||||||
|
* Email:angcyo@126.com
|
||||||
|
* @author angcyo
|
||||||
|
* @date 2019/12/13
|
||||||
|
* Copyright (c) 2019 ShenZhen O&M Cloud Co., Ltd. All rights reserved.
|
||||||
|
*/
|
||||||
|
open class DslTabBadge : DslBadgeDrawable() {
|
||||||
|
|
||||||
|
/**角标默认配置项*/
|
||||||
|
val defaultBadgeConfig = TabBadgeConfig()
|
||||||
|
|
||||||
|
/**预览的角标属性*/
|
||||||
|
var xmlBadgeText: String? = null
|
||||||
|
|
||||||
|
override fun initAttribute(context: Context, attributeSet: AttributeSet?) {
|
||||||
|
val typedArray =
|
||||||
|
context.obtainStyledAttributes(attributeSet, R.styleable.DslTabLayout)
|
||||||
|
gradientSolidColor =
|
||||||
|
typedArray.getColor(
|
||||||
|
R.styleable.DslTabLayout_tab_badge_solid_color,
|
||||||
|
defaultBadgeConfig.badgeSolidColor
|
||||||
|
)
|
||||||
|
defaultBadgeConfig.badgeSolidColor = gradientSolidColor
|
||||||
|
|
||||||
|
badgeTextColor =
|
||||||
|
typedArray.getColor(
|
||||||
|
R.styleable.DslTabLayout_tab_badge_text_color,
|
||||||
|
defaultBadgeConfig.badgeTextColor
|
||||||
|
)
|
||||||
|
defaultBadgeConfig.badgeTextColor = badgeTextColor
|
||||||
|
|
||||||
|
gradientStrokeColor =
|
||||||
|
typedArray.getColor(
|
||||||
|
R.styleable.DslTabLayout_tab_badge_stroke_color,
|
||||||
|
defaultBadgeConfig.badgeStrokeColor
|
||||||
|
)
|
||||||
|
defaultBadgeConfig.badgeStrokeColor = gradientStrokeColor
|
||||||
|
|
||||||
|
gradientStrokeWidth =
|
||||||
|
typedArray.getDimensionPixelOffset(
|
||||||
|
R.styleable.DslTabLayout_tab_badge_stroke_width,
|
||||||
|
defaultBadgeConfig.badgeStrokeWidth
|
||||||
|
)
|
||||||
|
defaultBadgeConfig.badgeStrokeWidth = gradientStrokeWidth
|
||||||
|
|
||||||
|
badgeGravity = typedArray.getInt(
|
||||||
|
R.styleable.DslTabLayout_tab_badge_gravity,
|
||||||
|
defaultBadgeConfig.badgeGravity
|
||||||
|
)
|
||||||
|
defaultBadgeConfig.badgeGravity = badgeGravity
|
||||||
|
|
||||||
|
badgeOffsetX = typedArray.getDimensionPixelOffset(
|
||||||
|
R.styleable.DslTabLayout_tab_badge_offset_x,
|
||||||
|
defaultBadgeConfig.badgeOffsetX
|
||||||
|
)
|
||||||
|
defaultBadgeConfig.badgeOffsetX = badgeOffsetX
|
||||||
|
badgeOffsetY = typedArray.getDimensionPixelOffset(
|
||||||
|
R.styleable.DslTabLayout_tab_badge_offset_y,
|
||||||
|
defaultBadgeConfig.badgeOffsetY
|
||||||
|
)
|
||||||
|
defaultBadgeConfig.badgeOffsetY = badgeOffsetY
|
||||||
|
|
||||||
|
badgeCircleOffsetX = typedArray.getDimensionPixelOffset(
|
||||||
|
R.styleable.DslTabLayout_tab_badge_circle_offset_x,
|
||||||
|
defaultBadgeConfig.badgeOffsetX
|
||||||
|
)
|
||||||
|
defaultBadgeConfig.badgeCircleOffsetX = badgeCircleOffsetX
|
||||||
|
badgeCircleOffsetY = typedArray.getDimensionPixelOffset(
|
||||||
|
R.styleable.DslTabLayout_tab_badge_circle_offset_y,
|
||||||
|
defaultBadgeConfig.badgeOffsetY
|
||||||
|
)
|
||||||
|
defaultBadgeConfig.badgeCircleOffsetY = badgeCircleOffsetY
|
||||||
|
|
||||||
|
badgeCircleRadius = typedArray.getDimensionPixelOffset(
|
||||||
|
R.styleable.DslTabLayout_tab_badge_circle_radius,
|
||||||
|
defaultBadgeConfig.badgeCircleRadius
|
||||||
|
)
|
||||||
|
defaultBadgeConfig.badgeCircleRadius = badgeCircleRadius
|
||||||
|
|
||||||
|
val badgeRadius = typedArray.getDimensionPixelOffset(
|
||||||
|
R.styleable.DslTabLayout_tab_badge_radius,
|
||||||
|
defaultBadgeConfig.badgeRadius
|
||||||
|
)
|
||||||
|
cornerRadius(badgeRadius.toFloat())
|
||||||
|
defaultBadgeConfig.badgeRadius = badgeRadius
|
||||||
|
|
||||||
|
badgePaddingLeft = typedArray.getDimensionPixelOffset(
|
||||||
|
R.styleable.DslTabLayout_tab_badge_padding_left,
|
||||||
|
defaultBadgeConfig.badgePaddingLeft
|
||||||
|
)
|
||||||
|
defaultBadgeConfig.badgePaddingLeft = badgePaddingLeft
|
||||||
|
|
||||||
|
badgePaddingRight = typedArray.getDimensionPixelOffset(
|
||||||
|
R.styleable.DslTabLayout_tab_badge_padding_right,
|
||||||
|
defaultBadgeConfig.badgePaddingRight
|
||||||
|
)
|
||||||
|
defaultBadgeConfig.badgePaddingRight = badgePaddingRight
|
||||||
|
|
||||||
|
badgePaddingTop = typedArray.getDimensionPixelOffset(
|
||||||
|
R.styleable.DslTabLayout_tab_badge_padding_top,
|
||||||
|
defaultBadgeConfig.badgePaddingTop
|
||||||
|
)
|
||||||
|
defaultBadgeConfig.badgePaddingTop = badgePaddingTop
|
||||||
|
|
||||||
|
badgePaddingBottom = typedArray.getDimensionPixelOffset(
|
||||||
|
R.styleable.DslTabLayout_tab_badge_padding_bottom,
|
||||||
|
defaultBadgeConfig.badgePaddingBottom
|
||||||
|
)
|
||||||
|
defaultBadgeConfig.badgePaddingBottom = badgePaddingBottom
|
||||||
|
|
||||||
|
xmlBadgeText = typedArray.getString(R.styleable.DslTabLayout_tab_badge_text)
|
||||||
|
|
||||||
|
badgeTextSize = typedArray.getDimensionPixelOffset(
|
||||||
|
R.styleable.DslTabLayout_tab_badge_text_size,
|
||||||
|
defaultBadgeConfig.badgeTextSize.toInt()
|
||||||
|
).toFloat()
|
||||||
|
defaultBadgeConfig.badgeTextSize = badgeTextSize
|
||||||
|
|
||||||
|
defaultBadgeConfig.badgeAnchorChildIndex =
|
||||||
|
typedArray.getInteger(
|
||||||
|
R.styleable.DslTabLayout_tab_badge_anchor_child_index,
|
||||||
|
defaultBadgeConfig.badgeAnchorChildIndex
|
||||||
|
)
|
||||||
|
defaultBadgeConfig.badgeIgnoreChildPadding = typedArray.getBoolean(
|
||||||
|
R.styleable.DslTabLayout_tab_badge_ignore_child_padding,
|
||||||
|
defaultBadgeConfig.badgeIgnoreChildPadding
|
||||||
|
)
|
||||||
|
|
||||||
|
defaultBadgeConfig.badgeMinWidth = typedArray.getLayoutDimension(
|
||||||
|
R.styleable.DslTabLayout_tab_badge_min_width,
|
||||||
|
defaultBadgeConfig.badgeMinWidth
|
||||||
|
)
|
||||||
|
|
||||||
|
defaultBadgeConfig.badgeMinHeight = typedArray.getLayoutDimension(
|
||||||
|
R.styleable.DslTabLayout_tab_badge_min_height,
|
||||||
|
defaultBadgeConfig.badgeMinHeight
|
||||||
|
)
|
||||||
|
|
||||||
|
typedArray.recycle()
|
||||||
|
super.initAttribute(context, attributeSet)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**使用指定配置, 更新[DslBadgeDrawable]*/
|
||||||
|
fun updateBadgeConfig(badgeConfig: TabBadgeConfig) {
|
||||||
|
gradientSolidColor = badgeConfig.badgeSolidColor
|
||||||
|
gradientStrokeColor = badgeConfig.badgeStrokeColor
|
||||||
|
gradientStrokeWidth = badgeConfig.badgeStrokeWidth
|
||||||
|
badgeTextColor = badgeConfig.badgeTextColor
|
||||||
|
badgeGravity = badgeConfig.badgeGravity
|
||||||
|
badgeOffsetX = badgeConfig.badgeOffsetX
|
||||||
|
badgeOffsetY = badgeConfig.badgeOffsetY
|
||||||
|
badgeCircleOffsetX = badgeConfig.badgeCircleOffsetX
|
||||||
|
badgeCircleOffsetY = badgeConfig.badgeCircleOffsetY
|
||||||
|
badgeCircleRadius = badgeConfig.badgeCircleRadius
|
||||||
|
badgePaddingLeft = badgeConfig.badgePaddingLeft
|
||||||
|
badgePaddingRight = badgeConfig.badgePaddingRight
|
||||||
|
badgePaddingTop = badgeConfig.badgePaddingTop
|
||||||
|
badgePaddingBottom = badgeConfig.badgePaddingBottom
|
||||||
|
badgeTextSize = badgeConfig.badgeTextSize
|
||||||
|
cornerRadius(badgeConfig.badgeRadius.toFloat())
|
||||||
|
badgeMinHeight = badgeConfig.badgeMinHeight
|
||||||
|
badgeMinWidth = badgeConfig.badgeMinWidth
|
||||||
|
badgeText = badgeConfig.badgeText
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**角标绘制参数配置*/
|
||||||
|
data class TabBadgeConfig(
|
||||||
|
/**角标的文本(默认居中绘制文本,暂不支持修改), 空字符串会绘制成小圆点
|
||||||
|
* null 不绘制角标
|
||||||
|
* "" 空字符绘制圆点
|
||||||
|
* 其他 正常绘制
|
||||||
|
* */
|
||||||
|
var badgeText: String? = null,
|
||||||
|
/**重力*/
|
||||||
|
var badgeGravity: Int = Gravity.CENTER,
|
||||||
|
/**角标背景颜色*/
|
||||||
|
var badgeSolidColor: Int = Color.RED,
|
||||||
|
/**角标边框颜色*/
|
||||||
|
var badgeStrokeColor: Int = Color.TRANSPARENT,
|
||||||
|
/**角标边框宽度*/
|
||||||
|
var badgeStrokeWidth: Int = 0,
|
||||||
|
|
||||||
|
/**角标文本颜色*/
|
||||||
|
var badgeTextColor: Int = Color.WHITE,
|
||||||
|
/**角标文本字体大小*/
|
||||||
|
@Px
|
||||||
|
var badgeTextSize: Float = 12 * dp,
|
||||||
|
/**圆点状态时的半径大小*/
|
||||||
|
var badgeCircleRadius: Int = 4 * dpi,
|
||||||
|
/**圆角大小*/
|
||||||
|
var badgeRadius: Int = 10 * dpi,
|
||||||
|
/**额外偏移距离, 会根据[Gravity]自动取负值*/
|
||||||
|
var badgeOffsetX: Int = 0,
|
||||||
|
var badgeOffsetY: Int = 0,
|
||||||
|
var badgeCircleOffsetX: Int = 0,
|
||||||
|
var badgeCircleOffsetY: Int = 0,
|
||||||
|
/**圆点状态时无效*/
|
||||||
|
var badgePaddingLeft: Int = 4 * dpi,
|
||||||
|
var badgePaddingRight: Int = 4 * dpi,
|
||||||
|
var badgePaddingTop: Int = 0,
|
||||||
|
var badgePaddingBottom: Int = 0,
|
||||||
|
|
||||||
|
var badgeAnchorChildIndex: Int = -1,
|
||||||
|
var badgeIgnoreChildPadding: Boolean = true,
|
||||||
|
|
||||||
|
/**最小的高度大小, px. 大于0生效; 圆点时属性无效*/
|
||||||
|
var badgeMinHeight: Int = -2,
|
||||||
|
|
||||||
|
/**最小的宽度大小, px. 大于0生效; 圆点时属性无效;
|
||||||
|
* -1 表示使用使用计算出来的高度值*/
|
||||||
|
var badgeMinWidth: Int = -1
|
||||||
|
)
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user