Commit 1cbb24c5 by Wyh

8.22 1、外送/自取模塊更名 2、取消外送/自取按鈕權限控制 3、多個頁面加入loading

Signed-off-by: Wyh <1239658231>
parent 7f69656f
...@@ -7,7 +7,6 @@ import android.content.pm.PackageManager; ...@@ -7,7 +7,6 @@ import android.content.pm.PackageManager;
import android.content.res.Configuration; import android.content.res.Configuration;
import android.os.Build; import android.os.Build;
import android.os.RemoteException; import android.os.RemoteException;
import android.util.Log;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
...@@ -124,6 +123,21 @@ public class GsaCloudApplication extends BaseApplication { ...@@ -124,6 +123,21 @@ public class GsaCloudApplication extends BaseApplication {
AppCrashHandler.getInstance().init(this); AppCrashHandler.getInstance().init(this);
androidSetting = new FunctionStyleUtils(); androidSetting = new FunctionStyleUtils();
// 设定一些通用的属性,这些属性在每次统计事件中都会附带
// 注意:如果此处的属性名与内置属性的名称相同,则内置属性会被覆盖
// Tracker.INSTANCE.addProperty("附加的属性1", "附加的属性1");
// Tracker.INSTANCE.addProperty("附加的属性2", "附加的属性2");
// // 设定上报数据的主机和接口
// // 注意:该方法一定要在Tracker.initialize()方法前调用
// // 否则会由于上报地址未初始化,在触发启动事件时导致崩溃
// Tracker.INSTANCE.setService("host", "path");
// // 设定上报数据的项目名称
// Tracker.INSTANCE.setProjectName("項目名");
// // 设定上报数据的模式
// Tracker.INSTANCE.setMode(TrackerMode.DEBUG_ONLY);
// // 初始化AndroidTracker
// Tracker.INSTANCE.initialize(this);
} }
public Activity getCurrentActivity() { public Activity getCurrentActivity() {
......
...@@ -198,10 +198,12 @@ public class AppCrashHandler implements UncaughtExceptionHandler { ...@@ -198,10 +198,12 @@ public class AppCrashHandler implements UncaughtExceptionHandler {
@Override @Override
public void onSubscribe(Disposable d) { public void onSubscribe(Disposable d) {
Log.e("eee", "onSubscribe");
} }
@Override @Override
public void onNext(String data) { public void onNext(String data) {
Log.e("eee", "上傳錯誤日誌成功");
if(!TextUtils.isEmpty(data)){ if(!TextUtils.isEmpty(data)){
BaseResult result = JsonUtils.parseObject(data, BaseResult.class); BaseResult result = JsonUtils.parseObject(data, BaseResult.class);
if (result != null && result.isSuccess()) { if (result != null && result.isSuccess()) {
...@@ -218,11 +220,14 @@ public class AppCrashHandler implements UncaughtExceptionHandler { ...@@ -218,11 +220,14 @@ public class AppCrashHandler implements UncaughtExceptionHandler {
@Override @Override
public void onError(Throwable t) { public void onError(Throwable t) {
XLog.d(TAG, "sendFileMultipart onError: " + t.getMessage()); XLog.d(TAG, "sendFileMultipart onError: " + t.getMessage());
Log.e("eee", "onError");
} }
@Override @Override
public void onComplete() { public void onComplete() {
Log.e("eee", "onComplete");
} }
}); });
} }
} }
...@@ -261,8 +266,6 @@ public class AppCrashHandler implements UncaughtExceptionHandler { ...@@ -261,8 +266,6 @@ public class AppCrashHandler implements UncaughtExceptionHandler {
fos.write(sb.toString().getBytes()); fos.write(sb.toString().getBytes());
fos.close(); fos.close();
return fileName; return fileName;
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) { } catch (IOException e) {
e.printStackTrace(); e.printStackTrace();
} }
......
...@@ -60,16 +60,17 @@ class BtnBuilder(var functions: List<Function>) { ...@@ -60,16 +60,17 @@ class BtnBuilder(var functions: List<Function>) {
} }
private fun getBtn(key:String, btnCode:Int, btnContent: String, btnColor: Int, btnImg: Int): BtnBean? { private fun getBtn(key:String, btnCode:Int, btnContent: String, btnColor: Int, btnImg: Int): BtnBean? {
for (value in functions) { // for (value in functions) {
if (value.resUrl == key) { // if (value.resUrl == key) {
return when { // return when {
value.status == 3 -> BtnBean(btnCode, btnContent, R.color.color_ccc, btnImg) // value.status == 3 -> BtnBean(btnCode, btnContent, R.color.color_ccc, btnImg)
// value.status == 1 -> BtnBean(btnCode, btnContent, btnColor, btnImg) //// value.status == 1 -> BtnBean(btnCode, btnContent, btnColor, btnImg)
else -> BtnBean(btnCode, btnContent, btnColor, btnImg) // else -> BtnBean(btnCode, btnContent, btnColor, btnImg)
} // }
} // }
} // }
return null // return null
return BtnBean(btnCode, btnContent, btnColor, btnImg)
} }
} }
...@@ -5,7 +5,8 @@ class OrderList { ...@@ -5,7 +5,8 @@ class OrderList {
private var success: Boolean = false private var success: Boolean = false
private var sysTime: Long = 0 private var sysTime: Long = 0
private var data: DataBeanX? = null private var data: DataBeanX? = null
var errCode: String? = null
var errMsg:String? = null
class DataBeanX { class DataBeanX {
......
...@@ -57,6 +57,7 @@ class PageViewModel(private val repository: WeatherRepository) : ViewModel() { ...@@ -57,6 +57,7 @@ class PageViewModel(private val repository: WeatherRepository) : ViewModel() {
var mOrderList = arrayListOf<MutableLiveData<ArrayList<OrderList.DataBeanX.DataBean>>>() var mOrderList = arrayListOf<MutableLiveData<ArrayList<OrderList.DataBeanX.DataBean>>>()
var orderList = MutableLiveData<OrderList>()
//其他的所有數據 //其他的所有數據
var otherInfo = MutableLiveData<OrderList.DataBeanX.DataBean>() var otherInfo = MutableLiveData<OrderList.DataBeanX.DataBean>()
...@@ -136,6 +137,8 @@ class PageViewModel(private val repository: WeatherRepository) : ViewModel() { ...@@ -136,6 +137,8 @@ class PageViewModel(private val repository: WeatherRepository) : ViewModel() {
getOrderGroupNum(restaurantId) getOrderGroupNum(restaurantId)
loadInfo(isLoadMore, position) loadInfo(isLoadMore, position)
} else { } else {
orderList.postValue(this)
mOrderList[position].value = null
OkHttp3Utils.noticePersonnel(AppConstans.RP_ORDER_LIST_ERROR, "獲取訂單列表失敗") OkHttp3Utils.noticePersonnel(AppConstans.RP_ORDER_LIST_ERROR, "獲取訂單列表失敗")
} }
} }
......
...@@ -11,6 +11,7 @@ import androidx.lifecycle.Observer ...@@ -11,6 +11,7 @@ import androidx.lifecycle.Observer
import androidx.lifecycle.ViewModelProvider import androidx.lifecycle.ViewModelProvider
import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.LinearLayoutManager
import com.gingersoft.gsa.cloud.base.application.GsaCloudApplication import com.gingersoft.gsa.cloud.base.application.GsaCloudApplication
import com.gingersoft.gsa.cloud.base.utils.other.TextUtil
import com.gingersoft.gsa.cloud.base.utils.toast.ToastUtils import com.gingersoft.gsa.cloud.base.utils.toast.ToastUtils
import com.gingersoft.gsa.cloud.ui.utils.AppDialog import com.gingersoft.gsa.cloud.ui.utils.AppDialog
import com.gingersoft.gsa.other_order_mode.R import com.gingersoft.gsa.other_order_mode.R
...@@ -79,6 +80,21 @@ class PlaceholderFragment : BaseFragment(R.layout.fragment_other_order) { ...@@ -79,6 +80,21 @@ class PlaceholderFragment : BaseFragment(R.layout.fragment_other_order) {
} }
}) })
pageViewModel.orderList.observe(viewLifecycleOwner, Observer {
refresh_layout.finishRefresh()
refresh_layout.finishLoadMore()
it?.let {
if (!it.isSuccess()) {
if (TextUtil.isNotEmptyOrNullOrUndefined(it.errMsg)) {
//訂單未請求成功才調用
ToastUtils.show(activity, it.errMsg)
} else {
ToastUtils.show(activity, "獲取訂單列表失敗")
}
}
}
})
// 设置 Header 顏色 // 设置 Header 顏色
refresh_layout.setPrimaryColorsId(R.color.color_f0, R.color.color_66) refresh_layout.setPrimaryColorsId(R.color.color_f0, R.color.color_66)
// 下拉刷新,加載更多 // 下拉刷新,加載更多
...@@ -106,7 +122,8 @@ class PlaceholderFragment : BaseFragment(R.layout.fragment_other_order) { ...@@ -106,7 +122,8 @@ class PlaceholderFragment : BaseFragment(R.layout.fragment_other_order) {
} }
private fun getOrderList(pageViewModel: PageViewModel, isLoadMore: Boolean) { private fun getOrderList(pageViewModel: PageViewModel, isLoadMore: Boolean) {
pageViewModel.getOrderList(GsaCloudApplication.getRestaurantId(activity).toString(), arguments?.getInt(INDEX) ?: 0, page.toString(), isLoadMore) pageViewModel.getOrderList(GsaCloudApplication.getRestaurantId(activity).toString(), arguments?.getInt(INDEX)
?: 0, page.toString(), isLoadMore)
} }
companion object { companion object {
......
...@@ -80,7 +80,7 @@ dependencies { ...@@ -80,7 +80,7 @@ dependencies {
addComponent 'download-module' addComponent 'download-module'
addComponent 'table-module' addComponent 'table-module'
addComponent 'print-module' addComponent 'print-module'
addComponent 'other_order_mode' addComponent 'delivery_pick_mode'
addComponent 'manager-module' addComponent 'manager-module'
implementation 'androidx.viewpager2:viewpager2:1.0.0-alpha03' implementation 'androidx.viewpager2:viewpager2:1.0.0-alpha03'
......
...@@ -32,6 +32,7 @@ import com.gingersoft.gsa.cloud.ui.adapter.BasTextSectiontAdapter; ...@@ -32,6 +32,7 @@ import com.gingersoft.gsa.cloud.ui.adapter.BasTextSectiontAdapter;
import com.gingersoft.gsa.cloud.ui.bean.mode.BrandsBean; import com.gingersoft.gsa.cloud.ui.bean.mode.BrandsBean;
import com.gingersoft.gsa.cloud.ui.bean.view.SectionHeader; import com.gingersoft.gsa.cloud.ui.bean.view.SectionHeader;
import com.gingersoft.gsa.cloud.ui.bean.view.SectionRestaurantItem; import com.gingersoft.gsa.cloud.ui.bean.view.SectionRestaurantItem;
import com.gingersoft.gsa.cloud.ui.widget.dialog.LoadingDialog;
import com.jess.arms.base.BaseActivity; import com.jess.arms.base.BaseActivity;
import com.jess.arms.di.component.AppComponent; import com.jess.arms.di.component.AppComponent;
import com.jess.arms.utils.ArmsUtils; import com.jess.arms.utils.ArmsUtils;
...@@ -134,18 +135,20 @@ public class ChooseRestaurantActivity extends BaseActivity<ChooseRestaurantPrese ...@@ -134,18 +135,20 @@ public class ChooseRestaurantActivity extends BaseActivity<ChooseRestaurantPrese
@Override @Override
public void showLoading(String message) { public void showLoading(String message) {
if (message != null)
LoadingDialog.showDialogForLoading(this, message, true);
else
LoadingDialog.showDialogForLoading(this);
} }
@Override @Override
public void hideLoading() { public void hideLoading() {
LoadingDialog.cancelDialogForLoading();
} }
@Override @Override
public void showMessage(@NonNull String message) { public void showMessage(@NonNull String message) {
checkNotNull(message); ArmsUtils.makeText(this, message);
ArmsUtils.snackbarText(message);
} }
@Override @Override
......
...@@ -61,7 +61,6 @@ public class LoginActivity extends LoginInterfaceImpl<LoginPresenter> implements ...@@ -61,7 +61,6 @@ public class LoginActivity extends LoginInterfaceImpl<LoginPresenter> implements
@Inject @Inject
AppManager mAppManager; AppManager mAppManager;
@BindView(R2.id.ed_login_user_account) @BindView(R2.id.ed_login_user_account)
TextInputEditText edAccount; TextInputEditText edAccount;
@BindView(R2.id.ed_login_user_pwd) @BindView(R2.id.ed_login_user_pwd)
......
...@@ -75,7 +75,7 @@ public class BusinessReportPresenter extends BasePresenter<BusinessReportContrac ...@@ -75,7 +75,7 @@ public class BusinessReportPresenter extends BasePresenter<BusinessReportContrac
map.put("endTime", endTime); map.put("endTime", endTime);
mModel.getRestaurantBusinessAmount(map) mModel.getRestaurantBusinessAmount(map)
.subscribeOn(Schedulers.io()) .subscribeOn(Schedulers.io())
.doOnSubscribe(disposable -> mRootView.showLoading("")) .doOnSubscribe(disposable -> mRootView.showLoading("加載營業報表..."))
.subscribeOn(AndroidSchedulers.mainThread()) .subscribeOn(AndroidSchedulers.mainThread())
.observeOn(AndroidSchedulers.mainThread()) .observeOn(AndroidSchedulers.mainThread())
.doAfterTerminate(() -> mRootView.hideLoading()) .doAfterTerminate(() -> mRootView.hideLoading())
...@@ -128,7 +128,7 @@ public class BusinessReportPresenter extends BasePresenter<BusinessReportContrac ...@@ -128,7 +128,7 @@ public class BusinessReportPresenter extends BasePresenter<BusinessReportContrac
map.put("endDate", endTime); map.put("endDate", endTime);
mModel.getDaySalesReport(map) mModel.getDaySalesReport(map)
.subscribeOn(Schedulers.io()) .subscribeOn(Schedulers.io())
.doOnSubscribe(disposable -> mRootView.showLoading("")) .doOnSubscribe(disposable -> mRootView.showLoading("加載每日營業詳情..."))
.subscribeOn(AndroidSchedulers.mainThread()) .subscribeOn(AndroidSchedulers.mainThread())
.observeOn(AndroidSchedulers.mainThread()) .observeOn(AndroidSchedulers.mainThread())
.doAfterTerminate(() -> mRootView.hideLoading()) .doAfterTerminate(() -> mRootView.hideLoading())
...@@ -159,7 +159,7 @@ public class BusinessReportPresenter extends BasePresenter<BusinessReportContrac ...@@ -159,7 +159,7 @@ public class BusinessReportPresenter extends BasePresenter<BusinessReportContrac
map.put("endDate", endTime); map.put("endDate", endTime);
mModel.getOrderAnalysis(map) mModel.getOrderAnalysis(map)
.subscribeOn(Schedulers.io()) .subscribeOn(Schedulers.io())
.doOnSubscribe(disposable -> mRootView.showLoading("")) .doOnSubscribe(disposable -> mRootView.showLoading("加載營業金額詳情..."))
.subscribeOn(AndroidSchedulers.mainThread()) .subscribeOn(AndroidSchedulers.mainThread())
.observeOn(AndroidSchedulers.mainThread()) .observeOn(AndroidSchedulers.mainThread())
.doAfterTerminate(() -> mRootView.hideLoading()) .doAfterTerminate(() -> mRootView.hideLoading())
...@@ -199,7 +199,7 @@ public class BusinessReportPresenter extends BasePresenter<BusinessReportContrac ...@@ -199,7 +199,7 @@ public class BusinessReportPresenter extends BasePresenter<BusinessReportContrac
map.put("endDate", endTime); map.put("endDate", endTime);
mModel.getOrderDetailsReport(map) mModel.getOrderDetailsReport(map)
.subscribeOn(Schedulers.io()) .subscribeOn(Schedulers.io())
.doOnSubscribe(disposable -> mRootView.showLoading("")) .doOnSubscribe(disposable -> mRootView.showLoading("加載訂單詳情..."))
.subscribeOn(AndroidSchedulers.mainThread()) .subscribeOn(AndroidSchedulers.mainThread())
.observeOn(AndroidSchedulers.mainThread()) .observeOn(AndroidSchedulers.mainThread())
.doAfterTerminate(() -> mRootView.hideLoading()) .doAfterTerminate(() -> mRootView.hideLoading())
...@@ -217,7 +217,7 @@ public class BusinessReportPresenter extends BasePresenter<BusinessReportContrac ...@@ -217,7 +217,7 @@ public class BusinessReportPresenter extends BasePresenter<BusinessReportContrac
dailyReportBeans.add(new DailyReportBean("合計", info.getData().getBillNumSum() + "", "100", info.getData().getAmountSum() + "")); dailyReportBeans.add(new DailyReportBean("合計", info.getData().getBillNumSum() + "", "100", info.getData().getAmountSum() + ""));
mRootView.loadOrderDetailsReport(dailyReportBeans); mRootView.loadOrderDetailsReport(dailyReportBeans);
} else { } else {
mRootView.loadOrderDetailsReport(null); mRootView.loadOrderDetailsReport(new ArrayList<>());
} }
} else { } else {
mRootView.loadOrderDetailsReport(null); mRootView.loadOrderDetailsReport(null);
...@@ -239,7 +239,7 @@ public class BusinessReportPresenter extends BasePresenter<BusinessReportContrac ...@@ -239,7 +239,7 @@ public class BusinessReportPresenter extends BasePresenter<BusinessReportContrac
map.put("endDate", endTime); map.put("endDate", endTime);
mModel.getDiscountDetails(map) mModel.getDiscountDetails(map)
.subscribeOn(Schedulers.io()) .subscribeOn(Schedulers.io())
.doOnSubscribe(disposable -> mRootView.showLoading("")) .doOnSubscribe(disposable -> mRootView.showLoading("加載折扣詳情..."))
.subscribeOn(AndroidSchedulers.mainThread()) .subscribeOn(AndroidSchedulers.mainThread())
.observeOn(AndroidSchedulers.mainThread()) .observeOn(AndroidSchedulers.mainThread())
.doAfterTerminate(() -> mRootView.hideLoading()) .doAfterTerminate(() -> mRootView.hideLoading())
...@@ -257,7 +257,7 @@ public class BusinessReportPresenter extends BasePresenter<BusinessReportContrac ...@@ -257,7 +257,7 @@ public class BusinessReportPresenter extends BasePresenter<BusinessReportContrac
dailyReportBeans.add(new DailyReportBean("合計", info.getData().getBillNumSum() + "", info.getData().getAmountSum() + "")); dailyReportBeans.add(new DailyReportBean("合計", info.getData().getBillNumSum() + "", info.getData().getAmountSum() + ""));
mRootView.loadDiscountDetailsReport(dailyReportBeans); mRootView.loadDiscountDetailsReport(dailyReportBeans);
} else { } else {
mRootView.loadDiscountDetailsReport(null); mRootView.loadDiscountDetailsReport(new ArrayList<>());
} }
} else { } else {
mRootView.loadDiscountDetailsReport(null); mRootView.loadDiscountDetailsReport(null);
......
...@@ -48,6 +48,7 @@ import com.gingersoft.gsa.cloud.main.mvp.ui.view.SlidingMenu; ...@@ -48,6 +48,7 @@ import com.gingersoft.gsa.cloud.main.mvp.ui.view.SlidingMenu;
import com.gingersoft.gsa.cloud.ui.bean.mode.BrandsBean; import com.gingersoft.gsa.cloud.ui.bean.mode.BrandsBean;
import com.gingersoft.gsa.cloud.ui.widget.dialog.ChooseRestaurantDialog; import com.gingersoft.gsa.cloud.ui.widget.dialog.ChooseRestaurantDialog;
import com.gingersoft.gsa.cloud.ui.widget.dialog.CommonTipDialog; import com.gingersoft.gsa.cloud.ui.widget.dialog.CommonTipDialog;
import com.gingersoft.gsa.cloud.ui.widget.dialog.LoadingDialog;
import com.gyf.immersionbar.ImmersionBar; import com.gyf.immersionbar.ImmersionBar;
import com.jess.arms.base.BaseFragmentActivity; import com.jess.arms.base.BaseFragmentActivity;
import com.jess.arms.di.component.AppComponent; import com.jess.arms.di.component.AppComponent;
...@@ -261,53 +262,53 @@ public class NewMainActivity extends BaseFragmentActivity<NewMainPresenter> impl ...@@ -261,53 +262,53 @@ public class NewMainActivity extends BaseFragmentActivity<NewMainPresenter> impl
List<Function> functions = new ArrayList<>(); List<Function> functions = new ArrayList<>();
//// if (!BuildConfig.DEBUG) { //// if (!BuildConfig.DEBUG) {
// functions.add(new Function((long) 150, 0, 5, "點餐", 0, 0)); functions.add(new Function((long) 150, 0, 5, "點餐", 0, 0));
// if (BuildConfig.DEBUG) { if (BuildConfig.DEBUG) {
// functions.add(new Function((long) 138, 150, 5, "餐檯模式", R.drawable.ic_dining_table_mode, 0)); functions.add(new Function((long) 138, 150, 5, "餐檯模式", R.drawable.ic_dining_table_mode, 0));
// }
// functions.add(new Function((long) 139, 150, 5, "外送/自取", R.drawable.ic_delivery_mode, 0));
//// functions.add(new Function((long) 140, 150, 5, "外賣模式", R.drawable.ic_outsourcing_model_close, 0));
//// functions.add(new Function((long) 141, 150, 5, "預點餐模式", R.drawable.ic_pre_order_mode_close, 1));
// functions.add(new Function((long) 151, 0, 5, "管理", 0, 0));
// if (BuildConfig.DEBUG) {
// functions.add(new Function((long) 142, 151, 5, "賬單管理", R.drawable.ic_meals_menu_management, 0));
// functions.add(new Function((long) 143, 151, 5, "餐檯管理", R.drawable.ic_dining_table_management, 0));
// }
// functions.add(new Function((long) 144, 151, 5, "打印管理", R.drawable.ic_print_management, 0));
// if (BuildConfig.DEBUG) {
// functions.add(new Function((long) 145, 151, 5, "支付管理", R.drawable.ic_pay_management_close, 1));
// functions.add(new Function((long) 146, 151, 5, "折扣管理", R.drawable.ic_discount_management_close, 1));
// functions.add(new Function((long) 147, 151, 5, "沽清管理", R.drawable.ic_sell_off_manger, 0));
// }
// functions.add(new Function((long) 152, 0, 5, "員工", 0, 0));
// functions.add(new Function((long) 147, 152, 5, "員工管理", R.drawable.ic_staff_management_close, 1));
// functions.add(new Function((long) 148, 152, 5, "權限管理", R.drawable.ic_authority_management_close, 1));
// functions.add(new Function((long) 149, 152, 5, "操作記錄", R.drawable.ic_operation_record_close, 1));
// } else {
List<Function> orderFuncations = FunctionManager.getDefault().getFunctionByResModule(this, ComponentMain.main.class, ComponentMain.main.order, "order");
if (orderFuncations.size() > 0) {
functions.addAll(orderFuncations);
sl_order.setVisibility(View.VISIBLE);
} else {
sl_order.setVisibility(View.INVISIBLE);
} }
functions.add(new Function((long) 139, 150, 5, "外送/自取", R.drawable.ic_delivery_mode, 0));
List<Function> managerFuncations = FunctionManager.getDefault().getFunctionByResModule(this, ComponentMain.main.class, ComponentMain.main.manager, "manager"); // functions.add(new Function((long) 140, 150, 5, "外賣模式", R.drawable.ic_outsourcing_model_close, 0));
if (managerFuncations.size() > 0) { // functions.add(new Function((long) 141, 150, 5, "預點餐模式", R.drawable.ic_pre_order_mode_close, 1));
functions.addAll(managerFuncations); functions.add(new Function((long) 151, 0, 5, "管理", 0, 0));
ll_management.setVisibility(View.VISIBLE); if (BuildConfig.DEBUG) {
} else { functions.add(new Function((long) 142, 151, 5, "賬單管理", R.drawable.ic_meals_menu_management, 0));
ll_management.setVisibility(View.GONE); functions.add(new Function((long) 143, 151, 5, "餐檯管理", R.drawable.ic_dining_table_management, 0));
} }
functions.add(new Function((long) 144, 151, 5, "打印管理", R.drawable.ic_print_management, 0));
List<Function> employeeFuncations = FunctionManager.getDefault().getFunctionByResModule(this, ComponentMain.main.class, ComponentMain.main.employee, "employee"); if (BuildConfig.DEBUG) {
if (employeeFuncations.size() > 0) { functions.add(new Function((long) 145, 151, 5, "支付管理", R.drawable.ic_pay_management_close, 1));
functions.addAll(employeeFuncations); functions.add(new Function((long) 146, 151, 5, "折扣管理", R.drawable.ic_discount_management_close, 1));
ll_staff_management.setVisibility(View.VISIBLE); functions.add(new Function((long) 147, 151, 5, "沽清管理", R.drawable.ic_sell_off_manger, 0));
} else {
ll_staff_management.setVisibility(View.GONE);
} }
functions.add(new Function((long) 152, 0, 5, "員工", 0, 0));
functions.add(new Function((long) 147, 152, 5, "員工管理", R.drawable.ic_staff_management_close, 1));
functions.add(new Function((long) 148, 152, 5, "權限管理", R.drawable.ic_authority_management_close, 1));
functions.add(new Function((long) 149, 152, 5, "操作記錄", R.drawable.ic_operation_record_close, 1));
// } else {
// List<Function> orderFuncations = FunctionManager.getDefault().getFunctionByResModule(this, ComponentMain.main.class, ComponentMain.main.order, "order");
// if (orderFuncations.size() > 0) {
// functions.addAll(orderFuncations);
// sl_order.setVisibility(View.VISIBLE);
// } else {
// sl_order.setVisibility(View.INVISIBLE);
// }
//
// List<Function> managerFuncations = FunctionManager.getDefault().getFunctionByResModule(this, ComponentMain.main.class, ComponentMain.main.manager, "manager");
// if (managerFuncations.size() > 0) {
// functions.addAll(managerFuncations);
// ll_management.setVisibility(View.VISIBLE);
// } else {
// ll_management.setVisibility(View.GONE);
// }
//
// List<Function> employeeFuncations = FunctionManager.getDefault().getFunctionByResModule(this, ComponentMain.main.class, ComponentMain.main.employee, "employee");
// if (employeeFuncations.size() > 0) {
// functions.addAll(employeeFuncations);
// ll_staff_management.setVisibility(View.VISIBLE);
// } else {
// ll_staff_management.setVisibility(View.GONE);
// }
// } // }
//將功能列表數據分組 //將功能列表數據分組
...@@ -497,19 +498,20 @@ public class NewMainActivity extends BaseFragmentActivity<NewMainPresenter> impl ...@@ -497,19 +498,20 @@ public class NewMainActivity extends BaseFragmentActivity<NewMainPresenter> impl
@Override @Override
public void showLoading(String message) { public void showLoading(String message) {
// if (message != null)
// LoadingDialog.showDialogForLoading(mContext, message, true);
// else
// LoadingDialog.showDialogForLoading(mContext);
} }
@Override @Override
public void hideLoading() { public void hideLoading() {
// LoadingDialog.cancelDialogForLoading();
} }
@Override @Override
public void showMessage(@NonNull String message) { public void showMessage(@NonNull String message) {
checkNotNull(message); ArmsUtils.makeText(mContext, message);
// ArmsUtils.snackbarText(message);
ToastUtils.show(mContext, message);
} }
@Override @Override
...@@ -518,6 +520,7 @@ public class NewMainActivity extends BaseFragmentActivity<NewMainPresenter> impl ...@@ -518,6 +520,7 @@ public class NewMainActivity extends BaseFragmentActivity<NewMainPresenter> impl
ArmsUtils.startActivity(intent); ArmsUtils.startActivity(intent);
} }
@Override @Override
public void killMyself() { public void killMyself() {
finish(); finish();
......
...@@ -15,6 +15,7 @@ import com.gingersoft.gsa.cloud.main.mvp.ui.fragment.BusinessReportFragment; ...@@ -15,6 +15,7 @@ import com.gingersoft.gsa.cloud.main.mvp.ui.fragment.BusinessReportFragment;
import com.gingersoft.gsa.cloud.main.mvp.ui.fragment.PaymentMethodReportFragment; import com.gingersoft.gsa.cloud.main.mvp.ui.fragment.PaymentMethodReportFragment;
import com.gingersoft.gsa.cloud.main.mvp.ui.fragment.SalesFragment; import com.gingersoft.gsa.cloud.main.mvp.ui.fragment.SalesFragment;
import com.gingersoft.gsa.cloud.ui.adapter.TabFragmentAdapter; import com.gingersoft.gsa.cloud.ui.adapter.TabFragmentAdapter;
import com.gingersoft.gsa.cloud.ui.widget.dialog.LoadingDialog;
import com.google.android.material.tabs.TabLayout; import com.google.android.material.tabs.TabLayout;
import com.jess.arms.base.BaseActivity; import com.jess.arms.base.BaseActivity;
import com.jess.arms.base.BaseFragmentActivity; import com.jess.arms.base.BaseFragmentActivity;
...@@ -182,19 +183,20 @@ public class ReportActivity extends BaseFragmentActivity<ReportPresenter> implem ...@@ -182,19 +183,20 @@ public class ReportActivity extends BaseFragmentActivity<ReportPresenter> implem
@Override @Override
public void showLoading(String message) { public void showLoading(String message) {
if (message != null)
LoadingDialog.showDialogForLoading(this, message, true);
else
LoadingDialog.showDialogForLoading(this);
} }
@Override @Override
public void hideLoading() { public void hideLoading() {
LoadingDialog.cancelDialogForLoading();
} }
@Override @Override
public void showMessage(@NonNull String message) { public void showMessage(@NonNull String message) {
checkNotNull(message); ArmsUtils.makeText(this, message);
// ArmsUtils.snackbarText(message);
ToastUtils.show(mContext, message);
} }
@Override @Override
......
...@@ -40,6 +40,7 @@ import com.gingersoft.gsa.cloud.ui.AAChartCore.AAChartCoreLib.AAChartEnum.AAChar ...@@ -40,6 +40,7 @@ import com.gingersoft.gsa.cloud.ui.AAChartCore.AAChartCoreLib.AAChartEnum.AAChar
import com.gingersoft.gsa.cloud.ui.AAChartCore.AAChartCoreLib.AATools.AAGradientColor; import com.gingersoft.gsa.cloud.ui.AAChartCore.AAChartCoreLib.AATools.AAGradientColor;
import com.gingersoft.gsa.cloud.ui.AAChartCore.AAChartCoreLib.AATools.AALinearGradientDirection; import com.gingersoft.gsa.cloud.ui.AAChartCore.AAChartCoreLib.AATools.AALinearGradientDirection;
import com.gingersoft.gsa.cloud.ui.view.TriangleView; import com.gingersoft.gsa.cloud.ui.view.TriangleView;
import com.gingersoft.gsa.cloud.ui.widget.dialog.LoadingDialog;
import com.jess.arms.base.BaseFragment; import com.jess.arms.base.BaseFragment;
import com.jess.arms.di.component.AppComponent; import com.jess.arms.di.component.AppComponent;
import com.jess.arms.utils.ArmsUtils; import com.jess.arms.utils.ArmsUtils;
...@@ -182,21 +183,23 @@ public class BusinessReportFragment extends BaseFragment<BusinessReportPresenter ...@@ -182,21 +183,23 @@ public class BusinessReportFragment extends BaseFragment<BusinessReportPresenter
} }
@Override @Override
public void showLoading(String message) { public void showLoading(String message) {
if (message != null)
LoadingDialog.showDialogForLoading(getActivity(), message, true);
else
LoadingDialog.showDialogForLoading(getActivity());
} }
@Override @Override
public void hideLoading() { public void hideLoading() {
LoadingDialog.cancelDialogForLoading();
} }
@Override @Override
public void showMessage(@NonNull String message) { public void showMessage(@NonNull String message) {
checkNotNull(message); ArmsUtils.makeText(mContext, message);
// ArmsUtils.snackbarText(message);
ToastUtils.show(mContext, message);
} }
@Override @Override
......
...@@ -22,6 +22,7 @@ import com.gingersoft.gsa.cloud.main.mvp.presenter.HomePresenter; ...@@ -22,6 +22,7 @@ import com.gingersoft.gsa.cloud.main.mvp.presenter.HomePresenter;
import com.gingersoft.gsa.cloud.main.mvp.ui.adapter.HomeFunctionAdapter; import com.gingersoft.gsa.cloud.main.mvp.ui.adapter.HomeFunctionAdapter;
import com.gingersoft.gsa.cloud.ui.bean.view.SectionHeader; import com.gingersoft.gsa.cloud.ui.bean.view.SectionHeader;
import com.gingersoft.gsa.cloud.ui.bean.view.SectionNormalItem; import com.gingersoft.gsa.cloud.ui.bean.view.SectionNormalItem;
import com.gingersoft.gsa.cloud.ui.widget.dialog.LoadingDialog;
import com.jess.arms.base.BaseFragment; import com.jess.arms.base.BaseFragment;
import com.jess.arms.di.component.AppComponent; import com.jess.arms.di.component.AppComponent;
import com.jess.arms.utils.ArmsUtils; import com.jess.arms.utils.ArmsUtils;
...@@ -40,6 +41,7 @@ import androidx.annotation.NonNull; ...@@ -40,6 +41,7 @@ import androidx.annotation.NonNull;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import androidx.recyclerview.widget.GridLayoutManager; import androidx.recyclerview.widget.GridLayoutManager;
import androidx.recyclerview.widget.RecyclerView; import androidx.recyclerview.widget.RecyclerView;
import butterknife.BindView; import butterknife.BindView;
import static com.jess.arms.utils.Preconditions.checkNotNull; import static com.jess.arms.utils.Preconditions.checkNotNull;
...@@ -217,19 +219,20 @@ public class HomeFragment extends BaseFragment<HomePresenter> implements HomeCon ...@@ -217,19 +219,20 @@ public class HomeFragment extends BaseFragment<HomePresenter> implements HomeCon
@Override @Override
public void showLoading(String message) { public void showLoading(String message) {
if (message != null)
LoadingDialog.showDialogForLoading(mContext, message, true);
else
LoadingDialog.showDialogForLoading(mContext);
} }
@Override @Override
public void hideLoading() { public void hideLoading() {
LoadingDialog.cancelDialogForLoading();
} }
@Override @Override
public void showMessage(@NonNull String message) { public void showMessage(@NonNull String message) {
checkNotNull(message); ArmsUtils.makeText(mContext, message);
// ArmsUtils.snackbarText(message);
ToastUtils.show(mContext, message);
} }
@Override @Override
...@@ -238,6 +241,7 @@ public class HomeFragment extends BaseFragment<HomePresenter> implements HomeCon ...@@ -238,6 +241,7 @@ public class HomeFragment extends BaseFragment<HomePresenter> implements HomeCon
ArmsUtils.startActivity(intent); ArmsUtils.startActivity(intent);
} }
@Override @Override
public void killMyself() { public void killMyself() {
......
...@@ -34,6 +34,7 @@ import com.gingersoft.gsa.cloud.ui.AAChartCore.AAChartCoreLib.AAChartEnum.AAChar ...@@ -34,6 +34,7 @@ import com.gingersoft.gsa.cloud.ui.AAChartCore.AAChartCoreLib.AAChartEnum.AAChar
import com.gingersoft.gsa.cloud.ui.AAChartCore.AAChartCoreLib.AAChartEnum.AAChartType; import com.gingersoft.gsa.cloud.ui.AAChartCore.AAChartCoreLib.AAChartEnum.AAChartType;
import com.gingersoft.gsa.cloud.ui.AAChartCore.AAChartCoreLib.AATools.AAGradientColor; import com.gingersoft.gsa.cloud.ui.AAChartCore.AAChartCoreLib.AATools.AAGradientColor;
import com.gingersoft.gsa.cloud.ui.AAChartCore.AAChartCoreLib.AATools.AALinearGradientDirection; import com.gingersoft.gsa.cloud.ui.AAChartCore.AAChartCoreLib.AATools.AALinearGradientDirection;
import com.gingersoft.gsa.cloud.ui.widget.dialog.LoadingDialog;
import com.jess.arms.base.BaseFragment; import com.jess.arms.base.BaseFragment;
import com.jess.arms.di.component.AppComponent; import com.jess.arms.di.component.AppComponent;
import com.jess.arms.utils.ArmsUtils; import com.jess.arms.utils.ArmsUtils;
...@@ -186,21 +187,22 @@ public class MainTopFragment extends BaseFragment<MainTopPresenter> implements M ...@@ -186,21 +187,22 @@ public class MainTopFragment extends BaseFragment<MainTopPresenter> implements M
} }
} }
@Override @Override
public void showLoading(String message) { public void showLoading(String message) {
if (message != null)
LoadingDialog.showDialogForLoading(mContext, message, true);
else
LoadingDialog.showDialogForLoading(mContext);
} }
@Override @Override
public void hideLoading() { public void hideLoading() {
LoadingDialog.cancelDialogForLoading();
} }
@Override @Override
public void showMessage(@NonNull String message) { public void showMessage(@NonNull String message) {
checkNotNull(message); ArmsUtils.makeText(mContext, message);
ToastUtils.show(mContext, message);
} }
@Override @Override
...@@ -209,6 +211,7 @@ public class MainTopFragment extends BaseFragment<MainTopPresenter> implements M ...@@ -209,6 +211,7 @@ public class MainTopFragment extends BaseFragment<MainTopPresenter> implements M
ArmsUtils.startActivity(intent); ArmsUtils.startActivity(intent);
} }
@Override @Override
public void killMyself() { public void killMyself() {
......
...@@ -29,6 +29,7 @@ import com.gingersoft.gsa.cloud.main.mvp.presenter.PaymentMethodReportPresenter; ...@@ -29,6 +29,7 @@ import com.gingersoft.gsa.cloud.main.mvp.presenter.PaymentMethodReportPresenter;
import com.gingersoft.gsa.cloud.main.mvp.ui.adapter.PaymentMethodAdapter; import com.gingersoft.gsa.cloud.main.mvp.ui.adapter.PaymentMethodAdapter;
import com.gingersoft.gsa.cloud.main.mvp.ui.adapter.PaymentMethodColorAdapter; import com.gingersoft.gsa.cloud.main.mvp.ui.adapter.PaymentMethodColorAdapter;
import com.gingersoft.gsa.cloud.ui.view.TriangleView; import com.gingersoft.gsa.cloud.ui.view.TriangleView;
import com.gingersoft.gsa.cloud.ui.widget.dialog.LoadingDialog;
import com.github.mikephil.charting.animation.Easing; import com.github.mikephil.charting.animation.Easing;
import com.github.mikephil.charting.charts.PieChart; import com.github.mikephil.charting.charts.PieChart;
import com.github.mikephil.charting.components.Legend; import com.github.mikephil.charting.components.Legend;
...@@ -161,19 +162,20 @@ public class PaymentMethodReportFragment extends BaseFragment<PaymentMethodRepor ...@@ -161,19 +162,20 @@ public class PaymentMethodReportFragment extends BaseFragment<PaymentMethodRepor
@Override @Override
public void showLoading(String message) { public void showLoading(String message) {
if (message != null)
LoadingDialog.showDialogForLoading(mContext, message, true);
else
LoadingDialog.showDialogForLoading(mContext);
} }
@Override @Override
public void hideLoading() { public void hideLoading() {
LoadingDialog.cancelDialogForLoading();
} }
@Override @Override
public void showMessage(@NonNull String message) { public void showMessage(@NonNull String message) {
checkNotNull(message); ArmsUtils.makeText(mContext, message);
// ArmsUtils.snackbarText(message);
ToastUtils.show(mContext, message);
} }
@Override @Override
......
...@@ -41,6 +41,7 @@ import com.gingersoft.gsa.cloud.ui.AAChartCore.AAChartCoreLib.AAChartEnum.AAChar ...@@ -41,6 +41,7 @@ import com.gingersoft.gsa.cloud.ui.AAChartCore.AAChartCoreLib.AAChartEnum.AAChar
import com.gingersoft.gsa.cloud.ui.AAChartCore.AAChartCoreLib.AATools.AAGradientColor; import com.gingersoft.gsa.cloud.ui.AAChartCore.AAChartCoreLib.AATools.AAGradientColor;
import com.gingersoft.gsa.cloud.ui.AAChartCore.AAChartCoreLib.AATools.AALinearGradientDirection; import com.gingersoft.gsa.cloud.ui.AAChartCore.AAChartCoreLib.AATools.AALinearGradientDirection;
import com.gingersoft.gsa.cloud.ui.view.TriangleView; import com.gingersoft.gsa.cloud.ui.view.TriangleView;
import com.gingersoft.gsa.cloud.ui.widget.dialog.LoadingDialog;
import com.github.mikephil.charting.charts.PieChart; import com.github.mikephil.charting.charts.PieChart;
import com.github.mikephil.charting.components.Legend; import com.github.mikephil.charting.components.Legend;
import com.github.mikephil.charting.data.Entry; import com.github.mikephil.charting.data.Entry;
...@@ -181,21 +182,23 @@ public class SalesFragment extends BaseFragment<SalesPresenter> implements Sales ...@@ -181,21 +182,23 @@ public class SalesFragment extends BaseFragment<SalesPresenter> implements Sales
} }
@Override @Override
public void showLoading(String message) { public void showLoading(String message) {
if (message != null)
LoadingDialog.showDialogForLoading(mContext, message, true);
else
LoadingDialog.showDialogForLoading(mContext);
} }
@Override @Override
public void hideLoading() { public void hideLoading() {
LoadingDialog.cancelDialogForLoading();
} }
@Override @Override
public void showMessage(@NonNull String message) { public void showMessage(@NonNull String message) {
checkNotNull(message); ArmsUtils.makeText(mContext, message);
// ArmsUtils.snackbarText(message);
ToastUtils.show(mContext, message);
} }
@Override @Override
......
...@@ -19,6 +19,7 @@ include 'cc-register', ...@@ -19,6 +19,7 @@ include 'cc-register',
'print-module', 'print-module',
'manager-module', 'manager-module',
'manager-module', 'manager-module',
'other_order_mode' 'delivery_pick_mode',
'tracker'
apply plugin: 'com.android.library'
apply plugin: 'kotlin-android'
//apply from: '../maven_push.gradle'
android {
compileSdkVersion rootProject.ext.android["compileSdkVersion"]
compileOptions {
targetCompatibility JavaVersion.VERSION_1_8
sourceCompatibility JavaVersion.VERSION_1_8
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
sourceSets {
main {
java {
include '**/*.java'
include '**/*.kt'
}
}
}
}
dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
implementation "com.android.support:appcompat-v7:27.1.1"
implementation "com.android.support:support-fragment:27.1.1"
implementation "com.android.support:support-annotations:27.1.1"
implementation "com.google.code.gson:gson:2.8.2"
implementation "com.squareup.okhttp3:okhttp:3.9.1"
implementation "com.squareup.retrofit2:retrofit:2.3.0"
implementation("com.squareup.retrofit2:converter-gson:2.3.0") {
exclude group: 'com.squareup.okhttp3'
exclude group: 'com.google.code.gson'
}
implementation("com.squareup.retrofit2:adapter-rxjava2:2.3.0") {
exclude group: 'com.squareup.okhttp3'
}
implementation "io.reactivex.rxjava2:rxjava:2.1.13"
implementation("io.reactivex.rxjava2:rxandroid:2.0.2") {
exclude module: 'rxjava'
}
implementation("com.squareup.okhttp3:logging-interceptor:3.9.1") {
exclude group: 'com.android.support'
exclude group: 'com.squareup.okhttp3'
}
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:1.3.41"
//implementation "org.jetbrains.anko:anko-commons:$anko_version"
implementation "org.jetbrains.anko:anko-sqlite:0.10.5"
}
\ No newline at end of file
# 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
-dontwarn okio.**
-dontwarn javax.annotation.**
\ No newline at end of file
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.foolchen.lib.tracker"/>
package com.foolchen.lib.tracker
import android.view.MotionEvent
import android.view.View
import android.widget.AdapterView
import android.widget.Button
import android.widget.TextView
import androidx.fragment.app.Fragment
import com.foolchen.lib.tracker.data.*
import com.foolchen.lib.tracker.lifecycle.ITrackerContext
import com.foolchen.lib.tracker.lifecycle.TrackerActivityLifeCycle
import com.foolchen.lib.tracker.utils.getTrackProperties
import com.foolchen.lib.tracker.utils.initBuildInProperties
import com.foolchen.lib.tracker.utils.trackEvent
import java.util.*
import kotlin.collections.HashMap
import com.foolchen.lib.tracker.utils.login as buildInLogin
import com.foolchen.lib.tracker.utils.logout as buildInLogout
/**
* 统计工具
*
* 该工具用于统计的初始化、登录、注册等操作
* @author chenchong
* 2017/11/4
* 上午11:17
*/
object Tracker {
/**当前正在浏览的页面的名称*/
internal var screenName: String = ""
internal var screenClass: String = ""
internal var screenTitle: String = ""
/**当前正在浏览的页面所依附的页面*/
internal var parent: String = ""
internal var parentClass: String = ""
/** 上一个浏览页面的名称 */
internal var referer: String = ""
internal var refererClass: String = ""
/**
* 开发者在初始化时附加的属性
* 这些属性在所有的事件中都会存在
*/
internal val additionalProperties = HashMap<String, Any?>()
/**
* 用于保存各个元素需要的附加属性
*/
internal val elementsProperties = WeakHashMap<View, Map<String, Any?>?>()
internal var channelId: String? = null
internal var mode = TrackerMode.RELEASE
internal var isBackground = false
internal var clearOnBackground = true
internal var appStartTime = 0L
internal lateinit var trackContext: ITrackerContext
internal var serviceHost: String? = null
internal var servicePath: String? = null
internal var projectName: String? = null
internal var isBase64EncodeEnable = true
internal var isUrlEncodeEnable = true
/**
* 上报接口的默认超时时间为3000ms
*/
internal var timeoutDuration = 3000L
private var isInitialized = false
private var trackerActivityLifeCycle: TrackerActivityLifeCycle? = null
/**
* 对AndroidTracker进行初始化
*
* 如果未调用该方法进行初始化,使用过程中可能会出现无法统计、Crash等情况
*/
fun initialize(app: ITrackerContext) {
if (isDisable()) return
trackContext = app
initBuildInProperties(app.getApplicationContext())
trackerActivityLifeCycle = TrackerActivityLifeCycle()
app.registerActivityLifecycleCallbacks(trackerActivityLifeCycle!!)
isInitialized = true
// 此处触发第一次启动事件,保证事件的触发在所有其他事件之前
//onForeground()
}
/**
* 设置接口地址
*
* AndroidTracker中的数据上报使用了Retrofit,此处需要对host和path进行分别设置
*
* **注意:该方法要在[initialize]方法之前调用,否则会崩溃**
*
* @param host 上报数据的域名,例如:https://www.demo.com.cn
* @param path 上报数据的接口名,例如:report.php
*/
fun setService(host: String, path: String) {
if (isDisable()) return
this.serviceHost = host
this.servicePath = path
}
/**
* 设置项目名称
*/
fun setProjectName(projectName: String) {
if (isDisable()) return
this.projectName = projectName
}
/**
* 设置是否要对上报的数据进行BASE64编码,默认为开启
*/
fun setBase64EncodeEnable(enable: Boolean) {
this.isBase64EncodeEnable = enable
}
/**
* 设置是否要对上报的数据进行Url编码,默认为开启
*/
fun setUrlEncodeEnable(enable: Boolean) {
this.isUrlEncodeEnable = enable
}
/**
* 设置统计的模式
* @see [TrackerMode]]
* @see [TrackerMode.DEBUG_ONLY]
* @see [TrackerMode.DEBUG_TRACK]
* @see [TrackerMode.RELEASE]
*/
fun setMode(mode: TrackerMode) {
this@Tracker.mode = mode
}
/**
* 设置是否在App切换到后台时,将前向地址等信息清空
* 该功能默认为开启
* @param clear 设置为true,则之前所有的前向地址、前向类名等都会在App被切换到后台时被清空,从后台切换回App时,访问的页面没有前向地址等信息
*/
fun clearOnBackground(clear: Boolean) {
if (isDisable()) return
clearOnBackground = clear
}
/**
* 增加自定义属性
*/
fun addProperty(key: String, value: Any?) {
if (isDisable()) return
if (value != null) {
additionalProperties[key] = value
}
}
/**
* 增加自定义属性
*/
fun addProperties(properties: Map<String, Any?>?) {
if (isDisable()) return
properties?.forEach({
if (it.value != null) {
additionalProperties[it.key] = it.value!!
}
})
}
/**
* 用户登录
*/
fun login(userId: String) {
if (isDisable()) return
buildInLogin(userId)
}
/**
* 用户登出
*/
fun logout() {
if (isDisable()) return
trackContext.let { buildInLogout() }
}
fun setChannelId(channelId: String?) {
if (isDisable()) return
this.channelId = channelId
}
/**
* 为要统计的元素增加需要附加的属性
*
* 注意:该方法不要与[ignoreView]同时调用,否则可能会失效
*
* @param view 要统计的View
* @param properties 要额外添加的属性
*/
fun trackView(view: View, properties: Map<String, Any?>?) {
if (isDisable()) return
properties?.let {
elementsProperties.put(view, properties)
}
}
/**
* 忽略View本次的点击事件(该方法仅生效一次,如需每次都忽略,应每次都调用该方法)
*
* 注意:该方法不要与[trackView]方法同时调用,否则可能会失效
*
* @param view 要忽略统计的View
*/
fun ignoreView(view: View) {
if (isDisable()) return
val properties = HashMap<String, Any>()
properties[IGNORE_CLICK] = true
elementsProperties[view] = properties
}
/**
* 手动对自定义事件进行统计
*
* @param name 自定义事件的名称,对应内置属性中的event字段
* @param properties 要统计的自定义事件的属性
*/
fun trackEvent(name: String, properties: Map<String, Any?>?) {
if (isDisable()) return
val event = TrackerEvent(name)
event.addProperties(properties)
trackEvent(event)
}
fun onHiddenChanged(f: Fragment, hidden: Boolean) {
trackerActivityLifeCycle?.getFragmentLifeCycle()?.onFragmentVisibilityChanged(!hidden, f)
}
fun setUserVisibleHint(f: Fragment, isVisibleToUser: Boolean) {
trackerActivityLifeCycle?.getFragmentLifeCycle()?.onFragmentVisibilityChanged(isVisibleToUser,
f)
}
internal fun trackScreen(properties: Map<String, Any?>?) {
val event = TrackerEvent(VIEW_SCREEN)
event.addProperties(properties)
trackEvent(event)
}
/**
* 对View的点击进行统计
*
* 注意:在已经调用了[ignoreView]方法时,该方法被会直接返回,并不会执行View的点击事件统计
*
* @param view 要统计的点击事件View
* @param ev 要统计的View点击事件触发时的[MotionEvent]
* @param time 点击事件触发时的时间
*/
internal fun trackView(view: View, ev: MotionEvent, time: Long) {
val trackProperties = view.getTrackProperties(ev)
if (trackProperties[IGNORE_CLICK] != null && trackProperties[IGNORE_CLICK] == true) {
// 如果事件被忽略了,则直接返回
return
}
val event = TrackerEvent(CLICK)
event.time = time
event.addProperties(trackProperties)
trackEvent(event)
}
/**
* 对AdapterView的点击进行统计
*/
@Suppress("UNUSED_PARAMETER")
internal fun trackAdapterView(adapterView: AdapterView<*>, view: View, position: Int, id: Long,
ev: MotionEvent, time: Long) {
val event = TrackerEvent(CLICK)
event.time = time
val trackProperties = view.getTrackProperties(ev)
event.addProperties(trackProperties)
trackEvent(event)
}
/**
* 清空已保存的前向地址等状态
*/
internal fun onBackground() {
isBackground = true
if (isInitialized) {
val event = TrackerEvent(APP_END)
val properties = HashMap<String, Any>()
properties[EVENT_DURATION] = System.currentTimeMillis() - appStartTime
event.addProperties(properties)
trackEvent(event, background = true)
}
clearOnBackground.let {
screenName = ""
screenClass = ""
parent = ""
parentClass = ""
referer = ""
refererClass = ""
}
}
internal fun onForeground() {
appStartTime = System.currentTimeMillis()
if (isInitialized) {
val event = TrackerEvent(APP_START)
val properties = HashMap<String, Any>()
properties[RESUME_FROM_BACKGROUND] = isBackground
event.addProperties(properties)
trackEvent(event, foreground = true)
}
isBackground = false
}
private fun isDisable(): Boolean = mode == TrackerMode.DISABLE
}
\ No newline at end of file
package com.foolchen.lib.tracker.data
import com.foolchen.lib.tracker.Tracker
import com.foolchen.lib.tracker.utils.*
import com.google.gson.annotations.SerializedName
/**
* 统计事件
* @author chenchong
* 2017/11/4
* 下午2:48
*/
data class TrackerEvent(
@SerializedName("event")
@EventType private var event: String) {
@SerializedName("properties")
private var properties = HashMap<String, Any>()
@SerializedName("time")
internal var time = System.currentTimeMillis()
@SerializedName("screenName")
private var screenName = Tracker.screenName
@SerializedName("screenClass")
private var screenClass = Tracker.screenClass
@SerializedName("screenTitle")
private var screenTitle = Tracker.screenTitle
@SerializedName("referer")
private var referer = Tracker.referer
@SerializedName("refererClass")
private var refererClass = Tracker.refererClass
@SerializedName("parent")
private var parent = Tracker.parent
@SerializedName("parentClass")
private var parentClass = Tracker.parentClass
init {
Tracker.additionalProperties.filter { it.value != null }.forEach {
this@TrackerEvent.properties[it.key] = it.value!!
}
}
fun addProperties(properties: Map<String, Any?>?) {
if (properties == null) {
return
}
properties.filter { it.value != null }.forEach {
this@TrackerEvent.properties[it.key] = it.value!!
}
}
fun build(): Map<String, Any> {
val o = HashMap<String, Any>()
o.putAll(buildInObject)
o[EVENT] = event
o[TIME] = time
o[LIB] = buildInLib
val properties = HashMap<String, Any>()
properties.putAll(buildInProperties)
properties[SCREEN_NAME] = screenName
properties[SCREEN_CLASS] = screenClass
properties[TITLE] = screenTitle
properties[REFERER] = referer
properties[REFERER_CLASS] = refererClass
properties[PARENT] = parent
properties[PARENT_CLASS] = parentClass
Tracker.trackContext.let {
properties[NETWORK_TYPE] = it.getApplicationContext().getNetworkType().desc()
properties.put(WIFI, it.getApplicationContext().isWiFi())
}
Tracker.channelId?.let {
properties.put(CHANNEL, it)
}
this@TrackerEvent.properties.let {
properties.putAll(it)
}
o[PROPERTIES] = properties
return o
}
fun toPrettyJson(): String {
return PRETTY_GSON.toJson(build())
}
}
\ No newline at end of file
package com.foolchen.lib.tracker.data
import androidx.annotation.StringDef
/** 页面浏览事件 */
const val VIEW_SCREEN = "AppViewScreen"
/** 点击事件 */
const val CLICK = "AppClick"
/** APP启动(切换到前台)事件 */
const val APP_START = "AppStart"
/** APP关闭(切换到后台)事件 */
const val APP_END = "AppEnd"
/** APP统计事件(用于自定义) */
const val APP_TRACK = "track"
@StringDef(VIEW_SCREEN, CLICK, APP_START, APP_END)
@Retention(AnnotationRetention.SOURCE) internal annotation class EventType
package com.foolchen.lib.tracker.data
/**
* 运营商枚举
* @author chenchong
* 2017/11/28
* 下午4:51
*/
internal enum class TrackerMNC {
/** 其他 */
OTHER {
override fun desc(): String = "未知"
},
/** 中国移动 */
CMCC {
override fun desc(): String = "中国移动"
},
/** 中国联通 */
CUCC {
override fun desc(): String = "中国联通"
},
/** 中国电信 */
CTCC {
override fun desc(): String = "中国电信"
};
abstract fun desc(): String
}
\ No newline at end of file
package com.foolchen.lib.tracker.data
/**
* 日志输出&追踪的模式
* @author chenchong
* 2017/11/27
* 上午11:44
*/
enum class TrackerMode {
/**
* 仅调试模式,数据不入库,可在后台实时追踪日志输出
*
* 该模式一般供开发人员使用,不要在上线时使用该模式
*/
DEBUG_ONLY,
/**
* 调试&追踪模式,数据实时入库,并且可在后台实时追踪日志输出
*
* 该模式一般供测试人员使用
*/
DEBUG_TRACK,
/**
* 发布模式,数据根据既定策略入库,并且无法在后台实时追踪日志输出
*
* 该模式为发布时使用的模式,不要在上线时使用该模式
*/
RELEASE,
/**
* 禁用
*
* 该模式时,调用的任何方法都会失效
*/
DISABLE
}
\ No newline at end of file
package com.foolchen.lib.tracker.data
internal const val SCREEN_NAME = "screen_name"
internal const val SCREEN_CLASS = "screen_class"
internal const val TITLE = "title"
internal const val TIME = "time"
internal const val EVENT = "event"
internal const val REFERER = "referrer"
internal const val REFERER_CLASS = "referrer_class"
internal const val PARENT = "parent"
internal const val PARENT_CLASS = "parent_class"
internal const val DISTINCT_ID = "distinct_id"
internal const val LIB = "lib"
internal const val PROPERTIES = "properties"
internal const val NETWORK_TYPE = "network_type"
internal const val WIFI = "wifi"
internal const val CHANNEL = "channel"
internal const val RESUME_FROM_BACKGROUND = "resume_from_background"
internal const val EVENT_DURATION = "event_duration"
// 以下为点击元素用字段
internal const val ELEMENT_TYPE = "element_type"
internal const val ELEMENT_CONTENT = "element_content"
internal const val ELEMENT_X = "element_x"
internal const val ELEMENT_Y = "element_y"
// 其他
// 忽略点击事件
internal const val IGNORE_CLICK = "ignore_click"
package com.foolchen.lib.tracker.data
/**
* 网络类型枚举
* @author chenchong
* 2017/11/28
* 下午5:11
*/
internal enum class TrackerNetworkType() {
/** 未知类型 */
UNKNOWN {
override fun desc(): String = "未知"
},
/** wifi */
WIFI {
override fun desc(): String = "WIFI"
},
/** 2G */
G2 {
override fun desc(): String = "2G"
},
/** 3G */
G3 {
override fun desc(): String = "3G"
},
/** 4G */
G4 {
override fun desc(): String = "4G"
},
/** 暂未处理的类型 */
NO_DEAL {
override fun desc(): String = "未知"
},
/** 断网了 */
NO_NET {
override fun desc(): String = "无网络"
},
/** 飞行模式 */
AIR_MODE {
override fun desc(): String = "飞行模式"
};
abstract fun desc(): String
}
\ No newline at end of file
package com.foolchen.lib.tracker.data
import retrofit2.Response
/**
* 用于存储上报接口的结果及上报的数据
*
* 用于上传结果的回调,内部含有上报的时间,便于在回调时对数据进行移除或者持久化处理
*
* @author chenchong
* 2017/12/7
* 上午11:55
*/
data class TrackerResult(val events: List<TrackerEvent>, val response: Response<String>)
\ No newline at end of file
package com.foolchen.lib.tracker.db
/** Event表约束 */
object EventContract {
const val TABLE_NAME = "Event"
const val ID = "_id"
const val DATA = "data"
const val TIME = "time"
}
\ No newline at end of file
package com.foolchen.lib.tracker.db
import android.content.Context
import android.database.sqlite.SQLiteDatabase
import org.jetbrains.anko.db.*
/**
* 数据库工具
* @author chenchong
* 2017/12/6
* 下午6:39
*/
class TrackerDbOpenHelper(context: Context) : ManagedSQLiteOpenHelper(context, "android_tracker",
null, 1) {
companion object {
private var instance: TrackerDbOpenHelper? = null
@Synchronized
fun getInstance(context: Context): TrackerDbOpenHelper {
if (instance == null) {
instance = TrackerDbOpenHelper(context)
}
return instance!!
}
}
override fun onCreate(db: SQLiteDatabase?) {
db?.createTable(EventContract.TABLE_NAME, true,
EventContract.ID to INTEGER + PRIMARY_KEY + AUTOINCREMENT,
EventContract.DATA to TEXT,
EventContract.TIME to INTEGER)
}
override fun onUpgrade(db: SQLiteDatabase?, oldVersion: Int, newVersion: Int) {
// 此处对表结构进行更新
}
}
internal val Context.database: TrackerDbOpenHelper
get() = TrackerDbOpenHelper.getInstance(this)
\ No newline at end of file
package com.foolchen.lib.tracker.layout
import android.app.Activity
import android.view.ViewGroup
import androidx.core.view.ViewCompat
import com.foolchen.lib.tracker.Tracker
fun wrap(activity: Activity) {
val decorView = activity.window.decorView
if (decorView is ViewGroup) {
val trackLayout = TrackLayout(activity)
trackLayout.registerClickFunc { view, ev, time ->
Tracker.trackView(view, ev, time)
}
trackLayout.registerItemClickFunc { adapterView, view, position, id, ev, time ->
Tracker.trackAdapterView(adapterView, view, position, id, ev, time)
}
decorView.addView(trackLayout,
ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.MATCH_PARENT))
ViewCompat.setElevation(trackLayout,
999F)// 提升布局层次,防止fragmentation等库由于侧滑返回添加的布局导致该布局被覆盖,从而导致点击统计失效
}
}
\ No newline at end of file
package com.foolchen.lib.tracker.layout
import android.content.Context
import android.graphics.Rect
import android.os.Build
import android.util.AttributeSet
import android.view.MotionEvent
import android.view.View
import android.view.ViewGroup
import android.widget.AdapterView
import android.widget.FrameLayout
import android.widget.TextView
import androidx.annotation.RequiresApi
import com.foolchen.lib.tracker.R
/**
* 统计用的Layout
* @author chenchong
* 2017/11/9
* 上午10:07
*/
class TrackLayout : FrameLayout {
private val rect = Rect()
private var clickFunc: ((View, MotionEvent, Long) -> Unit)? = null
private var itemClickFunc: ((AdapterView<*>, View, Int, Long, MotionEvent, Long) -> Unit)? = null
private val listenerInfoField by lazy {
// 通过反射拿到mListenerInfo,并且设置为可访问(用于后续替换点击事件)
val declaredField = View::class.java.getDeclaredField("mListenerInfo")
declaredField.isAccessible = true
return@lazy declaredField
}
constructor(context: Context) : super(context)
constructor(context: Context, attrs: AttributeSet?) : super(context, attrs)
constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int) : super(context, attrs,
defStyleAttr)
override fun onTouchEvent(ev: MotionEvent?): Boolean {
if (ev != null) {
val ac = ev.action and MotionEvent.ACTION_MASK
if (ac == MotionEvent.ACTION_DOWN) {
val hitViews = findHitView(rootView, ev.x.toInt(), ev.y.toInt())
hitViews.forEach {
if (it is AdapterView<*>) {
wrapItemClick(it, ev)
} else {
wrapClick(it, ev)
}
}
}
}
return super.onTouchEvent(ev)
}
internal fun registerClickFunc(func: ((View, MotionEvent, Long) -> Unit)) {
this@TrackLayout.clickFunc = func
}
internal fun registerItemClickFunc(
func: ((AdapterView<*>, View, Int, Long, MotionEvent, Long) -> Unit)) {
this@TrackLayout.itemClickFunc = func
}
/**
* 根据当前坐标值查找对应位置的View
*
* 该方法为递归实现,如果传入的布局为[ViewGroup],则执行递归;否则,则对[View]进行判断。
* 在递归过程中,如果发现[ViewGroup]中没有合适的[View],则会对[ViewGroup]本身进行判断,
* 如果[ViewGroup]本身可点击,则会将[ViewGroup]当做点击的[View]
*/
private fun findHitView(parent: View, x: Int, y: Int): ArrayList<View> {
/*var hitView: View? = null
if (hitAdapterView(parent, x, y)) {
// 如果是AdapterView(ListView、GridView等),则直接返回
// AdapterView需要设置OnItemClickListener,而不是OnClickListener
hitView = parent
} else if (parent !is AdapterView<*>) {
if (parent is ViewGroup && parent.childCount > 0) {
val childCount = parent.childCount
for (i in 0 until childCount) {
val child = parent.getChildAt(i)
hitView = findHitView(child, x, y)
// 如果hitView不为空,则直接返回该View
if (hitView != null) {
break
}
}
// 如果查找了所有的子View,都没有找到可点击的View
if (hitView == null && parent.isClickable && hitPoint(parent, x, y)) {
// 此时如果parent可点击,则认为点击的就是parent
hitView = parent
}
} else if (parent.isClickable && hitPoint(parent, x, y)) {
// 如果已经没有子View/或者本身为View,并且View可点击,则认为点击的就是该View
hitView = parent
}
}*/
val hitViews = ArrayList<View>()
if (parent.isVisible() && parent.hitPoint(x, y)) {
// 仅在parent可见,并且命中了点击位置时才对该parent进行判断/递归查找,减少查找的次数,提高效率
if (parent is AdapterView<*>) {
hitViews.add(parent)
// 由于在AdapterView中可能会有局部的View可点击的情况,故此处需要对AdapterView中的子View进行递归查询
// 如果子View可点击,则只会触发子View的点击,而不会触发AdapterView的点击
findHitViewsInGroup(parent, x, y, hitViews)
} else if (parent is ViewGroup) {
// 如果是ViewGroup,则去对其子View进行查询
findHitViewsInGroup(parent, x, y, hitViews)
} else if (parent !is ViewGroup && parent.isClickable) {
// 如果parent本身不是ViewGroup,且可点击,则当做可触发点击事件的View返回
hitViews.add(parent)
}
}
return hitViews
}
/**
* 对ViewGroup中的所有子View进行查询,如果子View中没有符合条件的View
* 则会对父View进行检查,如果父View可点击,则List中会包含父View
*/
private fun findHitViewsInGroup(parent: ViewGroup, x: Int, y: Int,
hitViews: ArrayList<View>) {
val childCount = parent.childCount
for (i in 0 until childCount) {
val child = parent.getChildAt(i)
val hitChildren = findHitView(child, x, y)
if (hitChildren.isNotEmpty()) {
hitViews.addAll(hitChildren)
} else if (child.isVisible() && child.isClickable && child.hitPoint(x, y)) {
if (child is ViewGroup) {
val count = child.childCount
var view = child
for (j in 0 until count) {
if(child.getChildAt(j) is TextView){
view = child.getChildAt(j)
}
}
hitViews.add(view)
} else {
hitViews.add(child)
}
}
}
}
private fun View.isVisible(): Boolean = this.visibility == View.VISIBLE
/**
* 判断一个View是否包含了对应的坐标
*/
private fun View.hitPoint(x: Int, y: Int): Boolean {
this.getGlobalVisibleRect(rect)
return rect.contains(x, y)
}
/**
* 对View的点击事件进行包装,便于增加统计代码
*/
private fun wrapClick(view: View, ev: MotionEvent) {
if (view.hasOnClickListeners()) {
val viewInfo = listenerInfoField.get(view)
val clickInfo = viewInfo?.javaClass?.getDeclaredField("mOnClickListener")
val source = clickInfo?.get(viewInfo) as? OnClickListener
source?.let {
// 如果source已经是ClickWrapper则不需继续处理
if (it !is ClickWrapper) {
// 如果source不是ClickWrapper,则首先尝试复用原先已有的ClickWrapper(可能在RecyclerView中对View重新设置了OnClickListener,但是其ClickWrapper对象还在)
var wrapper = view.getTag(R.id.android_tracker_click_listener)
if (wrapper is ClickWrapper) {
// 如果原先已存在ClickWrapper
// 则对比原先ClickWrapper中的OnClickListener是否与source为同一个实例
if (wrapper.source != source) {
wrapper.source = source
}
} else {
// 如果原先不存在ClickWrapper,则创建ClickWrapper
wrapper = ClickWrapper(source, ev)
view.setTag(R.id.android_tracker_click_listener, wrapper)
}
clickInfo.let {
it.isAccessible = true
it.set(viewInfo, wrapper)
}
}
}
}
}
/**
* 对AdapterView条目的点击监听进行包装,便于增加统计代码
*/
private fun wrapItemClick(view: AdapterView<*>, ev: MotionEvent) {
val source = view.onItemClickListener
source?.let {
if (source !is ItemClickWrapper) {
// 如果原先设置的监听不为ItemClickWrapper类型,则对source进行包装
// 如果已经为ItemClickWrapper,则直接复用原先监听即可,不需要再次包装
view.onItemClickListener = ItemClickWrapper(source, ev)
}
}
}
/**
* [View.OnClickListener]的包装类,内部包装了View的原[View.OnClickListener],并且增加了点击统计
*
* @param source View的原[View.OnClickListener]
* @param ev 触发点击时的坐标位置
*/
private inner class ClickWrapper(var source: OnClickListener?,
val ev: MotionEvent) : OnClickListener {
override fun onClick(view: View) {
source?.let {
// 由于clickFunc的执行是在实际的onClick()方法后,故在统计时可能会有延时
// 此处首先记录onClick()的触发时间,在统计时对时间进行纠正,这样数据在入库时
// 会记录正确的时间
// 但是在日志中查看和上报过程中,可能还是会有滞后
val time = System.currentTimeMillis()
source?.onClick(view)
clickFunc?.invoke(view, ev, time)
}
}
}
/**
* [AdapterView.OnItemClickListener]的包装类,内部包装了原监听器,并且增加了点击统计
*/
private inner class ItemClickWrapper(val source: AdapterView.OnItemClickListener,
val ev: MotionEvent) : AdapterView.OnItemClickListener {
override fun onItemClick(adapterView: AdapterView<*>, view: View, position: Int, id: Long) {
val time = System.currentTimeMillis()
source.onItemClick(adapterView, view, position, id)
itemClickFunc?.invoke(adapterView, view, position, id, ev, time)
}
}
}
\ No newline at end of file
package com.foolchen.lib.tracker.lifecycle
import android.app.Application
import android.content.Context
/**
* 辅助获取[Context]的接口
*
* 需要在APP的[Application]类中实现该接口
*
* @author chenchong
* 2017/11/28
* 下午8:43
*/
interface ITrackerContext {
fun getApplicationContext(): Context
fun registerActivityLifecycleCallbacks(callbacks: Application.ActivityLifecycleCallbacks)
}
\ No newline at end of file
package com.foolchen.lib.tracker.lifecycle
import androidx.fragment.app.Fragment
/**
* 用于监听[Fragment]的可见性
* @author chenchong
* 2017/11/4
* 下午12:02
*/
interface ITrackerFragmentVisible {
/**
* 在Fragment中的setUserVisibleHint()和onHidden()方法被调用时,同步调用该方法
* 以便于能够正确的观察到Fragment状态的变化
*/
fun onFragmentVisibilityChanged(visible: Boolean, f: Fragment?)
}
\ No newline at end of file
package com.foolchen.lib.tracker.lifecycle
import android.content.Context
/**
* 用于获取页面信息的接口
* @author chenchong
* 2017/11/4
* 下午1:45
*/
interface ITrackerHelper {
/**
* 获取当前页面的名称
*
* **注意:在Fragment中使用时,不要使用Fragment.getString()等方法。
* 由于setUserVisibleHint()方法的调用可能在所有的生命周期之前,如果调用Fragment.getString()等方法可能会导致错误。
* 此处应该使用方法回传的[Context]**
*/
fun getTrackName(context: Context): String?
/**
* 获取当前页面需要附加的参数
*
* **注意:在Fragment中使用时,不要使用Fragment.getString()等方法。
* 由于setUserVisibleHint()方法的调用可能在所有的生命周期之前,如果调用Fragment.getString()等方法可能会导致错误
* 此处应该使用方法回传的[Context]**
*/
fun getTrackProperties(context: Context): Map<String, *>?
}
\ No newline at end of file
package com.foolchen.lib.tracker.lifecycle
import android.app.Activity
/**
* 获取当前[Activity]/[Fragment]中所有[Fragment]的接口<p/>
* 注意:获取到的[Fragment]都为弱引用
* @author chenchong
* 2017/11/4
* 下午12:04
*/
interface ITrackerIgnore {
/**
* 强制规定是否忽略当前页面的统计
* 如果该方法返回false,则会被强制当做需要被统计的Fragment进行统计,以免由于Fragment没有及时初始化,导致监听的Fragment错误
*/
fun isIgnored(): Boolean
}
\ No newline at end of file
package com.foolchen.lib.tracker.lifecycle
import android.app.Activity
import android.app.Application
import android.os.Build
import android.os.Bundle
import androidx.annotation.RequiresApi
import androidx.fragment.app.FragmentActivity
import com.foolchen.lib.tracker.Tracker
import com.foolchen.lib.tracker.layout.wrap
import com.foolchen.lib.tracker.utils.getTrackName
import com.foolchen.lib.tracker.utils.getTrackProperties
import com.foolchen.lib.tracker.utils.getTrackTitle
import java.lang.ref.WeakReference
/**
* 该类用于监听项目中所有Activity的生命周期<p/>
* 需要在[Application]中初始化,以便于能够及时监听所有的[Activity]
* @author chenchong
* 2017/11/4
* 上午11:26
*/
@RequiresApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH)
class TrackerActivityLifeCycle : Application.ActivityLifecycleCallbacks {
private val fragmentLifeCycle = TrackerFragmentLifeCycle()
private val refs = ArrayList<WeakReference<Activity>>()
fun getFragmentLifeCycle(): TrackerFragmentLifeCycle {
return fragmentLifeCycle
}
override fun onActivityCreated(activity: Activity?, savedInstanceState: Bundle?) {
if (activity != null) {
wrap(activity)
}
if (activity is FragmentActivity) {
activity.supportFragmentManager.registerFragmentLifecycleCallbacks(fragmentLifeCycle, true)
}
}
override fun onActivityStarted(activity: Activity?) {
if (/*Tracker.isBackground &&*/ refs.isEmpty()) {
// 此处仅从后台切换到前台时触发,首次触发为初始化时,防止首次触发滞后
Tracker.onForeground()
}
activity?.let {
refs.add(WeakReference(activity))
}
}
override fun onActivityResumed(activity: Activity?) {
if (activity != null) {
if (activity is ITrackerIgnore) {
if (!activity.isIgnored()) {
// 内部没有Fragment,直接进行统计
track(activity)
}
} else {
// Activity内部没有Fragment,则直接进行统计
track(activity)
}
}
}
override fun onActivityPaused(activity: Activity?) {
}
override fun onActivityStopped(activity: Activity?) {
activity?.let {
for (ref in refs) {
if (ref.get() == activity) {
refs.remove(ref)
break
}
}
}
if (refs.isEmpty()) {
Tracker.onBackground()
}
}
override fun onActivityDestroyed(activity: Activity?) {
if (activity is FragmentActivity) {
activity.supportFragmentManager.unregisterFragmentLifecycleCallbacks(fragmentLifeCycle)
}
}
override fun onActivitySaveInstanceState(activity: Activity?, outState: Bundle?) {
}
private fun track(activity: Activity) {
Tracker.referer = Tracker.screenName
Tracker.refererClass = Tracker.screenClass
Tracker.screenName = activity.getTrackName()
Tracker.screenClass = activity.javaClass.canonicalName ?:""
Tracker.screenTitle = activity.getTrackTitle()
Tracker.parent = ""
Tracker.parentClass = ""
Tracker.trackScreen(activity.getTrackProperties())
}
}
\ No newline at end of file
package com.foolchen.lib.tracker.lifecycle
import androidx.fragment.app.Fragment
import androidx.fragment.app.FragmentManager
import com.foolchen.lib.tracker.Tracker
import com.foolchen.lib.tracker.utils.getTrackName
import com.foolchen.lib.tracker.utils.getTrackProperties
import com.foolchen.lib.tracker.utils.getTrackTitle
import java.lang.ref.WeakReference
import java.util.*
/**
* 该类用于监听所有Fragment的生命周期<p/>
*
* @author chenchong
* 2017/11/4
* 上午11:27
*/
class TrackerFragmentLifeCycle : FragmentManager.FragmentLifecycleCallbacks(), ITrackerFragmentVisible {
private val refs = ArrayList<WeakReference<Fragment>>()
private val trackedRefs = WeakHashMap<Fragment, Boolean>()
// override fun onFragmentResumed(fm: FragmentManager?, f: Fragment?) {
// if (f != null) {
// refs.add(WeakReference(f))
// }
// f?.let {
// if (isAncestorVisible(f) && !isParentFragment(f) && isVisible(f)) {
// // 如果父Fragment可见
// // 并且本身不是父Fragment
// // 并且本身可见,则进行统计
// track(f)
// }
// }
// }
override fun onFragmentVisibilityChanged(visible: Boolean, f: Fragment?) {
if (visible) {
f?.let {
// 由于内嵌的Fragment不会触发onHiddenChange()和setUserVisibleHint()方法,故此处只能根据其父Fragment来判断
findVisibleChildren(f).forEach {
track(it)
}
}
} else {
trackedRefs.remove(f)
}
}
// override fun onFragmentPaused(fm: FragmentManager?, f: Fragment?) {
// // 在Fragment不可见时对应的移除该Fragment
// for (ref in refs) {
// if (ref.get() == f) {
// refs.remove(ref)
// break
// }
// }
// trackedRefs.remove(f)
// }
private fun track(f: Fragment) {
if (trackedRefs[f] == true) {
return
}
val screenName = f.getTrackName()
Tracker.referer = Tracker.screenName
Tracker.refererClass = Tracker.screenClass
Tracker.screenName = screenName
Tracker.screenClass = f.javaClass.canonicalName?:""
Tracker.screenTitle = f.getTrackTitle()
var parentAlias = ""
var parent = ""
val parentFragment = f.parentFragment
if (parentFragment != null) {
parentAlias = parentFragment.getTrackName()
parent = parentFragment.javaClass.canonicalName?:""
} else {
val activity = f.activity
if (activity != null) {
parentAlias = activity.getTrackName()
parent = activity.javaClass.canonicalName?:""
}
}
Tracker.parent = parentAlias
Tracker.parentClass = parent
Tracker.trackScreen(f.getTrackProperties())
trackedRefs[f] = true
}
/**
* 根据一个Fragment,从[refs]中查找其所有的子Fragment/子孙Fragment
* @param parent 要查找的父/祖先Fragment
* @return 查找到的Fragment,如果不存在Fragment,则返回的列表元素数量为0
*/
private fun findVisibleChildren(parent: Fragment): List<Fragment> {
val children = ArrayList<Fragment>()
refs.filter {
// 首先过滤掉已经被忽略的Fragment
val child = it.get()
!(child is ITrackerIgnore && child.isIgnored())
}.filter {
// 此处用于过滤掉父Fragment不符的Fragment
val child = it.get()
child != null && checkParent(child, parent)
}.filter {
// 此处用于过滤掉不可见的Fragment
val child = it.get()
child != null && !child.isHidden && child.userVisibleHint && isAncestorVisible(child)
}.forEach {
val child = it.get()
child?.let { children.add(child) }
}
// 如果没有符合需要的children,则其自身就为符合需要的Fragment
if (children.isEmpty() && !isParentFragment(parent)) {
children.add(parent)
}
return children
}
/**
* 判断一个Fragment是否可见
* @param f 要判断的Fragment
* @return 在Fragment的[Fragment.isHidden]为false,并且[Fragment.getUserVisibleHint]为true时,才返回true;否则false
*/
private fun isVisible(f: Fragment): Boolean = !f.isHidden && f.userVisibleHint
/**
* 判断父Fragment是否可见
* @return 父Fragment不存在时,直接返回true;父Fragment可见时返回true;其他情况时返回false
*/
private fun isParentVisible(f: Fragment): Boolean {
val parent = f.parentFragment
return if (parent == null) {
true
} else {
!parent.isHidden && parent.userVisibleHint
}
}
/**
* 检查一个Fragment的祖先是否都可见
* @param f 要检查的Fragment
* @return 如果祖先都可见则返回true;如果不存在祖先(其直接宿主为Activity),则返回true;否则返回false
*/
private fun isAncestorVisible(f: Fragment): Boolean {
val parent = f.parentFragment
return if (parent == null) {
true
} else if (!parent.isHidden && parent.userVisibleHint) {
isAncestorVisible(parent)
} else {
false
}
}
/**
* 判断是否为其他Fragment的父级(实现了[IFragment]接口,并且[ITrackerIgnore.isIgnored]的值为true)
* @param f 需要检查的Fragment
* @return 在[f]实现了[ITrackerIgnore]接口,并且[ITrackerIgnore.isIgnored]值为true时,返回true;其他情况下返回false
*/
private fun isParentFragment(f: Fragment): Boolean = f is ITrackerIgnore && f.isIgnored()
/**
* 检查一个[parent]是否是[child]的父Fragment/祖先Fragment
*/
private fun checkParent(child: Fragment, parent: Fragment): Boolean {
val parentFragment = child.parentFragment
return if (parentFragment != null) {
if (parentFragment == parent) {// 如果是父Fragment,则直接返回true
true
} else {// 如果不是父Fragment,并且还存在祖先Fragment,则进入递归
checkParent(parentFragment, parent)
}
} else {// 如果不存在父Fragment,则直接返回false
false
}
}
}
\ No newline at end of file
package com.foolchen.lib.tracker.service
import okhttp3.MediaType
import okhttp3.RequestBody
import okhttp3.ResponseBody
import retrofit2.Converter
import retrofit2.Retrofit
import java.lang.reflect.Type
/**
* @author chenchong
* 2017/12/6
* 下午4:40
*/
class ToStringConverterFactory : Converter.Factory() {
private val MEDIA_TYPE = MediaType.parse("text/plain")
override fun responseBodyConverter(type: Type, annotations: Array<Annotation>,
retrofit: Retrofit): Converter<ResponseBody, *>? {
return if (String::class.java == type) {
Converter<ResponseBody, String> { value -> value.string() }
} else null
}
override fun requestBodyConverter(type: Type?, parameterAnnotations: Array<out Annotation>?,
methodAnnotations: Array<out Annotation>?, retrofit: Retrofit?): Converter<*, RequestBody>? {
return if (String::class.java == type) {
Converter<String, RequestBody> { value -> RequestBody.create(MEDIA_TYPE, value) }
} else null
}
}
\ No newline at end of file
package com.foolchen.lib.tracker.service
import io.reactivex.Observable
import retrofit2.Response
import retrofit2.http.Field
import retrofit2.http.FormUrlEncoded
import retrofit2.http.POST
import retrofit2.http.Path
/**
* 上报接口的定义
* @author chenchong
* 2017/12/6
* 下午3:18
*/
interface TrackerAPIDef {
/**
* 上报数据接口
*
* @param path 上报数据的接口地址
* @param projectName 要上报数据的项目名称
* @param data 要上报的JSON数据
* @param mode 上报数据的模式
*/
@FormUrlEncoded
@POST("{url}")
fun report(@Path("url") path: String, @Field("project") projectName: String, @Field(
"data") data: String, @Field("mode") mode: Int): Observable<Response<String>>
}
\ No newline at end of file
package com.foolchen.lib.tracker.service
import android.util.Log
import com.foolchen.lib.tracker.Tracker
import com.foolchen.lib.tracker.data.TrackerEvent
import com.foolchen.lib.tracker.data.TrackerMode
import com.foolchen.lib.tracker.db.EventContract
import com.foolchen.lib.tracker.db.database
import com.foolchen.lib.tracker.utils.GSON
import com.foolchen.lib.tracker.utils.encodeBASE64
import com.foolchen.lib.tracker.utils.urlEncode
import io.reactivex.Observable
import io.reactivex.Observer
import io.reactivex.disposables.Disposable
import io.reactivex.schedulers.Schedulers
import okhttp3.OkHttpClient
import org.jetbrains.anko.db.*
import retrofit2.Response
import retrofit2.Retrofit
import retrofit2.adapter.rxjava2.RxJava2CallAdapterFactory
import java.util.*
import java.util.concurrent.TimeUnit
import kotlin.reflect.KFunction0
/**
* 用于网络请求
* @author chenchong
* 2017/11/4
* 下午3:13
*/
object TrackerService {
private val TAG = "TrackerService"
/** 每次上报数据的数量(每几条数据触发一次上报) */
internal var mReportThreshold = 10
private val mService = createRetrofit().create(TrackerAPIDef::class.java)
/** 用于存储已触发的事件生成的JSON数据 */
private val mEvents = ArrayList<TrackerEvent>()
/**
* 尝试上报数据
*
* @param event 本次要统计的事件
* @param background 本次事件是否为切换到后台
* @param foreground 本次事件是否为切换到前台
*/
fun report(event: TrackerEvent, background: Boolean = false, foreground: Boolean = false) {
if (Tracker.mode == TrackerMode.RELEASE) {
// 如果为release模式,则此处需要考虑同步、批量上传等处理
// 首先将事件添加到事件列表中
mEvents.add(event)
// 然后判断事件数量是否达到了上传的阈值
if (mEvents.size >= threshold() || background || foreground) {
// 如果达到了阈值,则进行后续操作
val reportEvents = prepareEvents()// 准备要上报的事件
report(reportEvents,
// 在从后台切换到前台时,将反序列化方法当做实参传入,用于将数据库中的事件读取出来
if (foreground) this@TrackerService::deserializeEvents else null,
// 如果当前要统计的事件为切换到后台,则将序列化方法当做实参传递,用于在上报失败后将数据存储到数据库
// 否则,将恢复事件方法当做实参传递,在上报失败后,将数据再次添加到候选列表中
if (background) this@TrackerService::serializeEvents else this@TrackerService::addEvents)
}
} else if (Tracker.mode == TrackerMode.DEBUG_TRACK) {
// 如果为debug&track模式,则直接上传数据,并且不关注失败
mService.report(Tracker.servicePath!!, Tracker.projectName!!,
prepareReportJson(listOf(event)),
mode()).subscribeOn(Schedulers.io()).observeOn(Schedulers.io()).subscribe(
IgnoreObserver())
}
}
private fun report(events: List<TrackerEvent>, deserializeFunc: KFunction0<List<TrackerEvent>>?,
failureFunc: (List<TrackerEvent>) -> Unit) {
Observable
.create<List<TrackerEvent>> {
// 如果传入的反序列化方法不为空,则将反序列化的数据传递到下一步
// 否则,则传递一个空的List
it.onNext(deserializeFunc?.invoke() ?: Collections.emptyList<TrackerEvent>())
it.onComplete()
}
.map {
// 此处,在反序列化数据不为空的情况下,将反序列化数据添加到要上报的数据中
(events as MutableList<TrackerEvent>).addAll(it) // 由于已知此处肯定为ArrayList,故直接进行转换
events
}
.flatMap {
mService.report(Tracker.servicePath!!, Tracker.projectName!!,
prepareReportJson(it), mode())
}
.subscribeOn(Schedulers.io())
.observeOn(Schedulers.io())
.subscribe(object : IgnoreObserver() {
override fun onNext(t: Response<String>) {
super.onNext(t)
if (t.code() == 200) {
// 接口请求成功
// 则此时不做任何处理
} else {
// 接口请求失败
// 则此时将上报失败的事件添加回待上报的事件列表中
failureFunc.invoke(events)
}
}
override fun onError(e: Throwable) {
super.onError(e)
// 接口请求失败
// 则此时将上报失败的事件添加回待上报的事件列表中
failureFunc.invoke(events)
}
})
}
/** 根据枚举类型来计算上报接口时使用的模式 */
private fun mode(): Int {
return when (Tracker.mode) {
TrackerMode.DEBUG_ONLY -> 1
TrackerMode.DEBUG_TRACK -> 2
else -> {
3
}
}
}
/** 根据当前的上报模式计算触发上报的阈值 */
private fun threshold(): Int {
return when (Tracker.mode) {
TrackerMode.DEBUG_ONLY -> 1
TrackerMode.DEBUG_TRACK -> 1
TrackerMode.RELEASE -> mReportThreshold
else -> -1
}
}
/**
* 准备上报用的事件
*
* 该方法会将要上传的事件从事件列表中暂时移除,并生成一个新的事件列表用于上报。该操作为同步操作,防止发生错误
*/
@Synchronized
private fun prepareEvents(): List<TrackerEvent> {
val reportEvents = ArrayList<TrackerEvent>(mEvents)
mEvents.removeAll(reportEvents)
return reportEvents
}
/** 将一批事件添加到事件列表中,该操作为同步操作 */
@Synchronized
private fun addEvents(events: List<TrackerEvent>) {
mEvents.addAll(events)
// 在添加到列表中后,根据时间对所有的时间进行排序
mEvents.sortBy { it.time }
}
/** 准备上传使用的JSON数据 */
private fun prepareReportJson(events: List<TrackerEvent>): String {
val array = ArrayList<Map<String, Any>>(events.size)
events.mapTo(array) { it.build() }
var json = GSON.toJson(array)
if (Tracker.isBase64EncodeEnable) {
json = json.encodeBASE64()
}
if (Tracker.isUrlEncodeEnable) {
json = json.urlEncode()
}
return json
}
/** 对事件列表进行持久化 */
private fun serializeEvents(events: List<TrackerEvent>) {
Tracker.trackContext.getApplicationContext().let {
it.database.use {
transaction {
events.forEach {
insert(EventContract.TABLE_NAME, EventContract.DATA to GSON.toJson(it),
EventContract.TIME to it.time)
}
}
}
}
}
/** 对事件列表进行反持久化 */
private fun deserializeEvents(): List<TrackerEvent> {
val events = ArrayList<TrackerEvent>()
Tracker.trackContext.getApplicationContext().database.use {
// 查找出所有的数据
select(EventContract.TABLE_NAME, EventContract.DATA).orderBy(EventContract.TIME,
SqlOrderDirection.ASC)
.exec {
val deserializeEvents = parseList(object : RowParser<TrackerEvent> {
override fun parseRow(columns: Array<Any?>): TrackerEvent {
val data = columns[0] as String
return GSON.fromJson(data, TrackerEvent::class.java)
}
})
events.addAll(deserializeEvents)
}
// 然后将所有的数据从数据库中删除
delete(EventContract.TABLE_NAME)
}
return events
}
private var mRetrofit: Retrofit? = null
private fun createRetrofit(): Retrofit {
if (Tracker.serviceHost.isNullOrBlank()) {
throw RuntimeException("serviceHost未设置")
}
if (Tracker.servicePath.isNullOrBlank()) {
throw RuntimeException("servicePath未设置")
}
if (Tracker.projectName.isNullOrBlank()) {
throw RuntimeException("projectName未设置")
}
if (mRetrofit == null) {
synchronized(this) {
val builder = Retrofit.Builder()
builder.baseUrl(Tracker.serviceHost!!)
builder.client(createOkHttpClient())
mRetrofit = builder
.addConverterFactory(ToStringConverterFactory())
.addCallAdapterFactory(RxJava2CallAdapterFactory.create())
.build()
}
}
return mRetrofit!!
}
private fun createOkHttpClient(): OkHttpClient {
return OkHttpClient.Builder().connectTimeout(Tracker.timeoutDuration,
TimeUnit.MILLISECONDS)
.readTimeout(Tracker.timeoutDuration, TimeUnit.MILLISECONDS)
.writeTimeout(Tracker.timeoutDuration, TimeUnit.MILLISECONDS).build()
}
/** [Observer]的实现类,默认实现忽略所有回调。如有需要可覆写对应方法 */
private open class IgnoreObserver : Observer<Response<String>> {
override fun onError(e: Throwable) {
if (Tracker.mode != TrackerMode.RELEASE) {
Log.w(TAG, "onError() , ex = $e")
}
}
override fun onComplete() {
}
override fun onNext(t: Response<String>) {
if (Tracker.mode != TrackerMode.RELEASE) {
Log.d(TAG, "onNext() , response = $t")
}
}
override fun onSubscribe(d: Disposable) {
}
}
}
\ No newline at end of file
package com.foolchen.lib.tracker.utils
import android.util.Base64
import java.net.URLEncoder
fun String.encodeBASE64() = String(
Base64.encode(toByteArray(Charsets.UTF_8), Base64.NO_WRAP), Charsets.UTF_8)
fun String.urlEncode(): String = URLEncoder.encode(this, "UTF-8")
\ No newline at end of file
package com.foolchen.lib.tracker.utils
import android.Manifest
import android.annotation.SuppressLint
import android.content.Context
import android.content.pm.PackageManager
import android.net.ConnectivityManager
import android.os.Build
import android.telephony.TelephonyManager
import android.util.DisplayMetrics
import android.view.WindowManager
import androidx.core.content.ContextCompat
import com.foolchen.lib.tracker.BuildConfig
import com.foolchen.lib.tracker.data.DISTINCT_ID
import com.foolchen.lib.tracker.data.TrackerMNC
import com.foolchen.lib.tracker.data.TrackerNetworkType
internal val buildInObject: HashMap<String, Any> = HashMap()
internal val buildInLib: HashMap<String, Any> = HashMap()
internal val buildInProperties: HashMap<String, Any> = HashMap()
internal var buildInUUID = ""
// 用于标识是否已经登录
internal var isLogin = false
/**
* 获取内置属性
*/
internal fun initBuildInProperties(context: Context) {
buildInUUID = context.getUUID()
if (!isLogin) {
buildInObject.put(DISTINCT_ID, buildInUUID)
}
buildInLib.put("lib", "Android")
buildInLib.put("lib_version", BuildConfig.VERSION_NAME)
buildInLib.put("app_version", context.getVersionName())
buildInProperties.put("lib", "Android")
buildInProperties.put("lib_version", BuildConfig.VERSION_NAME)
buildInProperties.put("app_version", context.getVersionName())
buildInProperties.put("manufacturer", Build.BRAND)
buildInProperties.put("model", Build.MODEL)
buildInProperties.put("os", "Android")
buildInProperties.put("os_version", Build.VERSION.RELEASE)
buildInProperties.put("os_version", Build.VERSION.RELEASE)
buildInProperties.put("screen_height", context.getScreenWidth())
buildInProperties.put("screen_width", context.getScreenHeight())
buildInProperties.put("carrier", context.getMNC().desc())
buildInProperties.put("imeicode", context.getIMEI())
buildInProperties.put("device_id", context.getAndroidId())
}
internal fun login(userId: String) {
buildInObject.put(DISTINCT_ID, userId)
isLogin = true
}
internal fun logout() {
buildInObject.put(DISTINCT_ID, buildInUUID)
isLogin = false
}
/**
* 获取当前app的版本名称
*/
private fun Context.getVersionName(): String {
var versionName = ""
try {
val packageInfo = packageManager.getPackageInfo(packageName, PackageManager.GET_CONFIGURATIONS)
versionName = packageInfo.versionName
} catch (e: PackageManager.NameNotFoundException) {
}
return versionName
}
/**
* 使用上下文对象获取当前手机屏幕宽度
*/
private fun Context.getScreenWidth(): Int {
val wm = getSystemService(Context.WINDOW_SERVICE) as WindowManager
val display = wm.defaultDisplay
val metrics = DisplayMetrics()
display.getMetrics(metrics)
return metrics.widthPixels
}
/**
* 使用上下文对象获取当前手机屏幕高度
*/
private fun Context.getScreenHeight(): Int {
val wm = getSystemService(Context.WINDOW_SERVICE) as WindowManager
val display = wm.defaultDisplay
val metrics = DisplayMetrics()
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
display.getRealMetrics(metrics)
} else {
display.getMetrics(metrics)
}
return metrics.heightPixels
}
@SuppressLint("MissingPermission")
/**
* 判断当前网络状态是否为WiFi
*/
internal fun Context.isWiFi(): Boolean {
return if (ContextCompat.checkSelfPermission(this,
Manifest.permission.ACCESS_NETWORK_STATE) == PackageManager.PERMISSION_GRANTED) {
val connectivityManager = this.getSystemService(
Context.CONNECTIVITY_SERVICE) as ConnectivityManager
// 执行到此处时,已有权限,忽略该警告
val activeNetInfo = connectivityManager.activeNetworkInfo
activeNetInfo != null && activeNetInfo.type == ConnectivityManager.TYPE_WIFI
} else {
false
}
}
private fun Context.getMNC(): TrackerMNC {
if (ContextCompat.checkSelfPermission(this,
Manifest.permission.READ_PHONE_STATE) == PackageManager.PERMISSION_GRANTED) {
val telManager = this.getSystemService(Context.TELEPHONY_SERVICE) as TelephonyManager
val operator = telManager.networkOperator
// int mcc = Integer.parseInt(operator.substring(0, 3));//移动国家代码(中国的为460);
try {//防止启动就崩溃,这里加上异常处理
val mnc = Integer.parseInt(operator.substring(3))//,移动网络号码(中国移动为0,2,中国联通为1,中国电信为3);
when (mnc) {
0 -> return TrackerMNC.CMCC
1 -> return TrackerMNC.CUCC
2 //因为移动网络编号46000下的IMSI已经用完,所以虚拟了一个46002编号,134/159号段使用了此编号 //中国移动
-> return TrackerMNC.CMCC
3 -> return TrackerMNC.CTCC
11//在电信4g的情况下返回46011
-> return TrackerMNC.CTCC
}
} catch (e: Exception) {
e.printStackTrace()
}
}
return TrackerMNC.OTHER
}
/**
* 根据[Context]来获取网络类型
*
* 需要 android.permission.ACCESS_NETWORK_STATE 权限
*
* @return [NetType]
*/
internal fun Context.getNetworkType(): TrackerNetworkType {
if (ContextCompat.checkSelfPermission(this,
Manifest.permission.ACCESS_NETWORK_STATE) == PackageManager.PERMISSION_GRANTED) {
if (isWiFi()) {
return TrackerNetworkType.WIFI
}
if (!isNetworkAvailable()) {
return TrackerNetworkType.NO_NET
}
//下面类型的判断不包含WIFI,如果是wifi类型会返回 UNKNOWN
val telManager = getSystemService(Context.TELEPHONY_SERVICE) as TelephonyManager
val networkType = telManager.networkType
return when (networkType) {
TelephonyManager.NETWORK_TYPE_LTE // 4G
, TelephonyManager.NETWORK_TYPE_HSPAP, TelephonyManager.NETWORK_TYPE_EHRPD -> TrackerNetworkType.G4
TelephonyManager.NETWORK_TYPE_UMTS // 3G
, TelephonyManager.NETWORK_TYPE_CDMA, TelephonyManager.NETWORK_TYPE_EVDO_0, TelephonyManager.NETWORK_TYPE_EVDO_A, TelephonyManager.NETWORK_TYPE_EVDO_B, 17//隐藏API
-> TrackerNetworkType.G3
TelephonyManager.NETWORK_TYPE_GPRS // 2G
, TelephonyManager.NETWORK_TYPE_EDGE, 16 -> TrackerNetworkType.G2
TelephonyManager.NETWORK_TYPE_UNKNOWN -> TrackerNetworkType.UNKNOWN
else -> TrackerNetworkType.NO_DEAL
}
} else {
return TrackerNetworkType.UNKNOWN
}
}
@SuppressLint("MissingPermission")
/**
* 当前是否连接网络
*
* 需要 android.permission.ACCESS_NETWORK_STATE 权限
*
* @return true联网,false没有联网
*/
private fun Context.isNetworkAvailable(): Boolean {
if (ContextCompat.checkSelfPermission(this,
Manifest.permission.ACCESS_NETWORK_STATE) == PackageManager.PERMISSION_GRANTED) {
val connectivityManager = this.getSystemService(
Context.CONNECTIVITY_SERVICE) as ConnectivityManager
// 执行到此处时,已有权限,忽略该警告
val activeNetworkInfo = connectivityManager.activeNetworkInfo
return activeNetworkInfo != null && activeNetworkInfo.isConnectedOrConnecting
}
// 默认网络可用
return true
}
@SuppressLint("HardwareIds")
private fun Context.getAndroidId(): String {
return android.provider.Settings.Secure.getString(contentResolver,
android.provider.Settings.Secure.ANDROID_ID) ?: ""
}
private fun Context.getUUID(): String = (Build.BRAND + Build.MODEL).hashCode().toString() + getAndroidId()
@SuppressLint("MissingPermission")
/**
* 获取设备Id(IMEI)
*
* @param context 上下文对象
* @return 设备Id
*/
private fun Context.getIMEI(): String {
var deviceId: String? = null
if (ContextCompat.checkSelfPermission(this,
Manifest.permission.READ_PHONE_STATE) == PackageManager.PERMISSION_GRANTED) {
// 如果有权限才获取设备id,防止崩溃
val tm = getSystemService(Context.TELEPHONY_SERVICE) as TelephonyManager
deviceId = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
tm.imei
} else {
// 执行到此处时,已有权限,忽略该警告
@Suppress("DEPRECATION")
tm.deviceId
}
}
return deviceId ?: ""
}
package com.foolchen.lib.tracker.utils
/**
* 生成唯一的id
*/
fun generateId(seed: String): Long {
return StringBuilder(seed.hashCode()).append(System.currentTimeMillis()).toString().toLong()
}
package com.foolchen.lib.tracker.utils
import android.app.Activity
import android.util.Log
import android.view.MotionEvent
import android.view.View
import android.widget.TextView
import androidx.fragment.app.Fragment
import com.foolchen.lib.tracker.Tracker
import com.foolchen.lib.tracker.data.ELEMENT_CONTENT
import com.foolchen.lib.tracker.data.ELEMENT_TYPE
import com.foolchen.lib.tracker.data.TrackerEvent
import com.foolchen.lib.tracker.data.TrackerMode
import com.foolchen.lib.tracker.lifecycle.ITrackerHelper
import com.foolchen.lib.tracker.service.TrackerService
import com.google.gson.Gson
import com.google.gson.GsonBuilder
val PRETTY_GSON: Gson by lazy {
GsonBuilder().setPrettyPrinting().create()
}
val GSON: Gson by lazy {
Gson()
}
const val TAG = "AndroidTracker"
/**
* 用于获取Activity的名字
*/
internal fun Activity.getTrackName(): String {
var name: String? = null
if (this is ITrackerHelper) {
name = this.getTrackName(Tracker.trackContext.getApplicationContext())
}
if (name.isNullOrEmpty()) {
name = this.javaClass.canonicalName
}
if (name.isNullOrEmpty()) {
name = this.toString()
}
return name!!
}
/**
* 用于获取Fragment的名字
*/
internal fun Fragment.getTrackName(): String {
var name: String? = null
if (this is ITrackerHelper) {
name = this.getTrackName(Tracker.trackContext.getApplicationContext())
}
if (name.isNullOrEmpty()) {
name = this.javaClass.canonicalName
}
if (name.isNullOrEmpty()) {
name = this.toString()
}
return name!!
}
internal fun Activity?.getTrackTitle(): String = this?.title?.toString() ?: ""
internal fun Fragment.getTrackTitle(): String = activity?.getTrackTitle() ?: ""
/**
* 获取Activity中需要的附加属性
*/
internal fun Activity.getTrackProperties(): Map<String, Any> {
val properties = HashMap<String, Any>()
if (this is ITrackerHelper) {
this.getTrackProperties(Tracker.trackContext.getApplicationContext())?.let {
it.filter { it.value != null }.forEach {
properties[it.key] = it.value!!
}
}
}
return properties
}
/**
* 获取Fragment中需要的附加属性
*/
internal fun Fragment.getTrackProperties(): Map<String, Any> {
val properties = HashMap<String, Any>()
if (this is ITrackerHelper) {
this.getTrackProperties(Tracker.trackContext.getApplicationContext())?.let {
it.filter { it.value != null }.forEach {
properties[it.key] = it.value!!
}
}
}
return properties
}
@Suppress("UNUSED_PARAMETER")
internal fun View.getTrackProperties(ev: MotionEvent?): Map<String, Any> {
// 首先获取元素本身的属性
val properties = HashMap<String, Any>()
properties[ELEMENT_TYPE] = this.javaClass.name
if (this is TextView) {
properties[ELEMENT_CONTENT] = this.text?.toString() ?: ""
}
/*ev?.let {
properties.put(ELEMENT_X, ev.x)
properties.put(ELEMENT_Y, ev.y)
}*/
// 然后获取开发者附加的属性
val additionalProperties = Tracker.elementsProperties[this]
additionalProperties?.filter { it.value != null }?.forEach {
properties[it.key] = it.value!!
}
Tracker.elementsProperties.remove(this)
return properties
}
/**
* 对事件进行统计
*
* @param event 要统计的事件
* @param background 当前事件是否为切换到后台
* @param foreground 当前事件是否为切换到前台
*/
internal fun trackEvent(event: TrackerEvent, background: Boolean = false,
foreground: Boolean = false) {
// 此处尝试对数据进行上报
// 具体的上报策略由TrackerService掌控
// TrackerService.report(event, background, foreground)
// 打印日志
log(event)
}
internal fun log(event: TrackerEvent) {
if (Tracker.mode == TrackerMode.DEBUG_ONLY) {
log(event.toPrettyJson())
} else if (Tracker.mode == TrackerMode.DEBUG_TRACK) {
log(event.toPrettyJson())
}
}
private fun log(s: String) {
Log.d(TAG, s)
}
<?xml version="1.0" encoding="utf-8"?>
<resources>
<item name="android_tracker_click_listener" type="id"/>
</resources>
\ No newline at end of file
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment