[taxi乘客无人化]
This commit is contained in:
yangyakun
2023-08-03 20:16:37 +08:00
committed by zhongchao
parent f34b2e68d8
commit 4058827d43
414 changed files with 9759 additions and 2 deletions

View File

@@ -0,0 +1 @@
/build

View File

@@ -0,0 +1,66 @@
apply plugin: 'com.android.library'
apply plugin: 'kotlin-android'
apply plugin: 'kotlin-android-extensions'
android {
compileSdkVersion rootProject.ext.android.compileSdkVersion
// buildToolsVersion rootProject.ext.android.buildToolsVersion
defaultConfig {
minSdkVersion rootProject.ext.android.minSdkVersion
targetSdkVersion rootProject.ext.android.targetSdkVersion
versionCode Integer.valueOf(VERSION_CODE)
versionName getValueFromRootProperties("${project.name.replace("-", "_").toUpperCase()}_VERSION")
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
consumerProguardFiles "consumer-rules.pro"
javaCompileOptions {
annotationProcessorOptions {
arguments = [AROUTER_MODULE_NAME: project.getName()]
}
}
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
lintOptions {
abortOnError false
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
debug {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
}
}
dependencies {
implementation fileTree(dir: "libs", include: ["*.jar"])
implementation rootProject.ext.dependencies.kotlinstdlibjdk7
implementation rootProject.ext.dependencies.androidxappcompat
implementation rootProject.ext.dependencies.arouter
implementation rootProject.ext.dependencies.androidxrecyclerview
implementation rootProject.ext.dependencies.material
implementation rootProject.ext.dependencies.flexbox
annotationProcessor rootProject.ext.dependencies.aroutercompiler
implementation rootProject.ext.dependencies.rxandroid
implementation rootProject.ext.dependencies.androidxconstraintlayout
implementation rootProject.ext.dependencies.amapnavi3dmap
implementation rootProject.ext.dependencies.amapsearch
implementation project(":OCH:mogo-och-common-module")
compileOnly project(":libraries:mogo-map")
implementation project(':core:mogo-core-res')
}
apply from: new File(rootProject.rootDir, "gradle/upload.gradle").toString()

View File

@@ -0,0 +1,3 @@
GROUP=com.mogo.och
POM_ARTIFACT_ID=och-taxi-passenger
VERSION_CODE=1

View 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

View File

@@ -0,0 +1,13 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
package="com.mogo.och.taxi.passenger">
<uses-permission android:name="android.permission.KILL_BACKGROUND_PROCESSES" />
<uses-permission android:name="android.permission.WRITE_SETTINGS"
tools:ignore="ProtectedPermissions" />
<application>
</application>
</manifest>

Binary file not shown.

View File

@@ -0,0 +1,24 @@
package com.mogo.och.taxi.passenger;
import androidx.annotation.IdRes;
import androidx.fragment.app.FragmentActivity;
import com.mogo.eagle.core.function.api.base.IMoGoFunctionProvider;
/**
*
* @author congtaowang
* @since 2021/1/15
*
* 网约车抽象接口
*/
public interface IMogoOCH extends IMoGoFunctionProvider {
/**
* 初始化网约车容器
*
* @param activity
* @param containerId 容器ID
*/
void createCoverage(FragmentActivity activity, @IdRes int containerId);
}

View File

@@ -0,0 +1,99 @@
package com.mogo.och.taxi.passenger;
import static com.mogo.eagle.core.utilcode.mogo.logger.scene.SceneConstant.M_TAXI_P;
import android.content.Context;
import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentActivity;
import androidx.fragment.app.FragmentManager;
import com.alibaba.android.arouter.facade.annotation.Route;
import com.mogo.eagle.core.function.call.setting.CallerMoGoUiSettingManager;
import com.mogo.eagle.core.utilcode.mogo.logger.CallerLogger;
import com.mogo.och.taxi.passenger.constant.TaxiPassengerConst;
import com.mogo.och.taxi.passenger.ui.TaxiPassengerBaseFragment;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
/**
* @author congtaowang
* @since 2021/1/15
* <p>
* 网约车-出租车-乘客端
*/
@Route(path = TaxiPassengerConst.PATH)
public class MogoOCHTaxiPassenger implements IMogoOCH {
private static final String TAG = "MogoOCHTaxiPassenger";
private TaxiPassengerBaseFragment ochTaxiPassengerFragment;
private FragmentActivity mActivity;
private int mContainerId;
@Override
public void init(Context context) {
CallerLogger.INSTANCE.d(M_TAXI_P + TAG, "init");
}
/**
* 进入鹰眼模式,设置手势缩放地图失效
*/
private void stepIntoVrMode() {
CallerLogger.INSTANCE.d(M_TAXI_P + TAG, "进入vr模式");
CallerMoGoUiSettingManager.INSTANCE.stepInNightMode();//夜间模式 状态栏字体颜色变黑
}
private void showFragment() {
FragmentManager supportFragmentManager = mActivity.getSupportFragmentManager();
if (ochTaxiPassengerFragment == null) {
CallerLogger.INSTANCE.d(M_TAXI_P + TAG, "准备add fragment======");
Fragment fragmentByTag = supportFragmentManager.findFragmentByTag(TaxiPassengerBaseFragment.TAG);
if (fragmentByTag instanceof TaxiPassengerBaseFragment){
ochTaxiPassengerFragment = (TaxiPassengerBaseFragment) fragmentByTag;
}else {
ochTaxiPassengerFragment = new TaxiPassengerBaseFragment();
}
if (!ochTaxiPassengerFragment.isAdded()){
supportFragmentManager.beginTransaction().add(mContainerId, ochTaxiPassengerFragment
,TaxiPassengerBaseFragment.TAG).commitAllowingStateLoss();
}
return;
}
CallerLogger.INSTANCE.d(M_TAXI_P + TAG, "准备show fragment");
supportFragmentManager.beginTransaction().show(ochTaxiPassengerFragment).commitAllowingStateLoss();
}
private void hideFragment() {
if (ochTaxiPassengerFragment != null) {
mActivity.getSupportFragmentManager().beginTransaction().hide(ochTaxiPassengerFragment).commitAllowingStateLoss();
}
}
@Override
public void createCoverage(FragmentActivity activity, int containerId) {
}
@NotNull
@Override
public String getFunctionName() {
return null;
}
@Nullable
@Override
public Fragment createCoverage(@Nullable FragmentActivity fragmentActivity, @Nullable Integer integer) {
this.mActivity = fragmentActivity;
this.mContainerId = integer;
showFragment();
return null;
}
@Override
public void onDestroy() {
// 若不调用finish, 设置中打开关闭UITouch,会造成och fragment 重叠
if (mActivity == null) return;
mActivity.finish();
}
}

View File

@@ -0,0 +1,11 @@
package com.mogo.och.taxi.passenger.bean;
import com.mogo.eagle.core.data.BaseData;
/**
* Created by pangfan on 2021/8/19
* 查询订单返回数据结构
*/
public class TaxiPassengerBaseRespBean extends BaseData {
public Object data;
}

View File

@@ -0,0 +1,16 @@
package com.mogo.och.taxi.passenger.bean;
/**
* Created by pangfan on 2021/8/19
* 验证手机号后四位同时流转订单状态
*/
public class TaxiPassengerCheckPhoneUpdateOrderReqBean {
public String orderNo;
public String phone;
public TaxiPassengerCheckPhoneUpdateOrderReqBean(String orderNo,String phone) {
this.orderNo = orderNo;
this.phone = phone;
}
}

View File

@@ -0,0 +1,16 @@
package com.mogo.och.taxi.passenger.bean;
/**
* Created by pangfan on 2021/8/19
* 查询订单信息请求数据结构
*/
public class TaxiPassengerOrderQueryReqBean {
public String driverSn;
public String orderNo;
public TaxiPassengerOrderQueryReqBean(String driverSn, String orderNo) {
this.driverSn = driverSn;
this.orderNo = orderNo;
}
}

View File

@@ -0,0 +1,89 @@
package com.mogo.och.taxi.passenger.bean;
import com.mogo.eagle.core.data.BaseData;
import java.util.List;
import java.util.Objects;
/**
* Created by pangfan on 2021/8/19
* 查询订单返回数据结构
*/
public class TaxiPassengerOrderQueryRespBean extends BaseData {
public Result data;
public static class Result implements Comparable<Result>{
// 订单no
public String orderNo;
// 订单类型
public int orderType; //1即时单 2预约单
// 订单状态
public int orderStatus;
// 订单运营类型 9出租车10小巴
public int businessType;
// 起始站点id
public int startSiteId;
// 起始站点名称
public String startSiteAddr;
// 起始站点坐标
public List<Double> startSitePoint; //wgs坐标用于自动驾驶 [lon,lat]
public List<Double> startSiteGcjPoint; //高德坐标,用于本地计算距离 [lon,lat]
// 终点站点id
public int endSiteId;
// 终点站点名称
public String endSiteAddr;
// 终点站点坐标
public List<Double> endSitePoint; //wgs坐标用于自动驾驶 [lon,lat]
public List<Double> endSiteGcjPoint; //高德坐标,用于计算距离 [lon,lat]
// 车牌号
public String carNumber;
//订单创建时间戳
public long createTime;
//开始服务时间戳:司机点击'开始服务'后订单状态更新成功的时间
public long startTime;
//预计用车时间:预约单=下单时的预约用车时间;即时单=派单成功的时间+预估的达到上车点的时间
public long bookingTime;
//乘客手机号
public String passengerPhone;
//订单多少乘客
public String passengerNum;
public long lineId = -1; //路线id默认-1
public String csvFileUrl = ""; //轨迹文件下载的cos url默认“”
public String csvFileMd5 = ""; //轨迹文件md5默认“”
public String txtFileUrl = ""; //打点文件下载的cos url默认“”
public String txtFileMd5 = ""; //轨迹文件md5默认“”
public long contrailSaveTime; //上传轨迹完成时间戳ms用于MEC本地手动导入轨迹验证时不会被云端轨迹覆盖
public String carModel = ""; //[optional] 车型号如红旗H9默认“”暂不加入校验逻辑、用于人工排查问题
public String csvFileUrlDPQP = ""; //轨迹文件下载的cos url默认“”
public String csvFileMd5DPQP = ""; //轨迹文件md5默认“”
public String txtFileUrlDPQP = ""; //打点文件下载的cos url默认“”
public String txtFileMd5DPQP = ""; //轨迹文件md5默认“”
public long contrailSaveTimeDPQP; //上传轨迹完成时间戳ms用于MEC本地手动导入轨迹验证时不会被云端轨迹覆盖
@Override
public int compareTo(Result o) {
boolean isEqual = this.orderNo.equals(o.orderNo);
return isEqual ? 0 : 1;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Result result = (Result) o;
return Objects.equals(orderNo, result.orderNo) &&
orderType == result.orderType &&
orderStatus == result.orderStatus &&
businessType == result.businessType;
}
@Override
public int hashCode() {
return Objects.hash(orderNo, orderType, orderStatus, businessType, startSiteId,
startSiteAddr, startSitePoint, startSiteGcjPoint, endSiteId, endSiteAddr,
endSitePoint, endSiteGcjPoint, carNumber, createTime, startTime);
}
}
}

View File

@@ -0,0 +1,17 @@
package com.mogo.och.taxi.passenger.bean;
import com.mogo.eagle.core.data.BaseData;
import java.util.List;
/**
* Created on 2021/9/8
* 查询全部服务中/待服务订单的返回数据
*/
public class TaxiPassengerOrdersInServiceQueryRespBean extends BaseData {
public Result data;
public static class Result {
public List<TaxiPassengerOrderQueryRespBean.Result> servicing; //服务中订单
}
}

View File

@@ -0,0 +1,23 @@
package com.mogo.och.taxi.passenger.bean;
/**
* Created by pangfan on 2021/8/19
* 司机端准备好或者乘客已验证上车请求参数
*/
public class TaxiPassengerStartReqBean {
public String orderNo;
public String sn;
public TaxiPassengerStartReqBean.Result loc;
public static class Result {
public Double lat;
public Double lon;
}
public TaxiPassengerStartReqBean(String sn, String orderNo, TaxiPassengerStartReqBean.Result point) {
this.sn = sn;
this.orderNo = orderNo;
this.loc = point;
}
}

View File

@@ -0,0 +1,3 @@
package com.mogo.och.taxi.passenger.bean
class TaxiPassengerVideoPlay(var url: String, var imageUrl: String, var title: String)

View File

@@ -0,0 +1,21 @@
package com.mogo.och.taxi.passenger.callback
import com.mogo.och.taxi.passenger.bean.TaxiPassengerOrderQueryRespBean
/**
* Created on 2021/9/8
*
* Model->Presenter回调订单相关进行中/待服务单变更,当前进行单状态变更,新到预约单,抢单,抢单结果状态等等)
*/
interface IOCHTaxiPassengerOrderStatusCallback {
// 当前进行单状态变更:新到进行中订单、进行中单状态变更
fun onCurrentOrderStatusChanged(order: TaxiPassengerOrderQueryRespBean.Result?){}
// 当前位置距离上车点的距离(米)、预估时间(秒)
fun onCurrentOrderDistToEndChanged(meters: Long, timeInSecond: Long,stationDistance:Int){}
// 司机已确认开启自动驾驶环境
fun onDriverHasCheckedPilotCondition(isBoarded: Boolean){}
fun onMessageGo2OverMapview(){}
}

View File

@@ -0,0 +1,5 @@
package com.mogo.och.taxi.passenger.callback;
public interface ITaxiPassengerCommonCallback {
void onCommonCallback();
}

View File

@@ -0,0 +1,5 @@
package com.mogo.och.taxi.passenger.callback;
public interface ITaxiPassengerCommonValueCallback<T> {
void onCommonCallback(T t);
}

View File

@@ -0,0 +1,37 @@
package com.mogo.och.taxi.passenger.constant
/**
* Created on 2021/12/6
*/
class TaxiPassengerConst {
companion object {
// OCH arouter 路由path
const val PATH = "/passenger/api"
// 开始服务启动自动驾驶等待时间(埋点上传)
const val LOOP_PERIOD_15S = 15 * 1000L
const val TAXI_AVERAGE_SPEED = 38
// 埋点key接管后点击'自动驾驶'按钮启动
const val EVENT_KEY_RESTART_AUTOPILOT = "event_key_och_taxi_restart_autopilot"
// 埋点key开始服务开启自动驾驶成功/失败)
const val EVENT_KEY_START_SERVICE = "event_key_och_taxi_start_service"
const val EVENT_PARAM_SN = "sn"
const val EVENT_PARAM_TIME = "time"
const val EVENT_PARAM_START_NAME = "start_name"
const val EVENT_PARAM_END_NAME = "end_name"
const val EVENT_PARAM_ORDER_NUMBER = "order_num"
const val EVENT_PARAM_START_RESULT = "start_autopilot" // true/false
const val EVENT_PARAM_START_FAILURE_CODE = "start_autopilot_failure_code" // 启动自驾失败code
const val EVENT_PARAM_START_FAILURE_MSG = "start_autopilot_failure_msg" // 启动自驾失败原因
const val EVENT_PARAM_PLATE_NUM = "plate_number" // 车牌号
const val EVENT_PARAM_ENV_ONLINE = "env_online" // 是否线上环境true/false
// 埋点key开启自动驾驶前已识别的异常会导致无法开启自驾
const val EVENT_KEY_AP_UNABLE_START_REASON = "event_key_och_taxi_ap_unable_start_reason"
const val EVENT_PARAM_UNABLE_START_REASON = "unable_start_reason";
}
}

View File

@@ -0,0 +1,47 @@
package com.mogo.och.taxi.passenger.constant
/**
* Created on 2021/12/7
*
* * Old codeSTART
* 未派单 0
* 去往上车站点 1
* 车辆已到达上车站点 2
* 乘客已到达上车站点 3
* 去往下车站点 4
* 到达下车站点 5
* 已完成 6
* 已取消 7
* Old codeEND
*
* 0 订单创建(为派单),
* 10 已派上司机(司机去往上车点),
* 20 司机到达上车点,
* 30 乘客到达上车点,
* 40 服务中(去往目的地),
* 50 到达目的地,
* 60 已完成,
* 70 已取消
*/
enum class TaxiPassengerOrderStatusEnum(val code: Int) {
None( 0 ),
OnTheWayToStart( 10),
ArriveAtStart( 20),
UserArriveAtStart( 30),
OnTheWayToEnd( 40),
ArriveAtEnd( 50),
JourneyCompleted(60),//行程完成
Cancel( 70);
companion object {
@JvmStatic
fun valueOf(code: Int): TaxiPassengerOrderStatusEnum {
for (value in values()) {
if (value.code == code) {
return value
}
}
return None
}
}
}

View File

@@ -0,0 +1,193 @@
package com.mogo.och.taxi.passenger.model
import com.elegant.network.utils.GsonUtil
import com.mogo.commons.voice.AIAssist
import com.mogo.eagle.core.data.autopilot.AutopilotControlParameters
import com.mogo.eagle.core.data.config.FunctionBuildConfig
import com.mogo.eagle.core.function.api.autopilot.IMoGoAutopilotStatusListener
import com.mogo.eagle.core.function.call.autopilot.CallerAutoPilotControlManager
import com.mogo.eagle.core.function.call.autopilot.CallerAutoPilotStatusListenerManager
import com.mogo.eagle.core.function.call.autopilot.CallerChassisLocationGCJ02ListenerManager
import com.mogo.eagle.core.utilcode.mogo.logger.CallerLogger
import com.mogo.eagle.core.utilcode.mogo.logger.scene.SceneConstant
import com.mogo.eagle.core.utilcode.util.ToastUtils
import com.mogo.och.common.module.biz.network.OchCommonServiceCallback
import com.mogo.och.common.module.manager.OCHAdasAbilityManager
import com.mogo.och.common.module.utils.PinYinUtil
import com.mogo.och.common.module.voice.VoiceNotice
import com.mogo.och.taxi.passenger.bean.TaxiPassengerBaseRespBean
import com.mogo.och.taxi.passenger.bean.TaxiPassengerStartReqBean
import com.mogo.och.taxi.passenger.constant.TaxiPassengerOrderStatusEnum
import com.mogo.och.taxi.passenger.network.TaxiPassengerServiceManager
import com.mogo.och.taxi.passenger.utils.TaxiPassengerAnalyticsManager
object AutopilotManager : IMoGoAutopilotStatusListener {
private const val TAG = "AutopilotManager"
init {
CallerAutoPilotStatusListenerManager.addListener(TAG, this)
}
//检测当前订单
private fun checkCurrentOCHOrder(): Boolean {
return TaxiPassengerModel.currentOCHOrder != null && TaxiPassengerModel.currentOCHOrder!!.startSiteGcjPoint != null && TaxiPassengerModel.currentOCHOrder!!.endSiteGcjPoint != null
}
fun startAutopilot() {
if (!checkCurrentOCHOrder()) {
CallerLogger.e(
SceneConstant.M_TAXI_P + TAG,
"no order or order is empty."
)
ToastUtils.showShort("当前订单不存在或异常!")
return
}
if (TaxiPassengerModel.currentOCHOrder!!.orderStatus == TaxiPassengerOrderStatusEnum.UserArriveAtStart.code) {
startServicePilotDone()
}
if(CallerAutoPilotStatusListenerManager.getState()
== IMoGoAutopilotStatusListener.STATUS_AUTOPILOT_RUNNING){
ToastUtils.showShort("自驾中、请勿重复启动")
return
}
if (!FunctionBuildConfig.isDemoMode && !OCHAdasAbilityManager.getInstance().autopilotAbilityStatus) {
ToastUtils.showLong(
OCHAdasAbilityManager.getInstance().autopilotUnAbilityReason +
", 请稍候重试"
)
TaxiPassengerAnalyticsManager.triggerUnableStartAPReasonEvent(
TaxiPassengerModel.currentOCHOrder!!.startSiteAddr,
TaxiPassengerModel.currentOCHOrder!!.endSiteAddr,
TaxiPassengerModel.currentOCHOrder!!.orderNo,
OCHAdasAbilityManager.getInstance().autopilotUnAbilityReason
)
return
}
val parameters = initAutopilotControlParameters()
if (parameters == null) {
CallerLogger.d(
SceneConstant.M_TAXI_P + TAG,
"AutopilotControlParameters is empty."
)
return
}
CallerAutoPilotControlManager.startAutoPilot(parameters)
CallerLogger.d(
SceneConstant.M_TAXI_P + TAG,
"start autopilot with parameter: %s",
GsonUtil.jsonFromObject(parameters)
+ " ,startSiteName=" + TaxiPassengerModel.currentOCHOrder!!.startSiteAddr
+ " ,endSiteName=" + TaxiPassengerModel.currentOCHOrder!!.endSiteAddr
)
TaxiPassengerAnalyticsManager.triggerStartAutopilotEvent(false, false, TaxiPassengerModel.currentOCHOrder!!.startSiteAddr, TaxiPassengerModel.currentOCHOrder!!.endSiteAddr, TaxiPassengerModel.currentOCHOrder!!.orderNo)
}
private fun initAutopilotControlParameters(): AutopilotControlParameters? {
if (!checkCurrentOCHOrder()) {
CallerLogger.e(
SceneConstant.M_TAXI_P + TAG,
"no order or order is empty."
)
ToastUtils.showShort("当前订单不存在或异常!")
return null
}
val parameters = AutopilotControlParameters()
val startWgsLon = TaxiPassengerModel.currentOCHOrder!!.startSitePoint[0]
val startWgsLat = TaxiPassengerModel.currentOCHOrder!!.startSitePoint[1]
val endWgsLon = TaxiPassengerModel.currentOCHOrder!!.endSitePoint[0]
val endWgsLat = TaxiPassengerModel.currentOCHOrder!!.endSitePoint[1]
parameters.vehicleType = TaxiPassengerModel.currentOCHOrder!!.businessType
parameters.startName =
PinYinUtil.getPinYinHeadChar(TaxiPassengerModel.currentOCHOrder!!.startSiteAddr) // 起点名称拼音首字母大写科学城B区2号门KXCBQ2HM
parameters.endName =
PinYinUtil.getPinYinHeadChar(TaxiPassengerModel.currentOCHOrder!!.endSiteAddr) // 终点名称拼音首字母大写科学城C区三号门KXCCQSHM
parameters.startLatLon =
AutopilotControlParameters.AutoPilotLonLat(startWgsLat, startWgsLon)
parameters.endLatLon = AutopilotControlParameters.AutoPilotLonLat(endWgsLat, endWgsLon)
if (parameters.autoPilotLine == null) {
parameters.autoPilotLine = AutopilotControlParameters.AutoPilotLine(
TaxiPassengerModel.currentOCHOrder!!.lineId,
TaxiPassengerModel.currentOCHOrder!!.csvFileUrl,
TaxiPassengerModel.currentOCHOrder!!.csvFileMd5,
TaxiPassengerModel.currentOCHOrder!!.txtFileUrl,
TaxiPassengerModel.currentOCHOrder!!.txtFileMd5,
TaxiPassengerModel.currentOCHOrder!!.contrailSaveTime,
TaxiPassengerModel.currentOCHOrder!!.carModel,
TaxiPassengerModel.currentOCHOrder!!.csvFileUrlDPQP,
TaxiPassengerModel.currentOCHOrder!!.csvFileMd5DPQP,
TaxiPassengerModel.currentOCHOrder!!.txtFileUrlDPQP,
TaxiPassengerModel.currentOCHOrder!!.txtFileMd5DPQP,
TaxiPassengerModel.currentOCHOrder!!.contrailSaveTimeDPQP
)
}
return parameters
}
/**
* 将业务订单信息保存,鹰眼可取用
*/
fun updateAutopilotControlParameters() {
val parameters = initAutopilotControlParameters()
if (null == parameters) {
CallerLogger.e(
SceneConstant.M_TAXI_P + TAG,
"AutopilotControlParameters is empty."
)
return
}
CallerLogger.d(
SceneConstant.M_TAXI_P + TAG,
"AutopilotControlParameters is update."
)
CallerAutoPilotStatusListenerManager.updateAutopilotControlParameters(parameters)
}
fun clearAutopilotControlParameters() {
CallerLogger.d(
SceneConstant.M_TAXI_P + TAG,
"AutopilotControlParameters is clear."
)
CallerAutoPilotStatusListenerManager.updateAutopilotControlParameters(null)
}
override fun onAutopilotStatusResponse(state: Int) {
// 启动自驾成功
when (state) {
IMoGoAutopilotStatusListener.STATUS_AUTOPILOT_RUNNING -> {
if (TaxiPassengerModel.currentOCHOrder != null && TaxiPassengerModel.curOrderStatus === TaxiPassengerOrderStatusEnum.UserArriveAtStart) {
TaxiPassengerAnalyticsManager.triggerStartAutopilotEvent(
false,
true,
TaxiPassengerModel.currentOCHOrder!!.startSiteAddr,
TaxiPassengerModel.currentOCHOrder!!.endSiteAddr,
TaxiPassengerModel.currentOCHOrder!!.orderNo
)
startServicePilotDone()
}
}
else -> {}
}
}
/**
* 乘客屏启动自动驾驶成功
*/
fun startServicePilotDone() {
if (TaxiPassengerModel.currentOCHOrder == null) return
val result = TaxiPassengerStartReqBean.Result()
val currentLocation = CallerChassisLocationGCJ02ListenerManager.getChassisLocationGCJ02()
result.lat = currentLocation.latitude
result.lon = currentLocation.longitude
TaxiPassengerServiceManager.startServicePilotDone(
TaxiPassengerModel.currentOCHOrder!!.orderNo, result,
object : OchCommonServiceCallback<TaxiPassengerBaseRespBean> {
override fun onSuccess(data: TaxiPassengerBaseRespBean) {
VoiceNotice.showNotice("坐稳扶好,我们出发咯!", AIAssist.LEVEL2)
}
override fun onFail(code: Int, msg: String) {}
})
}
}

View File

@@ -0,0 +1,382 @@
package com.mogo.och.taxi.passenger.model
import android.annotation.SuppressLint
import android.content.Context
import android.net.ConnectivityManager
import com.mogo.aicloud.services.socket.MogoAiCloudSocketManager
import com.mogo.commons.module.intent.IMogoIntentListener
import com.mogo.commons.module.intent.IntentManager
import com.mogo.commons.voice.AIAssist
import com.mogo.eagle.core.data.map.MogoLocation
import com.mogo.eagle.core.function.api.datacenter.msgbox.IMsgBoxEventListener
import com.mogo.eagle.core.function.call.biz.CallerFuncBizManager
import com.mogo.eagle.core.function.call.msgbox.CallerMsgBoxEventListenerManager
import com.mogo.eagle.core.function.call.order.CallerOrderListenerManager
import com.mogo.eagle.core.utilcode.mogo.logger.CallerLogger
import com.mogo.eagle.core.utilcode.mogo.logger.CallerLogger.d
import com.mogo.eagle.core.utilcode.mogo.logger.CallerLogger.e
import com.mogo.eagle.core.utilcode.mogo.logger.scene.SceneConstant.Companion.M_TAXI_P
import com.mogo.eagle.core.utilcode.util.NetworkUtils
import com.mogo.eagle.core.utilcode.util.ToastUtils
import com.mogo.och.common.module.biz.network.OchCommonServiceCallback
import com.mogo.och.common.module.callback.OchAdasStartFailureCallback
import com.mogo.och.common.module.manager.AbnormalFactorsLoopManager
import com.mogo.och.common.module.manager.OCHAdasAbilityManager
import com.mogo.och.common.module.manager.distancemamager.IDistanceListener
import com.mogo.och.common.module.manager.distancemamager.TrajectoryAndDistanceManager
import com.mogo.och.common.module.manager.loopmanager.BizLoopManager
import com.mogo.och.common.module.manager.loopmanager.LoopInfo
import com.mogo.och.common.module.utils.RxUtils
import com.mogo.och.common.module.voice.VoiceNotice
import com.mogo.och.taxi.passenger.bean.TaxiPassengerBaseRespBean
import com.mogo.och.taxi.passenger.bean.TaxiPassengerOrderQueryRespBean
import com.mogo.och.taxi.passenger.bean.TaxiPassengerOrdersInServiceQueryRespBean
import com.mogo.och.taxi.passenger.callback.IOCHTaxiPassengerOrderStatusCallback
import com.mogo.och.taxi.passenger.callback.ITaxiPassengerCommonCallback
import com.mogo.och.taxi.passenger.constant.TaxiPassengerConst
import com.mogo.och.taxi.passenger.constant.TaxiPassengerOrderStatusEnum
import com.mogo.och.taxi.passenger.constant.TaxiPassengerOrderStatusEnum.Companion.valueOf
import com.mogo.och.taxi.passenger.network.TaxiPassengerServiceManager
import com.mogo.och.taxi.passenger.utils.TaxiPassengerAnalyticsManager
import java.util.concurrent.ConcurrentHashMap
/**
* Created by pangfan on 2021/8/19
*
* 网约车 - 出租车业务逻辑处理
*/
@SuppressLint("StaticFieldLeak")
object TaxiPassengerModel {
private var mContext: Context? = null
private val TAG = TaxiPassengerModel::class.java.simpleName
private const val STARTREADYTOAUTOPILOT = "startReadyToAutopilot"
private const val MINANDWAITSERVICE = "mInAndWaitService"
// 获取当前订单
@Volatile
var currentOCHOrder: TaxiPassengerOrderQueryRespBean.Result? = null//当前订单
private val mOrderStatusCallbackMap: MutableMap<String, IOCHTaxiPassengerOrderStatusCallback> = ConcurrentHashMap()
fun setOrderStatusCallback(tag: String?, callback: IOCHTaxiPassengerOrderStatusCallback?) {
if (tag == null || "" == tag) return
if (callback == null) {
mOrderStatusCallbackMap.remove(tag)
return
}
mOrderStatusCallbackMap[tag] = callback
}
fun init(context: Context) {
mContext = context.applicationContext
initListeners()
RxUtils.errCatch()
startOrStopOrderLoop()
}
/**
* 订单轮询 初始化主Fragment的Presenter init 调用
*/
private fun startOrStopOrderLoop() {
if (NetworkUtils.isConnected(mContext)) {
startOrStopOrderLoop(true)
}
}
/**
* 关闭订单轮训 页面摧毁时
*/
fun release() {
startOrStopOrderLoop(false)
//startOrStopQueryOrderRemaining(false)
releaseListeners()
}
private fun initListeners() {
// 2021.11.1重构自动驾驶 实现接口 IMoGoAutopilotStatusListener 注册监听 替换IMogoAdasOCHCallback接口
IntentManager.getInstance().registerIntentListener(ConnectivityManager.CONNECTIVITY_ACTION, mNetWorkIntentListener)
AbnormalFactorsLoopManager.startLoopAbnormalFactors(mContext!!)
//开启自驾后 异常信息返回
OCHAdasAbilityManager.getInstance().setAdasStartFailureCallback(mAdasStartFailureListener)
CallerMsgBoxEventListenerManager.addListener(TAG, iMsgBoxEventListener)
TrajectoryAndDistanceManager.addDistanceListener(TAG, distanceListener)
}
private fun releaseListeners() {
MogoAiCloudSocketManager.getInstance(mContext).unregisterLifecycleListener(10010)
AbnormalFactorsLoopManager.stopLoopAbnormalFactors()
OCHAdasAbilityManager.getInstance().setAdasStartFailureCallback(null)
CallerMsgBoxEventListenerManager.removeListener(iMsgBoxEventListener)
TrajectoryAndDistanceManager.removeListener(TAG)
}
/**
* 订单轮训
* @param start true 开启订单轮训
* false 关闭订单轮训
*/
private fun startOrStopOrderLoop(start: Boolean) {
d(M_TAXI_P + TAG, "startOrStopOrderLoop() $start")
if (start) {
BizLoopManager.setLoopFunction(MINANDWAITSERVICE,LoopInfo(2, TaxiPassengerModel::queryInAndWaitOrders))
} else {
BizLoopManager.removeLoopFunction(MINANDWAITSERVICE)
}
}
/**
* 查询订单状态:进行中/待服务轮询防止因crash导致应用重启、断网没收到推送等
*
* 注只有在本地缓存mCurrentOCHOrder为null时已完成or已取消有明确结果或id相同且status不同时
* 才更新最新进行中单到本地
*/
private fun queryInAndWaitOrders() {
TaxiPassengerServiceManager.queryOrdersInAndWaitService(
object : OchCommonServiceCallback<TaxiPassengerOrdersInServiceQueryRespBean> {
override fun onSuccess(data: TaxiPassengerOrdersInServiceQueryRespBean) {
if (data.data == null) {
if(currentOCHOrder!=null){
queryCurOrderStatus()
}
return
}
//1. 处理进行中订单
if (data.data.servicing != null && data.data.servicing.isNotEmpty()) {
// 1.1. 当存在进行中单时对本地currentOrder进行更新
val currentOrder = data.data.servicing[0]
if(currentOCHOrder==null||currentOCHOrder?.orderStatus!=currentOrder.orderStatus){
currentOCHOrder = currentOrder
orderStatusChange()
}else {
currentOCHOrder = currentOrder
}
}else{
if(currentOCHOrder!=null){
queryCurOrderStatus()
}
}
}
override fun onError() {
e(M_TAXI_P + TAG, "queryInAndWaitOrders onError")
}
override fun onFail(code: Int, msg: String) {
e(M_TAXI_P + TAG, "queryInAndWaitOrders$code$msg")
}
})
}
//仅用于轮询时查到本地有mCurrentOCHOrder但请求结果无进行中单or orderId不一致是复查本地currentOrder
private fun queryCurOrderStatus() {
currentOCHOrder?.orderNo?.let {
TaxiPassengerServiceManager.queryOrderById(
mContext!!, it,
object : OchCommonServiceCallback<TaxiPassengerOrderQueryRespBean> {
override fun onSuccess(data: TaxiPassengerOrderQueryRespBean) {
if (data.data != null && currentOCHOrder != null && currentOCHOrder!!.orderNo == data.data.orderNo) {
if (data.data.orderStatus == TaxiPassengerOrderStatusEnum.Cancel.code || data.data.orderStatus == TaxiPassengerOrderStatusEnum.JourneyCompleted.code || data.data.orderStatus == TaxiPassengerOrderStatusEnum.None.code) {
currentOCHOrder = data.data
orderStatusChange()
currentOCHOrder = null
} else {
currentOCHOrder = data.data
orderStatusChange()
}
}
}
override fun onFail(code: Int, msg: String) {}
})
}
}
// 获取当前订单状态
val curOrderStatus: TaxiPassengerOrderStatusEnum
get() {
val order: TaxiPassengerOrderQueryRespBean.Result =
currentOCHOrder
?: return TaxiPassengerOrderStatusEnum.None
return valueOf(order.orderStatus)
}
//监听网络变化,避免启动机器时无网导致无法更新订单信息
private val distanceListener: IDistanceListener = object : IDistanceListener {
var allDistance = 0f
override fun stationDistanceCallback(stationDistance: Float) {
allDistance = stationDistance
}
override fun distanceCallback(distance: Float) {
val lastTime: Double = distance / TaxiPassengerConst.TAXI_AVERAGE_SPEED * 3.6 //秒
for (callback in mOrderStatusCallbackMap.values) {
callback.onCurrentOrderDistToEndChanged(distance.toLong(),lastTime.toLong(),allDistance.toInt())
}
}
}
private val mNetWorkIntentListener = IMogoIntentListener { intentStr, intent ->
d(M_TAXI_P + TAG, "onIntentReceived = %s", intentStr)
if (ConnectivityManager.CONNECTIVITY_ACTION == intentStr) {
if (NetworkUtils.isConnected(mContext)) {
startOrStopOrderLoop(true)
}
}
}
private val mAdasStartFailureListener: OchAdasStartFailureCallback = object : OchAdasStartFailureCallback {
override fun onStartAutopilotFailure(startFailedCode: String, startFailedMessage: String) {
TaxiPassengerAnalyticsManager.triggerStartAutopilotFailureEventByAdas(startFailedCode, startFailedMessage)
}
}
private val iMsgBoxEventListener: IMsgBoxEventListener = object : IMsgBoxEventListener {
override fun onSummaryClickEvent() {
if (currentOCHOrder == null) {
ToastUtils.showLong("行程已结束")
} else {
for (callback in mOrderStatusCallbackMap.values) {
callback.onMessageGo2OverMapview()
}
}
}
}
fun checkPhoneAndUpdateStatus(
phoneTail: String?,
commonCallback: ITaxiPassengerCommonCallback?
) {
if (currentOCHOrder == null) return
TaxiPassengerServiceManager.checkPhoneAndUpdateOrderStatus(
currentOCHOrder!!.orderNo,
phoneTail, object : OchCommonServiceCallback<TaxiPassengerBaseRespBean> {
override fun onSuccess(data: TaxiPassengerBaseRespBean) {
if (data.code == 0 && currentOCHOrder != null) {
currentOCHOrder!!.orderStatus = TaxiPassengerOrderStatusEnum.UserArriveAtStart.code
//乘客验证成功,更新订单状态为 "乘客已上车", 立马弹出乘客开始行程页面,不再等待轮询
orderStatusChange()
VoiceNotice.showNotice("验证成功!关闭车门并佩戴安全带后开启行程吧!", AIAssist.LEVEL2)
}
commonCallback?.onCommonCallback()
}
override fun onFail(code: Int, msg: String) {
ToastUtils.showLong("当前网络异常,请重新验证;若始终异常,请您在手机端取消行程,给您带来不便,十分抱歉!")
e(
M_TAXI_P + TAG,
"提交用户输入的手机后4位、并进行状态扭转 后台结果错误$code$msg"
)
}
})
}
fun orderStatusChange(){
orderStatusChangeInner()
if (mOrderStatusCallbackMap.isNotEmpty()) {
d(M_TAXI_P + TAG, "最新的状态${curOrderStatus}")
for (callback in mOrderStatusCallbackMap.values) {
callback.onCurrentOrderStatusChanged(currentOCHOrder)
}
}
}
private fun orderStatusChangeInner() {
when (curOrderStatus) {
TaxiPassengerOrderStatusEnum.OnTheWayToStart -> {
}
TaxiPassengerOrderStatusEnum.ArriveAtStart -> {
}
TaxiPassengerOrderStatusEnum.UserArriveAtStart -> {
//开启轮询司机是否已准备好开启自动驾驶的环境
setStation()
}
TaxiPassengerOrderStatusEnum.OnTheWayToEnd -> {
CallerFuncBizManager.bizProvider.queryV2XEvents() //全览模式的V2X事件轮询开始
//startOrStopQueryOrderRemaining(true)
AutopilotManager.updateAutopilotControlParameters()
startOrStopReadyToAutopilotLoop(false)
setStation()
CallerOrderListenerManager.invokeOrderStatus(true)
}
TaxiPassengerOrderStatusEnum.ArriveAtEnd -> {
AutopilotManager.clearAutopilotControlParameters()
//startOrStopQueryOrderRemaining(false)
CallerOrderListenerManager.invokeOrderStatus(false)
cleanStation()
}
TaxiPassengerOrderStatusEnum.JourneyCompleted -> {
AutopilotManager.clearAutopilotControlParameters()
//startOrStopQueryOrderRemaining(false)
cleanStation()
}
TaxiPassengerOrderStatusEnum.Cancel -> {
AutopilotManager.clearAutopilotControlParameters()
//startOrStopQueryOrderRemaining(false)
startOrStopReadyToAutopilotLoop(false)
cleanStation()
}
TaxiPassengerOrderStatusEnum.None -> TODO()
}
}
/**
* 查询司机是否已确认可开启自动驾驶
*/
private fun loopQueryPilotStatus() {
if (currentOCHOrder == null) return
TaxiPassengerServiceManager.queryPilotStatus(
currentOCHOrder!!.orderNo,
object : OchCommonServiceCallback<TaxiPassengerBaseRespBean> {
override fun onSuccess(data: TaxiPassengerBaseRespBean) {
if (data.code == 0 && data.data == true) {
updateAutopilotStatus(true)
startOrStopReadyToAutopilotLoop(false)
}
}
override fun onFail(code: Int, msg: String) {
updateAutopilotStatus(false)
}
})
}
fun updateAutopilotStatus(isBoarded: Boolean) {
if (mOrderStatusCallbackMap.isNotEmpty()) {
for (callback in mOrderStatusCallbackMap.values) {
callback.onDriverHasCheckedPilotCondition(isBoarded)
}
}
}
fun startOrStopReadyToAutopilotLoop(isStart: Boolean) {
if (isStart) {
BizLoopManager.setLoopFunction(STARTREADYTOAUTOPILOT, LoopInfo(1, TaxiPassengerModel::loopQueryPilotStatus))
CallerLogger.i(M_TAXI_P + TAG, "startReadyToAutopilot()")
} else {
BizLoopManager.removeLoopFunction(STARTREADYTOAUTOPILOT)
CallerLogger.i(M_TAXI_P + TAG, "stopReadyToAutopilot()")
}
}
fun setStation() {
if (currentOCHOrder != null) {
val startStation = MogoLocation()
startStation.longitude = currentOCHOrder!!.startSiteGcjPoint[0]
startStation.latitude = currentOCHOrder!!.startSiteGcjPoint[1]
val endStation = MogoLocation()
endStation.longitude = currentOCHOrder!!.endSiteGcjPoint[0]
endStation.latitude = currentOCHOrder!!.endSiteGcjPoint[1]
TrajectoryAndDistanceManager.setStationPoint(startStation, endStation, currentOCHOrder!!.lineId)
}
}
fun cleanStation() {
TrajectoryAndDistanceManager.setStationPoint(null, null, -1L)
}
}

View File

@@ -0,0 +1,92 @@
package com.mogo.och.taxi.passenger.network
import com.mogo.cloud.passport.MoGoAiCloudClientConfig
import com.mogo.och.taxi.passenger.bean.TaxiPassengerBaseRespBean
import com.mogo.och.taxi.passenger.bean.TaxiPassengerCheckPhoneUpdateOrderReqBean
import com.mogo.och.taxi.passenger.bean.TaxiPassengerOrderQueryReqBean
import com.mogo.och.taxi.passenger.bean.TaxiPassengerOrderQueryRespBean
import com.mogo.och.taxi.passenger.bean.TaxiPassengerOrdersInServiceQueryRespBean
import com.mogo.och.taxi.passenger.bean.TaxiPassengerStartReqBean
import io.reactivex.Observable
import retrofit2.http.Body
import retrofit2.http.GET
import retrofit2.http.Header
import retrofit2.http.Headers
import retrofit2.http.POST
import retrofit2.http.Query
/**
* Created by pangfan on 2021/8/19
*
* 网约车-出租车接口定义
*/
internal interface TaxiPassengerServiceApi {
/**
* 查询全部服务中/待服务订单没有的时候返回code 0空列表
* @param driverSn
* @return
*/
@Headers("Content-type:application/json;charset=UTF-8")
@GET("/autopilot-car-hailing/order/v2/driver/taxi/passenger/orderInService/query")
fun queryOrdersInAndWaitService(
@Header("appId") appId: String = MoGoAiCloudClientConfig.getInstance().serviceAppId,
@Header("ticket") ticket: String=MoGoAiCloudClientConfig.getInstance().token,
@Query("driverSn") driverSn: String
): Observable<TaxiPassengerOrdersInServiceQueryRespBean>
/**
* 通过orderNo查询订单信息用于本地已经有orderNo时
* @param data
* @return
*/
@Headers("Content-type:application/json;charset=UTF-8")
@POST("/autopilot-car-hailing/order/v2/driver/taxi/passenger/queryOrderById")
fun queryOrderById(
@Header("appId") appId: String = MoGoAiCloudClientConfig.getInstance().serviceAppId,
@Header("ticket") ticket: String=MoGoAiCloudClientConfig.getInstance().token,
@Body data: TaxiPassengerOrderQueryReqBean
): Observable<TaxiPassengerOrderQueryRespBean>
/**
* 提交用户输入的手机后4位、并进行状态扭转
* @param data
* @return
*/
@Headers("Content-type:application/json;charset=UTF-8")
@POST("/autopilot-car-hailing/cab/flow/v1/driver/taxi/passenger/verification/phone")
fun checkPhoneAndUpdateOrderStatus(
@Header("appId") appId: String = MoGoAiCloudClientConfig.getInstance().serviceAppId,
@Header("ticket") ticket: String=MoGoAiCloudClientConfig.getInstance().token,
@Body data: TaxiPassengerCheckPhoneUpdateOrderReqBean?
): Observable<TaxiPassengerBaseRespBean>
/**
* 查询司机是否已确认可开启自动驾驶
* @param appId
* @param ticket
* @param orderNo
* @return
*/
@Headers("Content-type:application/json;charset=UTF-8")
@GET("/autopilot-car-hailing/cab/flow/v1/driver/taxi/pilot/status")
fun queryPilotStatus(
@Header("appId") appId: String = MoGoAiCloudClientConfig.getInstance().serviceAppId,
@Header("ticket") ticket: String=MoGoAiCloudClientConfig.getInstance().token,
@Query("orderNo") orderNo: String
): Observable<TaxiPassengerBaseRespBean>
/**
* 乘客屏启动自动驾驶成功
* @param appId
* @param ticket
* @param data
* @return
*/
@Headers("Content-type:application/json;charset=UTF-8")
@POST("/autopilot-car-hailing/cab/flow/v1/driver/taxi/passenger/startServicePilot")
fun startServicePilotDone(
@Header("appId") appId: String = MoGoAiCloudClientConfig.getInstance().serviceAppId,
@Header("ticket") ticket: String= MoGoAiCloudClientConfig.getInstance().token,
@Body data: TaxiPassengerStartReqBean
): Observable<TaxiPassengerBaseRespBean>
}

View File

@@ -0,0 +1,127 @@
package com.mogo.och.taxi.passenger.network
import android.content.Context
import com.mogo.och.taxi.passenger.bean.TaxiPassengerOrdersInServiceQueryRespBean
import com.mogo.commons.AbsMogoApplication
import com.mogo.eagle.core.function.call.telematic.CallerTelematicManager
import com.mogo.och.taxi.passenger.bean.TaxiPassengerBaseRespBean
import com.mogo.och.taxi.passenger.bean.TaxiPassengerCheckPhoneUpdateOrderReqBean
import com.mogo.och.taxi.passenger.bean.TaxiPassengerStartReqBean
import com.mogo.eagle.core.network.MoGoRetrofitFactory
import com.mogo.eagle.core.utilcode.mogo.logger.CallerLogger
import com.mogo.eagle.core.utilcode.mogo.logger.scene.SceneConstant.Companion.M_TAXI_P
import com.mogo.och.common.module.biz.constant.OchCommonConst
import com.mogo.och.common.module.biz.network.OchCommonServiceCallback
import com.mogo.och.common.module.biz.network.OchCommonSubscribeImpl
import com.mogo.och.common.module.biz.network.interceptor.transformTry
import com.mogo.och.taxi.passenger.bean.TaxiPassengerOrderQueryReqBean
import com.mogo.och.taxi.passenger.bean.TaxiPassengerOrderQueryRespBean
/**
* Created by pangfan on 2021/8/19
*/
object TaxiPassengerServiceManager {
private val mOCHTaxiServiceApi: TaxiPassengerServiceApi =
MoGoRetrofitFactory.getInstance(OchCommonConst.getBaseUrl()).create(TaxiPassengerServiceApi::class.java)
private const val TAG = "TaxiPassengerServiceManager"
private var draiverSnCacher = ""
/**
* 获取Bus司机端的sn
* @return
*/
val draiverSn: String
get(){
val serverToken = CallerTelematicManager.getServerToken()
if (serverToken != draiverSnCacher && serverToken.isNotEmpty()) {
draiverSnCacher = serverToken
}
return draiverSnCacher
}
val context:Context
get() {
return AbsMogoApplication.getApp()
}
private fun beforeNet():Boolean{
if (draiverSn.isBlank()) {
CallerLogger.e(M_TAXI_P + TAG, "没有司机屏sn 请稍等在请求")
return true
}
return false
}
/**
* 查询全部服务中/待服务订单列表
* @param callback
*/
@JvmStatic
fun queryOrdersInAndWaitService(
callback: OchCommonServiceCallback<TaxiPassengerOrdersInServiceQueryRespBean>?
) {
if(beforeNet()){
return
}
mOCHTaxiServiceApi.queryOrdersInAndWaitService(driverSn = draiverSn) //获取到司机端的sn
.transformTry()
.subscribe(OchCommonSubscribeImpl(context, callback, "queryOrdersInAndWaitService"))
}
@JvmStatic
fun checkPhoneAndUpdateOrderStatus(
orderNo: String?,
phone: String?,
callback: OchCommonServiceCallback<TaxiPassengerBaseRespBean>?
) {
mOCHTaxiServiceApi.checkPhoneAndUpdateOrderStatus(data= TaxiPassengerCheckPhoneUpdateOrderReqBean(orderNo, phone))
.transformTry()
.subscribe(OchCommonSubscribeImpl(context, callback, "checkPhoneAndUpdateOrderStatus"))
}
@JvmStatic
fun queryPilotStatus(
orderNo: String,
callback: OchCommonServiceCallback<TaxiPassengerBaseRespBean>?
) {
mOCHTaxiServiceApi.queryPilotStatus(orderNo = orderNo)
.transformTry()
.subscribe(OchCommonSubscribeImpl(context, callback, "queryPilotStatus"))
}
@JvmStatic
fun startServicePilotDone(
orderNo: String?, loc: TaxiPassengerStartReqBean.Result?,
callback: OchCommonServiceCallback<TaxiPassengerBaseRespBean>?
) {
if(beforeNet()){
return
}
mOCHTaxiServiceApi.startServicePilotDone(data = TaxiPassengerStartReqBean(draiverSn, orderNo, loc))
.transformTry()
.subscribe(OchCommonSubscribeImpl(context, callback, "startServicePilotDone"))
}
/**
* 通过orderId查询订单信息用于本地已经有orderId时
* @param context
* @param orderNo
* @param callback
*/
@JvmStatic
fun queryOrderById(
context: Context, orderNo: String?,
callback: OchCommonServiceCallback<TaxiPassengerOrderQueryRespBean>?
) {
if(beforeNet()){
return
}
mOCHTaxiServiceApi.queryOrderById(
data=TaxiPassengerOrderQueryReqBean(draiverSn, orderNo)
)
.transformTry()
.subscribe(OchCommonSubscribeImpl(context, callback, "queryOrderById"))
}
}

View File

@@ -0,0 +1,153 @@
package com.mogo.och.taxi.passenger.presenter
import androidx.lifecycle.LifecycleOwner
import com.mogo.commons.AbsMogoApplication
import com.mogo.commons.mvp.Presenter
import com.mogo.eagle.core.function.call.biz.CallerFuncBizManager
import com.mogo.eagle.core.utilcode.mogo.logger.CallerLogger.d
import com.mogo.eagle.core.utilcode.mogo.logger.scene.SceneConstant
import com.mogo.eagle.core.utilcode.util.UiThreadHandler
import com.mogo.och.common.module.manager.OCHAdasAbilityManager
import com.mogo.och.taxi.passenger.bean.TaxiPassengerOrderQueryRespBean
import com.mogo.och.taxi.passenger.callback.IOCHTaxiPassengerOrderStatusCallback
import com.mogo.och.taxi.passenger.constant.TaxiPassengerOrderStatusEnum
import com.mogo.och.taxi.passenger.model.AutopilotManager
import com.mogo.och.taxi.passenger.model.TaxiPassengerModel
import com.mogo.och.taxi.passenger.ui.TaxiPassengerBaseFragment
/**
* @author: wangmingjun
* @date: 2022/3/4
*/
class BaseTaxiPassengerPresenter(view: TaxiPassengerBaseFragment?) :
Presenter<TaxiPassengerBaseFragment?>(view), IOCHTaxiPassengerOrderStatusCallback {
init {
TaxiPassengerModel.init(AbsMogoApplication.getApp())
OCHAdasAbilityManager.getInstance().init(AbsMogoApplication.getApp())
initListeners()
}
override fun onCreate(owner: LifecycleOwner) {
super.onCreate(owner)
d(SceneConstant.M_TAXI_P + TAG, "网约车-出租车拿到订单")
}
override fun onDestroy(owner: LifecycleOwner) {
super.onDestroy(owner)
releaseListeners()
TaxiPassengerModel.release()
OCHAdasAbilityManager.getInstance().release()
}
private fun initListeners() {
TaxiPassengerModel.setOrderStatusCallback("BaseTaxiPassengerPresenter", this)
}
private fun releaseListeners() {
TaxiPassengerModel.setOrderStatusCallback("BaseTaxiPassengerPresenter", null)
}
override fun onCurrentOrderStatusChanged(order: TaxiPassengerOrderQueryRespBean.Result?) {
order?.let {
updateOrderView(order)
}
}
override fun onCurrentOrderDistToEndChanged(
meters: Long,
timeInSecond: Long,
stationDistance: Int
) {
}
private fun updateOrderView(order: TaxiPassengerOrderQueryRespBean.Result?) {
order?.let {
setItineraryVisibility()
when (TaxiPassengerModel.curOrderStatus) {
TaxiPassengerOrderStatusEnum.ArriveAtStart -> {
// 20 司机到达上车点
mView?.showOrHideArrivedEndLayout(isShow = false)
mView?.showOrHidePressengerCheckPager(true, order.startSiteAddr,
order.endSiteAddr, order.passengerNum, order.passengerPhone)
mView?.showOrHideStartAutopilotView(isShow = false)
}
TaxiPassengerOrderStatusEnum.UserArriveAtStart -> {
// 30 乘客到达上车点
mView?.showOrHideArrivedEndLayout(isShow = false)
mView?.showOrHidePressengerCheckPager(isShow = false)
mView?.showOrHideStartAutopilotView(isShow = true)
}
TaxiPassengerOrderStatusEnum.OnTheWayToEnd -> {
// 服务中(去往目的地)
mView?.showOrHideArrivedEndLayout(isShow = false)
mView?.showOrHidePressengerCheckPager(isShow = false)
mView?.showOrHideStartAutopilotView(isShow = false)
overMapViewShow()
}
TaxiPassengerOrderStatusEnum.ArriveAtEnd -> {
// 50 到达终点 乘客可以评价
mView?.showOrHideArrivedEndLayout(true)
mView?.showOrHidePressengerCheckPager(isShow = false)
mView?.showOrHideStartAutopilotView(isShow = false)
overMapViewClear()
}
TaxiPassengerOrderStatusEnum.JourneyCompleted -> {
// 60 行程完成
mView?.showOrHideStartAutopilotView(isShow = false)
mView?.showOrHidePressengerCheckPager(isShow = false)
mView?.showOrHideArrivedEndLayout(false)
overMapViewClear()
}
TaxiPassengerOrderStatusEnum.Cancel -> {
// 70 取消订单
mView?.showOrHideStartAutopilotView(isShow = false)
mView?.showOrHidePressengerCheckPager(isShow = false)
mView?.showOrHideArrivedEndLayout(isShow = false)
overMapViewClear()
}
else -> {}
}
}
}
private fun overMapViewShow(){
CallerFuncBizManager.bizProvider.getAllV2XEvents()
}
private fun overMapViewClear(){
CallerFuncBizManager.bizProvider.stopQueryV2XEvents()
mView?.showOrHideOverMapView()
}
fun checkAndUpdateStatus(phone: String?) {
TaxiPassengerModel.checkPhoneAndUpdateStatus(phone) {
mView?.showOrHidePressengerCheckPager(isShow = false)
}
}
fun setItineraryVisibility() {
UiThreadHandler.post {
when (TaxiPassengerModel.curOrderStatus) {
TaxiPassengerOrderStatusEnum.None,
TaxiPassengerOrderStatusEnum.OnTheWayToStart,
TaxiPassengerOrderStatusEnum.ArriveAtStart,
TaxiPassengerOrderStatusEnum.JourneyCompleted,
TaxiPassengerOrderStatusEnum.ArriveAtEnd,
TaxiPassengerOrderStatusEnum.Cancel -> mView?.showOrHideServingOrderFragment(false)
TaxiPassengerOrderStatusEnum.UserArriveAtStart,
TaxiPassengerOrderStatusEnum.OnTheWayToEnd -> mView?.showOrHideServingOrderFragment(true)
}
}
}
override fun onMessageGo2OverMapview() {
mView?.showOverMapView()
}
companion object {
private val TAG = BaseTaxiPassengerPresenter::class.java.simpleName
}
}

View File

@@ -0,0 +1,33 @@
package com.mogo.och.taxi.passenger.provider;
import android.content.Context;
import android.view.View;
import androidx.annotation.NonNull;
import com.alibaba.android.arouter.facade.annotation.Route;
import com.mogo.eagle.core.data.constants.MogoServicePaths;
import com.mogo.eagle.core.function.api.hmi.view.IStatusViewLayout;
import com.mogo.och.taxi.passenger.ui.statusview.StatusBarView;
/**
* @author congtaowang
* @since 2020-01-06
* <p>
* 根据优先级控制显示 window view.
*/
@Route( path = MogoServicePaths.PATH_STATUS_VIEW_MANAGER )
public class StatusViewManager implements IStatusViewLayout {
@NonNull
@Override
public View getStatusView(Context context) {
return new StatusBarView(context);
}
@Override
public void init(Context context) {
}
}

View File

@@ -0,0 +1,180 @@
package com.mogo.och.taxi.passenger.ui
import android.annotation.SuppressLint
import android.content.Context
import android.content.Intent
import android.graphics.drawable.ClipDrawable
import android.graphics.drawable.Drawable
import android.graphics.drawable.LayerDrawable
import android.media.AudioManager
import android.provider.Settings
import android.text.TextUtils
import android.util.AttributeSet
import android.view.Gravity
import android.view.LayoutInflater
import android.view.View
import android.widget.SeekBar
import androidx.constraintlayout.widget.ConstraintLayout
import androidx.core.content.ContextCompat
import com.mogo.commons.module.intent.IMogoIntentListener
import com.mogo.commons.module.intent.IntentManager
import com.mogo.commons.module.receiver.MogoReceiver
import com.mogo.commons.module.receiver.MogoReceiver.ACTION_VOLUME_CHANGE
import com.mogo.eagle.core.function.api.autopilot.IMoGoAutopilotStatusListener
import com.mogo.eagle.core.function.call.setting.CallerRequestActivityHandleManager
import com.mogo.eagle.core.utilcode.util.BrightnessUtils
import com.mogo.eagle.core.utilcode.util.ThreadUtils
import com.mogo.och.common.module.wigets.MineGradientDrawable
import com.mogo.och.taxi.passenger.R
import kotlinx.android.synthetic.main.taxi_p_setting_view.view.*
import me.jessyan.autosize.utils.AutoSizeUtils
class TaxiPSettingView @JvmOverloads constructor(
context: Context,
attrs: AttributeSet? = null,
defStyleAttr: Int = 0
) : ConstraintLayout(context, attrs, defStyleAttr), IMoGoAutopilotStatusListener,
IMogoIntentListener {
companion object {
const val TAG = "TaxiPSettingView"
}
init {
LayoutInflater.from(context).inflate(R.layout.taxi_p_setting_view, this, true)
initView()
}
private var mAudioManager: AudioManager? = null
private var mMaxVolume: Int? = 15
@SuppressLint("NewApi")
private fun initView() {
sb_light_bar.setProgressDrawableTiled(getDrawable())
sb_light_bar.max = 100
sb_light_bar.min = 5
sb_light_bar.setOnSeekBarChangeListener(object : SeekBar.OnSeekBarChangeListener {
override fun onProgressChanged(seekBar: SeekBar?, progress: Int, fromUser: Boolean) {
tvSoundPer.text = "$progress%"
if (fromUser) {
if (!Settings.System.canWrite(context)) {
CallerRequestActivityHandleManager.requestPermission(
TAG,
Settings.ACTION_MANAGE_WRITE_SETTINGS
)
return
}
if (BrightnessUtils.isAutoBrightnessEnabled()) {
BrightnessUtils.setBrightness(((progress.toFloat() / 100) * 255).toInt())
} else {
BrightnessUtils.setAutoBrightnessEnabled(true)
}
}
}
override fun onStartTrackingTouch(seekBar: SeekBar?) {}
override fun onStopTrackingTouch(seekBar: SeekBar?) {}
})
sb_light_bar.progress = (BrightnessUtils.getBrightness() * 100) / 255
sb_voice_bar.setProgressDrawableTiled(getDrawable())
sb_voice_bar.max = 100
sb_voice_bar.min = 5
sb_voice_bar.setOnSeekBarChangeListener(object : SeekBar.OnSeekBarChangeListener {
override fun onProgressChanged(seekBar: SeekBar?, progress: Int, fromUser: Boolean) {
tvVoicePer.text = "$progress%"
if (fromUser) {
mMaxVolume?.let {
var currentValue = ((progress.toFloat() / 100) * it).toInt()
if (currentValue <= 0) {
currentValue = 1
}
mAudioManager?.setStreamVolume(
AudioManager.STREAM_MUSIC,
currentValue,
AudioManager.FLAG_REMOVE_SOUND_AND_VIBRATE
)
}
}
}
override fun onStartTrackingTouch(seekBar: SeekBar?) {}
override fun onStopTrackingTouch(seekBar: SeekBar?) {}
})
context?.let {
mAudioManager = it.getSystemService(Context.AUDIO_SERVICE) as AudioManager
mMaxVolume = mAudioManager?.getStreamMaxVolume(AudioManager.STREAM_MUSIC)
updateVolume()
}
}
private fun updateVolume() {
val mCurrentVolume = mAudioManager?.getStreamVolume(AudioManager.STREAM_MUSIC)
mMaxVolume?.let { max ->
mCurrentVolume?.let { current ->
if (current == 1) {
sb_voice_bar.progress = 5
tvVoicePer.text = "5%"
} else {
val fl = current.toFloat() / max * 100
sb_voice_bar.progress = fl.toInt()
tvVoicePer.text = "${fl.toInt()}%"
}
}
}
}
private fun getDrawable(): Drawable {
val dp2px = AutoSizeUtils.dp2px(context, 26f)//进度条高度
val color2CBFFC = ContextCompat.getColor(context, R.color.taxi_p_2CBFFC)
val color1060FF = ContextCompat.getColor(context, R.color.taxi_p_1060ff)
val color96A5C2 = ContextCompat.getColor(context, R.color.taxi_p_96a5c2)
val temp03 = MineGradientDrawable(color2CBFFC, color1060FF, dp2px)
val clipDrawable3 = ClipDrawable(temp03, Gravity.START, ClipDrawable.HORIZONTAL)
val temp01 = MineGradientDrawable(color96A5C2, color96A5C2, dp2px)
val arr = arrayOf(temp01, clipDrawable3)
val ld = LayerDrawable(arr)
ld.setDrawableByLayerId(android.R.id.background, temp01)
ld.setDrawableByLayerId(android.R.id.progress, clipDrawable3)
return ld
}
override fun onAttachedToWindow() {
super.onAttachedToWindow()
IntentManager.getInstance().registerIntentListener(ACTION_VOLUME_CHANGE, this)
}
override fun onDetachedFromWindow() {
super.onDetachedFromWindow()
IntentManager.getInstance().unregisterIntentListener(ACTION_VOLUME_CHANGE, this)
}
override fun onWindowVisibilityChanged(visibility: Int) {
super.onWindowVisibilityChanged(visibility)
if (visibility == View.VISIBLE) {
sb_light_bar.progress = (BrightnessUtils.getBrightness() * 100) / 255
}
}
override fun onWindowFocusChanged(hasWindowFocus: Boolean) {
super.onWindowFocusChanged(hasWindowFocus)
if (hasWindowFocus) {
sb_light_bar.progress = (BrightnessUtils.getBrightness() * 100) / 255
}
}
override fun onIntentReceived(intentStr: String?, intent: Intent?) {
if (TextUtils.equals(ACTION_VOLUME_CHANGE, intentStr)) {
if (intent!!.getIntExtra(
MogoReceiver.EXTRA_VOLUME_STREAM_TYPE, -1
) == AudioManager.STREAM_MUSIC
) {
ThreadUtils.runOnUiThread {
updateVolume()
}
}
}
}
}

View File

@@ -0,0 +1,344 @@
package com.mogo.och.taxi.passenger.ui
import android.os.Bundle
import android.view.View
import com.mogo.commons.mvp.MvpFragment
import com.mogo.commons.voice.AIAssist
import com.mogo.eagle.core.function.call.hmi.CallerHmiManager
import com.mogo.eagle.core.function.call.map.CallerMapUIServiceManager
import com.mogo.eagle.core.utilcode.kotlin.onClick
import com.mogo.eagle.core.utilcode.mogo.logger.CallerLogger
import com.mogo.eagle.core.utilcode.mogo.logger.scene.SceneConstant.Companion.M_TAXI_P
import com.mogo.eagle.core.utilcode.util.DeviceUtils
import com.mogo.eagle.core.utilcode.util.OverlayViewUtils
import com.mogo.map.listener.IMogoMapListener
import com.mogo.map.uicontroller.VisualAngleMode
import com.mogo.och.common.module.utils.RxUtils
import com.mogo.och.common.module.voice.VoiceNotice
import com.mogo.och.taxi.passenger.R
import com.mogo.och.taxi.passenger.callback.ITaxiPassengerCommonValueCallback
import com.mogo.och.taxi.passenger.presenter.BaseTaxiPassengerPresenter
import com.mogo.och.taxi.passenger.ui.arrived.ArrivedView
import com.mogo.och.taxi.passenger.ui.bottom.BottomBar
import com.mogo.och.taxi.passenger.ui.check.TaxiPassengerCheckView
import com.mogo.och.taxi.passenger.ui.startautopilot.StartAutopilotView
import com.mogo.och.taxi.passenger.widget.animutils.AnimationsContainer
import kotlinx.android.synthetic.main.taxi_p_base_fragment.*
import java.lang.ref.WeakReference
/**
* 网约车基础Fragment主要负责布局通用界面处理站点面板和通话面板互斥情况
*
*
* 部分业务放在了此处处理
*
* @author tongchenfei
*/
class TaxiPassengerBaseFragment() :
MvpFragment<TaxiPassengerBaseFragment?, BaseTaxiPassengerPresenter?>(), IMogoMapListener,
TaxiPassengerTaxiView {
/**
* 到达目的地
*/
private var mArrivedEndView: WeakReference<ArrivedView?>? = null
/**
* 手机号后四位验证
*/
private var mArrivedCheckView: WeakReference<TaxiPassengerCheckView?>? = null
/**
* 启动自驾页面
*/
private var mStartAutopilotView: WeakReference<StartAutopilotView?>? = null
private var createProgressDialogAnim: AnimationsContainer?=null
override fun getLayoutId(): Int {
return R.layout.taxi_p_base_fragment
}
override fun getTagName(): String {
return "BaseOchTaxiPassengerFragment"
}
override fun initViews() {
initListener()
}
override fun initViews(savedInstanceState: Bundle?) {
super.initViews(savedInstanceState)
mapBizView!!.onCreate(savedInstanceState)
overMapView.onCreateView(savedInstanceState)
overMapView.hideResetView()
createProgressDialogAnim = AnimationsContainer(R.array.xiaozhi_normal, 20,aciv_xiaozhi_normal)
createProgressDialogAnim?.setOnAnimStopListener(object :AnimationsContainer.OnAnimationStoppedListener{
override fun AnimationStopped() {
CallerLogger.d(M_TAXI_P + TAG, "动画暂停")
}
})
}
private fun initListener() {
ck_setting.isChecked = false
ck_setting.setOnCheckedChangeListener { _, isChecked ->
clSettingView.visibility = if(isChecked) View.VISIBLE else View.GONE
}
bottom.setOverMapApplyClick(object : BottomBar.ApplyClickLintener{
override fun onApplyClick(selectItem: BottomBar.SelectView) {
when (selectItem) {
BottomBar.SelectView.PRECISIONMAP -> {
overMapView.visibility = View.GONE
mapBizView.visibility = View.VISIBLE
presenter?.setItineraryVisibility()
ck_setting.visibility = View.VISIBLE
if (DeviceUtils.isLenovoModel() || DeviceUtils.isEB5Model()) {
romaPView.visibility = View.VISIBLE
} else {
romaPView.visibility = View.GONE
}
rv_location_center.visibility = View.VISIBLE
pcnActionView.visibility = View.VISIBLE
CallerHmiManager.showTrafficLightView()
infoVideoView.visibility = View.GONE
CallerHmiManager.showTurnLightView()
}
BottomBar.SelectView.OVERMAPVIEW -> {
overMapView.visibility = View.VISIBLE
mapBizView.visibility = View.GONE
presenter?.setItineraryVisibility()
ck_setting.visibility = View.VISIBLE
romaPView.visibility = View.GONE
rv_location_center.visibility = View.VISIBLE
pcnActionView.visibility = View.VISIBLE
CallerHmiManager.showTrafficLightView()
infoVideoView.visibility = View.GONE
CallerHmiManager.showTurnLightView()
}
BottomBar.SelectView.VIDEO -> {
overMapView.visibility = View.GONE
mapBizView.visibility = View.GONE
presenter?.setItineraryVisibility()
ck_setting.visibility = View.GONE
ck_setting.isChecked = false
romaPView.visibility = View.GONE
rv_location_center.visibility = View.GONE
pcnActionView.visibility = View.GONE
CallerHmiManager.hideTrafficLightView()
infoVideoView.visibility = View.VISIBLE
CallerHmiManager.hideTurnLightView()
}
else -> {}
}
}
})
rv_location_center.onClick {
when (bottom.getCurrentPage()) {
BottomBar.SelectView.PRECISIONMAP -> {
val controller = CallerMapUIServiceManager.getMapUIController()
if (controller != null) {
//切换到地图中间
controller.changeMapVisualAngle(VisualAngleMode.MODE_MEDIUM_SIGHT, null)
// 切换缩放到中视角
controller.changeZoom2(0.8f)
}
}
BottomBar.SelectView.OVERMAPVIEW -> {
overMapView.displayCustomOverView()
}
else -> {}
}
}
view?.viewTreeObserver?.addOnWindowFocusChangeListener {
if(it){
CallerLogger.d(M_TAXI_P + TAG, "windows获取焦点")
createProgressDialogAnim?.start()
}else{
CallerLogger.d(M_TAXI_P + TAG, "window失去焦点")
createProgressDialogAnim?.stop()
}
}
}
private fun initCheckView() {
mArrivedCheckView = WeakReference(TaxiPassengerCheckView(context))
mArrivedCheckView!!.get()!!.iTaxiPassengerCommonValueCallback =
ITaxiPassengerCommonValueCallback { phoneTail: String? ->
getPresenter()!!.checkAndUpdateStatus(phoneTail)
}
}
override fun onResume() {
super.onResume()
mapBizView!!.onResume()
overMapView.onResume()
CallerLogger.d(M_TAXI_P + TAG, "onResume")
createProgressDialogAnim?.start()
}
override fun createPresenter(): BaseTaxiPassengerPresenter {
return BaseTaxiPassengerPresenter(this)
}
override fun onLowMemory() {
super.onLowMemory()
mapBizView!!.onLowMemory()
}
override fun onSaveInstanceState(outState: Bundle) {
super.onSaveInstanceState(outState)
mapBizView!!.onSaveInstanceState(outState)
}
override fun onPause() {
super.onPause()
mapBizView!!.onPause()
overMapView?.onPause()
CallerLogger.d(M_TAXI_P + TAG, "onPause")
createProgressDialogAnim?.stop()
}
override fun onDestroyView() {
mapBizView!!.onDestroy()
overMapView?.onDestroy()
super.onDestroyView()
}
/**
* 显示或隐藏订单信息
*
* @param isShow
*/
fun showOrHideServingOrderFragment(isShow: Boolean) {
when (bottom.getCurrentPage()) {
BottomBar.SelectView.OVERMAPVIEW,BottomBar.SelectView.PRECISIONMAP -> {
if (isShow) {
if(itinerary.visibility!=View.VISIBLE) {
itinerary.visibility = View.VISIBLE
}
} else {
if(itinerary.visibility!=View.GONE) {
itinerary.visibility = View.GONE
}
}
}
BottomBar.SelectView.VIDEO,BottomBar.SelectView.NONE -> {
if(itinerary.visibility!=View.GONE) {
itinerary.visibility = View.GONE
}
}
}
}
/**
* 显示或者隐藏乘客可点击自动驾驶页面
* 乘客验证成功,页面显示,按钮置于不可点击
* 司机端确认可点击开启自动驾驶, 按钮置为可点击
* 订单前往目的地,页面消失
*
* @param isShow
*/
fun showOrHideStartAutopilotView(isShow: Boolean) {
if (isShow) {
exitFullVideoScreen(false)
if (mStartAutopilotView == null || mStartAutopilotView!!.get() == null) {
mStartAutopilotView = WeakReference(StartAutopilotView(requireContext()))
}
mStartAutopilotView?.get()?.let {
OverlayViewUtils.showOverlayView(activity, it)
it.handleStartAutopilotBtnStatus(false)
}
} else {
mStartAutopilotView?.get()?.closeAllAnimsAndView()
mStartAutopilotView = null
}
}
/**
* 显示或者隐藏到达乘客站点的洁面
* ① 取消订单 可有可无
* ② 到达上车点 隐藏到达终点的页面(上一个订单没有评价)
* ③ 到达目的地 显示到达终点的页面
* ④ debug 使用
*
* @param isShow true 展示 false 隐藏
*/
fun showOrHideArrivedEndLayout(isShow: Boolean) {
if (isShow) {
exitFullVideoScreen(true)
if (mArrivedEndView == null || mArrivedEndView!!.get() == null) {
mArrivedEndView = WeakReference(ArrivedView(context))
}
mArrivedEndView?.get()?.let {
OverlayViewUtils.showOverlayView(activity, it, R.style.och_window_anim_alpha)
RxUtils.createSubscribe(500) {
it.setDataAndStartAnimation()
VoiceNotice.showNotice("已到达目的地,带好随身物品,右侧下车更安全!期待下次再见", AIAssist.LEVEL2)
}
}
} else {
mArrivedEndView?.get()?.let {
OverlayViewUtils.dismissOverlayView(it)
}
}
}
private fun exitFullVideoScreen(resetVideoPlayer: Boolean) {
infoVideoView.exitFullScreenMode(resetVideoPlayer)
}
fun showOrHideOverMapView(){
overMapView?.clearV2XMarkers()
overMapView?.clearCustomPolyline()
}
/**
* ① 取消订单 到达上车点后乘客取消订单 隐藏乘客验证页面
* ② 司机到达上车点 到达上车点 展示乘客验证页面
* ③ 乘客到达上车点 手机号验证成功后 隐藏乘客验证页面
* ④ debug 使用
*/
fun showOrHidePressengerCheckPager(
isShow: Boolean,
startSiteAddr: String? = "",
endSiteAddr: String? = "",
passengerNum: String? = "",
phone: String? = ""
) {
try {
if (isShow) {
exitFullVideoScreen(false)
if (mArrivedCheckView == null || mArrivedCheckView!!.get() == null) {
initCheckView()
}
mArrivedCheckView!!.get()!!
.setData(startSiteAddr, endSiteAddr, passengerNum, phone)
OverlayViewUtils.showOverlayView(activity, mArrivedCheckView!!.get())
} else {
if (mArrivedCheckView == null || mArrivedCheckView!!.get() == null) {
return
}
OverlayViewUtils.dismissOverlayView(mArrivedCheckView!!.get())
}
} catch (e: Exception) {
e.printStackTrace()
}
}
fun showOverMapView() {
bottom.setCheckIndex(BottomBar.SelectView.OVERMAPVIEW)
}
companion object {
@JvmField
val TAG = "TaxiPassengerBaseFragment"
}
}

View File

@@ -0,0 +1,13 @@
package com.mogo.och.taxi.passenger.ui;
import com.mogo.commons.mvp.IView;
/**
* @author congtaowang
* @since 2021/1/18
*
* 描述
*/
public interface TaxiPassengerTaxiView extends IView {
}

View File

@@ -0,0 +1,130 @@
package com.mogo.och.taxi.passenger.ui.arrived
import android.content.Context
import android.util.AttributeSet
import android.view.LayoutInflater
import androidx.lifecycle.ViewModelProvider
import com.mogo.commons.AbsMogoApplication
import com.mogo.eagle.core.utilcode.kotlin.onClick
import com.mogo.eagle.core.utilcode.mogo.logger.CallerLogger.d
import com.mogo.eagle.core.utilcode.mogo.logger.scene.SceneConstant
import com.mogo.eagle.core.utilcode.util.OverlayViewUtils
import com.mogo.och.common.module.utils.RxUtils
import com.mogo.och.taxi.passenger.R
import com.mogo.och.taxi.passenger.widget.WindowRelativeLayout
import com.mogo.och.taxi.passenger.widget.animutils.AnimationsContainer
import com.shuyu.gsyvideoplayer.GSYVideoManager
import com.shuyu.gsyvideoplayer.builder.GSYVideoOptionBuilder
import com.shuyu.gsyvideoplayer.listener.GSYSampleCallBack
import io.reactivex.disposables.Disposable
import kotlinx.android.synthetic.main.taxi_p_arrived_end_panel.view.aciv_close
import kotlinx.android.synthetic.main.taxi_p_arrived_end_panel.view.actv_endstation
import kotlinx.android.synthetic.main.taxi_p_arrived_end_panel.view.iv_xiaozhi_belt
import kotlinx.android.synthetic.main.taxi_p_arrived_end_panel.view.svp_frame
import kotlinx.android.synthetic.main.taxi_p_arrived_end_panel.view.v_video_right_rear_view
/**
*
* 评价View
* Created on 2022/5/16
*/
class ArrivedView : WindowRelativeLayout, ArrivedViewModel.ArrivedViewCallback {
constructor(context: Context?) : super(context)
constructor(context: Context?, attributeSet: AttributeSet) : super(context, attributeSet)
constructor(context: Context?, attributeSet: AttributeSet, defStyleAttr: Int) : super(context, attributeSet, defStyleAttr)
constructor(context: Context?, attributeSet: AttributeSet, defStyleAttr: Int, defStyleRes: Int) : super(context, attributeSet, defStyleAttr, defStyleRes)
private var subscribe: Disposable?=null
private val gsyVideoOptionBuilder = GSYVideoOptionBuilder()
private var taxiPxiaozhiLove: AnimationsContainer?=null
private fun initView() {
d(SceneConstant.M_TAXI_P + TAG, "initView")
LayoutInflater.from(context).inflate(R.layout.taxi_p_arrived_end_panel, this, true)
svp_frame.setBackgroundResource(R.drawable.tail_ani_0000)
svp_frame.setIsTouchWiget(false)
svp_frame.setIsTouchWigetFull(false)
svp_frame.enableshowProgressDialog = false
svp_frame.enableDoubleClick = false
GSYVideoManager.instance().enableRawPlay(AbsMogoApplication.getApp())
val url = "android.resource://" + context.packageName + "/" + R.raw.end_video
gsyVideoOptionBuilder.setUrl(url)
.setCacheWithPlay(false)
.setPlayTag("TaxiPassengerArrivedView")
.build(svp_frame)
aciv_close.onClick {
OverlayViewUtils.dismissOverlayView(this)
}
taxiPxiaozhiLove = AnimationsContainer(R.array.xiaozhi_love, 20,iv_xiaozhi_belt)
taxiPxiaozhiLove?.setOnAnimStopListener(object :AnimationsContainer.OnAnimationStoppedListener{
override fun AnimationStopped() {
d(SceneConstant.M_TAXI_P + TAG, "动画暂停")
}
})
}
override fun onAttachedToWindow() {
super.onAttachedToWindow()
val viewModel = ViewModelProvider(this).get(ArrivedViewModel::class.java)
viewModel.setViewCallback(this)
taxiPxiaozhiLove?.start()
v_video_right_rear_view.resetView()
}
override fun onDetachedFromWindow() {
svp_frame.setBackgroundResource(R.drawable.tail_ani_0000)
svp_frame.setVideoAllCallBack(null)
svp_frame.onVideoReset()
svp_frame.release()
taxiPxiaozhiLove?.stop()
v_video_right_rear_view.resetView()
super.onDetachedFromWindow()
subscribe?.let {
if (!it.isDisposed) {
it.dispose()
}
}
}
/**
* 设置目的地重置星星状态
*/
fun setDataAndStartAnimation() {
svp_frame.setVideoAllCallBack(object : GSYSampleCallBack() {
override fun onAutoComplete(url: String?, vararg objects: Any?) {
svp_frame.setBackgroundResource(R.drawable.tail_ani_0090)
}
})
svp_frame.startPlayLogic()
RxUtils.createSubscribe(60_000) {
OverlayViewUtils.dismissOverlayView(this@ArrivedView)
}
}
companion object {
const val TAG = "TaxiPassengerArrivedView"
}
init {
try {
initView()
} catch (e: Exception) {
e.printStackTrace()
}
}
override fun setEndStation(stationName: String) {
actv_endstation.text = stationName
}
}

View File

@@ -0,0 +1,32 @@
package com.mogo.och.taxi.passenger.ui.arrived
import androidx.lifecycle.ViewModel
import com.mogo.och.taxi.passenger.model.TaxiPassengerModel
class ArrivedViewModel: ViewModel() {
private val TAG = ArrivedViewModel::class.java.simpleName
private var viewCallback:ArrivedViewCallback?=null
init {
}
fun setViewCallback(viewCallback: ArrivedViewCallback){
this.viewCallback = viewCallback
TaxiPassengerModel.currentOCHOrder?.endSiteAddr?.let {
this.viewCallback?.setEndStation(it)
}
}
override fun onCleared() {
super.onCleared()
this.viewCallback = null
}
interface ArrivedViewCallback{
fun setEndStation(stationName:String)
}
}

View File

@@ -0,0 +1,162 @@
package com.mogo.och.taxi.passenger.ui.arrived
import android.content.Context
import android.graphics.Bitmap
import android.graphics.BitmapFactory
import android.graphics.drawable.BitmapDrawable
import android.util.AttributeSet
import android.util.Log
import android.view.LayoutInflater
import androidx.constraintlayout.widget.ConstraintLayout
import com.mogo.eagle.core.function.api.autopilot.IMoGoBackCameraVideoListener
import com.mogo.eagle.core.function.api.autopilot.IMoGoRoboBusJinlvM1StitchedVideoListener
import com.mogo.eagle.core.function.call.autopilot.CallerAutoPilotControlManager.setIsSubscribeBackCameraVideoVideo
import com.mogo.eagle.core.function.call.autopilot.CallerBackCameraVideoListenerManager
import com.mogo.eagle.core.function.call.autopilot.CallerRoboBusJinlvM1StitchedVideoListenerManager
import com.mogo.eagle.core.utilcode.mogo.logger.CallerLogger.d
import com.mogo.eagle.core.utilcode.mogo.logger.scene.SceneConstant
import com.mogo.eagle.core.utilcode.util.UiThreadHandler
import com.mogo.eagle.core.widget.media.video.TextureVideoViewOutlineProvider
import com.mogo.och.taxi.passenger.R
import kotlinx.android.synthetic.main.taxi_p_right_rear_cam.view.actv_cam_position_group
import kotlinx.android.synthetic.main.taxi_p_right_rear_cam.view.v_video_right_rear
/**
*
* 评价View
* Created on 2022/5/16
*/
class RightRearCamView : ConstraintLayout , IMoGoBackCameraVideoListener,
IMoGoRoboBusJinlvM1StitchedVideoListener,Runnable {
constructor(context: Context) : super(context)
constructor(context: Context, attributeSet: AttributeSet) : super(context, attributeSet)
constructor(context: Context, attributeSet: AttributeSet, defStyleAttr: Int) : super(context, attributeSet, defStyleAttr)
constructor(context: Context, attributeSet: AttributeSet, defStyleAttr: Int, defStyleRes: Int) : super(context, attributeSet, defStyleAttr, defStyleRes)
private var mBitmap: Bitmap? = null
private var mBitmapOptions: BitmapFactory.Options? = null //Bitmap管理类可有效减少Bitmap的OOM问题
private fun initView() {
d(SceneConstant.M_TAXI_P + TAG, "initView")
LayoutInflater.from(context).inflate(R.layout.taxi_p_right_rear_cam, this, true)
}
override fun onAttachedToWindow() {
super.onAttachedToWindow()
setIsSubscribeBackCameraVideoVideo(1, true)
CallerBackCameraVideoListenerManager.addListener(TAG, this)
CallerRoboBusJinlvM1StitchedVideoListenerManager.addListener(TAG, this)
}
override fun onDetachedFromWindow() {
super.onDetachedFromWindow()
setIsSubscribeBackCameraVideoVideo(1, false)
CallerBackCameraVideoListenerManager.removeListener(this)
CallerRoboBusJinlvM1StitchedVideoListenerManager.removeListener(this)
}
companion object {
const val TAG = "RightRearCamView"
}
init {
try {
initView()
} catch (e: Exception) {
e.printStackTrace()
}
}
fun resetView(){
actv_cam_position_group.visibility = GONE
v_video_right_rear.setImageResource(R.drawable.taxi_p_right_rear_cam)
}
override fun onBackCameraVideo(data: ByteArray) {
decodeData(data)
}
override fun onRoboBusJinlvM1StitchedVideo(data: ByteArray) {
decodeData(data)
}
var preTime :Long=System.currentTimeMillis()
@Synchronized
private fun decodeData(data: ByteArray){
val currentTimeMillis = System.currentTimeMillis()
val dexTime = currentTimeMillis - preTime
preTime = currentTimeMillis
if(dexTime<20){
return
}
d(SceneConstant.M_TAXI_P + TAG, "图片频率:$dexTime")
if (mBitmapOptions == null) {
val bmp = (v_video_right_rear.drawable as BitmapDrawable).bitmap
val width = bmp.width
val height = bmp.height
val config = bmp.config
mBitmap = Bitmap.createBitmap(width, height, config)
mBitmapOptions = BitmapFactory.Options()
//设置Bitmap内存复用
mBitmapOptions!!.inBitmap = mBitmap //Bitmap复用内存块类似对象池避免不必要的内存分配和回收
mBitmapOptions!!.inMutable = true //解码时返回可变Bitmap
val options = BitmapFactory.Options()
options.inJustDecodeBounds = true
BitmapFactory.decodeByteArray(data, 0, data.size, options)
mBitmapOptions!!.inSampleSize = calculateInSampleSize(options, width, height)
}
mBitmapOptions?.let {
try {
val preTime = System.currentTimeMillis()
BitmapFactory.decodeByteArray(data, 0, data.size, mBitmapOptions)
d(SceneConstant.M_TAXI_P + TAG, "decode时间${System.currentTimeMillis()-preTime}")
UiThreadHandler.post(this)
} catch (e: Exception) {
e.printStackTrace()
}
}
}
private fun calculateInSampleSize(options: BitmapFactory.Options, reqWidth: Int, reqHeight: Int): Int {
val width = options.outWidth
val height = options.outHeight
Log.i(TAG, "calculateInSampleSize: out width and height is $width height $height")
var inSampleWidth = 1
if (height > reqHeight || width > reqWidth) {
val halfHeight = height / 2
val halfWidth = width / 2
// 采样率设置为2的指数
while (halfHeight / inSampleWidth >= reqHeight && halfWidth / inSampleWidth >= reqWidth) {
inSampleWidth *= 2
}
}
while (width/inSampleWidth>reqWidth||height/inSampleWidth>reqHeight){
inSampleWidth++
}
return inSampleWidth
}
override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) {
super.onSizeChanged(w, h, oldw, oldh)
outlineProvider = TextureVideoViewOutlineProvider(36f)
clipToOutline = true
}
override fun run() {
if(actv_cam_position_group?.visibility == GONE) {
actv_cam_position_group?.visibility = VISIBLE
}
v_video_right_rear?.setImageBitmap(mBitmap)
}
}

View File

@@ -0,0 +1,81 @@
package com.mogo.och.taxi.passenger.ui.bottom
import android.content.Context
import android.util.AttributeSet
import android.view.LayoutInflater
import androidx.constraintlayout.widget.ConstraintLayout
import com.mogo.och.taxi.passenger.R
import kotlinx.android.synthetic.main.taxi_p_bottom_bar.view.actv_overmap
import kotlinx.android.synthetic.main.taxi_p_bottom_bar.view.actv_precisionmap
import kotlinx.android.synthetic.main.taxi_p_bottom_bar.view.actv_video
class BottomBar @JvmOverloads constructor(
context: Context,
attrs: AttributeSet? = null,
defStyleAttr: Int = 0
) : ConstraintLayout(context, attrs, defStyleAttr) {
private var checkIndex = SelectView.NONE
private var overMapViewApply:ApplyClickLintener?=null
init {
isClickable = true
LayoutInflater.from(context).inflate(R.layout.taxi_p_bottom_bar, this, true)
setBackgroundResource(R.drawable.taxi_p_bottom_bar_bg)
actv_precisionmap.setOnClickListener {
setCheckIndex(SelectView.PRECISIONMAP)
}
actv_overmap.setOnClickListener {
setCheckIndex(SelectView.OVERMAPVIEW)
}
actv_video.setOnClickListener {
setCheckIndex(SelectView.VIDEO)
}
}
fun getCurrentPage():SelectView{
return checkIndex
}
override fun onAttachedToWindow() {
super.onAttachedToWindow()
setCheckIndex(SelectView.PRECISIONMAP)
}
fun setOverMapApplyClick(overMapViewApply:ApplyClickLintener){
this.overMapViewApply = overMapViewApply
}
fun setCheckIndex(index: SelectView){
if(checkIndex==index){
return
}else{
checkIndex = index
}
overMapViewApply?.onApplyClick(checkIndex)
if(checkIndex == SelectView.OVERMAPVIEW){
actv_overmap.setCheckItem(true)
}else{
actv_overmap.setCheckItem(false)
}
if(checkIndex == SelectView.VIDEO){
actv_video.setCheckItem(true)
}else{
actv_video.setCheckItem(false)
}
if(checkIndex == SelectView.PRECISIONMAP){
actv_precisionmap.setCheckItem(true)
}else{
actv_precisionmap.setCheckItem(false)
}
}
enum class SelectView{
NONE,PRECISIONMAP,OVERMAPVIEW,VIDEO
}
interface ApplyClickLintener{
fun onApplyClick(selectItem:SelectView)
}
}

View File

@@ -0,0 +1,92 @@
package com.mogo.och.bus.passenger.ui.view.bottom
import android.content.Context
import android.util.AttributeSet
import android.view.LayoutInflater
import android.view.View
import androidx.constraintlayout.widget.ConstraintLayout
import androidx.core.content.ContextCompat
import com.mogo.och.taxi.passenger.R
import kotlinx.android.synthetic.main.m1_bottom_check.view.aciv_center_image
import kotlinx.android.synthetic.main.m1_bottom_check.view.actv_title
open class BottomCheckView @JvmOverloads constructor(
context: Context,
attrs: AttributeSet? = null,
defStyleAttr: Int = 0
) : ConstraintLayout(context, attrs, defStyleAttr) {
companion object {
private const val TAG = "StopSiteView"
}
private var backageViewId: Int = -1
private var bottomTitle: String = ""
private var selectedDrawable: Int = -1
private var normalDrawable: Int = -1
private var bottomTitleNormalColor:Int = -1
private var bottomTitleCheckedColor:Int = -1
private var backageView: View? = null
private var isCheck = false
init {
LayoutInflater.from(context).inflate(R.layout.m1_bottom_check, this, true)
try {
val typedArray = context.obtainStyledAttributes(attrs, R.styleable.BottomSelectView)
backageViewId = typedArray.getResourceId(R.styleable.BottomSelectView_backageViewId, -1)
bottomTitle = typedArray.getString(R.styleable.BottomSelectView_bottomTitle) ?: ""
selectedDrawable = typedArray.getResourceId(R.styleable.BottomSelectView_selectedDrawable, -1)
normalDrawable = typedArray.getResourceId(R.styleable.BottomSelectView_normalDrawable, -1)
bottomTitleNormalColor = typedArray.getColor(R.styleable.BottomSelectView_bottomTitleNormalColor,
ContextCompat.getColor(context,R.color.white))
bottomTitleCheckedColor = typedArray.getColor(R.styleable.BottomSelectView_bottomTitleCheckedColor,
ContextCompat.getColor(context,R.color.white))
typedArray.recycle()
initView(context)
} catch (e: Exception) {
e.printStackTrace()
}
}
private fun initView(context: Context) {
if (selectedDrawable > 0) {
aciv_center_image.setImageResource(normalDrawable)
}
actv_title.text = bottomTitle
}
fun setCheckItem(isCheck: Boolean) {
if (isCheck != this.isCheck) {
this.isCheck = isCheck
notifiBackageView()
}
}
private fun notifiBackageView() {
if (isCheck) {
backageView?.visibility = View.VISIBLE
aciv_center_image.setImageResource(selectedDrawable)
actv_title.setTextColor(bottomTitleCheckedColor)
} else {
backageView?.visibility = View.GONE
aciv_center_image.setImageResource(normalDrawable)
actv_title.setTextColor(bottomTitleNormalColor)
}
}
override fun onAttachedToWindow() {
super.onAttachedToWindow()
parent?.let {
if (parent is ConstraintLayout) {
if (backageViewId > 0) {
backageView = (parent as ConstraintLayout).findViewById(backageViewId)
}
}
}
if (isCheck) {
backageView?.visibility = View.VISIBLE
}
}
}

View File

@@ -0,0 +1,250 @@
package com.mogo.och.taxi.passenger.ui.check
import android.content.Context
import android.graphics.Typeface
import android.text.Spannable
import android.text.SpannableStringBuilder
import android.text.style.TextAppearanceSpan
import android.util.AttributeSet
import android.view.LayoutInflater
import android.view.View
import android.widget.RelativeLayout
import android.widget.TextView
import androidx.core.content.ContextCompat
import com.mogo.commons.voice.AIAssist
import com.mogo.eagle.core.utilcode.mogo.logger.CallerLogger.d
import com.mogo.eagle.core.utilcode.mogo.logger.scene.SceneConstant
import com.mogo.eagle.core.utilcode.util.ToastUtils
import com.mogo.och.common.module.voice.VoiceNotice
import com.mogo.och.taxi.passenger.R
import com.mogo.och.taxi.passenger.callback.ITaxiPassengerCommonValueCallback
import kotlinx.android.synthetic.main.taxi_p_passenger_check_panel.view.tv_passenger_count
import kotlinx.android.synthetic.main.taxi_p_passenger_check_panel.view.tv_passenger_end
import kotlinx.android.synthetic.main.taxi_p_passenger_check_panel.view.tv_passenger_start
import kotlinx.android.synthetic.main.taxi_p_passenger_check_panel.view.tv_taxi_passenger_number_back
import kotlinx.android.synthetic.main.taxi_p_passenger_check_panel.view.tv_taxi_passenger_number_eight
import kotlinx.android.synthetic.main.taxi_p_passenger_check_panel.view.tv_taxi_passenger_number_first
import kotlinx.android.synthetic.main.taxi_p_passenger_check_panel.view.tv_taxi_passenger_number_five
import kotlinx.android.synthetic.main.taxi_p_passenger_check_panel.view.tv_taxi_passenger_number_four
import kotlinx.android.synthetic.main.taxi_p_passenger_check_panel.view.tv_taxi_passenger_number_fourth
import kotlinx.android.synthetic.main.taxi_p_passenger_check_panel.view.tv_taxi_passenger_number_nine
import kotlinx.android.synthetic.main.taxi_p_passenger_check_panel.view.tv_taxi_passenger_number_one
import kotlinx.android.synthetic.main.taxi_p_passenger_check_panel.view.tv_taxi_passenger_number_second
import kotlinx.android.synthetic.main.taxi_p_passenger_check_panel.view.tv_taxi_passenger_number_seven
import kotlinx.android.synthetic.main.taxi_p_passenger_check_panel.view.tv_taxi_passenger_number_six
import kotlinx.android.synthetic.main.taxi_p_passenger_check_panel.view.tv_taxi_passenger_number_submit
import kotlinx.android.synthetic.main.taxi_p_passenger_check_panel.view.tv_taxi_passenger_number_third
import kotlinx.android.synthetic.main.taxi_p_passenger_check_panel.view.tv_taxi_passenger_number_three
import kotlinx.android.synthetic.main.taxi_p_passenger_check_panel.view.tv_taxi_passenger_number_two
import kotlinx.android.synthetic.main.taxi_p_passenger_check_panel.view.tv_taxi_passenger_number_zero
/**
* V2X预警事件view通过FloatWindow呈现无需加入到自定义layout中
*
* Created on 2022/3/16
*/
class TaxiPassengerCheckView :RelativeLayout, View.OnClickListener {
constructor(context: Context?) : super(context)
constructor(context: Context?, attributeSet: AttributeSet) : super(context, attributeSet)
constructor(context: Context?, attributeSet: AttributeSet, defStyleAttr: Int) : super(context, attributeSet, defStyleAttr)
constructor(context: Context?, attributeSet: AttributeSet, defStyleAttr: Int, defStyleRes: Int) : super(context, attributeSet, defStyleAttr, defStyleRes)
var iTaxiPassengerCommonValueCallback: ITaxiPassengerCommonValueCallback<String>?=null
private var index = 0
private var phone = ""
private val numSelect = arrayOfNulls<Int>(4)
private val numSelectTextView = arrayOfNulls<TextView>(4)
private fun initView(context: Context) {
d(SceneConstant.M_TAXI_P + TAG, "initView")
LayoutInflater.from(context).inflate(R.layout.taxi_p_passenger_check_panel, this, true)
keyBoardLogic()
numSelectTextView[0] = tv_taxi_passenger_number_first
numSelectTextView[1] = tv_taxi_passenger_number_second
numSelectTextView[2] = tv_taxi_passenger_number_third
numSelectTextView[3] = tv_taxi_passenger_number_fourth
}
private fun keyBoardLogic() {
tv_taxi_passenger_number_one.setOnClickListener(this)
tv_taxi_passenger_number_two.setOnClickListener(this)
tv_taxi_passenger_number_three.setOnClickListener(this)
tv_taxi_passenger_number_four.setOnClickListener(this)
tv_taxi_passenger_number_five.setOnClickListener(this)
tv_taxi_passenger_number_six.setOnClickListener(this)
tv_taxi_passenger_number_seven.setOnClickListener(this)
tv_taxi_passenger_number_eight.setOnClickListener(this)
tv_taxi_passenger_number_nine.setOnClickListener(this)
tv_taxi_passenger_number_zero.setOnClickListener(this)
tv_taxi_passenger_number_back.setOnClickListener(this)
tv_taxi_passenger_number_submit.setOnClickListener(this)
tv_taxi_passenger_number_first.setOnClickListener(this)
tv_taxi_passenger_number_second.setOnClickListener(this)
tv_taxi_passenger_number_third.setOnClickListener(this)
tv_taxi_passenger_number_fourth.setOnClickListener(this)
}
override fun onClick(v: View?) {
when (v?.id) {
R.id.tv_taxi_passenger_number_one -> {showNumver(1)}
R.id.tv_taxi_passenger_number_two -> {showNumver(2)}
R.id.tv_taxi_passenger_number_three-> {showNumver(3)}
R.id.tv_taxi_passenger_number_four-> {showNumver(4)}
R.id.tv_taxi_passenger_number_five -> {showNumver(5)}
R.id.tv_taxi_passenger_number_six -> {showNumver(6)}
R.id.tv_taxi_passenger_number_seven -> {showNumver(7)}
R.id.tv_taxi_passenger_number_eight -> {showNumver(8)}
R.id.tv_taxi_passenger_number_nine -> {showNumver(9)}
R.id.tv_taxi_passenger_number_zero -> {showNumver(0)}
R.id.tv_taxi_passenger_number_back -> {deleteNumver()}
R.id.tv_taxi_passenger_number_first -> {selectIndex(0)}
R.id.tv_taxi_passenger_number_second -> {selectIndex(1)}
R.id.tv_taxi_passenger_number_third -> {selectIndex(2)}
R.id.tv_taxi_passenger_number_fourth -> {selectIndex(3)}
R.id.tv_taxi_passenger_number_submit -> {clearNumber()}
else -> {}
}
}
private fun checkAndCommit() {
val numberStr = "${numSelect[0]}${numSelect[1]}${numSelect[2]}${numSelect[3]}"
if(!phone.endsWith(numberStr)){
ToastUtils.showLong("请输入正确的手机尾号")
VoiceNotice.showNotice("验证失败!再检查一下吧~", AIAssist.LEVEL2)
return
}
iTaxiPassengerCommonValueCallback?.onCommonCallback(numberStr)
}
private fun selectIndex(i: Int) {
index = i
changeStyle()
}
private fun showNumver(number: Int) {
if (index in 0..3) {
numSelect[index] = number
numSelectTextView[index]!!.text = number.toString()
if(index!=3){
index++
}
changeStyle()
numSelect.forEach {
if(it==null){
return
}
}
checkAndCommit()
}
}
private fun clearNumber(){
for(i in numSelect.indices){
numSelect[i] = null
}
numSelectTextView.forEach {
it?.text = ""
}
index = 0
changeStyle()
}
private fun deleteNumver() {
if (index in 0..3) {
if(numSelect[index]==null){
if(index!=0){
index--
}
changeStyle()
//return
}
numSelect[index] = null
numSelectTextView[index]!!.text = ""
}
}
private fun changeStyle() {
numSelectTextView.forEachIndexed { indexIn, textView ->
if(indexIn==index){
numSelectTextView[index]!!.setBackgroundResource(R.drawable.bg_taxi_p_checked_input_background)
numSelectTextView[index]!!.setTextColor(
ContextCompat.getColor(
context,
R.color.taxi_p_check_keyboard_input_field_checked
)
)
numSelectTextView[index]!!.setShadowLayer(
0f, 0f, 0f,
ContextCompat.getColor(
context,
R.color.taxi_p_check_keyboard_input_field_checked_text_shadow
)
)
}else{
numSelectTextView[indexIn]!!.setBackgroundResource(R.drawable.bg_taxi_p_check_input_background)
numSelectTextView[indexIn]!!.setTextColor(
ContextCompat.getColor(
context,
R.color.taxi_p_check_keyboard_input_field
)
)
numSelectTextView[indexIn]!!.setShadowLayer(
20f,
0f,
2f,
ContextCompat.getColor(
context,
R.color.taxi_p_check_keyboard_input_field_checked_text_shadow
)
)
}
}
}
fun setData(
startSiteAddr: String?,
endSiteAddr: String?,
passengerNum: String?,
phone: String?
) {
this.phone = phone?:""
val sb = SpannableStringBuilder("乘客数:$passengerNum") // 包装字体内容
sb.setSpan(
TextAppearanceSpan("default",
Typeface.NORMAL,100,
ContextCompat.getColorStateList(context,R.color.taxi_p_check_passenger_number) ,null ),
4, 5, Spannable.SPAN_INCLUSIVE_INCLUSIVE)
tv_passenger_count.text = sb
tv_passenger_start.text = "起 点 : $startSiteAddr"
tv_passenger_end.text = "终 点 : $endSiteAddr"
for(i in numSelect.indices){
numSelect[i] = null
}
numSelectTextView.forEach {
it?.text = ""
}
}
companion object {
const val TAG = "TaxiPassengerCheckView"
}
init {
try {
initView(context)
} catch (e: Exception) {
e.printStackTrace()
}
}
}

View File

@@ -0,0 +1,4 @@
package com.mogo.och.taxi.passenger.ui.debug
class DebugEvent {
}

View File

@@ -0,0 +1,80 @@
package com.mogo.och.taxi.passenger.ui.debug
import android.content.Context
import android.util.AttributeSet
import android.view.LayoutInflater
import androidx.constraintlayout.widget.ConstraintLayout
import androidx.fragment.app.FragmentActivity
import com.mogo.eagle.core.function.api.autopilot.IMoGoAutopilotStatusListener
import com.mogo.eagle.core.utilcode.kotlin.onClick
import com.mogo.eagle.core.utilcode.util.ActivityUtils
import com.mogo.och.taxi.passenger.R
import com.mogo.och.taxi.passenger.ui.TaxiPassengerBaseFragment
import kotlinx.android.synthetic.main.taxi_p_debug.view.tv_show_arrive
import kotlinx.android.synthetic.main.taxi_p_debug.view.tv_show_phone_check
import kotlinx.android.synthetic.main.taxi_p_debug.view.tv_show_start_autopilot
import org.greenrobot.eventbus.EventBus
import org.greenrobot.eventbus.Subscribe
import org.greenrobot.eventbus.ThreadMode
class DebugView @JvmOverloads constructor(
context: Context,
attrs: AttributeSet? = null,
defStyleAttr: Int = 0
) : ConstraintLayout(context, attrs, defStyleAttr), IMoGoAutopilotStatusListener {
companion object {
const val TAG = "DebugView"
}
init {
LayoutInflater.from(context).inflate(R.layout.taxi_p_debug, this, true)
visibility = GONE
}
private var fragment:TaxiPassengerBaseFragment?=null
override fun onAttachedToWindow() {
super.onAttachedToWindow()
EventBus.getDefault().register(this)
val activityByContext = ActivityUtils.getActivityByContext(context)
if(activityByContext is FragmentActivity){
val fragment =
activityByContext.supportFragmentManager.findFragmentByTag(TaxiPassengerBaseFragment.TAG)
if(fragment is TaxiPassengerBaseFragment){
this.fragment = fragment
}
}
tv_show_arrive.onClick {
fragment?.showOrHideArrivedEndLayout(true)
}
tv_show_phone_check.onClick {
fragment?.showOrHidePressengerCheckPager(isShow = true,"13号路口终(鹰眼专用)","13号路口终(鹰眼专用)","2","18811539480")
}
tv_show_start_autopilot.onClick {
fragment?.showOrHideStartAutopilotView(true)
}
}
@Subscribe(threadMode = ThreadMode.MAIN)
fun changeOverview(debugEvent: DebugEvent) {
if(visibility== VISIBLE){
visibility = GONE
}else{
visibility = VISIBLE
}
}
override fun onDetachedFromWindow() {
super.onDetachedFromWindow()
EventBus.getDefault().unregister(this)
}
}

View File

@@ -0,0 +1,148 @@
package com.mogo.och.taxi.passenger.ui.orderinfo
import android.content.Context
import android.graphics.drawable.GradientDrawable
import android.graphics.drawable.LayerDrawable
import android.graphics.drawable.ScaleDrawable
import android.os.Build
import android.util.AttributeSet
import android.view.Gravity
import android.view.LayoutInflater
import androidx.constraintlayout.widget.ConstraintLayout
import androidx.core.content.ContextCompat
import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.findViewTreeViewModelStoreOwner
import com.mogo.och.taxi.passenger.R
import kotlinx.android.synthetic.main.taxi_p_itinerary.view.actv_arrived_time
import kotlinx.android.synthetic.main.taxi_p_itinerary.view.actv_distance
import kotlinx.android.synthetic.main.taxi_p_itinerary.view.actv_distance_unit
import kotlinx.android.synthetic.main.taxi_p_itinerary.view.actv_endstation
import kotlinx.android.synthetic.main.taxi_p_itinerary.view.actv_speed_value
import kotlinx.android.synthetic.main.taxi_p_itinerary.view.actv_surplus_time
import kotlinx.android.synthetic.main.taxi_p_itinerary.view.actv_surplus_time_unit
import kotlinx.android.synthetic.main.taxi_p_itinerary.view.progress_distance
import me.jessyan.autosize.utils.AutoSizeUtils
class ItineraryView : ConstraintLayout, OrderInfoViewModel.ItineraryViewCallback {
private val TAG = "ItineraryView"
constructor(context: Context) : super(context)
constructor(context: Context, attributeSet: AttributeSet) : super(context, attributeSet)
constructor(context: Context, attributeSet: AttributeSet, defStyleAttr: Int) : super(context, attributeSet, defStyleAttr)
constructor(context: Context, attributeSet: AttributeSet, defStyleAttr: Int, defStyleRes: Int) : super(context, attributeSet, defStyleAttr, defStyleRes)
private fun initView() {
LayoutInflater.from(context).inflate(R.layout.taxi_p_itinerary, this, true)
setDrawable(true)
progress_distance.progress = 0
progress_distance.max = 100
}
private fun setDrawable(normal:Boolean) {
val gradientDrawable = GradientDrawable()
gradientDrawable.shape = GradientDrawable.RECTANGLE
val corner = AutoSizeUtils.dp2px(context, 40f).toFloat()
val cornerTop = AutoSizeUtils.dp2px(context, 20f).toFloat()
if(normal) {
gradientDrawable.cornerRadii =
floatArrayOf(0f, 0f, cornerTop, cornerTop, cornerTop, cornerTop, corner, corner)
}else {
gradientDrawable.cornerRadii =
floatArrayOf(0f, 0f, 0f, 0f, corner, corner, corner, corner)
}
val firstColor = ContextCompat.getColor(context, R.color.taxi_p_OF5FFF)
val setondColor = ContextCompat.getColor(context, R.color.taxi_p_44C8FF)
val thirdColor = ContextCompat.getColor(context, R.color.taxi_p_8AE4ED)
val fourceColor = ContextCompat.getColor(context, R.color.taxi_p_C8F3F4)
val bottomColor = ContextCompat.getColor(context, R.color.taxi_p_66476FBE)
gradientDrawable.colors = intArrayOf(firstColor, setondColor, thirdColor, fourceColor)
gradientDrawable.orientation = GradientDrawable.Orientation.LEFT_RIGHT
val temp01 = GradientDrawable()
temp01.cornerRadii = floatArrayOf(0f, 0f, 0f, 0f, corner, corner, corner, corner)
temp01.colors = intArrayOf(bottomColor, bottomColor)
val scaleDrawable3 = ScaleDrawable(gradientDrawable, Gravity.START, 1f, -1f)
val arr = arrayOf(temp01, scaleDrawable3)
val ld = LayerDrawable(arr)
ld.setDrawableByLayerId(android.R.id.background, temp01)
ld.setDrawableByLayerId(android.R.id.progress, scaleDrawable3)
progress_distance.setProgressDrawableTiled(ld)
}
override fun onAttachedToWindow() {
super.onAttachedToWindow()
val viewModel = findViewTreeViewModelStoreOwner()?.let {
ViewModelProvider(it).get(OrderInfoViewModel::class.java)
}
viewModel?.setDistanceCallback(this)
}
override fun setSpeed(speedValue:String){
actv_speed_value.text = speedValue
}
override fun setEndStation(endStation:String){
if(endStation.length>9){
actv_endstation.text = "${endStation.subSequence(0,9)}"
}else {
actv_endstation.text = endStation
}
}
var prePercentage = 0f
val needChangeStyleNumber = 0.99
override fun setDistanceInfo(surplusdistance:String,distanceUnit:String,
surplusTime:String,surplusTimeUnit:String,
arrivedTime:String,alreadyGone:Int,stationDistance:Int
){
actv_distance.text = surplusdistance
actv_distance_unit.text = distanceUnit
actv_surplus_time .text= surplusTime
actv_surplus_time_unit.text = surplusTimeUnit
actv_arrived_time.text= arrivedTime
if(stationDistance>0&&alreadyGone<stationDistance){
if (progress_distance.max != stationDistance) {
progress_distance.max = stationDistance
}
val currentPercentage = alreadyGone.toFloat() / stationDistance
if((prePercentage>needChangeStyleNumber) xor (currentPercentage>needChangeStyleNumber)){
if(currentPercentage>needChangeStyleNumber){
setDrawable(false)
}else{
setDrawable(true)
}
progress_distance.progress = alreadyGone
}else{
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
progress_distance.setProgress(alreadyGone,true)
}else{
progress_distance.progress = alreadyGone
}
}
prePercentage = currentPercentage
}
}
init {
try {
initView()
} catch (e: Exception) {
e.printStackTrace()
}
}
}

View File

@@ -0,0 +1,106 @@
package com.mogo.och.taxi.passenger.ui.orderinfo
import androidx.lifecycle.ViewModel
import com.mogo.commons.AbsMogoApplication
import com.mogo.eagle.core.data.map.MogoLocation
import com.mogo.eagle.core.function.api.autopilot.IMoGoChassisLocationGCJ02Listener
import com.mogo.eagle.core.function.call.autopilot.CallerChassisLocationGCJ02ListenerManager
import com.mogo.eagle.core.utilcode.util.UiThreadHandler
import com.mogo.och.common.module.utils.DateTimeUtil
import com.mogo.och.common.module.utils.NumberFormatUtil
import com.mogo.och.taxi.passenger.R
import com.mogo.och.taxi.passenger.bean.TaxiPassengerOrderQueryRespBean
import com.mogo.och.taxi.passenger.callback.IOCHTaxiPassengerOrderStatusCallback
import com.mogo.och.taxi.passenger.constant.TaxiPassengerOrderStatusEnum
import com.mogo.och.taxi.passenger.model.TaxiPassengerModel
import kotlin.math.abs
import kotlin.math.ceil
class OrderInfoViewModel: ViewModel(), IMoGoChassisLocationGCJ02Listener,
IOCHTaxiPassengerOrderStatusCallback {
private val TAG = OrderInfoViewModel::class.java.simpleName
private var viewCallback:ItineraryViewCallback?=null
private var disUnit:String
private var surplusTimeUnit:String
init {
disUnit = AbsMogoApplication.getApp().getString(R.string.taxi_p_distance_unit_km)
surplusTimeUnit = AbsMogoApplication.getApp().getString(R.string.taxi_p_surplustime)
// 设置起点和终点marker和实时车辆位置
CallerChassisLocationGCJ02ListenerManager.addListener(TAG, 4, this)
TaxiPassengerModel.setOrderStatusCallback(TAG,this)
}
fun setDistanceCallback(viewCallback:ItineraryViewCallback){
this.viewCallback = viewCallback
}
override fun onCleared() {
super.onCleared()
this.viewCallback = null
TaxiPassengerModel.setOrderStatusCallback(TAG,null)
}
override fun onChassisLocationGCJ02(mogoLocation: MogoLocation?) {
mogoLocation?.let {
UiThreadHandler.post {
val speedKM = (abs(it.gnssSpeed) * 3.6f).toInt()
viewCallback?.setSpeed(speedKM.toString())
}
}
}
interface ItineraryViewCallback{
fun setDistanceInfo(surplusdistance:String,distanceUnit:String,
surplusTime:String,surplusTimeUnit:String,
arrivedTime:String,alreadyGone:Int,distance:Int
)
fun setEndStation(endStation:String)
fun setSpeed(speedValue:String)
}
override fun onCurrentOrderStatusChanged(order: TaxiPassengerOrderQueryRespBean.Result?) {
order?.endSiteAddr?.let {
UiThreadHandler.post {
viewCallback?.setEndStation(it)
}
}
when (TaxiPassengerModel.curOrderStatus) {
TaxiPassengerOrderStatusEnum.OnTheWayToEnd -> {
}
else ->{
UiThreadHandler.post {
viewCallback?.setDistanceInfo(
"--", disUnit, "--", surplusTimeUnit, "--", 0, 100
)
}
}
}
}
override fun onCurrentOrderDistToEndChanged(meters: Long, timeInSecond: Long,stationDistance:Int) {
var dis: String? = "0"
var disUnit = "KM"
if (meters > 0) {
if (meters / 1000 < 1) {
disUnit = AbsMogoApplication.getApp().getString(R.string.taxi_p_distance_unit_m)
dis = Math.round(meters.toFloat()).toString()
} else {
disUnit = AbsMogoApplication.getApp().getString(R.string.taxi_p_distance_unit_km)
dis = NumberFormatUtil.formatLong(meters.toDouble() / 1000)
}
}
val time = ceil(timeInSecond / 60f).toInt()
val arriveTime = DateTimeUtil.getAfterSecondTime(timeInSecond.toInt())
UiThreadHandler.post {
viewCallback?.setDistanceInfo(
dis!!,disUnit,time.toString(),surplusTimeUnit,arriveTime,stationDistance-meters.toInt(),stationDistance)
}
}
}

View File

@@ -0,0 +1,245 @@
package com.mogo.och.taxi.passenger.ui.startautopilot
import android.annotation.SuppressLint
import android.content.Context
import android.util.AttributeSet
import android.view.LayoutInflater
import androidx.core.content.ContextCompat
import androidx.core.content.res.ResourcesCompat
import androidx.lifecycle.ViewModelProvider
import com.elegant.utils.UiThreadHandler
import com.mogo.eagle.core.utilcode.kotlin.onClick
import com.mogo.eagle.core.utilcode.mogo.logger.CallerLogger
import com.mogo.eagle.core.utilcode.mogo.logger.scene.SceneConstant
import com.mogo.eagle.core.utilcode.util.OverlayViewUtils
import com.mogo.eagle.core.utilcode.util.ToastUtils
import com.mogo.och.taxi.passenger.R
import com.mogo.och.taxi.passenger.widget.WindowRelativeLayout
import com.mogo.och.taxi.passenger.widget.animutils.AnimationsContainer
import kotlinx.android.synthetic.main.taxi_p_start_autopilot_view.view.actv_front_left_door
import kotlinx.android.synthetic.main.taxi_p_start_autopilot_view.view.actv_front_right_door
import kotlinx.android.synthetic.main.taxi_p_start_autopilot_view.view.actv_orderinfo
import kotlinx.android.synthetic.main.taxi_p_start_autopilot_view.view.actv_rear_left_door
import kotlinx.android.synthetic.main.taxi_p_start_autopilot_view.view.actv_rear_right_door
import kotlinx.android.synthetic.main.taxi_p_start_autopilot_view.view.iv_xiaozhi_belt
import kotlinx.android.synthetic.main.taxi_p_start_autopilot_view.view.starting_autopilot_view_close
import kotlinx.android.synthetic.main.taxi_p_start_autopilot_view.view.taxi_p_autopilot_btn_bg
import kotlinx.android.synthetic.main.taxi_p_start_autopilot_view.view.taxi_p_autopilot_starting
import kotlinx.android.synthetic.main.taxi_p_start_autopilot_view.view.taxi_p_start_autopilot
/**
* @author: wangmingjun
* @date: 2022/6/14
*/
class StartAutopilotView : WindowRelativeLayout, StartAutopilotViewModel.StartAutopilotCallback{
constructor(context: Context) : super(context)
constructor(context: Context, attributeSet: AttributeSet) : super(context, attributeSet)
constructor(context: Context, attributeSet: AttributeSet, defStyleAttr: Int) : super(context, attributeSet, defStyleAttr)
constructor(context: Context, attributeSet: AttributeSet, defStyleAttr: Int, defStyleRes: Int) : super(context, attributeSet, defStyleAttr, defStyleRes)
companion object {
private val TAG = StartAutopilotView::class.java.simpleName
private const val TIMER_START_AUTOPILOT_INTERVAL = 20 * 1000L
}
var isStarting = false
private var taxiPStartAutopilot: AnimationsContainer?=null
private var taxiPStartAutopilotCar: AnimationsContainer?=null
private var taxiPXiaozhiBelt: AnimationsContainer?=null
init {
initView()
}
private fun initView() {
LayoutInflater.from(context).inflate(R.layout.taxi_p_start_autopilot_view, this, true)
taxiPStartAutopilotCar = AnimationsContainer(R.array.taxi_p_start_autopilot_car, 20,taxi_p_autopilot_starting)
taxiPStartAutopilotCar?.setOnAnimStopListener(object :AnimationsContainer.OnAnimationStoppedListener{
override fun AnimationStopped() {
CallerLogger.d(SceneConstant.M_TAXI_P + TAG, "动画暂停")
}
})
taxiPStartAutopilot = AnimationsContainer(R.array.taxi_p_start_autopilot, 15,taxi_p_autopilot_btn_bg)
taxiPStartAutopilot?.setOnAnimStopListener(object :AnimationsContainer.OnAnimationStoppedListener{
override fun AnimationStopped() {
CallerLogger.d(SceneConstant.M_TAXI_P + TAG, "动画暂停")
}
})
taxiPXiaozhiBelt = AnimationsContainer(R.array.xiaozhi_belt, 15,iv_xiaozhi_belt)
taxiPXiaozhiBelt?.setOnAnimStopListener(object :AnimationsContainer.OnAnimationStoppedListener{
override fun AnimationStopped() {
CallerLogger.d(SceneConstant.M_TAXI_P + TAG, "动画暂停")
}
})
}
fun startAutopilotBgAnimatorDrawable(isStart: Boolean) {
if (isStart) {
taxiPStartAutopilot?.start()
} else {
taxiPStartAutopilot?.stop()
}
}
@SuppressLint("UseCompatLoadingForDrawables")
override fun handleStartAutopilotBtnStatus(isBoarded: Boolean) {
taxi_p_autopilot_starting?.setImageResource(R.drawable.light_00003)
updateStartAutopilotBtnStatus(isBoarded)
if (isBoarded) { //高亮可点击状态下动画一直进行
startAutopilotBgAnimatorDrawable(true)
} else { // 置灰色可点击状态下动画停止
startAutopilotBgAnimatorDrawable(false)
}
}
override fun setOrderInfo(show: String) {
actv_orderinfo.text = show
}
override fun setDoorStatus(
doorPosition: StartAutopilotViewModel.DoorPosition,
isOpen: Boolean
) {
when (doorPosition) {
StartAutopilotViewModel.DoorPosition.FRONT_LEFT -> {
if(isOpen){
actv_front_left_door.visibility = VISIBLE
}else{
actv_front_left_door.visibility = GONE
}
}
StartAutopilotViewModel.DoorPosition.FRONT_RIGHT -> {
if(isOpen){
actv_front_right_door.visibility = VISIBLE
}else{
actv_front_right_door.visibility = GONE
}
}
StartAutopilotViewModel.DoorPosition.REAR_LEFT -> {
if(isOpen){
actv_rear_left_door.visibility = VISIBLE
}else{
actv_rear_left_door.visibility = GONE
}
}
StartAutopilotViewModel.DoorPosition.REAR_RIGHT -> {
if(isOpen){
actv_rear_right_door.visibility = VISIBLE
}else{
actv_rear_right_door.visibility = GONE
}
}
else ->{
}
}
}
fun closeAllAnimsAndView() {
isStarting = false
clearStartingAnimFrame()
clearBgAnimDrawable()
OverlayViewUtils.dismissOverlayView(this)
}
fun updateStartAutopilotBtnStatus(isBoarded: Boolean) {
taxi_p_start_autopilot?.let {
if (isBoarded) {
it.setTextColor(ContextCompat.getColor(context,R.color.taxi_p_start_autopilot_txt_color))
it.background = null
} else {
it.background = ResourcesCompat.getDrawable(resources,R.drawable.taxi_p_start_autopilot_txt_btn_bg,null)
taxi_p_autopilot_btn_bg!!.background = null
it.setTextColor( ContextCompat.getColor(context,R.color.taxi_p_start_autopilot_txt_un_color))
}
it.tag = isBoarded
it.text = resources.getString(R.string.taxi_p_start_autopilot_txt)
}
}
private fun startingCarBgAnimatorDrawable(isStart: Boolean) {
if (isStart) {
taxi_p_autopilot_starting!!.setImageResource(0)
taxiPStartAutopilotCar?.start()
} else {
taxiPStartAutopilotCar?.stop()
taxi_p_autopilot_starting!!.setImageResource(R.drawable.light_00003)
}
}
private fun startOrStopLoadingAnim(start: Boolean) {
startingCarBgAnimatorDrawable(start)
if (start) {
isStarting = true
taxi_p_start_autopilot?.text = resources.getString(R.string.taxi_p_start_autopilot_loading)
taxi_p_start_autopilot?.setTextColor(ContextCompat.getColor(context,R.color.taxi_p_start_autopilot_txt_color))
startingAutopilotCountDown()
} else {
clearBgAnimDrawable()
isStarting = false
handleStartAutopilotBtnStatus(true)
}
}
private fun clearBgAnimDrawable() {
taxiPStartAutopilot?.stop()
}
fun clearStartingAnimFrame() {
taxiPStartAutopilotCar?.stop()
}
private fun startingAutopilotCountDown() {
UiThreadHandler.postDelayed({
//未启动成功20s后做处理
if (isStarting) { //判断动画是否在进行
ToastUtils.showLong(R.string.taxi_p_start_autopilot_fail_10s_tip)
updateStatusCountDownOver()
}
}, TIMER_START_AUTOPILOT_INTERVAL)
}
private fun updateStatusCountDownOver() {
isStarting = false
startingCarBgAnimatorDrawable(false)
taxi_p_start_autopilot?.text = resources.getString(R.string.taxi_p_start_autopilot_txt)
}
override fun onAttachedToWindow() {
super.onAttachedToWindow()
val viewModel = ViewModelProvider(this).get(StartAutopilotViewModel::class.java)
viewModel.setStartAutopilotCallback(this)
taxi_p_start_autopilot.onClick {
//开启动画和自动驾驶
if (!(taxi_p_start_autopilot!!.tag as Boolean)) {
ToastUtils.showLong(R.string.taxi_p_start_autopilot_un_click_tip)
return@onClick
}
if (!isStarting) {
startOrStopLoadingAnim(true)
viewModel.startAutopilot()
}
}
starting_autopilot_view_close.onClick {
closeAllAnimsAndView()
}
taxiPXiaozhiBelt?.start()
}
override fun onDetachedFromWindow() {
isStarting = false
clearStartingAnimFrame()
clearBgAnimDrawable()
taxiPXiaozhiBelt?.stop()
super.onDetachedFromWindow()
}
}

View File

@@ -0,0 +1,125 @@
package com.mogo.och.taxi.passenger.ui.startautopilot
import androidx.lifecycle.ViewModel
import chassis.Chassis.DoorNumber
import com.mogo.eagle.core.function.api.autopilot.IMoGoChassisDoorStateListener
import com.mogo.eagle.core.function.call.autopilot.CallerChassisDoorStateListenerManager
import com.mogo.eagle.core.utilcode.mogo.logger.CallerLogger
import com.mogo.eagle.core.utilcode.mogo.logger.scene.SceneConstant
import com.mogo.eagle.core.utilcode.util.UiThreadHandler
import com.mogo.och.taxi.passenger.callback.IOCHTaxiPassengerOrderStatusCallback
import com.mogo.och.taxi.passenger.model.AutopilotManager
import com.mogo.och.taxi.passenger.model.TaxiPassengerModel
class StartAutopilotViewModel : ViewModel(), IOCHTaxiPassengerOrderStatusCallback,
IMoGoChassisDoorStateListener {
private val TAG = StartAutopilotViewModel::class.java.simpleName
private var viewCallback: StartAutopilotCallback? = null
init {
TaxiPassengerModel.setOrderStatusCallback(TAG, this)
CallerChassisDoorStateListenerManager.addListener(TAG, this)
}
fun setStartAutopilotCallback(viewCallback: StartAutopilotCallback) {
this.viewCallback = viewCallback
TaxiPassengerModel.startOrStopReadyToAutopilotLoop(true)
setOrderInfo()
setDoorInfo()
}
private fun setDoorInfo() {
val doorList = CallerChassisDoorStateListenerManager.getDoorList()
CallerLogger.d(SceneConstant.M_TAXI_P + TAG, "门太变化初始化:${doorList}")
doorList?.forEach {
exchangeEnum(it.number,it.status==1)
}
}
private fun setOrderInfo() {
val currentOCHOrder = TaxiPassengerModel.currentOCHOrder
currentOCHOrder?.let {
val phone = it.passengerPhone
val show = if (phone.length > 8) {
//截取电话号码前三位
val phoneNumPre = phone.substring(0, 3)
//截取电话号码后四位
val phoneNumFix = phone.substring(7)
"用户:$phoneNumPre****$phoneNumFix 目的地:${it.endSiteAddr}"
} else {
"用户:${phone} 目的地:${it.endSiteAddr}"
}
viewCallback?.setOrderInfo(show)
}
}
override fun onCleared() {
super.onCleared()
this.viewCallback = null
TaxiPassengerModel.setOrderStatusCallback(TAG, null)
CallerChassisDoorStateListenerManager.removeListener(TAG)
}
override fun onDriverHasCheckedPilotCondition(isBoarded: Boolean) {
viewCallback?.handleStartAutopilotBtnStatus(isBoarded)
}
/**
* 开启自动驾驶
*/
fun startAutopilot() {
AutopilotManager.startAutopilot()
}
override fun onAutopilotSingleDoorState(num: DoorNumber, open: Boolean) {
super.onAutopilotSingleDoorState(num, open)
CallerLogger.d(SceneConstant.M_TAXI_P + TAG, "门太变化:${num}--${open}")
exchangeEnum(num,open)
}
private fun exchangeEnum(num: DoorNumber,open: Boolean){
when (num) {
DoorNumber.FRONT_LEFT -> {
runMain(DoorPosition.FRONT_LEFT,open)
}
DoorNumber.FRONT_RIGHT -> {
runMain(DoorPosition.FRONT_RIGHT,open)
}
DoorNumber.REAR_LEFT -> {
runMain(DoorPosition.REAR_LEFT,open)
}
DoorNumber.REAR_RIGHT -> {
runMain(DoorPosition.REAR_RIGHT,open)
}
DoorNumber.MIDDLE -> {
runMain(DoorPosition.MIDDLE,open)
}
else -> {}
}
}
private fun runMain(posttion:DoorPosition,isOpen: Boolean){
UiThreadHandler.post {
viewCallback?.setDoorStatus(posttion,isOpen)
}
}
interface StartAutopilotCallback {
fun handleStartAutopilotBtnStatus(isBoarded: Boolean)
fun setOrderInfo(show: String)
fun setDoorStatus(doorPosition: DoorPosition,isOpen:Boolean)
}
enum class DoorPosition {
FRONT_LEFT, FRONT_RIGHT, REAR_LEFT, REAR_RIGHT, MIDDLE
}
}

View File

@@ -0,0 +1,102 @@
package com.mogo.och.taxi.passenger.ui.statusview
import android.content.Context
import android.os.SystemClock
import android.util.AttributeSet
import android.view.LayoutInflater
import android.view.ViewGroup
import androidx.constraintlayout.widget.ConstraintLayout
import androidx.fragment.app.FragmentActivity
import com.mogo.eagle.core.function.api.autopilot.IMoGoAutopilotStatusListener
import com.mogo.eagle.core.function.call.autopilot.CallerAutoPilotStatusListenerManager
import com.mogo.eagle.core.function.call.devatools.CallerDevaToolsManager
import com.mogo.eagle.core.function.call.hmi.CallerHmiViewControlListenerManager
import com.mogo.eagle.core.function.call.setting.CallerSkinModeListenerManager
import com.mogo.eagle.core.function.hmi.ui.setting.ToggleDebugView
import com.mogo.eagle.core.utilcode.util.ActivityUtils
import com.mogo.och.common.module.manager.debug.DebugViewWatchDogFragment
import com.mogo.och.taxi.passenger.R
import com.mogo.och.taxi.passenger.ui.debug.DebugEvent
import kotlinx.android.synthetic.main.taxi_p_statusview.view.iv_biz_icon
import kotlinx.android.synthetic.main.taxi_p_statusview.view.vShowDebugView
import me.jessyan.autosize.utils.AutoSizeUtils
import org.greenrobot.eventbus.EventBus
import java.lang.ref.WeakReference
class StatusBarView @JvmOverloads constructor(
context: Context,
attrs: AttributeSet? = null,
defStyleAttr: Int = 0
) : ConstraintLayout(context, attrs, defStyleAttr), IMoGoAutopilotStatusListener {
companion object {
const val TAG = "StatusBarView"
private const val COUNTS = 4 // 点击次数
private const val DURATION: Long = 1000 // 规定有效时间
}
private var debugViewWatchDogFragment: WeakReference<DebugViewWatchDogFragment>? = null
private var mHits = LongArray(COUNTS)
private fun continuousClick() {
if (ToggleDebugView.toggleDebugView.isShowIng()) {
ToggleDebugView.toggleDebugView.dismiss()
return
}
//每次点击时,数组向前移动一位
System.arraycopy(mHits, 1, mHits, 0, mHits.size - 1)
//为数组最后一位赋值
mHits[mHits.size - 1] = SystemClock.uptimeMillis()
if (mHits[0] >= (SystemClock.uptimeMillis() - DURATION)) {
mHits = LongArray(COUNTS) //重新初始化数组
showDebugView()
}
}
init {
LayoutInflater.from(context).inflate(R.layout.taxi_p_statusview, this, true)
setBackgroundResource(R.drawable.taxi_p_status_bg)
isClickable = true
isFocusable = true
}
override fun onAttachedToWindow() {
super.onAttachedToWindow()
post {
val params: ViewGroup.LayoutParams = layoutParams
params.height = AutoSizeUtils.dp2px(context,120f)
layoutParams = params
}
CallerAutoPilotStatusListenerManager.addListener(TAG, this)
iv_biz_icon.setOnClickListener { continuousClick() }
vShowDebugView.setOnLongClickListener {
EventBus.getDefault().post(DebugEvent())
false
}
}
private fun showDebugView() {
if (debugViewWatchDogFragment?.get() == null) {
debugViewWatchDogFragment = WeakReference(DebugViewWatchDogFragment.newInstance())
}
val debugViewFragment = debugViewWatchDogFragment?.get()
if (ActivityUtils.getTopActivity() is FragmentActivity) {
val fragmentActivity = ActivityUtils.getTopActivity() as FragmentActivity
DebugViewWatchDogFragment.showDebugView(fragmentActivity.supportFragmentManager,fragmentActivity.supportFragmentManager,debugViewFragment)
}
}
override fun onDetachedFromWindow() {
super.onDetachedFromWindow()
CallerHmiViewControlListenerManager.removeListener(TAG)
CallerSkinModeListenerManager.removeListener(TAG)
CallerDevaToolsManager.hideStatusBar()
}
}

View File

@@ -0,0 +1,66 @@
package com.mogo.och.taxi.passenger.ui.statusview
import android.annotation.SuppressLint
import android.content.Context
import android.util.AttributeSet
import android.view.LayoutInflater
import android.widget.RelativeLayout
import com.mogo.eagle.core.function.api.devatools.mofang.IMoGoMoFangProvider
import com.mogo.eagle.core.function.call.devatools.CallerDevaToolsManager
import com.mogo.och.taxi.passenger.R
import kotlinx.android.synthetic.main.taxi_p_blue_tooth.view.mofangView
/**
* 魔戒蓝牙控件
* 放置于StatusBar右侧位置
* todo arrow
*/
class TaxiPBlueToothView : RelativeLayout, IMoGoMoFangProvider.OnMoFangStatusListener {
companion object{
const val TAG = "TaxiPBlueToothView"
}
constructor(context: Context) : this(context, null)
constructor(context: Context, attrs: AttributeSet?) : this(context, attrs, 0)
constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int) : this(context, attrs, defStyleAttr, 0)
constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int, defStyleRes: Int) : super(context, attrs, defStyleAttr, defStyleRes)
init {
LayoutInflater.from(context).inflate(R.layout.taxi_p_blue_tooth, this, true)
}
override fun onAttachedToWindow() {
super.onAttachedToWindow()
CallerDevaToolsManager.mofang()?.registerMoFangStatusListener(TAG, this)
}
override fun onDetachedFromWindow() {
super.onDetachedFromWindow()
CallerDevaToolsManager.mofang()?.unRegisterMoFangStatusListener(this)
}
override fun onMoFangConnected() {
mofangView.setImageResource(R.drawable.taxi_p_blue_tooth_close)
}
override fun onMoFangDisconnected() {
mofangView.setImageResource(R.drawable.taxi_p_blue_tooth_open)
}
@SuppressLint("SetTextI18n")
override fun onMoFangBatteryChanged(battery: Int) {
}
override fun onMoFangClicked(keyCode: Int) {}
override fun onMoFangLongClicked(keyCode: Int) {}
override fun onMoFangCombineClicked(vararg keyCodes: Int) {}
override fun onMoFangStatusError(msg: String) {}
}

View File

@@ -0,0 +1,95 @@
package com.mogo.och.taxi.passenger.ui.video
import android.annotation.SuppressLint
import android.app.Activity
import android.view.View
import android.view.WindowManager
import com.mogo.och.taxi.passenger.widget.ConsultVideoPlayer
import com.shuyu.gsyvideoplayer.GSYVideoManager
import java.lang.Exception
/**
* 视频全屏播放
*
* @author yangyakun
*/
@SuppressLint("StaticFieldLeak")
object FullVideoUtils {
private const val TAG = "FullVideoUtils"
private var windowManager: WindowManager? = null
@Volatile
private var isShowing = false
/**
* 记录上一次的View
*/
private var lastOverlayView: View? = null
/**
* 添加覆盖View在Activity上面
*/
@JvmOverloads
fun showOverlayView(context: Activity, overlayView: View, ani: Int = -1) {
if (windowManager == null) {
windowManager = context.windowManager
}
// 设置View显示模式沉浸式的侵入到状态栏导航栏
overlayView.systemUiVisibility = (View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
or View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY
or View.SYSTEM_UI_FLAG_HIDE_NAVIGATION
or View.SYSTEM_UI_FLAG_LAYOUT_STABLE
or View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION)
val params = WindowManager.LayoutParams()
params.width = WindowManager.LayoutParams.MATCH_PARENT
params.height = WindowManager.LayoutParams.MATCH_PARENT
params.alpha = 1.0f
// 设置窗口类型为应用子窗口和PopupWindow同类型
params.type = WindowManager.LayoutParams.TYPE_APPLICATION_PANEL
// 没有边界限制,允许窗口扩展到屏幕外
params.flags = WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS
if (ani != -1) {
params.windowAnimations = ani
}
try {
// 后门逻辑,长时间触摸消失
lastOverlayView = overlayView
windowManager!!.addView(overlayView, params)
isShowing = true
} catch (e: Exception) {
e.printStackTrace()
}
}
/**
* 移除覆盖View在Activity上面
*/
fun dismissOverlayView(needReleas:Boolean) {
if (!isShowing) {
return
}
val consultVideoPlayer =
lastOverlayView?.findViewById<ConsultVideoPlayer>(GSYVideoManager.FULLSCREEN_ID)
consultVideoPlayer?.let {
if(needReleas){
it.onVideoReset()
it.setVideoAllCallBack(null)
it.smalllPlayer?.clearFullscreenLayout(it)
}
consultVideoPlayer.removeAllViews()
}
try {
if (windowManager != null) {
windowManager!!.removeViewImmediate(lastOverlayView)
windowManager = null
}
if (lastOverlayView != null) {
lastOverlayView = null
}
isShowing = false
} catch (e: Exception) {
e.printStackTrace()
}
}
}

View File

@@ -0,0 +1,232 @@
package com.mogo.och.taxi.passenger.ui.video
import android.content.Context
import android.graphics.Color
import android.util.AttributeSet
import android.view.LayoutInflater
import android.view.View
import android.widget.FrameLayout
import androidx.constraintlayout.widget.ConstraintLayout
import androidx.recyclerview.widget.RecyclerView
import com.mogo.och.taxi.passenger.R
import com.mogo.och.taxi.passenger.bean.TaxiPassengerVideoPlay
import com.mogo.och.taxi.passenger.ui.video.layoutmanage.CarouselLayoutManager
import com.mogo.och.taxi.passenger.ui.video.layoutmanage.CarouselZoomPostLayoutListener
import com.mogo.och.taxi.passenger.ui.video.layoutmanage.CenterScrollListener
import com.mogo.och.taxi.passenger.widget.ConsultVideoPlayer
import com.mogo.och.taxi.passenger.widget.indicator.IndicatorView
import com.mogo.och.taxi.passenger.widget.indicator.enums.IndicatorOrientation
import com.mogo.och.taxi.passenger.widget.indicator.enums.IndicatorSlideMode
import com.mogo.och.taxi.passenger.widget.indicator.enums.IndicatorStyle
import com.shuyu.gsyvideoplayer.video.base.GSYVideoView
import kotlin.math.floor
/**
* @author ChenFufeng
* 蘑菇资讯视频
*/
internal class InfoVideoView @JvmOverloads constructor(
context: Context,
attrs: AttributeSet? = null,
defStyleAttr: Int = 0
) : FrameLayout(
context,
attrs,
defStyleAttr
) {
companion object{
private const val TAG = "VideoView"
}
init {
LayoutInflater.from(context).inflate(R.layout.taxi_p_mogo_video_layout, this, true)
initView()
}
private var rvVideoPlaylist: RecyclerView? = null
private lateinit var indicatorView: IndicatorView
private lateinit var clContain: ConstraintLayout
private val arrayListOf by lazy {
arrayListOf<TaxiPassengerVideoPlay>().apply {
add(TaxiPassengerVideoPlay(
"https://img.zhidaohulian.com/fileServer/online_car_hailing/1655708596763/全车型混剪增加红旗车队.m4v",
"https://img.zhidaohulian.com/fileServer/online_car_hailing/1655969511280/车队.png",
"蘑菇车联覆盖生活的方方面面"
))
add(TaxiPassengerVideoPlay(
"https://img.zhidaohulian.com/fileServer/online_car_hailing/1655708554279/红旗车队.m4v",
"https://img.zhidaohulian.com/fileServer/online_car_hailing/1655969553174/红旗重新排版.png",
"蘑菇车联之红旗车队"
))
add(
TaxiPassengerVideoPlay(
"https://img.zhidaohulian.com/fileServer/online_car_hailing/1655708499497/大运会合作解说版.m4v",
"https://img.zhidaohulian.com/fileServer/online_car_hailing/1655969536177/大运会.png",
"蘑菇车联牵手成都大运会"
)
)
add(
TaxiPassengerVideoPlay(
"https://img.zhidaohulian.com/fileServer/online_car_hailing/1655708409810/20210610重新排版3屏.m4v",
"https://img.zhidaohulian.com/fileServer/online_car_hailing/1655969579713/三屏.png",
"多视角体验蘑菇车联自动驾驶"
)
)
}
}
fun exitFullScreenMode(resetVideoPlayer: Boolean) {
val carouselLayoutManager = rvVideoPlaylist?.layoutManager as CarouselLayoutManager
val (_: Int, player) = getPlayer(carouselLayoutManager)
player?.let {
it.exitFullScreenMode()
it.onVideoPause()
if(resetVideoPlayer) {
it.onVideoReset()
}
}
}
override fun onAttachedToWindow() {
super.onAttachedToWindow()
configPage()
}
override fun onVisibilityChanged(changedView: View, visibility: Int) {
super.onVisibilityChanged(changedView, visibility)
if(changedView!=this){
return
}
val carouselLayoutManager = rvVideoPlaylist?.layoutManager as CarouselLayoutManager
val (_: Int, player) = getPlayer(carouselLayoutManager)
when (visibility) {
View.VISIBLE -> {
player?.let {
if (!player.isIfCurrentIsFullscreen) {
when (player.currentState) {
GSYVideoView.CURRENT_STATE_PAUSE -> {
//player.onVideoResume(false)
}
else -> {}
}
}
}
}
else -> {
player?.let {
if (!player.isIfCurrentIsFullscreen) {
player.onVideoPause()
}
}
}
}
}
private fun initView() {
rvVideoPlaylist = findViewById(R.id.infoVideoPlaylist)
indicatorView = findViewById(R.id.infoIndicatorView)
clContain = findViewById(R.id.infoContainer)
}
private fun configPage() {
// FullVideoUtils.dismissOverlayView(true)
initData()
}
private fun initData() {
val carouselLayoutManager = CarouselLayoutManager(CarouselLayoutManager.HORIZONTAL, true)
carouselLayoutManager.setPostLayoutListener(CarouselZoomPostLayoutListener())
carouselLayoutManager.maxVisibleItems = 1
indicatorView.notifyDataChanged(arrayListOf.size)
indicatorView.setSlideMode(IndicatorSlideMode.SCALE)
indicatorView.setOrientation(IndicatorOrientation.INDICATOR_HORIZONTAL)
indicatorView.setIndicatorStyle(IndicatorStyle.ROUND_RECT)
indicatorView.setSliderColor(
Color.parseColor("#80000000"), Color.parseColor("#3FACFD"),
Color.parseColor("#3FACFD")
)
indicatorView.setSliderWidth(9f, 54f)
indicatorView.setSliderHeight(9f)
indicatorView.setSliderGap(36f)
rvVideoPlaylist?.addOnScrollListener(object : CenterScrollListener() {
var prePlayerPosition = 0
override fun pageSelect(recyclerView: RecyclerView?, newState: Int) {
//播放视频
val (centerItemPosition: Int, player) = getPlayer(carouselLayoutManager)
indicatorView.onPageSelected(centerItemPosition)
if (player is ConsultVideoPlayer) {
if (prePlayerPosition != centerItemPosition) {
if (player.currentState == GSYVideoView.CURRENT_STATE_PAUSE) {
player.onVideoReset()
}
val playerHolder =
carouselLayoutManager.findViewByPosition(prePlayerPosition)
val prePlayer =
playerHolder?.findViewById<ConsultVideoPlayer>(R.id.video_item_player)
prePlayer?.onVideoReset()
} else {
player.onVideoResume(false)
}
}
prePlayerPosition = centerItemPosition
}
override fun pageStop() {
val (_: Int, player) = getPlayer(carouselLayoutManager)
if (player is ConsultVideoPlayer) {
player.onVideoPause()
}
}
})
carouselLayoutManager.addOnDargAutoDiffListener { adapterPosition, currentPosition ->
val fl = adapterPosition - floor(adapterPosition)
var currentIndex = currentPosition
if (fl > 0.5) {
if (currentPosition == 0) {
currentIndex = rvVideoPlaylist?.adapter!!.itemCount - 1
} else {
currentIndex -= 1
}
}
indicatorView.onPageScrolled(currentIndex, fl, 0)
}
val recyclerVideoAdapter = RecyclerVideoAdapter(context, arrayListOf, rvVideoPlaylist)
recyclerVideoAdapter.setOnThumbImageClilckListener {
val (_: Int, player) = getPlayer(carouselLayoutManager)
if (player is ConsultVideoPlayer) {
player.onVideoReset()
player.thumbImageViewLayout.visibility = View.VISIBLE
}
rvVideoPlaylist?.smoothScrollToPosition(it)
}
rvVideoPlaylist?.layoutManager = carouselLayoutManager
rvVideoPlaylist?.setHasFixedSize(true)
rvVideoPlaylist?.adapter = recyclerVideoAdapter
}
private fun getPlayer(carouselLayoutManager: CarouselLayoutManager): Pair<Int, ConsultVideoPlayer?> {
val centerItemPosition: Int = carouselLayoutManager.centerItemPosition
val playerHolder = carouselLayoutManager.findViewByPosition(centerItemPosition)
val player = playerHolder?.findViewById<ConsultVideoPlayer>(R.id.video_item_player)
return Pair(centerItemPosition, player)
}
override fun onWindowFocusChanged(hasWindowFocus: Boolean) {
super.onWindowFocusChanged(hasWindowFocus)
val carouselLayoutManager = rvVideoPlaylist?.layoutManager as CarouselLayoutManager
val (_: Int, player) = getPlayer(carouselLayoutManager)
player?.let {
if(it.isInPlayingState&&!it.isIfCurrentIsFullscreen&&!hasWindowFocus){
player.onVideoPause()
}
}
}
override fun onDetachedFromWindow() {
super.onDetachedFromWindow()
}
}

View File

@@ -0,0 +1,29 @@
package com.mogo.och.taxi.passenger.ui.video;
import android.content.Context;
import android.view.View;
import androidx.recyclerview.widget.RecyclerView;
import com.mogo.och.taxi.passenger.R;
import com.mogo.och.taxi.passenger.widget.ConsultVideoPlayer;
import com.shuyu.gsyvideoplayer.builder.GSYVideoOptionBuilder;
public class RecyclerItemVideoHolder extends RecyclerView.ViewHolder {
public final static String TAG = "RecyclerView2List";
protected Context context;
public ConsultVideoPlayer gsyVideoPlayer;
GSYVideoOptionBuilder gsyVideoOptionBuilder;
public RecyclerItemVideoHolder(Context context, View v) {
super(v);
this.context = context;
gsyVideoPlayer = v.findViewById(R.id.video_item_player);
gsyVideoOptionBuilder = new GSYVideoOptionBuilder();
}
}

View File

@@ -0,0 +1,118 @@
package com.mogo.och.taxi.passenger.ui.video;
import android.content.Context;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import androidx.annotation.NonNull;
import androidx.recyclerview.widget.RecyclerView;
import com.bumptech.glide.Glide;
import com.bumptech.glide.request.RequestOptions;
import com.mogo.eagle.core.utilcode.util.ToastUtils;
import com.mogo.och.taxi.passenger.R;
import com.mogo.och.taxi.passenger.bean.TaxiPassengerVideoPlay;
import com.shuyu.gsyvideoplayer.listener.GSYSampleCallBack;
import java.util.List;
import me.jessyan.autosize.AutoSizeCompat;
public class RecyclerVideoAdapter extends RecyclerView.Adapter<RecyclerItemVideoHolder> {
private final static String TAG = "RecyclerVideoAdapter";
private List<TaxiPassengerVideoPlay> itemDataList ;
private final Context context;
private OnThumbImageClilckListener onThumbImageClilckListener;
private final RecyclerView recyclerView;
public void setOnThumbImageClilckListener(OnThumbImageClilckListener onThumbImageClilckListener) {
this.onThumbImageClilckListener = onThumbImageClilckListener;
}
public RecyclerVideoAdapter(Context context, List<TaxiPassengerVideoPlay> itemDataList,RecyclerView recyclerView) {
this.itemDataList = itemDataList;
this.context = context;
this.recyclerView = recyclerView;
}
@NonNull
@Override
public RecyclerItemVideoHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
View v = LayoutInflater.from(context).inflate(R.layout.list_video_item_light, parent, false);
RecyclerItemVideoHolder recyclerItemVideoHolder = new RecyclerItemVideoHolder(context, v);
recyclerItemVideoHolder.setIsRecyclable(false);
return recyclerItemVideoHolder;
}
@Override
public void onBindViewHolder(@NonNull final RecyclerItemVideoHolder holder, int position) {
final TaxiPassengerVideoPlay taxiPassengerVideoPlay = itemDataList.get(position);
AutoSizeCompat.autoConvertDensityOfGlobal(holder.itemView.getResources());
holder.gsyVideoOptionBuilder
.setEnlargeImageRes(R.drawable.taxi_p_change_full)
.setUrl(taxiPassengerVideoPlay.getUrl())
.setCacheWithPlay(true)
.setPlayTag(taxiPassengerVideoPlay.getImageUrl()+position)
.setThumbPlay(false)
.build(holder.gsyVideoPlayer);
holder.gsyVideoPlayer.getTitleTextView().setText(taxiPassengerVideoPlay.getTitle());
Glide.with(context)
.load(taxiPassengerVideoPlay.getImageUrl())
.apply(new RequestOptions().placeholder(R.drawable.taxi_p_video_holder).centerCrop())
.into(holder.gsyVideoPlayer.coverImage);
holder.gsyVideoPlayer.getThumbImageViewLayout().setOnClickListener(v -> {
if(onThumbImageClilckListener!=null){
onThumbImageClilckListener.onDxChanged(holder.getAbsoluteAdapterPosition());
}
});
holder.gsyVideoPlayer.setVideoAllCallBack(new GSYSampleCallBack(){
@Override
public void onAutoComplete(String url, Object... objects) {
holder.gsyVideoPlayer.onVideoReset();
if(holder.getAbsoluteAdapterPosition()==getItemCount()-1){
recyclerView.smoothScrollToPosition(0);
}else {
recyclerView.smoothScrollToPosition(holder.getAbsoluteAdapterPosition()+1);
}
}
@Override
public void onClickBlank(String url, Object... objects) {
super.onClickBlank(url, objects);
recyclerView.smoothScrollToPosition(holder.getAbsoluteAdapterPosition());
}
@Override
public void onPlayError(String url, Object... objects) {
ToastUtils.showLong("哎呀,出错了,看看其他视频吧");
}
@Override
public void onClickStartError(String url, Object... objects) {
ToastUtils.showLong("哎呀,出错了,看看其他视频吧");
}
});
}
@Override
public int getItemCount() {
return itemDataList.size();
}
@Override
public int getItemViewType(int position) {
return 1;
}
public interface OnThumbImageClilckListener {
void onDxChanged(int targetPosition);
}
}

View File

@@ -0,0 +1,970 @@
package com.mogo.och.taxi.passenger.ui.video.layoutmanage;
import android.graphics.PointF;
import android.os.Build;
import android.os.Handler;
import android.os.Looper;
import android.os.Parcel;
import android.os.Parcelable;
import android.view.View;
import android.view.ViewGroup;
import androidx.annotation.CallSuper;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.core.view.ViewCompat;
import androidx.recyclerview.widget.LinearSmoothScroller;
import androidx.recyclerview.widget.OrientationHelper;
import androidx.recyclerview.widget.RecyclerView;
import java.lang.ref.WeakReference;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
/**
* An implementation of {@link RecyclerView.LayoutManager} that layout items like carousel.
* Generally there is one center item and bellow this item there are maximum {@link CarouselLayoutManager#getMaxVisibleItems()} items on each side of the center
* item. By default {@link CarouselLayoutManager#getMaxVisibleItems()} is {@link CarouselLayoutManager#MAX_VISIBLE_ITEMS}.<br />
* <br />
* This LayoutManager supports only fixedSized adapter items.<br />
* <br />
* This LayoutManager supports {@link CarouselLayoutManager#HORIZONTAL} and {@link CarouselLayoutManager#VERTICAL} orientations. <br />
* <br />
* This LayoutManager supports circle layout. By default it if disabled. We don't recommend to use circle layout with adapter items count less then 3. <br />
* <br />
* Please be sure that layout_width of adapter item is a constant value and not {@link ViewGroup.LayoutParams#MATCH_PARENT}
* for {@link #HORIZONTAL} orientation.
* So like layout_height is not {@link ViewGroup.LayoutParams#MATCH_PARENT} for {@link CarouselLayoutManager#VERTICAL}<br />
* <br />
*/
public class CarouselLayoutManager extends RecyclerView.LayoutManager implements RecyclerView.SmoothScroller.ScrollVectorProvider {
public static final int HORIZONTAL = OrientationHelper.HORIZONTAL;
public static final int VERTICAL = OrientationHelper.VERTICAL;
/**
* 固定值一直不变
*/
public static final int INVALID_POSITION = -1;
public static final int MAX_VISIBLE_ITEMS = 3;
private static final boolean CIRCLE_LAYOUT = false;
private boolean mDecoratedChildSizeInvalid;
private Integer mDecoratedChildWidth;
private Integer mDecoratedChildHeight;
private final int mOrientation;
private boolean mCircleLayout;
private int mPendingScrollPosition;
private final LayoutHelper mLayoutHelper = new LayoutHelper(MAX_VISIBLE_ITEMS);
private PostLayoutListener mViewPostLayout;
private final List<OnCenterItemSelectionListener> mOnCenterItemSelectionListeners = new ArrayList<>();
private final List<OnDargAutoDiffListener> onDargAutoDiffListeners = new ArrayList<>();
private int mCenterItemPosition = INVALID_POSITION;
private int mItemsCount;
@Nullable
private CarouselSavedState mPendingCarouselSavedState;
/**
* @param orientation should be {@link #VERTICAL} or {@link #HORIZONTAL}
*/
@SuppressWarnings("unused")
public CarouselLayoutManager(final int orientation) {
this(orientation, CIRCLE_LAYOUT);
}
/**
* If circleLayout is true then all items will be in cycle. Scroll will be infinite on both sides.
*
* @param orientation should be {@link #VERTICAL} or {@link #HORIZONTAL}
* @param circleLayout true for enabling circleLayout
*/
@SuppressWarnings("unused")
public CarouselLayoutManager(final int orientation, final boolean circleLayout) {
if (HORIZONTAL != orientation && VERTICAL != orientation) {
throw new IllegalArgumentException("orientation should be HORIZONTAL or VERTICAL");
}
mOrientation = orientation;
mCircleLayout = circleLayout;
mPendingScrollPosition = INVALID_POSITION;
}
/**
* Change circle layout type
*/
@SuppressWarnings("unused")
public void setCircleLayout(final boolean circleLayout) {
if (mCircleLayout != circleLayout) {
mCircleLayout = circleLayout;
requestLayout();
}
}
/**
* Setup {@link PostLayoutListener} for this LayoutManager.
* Its methods will be called for each visible view item after general LayoutManager layout finishes. <br />
* <br />
* Generally this method should be used for scaling and translating view item for better (different) view presentation of layouting.
*
* @param postLayoutListener listener for item layout changes. Can be null.
*/
@SuppressWarnings("unused")
public void setPostLayoutListener(@Nullable final PostLayoutListener postLayoutListener) {
mViewPostLayout = postLayoutListener;
requestLayout();
}
/**
* Setup maximum visible (layout) items on each side of the center item.
* Basically during scrolling there can be more visible items (+1 item on each side), but in idle state this is the only reached maximum.
*
* @param maxVisibleItems should be great then 0, if bot an {@link IllegalAccessException} will be thrown
*/
@CallSuper
@SuppressWarnings("unused")
public void setMaxVisibleItems(final int maxVisibleItems) {
if (0 > maxVisibleItems) {
throw new IllegalArgumentException("maxVisibleItems can't be less then 0");
}
mLayoutHelper.mMaxVisibleItems = maxVisibleItems;
requestLayout();
}
/**
* @return current setup for maximum visible items.
* @see #setMaxVisibleItems(int)
*/
@SuppressWarnings("unused")
public int getMaxVisibleItems() {
return mLayoutHelper.mMaxVisibleItems;
}
@Override
public RecyclerView.LayoutParams generateDefaultLayoutParams() {
return new RecyclerView.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);
}
/**
* @return current layout orientation
* @see #VERTICAL
* @see #HORIZONTAL
*/
public int getOrientation() {
return mOrientation;
}
@Override
public boolean canScrollHorizontally() {
return 0 != getChildCount() && HORIZONTAL == mOrientation;
}
@Override
public boolean canScrollVertically() {
return 0 != getChildCount() && VERTICAL == mOrientation;
}
/**
* @return current layout center item
*/
public int getCenterItemPosition() {
return mCenterItemPosition;
}
/**
* @param onCenterItemSelectionListener listener that will trigger when ItemSelectionChanges. can't be null
*/
public void addOnItemSelectionListener(@NonNull final OnCenterItemSelectionListener onCenterItemSelectionListener) {
mOnCenterItemSelectionListeners.add(onCenterItemSelectionListener);
}
/**
* @param onCenterItemSelectionListener listener that was previously added by {@link #addOnItemSelectionListener(OnCenterItemSelectionListener)}
*/
public void removeOnItemSelectionListener(@NonNull final OnCenterItemSelectionListener onCenterItemSelectionListener) {
mOnCenterItemSelectionListeners.remove(onCenterItemSelectionListener);
}
public void addOnDargAutoDiffListener(@NonNull final OnDargAutoDiffListener onDargAutoDiffListener) {
onDargAutoDiffListeners.add(onDargAutoDiffListener);
}
public void removeOnDargAutoDiffListener(@NonNull final OnDargAutoDiffListener onDargAutoDiffListener) {
onDargAutoDiffListeners.remove(onDargAutoDiffListener);
}
@SuppressWarnings("RefusedBequest")
@Override
public void scrollToPosition(final int position) {
if (0 > position) {
throw new IllegalArgumentException("position can't be less then 0. position is : " + position);
}
mPendingScrollPosition = position;
requestLayout();
}
@SuppressWarnings("RefusedBequest")
@Override
public void smoothScrollToPosition(@NonNull final RecyclerView recyclerView, @NonNull final RecyclerView.State state, final int position) {
final LinearSmoothScroller linearSmoothScroller = new LinearSmoothScroller(recyclerView.getContext()) {
@Override
public int calculateDyToMakeVisible(final View view, final int snapPreference) {
if (!canScrollVertically()) {
return 0;
}
return getOffsetForCurrentView(view);
}
@Override
public int calculateDxToMakeVisible(final View view, final int snapPreference) {
if (!canScrollHorizontally()) {
return 0;
}
return getOffsetForCurrentView(view);
}
};
linearSmoothScroller.setTargetPosition(position);
startSmoothScroll(linearSmoothScroller);
}
@Override
@Nullable
public PointF computeScrollVectorForPosition(final int targetPosition) {
if (0 == getChildCount()) {
return null;
}
final float directionDistance = getScrollDirection(targetPosition);
//noinspection NumericCastThatLosesPrecision
final int direction = (int) -Math.signum(directionDistance);
if (HORIZONTAL == mOrientation) {
return new PointF(direction, 0);
} else {
return new PointF(0, direction);
}
}
private float getScrollDirection(final int targetPosition) {
final float currentScrollPosition = makeScrollPositionInRange0ToCount(getCurrentScrollPosition(), mItemsCount);
if (mCircleLayout) {
final float t1 = currentScrollPosition - targetPosition;
final float t2 = Math.abs(t1) - mItemsCount;
if (Math.abs(t1) > Math.abs(t2)) {
return Math.signum(t1) * t2;
} else {
return t1;
}
} else {
return currentScrollPosition - targetPosition;
}
}
@Override
public int scrollVerticallyBy(final int dy, @NonNull final RecyclerView.Recycler recycler, @NonNull final RecyclerView.State state) {
if (HORIZONTAL == mOrientation) {
return 0;
}
return scrollBy(dy, recycler, state);
}
@Override
public int scrollHorizontallyBy(final int dx, final RecyclerView.Recycler recycler, final RecyclerView.State state) {
if (VERTICAL == mOrientation) {
return 0;
}
return scrollBy(dx, recycler, state);
}
/**
* This method is called from {@link #scrollHorizontallyBy(int, RecyclerView.Recycler, RecyclerView.State)} and
* {@link #scrollVerticallyBy(int, RecyclerView.Recycler, RecyclerView.State)} to calculate needed scroll that is allowed. <br />
* <br />
* This method may do relayout work.
*
* @param diff 要滚动的距离
* @param recycler 回收期
* @param state Transient state of RecyclerView
* @return distance that we actually scrolled by
*/
@CallSuper
protected int scrollBy(final int diff, @NonNull final RecyclerView.Recycler recycler, @NonNull final RecyclerView.State state) {
if (null == mDecoratedChildWidth || null == mDecoratedChildHeight) {
return 0;
}
if (0 == getChildCount() || 0 == diff) {
return 0;
}
final int resultScroll;
if (mCircleLayout) {
resultScroll = diff;
mLayoutHelper.mScrollOffset += resultScroll;
final int maxOffset = getScrollItemSize() * mItemsCount;
while (0 > mLayoutHelper.mScrollOffset) {
mLayoutHelper.mScrollOffset += maxOffset;
}
while (mLayoutHelper.mScrollOffset > maxOffset) {
mLayoutHelper.mScrollOffset -= maxOffset;
}
mLayoutHelper.mScrollOffset -= resultScroll;
} else {
final int maxOffset = getMaxScrollOffset();
if (0 > mLayoutHelper.mScrollOffset + diff) {
resultScroll = -mLayoutHelper.mScrollOffset; //to make it 0
} else if (mLayoutHelper.mScrollOffset + diff > maxOffset) {
resultScroll = maxOffset - mLayoutHelper.mScrollOffset; //to make it maxOffset
} else {
resultScroll = diff;
}
}
if (0 != resultScroll) {
mLayoutHelper.mScrollOffset += resultScroll;
fillData(recycler, state);
}
return resultScroll;
}
@Override
public void onMeasure(@NonNull final RecyclerView.Recycler recycler, @NonNull final RecyclerView.State state, final int widthSpec, final int heightSpec) {
mDecoratedChildSizeInvalid = true;
super.onMeasure(recycler, state, widthSpec, heightSpec);
}
@SuppressWarnings("rawtypes")
@Override
public void onAdapterChanged(final RecyclerView.Adapter oldAdapter, final RecyclerView.Adapter newAdapter) {
super.onAdapterChanged(oldAdapter, newAdapter);
removeAllViews();
}
@SuppressWarnings("RefusedBequest")
@Override
@CallSuper
public void onLayoutChildren(@NonNull final RecyclerView.Recycler recycler, @NonNull final RecyclerView.State state) {
if (0 == state.getItemCount()) {
removeAndRecycleAllViews(recycler);
selectItemCenterPosition(INVALID_POSITION);
return;
}
detachAndScrapAttachedViews(recycler);
if (null == mDecoratedChildWidth || mDecoratedChildSizeInvalid) {
final List<RecyclerView.ViewHolder> scrapList = recycler.getScrapList();
final boolean shouldRecycle;
final View view;
if (scrapList.isEmpty()) {
shouldRecycle = true;
final int itemsCount = state.getItemCount();
view = recycler.getViewForPosition(
mPendingScrollPosition == INVALID_POSITION ?
0 :
Math.max(0, Math.min(itemsCount - 1, mPendingScrollPosition))
);
addView(view);
} else {
shouldRecycle = false;
view = scrapList.get(0).itemView;
}
measureChildWithMargins(view, 0, 0);
final int decoratedChildWidth = getDecoratedMeasuredWidth(view);
final int decoratedChildHeight = getDecoratedMeasuredHeight(view);
if (shouldRecycle) {
detachAndScrapView(view, recycler);
}
if (null != mDecoratedChildWidth && (mDecoratedChildWidth != decoratedChildWidth || mDecoratedChildHeight != decoratedChildHeight)) {
if (INVALID_POSITION == mPendingScrollPosition && null == mPendingCarouselSavedState) {
mPendingScrollPosition = mCenterItemPosition;
}
}
mDecoratedChildWidth = decoratedChildWidth;
mDecoratedChildHeight = decoratedChildHeight;
mDecoratedChildSizeInvalid = false;
}
if (INVALID_POSITION != mPendingScrollPosition) {
final int itemsCount = state.getItemCount();
mPendingScrollPosition = 0 == itemsCount ? INVALID_POSITION : Math.max(0, Math.min(itemsCount - 1, mPendingScrollPosition));
}
if (INVALID_POSITION != mPendingScrollPosition) {
mLayoutHelper.mScrollOffset = calculateScrollForSelectingPosition(mPendingScrollPosition, state);
mPendingScrollPosition = INVALID_POSITION;
mPendingCarouselSavedState = null;
} else if (null != mPendingCarouselSavedState) {
mLayoutHelper.mScrollOffset = calculateScrollForSelectingPosition(mPendingCarouselSavedState.mCenterItemPosition, state);
mPendingCarouselSavedState = null;
} else if (state.didStructureChange() && INVALID_POSITION != mCenterItemPosition) {
mLayoutHelper.mScrollOffset = calculateScrollForSelectingPosition(mCenterItemPosition, state);
}
fillData(recycler, state);
}
private int calculateScrollForSelectingPosition(final int itemPosition, final RecyclerView.State state) {
if (itemPosition == INVALID_POSITION) {
return 0;
}
final int fixedItemPosition = itemPosition < state.getItemCount() ? itemPosition : state.getItemCount() - 1;
return fixedItemPosition * (VERTICAL == mOrientation ? mDecoratedChildHeight : mDecoratedChildWidth);
}
private void fillData(@NonNull final RecyclerView.Recycler recycler, @NonNull final RecyclerView.State state) {
final float currentScrollPosition = getCurrentScrollPosition();
generateLayoutOrder(currentScrollPosition, state);
detachAndScrapAttachedViews(recycler);
recyclerOldViews(recycler);
final int width = getWidthNoPadding();
final int height = getHeightNoPadding();
if (VERTICAL == mOrientation) {
fillDataVertical(recycler, width, height);
} else {
fillDataHorizontal(recycler, width, height);
}
recycler.clear();
detectOnItemSelectionChanged(currentScrollPosition, state);
}
private void detectOnItemSelectionChanged(final float currentScrollPosition, final RecyclerView.State state) {
final float absCurrentScrollPosition = makeScrollPositionInRange0ToCount(currentScrollPosition, state.getItemCount());
final int centerItem = Math.round(absCurrentScrollPosition);
if(currentScrollPosition-centerItem!=0){
new Handler(Looper.getMainLooper()).post(() -> dragDxDiff(currentScrollPosition,mCenterItemPosition));
}
if (mCenterItemPosition != centerItem) {
mCenterItemPosition = centerItem;
new Handler(Looper.getMainLooper()).post(new Runnable() {
@Override
public void run() {
selectItemCenterPosition(centerItem);
}
});
}
}
private void selectItemCenterPosition(final int centerItem) {
for (final OnCenterItemSelectionListener onCenterItemSelectionListener : mOnCenterItemSelectionListeners) {
onCenterItemSelectionListener.onCenterItemChanged(centerItem);
}
}
private void dragDxDiff(final float centerItem,final int currentPosition) {
for (final OnDargAutoDiffListener onDargAutoDiffListener : onDargAutoDiffListeners) {
onDargAutoDiffListener.onDxChanged(centerItem,currentPosition);
}
}
private void fillDataVertical(final RecyclerView.Recycler recycler, final int width, final int height) {
final int start = (width - mDecoratedChildWidth) / 2;
final int end = start + mDecoratedChildWidth;
final int centerViewTop = (height - mDecoratedChildHeight) / 2;
for (int i = 0, count = mLayoutHelper.mLayoutOrder.length; i < count; ++i) {
final LayoutOrder layoutOrder = mLayoutHelper.mLayoutOrder[i];
final int offset = getCardOffsetByPositionDiff(layoutOrder.mItemPositionDiff);
final int top = centerViewTop + offset;
final int bottom = top + mDecoratedChildHeight;
fillChildItem(start, top, end, bottom, layoutOrder, recycler, i);
}
}
private void fillDataHorizontal(final RecyclerView.Recycler recycler, final int width, final int height) {
final int top = (height - mDecoratedChildHeight) / 2;
final int bottom = top + mDecoratedChildHeight;
final int centerViewStart = (width - mDecoratedChildWidth) / 2;
for (int i = 0, count = mLayoutHelper.mLayoutOrder.length; i < count; ++i) {
final LayoutOrder layoutOrder = mLayoutHelper.mLayoutOrder[i];
final int offset = getCardOffsetByPositionDiff(layoutOrder.mItemPositionDiff);
final int start = centerViewStart + offset;
final int end = start + mDecoratedChildWidth;
fillChildItem(start, top, end, bottom, layoutOrder, recycler, i);
}
}
@SuppressWarnings("MethodWithTooManyParameters")
private void fillChildItem(final int start, final int top, final int end, final int bottom, @NonNull final LayoutOrder layoutOrder, @NonNull final RecyclerView.Recycler recycler, final int i) {
final View view = bindChild(layoutOrder.mItemAdapterPosition, recycler);
ViewCompat.setElevation(view, i);
ItemTransformation transformation = null;
if (null != mViewPostLayout) {
transformation = mViewPostLayout.transformChild(view, layoutOrder.mItemPositionDiff, mOrientation, layoutOrder.mItemAdapterPosition);
}
if (null == transformation) {
view.layout(start, top, end, bottom);
} else {
view.layout(Math.round(start + transformation.mTranslationX), Math.round(top + transformation.mTranslationY),
Math.round(end + transformation.mTranslationX), Math.round(bottom + transformation.mTranslationY));
view.setScaleX(transformation.mScaleX);
view.setScaleY(transformation.mScaleY);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
view.setTransitionAlpha(transformation.mAlpha);
}else {
view.setAlpha(transformation.mAlpha);
}
}
}
/**
* 中心项目的当前滚动位置。如果是循环布局,则该值可以在任何范围内。如果不是,那么它在[0-1]
*/
private float getCurrentScrollPosition() {
final int fullScrollSize = getMaxScrollOffset();
if (0 == fullScrollSize) {
return 0;
}
return 1.0f * mLayoutHelper.mScrollOffset / getScrollItemSize();
}
/**
* 填充布局中所有项目的最大滚动值。通常,这仅适用于非循环布局。
*/
private int getMaxScrollOffset() {
return getScrollItemSize() * (mItemsCount - 1);
}
/**
* Because we can support old Android versions, we should layout our children in specific order to make our center view in the top of layout
* (this item should layout last). So this method will calculate layout order and fill up {@link #mLayoutHelper} object.
* This object will be filled by only needed to layout items. Non visible items will not be there.
*
* @param currentScrollPosition current scroll position this is a value that indicates position of center item
* (if this value is int, then center item is really in the center of the layout, else it is near state).
* Be aware that this value can be in any range is it is cycle layout
* @param state Transient state of RecyclerView
* @see #getCurrentScrollPosition()
*/
private void generateLayoutOrder(final float currentScrollPosition, @NonNull final RecyclerView.State state) {
mItemsCount = state.getItemCount();
final float absCurrentScrollPosition = makeScrollPositionInRange0ToCount(currentScrollPosition, mItemsCount);
final int centerItem = Math.round(absCurrentScrollPosition);
if (mCircleLayout && 1 < mItemsCount) {
final int layoutCount = Math.min(mLayoutHelper.mMaxVisibleItems * 2 + 1, mItemsCount);
mLayoutHelper.initLayoutOrder(layoutCount);
final int countLayoutHalf = layoutCount / 2;
// before center item
for (int i = 1; i <= countLayoutHalf; ++i) {
final int position = Math.round(absCurrentScrollPosition - i + mItemsCount) % mItemsCount;
mLayoutHelper.setLayoutOrder(countLayoutHalf - i, position, centerItem - absCurrentScrollPosition - i);
}
// after center item
for (int i = layoutCount - 1; i >= countLayoutHalf + 1; --i) {
final int position = Math.round(absCurrentScrollPosition - i + layoutCount) % mItemsCount;
mLayoutHelper.setLayoutOrder(i - 1, position, centerItem - absCurrentScrollPosition + layoutCount - i);
}
mLayoutHelper.setLayoutOrder(layoutCount - 1, centerItem, centerItem - absCurrentScrollPosition);
} else {
final int firstVisible = Math.max(centerItem - mLayoutHelper.mMaxVisibleItems, 0);
final int lastVisible = Math.min(centerItem + mLayoutHelper.mMaxVisibleItems, mItemsCount - 1);
final int layoutCount = lastVisible - firstVisible + 1;
mLayoutHelper.initLayoutOrder(layoutCount);
for (int i = firstVisible; i <= lastVisible; ++i) {
if (i == centerItem) {
mLayoutHelper.setLayoutOrder(layoutCount - 1, i, i - absCurrentScrollPosition);
} else if (i < centerItem) {
mLayoutHelper.setLayoutOrder(i - firstVisible, i, i - absCurrentScrollPosition);
} else {
mLayoutHelper.setLayoutOrder(layoutCount - (i - centerItem) - 1, i, i - absCurrentScrollPosition);
}
}
}
}
public int getWidthNoPadding() {
return getWidth() - getPaddingStart() - getPaddingEnd();
}
public int getHeightNoPadding() {
return getHeight() - getPaddingEnd() - getPaddingStart();
}
private View bindChild(final int position, @NonNull final RecyclerView.Recycler recycler) {
final View view = recycler.getViewForPosition(position);
addView(view);
measureChildWithMargins(view, 0, 0);
return view;
}
private void recyclerOldViews(final RecyclerView.Recycler recycler) {
for (RecyclerView.ViewHolder viewHolder : new ArrayList<>(recycler.getScrapList())) {
int adapterPosition = viewHolder.getAdapterPosition();
boolean found = false;
for (LayoutOrder layoutOrder : mLayoutHelper.mLayoutOrder) {
if (layoutOrder.mItemAdapterPosition == adapterPosition) {
found = true;
break;
}
}
if (!found) {
recycler.recycleView(viewHolder.itemView);
}
}
}
/**
* Called during {@link #fillData(RecyclerView.Recycler, RecyclerView.State)} to calculate item offset from layout center line. <br />
* <br />
* Returns {@link #convertItemPositionDiffToSmoothPositionDiff(float)} * (size off area above center item when it is on the center). <br />
* Sign is: plus if this item is bellow center line, minus if not<br />
* <br />
* ----- - area above it<br />
* ||||| - center item<br />
* ----- - area bellow it (it has the same size as are above center item)<br />
*
* @param itemPositionDiff current item difference with layout center line. if this is 0, then this item center is in layout center line.
* if this is 1 then this item is bellow the layout center line in the full item size distance.
* @return offset in scroll px coordinates.
*/
protected int getCardOffsetByPositionDiff(final float itemPositionDiff) {
final double smoothPosition = convertItemPositionDiffToSmoothPositionDiff(itemPositionDiff);
final int dimenDiff;
if (VERTICAL == mOrientation) {
dimenDiff = (getHeightNoPadding() - mDecoratedChildHeight) / 2;
} else {
dimenDiff = (getWidthNoPadding() - mDecoratedChildWidth) / 2;
}
//noinspection NumericCastThatLosesPrecision
return (int) Math.round(Math.signum(itemPositionDiff) * dimenDiff * smoothPosition);
}
/**
* Called during {@link #getCardOffsetByPositionDiff(float)} for better item movement. <br/>
* Current implementation speed up items that are far from layout center line and slow down items that are close to this line.
* This code is full of maths. If you want to make items move in a different way, probably you should override this method.<br />
* Please see code comments for better explanations.
*
* @param itemPositionDiff current item difference with layout center line. if this is 0, then this item center is in layout center line.
* if this is 1 then this item is bellow the layout center line in the full item size distance.
* @return smooth position offset. needed for scroll calculation and better user experience.
* @see #getCardOffsetByPositionDiff(float)
*/
@SuppressWarnings({"MagicNumber", "InstanceMethodNamingConvention"})
protected double convertItemPositionDiffToSmoothPositionDiff(final float itemPositionDiff) {
// generally item moves the same way above center and bellow it. So we don't care about diff sign.
final float absIemPositionDiff = Math.abs(itemPositionDiff);
// we detect if this item is close for center or not. We use (1 / maxVisibleItem) ^ (1/3) as close definer.
if (absIemPositionDiff > StrictMath.pow(1.0f / mLayoutHelper.mMaxVisibleItems, 1.0f / 3)) {
// this item is far from center line, so we should make it move like square root function
return StrictMath.pow(absIemPositionDiff / mLayoutHelper.mMaxVisibleItems, 1 / 2.0f);
} else {
// this item is close from center line. we should slow it down and don't make it speed up very quick.
// so square function in range of [0, (1/maxVisible)^(1/3)] is quite good in it;
return StrictMath.pow(absIemPositionDiff, 2.0f);
}
}
/**
* @return full item size
*/
protected int getScrollItemSize() {
if (VERTICAL == mOrientation) {
return mDecoratedChildHeight;
} else {
return mDecoratedChildWidth;
}
}
@Override
public Parcelable onSaveInstanceState() {
if (null != mPendingCarouselSavedState) {
return new CarouselSavedState(mPendingCarouselSavedState);
}
final CarouselSavedState savedState = new CarouselSavedState(super.onSaveInstanceState());
savedState.mCenterItemPosition = mCenterItemPosition;
return savedState;
}
@Override
public void onRestoreInstanceState(final Parcelable state) {
if (state instanceof CarouselSavedState) {
mPendingCarouselSavedState = (CarouselSavedState) state;
super.onRestoreInstanceState(mPendingCarouselSavedState.mSuperState);
} else {
super.onRestoreInstanceState(state);
}
}
/**
* @return 从中心到最近项目的滚动偏移量
*/
protected int getOffsetCenterView() {
return Math.round(getCurrentScrollPosition()) * getScrollItemSize() - mLayoutHelper.mScrollOffset;
}
protected int getOffsetForCurrentView(@NonNull final View view) {
final int targetPosition = getPosition(view);
final float directionDistance = getScrollDirection(targetPosition);
return Math.round(directionDistance * getScrollItemSize());
}
/**
* 使滚动范围在[0count]内的Helper方法。通常只有循环布局才需要此方法。
*
* @param currentScrollPosition 滚动位置范围 个位数 view的index 小数滚动的范围
* @param count adapter 中的数量
* @return 在[0总数]范围内滚动位置良好
*/
private static float makeScrollPositionInRange0ToCount(final float currentScrollPosition, final int count) {
float absCurrentScrollPosition = currentScrollPosition;
while (0 > absCurrentScrollPosition) {
absCurrentScrollPosition += count;
}
while (Math.round(absCurrentScrollPosition) >= count) {
absCurrentScrollPosition -= count;
}
return absCurrentScrollPosition;
}
/**
* This interface methods will be called for each visible view item after general LayoutManager layout finishes. <br />
* <br />
* Generally this method should be used for scaling and translating view item for better (different) view presentation of layouting.
*/
@SuppressWarnings("InterfaceNeverImplemented")
public abstract static class PostLayoutListener {
/**
* 子布局完成后调用。通常,您可以在这里进行任何平移和缩放工作。
*
* @param child view that was layout
* @param itemPositionToCenterDiff view center line difference to layout center. if > 0 then this item is bellow layout center line, else if not
* @param orientation layoutManager orientation {@link #getLayoutDirection()}
* @param itemPositionInAdapter item position inside adapter for this layout pass
*/
public ItemTransformation transformChild(
@NonNull final View child,
final float itemPositionToCenterDiff,
final int orientation,
final int itemPositionInAdapter
) {
return transformChild(child, itemPositionToCenterDiff, orientation);
}
/**
* Called after child layout finished. Generally you can do any translation and scaling work here.
*
* @param child view that was layout
* @param itemPositionToCenterDiff view center line difference to layout center. if > 0 then this item is bellow layout center line, else if not
* @param orientation layoutManager orientation {@link #getLayoutDirection()}
*/
public ItemTransformation transformChild(
@NonNull final View child,
final float itemPositionToCenterDiff,
final int orientation
) {
throw new IllegalStateException("at least one transformChild should be implemented");
}
}
public interface OnCenterItemSelectionListener {
/**
* Listener that will be called on every change of center item.
* This listener will be triggered on <b>every</b> layout operation if item was changed.
* Do not do any expensive operations in this method since this will effect scroll experience.
*
* @param adapterPosition current layout center item
*/
void onCenterItemChanged(final int adapterPosition);
}
public interface OnDargAutoDiffListener {
void onDxChanged(final float adapterPosition,final int currentPosition);
}
/**
* Helper class that holds currently visible items.
* Generally this class fills this list. <br />
* <br />
* This class holds all scroll and maxVisible items state.
*
* @see #getMaxVisibleItems()
*/
private static class LayoutHelper {
private int mMaxVisibleItems;
private int mScrollOffset;
private LayoutOrder[] mLayoutOrder;
private final List<WeakReference<LayoutOrder>> mReusedItems = new ArrayList<>();
LayoutHelper(final int maxVisibleItems) {
mMaxVisibleItems = maxVisibleItems;
}
/**
* Called before any fill calls. Needed to recycle old items and init new array list. Generally this list is an array an it is reused.
*
* @param layoutCount items count that will be layout
*/
void initLayoutOrder(final int layoutCount) {
if (null == mLayoutOrder || mLayoutOrder.length != layoutCount) {
if (null != mLayoutOrder) {
recycleItems(mLayoutOrder);
}
mLayoutOrder = new LayoutOrder[layoutCount];
fillLayoutOrder();
}
}
/**
* Called during layout generation process of filling this list. Should be called only after {@link #initLayoutOrder(int)} method call.
*
* @param arrayPosition position in layout order
* @param itemAdapterPosition adapter position of item for future data filling logic
* @param itemPositionDiff difference of current item scroll position and center item position.
* if this is a center item and it is in real center of layout, then this will be 0.
* if current layout is not in the center, then this value will never be int.
* if this item center is bellow layout center line then this value is greater then 0,
* else less then 0.
*/
void setLayoutOrder(final int arrayPosition, final int itemAdapterPosition, final float itemPositionDiff) {
final LayoutOrder item = mLayoutOrder[arrayPosition];
item.mItemAdapterPosition = itemAdapterPosition;
item.mItemPositionDiff = itemPositionDiff;
}
/**
* Checks is this screen Layout has this adapterPosition view in layout
*
* @param adapterPosition adapter position of item for future data filling logic
* @return true is adapterItem is in layout
*/
boolean hasAdapterPosition(final int adapterPosition) {
if (null != mLayoutOrder) {
for (final LayoutOrder layoutOrder : mLayoutOrder) {
if (layoutOrder.mItemAdapterPosition == adapterPosition) {
return true;
}
}
}
return false;
}
@SuppressWarnings("VariableArgumentMethod")
private void recycleItems(@NonNull final LayoutOrder... layoutOrders) {
for (final LayoutOrder layoutOrder : layoutOrders) {
//noinspection ObjectAllocationInLoop
mReusedItems.add(new WeakReference<>(layoutOrder));
}
}
private void fillLayoutOrder() {
for (int i = 0, length = mLayoutOrder.length; i < length; ++i) {
if (null == mLayoutOrder[i]) {
mLayoutOrder[i] = createLayoutOrder();
}
}
}
private LayoutOrder createLayoutOrder() {
final Iterator<WeakReference<LayoutOrder>> iterator = mReusedItems.iterator();
while (iterator.hasNext()) {
final WeakReference<LayoutOrder> layoutOrderWeakReference = iterator.next();
final LayoutOrder layoutOrder = layoutOrderWeakReference.get();
iterator.remove();
if (null != layoutOrder) {
return layoutOrder;
}
}
return new LayoutOrder();
}
}
/**
* Class that holds item data.
* This class is filled during {@link #generateLayoutOrder(float, RecyclerView.State)} and used during {@link #fillData(RecyclerView.Recycler, RecyclerView.State)}
*/
private static class LayoutOrder {
/**
* Item adapter position
*/
private int mItemAdapterPosition;
/**
* Item center difference to layout center. If center of item is bellow layout center, then this value is greater then 0, else it is less.
*/
private float mItemPositionDiff;
}
protected static class CarouselSavedState implements Parcelable {
private final Parcelable mSuperState;
private int mCenterItemPosition;
protected CarouselSavedState(@Nullable final Parcelable superState) {
mSuperState = superState;
}
private CarouselSavedState(@NonNull final Parcel in) {
mSuperState = in.readParcelable(RecyclerView.LayoutManager.class.getClassLoader());
mCenterItemPosition = in.readInt();
}
protected CarouselSavedState(@NonNull final CarouselSavedState other) {
mSuperState = other.mSuperState;
mCenterItemPosition = other.mCenterItemPosition;
}
@Override
public int describeContents() {
return 0;
}
@Override
public void writeToParcel(final Parcel parcel, final int i) {
parcel.writeParcelable(mSuperState, i);
parcel.writeInt(mCenterItemPosition);
}
public static final Creator<CarouselSavedState> CREATOR
= new Creator<CarouselSavedState>() {
@Override
public CarouselSavedState createFromParcel(final Parcel parcel) {
return new CarouselSavedState(parcel);
}
@Override
public CarouselSavedState[] newArray(final int i) {
return new CarouselSavedState[i];
}
};
}
}

View File

@@ -0,0 +1,40 @@
package com.mogo.och.taxi.passenger.ui.video.layoutmanage;
import android.view.View;
import androidx.annotation.NonNull;
/**
* Implementation of {@link CarouselLayoutManager.PostLayoutListener} that makes interesting scaling of items. <br />
* We are trying to make items scaling quicker for closer items for center and slower for when they are far away.<br />
* Tis implementation uses atan function for this purpose.
*/
public class CarouselZoomPostLayoutListener extends CarouselLayoutManager.PostLayoutListener {
private final float mScaleMultiplier;
public CarouselZoomPostLayoutListener() {
this(0.21f);
}
public CarouselZoomPostLayoutListener(final float scaleMultiplier) {
mScaleMultiplier = scaleMultiplier;
}
@Override
public ItemTransformation transformChild(@NonNull final View child, final float itemPositionToCenterDiff, final int orientation) {
float scale = 1.0f - mScaleMultiplier * Math.abs(itemPositionToCenterDiff);
final float translateY;
final float translateX;
if (CarouselLayoutManager.VERTICAL == orientation) {
final float translateYGeneral = child.getMeasuredHeight() * (1 - scale) / 2f;
translateY = Math.signum(itemPositionToCenterDiff) * translateYGeneral;
translateX = 0;
} else {
final float translateXGeneral = child.getMeasuredWidth() * (1 - scale)/8;
translateX = Math.signum(itemPositionToCenterDiff) * translateXGeneral;
translateY = 0;
}
return new ItemTransformation(scale,scale, scale, 0, translateY);
}
}

View File

@@ -0,0 +1,52 @@
package com.mogo.och.taxi.passenger.ui.video.layoutmanage;
import androidx.annotation.NonNull;
import androidx.recyclerview.widget.RecyclerView;
/**
* Class for centering items after scroll event.<br />
* This class will listen to current scroll state and if item is not centered after scroll it will automatically scroll it to center.
*/
public class CenterScrollListener extends RecyclerView.OnScrollListener {
private boolean mAutoSet = true;
@Override
public void onScrollStateChanged(@NonNull final RecyclerView recyclerView, final int newState) {
super.onScrollStateChanged(recyclerView, newState);
final RecyclerView.LayoutManager layoutManager = recyclerView.getLayoutManager();
if (!(layoutManager instanceof CarouselLayoutManager)) {
mAutoSet = true;
return;
}
final CarouselLayoutManager lm = (CarouselLayoutManager) layoutManager;
if (!mAutoSet) {
if (RecyclerView.SCROLL_STATE_IDLE == newState) {
final int scrollNeeded = lm.getOffsetCenterView();
// 滚动到中心位置
if (CarouselLayoutManager.HORIZONTAL == lm.getOrientation()) {
recyclerView.smoothScrollBy(scrollNeeded, 0);
} else {
recyclerView.smoothScrollBy(0, scrollNeeded);
}
pageSelect(recyclerView,newState);
mAutoSet = true;
}
}
if (RecyclerView.SCROLL_STATE_DRAGGING == newState || RecyclerView.SCROLL_STATE_SETTLING == newState) {
mAutoSet = false;
}
if(RecyclerView.SCROLL_STATE_DRAGGING == newState){
pageStop();
}
}
protected void pageStop() {
}
protected void pageSelect(RecyclerView recyclerView, final int newState) {
}
}

View File

@@ -0,0 +1,18 @@
package com.mogo.och.taxi.passenger.ui.video.layoutmanage;
public class ItemTransformation {
final float mAlpha;
final float mScaleX;
final float mScaleY;
final float mTranslationX;
final float mTranslationY;
public ItemTransformation(final float alpha,final float scaleX, final float scaleY, final float translationX, final float translationY) {
mScaleX = scaleX;
mScaleY = scaleY;
mTranslationX = translationX;
mTranslationY = translationY;
mAlpha = alpha;
}
}

View File

@@ -0,0 +1,116 @@
package com.mogo.och.taxi.passenger.utils
import android.text.TextUtils
import com.mogo.cloud.passport.MoGoAiCloudClientConfig
import com.mogo.commons.debug.DebugConfig
import com.mogo.commons.utils.MogoAnalyticUtils
import com.mogo.eagle.core.data.app.AppConfigInfo
import com.mogo.eagle.core.function.api.autopilot.IMoGoAutopilotStatusListener
import com.mogo.eagle.core.function.call.autopilot.CallerAutoPilotStatusListenerManager
import com.mogo.eagle.core.utilcode.mogo.logger.CallerLogger.e
import com.mogo.eagle.core.utilcode.mogo.logger.scene.SceneConstant.Companion.M_TAXI_P
import com.mogo.eagle.core.utilcode.util.DateTimeUtils
import com.mogo.och.common.module.utils.RxUtils
import com.mogo.och.taxi.passenger.constant.TaxiPassengerConst
import io.reactivex.disposables.Disposable
/**
* OCH Taxi埋点工具
*
* Created on 2022/3/24
*/
object TaxiPassengerAnalyticsManager {
private var mStartAutopilotKey: String? = null
private val mStartAutopilotParams = HashMap<String, Any>()
var startAutopiloTimeOut: Disposable? = null
fun triggerStartAutopilotFailureEventByAdas(failCode: String, failMsg: String) {
RxUtils.disposeSubscribe(startAutopiloTimeOut)
triggerStartAutopilotFailureEvent(failCode, failMsg)
}
private fun clearStartAutopilotParams() {
mStartAutopilotParams.clear()
}
/**
* ① 15s超时调用
* ② 底盘明确给出错误原因
* 启动自驾失败写日志
*/
private fun triggerStartAutopilotFailureEvent(failCode: String, failMsg: String) {
if (mStartAutopilotParams.isEmpty()) return
e(M_TAXI_P + "triggerStartAutopilotFailureEvent", failMsg)
if (CallerAutoPilotStatusListenerManager.getState() != IMoGoAutopilotStatusListener.STATUS_AUTOPILOT_RUNNING) {
mStartAutopilotParams[TaxiPassengerConst.EVENT_PARAM_START_FAILURE_CODE] = failCode
mStartAutopilotParams[TaxiPassengerConst.EVENT_PARAM_START_FAILURE_MSG] = failMsg
}
mStartAutopilotParams[TaxiPassengerConst.EVENT_PARAM_START_RESULT] =
CallerAutoPilotStatusListenerManager.getState() == IMoGoAutopilotStatusListener.STATUS_AUTOPILOT_RUNNING
MogoAnalyticUtils.track(mStartAutopilotKey, mStartAutopilotParams)
clearStartAutopilotParams() //清空参数数据,防止误传
}
/**
*
* ① 自检完成后 启动自驾
* ② 自驾由其他状态转换到自驾中回调
* 触发'开启自动驾驶'埋点流程
* 开启自动驾驶15s内成功则发送成功埋点否则发送失败埋点
* @param restart false点击'开始服务'启动)/true接管后点击'自动驾驶'按钮启动)
* @param send 是否直接发送埋点15s内开启成功则直接发送成功埋点
*/
fun triggerStartAutopilotEvent(restart: Boolean, send: Boolean, startName: String, endName: String, orderNo: String) {
mStartAutopilotKey = if (restart) TaxiPassengerConst.EVENT_KEY_RESTART_AUTOPILOT else TaxiPassengerConst.EVENT_KEY_START_SERVICE
if (send) {
if (mStartAutopilotParams.isEmpty()) return
// 开启成功,取消失败定时任务
mStartAutopilotParams[TaxiPassengerConst.EVENT_PARAM_START_FAILURE_CODE] = ""
mStartAutopilotParams[TaxiPassengerConst.EVENT_PARAM_START_FAILURE_MSG] = ""
// 取消15s超时
RxUtils.disposeSubscribe(startAutopiloTimeOut)
mStartAutopilotParams[TaxiPassengerConst.EVENT_PARAM_START_RESULT] = true
MogoAnalyticUtils.track(mStartAutopilotKey, mStartAutopilotParams)
clearStartAutopilotParams() //清空参数数据,防止误传
} else {
val plateNum = AppConfigInfo.plateNumber
mStartAutopilotParams[TaxiPassengerConst.EVENT_PARAM_SN] = MoGoAiCloudClientConfig.getInstance().sn
mStartAutopilotParams[TaxiPassengerConst.EVENT_PARAM_PLATE_NUM] = if (TextUtils.isEmpty(plateNum)) "" else plateNum
mStartAutopilotParams[TaxiPassengerConst.EVENT_PARAM_ENV_ONLINE] = DebugConfig.getNetMode() == DebugConfig.NET_MODE_RELEASE
mStartAutopilotParams[TaxiPassengerConst.EVENT_PARAM_TIME] = DateTimeUtils.getTimeText(DateTimeUtils.yyyy_MM_dd_HH_mm_ss)
mStartAutopilotParams[TaxiPassengerConst.EVENT_PARAM_START_NAME] = startName
mStartAutopilotParams[TaxiPassengerConst.EVENT_PARAM_END_NAME] = endName
mStartAutopilotParams[TaxiPassengerConst.EVENT_PARAM_ORDER_NUMBER] = orderNo
startAutopiloTimeOut = RxUtils.createSubscribeOnOwnThread(TaxiPassengerConst.LOOP_PERIOD_15S) {
// 15s内未开启上报失败埋点
triggerStartAutopilotFailureEvent("", "15s后app等待超时")
}
}
}
/**
* 触发"无法开启自驾已知异常"埋点
* @param startName
* @param endName
* @param orderNo
*/
fun triggerUnableStartAPReasonEvent(startName: String, endName: String, orderNo: String, reason: String) {
val plateNum = AppConfigInfo.plateNumber
val params = HashMap<String, Any>()
params[TaxiPassengerConst.EVENT_PARAM_SN] = MoGoAiCloudClientConfig.getInstance().sn
params[TaxiPassengerConst.EVENT_PARAM_PLATE_NUM] = if (TextUtils.isEmpty(plateNum)) "" else plateNum
params[TaxiPassengerConst.EVENT_PARAM_ENV_ONLINE] = DebugConfig.getNetMode() == DebugConfig.NET_MODE_RELEASE
params[TaxiPassengerConst.EVENT_PARAM_TIME] = DateTimeUtils.getTimeText(DateTimeUtils.yyyy_MM_dd_HH_mm_ss)
params[TaxiPassengerConst.EVENT_PARAM_START_NAME] = startName
params[TaxiPassengerConst.EVENT_PARAM_END_NAME] = endName
params[TaxiPassengerConst.EVENT_PARAM_ORDER_NUMBER] = orderNo
params[TaxiPassengerConst.EVENT_PARAM_UNABLE_START_REASON] = reason
MogoAnalyticUtils.track(TaxiPassengerConst.EVENT_KEY_AP_UNABLE_START_REASON, params)
}
}

View File

@@ -0,0 +1,21 @@
package com.mogo.och.taxi.passenger.utils
import android.content.Context
import android.graphics.*
import android.graphics.drawable.BitmapDrawable
import android.graphics.drawable.Drawable
import androidx.annotation.DrawableRes
object ZoomDrawable {
fun zoomDrawableImage(context: Context,@DrawableRes id:Int,scaleX:Float,scaleY:Float):Drawable{
val bitmap: Bitmap = BitmapFactory.decodeResource(context.resources, id)
val bitmapWidth = bitmap.width
val bitmapHeight = bitmap.height
val matrix = Matrix()
matrix.postScale(scaleX, scaleY)
// 产生缩放后的Bitmap对象
val resizeBitmap = Bitmap.createBitmap(bitmap, 0, 0, bitmapWidth, bitmapHeight, matrix, true)
return BitmapDrawable(context.resources,resizeBitmap)
}
}

View File

@@ -0,0 +1,529 @@
package com.mogo.och.taxi.passenger.widget
import android.app.Activity
import android.content.Context
import android.graphics.Color
import android.graphics.drawable.GradientDrawable
import android.os.Build
import android.util.AttributeSet
import android.util.TypedValue
import android.view.Gravity
import android.view.Surface
import android.view.View
import android.view.ViewGroup
import android.widget.FrameLayout
import android.widget.ImageView
import android.widget.TextView
import androidx.appcompat.widget.AppCompatImageView
import androidx.constraintlayout.widget.ConstraintLayout
import androidx.core.app.ActivityCompat
import com.mogo.eagle.core.utilcode.util.TimeTransformUtils
import com.mogo.eagle.core.utilcode.util.ToastUtils
import com.mogo.eagle.core.widget.media.video.TextureVideoViewOutlineProvider
import com.mogo.och.taxi.passenger.R
import com.mogo.och.taxi.passenger.ui.statusview.StatusBarView
import com.mogo.och.taxi.passenger.ui.video.FullVideoUtils
import com.mogo.och.taxi.passenger.utils.ZoomDrawable
import com.shuyu.gsyvideoplayer.listener.VideoAllCallBack
import com.shuyu.gsyvideoplayer.utils.GSYVideoType
import com.shuyu.gsyvideoplayer.video.StandardGSYVideoPlayer
import com.shuyu.gsyvideoplayer.video.base.GSYBaseVideoPlayer
import com.shuyu.gsyvideoplayer.video.base.GSYVideoPlayer
import com.shuyu.gsyvideoplayer.video.base.GSYVideoView
import me.jessyan.autosize.utils.AutoSizeUtils
import java.lang.reflect.Constructor
/**
* @author lixiaopeng
* @since 2021/11/3
*
* 视频播放器ui定制
*/
class ConsultVideoPlayer : StandardGSYVideoPlayer {
private lateinit var start: ImageView
lateinit var coverImage: ImageView
private lateinit var currentTimeTextView: TextView
private lateinit var totalTimeTextView: TextView
private lateinit var aivStartPlay: AppCompatImageView
private lateinit var layoutBottom: ConstraintLayout
private lateinit var vPpenLeft: View
private var fullVideoPlayer:ConsultVideoPlayer?=null
var smalllPlayer:ConsultVideoPlayer?=null
private var statusBarView: StatusBarView? = null
private var currentTime = 0
constructor(context: Context?) : super(context)
constructor(context: Context?, attrs: AttributeSet?) : super(context, attrs)
constructor(context: Context?, fullFlag: Boolean?) : super(context, fullFlag)
override fun init(context: Context) {
mEnlargeImageRes = R.drawable.taxi_p_change_full
super.init(context)
start = findViewById(R.id.start)
coverImage = findViewById(R.id.thumbImage)
currentTimeTextView = findViewById(R.id.current)
totalTimeTextView = findViewById(R.id.total)
aivStartPlay = findViewById(R.id.aiv_start_play)
layoutBottom = findViewById(R.id.layout_bottom)
vPpenLeft = findViewById(R.id.v_open_left)
fullscreenButton.setOnClickListener(this)
aivStartPlay.setOnClickListener(this)
if (mThumbImageViewLayout != null
&& (mCurrentState == -1 || mCurrentState == CURRENT_STATE_NORMAL || mCurrentState == CURRENT_STATE_ERROR)
) {
mThumbImageViewLayout.visibility = View.VISIBLE
}
GSYVideoType.setShowType(GSYVideoType.SCREEN_TYPE_FULL)
aivStartPlay.scaleX = 0.8f
aivStartPlay.scaleY = 0.8f
mProgressBar.thumb = ZoomDrawable.zoomDrawableImage(context,R.drawable.bg_taxi_p_video_index,0.66f,0.66f)
}
private fun addDrageAnchor(){
vPpenLeft.visibility = VISIBLE
layoutBottom.post {
val layoutParams = layoutBottom.layoutParams as ConstraintLayout.LayoutParams
layoutParams.setMargins(333,0,333,90)
layoutParams.height = 148
layoutBottom.layoutParams = layoutParams
}
mTopContainer.post {
val layoutParams = mTopContainer.layoutParams as ConstraintLayout.LayoutParams
layoutParams.height = 320
mTopContainer.layoutParams = layoutParams
val background = layoutBottom.background as GradientDrawable
val x = floatArrayOf(12f, 12f,12f, 12f,12f, 12f,12f, 12f)
background.cornerRadii = x
layoutBottom.background = background
titleTextView.setTextSize(TypedValue.COMPLEX_UNIT_PX, 50f)
val layoutParams1 = titleTextView.layoutParams as ConstraintLayout.LayoutParams
layoutParams1.marginStart = 80
titleTextView.layoutParams = layoutParams1
aivStartPlay.scaleX = 1f
aivStartPlay.scaleY = 1f
val drawable = ActivityCompat.getDrawable(context, R.drawable.bg_taxi_p_video_index)
mProgressBar.thumb = drawable
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
mProgressBar.maxHeight = 6
mProgressBar.minHeight = 6
}
val layoutParams2 = fullscreenButton.layoutParams as ConstraintLayout.LayoutParams
layoutParams2.topMargin = AutoSizeUtils.dp2px(context,119f)
layoutParams2.rightMargin = AutoSizeUtils.dp2px(context,70f)
layoutParams2.height = AutoSizeUtils.dp2px(context,108f)
layoutParams2.width = AutoSizeUtils.dp2px(context,108f)
fullscreenButton.layoutParams = layoutParams2
fullscreenButton.visibility = View.VISIBLE
}
}
override fun getLayoutId(): Int {
return R.layout.taxi_p_video_show
}
override fun updateStartImage() {
when (mCurrentState) {
GSYVideoView.CURRENT_STATE_PLAYING ->{
start.setImageResource(R.drawable.notice_video_pause)
aivStartPlay.visibility = View.GONE
}
else -> {
start.setImageResource(R.drawable.notice_video_after_pause)
aivStartPlay.visibility = View.VISIBLE
}
}
}
override fun setStateAndUi(state: Int) {
super.setStateAndUi(state)
if(state==CURRENT_STATE_PLAYING_BUFFERING_START){
ToastUtils.showShort("加载中请稍等")
}
}
override fun onWindowFocusChanged(hasWindowFocus: Boolean) {
super.onWindowFocusChanged(hasWindowFocus)
if(isIfCurrentIsFullscreen&&smalllPlayer!=null){
if(hasWindowFocus){//获取焦点
//onVideoResume()
}else{
onVideoPause()
}
}
}
override fun touchDoubleUp() {
}
override fun changeUiToNormal() {
super.changeUiToNormal()
//setViewShowState(fullscreenButton, INVISIBLE)
this.statusBarView?.visibility = View.VISIBLE
}
override fun changeUiToPreparingShow() {
super.changeUiToPreparingShow()
this.statusBarView?.visibility = View.VISIBLE
}
override fun changeUiToPlayingShow() {
super.changeUiToPlayingShow()
setViewShowState(fullscreenButton, VISIBLE)
this.statusBarView?.visibility = View.VISIBLE
}
override fun changeUiToPlayingClear() {
super.changeUiToPlayingClear()
this.statusBarView?.visibility = View.GONE
}
override fun changeUiToPauseShow() {
super.changeUiToPauseShow()
this.statusBarView?.visibility = View.VISIBLE
}
override fun changeUiToPlayingBufferingShow() {
super.changeUiToPlayingBufferingShow()
this.statusBarView?.visibility = View.VISIBLE
}
override fun changeUiToCompleteShow() {
super.changeUiToCompleteShow()
this.statusBarView?.visibility = View.VISIBLE
}
public override fun hideAllWidget() {
super.hideAllWidget()
this.statusBarView?.visibility = View.GONE
}
override fun setProgressAndTime(
progress: Int,
secProgress: Int,
currentTime: Int,
totalTime: Int,
forceChange: Boolean
) {
super.setProgressAndTime(progress, secProgress, currentTime, totalTime, forceChange)
//时间显示
currentTimeTextView.text = TimeTransformUtils.stringForTime(currentTime)
totalTimeTextView.text = TimeTransformUtils.stringForTime(totalTime)
if(currentTime>=totalTime-3000){//
this.currentTime = -1
}else{
this.currentTime = currentTime
}
if (progress != 0) {
mProgressBar?.progress = progress
}
}
override fun showWifiDialog() {
//直接播放不显示WIFI对话框
startPlayLogic()
}
override fun onDetachedFromWindow() {
mProgressBar?.progress = 0
fullVideoPlayer?.let {
clearFullscreenLayout(it)
}
fullVideoPlayer = null
if(!isIfCurrentIsFullscreen) {
onVideoReset()
setVideoAllCallBack(null)
}
dismissProgressDialog()
super.onDetachedFromWindow()
}
override fun onClick(v: View?) {
super.onClick(v)
when (v?.id) {
R.id.fullscreen -> {
startWindowFullscreenOwn(context)
// startWindowFullscreen(context)
}
R.id.aiv_start_play -> {
if(currentState==GSYVideoView.CURRENT_STATE_PAUSE){
onVideoResume(false)
}else{
if (mProgressBar==null) {
startPlayLogic()
}else {
mProgressBar?.let {
if(currentTime>0) {
seekOnStart = currentTime.toLong()
}
startPlayLogic()
}
}
}
}
else -> {}
}
}
override fun onCompletion() {
start.setImageResource(R.drawable.notice_video_after_pause)
}
override fun onSurfaceUpdated(surface: Surface) {
super.onSurfaceUpdated(surface)
if (mThumbImageViewLayout != null && mThumbImageViewLayout.visibility == View.VISIBLE) {
mThumbImageViewLayout.visibility = View.INVISIBLE
}
}
override fun onPrepared() {
super.onPrepared()
}
override fun onBufferingUpdate(percent: Int) {
super.onBufferingUpdate(percent)
}
override fun onError(what: Int, extra: Int) {
super.onError(what, extra)
mThumbImageViewLayout?.visibility = View.VISIBLE
ToastUtils.showLong("哎呀,出错了,看看其他视频吧")
currentTime = -1
if(isIfCurrentIsFullscreen){
smalllPlayer?.clearFullscreenLayout(this)
smalllPlayer?.currentTime = -1
FullVideoUtils.dismissOverlayView(false)
}
}
override fun setViewShowState(view: View?, visibility: Int) {
if (view === mThumbImageViewLayout && visibility != View.VISIBLE) {
return
}
super.setViewShowState(view, visibility)
}
override fun onSurfaceAvailable(surface: Surface) {
super.onSurfaceAvailable(surface)
if (GSYVideoType.getRenderType() != GSYVideoType.TEXTURE) {
if (mThumbImageViewLayout != null && mThumbImageViewLayout.visibility == View.VISIBLE) {
mThumbImageViewLayout.visibility = View.INVISIBLE
}
}
}
override fun onAutoCompletion() {
super.onAutoCompletion()
if(mIfCurrentIsFullscreen){
if(smalllPlayer!=null){
smalllPlayer?.clearFullscreenLayout(this)
}
FullVideoUtils.dismissOverlayView(false)
}
}
override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) {
super.onSizeChanged(w, h, oldw, oldh)
if (!mIfCurrentIsFullscreen) {
this.outlineProvider = TextureVideoViewOutlineProvider(12F)
this.clipToOutline = true
}
}
fun exitFullScreenMode() {
fullVideoPlayer?.let {
clearFullscreenLayout(it)
FullVideoUtils.dismissOverlayView(false)
}
}
private fun startWindowFullscreenOwn(context:Context){
val gsyBaseVideoPlayer = startWindowFullscreen(context)
gsyBaseVideoPlayer?.let {
val gsyVideoPlayer = it as StandardGSYVideoPlayer
gsyVideoPlayer.setLockClickListener(mLockClickListener)
gsyVideoPlayer.isNeedLockFull = isNeedLockFull
initFullUI(gsyVideoPlayer)
}
}
private fun initFullUI(standardGSYVideoPlayer: StandardGSYVideoPlayer) {
if (mBottomProgressDrawable != null) {
standardGSYVideoPlayer.setBottomProgressBarDrawable(mBottomProgressDrawable)
}
if (mBottomShowProgressDrawable != null && mBottomShowProgressThumbDrawable != null) {
standardGSYVideoPlayer.setBottomShowProgressBarDrawable(
mBottomShowProgressDrawable,
mBottomShowProgressThumbDrawable
)
}
if (mVolumeProgressDrawable != null) {
standardGSYVideoPlayer.setDialogVolumeProgressBar(mVolumeProgressDrawable)
}
if (mDialogProgressBarDrawable != null) {
standardGSYVideoPlayer.setDialogProgressBar(mDialogProgressBarDrawable)
}
if (mDialogProgressHighLightColor >= 0 && mDialogProgressNormalColor >= 0) {
standardGSYVideoPlayer.setDialogProgressColor(
mDialogProgressHighLightColor,
mDialogProgressNormalColor
)
}
standardGSYVideoPlayer.titleTextView?.text = titleTextView.text
}
private fun startWindowFullscreen(context:Context):GSYBaseVideoPlayer?{
if (mTextureViewContainer.childCount > 0) {
mTextureViewContainer.removeAllViews()
}
var hadNewConstructor = true
//切换时关闭非全屏定时器
cancelProgressTimer()
try {
this@ConsultVideoPlayer.javaClass.getConstructor(
Context::class.java,
Boolean::class.java
)
} catch (e: java.lang.Exception) {
hadNewConstructor = false
}
try {
//通过被重载的不同构造器来选择
val constructor: Constructor<ConsultVideoPlayer>
val gsyVideoPlayer: ConsultVideoPlayer
if (!hadNewConstructor) {
constructor = this@ConsultVideoPlayer.javaClass.getConstructor(Context::class.java)
gsyVideoPlayer = constructor.newInstance(mContext)
} else {
constructor = this@ConsultVideoPlayer.javaClass.getConstructor(
Context::class.java,
Boolean::class.java
)
gsyVideoPlayer = constructor.newInstance(mContext, true)
}
this.fullVideoPlayer = gsyVideoPlayer
gsyVideoPlayer.id = fullId
gsyVideoPlayer.isIfCurrentIsFullscreen = true
gsyVideoPlayer.setVideoAllCallBack(mVideoAllCallBack)
gsyVideoPlayer.addDrageAnchor()
cloneParams(this, gsyVideoPlayer)
val frameLayout = FrameLayout(context)
if (gsyVideoPlayer.fullscreenButton != null) {
gsyVideoPlayer.fullscreenButton.setImageResource(R.drawable.taxi_p_change_normal)
gsyVideoPlayer.fullscreenButton.setOnClickListener { v ->
if (mBackFromFullScreenListener == null) {
clearFullscreenLayout(gsyVideoPlayer)
FullVideoUtils.dismissOverlayView(false)
} else {
mBackFromFullScreenListener.onClick(v)
}
}
}
// 点击视频不展示状态栏
gsyVideoPlayer.isHideKey = false
gsyVideoPlayer.smalllPlayer = this
frameLayout.setBackgroundColor(Color.BLACK)
val lp = LayoutParams(width, height)
frameLayout.addView(gsyVideoPlayer, lp)
gsyVideoPlayer.statusBarView = StatusBarView(context)
frameLayout.addView(gsyVideoPlayer.statusBarView)
FullVideoUtils.showOverlayView(context as Activity,frameLayout,R.style.och_window_anim_alpha)
gsyVideoPlayer.visibility = INVISIBLE
frameLayout.visibility = INVISIBLE
resolveFullVideoShow(context, gsyVideoPlayer, frameLayout)
gsyVideoPlayer.addTextureView()
gsyVideoPlayer.startProgressTimer()
gsyVideoManager.setLastListener(this)
gsyVideoManager.setListener(gsyVideoPlayer)
checkoutState()
thumbImageViewLayout.visibility = View.VISIBLE
return gsyVideoPlayer
} catch (e: java.lang.Exception) {
e.printStackTrace()
}
return null
}
/**
* 全屏
*/
override fun resolveFullVideoShow(context: Context?, gsyVideoPlayer: GSYBaseVideoPlayer,
frameLayout: FrameLayout) {
val lp = gsyVideoPlayer.layoutParams as LayoutParams
lp.setMargins(0, 0, 0, 0)
lp.height = ViewGroup.LayoutParams.WRAP_CONTENT
lp.width = ViewGroup.LayoutParams.MATCH_PARENT
lp.gravity = Gravity.BOTTOM
gsyVideoPlayer.layoutParams = lp
gsyVideoPlayer.isIfCurrentIsFullscreen = true
val isVertical = isVerticalFullByVideoSize
val isLockLand = isLockLandByAutoFullSize
if (isShowFullAnimation) {
mInnerHandler.postDelayed({ //autoFull模式下非横屏视频视频不横屏并且不自动旋转
if (!isVertical && isLockLand && mOrientationUtils != null && mOrientationUtils.isLand != 1) {
mOrientationUtils.resolveByClick()
}
gsyVideoPlayer.visibility = VISIBLE
frameLayout.visibility = VISIBLE
}, 300)
} else {
if (!isVertical && isLockLand && mOrientationUtils != null) {
mOrientationUtils.resolveByClick()
}
gsyVideoPlayer.visibility = VISIBLE
frameLayout.visibility = VISIBLE
}
if (mVideoAllCallBack != null) {
mVideoAllCallBack.onEnterFullscreen(mOriginUrl, mTitle, gsyVideoPlayer)
}
mIfCurrentIsFullscreen = true
checkoutState()
checkAutoFullWithSizeAndAdaptation(gsyVideoPlayer)
}
fun clearFullscreenLayout(gsyVideoPlayer:ConsultVideoPlayer) {
if (mIfCurrentIsFullscreen) {
mIfCurrentIsFullscreen = false
val delay = 100
gsyVideoPlayer.smalllPlayer = null
mInnerHandler.postDelayed({ resolveNormalVideoShow(gsyVideoPlayer) }, delay.toLong())
}
}
private fun resolveNormalVideoShow(gsyVideoPlayer: GSYVideoPlayer) {
mCurrentState = gsyVideoManager.lastState
cloneParams(gsyVideoPlayer, this)
gsyVideoManager.setListener(gsyVideoManager.lastListener())
gsyVideoManager.setLastListener(null)
gsyVideoPlayer.setVideoAllCallBack(null)
setStateAndUi(mCurrentState)
addTextureView()
mSaveChangeViewTIme = System.currentTimeMillis()
if (mVideoAllCallBack != null) {
mVideoAllCallBack.onQuitFullscreen(mOriginUrl, mTitle, this)
}
mIfCurrentIsFullscreen = false
if (fullscreenButton != null) {
fullscreenButton.setImageResource(enlargeImageRes)
}
this.fullVideoPlayer = null
}
fun getVideoAllCallBack(): VideoAllCallBack? {
return mVideoAllCallBack
}
}

View File

@@ -0,0 +1,30 @@
package com.mogo.och.taxi.passenger.widget
import android.content.Context
import android.util.AttributeSet
import android.widget.RelativeLayout
import androidx.lifecycle.ViewModelStore
import androidx.lifecycle.ViewModelStoreOwner
open class WindowRelativeLayout: RelativeLayout, ViewModelStoreOwner {
constructor(context: Context?) : super(context)
constructor(context: Context?, attributeSet: AttributeSet) : super(context, attributeSet)
constructor(context: Context?, attributeSet: AttributeSet, defStyleAttr: Int) : super(context, attributeSet, defStyleAttr)
constructor(context: Context?, attributeSet: AttributeSet, defStyleAttr: Int, defStyleRes: Int) : super(context, attributeSet, defStyleAttr, defStyleRes)
//定义ViewModelStore变量
private var mViewModelStore: ViewModelStore = ViewModelStore()
override fun getViewModelStore(): ViewModelStore {
return mViewModelStore
}
override fun onDetachedFromWindow() {
super.onDetachedFromWindow()
mViewModelStore.clear()
}
}

View File

@@ -0,0 +1,146 @@
package com.mogo.och.taxi.passenger.widget.animutils
import android.graphics.Bitmap
import android.graphics.BitmapFactory
import android.graphics.drawable.BitmapDrawable
import android.os.Handler
import android.os.Looper
import android.widget.ImageView
import com.mogo.commons.AbsMogoApplication
import java.lang.ref.SoftReference
class AnimationsContainer(resId: Int, fps: Int, imageView: ImageView) {
private lateinit var mFrames: IntArray // 帧数组
private var mIndex = 0 // 当前帧
private var mShouldRun = false // 开始/停止播放用
private var mIsRunning = false // 动画是否正在播放,防止重复播放
private var mSoftReferenceImageView: SoftReference<ImageView>? = null // 软引用ImageView以便及时释放掉
private var mHandler: Handler? = null
private var mDelayMillis = 0
private var mOnAnimationStoppedListener: OnAnimationStoppedListener? = null//播放停止监听
private var mBitmap: Bitmap? = null
private var mBitmapOptions: BitmapFactory.Options? = null //Bitmap管理类可有效减少Bitmap的OOM问题
init {
createAnimation(imageView, getData(resId), fps)
}
private fun createAnimation(imageView: ImageView, frames: IntArray, fps: Int) {
mHandler = Handler(Looper.myLooper()!!)
mFrames = frames
mIndex = -1
mSoftReferenceImageView = SoftReference(imageView)
mShouldRun = false
mIsRunning = false
mDelayMillis = 1000 / fps //帧动画时间间隔,毫秒
imageView.setImageResource(mFrames[0])
// 当图片大小类型相同时进行复用避免频繁GC
val bmp = (imageView.drawable as BitmapDrawable).bitmap
val width = bmp.width
val height = bmp.height
val config = bmp.config
mBitmap = Bitmap.createBitmap(width, height, config)
mBitmapOptions = BitmapFactory.Options()
//设置Bitmap内存复用
mBitmapOptions!!.inBitmap = mBitmap //Bitmap复用内存块类似对象池避免不必要的内存分配和回收
mBitmapOptions!!.inMutable = true //解码时返回可变Bitmap
mBitmapOptions!!.inSampleSize = 1 //缩放比例
}
//循环读取下一帧
private val next: Int
get() {
mIndex++
if (mIndex >= mFrames.size) mIndex = 0
return mFrames[mIndex]
}
/**
* 播放动画,同步锁防止多线程读帧时,数据安全问题
*/
@Synchronized
fun start() {
mShouldRun = true
if (mIsRunning) return
val runnable: Runnable = object : Runnable {
override fun run() {
val imageView = mSoftReferenceImageView!!.get()
if (!mShouldRun || imageView == null) {
mIsRunning = false
if (mOnAnimationStoppedListener != null) {
mOnAnimationStoppedListener!!.AnimationStopped()
}
return
}
mIsRunning = true
//新开线程去读下一帧
mHandler!!.postDelayed(this, mDelayMillis.toLong())
if (imageView.isShown) {
val imageRes: Int = next
if (mBitmap != null) { // so Build.VERSION.SDK_INT >= 11
var bitmap: Bitmap? = null
try {
bitmap = BitmapFactory.decodeResource(
imageView.resources,
imageRes,
mBitmapOptions
)
} catch (e: Exception) {
e.printStackTrace()
}
if (bitmap != null) {
imageView.setImageBitmap(bitmap)
} else {
imageView.setImageResource(imageRes)
mBitmap!!.recycle()
mBitmap = null
}
} else {
imageView.setImageResource(imageRes)
}
}
}
}
mHandler!!.post(runnable)
}
/**
* 停止播放
*/
@Synchronized
fun stop() {
mShouldRun = false
}
/**
* 设置停止播放监听
* @param listener 设置监听
*/
fun setOnAnimStopListener(listener: OnAnimationStoppedListener?) {
mOnAnimationStoppedListener = listener
}
/**
* 从xml中读取帧数组
* @param resId
* @return
*/
private fun getData(resId: Int): IntArray {
val array = AbsMogoApplication.getApp().resources.obtainTypedArray(resId)
val len = array.length()
val intArray = IntArray(array.length())
for (i in 0 until len) {
intArray[i] = array.getResourceId(i, 0)
}
array.recycle()
return intArray
}
/**
* 停止播放监听
*/
interface OnAnimationStoppedListener {
fun AnimationStopped()
}
}

View File

@@ -0,0 +1,68 @@
package com.mogo.och.taxi.passenger.widget.indicator
import android.content.Context
import android.graphics.Canvas
import android.util.AttributeSet
import com.mogo.och.taxi.passenger.widget.indicator.annotation.AIndicatorOrientation
import com.zhpan.indicator.base.BaseIndicatorView
import com.mogo.och.taxi.passenger.widget.indicator.drawer.DrawerProxy
import com.mogo.och.taxi.passenger.widget.indicator.enums.IndicatorOrientation
import com.mogo.och.taxi.passenger.widget.indicator.option.AttrsController
import com.mogo.och.taxi.passenger.widget.indicator.option.IndicatorOptions
class IndicatorView @JvmOverloads constructor(
context: Context,
attrs: AttributeSet? = null,
defStyleAttr: Int = 0
) : BaseIndicatorView(context, attrs, defStyleAttr) {
private var mDrawerProxy: DrawerProxy
init {
AttrsController.initAttrs(context, attrs, mIndicatorOptions)
mDrawerProxy = DrawerProxy(mIndicatorOptions)
}
override fun onLayout(changed: Boolean, left: Int, top: Int, right: Int, bottom: Int) {
super.onLayout(changed, left, top, right, bottom)
mDrawerProxy.onLayout(changed, left, top, right, bottom)
}
override fun onMeasure(
widthMeasureSpec: Int,
heightMeasureSpec: Int
) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec)
val measureResult = mDrawerProxy.onMeasure(widthMeasureSpec, heightMeasureSpec)
setMeasuredDimension(measureResult.measureWidth, measureResult.measureHeight)
}
override fun onDraw(canvas: Canvas) {
super.onDraw(canvas)
rotateCanvas(canvas)
mDrawerProxy.onDraw(canvas)
}
override fun setIndicatorOptions(options: IndicatorOptions) {
super.setIndicatorOptions(options)
mDrawerProxy.setIndicatorOptions(options)
}
override fun notifyDataChanged(itemCount:Int) {
mDrawerProxy = DrawerProxy(mIndicatorOptions)
super.notifyDataChanged(itemCount)
}
private fun rotateCanvas(canvas: Canvas) {
if (mIndicatorOptions.orientation == IndicatorOrientation.INDICATOR_VERTICAL) {
canvas.rotate(90f, width / 2f, width / 2f)
} else if (mIndicatorOptions.orientation == IndicatorOrientation.INDICATOR_RTL) {
canvas.rotate(180f, width / 2f, height / 2f)
}
}
fun setOrientation(@AIndicatorOrientation orientation: Int) {
mIndicatorOptions.orientation = orientation
}
}

View File

@@ -0,0 +1,17 @@
package com.mogo.och.taxi.passenger.widget.indicator.annotation
import androidx.annotation.IntDef
import com.mogo.och.taxi.passenger.widget.indicator.enums.IndicatorOrientation
/**
*
* @author zhangpan
* @date 2021/1/21
*/
@IntDef(
IndicatorOrientation.INDICATOR_HORIZONTAL, IndicatorOrientation.INDICATOR_VERTICAL,
IndicatorOrientation.INDICATOR_RTL
)
@kotlin.annotation.Retention(AnnotationRetention.SOURCE)
@Target(AnnotationTarget.VALUE_PARAMETER, AnnotationTarget.FIELD)
annotation class AIndicatorOrientation()

View File

@@ -0,0 +1,19 @@
package com.mogo.och.taxi.passenger.widget.indicator.annotation
import androidx.annotation.IntDef
import com.mogo.och.taxi.passenger.widget.indicator.enums.IndicatorSlideMode.Companion.COLOR
import com.mogo.och.taxi.passenger.widget.indicator.enums.IndicatorSlideMode.Companion.NORMAL
import com.mogo.och.taxi.passenger.widget.indicator.enums.IndicatorSlideMode.Companion.SCALE
import com.mogo.och.taxi.passenger.widget.indicator.enums.IndicatorSlideMode.Companion.SMOOTH
import com.mogo.och.taxi.passenger.widget.indicator.enums.IndicatorSlideMode.Companion.WORM
/**
* <pre>
* Created by zhangpan on 2019-10-18.
* Description:
</pre> *
*/
@IntDef(NORMAL, SMOOTH, WORM, COLOR, SCALE)
@kotlin.annotation.Retention(AnnotationRetention.SOURCE)
@Target(AnnotationTarget.VALUE_PARAMETER, AnnotationTarget.FIELD)
annotation class AIndicatorSlideMode

View File

@@ -0,0 +1,15 @@
package com.mogo.och.taxi.passenger.widget.indicator.annotation
import androidx.annotation.IntDef
import com.mogo.och.taxi.passenger.widget.indicator.enums.IndicatorStyle
/**
* <pre>
* Created by zhangpan on 2019-10-18.
* Description:
</pre> *
*/
@IntDef(IndicatorStyle.CIRCLE, IndicatorStyle.DASH, IndicatorStyle.ROUND_RECT)
@kotlin.annotation.Retention(AnnotationRetention.SOURCE)
@Target(AnnotationTarget.VALUE_PARAMETER, AnnotationTarget.FIELD)
annotation class AIndicatorStyle

View File

@@ -0,0 +1,205 @@
package com.zhpan.indicator.base
import android.content.Context
import android.util.AttributeSet
import android.view.View
import androidx.annotation.ColorInt
import androidx.recyclerview.widget.RecyclerView
import com.mogo.och.taxi.passenger.widget.indicator.annotation.AIndicatorSlideMode
import com.mogo.och.taxi.passenger.widget.indicator.annotation.AIndicatorStyle
import com.mogo.och.taxi.passenger.widget.indicator.base.IIndicator
import com.mogo.och.taxi.passenger.widget.indicator.enums.IndicatorSlideMode
import com.mogo.och.taxi.passenger.widget.indicator.option.IndicatorOptions
/**
* <pre>
* Created by zhangpan on 2019-09-04.
* Description:IndicatorView基类处理了页面滑动。
* </pre>
*/
@Suppress("UNUSED")
open class BaseIndicatorView constructor(
context: Context,
attrs: AttributeSet?,
defStyleAttr: Int
) : View(context, attrs, defStyleAttr), IIndicator {
var mIndicatorOptions: IndicatorOptions
private var recyclerView: RecyclerView? = null
init {
mIndicatorOptions = IndicatorOptions()
}
fun setPageSize(pageSize: Int): BaseIndicatorView {
mIndicatorOptions.pageSize = pageSize
return this
}
// 页面选定
fun onPageSelected(position: Int) {
if (getSlideMode() == IndicatorSlideMode.NORMAL) {
setCurrentPosition(position)
setSlideProgress(0f)
invalidate()
}
}
// 滚动距离
fun onPageScrolled(position: Int, positionOffset: Float, positionOffsetPixels: Int) {
if (getSlideMode() != IndicatorSlideMode.NORMAL && getPageSize() > 1) {
scrollSlider(position, positionOffset)
invalidate()
}
}
private fun scrollSlider(position: Int, positionOffset: Float) {
if (mIndicatorOptions.slideMode == IndicatorSlideMode.SCALE
|| mIndicatorOptions.slideMode == IndicatorSlideMode.COLOR) {
setCurrentPosition(position)
setSlideProgress(positionOffset)
} else {
if (position % getPageSize() == getPageSize() - 1) { // 最后一个页面与第一个页面
if (positionOffset < 0.5) {
setCurrentPosition(position)
setSlideProgress(0f)
} else {
setCurrentPosition(0)
setSlideProgress(0f)
}
} else { // 中间页面
setCurrentPosition(position)
setSlideProgress(positionOffset)
}
}
}
override fun notifyDataChanged(itemCount: Int) {
setPageSize(itemCount)
requestLayout()
invalidate()
}
private fun setupViewPager() {
}
fun getNormalSlideWidth(): Float {
return mIndicatorOptions.normalSliderWidth
}
fun setNormalSlideWidth(normalSliderWidth: Float) {
mIndicatorOptions.normalSliderWidth = normalSliderWidth
}
fun getCheckedSlideWidth(): Float {
return mIndicatorOptions.checkedSliderWidth
}
fun setCheckedSlideWidth(checkedSliderWidth: Float) {
mIndicatorOptions.checkedSliderWidth = checkedSliderWidth
}
val checkedSliderWidth: Float
get() = mIndicatorOptions.checkedSliderWidth
fun setCurrentPosition(currentPosition: Int) {
mIndicatorOptions.currentPosition = currentPosition
}
fun getCurrentPosition(): Int {
return mIndicatorOptions.currentPosition
}
fun getIndicatorGap(indicatorGap: Float) {
mIndicatorOptions.sliderGap = indicatorGap
}
fun setIndicatorGap(indicatorGap: Float) {
mIndicatorOptions.sliderGap = indicatorGap
}
fun setCheckedColor(@ColorInt normalColor: Int) {
mIndicatorOptions.checkedSliderColor = normalColor
}
fun getCheckedColor(): Int {
return mIndicatorOptions.checkedSliderColor
}
fun setNormalColor(@ColorInt normalColor: Int) {
mIndicatorOptions.normalSliderColor = normalColor
}
fun getSlideProgress(): Float {
return mIndicatorOptions.slideProgress
}
fun setSlideProgress(slideProgress: Float) {
mIndicatorOptions.slideProgress = slideProgress
}
fun getPageSize(): Int {
return mIndicatorOptions.pageSize
}
fun setSliderColor(
@ColorInt normalColor: Int,
@ColorInt selectedColor: Int,
@ColorInt selectedEndColor: Int
): BaseIndicatorView {
mIndicatorOptions.setSliderColor(normalColor, selectedColor,selectedEndColor)
return this
}
fun setSliderWidth(sliderWidth: Float): BaseIndicatorView {
mIndicatorOptions.setSliderWidth(sliderWidth)
return this
}
fun setSliderWidth(
normalSliderWidth: Float,
selectedSliderWidth: Float
): BaseIndicatorView {
mIndicatorOptions.setSliderWidth(normalSliderWidth, selectedSliderWidth)
return this
}
fun setSliderGap(sliderGap: Float): BaseIndicatorView {
mIndicatorOptions.sliderGap = sliderGap
return this
}
fun getSlideMode(): Int {
return mIndicatorOptions.slideMode
}
fun setSlideMode(@AIndicatorSlideMode slideMode: Int): BaseIndicatorView {
mIndicatorOptions.slideMode = slideMode
return this
}
fun setIndicatorStyle(@AIndicatorStyle indicatorStyle: Int): BaseIndicatorView {
mIndicatorOptions.indicatorStyle = indicatorStyle
return this
}
fun setSliderHeight(sliderHeight: Float): BaseIndicatorView {
mIndicatorOptions.sliderHeight = sliderHeight
return this
}
fun showIndicatorWhenOneItem(showIndicatorWhenOneItem: Boolean) {
mIndicatorOptions.showIndicatorOneItem = showIndicatorWhenOneItem
}
fun onPageScrollStateChanged(state: Int) {
}
override fun setIndicatorOptions(options: IndicatorOptions) {
mIndicatorOptions = options
}
}

View File

@@ -0,0 +1,16 @@
package com.mogo.och.taxi.passenger.widget.indicator.base
import com.mogo.och.taxi.passenger.widget.indicator.option.IndicatorOptions
/**
* <pre>
* Created by zhangpan on 2019-09-02.
* Description:
</pre> *
*/
interface IIndicator {
fun notifyDataChanged(itemCount:Int)
fun setIndicatorOptions(options: IndicatorOptions)
}

View File

@@ -0,0 +1,94 @@
package com.mogo.och.taxi.passenger.widget.indicator.drawer
import android.animation.ArgbEvaluator
import android.graphics.Paint
import com.mogo.och.taxi.passenger.widget.indicator.enums.IndicatorOrientation
import com.mogo.och.taxi.passenger.widget.indicator.enums.IndicatorSlideMode
import com.mogo.och.taxi.passenger.widget.indicator.option.IndicatorOptions
/**
* <pre>
* Created by zhpan on 2019/11/23.
* Description:
</pre> *
*/
abstract class BaseDrawer internal constructor(internal var mIndicatorOptions: IndicatorOptions) :
IDrawer {
private val mMeasureResult: MeasureResult
internal var maxWidth: Float = 0.toFloat()
internal var minWidth: Float = 0.toFloat()
internal var mPaint: Paint = Paint()
internal var argbEvaluator: ArgbEvaluator? = null
companion object {
const val INDICATOR_PADDING_ADDITION = 6
const val INDICATOR_PADDING = 3
}
protected val isWidthEquals: Boolean
get() = mIndicatorOptions.normalSliderWidth == mIndicatorOptions.checkedSliderWidth
init {
mPaint.isAntiAlias = true
mMeasureResult = MeasureResult()
if (mIndicatorOptions.slideMode == IndicatorSlideMode.SCALE
|| mIndicatorOptions.slideMode == IndicatorSlideMode.COLOR
) {
argbEvaluator = ArgbEvaluator()
}
}
override fun onMeasure(
widthMeasureSpec: Int,
heightMeasureSpec: Int
): MeasureResult {
maxWidth =
mIndicatorOptions.normalSliderWidth.coerceAtLeast(mIndicatorOptions.checkedSliderWidth)
minWidth =
mIndicatorOptions.normalSliderWidth.coerceAtMost(mIndicatorOptions.checkedSliderWidth)
if (mIndicatorOptions.orientation == IndicatorOrientation.INDICATOR_VERTICAL) {
mMeasureResult.setMeasureResult(measureHeight(), measureWidth())
} else {
mMeasureResult.setMeasureResult(measureWidth(), measureHeight())
}
return mMeasureResult
}
protected open fun measureHeight(): Int {
return mIndicatorOptions.sliderHeight.toInt() + INDICATOR_PADDING
}
private fun measureWidth(): Int {
val pageSize = mIndicatorOptions.pageSize
val indicatorGap = mIndicatorOptions.sliderGap
return ((pageSize - 1) * indicatorGap + maxWidth + (pageSize - 1) * minWidth).toInt() + INDICATOR_PADDING_ADDITION
}
override fun onLayout(
changed: Boolean,
left: Int,
top: Int,
right: Int,
bottom: Int
) {
}
inner class MeasureResult {
var measureWidth: Int = 0
internal set
var measureHeight: Int = 0
internal set
internal fun setMeasureResult(
measureWidth: Int,
measureHeight: Int
) {
this.measureWidth = measureWidth
this.measureHeight = measureHeight
}
}
}

View File

@@ -0,0 +1,157 @@
package com.mogo.och.taxi.passenger.widget.indicator.drawer
import android.graphics.Canvas
import android.graphics.RectF
import com.mogo.och.taxi.passenger.widget.indicator.enums.IndicatorSlideMode
import com.mogo.och.taxi.passenger.widget.indicator.option.IndicatorOptions
import com.mogo.och.taxi.passenger.widget.indicator.utils.IndicatorUtils
/**
* <pre>
* Created by zhpan on 2019/11/23.
* Description: Circle Indicator drawer.
</pre> *
*/
class CircleDrawer internal constructor(indicatorOptions: IndicatorOptions) : BaseDrawer(
indicatorOptions
) {
private val rectF = RectF()
override fun measureHeight(): Int {
return maxWidth.toInt() + INDICATOR_PADDING_ADDITION
}
override fun onDraw(canvas: Canvas) {
val pageSize = mIndicatorOptions.pageSize
if (pageSize > 1 || mIndicatorOptions.showIndicatorOneItem && pageSize == 1) {
drawNormal(canvas)
drawSlider(canvas)
}
}
private fun drawNormal(canvas: Canvas) {
val normalIndicatorWidth = mIndicatorOptions.normalSliderWidth
mPaint.color = mIndicatorOptions.normalSliderColor
for (i in 0 until mIndicatorOptions.pageSize) {
val coordinateX = IndicatorUtils.getCoordinateX(mIndicatorOptions, maxWidth, i)
val coordinateY = IndicatorUtils.getCoordinateY(maxWidth)
drawCircle(canvas, coordinateX, coordinateY, normalIndicatorWidth / 2)
}
}
private fun drawSlider(canvas: Canvas) {
mPaint.color = mIndicatorOptions.checkedSliderColor
when (mIndicatorOptions.slideMode) {
IndicatorSlideMode.NORMAL, IndicatorSlideMode.SMOOTH -> drawCircleSlider(canvas)
IndicatorSlideMode.WORM -> drawWormSlider(canvas)
IndicatorSlideMode.SCALE -> drawScaleSlider(canvas)
IndicatorSlideMode.COLOR -> drawColor(canvas)
}
}
private fun drawColor(canvas: Canvas) {
val currentPosition = mIndicatorOptions.currentPosition
val slideProgress = mIndicatorOptions.slideProgress
val coordinateX = IndicatorUtils.getCoordinateX(mIndicatorOptions, maxWidth, currentPosition)
val coordinateY = IndicatorUtils.getCoordinateY(maxWidth)
var evaluate = argbEvaluator?.evaluate(
slideProgress, mIndicatorOptions.checkedSliderColor, mIndicatorOptions.normalSliderColor
)
mPaint.color = (evaluate as Int)
drawCircle(canvas, coordinateX, coordinateY, mIndicatorOptions.normalSliderWidth / 2)
// 绘制可循环的ViewPager指示器渐变
evaluate = argbEvaluator?.evaluate(
1 - slideProgress, mIndicatorOptions.checkedSliderColor, mIndicatorOptions.normalSliderColor
)
mPaint.color = evaluate as Int
val nextCoordinateX = if (currentPosition == mIndicatorOptions.pageSize - 1) {
IndicatorUtils.getCoordinateX(mIndicatorOptions, maxWidth, 0)
} else {
coordinateX + mIndicatorOptions.sliderGap + mIndicatorOptions.normalSliderWidth
}
drawCircle(canvas, nextCoordinateX, coordinateY, mIndicatorOptions.checkedSliderWidth / 2)
}
private fun drawScaleSlider(canvas: Canvas) {
val currentPosition = mIndicatorOptions.currentPosition
val slideProgress = mIndicatorOptions.slideProgress
val coordinateX = IndicatorUtils.getCoordinateX(mIndicatorOptions, maxWidth, currentPosition)
val coordinateY = IndicatorUtils.getCoordinateY(maxWidth)
if (slideProgress < 1) {
val evaluate = argbEvaluator?.evaluate(
slideProgress, mIndicatorOptions.checkedSliderColor, mIndicatorOptions.normalSliderColor
)
mPaint.color = (evaluate as Int)
val radius =
mIndicatorOptions.checkedSliderWidth / 2 - (mIndicatorOptions.checkedSliderWidth / 2 - mIndicatorOptions.normalSliderWidth / 2) * slideProgress
drawCircle(canvas, coordinateX, coordinateY, radius)
}
if (currentPosition == mIndicatorOptions.pageSize - 1) {
val evaluate = argbEvaluator?.evaluate(
slideProgress, mIndicatorOptions.normalSliderColor, mIndicatorOptions.checkedSliderColor
)
mPaint.color = evaluate as Int
val nextCoordinateX = maxWidth / 2
val nextRadius = minWidth / 2 + (maxWidth / 2 - minWidth / 2) * (slideProgress)
drawCircle(canvas, nextCoordinateX, coordinateY, nextRadius)
} else {
if (slideProgress > 0) {
val evaluate = argbEvaluator?.evaluate(
slideProgress, mIndicatorOptions.normalSliderColor, mIndicatorOptions.checkedSliderColor
)
mPaint.color = evaluate as Int
val nextCoordinateX =
coordinateX + mIndicatorOptions.sliderGap + mIndicatorOptions.normalSliderWidth
val nextRadius =
mIndicatorOptions.normalSliderWidth / 2 + (mIndicatorOptions.checkedSliderWidth / 2 - mIndicatorOptions.normalSliderWidth / 2) * slideProgress
drawCircle(canvas, nextCoordinateX, coordinateY, nextRadius)
}
}
}
private fun drawCircleSlider(canvas: Canvas) {
val currentPosition = mIndicatorOptions.currentPosition
val startCoordinateX =
IndicatorUtils.getCoordinateX(mIndicatorOptions, maxWidth, currentPosition)
val endCoordinateX = IndicatorUtils.getCoordinateX(
mIndicatorOptions, maxWidth, (currentPosition + 1) % mIndicatorOptions.pageSize
)
val coordinateX =
startCoordinateX + (endCoordinateX - startCoordinateX) * mIndicatorOptions.slideProgress
val coordinateY = IndicatorUtils.getCoordinateY(maxWidth)
val radius = mIndicatorOptions.checkedSliderWidth / 2
drawCircle(canvas, coordinateX, coordinateY, radius)
}
private fun drawWormSlider(canvas: Canvas) {
val sliderHeight = mIndicatorOptions.normalSliderWidth
val slideProgress = mIndicatorOptions.slideProgress
val currentPosition = mIndicatorOptions.currentPosition
val distance = mIndicatorOptions.sliderGap + mIndicatorOptions.normalSliderWidth
val startCoordinateX =
IndicatorUtils.getCoordinateX(mIndicatorOptions, maxWidth, currentPosition)
val left = startCoordinateX + (distance * (slideProgress - 0.5f) * 2.0f).coerceAtLeast(
0f
) - mIndicatorOptions.normalSliderWidth / 2 + INDICATOR_PADDING
val right = startCoordinateX + (distance * slideProgress * 2f).coerceAtMost(
distance
) + mIndicatorOptions.normalSliderWidth / 2 + INDICATOR_PADDING
rectF.set(left, INDICATOR_PADDING.toFloat(), right, sliderHeight + INDICATOR_PADDING)
canvas.drawRoundRect(rectF, sliderHeight, sliderHeight, mPaint)
}
private fun drawCircle(
canvas: Canvas,
coordinateX: Float,
coordinateY: Float,
radius: Float
) {
canvas.drawCircle(
coordinateX + INDICATOR_PADDING, coordinateY + INDICATOR_PADDING, radius, mPaint
)
}
}

View File

@@ -0,0 +1,20 @@
package com.mogo.och.taxi.passenger.widget.indicator.drawer
import android.graphics.Canvas
import com.mogo.och.taxi.passenger.widget.indicator.option.IndicatorOptions
/**
* <pre>
* Created by zhpan on 2019/11/23.
* Description: Dash Indicator Drawer.
</pre> *
*/
class DashDrawer internal constructor(indicatorOptions: IndicatorOptions) : RectDrawer(
indicatorOptions
) {
override fun drawDash(canvas: Canvas) {
canvas.drawRect(mRectF, mPaint)
}
}

View File

@@ -0,0 +1,20 @@
package com.mogo.och.taxi.passenger.widget.indicator.drawer
import com.mogo.och.taxi.passenger.widget.indicator.enums.IndicatorStyle
import com.mogo.och.taxi.passenger.widget.indicator.option.IndicatorOptions
/**
* <pre>
* Created by zhpan on 2019/11/24.
* Description: Indicator Drawer Factory.
</pre> *
*/
internal object DrawerFactory {
fun createDrawer(indicatorOptions: IndicatorOptions): IDrawer {
return when (indicatorOptions.indicatorStyle) {
IndicatorStyle.DASH -> DashDrawer(indicatorOptions)
IndicatorStyle.ROUND_RECT -> RoundRectDrawer(indicatorOptions)
else -> CircleDrawer(indicatorOptions)
}
}
}

View File

@@ -0,0 +1,48 @@
package com.mogo.och.taxi.passenger.widget.indicator.drawer
import android.graphics.Canvas
import com.mogo.och.taxi.passenger.widget.indicator.option.IndicatorOptions
/**
* <pre>
* Created by zhpan on 2019/11/23.
* Description: Indicator Drawer Proxy.
</pre> *
*/
class DrawerProxy(indicatorOptions: IndicatorOptions) : IDrawer {
private lateinit var mIDrawer: IDrawer
init {
init(indicatorOptions)
}
private fun init(indicatorOptions: IndicatorOptions) {
mIDrawer = DrawerFactory.createDrawer(indicatorOptions)
}
override fun onLayout(
changed: Boolean,
left: Int,
top: Int,
right: Int,
bottom: Int
) {
}
override fun onMeasure(
widthMeasureSpec: Int,
heightMeasureSpec: Int
): BaseDrawer.MeasureResult {
return mIDrawer.onMeasure(widthMeasureSpec, heightMeasureSpec)
}
override fun onDraw(canvas: Canvas) {
mIDrawer.onDraw(canvas)
}
fun setIndicatorOptions(indicatorOptions: IndicatorOptions) {
init(indicatorOptions)
}
}

View File

@@ -0,0 +1,27 @@
package com.mogo.och.taxi.passenger.widget.indicator.drawer
import android.graphics.Canvas
/**
* <pre>
* Created by zhpan on 2019/11/23.
* Description:
</pre> *
*/
interface IDrawer {
fun onLayout(
changed: Boolean,
left: Int,
top: Int,
right: Int,
bottom: Int
)
fun onMeasure(
widthMeasureSpec: Int,
heightMeasureSpec: Int
): BaseDrawer.MeasureResult
fun onDraw(canvas: Canvas)
}

View File

@@ -0,0 +1,220 @@
package com.mogo.och.taxi.passenger.widget.indicator.drawer
import android.graphics.*
import com.mogo.och.taxi.passenger.widget.indicator.enums.IndicatorSlideMode
import com.mogo.och.taxi.passenger.widget.indicator.option.IndicatorOptions
import com.mogo.och.taxi.passenger.widget.indicator.utils.IndicatorUtils
/**
* <pre>
* Created by zhpan on 2020/1/17.
* Description:
</pre> *
*/
open class RectDrawer internal constructor(indicatorOptions: IndicatorOptions) : BaseDrawer(
indicatorOptions
) {
internal var mRectF: RectF = RectF()
override fun onDraw(canvas: Canvas) {
val pageSize = mIndicatorOptions.pageSize
if (pageSize > 1 || mIndicatorOptions.showIndicatorOneItem && pageSize == 1) {
if (isWidthEquals && mIndicatorOptions.slideMode != IndicatorSlideMode.NORMAL) {
drawUncheckedSlider(canvas, pageSize)
drawCheckedSlider(canvas)
} else { // 单独处理normalWidth与checkedWidth不一致的情况
if (mIndicatorOptions.slideMode == IndicatorSlideMode.SCALE) {
for (i in 0 until pageSize) {
drawScaleSlider(canvas, i)
}
} else {
drawInequalitySlider(canvas, pageSize)
}
}
}
}
private fun drawScaleSlider(
canvas: Canvas,
i: Int
) {
val checkedColor = mIndicatorOptions.checkedSliderColor
val indicatorGap = mIndicatorOptions.sliderGap
val sliderHeight = mIndicatorOptions.sliderHeight
val currentPosition = mIndicatorOptions.currentPosition
val normalWidth = mIndicatorOptions.normalSliderWidth
val checkedWidth = mIndicatorOptions.checkedSliderWidth
when {
i < currentPosition -> {
mPaint.color = mIndicatorOptions.normalSliderColor
val left: Float = if (currentPosition == mIndicatorOptions.pageSize - 1) {
(i * normalWidth + i * indicatorGap) + (checkedWidth - normalWidth) * mIndicatorOptions.slideProgress
} else {
(i * normalWidth + i * indicatorGap)
}
mRectF.set(left, 0f, left + normalWidth, sliderHeight)
drawRoundRect(canvas, sliderHeight, sliderHeight,mRectF.width()>checkedWidth/2)
}
i == currentPosition -> {
mPaint.color = checkedColor
val slideProgress = mIndicatorOptions.slideProgress
if (currentPosition == mIndicatorOptions.pageSize - 1) {
val evaluate = argbEvaluator?.evaluate(
slideProgress, checkedColor, mIndicatorOptions.normalSliderColor
)
mPaint.color = (evaluate as Int)
val right =
(mIndicatorOptions.pageSize - 1) * (normalWidth + mIndicatorOptions.sliderGap) + checkedWidth
val left = right - checkedWidth + (checkedWidth - normalWidth) * (slideProgress)
mRectF.set(left, 0f, right, sliderHeight)
drawRoundRect(canvas, sliderHeight, sliderHeight,mRectF.width()>checkedWidth/2)
} else {
if (slideProgress < 1) {
val evaluate = argbEvaluator?.evaluate(
slideProgress, checkedColor, mIndicatorOptions.normalSliderColor
)
mPaint.color = (evaluate as Int)
val left = i * normalWidth + i * indicatorGap
val right = left + normalWidth + (checkedWidth - normalWidth) * (1 - slideProgress)
mRectF.set(left, 0f, right, sliderHeight)
drawRoundRect(canvas, sliderHeight, sliderHeight,mRectF.width()>checkedWidth/2)
}
}
if (currentPosition == mIndicatorOptions.pageSize - 1) {
if (slideProgress > 0) {
val evaluate = argbEvaluator?.evaluate(
1 - slideProgress, checkedColor, mIndicatorOptions.normalSliderColor
)
mPaint.color = evaluate as Int
val left = 0f
val right = left + normalWidth + (checkedWidth - normalWidth) * slideProgress
mRectF.set(left, 0f, right, sliderHeight)
drawRoundRect(canvas, sliderHeight, sliderHeight,mRectF.width()>checkedWidth/2)
}
} else {
if (slideProgress > 0) {
val evaluate = argbEvaluator?.evaluate(
1 - slideProgress, checkedColor, mIndicatorOptions.normalSliderColor
)
mPaint.color = evaluate as Int
val right =
i * normalWidth + i * indicatorGap + normalWidth + (indicatorGap + checkedWidth)
val left = right - (normalWidth) - (checkedWidth - normalWidth) * (slideProgress)
mRectF.set(left, 0f, right, sliderHeight)
drawRoundRect(canvas, sliderHeight, sliderHeight,mRectF.width()>checkedWidth/2)
}
}
}
else -> {
if ((currentPosition + 1 != i || mIndicatorOptions.slideProgress == 0f)) { // 避免多余绘制
mPaint.color = mIndicatorOptions.normalSliderColor
val left = i * minWidth + i * indicatorGap + (checkedWidth - minWidth)
mRectF.set(left, 0f, left + minWidth, sliderHeight)
drawRoundRect(canvas, sliderHeight, sliderHeight,false)
}
}
}
}
private fun drawUncheckedSlider(
canvas: Canvas,
pageSize: Int
) {
for (i in 0 until pageSize) {
mPaint.color = mIndicatorOptions.normalSliderColor
val left = i * maxWidth + i * +mIndicatorOptions.sliderGap + (maxWidth - minWidth)
mRectF.set(left, 0f, left + minWidth, mIndicatorOptions.sliderHeight)
drawRoundRect(canvas, mIndicatorOptions.sliderHeight, mIndicatorOptions.sliderHeight,false)
}
}
private fun drawInequalitySlider(
canvas: Canvas,
pageSize: Int
) {
var left = 0f
for (i in 0 until pageSize) {
val sliderWidth = (if (i == mIndicatorOptions.currentPosition) maxWidth else minWidth)
mPaint.color =
if (i == mIndicatorOptions.currentPosition) mIndicatorOptions.checkedSliderColor else mIndicatorOptions.normalSliderColor
mRectF.set(left, 0f, left + sliderWidth, mIndicatorOptions.sliderHeight)
drawRoundRect(canvas, mIndicatorOptions.sliderHeight, mIndicatorOptions.sliderHeight,false)
left += sliderWidth + mIndicatorOptions.sliderGap
}
}
private fun drawCheckedSlider(canvas: Canvas) {
mPaint.color = mIndicatorOptions.checkedSliderColor
when (mIndicatorOptions.slideMode) {
IndicatorSlideMode.SMOOTH -> drawSmoothSlider(canvas)
IndicatorSlideMode.WORM -> drawWormSlider(canvas)
IndicatorSlideMode.COLOR -> drawColorSlider(canvas)
}
}
private fun drawColorSlider(canvas: Canvas) {
val currentPosition = mIndicatorOptions.currentPosition
val slideProgress = mIndicatorOptions.slideProgress
val left = currentPosition * minWidth + currentPosition * mIndicatorOptions.sliderGap
if (slideProgress < 0.99) {
val evaluate = argbEvaluator?.evaluate(
slideProgress, mIndicatorOptions.checkedSliderColor, mIndicatorOptions.normalSliderColor
)
mPaint.color = (evaluate as Int)
mRectF.set(left, 0f, left + minWidth, mIndicatorOptions.sliderHeight)
drawRoundRect(canvas, mIndicatorOptions.sliderHeight, mIndicatorOptions.sliderHeight,false)
}
var nextSliderLeft = left + mIndicatorOptions.sliderGap + mIndicatorOptions.normalSliderWidth
if (currentPosition == mIndicatorOptions.pageSize - 1) {
nextSliderLeft = 0f
}
val evaluate = argbEvaluator?.evaluate(
1 - slideProgress, mIndicatorOptions.checkedSliderColor, mIndicatorOptions.normalSliderColor
)
mPaint.color = evaluate as Int
mRectF.set(nextSliderLeft, 0f, nextSliderLeft + minWidth, mIndicatorOptions.sliderHeight)
drawRoundRect(canvas, mIndicatorOptions.sliderHeight, mIndicatorOptions.sliderHeight,false)
}
private fun drawWormSlider(canvas: Canvas) {
val sliderHeight = mIndicatorOptions.sliderHeight
val slideProgress = mIndicatorOptions.slideProgress
val currentPosition = mIndicatorOptions.currentPosition
val distance = mIndicatorOptions.sliderGap + mIndicatorOptions.normalSliderWidth
val startCoordinateX =
IndicatorUtils.getCoordinateX(mIndicatorOptions, maxWidth, currentPosition)
val left = startCoordinateX + (distance * (slideProgress - 0.5f) * 2.0f).coerceAtLeast(
0f
) - mIndicatorOptions.normalSliderWidth / 2
val right = startCoordinateX + (distance * slideProgress * 2f).coerceAtMost(
distance
) + mIndicatorOptions.normalSliderWidth / 2
mRectF.set(left, 0f, right, sliderHeight)
drawRoundRect(canvas, sliderHeight, sliderHeight,false)
}
private fun drawSmoothSlider(canvas: Canvas) {
val currentPosition = mIndicatorOptions.currentPosition
val indicatorGap = mIndicatorOptions.sliderGap
val sliderHeight = mIndicatorOptions.sliderHeight
val left =
currentPosition * maxWidth + currentPosition * +indicatorGap + (maxWidth + indicatorGap) * mIndicatorOptions.slideProgress
mRectF.set(left, 0f, left + maxWidth, sliderHeight)
drawRoundRect(canvas, sliderHeight, sliderHeight,false)
}
protected open fun drawRoundRect(
canvas: Canvas,
rx: Float,
ry: Float,
isWidth:Boolean
) {
drawDash(canvas)
}
protected open fun drawDash(canvas: Canvas) {}
}

View File

@@ -0,0 +1,38 @@
package com.mogo.och.taxi.passenger.widget.indicator.drawer
import android.graphics.Canvas
import android.graphics.LinearGradient
import android.graphics.Shader
import com.mogo.och.taxi.passenger.widget.indicator.option.IndicatorOptions
/**
* <pre>
* Created by zhpan on 2019/11/26.
* Description:
</pre> *
*/
class RoundRectDrawer internal constructor(val indicatorOptions: IndicatorOptions) : RectDrawer(
indicatorOptions
) {
override fun drawRoundRect(canvas: Canvas, rx: Float, ry: Float, isWidth: Boolean) {
if(isWidth) {
val linearGradient =
LinearGradient(
mRectF.left,
(mRectF.bottom - mRectF.top) / 2,
mRectF.right,
(mRectF.bottom - mRectF.top) / 2,
indicatorOptions.checkedSliderColor,
indicatorOptions.checkedEndSliderColor,
Shader.TileMode.CLAMP
)
mPaint.shader = linearGradient
}else{
mPaint.color = indicatorOptions.normalSliderColor
mPaint.shader = null
}
canvas.drawRoundRect(mRectF, rx, ry, mPaint)
}
}

View File

@@ -0,0 +1,16 @@
package com.mogo.och.taxi.passenger.widget.indicator.enums
import android.widget.LinearLayout
/**
*
* @author zhangpan
* @date 2021/1/21
*/
class IndicatorOrientation {
companion object {
const val INDICATOR_HORIZONTAL = LinearLayout.HORIZONTAL
const val INDICATOR_VERTICAL = LinearLayout.VERTICAL
const val INDICATOR_RTL = 3
}
}

View File

@@ -0,0 +1,17 @@
package com.mogo.och.taxi.passenger.widget.indicator.enums
/**
* <pre>
* Created by zhangpan on 2019-10-18.
* Description:
</pre> *
*/
interface IndicatorSlideMode {
companion object {
const val NORMAL = 0
const val SMOOTH = 2
const val WORM = 3
const val SCALE = 4
const val COLOR = 5
}
}

View File

@@ -0,0 +1,15 @@
package com.mogo.och.taxi.passenger.widget.indicator.enums
/**
* <pre>
* Created by zhangpan on 2019-10-18.
* Description:
</pre> *
*/
interface IndicatorStyle {
companion object {
const val CIRCLE = 0
const val DASH = 1 shl 1
const val ROUND_RECT = 1 shl 2
}
}

View File

@@ -0,0 +1,38 @@
package com.mogo.och.taxi.passenger.widget.indicator.option;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Color;
import android.util.AttributeSet;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.mogo.och.taxi.passenger.R;
import com.mogo.och.taxi.passenger.widget.indicator.utils.IndicatorUtils;
public class AttrsController {
public static void initAttrs(@NonNull Context context, @Nullable AttributeSet attrs,
IndicatorOptions indicatorOptions) {
if (attrs != null) {
TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.IndicatorView);
int indicatorSlideMode = typedArray.getInt(R.styleable.IndicatorView_vpi_slide_mode, 0);
int indicatorStyle = typedArray.getInt(R.styleable.IndicatorView_vpi_style, 0);
int checkedColor = typedArray.getColor(R.styleable.IndicatorView_vpi_slider_checked_color,
Color.parseColor("#6C6D72"));
int normalColor = typedArray.getColor(R.styleable.IndicatorView_vpi_slider_normal_color,
Color.parseColor("#8C18171C"));
int orientation = typedArray.getInt(R.styleable.IndicatorView_vpi_orientation, 0);
float radius = typedArray.getDimension(R.styleable.IndicatorView_vpi_slider_radius,
IndicatorUtils.dp2px(8));
indicatorOptions.setCheckedColor(checkedColor);
indicatorOptions.setNormalSliderColor(normalColor);
indicatorOptions.setOrientation(orientation);
indicatorOptions.setIndicatorStyle(indicatorStyle);
indicatorOptions.setSlideMode(indicatorSlideMode);
indicatorOptions.setSliderWidth(radius * 2, radius * 2);
typedArray.recycle();
}
}
}

View File

@@ -0,0 +1,114 @@
package com.mogo.och.taxi.passenger.widget.indicator.option
import android.graphics.Color
import com.mogo.och.taxi.passenger.widget.indicator.annotation.AIndicatorOrientation
import com.mogo.och.taxi.passenger.widget.indicator.annotation.AIndicatorSlideMode
import com.mogo.och.taxi.passenger.widget.indicator.annotation.AIndicatorStyle
import com.mogo.och.taxi.passenger.widget.indicator.enums.IndicatorOrientation
import com.mogo.och.taxi.passenger.widget.indicator.enums.IndicatorSlideMode
import com.mogo.och.taxi.passenger.widget.indicator.utils.IndicatorUtils
/**
* <pre>
* Created by zhpan on 2019/11/20.
* Description:Indicator的配置参数
</pre> *
*/
class IndicatorOptions {
@AIndicatorOrientation
var orientation = IndicatorOrientation.INDICATOR_HORIZONTAL
@AIndicatorStyle
var indicatorStyle: Int = 0
/**
* Indicator滑动模式目前仅支持两种
*
* @see IndicatorSlideMode.NORMAL
*
* @see IndicatorSlideMode.SMOOTH
*/
@AIndicatorSlideMode
var slideMode: Int = 0
/**
* 页面size
*/
var pageSize: Int = 0
/**
* 未选中时Indicator颜色
*/
var normalSliderColor: Int = 0
/**
* 选中时Indicator颜色
*/
var checkedSliderColor: Int = 0
/**
* 选中时IndicatorEnd颜色
*/
var checkedEndSliderColor: Int = 0
/**
* Indicator间距
*/
var sliderGap: Float = 0f
var sliderHeight: Float = 0f
get() = if (field > 0) field else normalSliderWidth / 2
var normalSliderWidth: Float = 0f
var checkedSliderWidth: Float = 0f
/**
* 指示器当前位置
*/
var currentPosition: Int = 0
/**
* 从一个点滑动到另一个点的进度
*/
var slideProgress: Float = 0f
var showIndicatorOneItem: Boolean = false
init {
normalSliderWidth = IndicatorUtils.dp2px(8f)
.toFloat()
checkedSliderWidth = normalSliderWidth
sliderGap = normalSliderWidth
normalSliderColor = Color.parseColor("#8C18171C")
checkedSliderColor = Color.parseColor("#8C6C6D72")
slideMode = IndicatorSlideMode.NORMAL
}
fun setCheckedColor(checkedColor: Int) {
this.checkedSliderColor = checkedColor
}
fun setSliderWidth(
normalIndicatorWidth: Float,
checkedIndicatorWidth: Float
) {
this.normalSliderWidth = normalIndicatorWidth
this.checkedSliderWidth = checkedIndicatorWidth
}
fun setSliderWidth(sliderWidth: Float) {
setSliderWidth(sliderWidth, sliderWidth)
}
fun setSliderColor(
normalColor: Int,
checkedColor: Int,
selectedEndColor: Int
) {
this.normalSliderColor = normalColor
this.checkedSliderColor = checkedColor
this.checkedEndSliderColor = selectedEndColor
}
}

View File

@@ -0,0 +1,32 @@
package com.mogo.och.taxi.passenger.widget.indicator.utils
import android.content.res.Resources
import com.mogo.och.taxi.passenger.widget.indicator.option.IndicatorOptions
/**
* <pre>
* Created by zhangpan on 2020-01-20.
* Description:
</pre> *
*/
object IndicatorUtils {
@JvmStatic
fun dp2px(dpValue: Float): Int {
return (0.5f + dpValue * Resources.getSystem().displayMetrics.density).toInt()
}
fun getCoordinateX(
indicatorOptions: IndicatorOptions,
maxDiameter: Float,
index: Int
): Float {
val normalIndicatorWidth = indicatorOptions.normalSliderWidth
return maxDiameter / 2 + (normalIndicatorWidth + indicatorOptions.sliderGap) * index
}
fun getCoordinateY(maxDiameter: Float): Float {
return maxDiameter / 2
}
}

View File

@@ -0,0 +1,3 @@
<?xml version="1.0" encoding="utf-8"?>
<alpha xmlns:android="http://schemas.android.com/apk/res/android"
android:duration="1000" android:fromAlpha="0" android:toAlpha="1" />

View File

@@ -0,0 +1,3 @@
<?xml version="1.0" encoding="utf-8"?>
<alpha xmlns:android="http://schemas.android.com/apk/res/android"
android:duration="1000" android:fromAlpha="1" android:toAlpha="0" />

View File

@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android" android:startOffset="500" >
<translate
android:duration="142"
android:fromXDelta="-110%"
android:toXDelta="0"/>
<alpha android:fromAlpha="0.0" android:toAlpha="1.0" android:duration="142" />
</set>

View File

@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android" android:startOffset="500" >
<translate
android:duration="142"
android:fromXDelta="0"
android:toXDelta="-110%"/>
<alpha android:fromAlpha="1.0" android:toAlpha="0.0" android:duration="142" />
</set>

View File

@@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android">
<objectAnimator android:propertyName="alpha" android:duration="1000" android:valueFrom="1.0" android:valueTo="0.0"/>
</set>

View File

@@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android">
<objectAnimator android:propertyName="alpha" android:duration="1000" android:valueFrom="0.0" android:valueTo="1.0"/>
</set>

View File

@@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:color="@color/taxi_autopilot_text_color_normal"/>
</selector>

View File

@@ -0,0 +1,26 @@
<?xml version="1.0" encoding="utf-8"?>
<layer-list
xmlns:android="http://schemas.android.com/apk/res/android">
<item android:id="@android:id/background">
<shape>
<corners android:radius="5dp"/>
<solid android:color="@android:color/transparent" />
</shape>
</item>
<item android:id="@android:id/secondaryProgress">
<clip>
<shape>
<corners android:radius="5dp"/>
<solid android:color="@android:color/transparent" />
</shape>
</clip>
</item>
<item android:id="@android:id/progress">
<scale android:scaleWidth="100%">
<shape android:shape="rectangle" >
<corners android:radius="7dp"/>
<gradient android:startColor="@android:color/transparent" android:endColor="#54D7FF" android:angle="0"/>
</shape>
</scale>
</item>
</layer-list>

View File

@@ -0,0 +1,26 @@
<?xml version="1.0" encoding="utf-8"?>
<layer-list
xmlns:android="http://schemas.android.com/apk/res/android">
<item android:id="@android:id/background">
<shape>
<corners android:radius="5dp"/>
<solid android:color="#99D8D8D8" />
</shape>
</item>
<item android:id="@android:id/secondaryProgress">
<clip>
<shape>
<corners android:radius="5dp"/>
<solid android:color="#66FFFFFF" />
</shape>
</clip>
</item>
<item android:id="@android:id/progress">
<scale android:scaleWidth="100%">
<shape android:shape="rectangle">
<corners android:radius="5dp"/>
<gradient android:startColor="#303CFF" android:centerColor="#216CFF" android:endColor="#25C1F9" android:angle="0"/>
</shape>
</scale>
</item>
</layer-list>

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 897 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 450 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 851 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 339 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 101 KiB

Some files were not shown because too many files have changed in this diff Show More