添加安卓端基本功能

This commit is contained in:
kerwincui
2021-05-19 15:46:10 +08:00
parent 6ae09fc6dc
commit a7c6f99c6f
260 changed files with 16889 additions and 0 deletions

View File

@@ -0,0 +1,44 @@
/*
* Copyright (C) 2021 xuexiangjys(xuexiangjys@163.com)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
package com.kerwin.wumei;
import android.content.Context;
import androidx.test.platform.app.InstrumentationRegistry;
import androidx.test.runner.AndroidJUnit4;
import org.junit.Test;
import org.junit.runner.RunWith;
import static org.junit.Assert.*;
/**
* Instrumented test, which will execute on an Android device.
*
* @see <a href="http://d.android.com/tools/testing">Testing documentation</a>
*/
@RunWith(AndroidJUnit4.class)
public class ExampleInstrumentedTest {
@Test
public void useAppContext() {
// Context of the app under test.
Context appContext = InstrumentationRegistry.getInstrumentation().getTargetContext();
assertEquals("com.kerwin.wumei", appContext.getPackageName());
}
}

View File

@@ -0,0 +1,147 @@
<?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.kerwin.wumei">
<!--进程杀死-->
<uses-permission android:name="android.permission.KILL_BACKGROUND_PROCESSES" />
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.CHANGE_WIFI_MULTICAST_STATE" />
<uses-permission android:name="android.permission.CHANGE_WIFI_STATE" />
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
<uses-permission android:name="android.permission.VIBRATE"/>
<application
android:name="com.kerwin.wumei.MyApp"
android:allowBackup="false"
android:configChanges="screenSize|keyboardHidden|orientation|keyboard"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:networkSecurityConfig="@xml/network_security_config"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/AppTheme"
android:windowSoftInputMode="adjustPan|stateHidden"
tools:ignore="LockedOrientationActivity"
tools:targetApi="n">
<activity
android:name="com.kerwin.wumei.activity.SplashActivity"
android:configChanges="screenSize|keyboardHidden|orientation|keyboard"
android:screenOrientation="portrait"
android:theme="@style/AppTheme.Launch.App"
android:windowSoftInputMode="adjustPan|stateHidden">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<activity
android:name="com.kerwin.wumei.activity.MainActivity"
android:configChanges="screenSize|keyboardHidden|orientation|keyboard"
android:screenOrientation="portrait"
android:theme="@style/AppTheme"
android:windowSoftInputMode="adjustPan|stateHidden" />
<activity
android:name="com.kerwin.wumei.activity.LoginActivity"
android:configChanges="screenSize|keyboardHidden|orientation|keyboard"
android:launchMode="singleInstance"
android:screenOrientation="portrait"
android:windowSoftInputMode="adjustPan|stateHidden" />
<activity
android:name="com.kerwin.wumei.activity.AddDeviceActivity"
android:configChanges="screenSize|keyboardHidden|orientation|keyboard"
android:screenOrientation="portrait"
android:launchMode="singleInstance"
android:theme="@style/AppTheme"
android:windowSoftInputMode="adjustPan|stateHidden" />
<!--通用浏览器-->
<activity
android:name="com.kerwin.wumei.core.webview.AgentWebActivity"
android:configChanges="screenSize|keyboardHidden|orientation|keyboard"
android:hardwareAccelerated="true"
android:label="@string/app_browser_name"
android:theme="@style/AppTheme">
<!-- Scheme -->
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<action android:name="com.xuexiang.xui.applink" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="http" />
<data android:scheme="https" />
<data android:scheme="about" />
<data android:scheme="javascript" />
<!-- 设置自己的deeplink -->
<!-- <data-->
<!-- android:host="xxx.com"-->
<!-- android:scheme="xui"/>-->
</intent-filter>
<!-- AppLink -->
<intent-filter
android:autoVerify="true"
tools:targetApi="m">
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.BROWSABLE" />
<category android:name="android.intent.category.DEFAULT" />
<data android:scheme="http" />
<data android:scheme="https" />
<data android:scheme="inline" />
<data android:mimeType="text/html" />
<data android:mimeType="text/plain" />
<data android:mimeType="application/xhtml+xml" />
<data android:mimeType="application/vnd.wap.xhtml+xml" />
<!-- 设置自己的applink -->
<!-- <data-->
<!-- android:host="xxx.com"-->
<!-- android:scheme="http"/>-->
<!-- <data-->
<!-- android:host="xxx.com"-->
<!-- android:scheme="https"/>-->
</intent-filter>
</activity>
<!--fragment的页面容器-->
<activity
android:name="com.kerwin.wumei.core.BaseActivity"
android:configChanges="screenSize|keyboardHidden|orientation|keyboard"
android:screenOrientation="portrait"
android:windowSoftInputMode="adjustPan|stateHidden" />
<!-- 版本更新提示-->
<activity
android:name="com.kerwin.wumei.utils.update.UpdateTipDialog"
android:screenOrientation="portrait"
android:theme="@style/DialogTheme" />
<!-- Webview拦截提示弹窗-->
<activity
android:name="com.kerwin.wumei.core.webview.WebViewInterceptDialog"
android:screenOrientation="portrait"
android:theme="@style/DialogTheme" />
<!-- applink的中转页面 -->
<activity
android:name="com.kerwin.wumei.core.XPageTransferActivity"
android:configChanges="screenSize|keyboardHidden|orientation|keyboard"
android:screenOrientation="portrait"
android:windowSoftInputMode="adjustPan|stateHidden" />
<!--屏幕自适应设计图-->
<meta-data
android:name="design_width_in_dp"
android:value="360" />
<meta-data
android:name="design_height_in_dp"
android:value="640" />
</application>
</manifest>

View File

@@ -0,0 +1,21 @@
{
"Code": 0,
"Data": [
{
"title": "微信公众号",
"content": "<a href=\"https://t.1yb.co/71xZ\">获取更多资讯内容,欢迎微信搜索公众号:「我的Android开源之旅」</a>"
},
{
"title": "关于作者",
"content": "点击关注作者,了解最新动态!<br /><a href=\"https://github.com/xuexiangjys\"><font color=\"#800080\">Github</font></a><br />\n<a href=\"https://www.zhihu.com/people/xuexiangjys/posts\"><font color=\"#0000FF\">知乎</font></a><br />\n<a href=\"https://juejin.im/user/598feef55188257d592e56ed/posts\"><font color=\"#000000\">掘金</font></a><br /><a href=\"https://www.jianshu.com/u/6bf605575337\"><font color=\"#FF0000\">简书</font></a><br />\n<a href=\"https://segmentfault.com/blog/openAndroidX\"><font color=\"#008000\">思否</font></a><br />\n<a href=\"https://space.bilibili.com/483850585/video\"><font color=\"#FFA500\">哔哩哔哩</font></a><br />\n<a href=\"https://www.toutiao.com/c/user/token/MS4wLjABAAAAqD0Pe01AhT2Hgi3w7HzboVuq57gntoAJJURwAkM3Elv0-EA9WSKUSy1DujIYLAEm/\"><font color=\"#FF0000\">今日头条</font></a>"
},
{
"title": "赞助作者",
"content": "你的打赏是我维护的动力,<a href=\"https://gitee.com/xuexiangjys/Resource/blob/master/doc/sponsor.md\"><font color=\"#800080\">点击此处支持我吧!</font></a>"
},
{
"title": "QQ交流群",
"content": "<a href=\"https://qm.qq.com/cgi-bin/qm/qr?k=i1rhgvRxlJRlJzigL0QemICtkk0H6g3J\">XUI开源交流1号群</a><br /><a href=\"https://qm.qq.com/cgi-bin/qm/qr?k=_0IhSCvLxdsgxAIBtTXr__bwsxZl4Eva\">XUI开源交流2号群</a><br /><a href=\"https://qm.qq.com/cgi-bin/qm/qr?k=GRVNTA4ehFuIUhQwqiZkjqYeQoxRBqCI\">AndroidGitHub开源交流群</a><br /><a href=\"https://qm.qq.com/cgi-bin/qm/qr?k=0OHp7jgMiF9SsfrzmOaxcauxMP9fZjc3\">XUpdate官方交流群</a>"
}
]
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 31 KiB

View File

@@ -0,0 +1,114 @@
package com.kerwin.wumei;
import android.app.Application;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.location.LocationManager;
import android.net.wifi.WifiManager;
import android.os.Build;
import androidx.lifecycle.LifecycleOwner;
import androidx.lifecycle.MutableLiveData;
import androidx.lifecycle.Observer;
import androidx.multidex.MultiDex;
import com.kerwin.wumei.BuildConfig;
import com.kerwin.wumei.utils.sdkinit.ANRWatchDogInit;
import com.kerwin.wumei.utils.sdkinit.UMengInit;
import com.kerwin.wumei.utils.sdkinit.XBasicLibInit;
import com.kerwin.wumei.utils.sdkinit.XUpdateInit;
import java.util.HashMap;
import java.util.Map;
/**
* @author xuexiang
* @since 2018/11/7 下午1:12
*/
public class MyApp extends Application {
private static MyApp app;
private MutableLiveData<String> mBroadcastData;
private BroadcastReceiver mReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
String action = intent.getAction();
if (action == null) {
return;
}
switch (action) {
case WifiManager.NETWORK_STATE_CHANGED_ACTION:
case LocationManager.PROVIDERS_CHANGED_ACTION:
mBroadcastData.setValue(action);
break;
}
}
};
@Override
protected void attachBaseContext(Context base) {
super.attachBaseContext(base);
//解决4.x运行崩溃的问题
MultiDex.install(this);
}
@Override
public void onCreate() {
super.onCreate();
initLibs();
app = this;
mBroadcastData = new MutableLiveData<>();
IntentFilter filter = new IntentFilter(WifiManager.NETWORK_STATE_CHANGED_ACTION);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
filter.addAction(LocationManager.PROVIDERS_CHANGED_ACTION);
}
registerReceiver(mReceiver, filter);
}
@Override
public void onTerminate() {
super.onTerminate();
unregisterReceiver(mReceiver);
}
public static MyApp getInstance() {
return app;
}
public void observeBroadcast(LifecycleOwner owner, Observer<String> observer) {
mBroadcastData.observe(owner, observer);
}
/**
* 初始化基础库
*/
private void initLibs() {
XBasicLibInit.init(this);
XUpdateInit.init(this);
//运营统计数据运行时不初始化
if (!MyApp.isDebug()) {
UMengInit.init(this);
}
//ANR监控
ANRWatchDogInit.init();
}
/**
* @return 当前app是否是调试开发模式
*/
public static boolean isDebug() {
return BuildConfig.DEBUG;
}
}

View File

@@ -0,0 +1,410 @@
package com.kerwin.wumei.activity;
import android.Manifest;
import android.app.Activity;
import android.app.ProgressDialog;
import android.content.Context;
import android.content.DialogInterface;
import android.content.pm.PackageManager;
import android.location.LocationManager;
import android.net.wifi.ScanResult;
import android.net.wifi.WifiInfo;
import android.net.wifi.WifiManager;
import android.os.AsyncTask;
import android.os.Build;
import android.os.Bundle;
import android.text.Spannable;
import android.text.SpannableString;
import android.text.SpannableStringBuilder;
import android.text.style.ForegroundColorSpan;
import android.util.Log;
import android.view.KeyEvent;
import android.widget.Toast;
import androidx.annotation.NonNull;
import androidx.appcompat.app.AlertDialog;
import androidx.core.location.LocationManagerCompat;
import com.espressif.iot.esptouch.EsptouchTask;
import com.espressif.iot.esptouch.IEsptouchResult;
import com.espressif.iot.esptouch.IEsptouchTask;
import com.espressif.iot.esptouch.util.ByteUtil;
import com.espressif.iot.esptouch.util.TouchNetUtil;
import com.kerwin.wumei.R;
import com.kerwin.wumei.adapter.entity.EspTouchViewModel;
import com.kerwin.wumei.core.BaseActivity;
import com.kerwin.wumei.fragment.LoginFragment;
import com.kerwin.wumei.utils.NetUtils;
import com.xuexiang.xui.utils.KeyboardUtils;
import com.xuexiang.xui.utils.StatusBarUtils;
import com.xuexiang.xutil.display.Colors;
import java.lang.ref.WeakReference;
import java.net.InetAddress;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
public class AddDeviceActivity extends BaseActivity {
// begin esptouch -------------------------------------
private static final String TAG = AddDeviceActivity.class.getSimpleName();
private static final int REQUEST_PERMISSION = 0x01;
private EspTouchViewModel mViewModel;
private EsptouchAsyncTask4 mTask;
private WifiManager mWifiManager;
private List<String> ssids;
private String selectedSSID;
public String GetSelectedSSID(){
return selectedSSID;
}
public List<String> GetSsids(){
return ssids;
}
public EspTouchViewModel GetMViewModel(){
return mViewModel;
}
public void executeEsptouch() {
EspTouchViewModel viewModel = mViewModel;
// byte[] ssid = viewModel.ssidBytes == null ? ByteUtil.getBytesByString(viewModel.ssid): viewModel.ssidBytes;
CharSequence ssidStr=mViewModel.ssidSpinner.getText();
byte[] ssid= ByteUtil.getBytesByString(ssidStr.toString());
CharSequence pwdStr = mViewModel.apPasswordEdit.getText();
byte[] password = pwdStr == null ? null : ByteUtil.getBytesByString(pwdStr.toString());
byte[] bssid = TouchNetUtil.parseBssid2bytes(viewModel.bssid);
byte[] broadcast = {(byte) (mViewModel.packageModeGroup.getCheckedRadioButtonId() == R.id.packageBroadcast? 1 : 0)};
byte[] deviceCount = "1".getBytes();
if (mTask != null) {
mTask.cancelEsptouch();
}
mTask = new EsptouchAsyncTask4(this);
mTask.execute(ssid, bssid, password, deviceCount, broadcast);
}
public void onWifiChanged() {
StateResult stateResult = check();
mViewModel.message = stateResult.message;
mViewModel.ssid = stateResult.ssid;
mViewModel.ssidBytes = stateResult.ssidBytes;
mViewModel.bssid = stateResult.bssid;
mViewModel.confirmEnable = false;
if (stateResult.wifiConnected) {
mViewModel.confirmEnable = true;
if (stateResult.is5G) {
mViewModel.message = getString(R.string.esptouch1_wifi_5g_message);
}
} else {
if (mTask != null) {
mTask.cancelEsptouch();
mTask = null;
new AlertDialog.Builder(AddDeviceActivity.this)
.setMessage(R.string.esptouch1_configure_wifi_change_message)
.setNegativeButton(android.R.string.cancel, null)
.show();
}
}
mViewModel.invalidateAll();
}
protected static class StateResult {
public CharSequence message = null;
public boolean permissionGranted = false;
public boolean locationRequirement = false;
public boolean wifiConnected = false;
public boolean is5G = false;
public InetAddress address = null;
public String ssid = null;
public byte[] ssidBytes = null;
public String bssid = null;
}
private StateResult check() {
StateResult result = checkPermission();
if (!result.permissionGranted) {
return result;
}
result = checkLocation();
result.permissionGranted = true;
if (result.locationRequirement) {
return result;
}
result = checkWifi();
result.permissionGranted = true;
result.locationRequirement = false;
return result;
}
protected StateResult checkWifi() {
StateResult result = new StateResult();
result.wifiConnected = false;
WifiInfo wifiInfo = mWifiManager.getConnectionInfo();
// 获取wifi列表
mWifiManager.startScan();
List<ScanResult> scanWifiList = mWifiManager.getScanResults();
List<ScanResult> wifiList = new ArrayList<>();
ssids=new ArrayList<>();
if (scanWifiList != null && scanWifiList.size() > 0) {
HashMap<String, Integer> signalStrength = new HashMap<String, Integer>();
for (int i = 0; i < scanWifiList.size(); i++) {
ScanResult scanResult = scanWifiList.get(i);
Log.e(TAG, "搜索的wifi-ssid:" + scanResult.SSID);
if (!scanResult.SSID.isEmpty()) {
String key = scanResult.SSID + " " + scanResult.capabilities;
if (!signalStrength.containsKey(key)) {
signalStrength.put(key, i);
wifiList.add(scanResult);
ssids.add(scanResult.SSID);
}
}
}
}
boolean connected = NetUtils.isWifiConnected(mWifiManager);
if (!connected) {
result.message = getString(R.string.esptouch_message_wifi_connection);
return result;
}
String ssid = NetUtils.getSsidString(wifiInfo);
selectedSSID=ssid;
int ipValue = wifiInfo.getIpAddress();
if (ipValue != 0) {
result.address = NetUtils.getAddress(wifiInfo.getIpAddress());
} else {
result.address = NetUtils.getIPv4Address();
if (result.address == null) {
result.address = NetUtils.getIPv6Address();
}
}
result.wifiConnected = true;
result.message = "";
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
result.is5G = NetUtils.is5G(wifiInfo.getFrequency());
}
if (result.is5G) {
result.message = getString(R.string.esptouch_message_wifi_frequency);
}
result.ssid = ssid;
result.ssidBytes = NetUtils.getRawSsidBytesOrElse(wifiInfo, ssid.getBytes());
result.bssid = wifiInfo.getBSSID();
return result;
}
protected StateResult checkLocation() {
StateResult result = new StateResult();
result.locationRequirement = true;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
LocationManager manager = getSystemService(LocationManager.class);
boolean enable = manager != null && LocationManagerCompat.isLocationEnabled(manager);
if (!enable) {
result.message = getString(R.string.esptouch_message_location);
return result;
}
}
result.locationRequirement = false;
return result;
}
protected StateResult checkPermission() {
StateResult result = new StateResult();
result.permissionGranted = false;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
boolean locationGranted = checkSelfPermission(Manifest.permission.ACCESS_FINE_LOCATION)
== PackageManager.PERMISSION_GRANTED;
if (!locationGranted) {
String[] splits = getString(R.string.esptouch_message_permission).split("\n");
if (splits.length != 2) {
throw new IllegalArgumentException("Invalid String @RES esptouch_message_permission");
}
SpannableStringBuilder ssb = new SpannableStringBuilder(splits[0]);
ssb.append('\n');
SpannableString clickMsg = new SpannableString(splits[1]);
ForegroundColorSpan clickSpan = new ForegroundColorSpan(0xFF0022FF);
clickMsg.setSpan(clickSpan, 0, clickMsg.length(), Spannable.SPAN_INCLUSIVE_INCLUSIVE);
ssb.append(clickMsg);
result.message = ssb;
return result;
}
}
result.permissionGranted = true;
return result;
}
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
if (requestCode == REQUEST_PERMISSION) {
if (grantResults[0] == PackageManager.PERMISSION_GRANTED) {
onWifiChanged();
} else {
new AlertDialog.Builder(this)
.setTitle(R.string.esptouch1_location_permission_title)
.setMessage(R.string.esptouch1_location_permission_message)
.setCancelable(false)
.setPositiveButton(android.R.string.ok, (dialog, which) -> finish())
.show();
}
return;
}
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
}
public static class EsptouchAsyncTask4 extends AsyncTask<byte[], IEsptouchResult, List<IEsptouchResult>> {
private WeakReference<AddDeviceActivity> mActivity;
private final Object mLock = new Object();
private ProgressDialog mProgressDialog;
private AlertDialog mResultDialog;
private IEsptouchTask mEsptouchTask;
EsptouchAsyncTask4(AddDeviceActivity activity) {
mActivity = new WeakReference<>(activity);
}
public void cancelEsptouch() {
cancel(true);
if (mProgressDialog != null) {
mProgressDialog.dismiss();
}
if (mResultDialog != null) {
mResultDialog.dismiss();
}
if (mEsptouchTask != null) {
mEsptouchTask.interrupt();
}
}
@Override
protected void onPreExecute() {
Activity activity = mActivity.get();
mProgressDialog = new ProgressDialog(activity);
mProgressDialog.setMessage(activity.getString(R.string.esptouch1_configuring_message));
mProgressDialog.setCanceledOnTouchOutside(false);
mProgressDialog.setOnCancelListener(dialog -> {
synchronized (mLock) {
if (mEsptouchTask != null) {
mEsptouchTask.interrupt();
}
}
});
mProgressDialog.setButton(DialogInterface.BUTTON_NEGATIVE, activity.getText(android.R.string.cancel),
(dialog, which) -> {
synchronized (mLock) {
if (mEsptouchTask != null) {
mEsptouchTask.interrupt();
}
}
});
mProgressDialog.show();
}
@Override
protected void onProgressUpdate(IEsptouchResult... values) {
Context context = mActivity.get();
if (context != null) {
IEsptouchResult result = values[0];
Log.i(TAG, "EspTouchResult: " + result);
String text = result.getBssid() + " is connected to the wifi";
Toast.makeText(context, text, Toast.LENGTH_SHORT).show();
}
}
@Override
protected List<IEsptouchResult> doInBackground(byte[]... params) {
AddDeviceActivity activity = mActivity.get();
int taskResultCount;
synchronized (mLock) {
byte[] apSsid = params[0];
byte[] apBssid = params[1];
byte[] apPassword = params[2];
byte[] deviceCountData = params[3];
byte[] broadcastData = params[4];
taskResultCount = deviceCountData.length == 0 ? -1 : Integer.parseInt(new String(deviceCountData));
Context context = activity.getApplicationContext();
mEsptouchTask = new EsptouchTask(apSsid, apBssid, apPassword, context);
mEsptouchTask.setPackageBroadcast(broadcastData[0] == 1);
mEsptouchTask.setEsptouchListener(this::publishProgress);
}
return mEsptouchTask.executeForResults(taskResultCount);
}
@Override
protected void onPostExecute(List<IEsptouchResult> result) {
AddDeviceActivity activity = mActivity.get();
activity.mTask = null;
mProgressDialog.dismiss();
if (result == null) {
mResultDialog = new AlertDialog.Builder(activity)
.setMessage(R.string.esptouch1_configure_result_failed_port)
.setPositiveButton(android.R.string.ok, null)
.show();
mResultDialog.setCanceledOnTouchOutside(false);
return;
}
// check whether the task is cancelled and no results received
IEsptouchResult firstResult = result.get(0);
if (firstResult.isCancelled()) {
return;
}
// the task received some results including cancelled while
// executing before receiving enough results
if (!firstResult.isSuc()) {
mResultDialog = new AlertDialog.Builder(activity)
.setMessage(R.string.esptouch1_configure_result_failed)
.setPositiveButton(android.R.string.ok, null)
.show();
mResultDialog.setCanceledOnTouchOutside(false);
return;
}
ArrayList<CharSequence> resultMsgList = new ArrayList<>(result.size());
for (IEsptouchResult touchResult : result) {
String message = activity.getString(R.string.esptouch1_configure_result_success_item,
touchResult.getBssid(), touchResult.getInetAddress().getHostAddress());
resultMsgList.add(message);
}
CharSequence[] items = new CharSequence[resultMsgList.size()];
mResultDialog = new AlertDialog.Builder(activity)
.setTitle(R.string.esptouch1_configure_result_success)
.setItems(resultMsgList.toArray(items), null)
.setPositiveButton(android.R.string.ok, null)
.show();
mResultDialog.setCanceledOnTouchOutside(false);
}
}
// end esptouch ----------------------------------------
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mWifiManager = (WifiManager) getApplicationContext().getSystemService(WIFI_SERVICE);
mViewModel = new EspTouchViewModel();
}
@Override
protected boolean isSupportSlideBack() {
return true;
}
@Override
protected void initStatusBarStyle() {
StatusBarUtils.initStatusBarStyle(this, false, Colors.WHITE);
}
@Override
public boolean onKeyDown(int keyCode, KeyEvent event) {
return KeyboardUtils.onDisableBackKeyDown(keyCode) && super.onKeyDown(keyCode, event);
}
}

View File

@@ -0,0 +1,36 @@
package com.kerwin.wumei.activity;
import android.os.Bundle;
import android.view.KeyEvent;
import com.kerwin.wumei.core.BaseActivity;
import com.kerwin.wumei.fragment.LoginFragment;
import com.xuexiang.xui.utils.KeyboardUtils;
import com.xuexiang.xui.utils.StatusBarUtils;
import com.xuexiang.xutil.display.Colors;
public class LoginActivity extends BaseActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
openPage(LoginFragment.class, getIntent().getExtras());
}
@Override
protected boolean isSupportSlideBack() {
return false;
}
@Override
protected void initStatusBarStyle() {
StatusBarUtils.initStatusBarStyle(this, false, Colors.WHITE);
}
@Override
public boolean onKeyDown(int keyCode, KeyEvent event) {
return KeyboardUtils.onDisableBackKeyDown(keyCode) && super.onKeyDown(keyCode, event);
}
}

View File

@@ -0,0 +1,329 @@
/*
* Copyright (C) 2021 xuexiangjys(xuexiangjys@163.com)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
package com.kerwin.wumei.activity;
import android.Manifest;
import android.app.Activity;
import android.app.ProgressDialog;
import android.content.Context;
import android.content.DialogInterface;
import android.content.pm.PackageManager;
import android.location.LocationManager;
import android.net.wifi.ScanResult;
import android.net.wifi.WifiInfo;
import android.net.wifi.WifiManager;
import android.os.AsyncTask;
import android.os.Build;
import android.os.Bundle;
import android.text.Spannable;
import android.text.SpannableString;
import android.text.SpannableStringBuilder;
import android.text.style.ForegroundColorSpan;
import android.util.Log;
import android.view.KeyEvent;
import android.view.MenuItem;
import android.view.View;
import android.widget.LinearLayout;
import android.widget.TextView;
import android.widget.Toast;
import androidx.annotation.NonNull;
import androidx.annotation.RequiresApi;
import androidx.appcompat.app.ActionBarDrawerToggle;
import androidx.appcompat.app.AlertDialog;
import androidx.appcompat.widget.Toolbar;
import androidx.core.location.LocationManagerCompat;
import androidx.drawerlayout.widget.DrawerLayout;
import androidx.viewpager.widget.ViewPager;
import com.espressif.iot.esptouch.EsptouchTask;
import com.espressif.iot.esptouch.IEsptouchResult;
import com.espressif.iot.esptouch.IEsptouchTask;
import com.espressif.iot.esptouch.util.ByteUtil;
import com.espressif.iot.esptouch.util.TouchNetUtil;
import com.google.android.material.bottomnavigation.BottomNavigationView;
import com.google.android.material.navigation.NavigationView;
import com.kerwin.wumei.R;
import com.kerwin.wumei.adapter.entity.EspTouchViewModel;
import com.kerwin.wumei.core.BaseActivity;
import com.kerwin.wumei.core.BaseFragment;
import com.kerwin.wumei.fragment.AboutFragment;
import com.kerwin.wumei.fragment.FeedbackFragment;
import com.kerwin.wumei.fragment.MessageFragment;
import com.kerwin.wumei.fragment.SettingsFragment;
import com.kerwin.wumei.fragment.device.AddDeviceFragment;
import com.kerwin.wumei.fragment.device.GroupFragment;
import com.kerwin.wumei.fragment.device.SceneFragment;
import com.kerwin.wumei.fragment.device.ShareDeviceFragment;
import com.kerwin.wumei.fragment.news.NewsFragment;
import com.kerwin.wumei.fragment.profile.ProfileFragment;
import com.kerwin.wumei.fragment.device.DeviceFragment;
import com.kerwin.wumei.utils.NetUtils;
import com.kerwin.wumei.utils.Utils;
import com.kerwin.wumei.utils.XToastUtils;
import com.kerwin.wumei.widget.GuideTipsDialog;
import com.xuexiang.xaop.annotation.SingleClick;
import com.xuexiang.xpage.core.PageOption;
import com.xuexiang.xpage.enums.CoreAnim;
import com.xuexiang.xui.adapter.FragmentAdapter;
import com.xuexiang.xui.adapter.simple.AdapterItem;
import com.xuexiang.xui.utils.ResUtils;
import com.xuexiang.xui.utils.ThemeUtils;
import com.xuexiang.xui.widget.imageview.RadiusImageView;
import com.xuexiang.xui.widget.popupwindow.popup.XUISimplePopup;
import com.xuexiang.xutil.XUtil;
import com.xuexiang.xutil.common.ClickUtils;
import com.xuexiang.xutil.common.CollectionUtils;
import com.xuexiang.xutil.display.Colors;
import java.lang.ref.WeakReference;
import java.net.InetAddress;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import butterknife.BindView;
public class MainActivity extends BaseActivity implements View.OnClickListener, ViewPager.OnPageChangeListener, BottomNavigationView.OnNavigationItemSelectedListener, ClickUtils.OnClick2ExitListener, Toolbar.OnMenuItemClickListener {
@BindView(R.id.toolbar)
Toolbar toolbar;
@BindView(R.id.view_pager)
ViewPager viewPager;
/**
* 底部导航栏
*/
@BindView(R.id.bottom_navigation)
BottomNavigationView bottomNavigation;
/**
* 侧边栏
*/
@BindView(R.id.nav_view)
NavigationView navView;
@BindView(R.id.drawer_layout)
DrawerLayout drawerLayout;
private String[] mTitles;
@Override
protected int getLayoutId() {
return R.layout.activity_main;
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
initViews();
initListeners();
}
@Override
protected boolean isSupportSlideBack() {
return true;
}
private void initViews() {
mTitles = ResUtils.getStringArray(R.array.home_titles);
toolbar.setTitle(mTitles[0]);
toolbar.inflateMenu(R.menu.menu_main);
toolbar.setOnMenuItemClickListener(this);
initHeader();
//主页内容填充
BaseFragment[] fragments = new BaseFragment[]{
new DeviceFragment(),
new SceneFragment(),
new NewsFragment(),
new ProfileFragment(),
};
FragmentAdapter<BaseFragment> adapter = new FragmentAdapter<>(getSupportFragmentManager(), fragments);
viewPager.setOffscreenPageLimit(mTitles.length - 1);
viewPager.setAdapter(adapter);
GuideTipsDialog.showTips(this);
}
/**
* 侧边栏头部
*/
private void initHeader() {
navView.setItemIconTintList(null);
View headerView = navView.getHeaderView(0);
LinearLayout navHeader = headerView.findViewById(R.id.nav_header);
RadiusImageView ivAvatar = headerView.findViewById(R.id.iv_avatar);
TextView tvAvatar = headerView.findViewById(R.id.tv_avatar);
TextView tvSign = headerView.findViewById(R.id.tv_sign);
if (Utils.isColorDark(ThemeUtils.resolveColor(this, R.attr.colorAccent))) {
tvAvatar.setTextColor(Colors.WHITE);
tvSign.setTextColor(Colors.WHITE);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
ivAvatar.setImageTintList(ResUtils.getColors(R.color.xui_config_color_white));
}
} else {
tvAvatar.setTextColor(ThemeUtils.resolveColor(this, R.attr.xui_config_color_title_text));
tvSign.setTextColor(ThemeUtils.resolveColor(this, R.attr.xui_config_color_explain_text));
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
ivAvatar.setImageTintList(ResUtils.getColors(R.color.xui_config_color_gray_3));
}
}
// TODO: 2019-10-09 初始化数据
ivAvatar.setImageResource(R.drawable.ic_default_head);
tvAvatar.setText("15208747707");
tvSign.setText("物美点亮智慧生活...");
navHeader.setOnClickListener(this);
}
protected void initListeners() {
ActionBarDrawerToggle toggle = new ActionBarDrawerToggle(this, drawerLayout, toolbar, R.string.navigation_drawer_open, R.string.navigation_drawer_close);
drawerLayout.addDrawerListener(toggle);
toggle.syncState();
//侧边栏点击事件
navView.setNavigationItemSelectedListener(menuItem -> {
switch (menuItem.getItemId()) {
case R.id.nav_add_device:
PageOption.to(AddDeviceFragment.class) //跳转的fragment
.setAnim(CoreAnim.slide) //页面转场动画
.setRequestCode(100) //请求码,用于返回结果
.setAddToBackStack(true) //是否加入堆栈
.setNewActivity(true,AddDeviceActivity.class) //是否使用新的Activity打开
.open(this); //打开页面进行跳转
break;
case R.id.nav_settings:
openNewPage(SettingsFragment.class);
break;
case R.id.nav_about:
openNewPage(AboutFragment.class);
break;
case R.id.nav_message:
openNewPage(MessageFragment.class);
break;
case R.id.nav_share_device:
openNewPage(ShareDeviceFragment.class);
break;
case R.id.nav_group:
openNewPage(GroupFragment.class);
break;
default:
XToastUtils.toast("点击了:" + menuItem.getTitle());
break;
}
return true;
});
//主页事件监听
viewPager.addOnPageChangeListener(this);
bottomNavigation.setOnNavigationItemSelectedListener(this);
}
@Override
public boolean onMenuItemClick(MenuItem item) {
switch (item.getItemId()) {
case R.id.add_device:
PageOption.to(AddDeviceFragment.class) //跳转的fragment
.setAnim(CoreAnim.slide) //页面转场动画
.setRequestCode(100) //请求码,用于返回结果
.setAddToBackStack(true) //是否加入堆栈
.setNewActivity(true, AddDeviceActivity.class) //是否使用新的Activity打开
.open(this); //打开页面进行跳转
break;
default:
break;
}
return false;
}
@SingleClick
@Override
public void onClick(View v) {
switch (v.getId()) {
case R.id.nav_header:
XToastUtils.toast("功能完善中...");
break;
default:
break;
}
}
//=============ViewPager===================//
@Override
public void onPageScrolled(int i, float v, int i1) {
}
@Override
public void onPageSelected(int position) {
MenuItem item = bottomNavigation.getMenu().getItem(position);
toolbar.setTitle(item.getTitle());
item.setChecked(true);
}
@Override
public void onPageScrollStateChanged(int i) {
}
//================Navigation================//
/**
* 底部导航栏点击事件
*
* @param menuItem
* @return
*/
@Override
public boolean onNavigationItemSelected(@NonNull MenuItem menuItem) {
int index = CollectionUtils.arrayIndexOf(mTitles, menuItem.getTitle());
if (index != -1) {
toolbar.setTitle(menuItem.getTitle());
viewPager.setCurrentItem(index, false);
return true;
}
return false;
}
/**
* 菜单、返回键响应
*/
@Override
public boolean onKeyDown(int keyCode, KeyEvent event) {
if (keyCode == KeyEvent.KEYCODE_BACK) {
ClickUtils.exitBy2Click(2000, this);
}
return true;
}
@Override
public void onRetry() {
XToastUtils.toast("再按一次退出程序");
}
@Override
public void onExit() {
XUtil.exitApp();
}
}

View File

@@ -0,0 +1,68 @@
package com.kerwin.wumei.activity;
import android.view.KeyEvent;
import com.kerwin.wumei.R;
import com.kerwin.wumei.utils.SettingUtils;
import com.kerwin.wumei.utils.TokenUtils;
import com.kerwin.wumei.utils.Utils;
import com.xuexiang.xui.utils.KeyboardUtils;
import com.xuexiang.xui.widget.activity.BaseSplashActivity;
import com.xuexiang.xutil.app.ActivityUtils;
import me.jessyan.autosize.internal.CancelAdapt;
/**
* 启动页【无需适配屏幕大小】
*
*/
public class SplashActivity extends BaseSplashActivity implements CancelAdapt {
@Override
protected long getSplashDurationMillis() {
return 500;
}
/**
* activity启动后的初始化
*/
@Override
protected void onCreateActivity() {
initSplashView(R.drawable.xui_config_bg_splash);
startSplash(false);
}
/**
* 启动页结束后的动作
*/
@Override
protected void onSplashFinished() {
if (SettingUtils.isAgreePrivacy()) {
loginOrGoMainPage();
} else {
Utils.showPrivacyDialog(this, (dialog, which) -> {
dialog.dismiss();
SettingUtils.setIsAgreePrivacy(true);
loginOrGoMainPage();
});
}
}
private void loginOrGoMainPage() {
if (TokenUtils.hasToken()) {
ActivityUtils.startActivity(MainActivity.class);
} else {
ActivityUtils.startActivity(LoginActivity.class);
}
finish();
}
/**
* 菜单、返回键响应
*/
@Override
public boolean onKeyDown(int keyCode, KeyEvent event) {
return KeyboardUtils.onDisableBackKeyDown(keyCode) && super.onKeyDown(keyCode, event);
}
}

View File

@@ -0,0 +1,102 @@
/*
* Copyright (C) 2021 xuexiangjys(xuexiangjys@163.com)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
package com.kerwin.wumei.adapter.base.broccoli;
import android.view.View;
import androidx.annotation.NonNull;
import com.xuexiang.xui.adapter.recyclerview.BaseRecyclerAdapter;
import com.xuexiang.xui.adapter.recyclerview.RecyclerViewHolder;
import com.xuexiang.xui.adapter.recyclerview.XRecyclerAdapter;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
import me.samlss.broccoli.Broccoli;
/**
* 使用Broccoli占位的基础适配器
*
* @author XUE
* @since 2019/4/8 16:33
*/
public abstract class BroccoliRecyclerAdapter<T> extends BaseRecyclerAdapter<T> {
/**
* 是否已经加载成功
*/
private boolean mHasLoad = false;
private Map<View, Broccoli> mBroccoliMap = new HashMap<>();
public BroccoliRecyclerAdapter(Collection<T> collection) {
super(collection);
}
@Override
protected void bindData(@NonNull RecyclerViewHolder holder, int position, T item) {
Broccoli broccoli = mBroccoliMap.get(holder.itemView);
if (broccoli == null) {
broccoli = new Broccoli();
mBroccoliMap.put(holder.itemView, broccoli);
}
if (mHasLoad) {
broccoli.removeAllPlaceholders();
onBindData(holder, item, position);
} else {
onBindBroccoli(holder, broccoli);
broccoli.show();
}
}
/**
* 绑定控件
*
* @param holder
* @param model
* @param position
*/
protected abstract void onBindData(RecyclerViewHolder holder, T model, int position);
/**
* 绑定占位控件
*
* @param broccoli
*/
protected abstract void onBindBroccoli(RecyclerViewHolder holder, Broccoli broccoli);
@Override
public XRecyclerAdapter refresh(Collection<T> collection) {
mHasLoad = true;
return super.refresh(collection);
}
/**
* 资源释放,防止内存泄漏
*/
public void recycle() {
for (Broccoli broccoli : mBroccoliMap.values()) {
broccoli.removeAllPlaceholders();
}
mBroccoliMap.clear();
clear();
}
}

View File

@@ -0,0 +1,112 @@
/*
* Copyright (C) 2021 xuexiangjys(xuexiangjys@163.com)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
package com.kerwin.wumei.adapter.base.broccoli;
import android.view.View;
import androidx.annotation.NonNull;
import com.alibaba.android.vlayout.LayoutHelper;
import com.kerwin.wumei.adapter.base.delegate.SimpleDelegateAdapter;
import com.kerwin.wumei.adapter.base.delegate.XDelegateAdapter;
import com.xuexiang.xui.adapter.recyclerview.RecyclerViewHolder;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
import me.samlss.broccoli.Broccoli;
/**
* 使用Broccoli占位的基础适配器
*
* @author xuexiang
* @since 2021/1/9 4:52 PM
*/
public abstract class BroccoliSimpleDelegateAdapter<T> extends SimpleDelegateAdapter<T> {
/**
* 是否已经加载成功
*/
private boolean mHasLoad = false;
private Map<View, Broccoli> mBroccoliMap = new HashMap<>();
public BroccoliSimpleDelegateAdapter(int layoutId, LayoutHelper layoutHelper) {
super(layoutId, layoutHelper);
}
public BroccoliSimpleDelegateAdapter(int layoutId, LayoutHelper layoutHelper, Collection<T> list) {
super(layoutId, layoutHelper, list);
}
public BroccoliSimpleDelegateAdapter(int layoutId, LayoutHelper layoutHelper, T[] data) {
super(layoutId, layoutHelper, data);
}
@Override
protected void bindData(@NonNull RecyclerViewHolder holder, int position, T item) {
Broccoli broccoli = mBroccoliMap.get(holder.itemView);
if (broccoli == null) {
broccoli = new Broccoli();
mBroccoliMap.put(holder.itemView, broccoli);
}
if (mHasLoad) {
broccoli.removeAllPlaceholders();
onBindData(holder, item, position);
} else {
onBindBroccoli(holder, broccoli);
broccoli.show();
}
}
/**
* 绑定控件
*
* @param holder
* @param model
* @param position
*/
protected abstract void onBindData(RecyclerViewHolder holder, T model, int position);
/**
* 绑定占位控件
*
* @param holder
* @param broccoli
*/
protected abstract void onBindBroccoli(RecyclerViewHolder holder, Broccoli broccoli);
@Override
public XDelegateAdapter refresh(Collection<T> collection) {
mHasLoad = true;
return super.refresh(collection);
}
/**
* 资源释放,防止内存泄漏
*/
public void recycle() {
for (Broccoli broccoli : mBroccoliMap.values()) {
broccoli.removeAllPlaceholders();
}
mBroccoliMap.clear();
clear();
}
}

View File

@@ -0,0 +1,61 @@
/*
* Copyright (C) 2021 xuexiangjys(xuexiangjys@163.com)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
package com.kerwin.wumei.adapter.base.delegate;
import android.view.ViewGroup;
import androidx.annotation.NonNull;
import com.xuexiang.xui.adapter.recyclerview.RecyclerViewHolder;
import java.util.Collection;
/**
* 通用的DelegateAdapter适配器
*
* @author xuexiang
* @since 2020/3/20 12:44 AM
*/
public abstract class BaseDelegateAdapter<T> extends XDelegateAdapter<T, RecyclerViewHolder> {
public BaseDelegateAdapter() {
super();
}
public BaseDelegateAdapter(Collection<T> list) {
super(list);
}
public BaseDelegateAdapter(T[] data) {
super(data);
}
/**
* 适配的布局
*
* @param viewType
* @return
*/
protected abstract int getItemLayoutId(int viewType);
@NonNull
@Override
protected RecyclerViewHolder getViewHolder(@NonNull ViewGroup parent, int viewType) {
return new RecyclerViewHolder(inflateView(parent, getItemLayoutId(viewType)));
}
}

View File

@@ -0,0 +1,64 @@
/*
* Copyright (C) 2021 xuexiangjys(xuexiangjys@163.com)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
package com.kerwin.wumei.adapter.base.delegate;
import com.alibaba.android.vlayout.LayoutHelper;
import java.util.Collection;
/**
* 简易DelegateAdapter适配器
*
* @author xuexiang
* @since 2020/3/20 12:55 AM
*/
public abstract class SimpleDelegateAdapter<T> extends BaseDelegateAdapter<T> {
private int mLayoutId;
private LayoutHelper mLayoutHelper;
public SimpleDelegateAdapter(int layoutId, LayoutHelper layoutHelper) {
super();
mLayoutId = layoutId;
mLayoutHelper = layoutHelper;
}
public SimpleDelegateAdapter(int layoutId, LayoutHelper layoutHelper, Collection<T> list) {
super(list);
mLayoutId = layoutId;
mLayoutHelper = layoutHelper;
}
public SimpleDelegateAdapter(int layoutId, LayoutHelper layoutHelper, T[] data) {
super(data);
mLayoutId = layoutId;
mLayoutHelper = layoutHelper;
}
@Override
protected int getItemLayoutId(int viewType) {
return mLayoutId;
}
@Override
public LayoutHelper onCreateLayoutHelper() {
return mLayoutHelper;
}
}

View File

@@ -0,0 +1,72 @@
/*
* Copyright (C) 2021 xuexiangjys(xuexiangjys@163.com)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
package com.kerwin.wumei.adapter.base.delegate;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import androidx.annotation.LayoutRes;
import androidx.annotation.NonNull;
import com.alibaba.android.vlayout.DelegateAdapter;
import com.alibaba.android.vlayout.LayoutHelper;
import com.alibaba.android.vlayout.layout.SingleLayoutHelper;
import com.xuexiang.xui.adapter.recyclerview.RecyclerViewHolder;
/**
* 单独布局的DelegateAdapter
*
* @author xuexiang
* @since 2020/3/20 1:04 AM
*/
public abstract class SingleDelegateAdapter extends DelegateAdapter.Adapter<RecyclerViewHolder> {
private int mLayoutId;
public SingleDelegateAdapter(int layoutId) {
mLayoutId = layoutId;
}
@Override
public LayoutHelper onCreateLayoutHelper() {
return new SingleLayoutHelper();
}
/**
* 加载布局获取控件
*
* @param parent 父布局
* @param layoutId 布局ID
* @return
*/
protected View inflateView(ViewGroup parent, @LayoutRes int layoutId) {
return LayoutInflater.from(parent.getContext()).inflate(layoutId, parent, false);
}
@NonNull
@Override
public RecyclerViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
return new RecyclerViewHolder(inflateView(parent, mLayoutId));
}
@Override
public int getItemCount() {
return 1;
}
}

View File

@@ -0,0 +1,300 @@
/*
* Copyright (C) 2021 xuexiangjys(xuexiangjys@163.com)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
package com.kerwin.wumei.adapter.base.delegate;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import androidx.annotation.LayoutRes;
import androidx.annotation.NonNull;
import androidx.recyclerview.widget.RecyclerView;
import com.alibaba.android.vlayout.DelegateAdapter;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
/**
* 基础DelegateAdapter
*
* @author xuexiang
* @since 2020/3/20 12:17 AM
*/
public abstract class XDelegateAdapter<T, V extends RecyclerView.ViewHolder> extends DelegateAdapter.Adapter<V> {
/**
* 数据源
*/
protected final List<T> mData = new ArrayList<>();
/**
* 当前点击的条目
*/
protected int mSelectPosition = -1;
public XDelegateAdapter() {
}
public XDelegateAdapter(Collection<T> list) {
if (list != null) {
mData.addAll(list);
}
}
public XDelegateAdapter(T[] data) {
if (data != null && data.length > 0) {
mData.addAll(Arrays.asList(data));
}
}
/**
* 构建自定义的ViewHolder
*
* @param parent
* @param viewType
* @return
*/
@NonNull
protected abstract V getViewHolder(@NonNull ViewGroup parent, int viewType);
/**
* 绑定数据
*
* @param holder
* @param position 索引
* @param item 列表项
*/
protected abstract void bindData(@NonNull V holder, int position, T item);
/**
* 加载布局获取控件
*
* @param parent 父布局
* @param layoutId 布局ID
* @return
*/
protected View inflateView(ViewGroup parent, @LayoutRes int layoutId) {
return LayoutInflater.from(parent.getContext()).inflate(layoutId, parent, false);
}
@NonNull
@Override
public V onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
return getViewHolder(parent, viewType);
}
@Override
public void onBindViewHolder(@NonNull V holder, int position) {
bindData(holder, position, mData.get(position));
}
/**
* 获取列表项
*
* @param position
* @return
*/
public T getItem(int position) {
return checkPosition(position) ? mData.get(position) : null;
}
private boolean checkPosition(int position) {
return position >= 0 && position <= mData.size() - 1;
}
public boolean isEmpty() {
return getItemCount() == 0;
}
@Override
public int getItemCount() {
return mData.size();
}
/**
* @return 数据源
*/
public List<T> getData() {
return mData;
}
/**
* 给指定位置添加一项
*
* @param pos
* @param item
* @return
*/
public XDelegateAdapter add(int pos, T item) {
mData.add(pos, item);
notifyItemInserted(pos);
return this;
}
/**
* 在列表末端增加一项
*
* @param item
* @return
*/
public XDelegateAdapter add(T item) {
mData.add(item);
notifyItemInserted(mData.size() - 1);
return this;
}
/**
* 删除列表中指定索引的数据
*
* @param pos
* @return
*/
public XDelegateAdapter delete(int pos) {
mData.remove(pos);
notifyItemRemoved(pos);
return this;
}
/**
* 刷新列表中指定位置的数据
*
* @param pos
* @param item
* @return
*/
public XDelegateAdapter refresh(int pos, T item) {
mData.set(pos, item);
notifyItemChanged(pos);
return this;
}
/**
* 刷新列表数据
*
* @param collection
* @return
*/
public XDelegateAdapter refresh(Collection<T> collection) {
if (collection != null) {
mData.clear();
mData.addAll(collection);
mSelectPosition = -1;
notifyDataSetChanged();
}
return this;
}
/**
* 刷新列表数据
*
* @param array
* @return
*/
public XDelegateAdapter refresh(T[] array) {
if (array != null && array.length > 0) {
mData.clear();
mData.addAll(Arrays.asList(array));
mSelectPosition = -1;
notifyDataSetChanged();
}
return this;
}
/**
* 加载更多
*
* @param collection
* @return
*/
public XDelegateAdapter loadMore(Collection<T> collection) {
if (collection != null) {
mData.addAll(collection);
notifyDataSetChanged();
}
return this;
}
/**
* 加载更多
*
* @param array
* @return
*/
public XDelegateAdapter loadMore(T[] array) {
if (array != null && array.length > 0) {
mData.addAll(Arrays.asList(array));
notifyDataSetChanged();
}
return this;
}
/**
* 添加一个
*
* @param item
* @return
*/
public XDelegateAdapter load(T item) {
if (item != null) {
mData.add(item);
notifyDataSetChanged();
}
return this;
}
/**
* @return 当前列表的选中项
*/
public int getSelectPosition() {
return mSelectPosition;
}
/**
* 设置当前列表的选中项
*
* @param selectPosition
* @return
*/
public XDelegateAdapter setSelectPosition(int selectPosition) {
mSelectPosition = selectPosition;
notifyDataSetChanged();
return this;
}
/**
* 获取当前列表选中项
*
* @return 当前列表选中项
*/
public T getSelectItem() {
return getItem(mSelectPosition);
}
/**
* 清除数据
*/
public void clear() {
if (!isEmpty()) {
mData.clear();
mSelectPosition = -1;
notifyDataSetChanged();
}
}
}

View File

@@ -0,0 +1,31 @@
package com.kerwin.wumei.adapter.entity;
import android.widget.Button;
import android.widget.EditText;
import android.widget.RadioGroup;
import android.widget.TextView;
import com.xuexiang.xui.widget.spinner.materialspinner.MaterialSpinner;
public class EspTouchViewModel {
public MaterialSpinner ssidSpinner;
public EditText apPasswordEdit;
public EditText deviceCountEdit;
public RadioGroup packageModeGroup;
public TextView messageView;
public Button confirmBtn;
public String ssid;
public byte[] ssidBytes;
public String bssid;
public CharSequence message;
public boolean confirmEnable;
public void invalidateAll() {
ssidSpinner.setText(ssid);
messageView.setText(message);
confirmBtn.setEnabled(confirmEnable);
}
}

View File

@@ -0,0 +1,199 @@
/*
* Copyright (C) 2021 xuexiangjys(xuexiangjys@163.com)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
package com.kerwin.wumei.adapter.entity;
/**
* 新闻信息
*
* @author xuexiang
* @since 2019/4/7 下午12:07
*/
public class NewInfo {
/**
* 用户名
*/
private String UserName = "kerwin";
/**
* 标签
*/
private String Tag;
/**
* 标题
*/
private String Title;
/**
* 摘要
*/
private String Summary;
/**
* 图片
*/
private String ImageUrl;
/**
* 点赞数
*/
private int Praise;
/**
* 评论数
*/
private int Comment;
/**
* 阅读量
*/
private int Read;
/**
* 新闻的详情地址
*/
private String DetailUrl;
public NewInfo() {
}
public NewInfo(String userName, String tag, String title, String summary, String imageUrl, int praise, int comment, int read, String detailUrl) {
UserName = userName;
Tag = tag;
Title = title;
Summary = summary;
ImageUrl = imageUrl;
Praise = praise;
Comment = comment;
Read = read;
DetailUrl = detailUrl;
}
public NewInfo(String tag, String title, String summary, String imageUrl, String detailUrl) {
Tag = tag;
Title = title;
Summary = summary;
ImageUrl = imageUrl;
DetailUrl = detailUrl;
}
public NewInfo(String tag, String title) {
Tag = tag;
Title = title;
Praise = (int) (Math.random() * 100 + 5);
Comment = (int) (Math.random() * 50 + 5);
Read = (int) (Math.random() * 500 + 50);
}
public String getUserName() {
return UserName;
}
public NewInfo setUserName(String userName) {
UserName = userName;
return this;
}
public String getTag() {
return Tag;
}
public NewInfo setTag(String tag) {
Tag = tag;
return this;
}
public String getTitle() {
return Title;
}
public NewInfo setTitle(String title) {
Title = title;
return this;
}
public String getSummary() {
return Summary;
}
public NewInfo setSummary(String summary) {
Summary = summary;
return this;
}
public String getImageUrl() {
return ImageUrl;
}
public NewInfo setImageUrl(String imageUrl) {
ImageUrl = imageUrl;
return this;
}
public int getPraise() {
return Praise;
}
public NewInfo setPraise(int praise) {
Praise = praise;
return this;
}
public int getComment() {
return Comment;
}
public NewInfo setComment(int comment) {
Comment = comment;
return this;
}
public int getRead() {
return Read;
}
public NewInfo setRead(int read) {
Read = read;
return this;
}
public String getDetailUrl() {
return DetailUrl;
}
public NewInfo setDetailUrl(String detailUrl) {
DetailUrl = detailUrl;
return this;
}
@Override
public String toString() {
return "NewInfo{" +
"UserName='" + UserName + '\'' +
", Tag='" + Tag + '\'' +
", Title='" + Title + '\'' +
", Summary='" + Summary + '\'' +
", ImageUrl='" + ImageUrl + '\'' +
", Praise=" + Praise +
", Comment=" + Comment +
", Read=" + Read +
", DetailUrl='" + DetailUrl + '\'' +
'}';
}
}

View File

@@ -0,0 +1,153 @@
/*
* Copyright (C) 2021 xuexiangjys(xuexiangjys@163.com)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
package com.kerwin.wumei.core;
import android.content.Context;
import android.os.Bundle;
import com.xuexiang.xpage.base.XPageActivity;
import com.xuexiang.xpage.base.XPageFragment;
import com.xuexiang.xpage.core.CoreSwitchBean;
import com.xuexiang.xrouter.facade.service.SerializationService;
import com.xuexiang.xrouter.launcher.XRouter;
import com.xuexiang.xui.utils.ResUtils;
import com.xuexiang.xui.widget.slideback.SlideBack;
import butterknife.ButterKnife;
import butterknife.Unbinder;
import io.github.inflationx.viewpump.ViewPumpContextWrapper;
/**
* 基础容器Activity
*
* @author XUE
* @since 2019/3/22 11:21
*/
public class BaseActivity extends XPageActivity {
Unbinder mUnbinder;
@Override
protected void attachBaseContext(Context newBase) {
//注入字体
super.attachBaseContext(ViewPumpContextWrapper.wrap(newBase));
}
/**
* 是否支持侧滑返回
*/
public static final String KEY_SUPPORT_SLIDE_BACK = "key_support_slide_back";
@Override
protected void onCreate(Bundle savedInstanceState) {
initStatusBarStyle();
super.onCreate(savedInstanceState);
mUnbinder = ButterKnife.bind(this);
registerSlideBack();
}
/**
* 初始化状态栏的样式
*/
protected void initStatusBarStyle() {
}
/**
* 打开fragment
*
* @param clazz 页面类
* @param addToBackStack 是否添加到栈中
* @return 打开的fragment对象
*/
public <T extends XPageFragment> T openPage(Class<T> clazz, boolean addToBackStack) {
CoreSwitchBean page = new CoreSwitchBean(clazz)
.setAddToBackStack(addToBackStack);
return (T) openPage(page);
}
/**
* 打开fragment
*
* @return 打开的fragment对象
*/
public <T extends XPageFragment> T openNewPage(Class<T> clazz) {
CoreSwitchBean page = new CoreSwitchBean(clazz)
.setNewActivity(true);
return (T) openPage(page);
}
/**
* 切换fragment
*
* @param clazz 页面类
* @return 打开的fragment对象
*/
public <T extends XPageFragment> T switchPage(Class<T> clazz) {
return openPage(clazz, false);
}
/**
* 序列化对象
*
* @param object
* @return
*/
public String serializeObject(Object object) {
return XRouter.getInstance().navigation(SerializationService.class).object2Json(object);
}
@Override
protected void onRelease() {
mUnbinder.unbind();
unregisterSlideBack();
super.onRelease();
}
/**
* 注册侧滑回调
*/
protected void registerSlideBack() {
if (isSupportSlideBack()) {
SlideBack.with(this)
.haveScroll(true)
.edgeMode(ResUtils.isRtl() ? SlideBack.EDGE_RIGHT : SlideBack.EDGE_LEFT)
.callBack(this::popPage)
.register();
}
}
/**
* 注销侧滑回调
*/
protected void unregisterSlideBack() {
if (isSupportSlideBack()) {
SlideBack.unregister(this);
}
}
/**
* @return 是否支持侧滑返回
*/
protected boolean isSupportSlideBack() {
CoreSwitchBean page = getIntent().getParcelableExtra(CoreSwitchBean.KEY_SWITCH_BEAN);
return page == null || page.getBundle() == null || page.getBundle().getBoolean(KEY_SUPPORT_SLIDE_BACK, true);
}
}

View File

@@ -0,0 +1,123 @@
/*
* Copyright (C) 2021 xuexiangjys(xuexiangjys@163.com)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
package com.kerwin.wumei.core;
import android.content.res.Configuration;
import android.view.View;
import android.view.ViewGroup;
import android.widget.AdapterView;
import com.umeng.analytics.MobclickAgent;
import com.xuexiang.xaop.annotation.SingleClick;
import com.xuexiang.xpage.base.XPageContainerListFragment;
import com.xuexiang.xui.widget.actionbar.TitleBar;
import com.xuexiang.xui.widget.actionbar.TitleUtils;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import static com.kerwin.wumei.core.SimpleListAdapter.KEY_SUB_TITLE;
import static com.kerwin.wumei.core.SimpleListAdapter.KEY_TITLE;
/**
* 修改列表样式为主副标题显示
*
* @author xuexiang
* @since 2018/11/22 上午11:26
*/
public abstract class BaseContainerFragment extends XPageContainerListFragment {
@Override
protected void initPage() {
initTitle();
initViews();
initListeners();
}
protected TitleBar initTitle() {
return TitleUtils.addTitleBarDynamic((ViewGroup) getRootView(), getPageTitle(), new View.OnClickListener() {
@Override
public void onClick(View v) {
popToBack();
}
});
}
@Override
protected void initData() {
mSimpleData = initSimpleData(mSimpleData);
List<Map<String, String>> data = new ArrayList<>();
for (String content : mSimpleData) {
Map<String, String> item = new HashMap<>();
int index = content.indexOf("\n");
if (index > 0) {
item.put(KEY_TITLE, String.valueOf(content.subSequence(0, index)));
item.put(KEY_SUB_TITLE, String.valueOf(content.subSequence(index + 1, content.length())));
} else {
item.put(KEY_TITLE, content);
item.put(KEY_SUB_TITLE, "");
}
data.add(item);
}
getListView().setAdapter(new SimpleListAdapter(getContext(), data));
initSimply();
}
@Override
public void onItemClick(AdapterView<?> adapterView, View view, int position, long id) {
onItemClick(view, position);
}
@SingleClick
private void onItemClick(View view, int position) {
onItemClick(position);
}
@Override
public void onDestroyView() {
getListView().setOnItemClickListener(null);
super.onDestroyView();
}
@Override
public void onConfigurationChanged(Configuration newConfig) {
//屏幕旋转时刷新一下title
super.onConfigurationChanged(newConfig);
ViewGroup root = (ViewGroup) getRootView();
if (root.getChildAt(0) instanceof TitleBar) {
root.removeViewAt(0);
initTitle();
}
}
@Override
public void onResume() {
super.onResume();
MobclickAgent.onPageStart(getPageName());
}
@Override
public void onPause() {
super.onPause();
MobclickAgent.onPageEnd(getPageName());
}
}

View File

@@ -0,0 +1,346 @@
/*
* Copyright (C) 2021 xuexiangjys(xuexiangjys@163.com)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
package com.kerwin.wumei.core;
import android.content.res.Configuration;
import android.os.Parcelable;
import android.view.ViewGroup;
import androidx.annotation.NonNull;
import androidx.fragment.app.Fragment;
import com.umeng.analytics.MobclickAgent;
import com.kerwin.wumei.core.http.loader.ProgressLoader;
import com.xuexiang.xhttp2.subsciber.impl.IProgressLoader;
import com.xuexiang.xpage.base.XPageActivity;
import com.xuexiang.xpage.base.XPageFragment;
import com.xuexiang.xpage.core.PageOption;
import com.xuexiang.xpage.enums.CoreAnim;
import com.xuexiang.xpage.utils.Utils;
import com.xuexiang.xrouter.facade.service.SerializationService;
import com.xuexiang.xrouter.launcher.XRouter;
import com.xuexiang.xui.widget.actionbar.TitleBar;
import com.xuexiang.xui.widget.actionbar.TitleUtils;
import java.io.Serializable;
import java.lang.reflect.Type;
/**
* 基础fragment
*
* @author xuexiang
* @since 2018/5/25 下午3:44
*/
public abstract class BaseFragment extends XPageFragment {
private IProgressLoader mIProgressLoader;
@Override
protected void initPage() {
initTitle();
initViews();
initListeners();
}
protected TitleBar initTitle() {
return TitleUtils.addTitleBarDynamic((ViewGroup) getRootView(), getPageTitle(), v -> popToBack());
}
@Override
protected void initListeners() {
}
/**
* 获取进度条加载者
*
* @return 进度条加载者
*/
public IProgressLoader getProgressLoader() {
if (mIProgressLoader == null) {
mIProgressLoader = ProgressLoader.create(getContext());
}
return mIProgressLoader;
}
/**
* 获取进度条加载者
*
* @param message
* @return 进度条加载者
*/
public IProgressLoader getProgressLoader(String message) {
if (mIProgressLoader == null) {
mIProgressLoader = ProgressLoader.create(getContext(), message);
} else {
mIProgressLoader.updateMessage(message);
}
return mIProgressLoader;
}
@Override
public void onConfigurationChanged(@NonNull Configuration newConfig) {
//屏幕旋转时刷新一下title
super.onConfigurationChanged(newConfig);
ViewGroup root = (ViewGroup) getRootView();
if (root.getChildAt(0) instanceof TitleBar) {
root.removeViewAt(0);
initTitle();
}
}
@Override
public void onDestroyView() {
if (mIProgressLoader != null) {
mIProgressLoader.dismissLoading();
}
super.onDestroyView();
}
@Override
public void onResume() {
super.onResume();
MobclickAgent.onPageStart(getPageName());
}
@Override
public void onPause() {
super.onPause();
MobclickAgent.onPageEnd(getPageName());
}
//==============================页面跳转api===================================//
/**
* 打开一个新的页面【建议只在主tab页使用】
*
* @param clazz 页面的类
* @param <T>
* @return
*/
public <T extends XPageFragment> Fragment openNewPage(Class<T> clazz) {
return new PageOption(clazz)
.setNewActivity(true)
.open(this);
}
/**
* 打开一个新的页面【建议只在主tab页使用】
*
* @param pageName 页面名
* @param <T>
* @return
*/
public <T extends XPageFragment> Fragment openNewPage(String pageName) {
return new PageOption(pageName)
.setAnim(CoreAnim.slide)
.setNewActivity(true)
.open(this);
}
/**
* 打开一个新的页面【建议只在主tab页使用】
*
* @param clazz 页面的类
* @param containActivityClazz 页面容器
* @param <T>
* @return
*/
public <T extends XPageFragment> Fragment openNewPage(Class<T> clazz, @NonNull Class<? extends XPageActivity> containActivityClazz) {
return new PageOption(clazz)
.setNewActivity(true)
.setContainActivityClazz(containActivityClazz)
.open(this);
}
/**
* 打开一个新的页面【建议只在主tab页使用】
*
* @param clazz 页面的类
* @param key 入参的键
* @param value 入参的值
* @param <T>
* @return
*/
public <T extends XPageFragment> Fragment openNewPage(Class<T> clazz, String key, Object value) {
PageOption option = new PageOption(clazz).setNewActivity(true);
return openPage(option, key, value);
}
public Fragment openPage(PageOption option, String key, Object value) {
if (value instanceof Integer) {
option.putInt(key, (Integer) value);
} else if (value instanceof Float) {
option.putFloat(key, (Float) value);
} else if (value instanceof String) {
option.putString(key, (String) value);
} else if (value instanceof Boolean) {
option.putBoolean(key, (Boolean) value);
} else if (value instanceof Long) {
option.putLong(key, (Long) value);
} else if (value instanceof Double) {
option.putDouble(key, (Double) value);
} else if (value instanceof Parcelable) {
option.putParcelable(key, (Parcelable) value);
} else if (value instanceof Serializable) {
option.putSerializable(key, (Serializable) value);
} else {
option.putString(key, serializeObject(value));
}
return option.open(this);
}
/**
* 打开页面
*
* @param clazz 页面的类
* @param addToBackStack 是否加入回退栈
* @param key 入参的键
* @param value 入参的值
* @param <T>
* @return
*/
public <T extends XPageFragment> Fragment openPage(Class<T> clazz, boolean addToBackStack, String key, String value) {
return new PageOption(clazz)
.setAddToBackStack(addToBackStack)
.putString(key, value)
.open(this);
}
/**
* 打开页面
*
* @param clazz 页面的类
* @param key 入参的键
* @param value 入参的值
* @param <T>
* @return
*/
public <T extends XPageFragment> Fragment openPage(Class<T> clazz, String key, Object value) {
return openPage(clazz, true, key, value);
}
/**
* 打开页面
*
* @param clazz 页面的类
* @param addToBackStack 是否加入回退栈
* @param key 入参的键
* @param value 入参的值
* @param <T>
* @return
*/
public <T extends XPageFragment> Fragment openPage(Class<T> clazz, boolean addToBackStack, String key, Object value) {
PageOption option = new PageOption(clazz).setAddToBackStack(addToBackStack);
return openPage(option, key, value);
}
/**
* 打开页面
*
* @param clazz 页面的类
* @param key 入参的键
* @param value 入参的值
* @param <T>
* @return
*/
public <T extends XPageFragment> Fragment openPage(Class<T> clazz, String key, String value) {
return new PageOption(clazz)
.putString(key, value)
.open(this);
}
/**
* 打开页面,需要结果返回
*
* @param clazz 页面的类
* @param key 入参的键
* @param value 入参的值
* @param requestCode 请求码
* @param <T>
* @return
*/
public <T extends XPageFragment> Fragment openPageForResult(Class<T> clazz, String key, Object value, int requestCode) {
PageOption option = new PageOption(clazz).setRequestCode(requestCode);
return openPage(option, key, value);
}
/**
* 打开页面,需要结果返回
*
* @param clazz 页面的类
* @param key 入参的键
* @param value 入参的值
* @param requestCode 请求码
* @param <T>
* @return
*/
public <T extends XPageFragment> Fragment openPageForResult(Class<T> clazz, String key, String value, int requestCode) {
return new PageOption(clazz)
.setRequestCode(requestCode)
.putString(key, value)
.open(this);
}
/**
* 打开页面,需要结果返回
*
* @param clazz 页面的类
* @param requestCode 请求码
* @param <T>
* @return
*/
public <T extends XPageFragment> Fragment openPageForResult(Class<T> clazz, int requestCode) {
return new PageOption(clazz)
.setRequestCode(requestCode)
.open(this);
}
/**
* 序列化对象
*
* @param object 需要序列化的对象
* @return 序列化结果
*/
public String serializeObject(Object object) {
return XRouter.getInstance().navigation(SerializationService.class).object2Json(object);
}
/**
* 反序列化对象
*
* @param input 反序列化的内容
* @param clazz 类型
* @return 反序列化结果
*/
public <T> T deserializeObject(String input, Type clazz) {
return XRouter.getInstance().navigation(SerializationService.class).parseObject(input, clazz);
}
@Override
protected void hideCurrentPageSoftInput() {
if (getActivity() == null) {
return;
}
// 记住要在xml的父布局加上android:focusable="true" 和 android:focusableInTouchMode="true"
Utils.hideSoftInputClearFocus(getActivity().getCurrentFocus());
}
}

View File

@@ -0,0 +1,284 @@
/*
* Copyright (C) 2021 xuexiangjys(xuexiangjys@163.com)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
package com.kerwin.wumei.core;
import android.content.res.Configuration;
import android.os.Parcelable;
import android.view.View;
import android.view.ViewGroup;
import androidx.annotation.NonNull;
import androidx.fragment.app.Fragment;
import com.umeng.analytics.MobclickAgent;
import com.xuexiang.xpage.base.XPageActivity;
import com.xuexiang.xpage.base.XPageFragment;
import com.xuexiang.xpage.base.XPageSimpleListFragment;
import com.xuexiang.xpage.core.PageOption;
import com.xuexiang.xpage.enums.CoreAnim;
import com.xuexiang.xrouter.facade.service.SerializationService;
import com.xuexiang.xrouter.launcher.XRouter;
import com.xuexiang.xui.widget.actionbar.TitleBar;
import com.xuexiang.xui.widget.actionbar.TitleUtils;
import java.io.Serializable;
/**
* @author xuexiang
* @since 2018/12/29 下午12:41
*/
public abstract class BaseSimpleListFragment extends XPageSimpleListFragment {
@Override
protected void initPage() {
initTitle();
initViews();
initListeners();
}
protected TitleBar initTitle() {
return TitleUtils.addTitleBarDynamic((ViewGroup) getRootView(), getPageTitle(), new View.OnClickListener() {
@Override
public void onClick(View v) {
popToBack();
}
});
}
@Override
public void onConfigurationChanged(@NonNull Configuration newConfig) {
//屏幕旋转时刷新一下title
super.onConfigurationChanged(newConfig);
ViewGroup root = (ViewGroup) getRootView();
if (root.getChildAt(0) instanceof TitleBar) {
root.removeViewAt(0);
initTitle();
}
}
@Override
public void onResume() {
super.onResume();
MobclickAgent.onPageStart(getPageName());
}
@Override
public void onPause() {
super.onPause();
MobclickAgent.onPageEnd(getPageName());
}
//==============================页面跳转api===================================//
/**
* 打开一个新的页面【建议只在主tab页使用】
*
* @param clazz 页面的类
* @param <T>
* @return
*/
public <T extends XPageFragment> Fragment openNewPage(Class<T> clazz) {
return new PageOption(clazz)
.setNewActivity(true)
.open(this);
}
/**
* 打开一个新的页面【建议只在主tab页使用】
*
* @param pageName 页面名
* @param <T>
* @return
*/
public <T extends XPageFragment> Fragment openNewPage(String pageName) {
return new PageOption(pageName)
.setAnim(CoreAnim.slide)
.setNewActivity(true)
.open(this);
}
/**
* 打开一个新的页面【建议只在主tab页使用】
*
* @param clazz 页面的类
* @param containActivityClazz 页面容器
* @param <T>
* @return
*/
public <T extends XPageFragment> Fragment openNewPage(Class<T> clazz, @NonNull Class<? extends XPageActivity> containActivityClazz) {
return new PageOption(clazz)
.setNewActivity(true)
.setContainActivityClazz(containActivityClazz)
.open(this);
}
/**
* 打开一个新的页面【建议只在主tab页使用】
*
* @param clazz 页面的类
* @param key 入参的键
* @param value 入参的值
* @param <T>
* @return
*/
public <T extends XPageFragment> Fragment openNewPage(Class<T> clazz, String key, Object value) {
PageOption option = new PageOption(clazz).setNewActivity(true);
return openPage(option, key, value);
}
public Fragment openPage(PageOption option, String key, Object value) {
if (value instanceof Integer) {
option.putInt(key, (Integer) value);
} else if (value instanceof Float) {
option.putFloat(key, (Float) value);
} else if (value instanceof String) {
option.putString(key, (String) value);
} else if (value instanceof Boolean) {
option.putBoolean(key, (Boolean) value);
} else if (value instanceof Long) {
option.putLong(key, (Long) value);
} else if (value instanceof Double) {
option.putDouble(key, (Double) value);
} else if (value instanceof Parcelable) {
option.putParcelable(key, (Parcelable) value);
} else if (value instanceof Serializable) {
option.putSerializable(key, (Serializable) value);
} else {
option.putString(key, serializeObject(value));
}
return option.open(this);
}
/**
* 打开页面
*
* @param clazz 页面的类
* @param addToBackStack 是否加入回退栈
* @param key 入参的键
* @param value 入参的值
* @param <T>
* @return
*/
public <T extends XPageFragment> Fragment openPage(Class<T> clazz, boolean addToBackStack, String key, String value) {
return new PageOption(clazz)
.setAddToBackStack(addToBackStack)
.putString(key, value)
.open(this);
}
/**
* 打开页面
*
* @param clazz 页面的类
* @param key 入参的键
* @param value 入参的值
* @param <T>
* @return
*/
public <T extends XPageFragment> Fragment openPage(Class<T> clazz, String key, Object value) {
return openPage(clazz, true, key, value);
}
/**
* 打开页面
*
* @param clazz 页面的类
* @param addToBackStack 是否加入回退栈
* @param key 入参的键
* @param value 入参的值
* @param <T>
* @return
*/
public <T extends XPageFragment> Fragment openPage(Class<T> clazz, boolean addToBackStack, String key, Object value) {
PageOption option = new PageOption(clazz).setAddToBackStack(addToBackStack);
return openPage(option, key, value);
}
/**
* 打开页面
*
* @param clazz 页面的类
* @param key 入参的键
* @param value 入参的值
* @param <T>
* @return
*/
public <T extends XPageFragment> Fragment openPage(Class<T> clazz, String key, String value) {
return new PageOption(clazz)
.putString(key, value)
.open(this);
}
/**
* 打开页面,需要结果返回
*
* @param clazz 页面的类
* @param key 入参的键
* @param value 入参的值
* @param requestCode 请求码
* @param <T>
* @return
*/
public <T extends XPageFragment> Fragment openPageForResult(Class<T> clazz, String key, Object value, int requestCode) {
PageOption option = new PageOption(clazz).setRequestCode(requestCode);
return openPage(option, key, value);
}
/**
* 打开页面,需要结果返回
*
* @param clazz 页面的类
* @param key 入参的键
* @param value 入参的值
* @param requestCode 请求码
* @param <T>
* @return
*/
public <T extends XPageFragment> Fragment openPageForResult(Class<T> clazz, String key, String value, int requestCode) {
return new PageOption(clazz)
.setRequestCode(requestCode)
.putString(key, value)
.open(this);
}
/**
* 打开页面,需要结果返回
*
* @param clazz 页面的类
* @param requestCode 请求码
* @param <T>
* @return
*/
public <T extends XPageFragment> Fragment openPageForResult(Class<T> clazz, int requestCode) {
return new PageOption(clazz)
.setRequestCode(requestCode)
.open(this);
}
/**
* 序列化对象
*
* @param object 需要序列化的对象
* @return 序列化结果
*/
public String serializeObject(Object object) {
return XRouter.getInstance().navigation(SerializationService.class).object2Json(object);
}
}

View File

@@ -0,0 +1,80 @@
/*
* Copyright (C) 2021 xuexiangjys(xuexiangjys@163.com)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
package com.kerwin.wumei.core;
import android.content.Context;
import android.view.View;
import android.widget.TextView;
import com.kerwin.wumei.R;
import com.xuexiang.xui.adapter.listview.BaseListAdapter;
import com.xuexiang.xutil.common.StringUtils;
import java.util.List;
import java.util.Map;
/**
* 主副标题显示适配器
*
* @author xuexiang
* @since 2018/12/19 上午12:19
*/
public class SimpleListAdapter extends BaseListAdapter<Map<String, String>, SimpleListAdapter.ViewHolder> {
public static final String KEY_TITLE = "key_title";
public static final String KEY_SUB_TITLE = "key_sub_title";
public SimpleListAdapter(Context context, List<Map<String, String>> data) {
super(context, data);
}
@Override
protected ViewHolder newViewHolder(View convertView) {
ViewHolder holder = new ViewHolder();
holder.mTvTitle = convertView.findViewById(R.id.device_item_title);
holder.mTvSubTitle = convertView.findViewById(R.id.tv_sub_title);
return holder;
}
@Override
protected int getLayoutId() {
return R.layout.adapter_item_simple_list_2;
}
@Override
protected void convert(ViewHolder holder, Map<String, String> item, int position) {
holder.mTvTitle.setText(item.get(KEY_TITLE));
if (!StringUtils.isEmpty(item.get(KEY_SUB_TITLE))) {
holder.mTvSubTitle.setText(item.get(KEY_SUB_TITLE));
holder.mTvSubTitle.setVisibility(View.VISIBLE);
} else {
holder.mTvSubTitle.setVisibility(View.GONE);
}
}
public static class ViewHolder {
/**
* 标题
*/
public TextView mTvTitle;
/**
* 副标题
*/
public TextView mTvSubTitle;
}
}

View File

@@ -0,0 +1,56 @@
/*
* Copyright (C) 2021 xuexiangjys(xuexiangjys@163.com)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
package com.kerwin.wumei.core;
import android.os.Bundle;
import com.kerwin.wumei.utils.XToastUtils;
import com.xuexiang.xrouter.annotation.AutoWired;
import com.xuexiang.xrouter.annotation.Router;
import com.xuexiang.xrouter.launcher.XRouter;
import com.xuexiang.xutil.common.StringUtils;
/**
* https://xuexiangjys.club/xpage/transfer?pageName=xxxxx&....
* applink的中转
*
* @author xuexiang
* @since 2019-07-06 9:37
*/
@Router(path = "/xpage/transfer")
public class XPageTransferActivity extends BaseActivity {
@AutoWired(name = "pageName")
String pageName;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
XRouter.getInstance().inject(this);
if (!StringUtils.isEmpty(pageName)) {
if (openPage(pageName, getIntent().getExtras()) == null) {
XToastUtils.error("页面未找到!");
finish();
}
} else {
XToastUtils.error("页面未找到!");
finish();
}
}
}

View File

@@ -0,0 +1,46 @@
/*
* Copyright (C) 2021 xuexiangjys(xuexiangjys@163.com)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
package com.kerwin.wumei.core.http.api;
import com.kerwin.wumei.core.http.entity.TipInfo;
import com.xuexiang.xhttp2.model.ApiResult;
import java.util.List;
import io.reactivex.Observable;
import retrofit2.http.GET;
/**
* @author xuexiang
* @since 2021/1/9 7:01 PM
*/
public class ApiService {
/**
* 使用的是retrofit的接口定义
*/
public interface IGetService {
/**
* 获得小贴士
*/
@GET("/xuexiangjys/Resource/raw/master/jsonapi/tips.json")
Observable<ApiResult<List<TipInfo>>> getTips();
}
}

View File

@@ -0,0 +1,59 @@
/*
* Copyright (C) 2021 xuexiangjys(xuexiangjys@163.com)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
package com.kerwin.wumei.core.http.callback;
import com.xuexiang.xhttp2.callback.SimpleCallBack;
import com.xuexiang.xhttp2.exception.ApiException;
import com.xuexiang.xhttp2.model.XHttpRequest;
import com.xuexiang.xutil.common.StringUtils;
import com.xuexiang.xutil.common.logger.Logger;
/**
* 不带错误提示的网络请求回调
*
* @author xuexiang
* @since 2019-11-18 23:02
*/
public abstract class NoTipCallBack<T> extends SimpleCallBack<T> {
/**
* 记录一下请求的url,确定出错的请求是哪个请求
*/
private String mUrl;
public NoTipCallBack() {
}
public NoTipCallBack(XHttpRequest req) {
this(req.getUrl());
}
public NoTipCallBack(String url) {
mUrl = url;
}
@Override
public void onError(ApiException e) {
if (!StringUtils.isEmpty(mUrl)) {
Logger.e("网络请求的url:" + mUrl, e);
} else {
Logger.e(e);
}
}
}

View File

@@ -0,0 +1,62 @@
/*
* Copyright (C) 2021 xuexiangjys(xuexiangjys@163.com)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
package com.kerwin.wumei.core.http.callback;
import com.kerwin.wumei.utils.XToastUtils;
import com.xuexiang.xhttp2.callback.SimpleCallBack;
import com.xuexiang.xhttp2.exception.ApiException;
import com.xuexiang.xhttp2.model.XHttpRequest;
import com.xuexiang.xutil.common.StringUtils;
import com.xuexiang.xutil.common.logger.Logger;
/**
* 带错误toast提示的网络请求回调
*
* @author xuexiang
* @since 2019-11-18 23:02
*/
public abstract class TipCallBack<T> extends SimpleCallBack<T> {
/**
* 记录一下请求的url,确定出错的请求是哪个请求
*/
private String mUrl;
public TipCallBack() {
}
public TipCallBack(XHttpRequest req) {
this(req.getUrl());
}
public TipCallBack(String url) {
mUrl = url;
}
@Override
public void onError(ApiException e) {
XToastUtils.error(e);
if (!StringUtils.isEmpty(mUrl)) {
Logger.e("网络请求的url:" + mUrl, e);
} else {
Logger.e(e);
}
}
}

View File

@@ -0,0 +1,70 @@
/*
* Copyright (C) 2021 xuexiangjys(xuexiangjys@163.com)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
package com.kerwin.wumei.core.http.callback;
import androidx.annotation.NonNull;
import com.kerwin.wumei.core.BaseFragment;
import com.kerwin.wumei.utils.XToastUtils;
import com.xuexiang.xhttp2.callback.ProgressLoadingCallBack;
import com.xuexiang.xhttp2.exception.ApiException;
import com.xuexiang.xhttp2.model.XHttpRequest;
import com.xuexiang.xhttp2.subsciber.impl.IProgressLoader;
import com.xuexiang.xutil.common.StringUtils;
import com.xuexiang.xutil.common.logger.Logger;
/**
* 带错误toast提示和加载进度条的网络请求回调
*
* @author xuexiang
* @since 2019-11-18 23:16
*/
public abstract class TipProgressLoadingCallBack<T> extends ProgressLoadingCallBack<T> {
/**
* 记录一下请求的url,确定出错的请求是哪个请求
*/
private String mUrl;
public TipProgressLoadingCallBack(BaseFragment fragment) {
super(fragment.getProgressLoader());
}
public TipProgressLoadingCallBack(IProgressLoader iProgressLoader) {
super(iProgressLoader);
}
public TipProgressLoadingCallBack(@NonNull XHttpRequest req, IProgressLoader iProgressLoader) {
this(req.getUrl(), iProgressLoader);
}
public TipProgressLoadingCallBack(String url, IProgressLoader iProgressLoader) {
super(iProgressLoader);
mUrl = url;
}
@Override
public void onError(ApiException e) {
super.onError(e);
XToastUtils.error(e);
if (!StringUtils.isEmpty(mUrl)) {
Logger.e("网络请求的url:" + mUrl, e);
} else {
Logger.e(e);
}
}
}

View File

@@ -0,0 +1,60 @@
/*
* Copyright (C) 2021 xuexiangjys(xuexiangjys@163.com)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
package com.kerwin.wumei.core.http.entity;
import androidx.annotation.Keep;
/**
* @author xuexiang
* @since 2019-08-28 15:35
*/
@Keep
public class TipInfo {
/**
* title : 小贴士3
* content : <p style=";font-family:'Microsoft YaHei';font-size:15px">欢迎关注我的微信公众号我的Android开源之旅。</p><p><br/></p>
*/
private String title;
private String content;
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
public String getContent() {
return content;
}
public void setContent(String content) {
this.content = content;
}
@Override
public String toString() {
return "TipInfo{" +
"title='" + title + '\'' +
", content='" + content + '\'' +
'}';
}
}

View File

@@ -0,0 +1,50 @@
/*
* Copyright (C) 2021 xuexiangjys(xuexiangjys@163.com)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
package com.kerwin.wumei.core.http.loader;
import android.content.Context;
import com.xuexiang.xhttp2.subsciber.impl.IProgressLoader;
/**
* IProgressLoader的创建工厂实现接口
*
* @author xuexiang
* @since 2019-11-18 23:17
*/
public interface IProgressLoaderFactory {
/**
* 创建进度加载者
*
* @param context
* @return
*/
IProgressLoader create(Context context);
/**
* 创建进度加载者
*
* @param context
* @param message 默认提示
* @return
*/
IProgressLoader create(Context context, String message);
}

View File

@@ -0,0 +1,96 @@
/*
* Copyright (C) 2021 xuexiangjys(xuexiangjys@163.com)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
package com.kerwin.wumei.core.http.loader;
import android.content.Context;
import android.content.DialogInterface;
import com.xuexiang.xhttp2.subsciber.impl.IProgressLoader;
import com.xuexiang.xhttp2.subsciber.impl.OnProgressCancelListener;
import com.xuexiang.xui.widget.dialog.MiniLoadingDialog;
/**
* 默认进度加载
*
* @author xuexiang
* @since 2019-11-18 23:07
*/
public class MiniLoadingDialogLoader implements IProgressLoader {
/**
* 进度loading弹窗
*/
private MiniLoadingDialog mDialog;
/**
* 进度框取消监听
*/
private OnProgressCancelListener mOnProgressCancelListener;
public MiniLoadingDialogLoader(Context context) {
this(context, "请求中...");
}
public MiniLoadingDialogLoader(Context context, String msg) {
mDialog = new MiniLoadingDialog(context, msg);
}
@Override
public boolean isLoading() {
return mDialog != null && mDialog.isShowing();
}
@Override
public void updateMessage(String msg) {
if (mDialog != null) {
mDialog.updateMessage(msg);
}
}
@Override
public void showLoading() {
if (mDialog != null && !mDialog.isShowing()) {
mDialog.show();
}
}
@Override
public void dismissLoading() {
if (mDialog != null && mDialog.isShowing()) {
mDialog.dismiss();
}
}
@Override
public void setCancelable(boolean flag) {
mDialog.setCancelable(flag);
if (flag) {
mDialog.setOnCancelListener(new DialogInterface.OnCancelListener() {
@Override
public void onCancel(DialogInterface dialogInterface) {
if (mOnProgressCancelListener != null) {
mOnProgressCancelListener.onCancelProgress();
}
}
});
}
}
@Override
public void setOnProgressCancelListener(OnProgressCancelListener listener) {
mOnProgressCancelListener = listener;
}
}

View File

@@ -0,0 +1,41 @@
/*
* Copyright (C) 2021 xuexiangjys(xuexiangjys@163.com)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
package com.kerwin.wumei.core.http.loader;
import android.content.Context;
import com.xuexiang.xhttp2.subsciber.impl.IProgressLoader;
/**
* 迷你加载框创建工厂
*
* @author xuexiang
* @since 2019-11-18 23:23
*/
public class MiniProgressLoaderFactory implements IProgressLoaderFactory {
@Override
public IProgressLoader create(Context context) {
return new MiniLoadingDialogLoader(context);
}
@Override
public IProgressLoader create(Context context, String message) {
return new MiniLoadingDialogLoader(context, message);
}
}

View File

@@ -0,0 +1,62 @@
/*
* Copyright (C) 2021 xuexiangjys(xuexiangjys@163.com)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
package com.kerwin.wumei.core.http.loader;
import android.content.Context;
import com.xuexiang.xhttp2.subsciber.impl.IProgressLoader;
/**
* 创建进度加载者
*
* @author xuexiang
* @since 2019-07-02 12:51
*/
public final class ProgressLoader {
private ProgressLoader() {
throw new UnsupportedOperationException("u can't instantiate me...");
}
private static IProgressLoaderFactory sIProgressLoaderFactory = new MiniProgressLoaderFactory();
public static void setIProgressLoaderFactory(IProgressLoaderFactory sIProgressLoaderFactory) {
ProgressLoader.sIProgressLoaderFactory = sIProgressLoaderFactory;
}
/**
* 创建进度加载者
*
* @param context
* @return
*/
public static IProgressLoader create(Context context) {
return sIProgressLoaderFactory.create(context);
}
/**
* 创建进度加载者
*
* @param context
* @param message 默认提示信息
* @return
*/
public static IProgressLoader create(Context context, String message) {
return sIProgressLoaderFactory.create(context, message);
}
}

View File

@@ -0,0 +1,59 @@
/*
* Copyright (C) 2021 xuexiangjys(xuexiangjys@163.com)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
package com.kerwin.wumei.core.http.subscriber;
import com.xuexiang.xhttp2.exception.ApiException;
import com.xuexiang.xhttp2.model.XHttpRequest;
import com.xuexiang.xhttp2.subsciber.BaseSubscriber;
import com.xuexiang.xutil.common.StringUtils;
import com.xuexiang.xutil.common.logger.Logger;
/**
* 不带错误toast提示的网络请求订阅只存储错误的日志
*
* @author xuexiang
* @since 2019-11-18 23:11
*/
public abstract class NoTipRequestSubscriber<T> extends BaseSubscriber<T> {
/**
* 记录一下请求的url,确定出错的请求是哪个请求
*/
private String mUrl;
public NoTipRequestSubscriber() {
}
public NoTipRequestSubscriber(XHttpRequest req) {
this(req.getUrl());
}
public NoTipRequestSubscriber(String url) {
mUrl = url;
}
@Override
public void onError(ApiException e) {
if (!StringUtils.isEmpty(mUrl)) {
Logger.e("网络请求的url:" + mUrl, e);
} else {
Logger.e(e);
}
}
}

View File

@@ -0,0 +1,75 @@
/*
* Copyright (C) 2021 xuexiangjys(xuexiangjys@163.com)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
package com.kerwin.wumei.core.http.subscriber;
import androidx.annotation.NonNull;
import com.kerwin.wumei.core.BaseFragment;
import com.kerwin.wumei.utils.XToastUtils;
import com.xuexiang.xhttp2.exception.ApiException;
import com.xuexiang.xhttp2.model.XHttpRequest;
import com.xuexiang.xhttp2.subsciber.ProgressLoadingSubscriber;
import com.xuexiang.xhttp2.subsciber.impl.IProgressLoader;
import com.xuexiang.xutil.common.StringUtils;
import com.xuexiang.xutil.common.logger.Logger;
/**
* 带错误toast提示和加载进度条的网络请求订阅
*
* @author xuexiang
* @since 2019-11-18 23:11
*/
public abstract class TipProgressLoadingSubscriber<T> extends ProgressLoadingSubscriber<T> {
/**
* 记录一下请求的url,确定出错的请求是哪个请求
*/
private String mUrl;
public TipProgressLoadingSubscriber() {
super();
}
public TipProgressLoadingSubscriber(BaseFragment fragment) {
super(fragment.getProgressLoader());
}
public TipProgressLoadingSubscriber(IProgressLoader iProgressLoader) {
super(iProgressLoader);
}
public TipProgressLoadingSubscriber(@NonNull XHttpRequest req) {
this(req.getUrl());
}
public TipProgressLoadingSubscriber(String url) {
super();
mUrl = url;
}
@Override
public void onError(ApiException e) {
super.onError(e);
XToastUtils.error(e);
if (!StringUtils.isEmpty(mUrl)) {
Logger.e("网络请求的url:" + mUrl, e);
} else {
Logger.e(e);
}
}
}

View File

@@ -0,0 +1,64 @@
/*
* Copyright (C) 2021 xuexiangjys(xuexiangjys@163.com)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
package com.kerwin.wumei.core.http.subscriber;
import androidx.annotation.NonNull;
import com.kerwin.wumei.utils.XToastUtils;
import com.xuexiang.xhttp2.exception.ApiException;
import com.xuexiang.xhttp2.model.XHttpRequest;
import com.xuexiang.xhttp2.subsciber.BaseSubscriber;
import com.xuexiang.xutil.common.StringUtils;
import com.xuexiang.xutil.common.logger.Logger;
/**
* 带错误toast提示的网络请求订阅
*
* @author xuexiang
* @since 2019-11-18 23:10
*/
public abstract class TipRequestSubscriber<T> extends BaseSubscriber<T> {
/**
* 记录一下请求的url,确定出错的请求是哪个请求
*/
private String mUrl;
public TipRequestSubscriber() {
}
public TipRequestSubscriber(@NonNull XHttpRequest req) {
this(req.getUrl());
}
public TipRequestSubscriber(String url) {
mUrl = url;
}
@Override
public void onError(ApiException e) {
XToastUtils.error(e);
if (!StringUtils.isEmpty(mUrl)) {
Logger.e("网络请求的url:" + mUrl, e);
} else {
Logger.e(e);
}
}
}

View File

@@ -0,0 +1,127 @@
/*
* Copyright (C) 2021 xuexiangjys(xuexiangjys@163.com)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
package com.kerwin.wumei.core.webview;
import android.content.Context;
import android.content.Intent;
import android.net.Uri;
import android.os.Bundle;
import android.view.KeyEvent;
import androidx.annotation.Nullable;
import androidx.appcompat.app.AppCompatActivity;
import androidx.fragment.app.FragmentTransaction;
import com.kerwin.wumei.R;
import com.kerwin.wumei.utils.XToastUtils;
import com.xuexiang.xrouter.facade.Postcard;
import com.xuexiang.xrouter.facade.callback.NavCallback;
import com.xuexiang.xrouter.launcher.XRouter;
import com.xuexiang.xui.widget.slideback.SlideBack;
/**
* 壳浏览器
*
* @author xuexiang
* @since 2019/1/5 上午12:15
*/
public class AgentWebActivity extends AppCompatActivity {
/**
* 请求浏览器
*
* @param url
*/
public static void goWeb(Context context, final String url) {
Intent intent = new Intent(context, AgentWebActivity.class);
intent.putExtra(AgentWebFragment.KEY_URL, url);
context.startActivity(intent);
}
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_agent_web);
SlideBack.with(this)
.haveScroll(true)
.callBack(this::finish)
.register();
Uri uri = getIntent().getData();
if (uri != null) {
XRouter.getInstance().build(uri).navigation(this, new NavCallback() {
@Override
public void onArrival(Postcard postcard) {
finish();
}
@Override
public void onLost(Postcard postcard) {
loadUrl(uri.toString());
}
});
} else {
String url = getIntent().getStringExtra(AgentWebFragment.KEY_URL);
loadUrl(url);
}
}
private void loadUrl(String url) {
if (url != null) {
openFragment(url);
} else {
XToastUtils.error("数据出错!");
finish();
}
}
private AgentWebFragment mAgentWebFragment;
private void openFragment(String url) {
FragmentTransaction ft = getSupportFragmentManager().beginTransaction();
ft.add(R.id.container_frame_layout, mAgentWebFragment = AgentWebFragment.getInstance(url));
ft.commit();
}
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
}
@Override
public boolean onKeyDown(int keyCode, KeyEvent event) {
AgentWebFragment agentWebFragment = mAgentWebFragment;
if (agentWebFragment != null) {
if (((FragmentKeyDown) agentWebFragment).onFragmentKeyDown(keyCode, event)) {
return true;
} else {
return super.onKeyDown(keyCode, event);
}
}
return super.onKeyDown(keyCode, event);
}
@Override
protected void onDestroy() {
SlideBack.unregister(this);
super.onDestroy();
}
}

View File

@@ -0,0 +1,658 @@
/*
* Copyright (C) 2021 xuexiangjys(xuexiangjys@163.com)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
package com.kerwin.wumei.core.webview;
import android.content.ClipData;
import android.content.ClipboardManager;
import android.content.Context;
import android.content.Intent;
import android.graphics.Bitmap;
import android.graphics.Color;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.text.TextUtils;
import android.util.Log;
import android.view.Gravity;
import android.view.KeyEvent;
import android.view.LayoutInflater;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import android.webkit.WebChromeClient;
import android.webkit.WebResourceError;
import android.webkit.WebResourceRequest;
import android.webkit.WebResourceResponse;
import android.webkit.WebView;
import android.webkit.WebViewClient;
import android.widget.FrameLayout;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.TextView;
import androidx.annotation.Nullable;
import androidx.annotation.RequiresApi;
import androidx.appcompat.widget.PopupMenu;
import androidx.fragment.app.Fragment;
import com.just.agentweb.action.PermissionInterceptor;
import com.just.agentweb.core.AgentWeb;
import com.just.agentweb.core.client.MiddlewareWebChromeBase;
import com.just.agentweb.core.client.MiddlewareWebClientBase;
import com.just.agentweb.core.client.WebListenerManager;
import com.just.agentweb.core.web.AbsAgentWebSettings;
import com.just.agentweb.core.web.AgentWebConfig;
import com.just.agentweb.core.web.IAgentWebSettings;
import com.just.agentweb.download.AgentWebDownloader;
import com.just.agentweb.download.DefaultDownloadImpl;
import com.just.agentweb.download.DownloadListenerAdapter;
import com.just.agentweb.download.DownloadingService;
import com.just.agentweb.utils.LogUtils;
import com.just.agentweb.widget.IWebLayout;
import com.kerwin.wumei.MyApp;
import com.kerwin.wumei.R;
import com.kerwin.wumei.utils.XToastUtils;
import com.xuexiang.xutil.net.JsonUtil;
import java.util.HashMap;
/**
* 通用WebView页面
*
* @author xuexiang
* @since 2019/1/4 下午11:13
*/
public class AgentWebFragment extends Fragment implements FragmentKeyDown {
public static final String KEY_URL = "com.xuexiang.xuidemo.base.webview.key_url";
private ImageView mBackImageView;
private View mLineView;
private ImageView mFinishImageView;
private TextView mTitleTextView;
private AgentWeb mAgentWeb;
private ImageView mMoreImageView;
private PopupMenu mPopupMenu;
public static final String TAG = AgentWebFragment.class.getSimpleName();
private DownloadingService mDownloadingService;
public static AgentWebFragment getInstance(String url) {
Bundle bundle = new Bundle();
bundle.putString(KEY_URL, url);
return getInstance(bundle);
}
public static AgentWebFragment getInstance(Bundle bundle) {
AgentWebFragment fragment = new AgentWebFragment();
if (bundle != null) {
fragment.setArguments(bundle);
}
return fragment;
}
@Nullable
@Override
public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
return inflater.inflate(R.layout.fragment_agentweb, container, false);
}
@Override
public void onViewCreated(View view, @Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
mAgentWeb = AgentWeb.with(this)
//传入AgentWeb的父控件。
.setAgentWebParent((LinearLayout) view, -1, new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT))
//设置进度条颜色与高度,-1为默认值高度为2单位为dp。
.useDefaultIndicator(-1, 3)
//设置 IAgentWebSettings。
.setAgentWebWebSettings(getSettings())
//WebViewClient 与 WebView 使用一致 但是请勿获取WebView调用setWebViewClient(xx)方法了,会覆盖AgentWeb DefaultWebClient,同时相应的中间件也会失效。
.setWebViewClient(mWebViewClient)
//WebChromeClient
.setWebChromeClient(mWebChromeClient)
//设置WebChromeClient中间件支持多个WebChromeClientAgentWeb 3.0.0 加入。
.useMiddlewareWebChrome(getMiddlewareWebChrome())
//设置WebViewClient中间件支持多个WebViewClient AgentWeb 3.0.0 加入。
.useMiddlewareWebClient(getMiddlewareWebClient())
//权限拦截 2.0.0 加入。
.setPermissionInterceptor(mPermissionInterceptor)
//严格模式 Android 4.2.2 以下会放弃注入对象 使用AgentWebView没影响。
.setSecurityType(AgentWeb.SecurityType.STRICT_CHECK)
//自定义UI AgentWeb3.0.0 加入。
.setAgentWebUIController(new UIController(getActivity()))
//参数1是错误显示的布局参数2点击刷新控件ID -1表示点击整个布局都刷新 AgentWeb 3.0.0 加入。
.setMainFrameErrorView(R.layout.agentweb_error_page, -1)
.setWebLayout(getWebLayout())
.interceptUnkownUrl()
//创建AgentWeb。
.createAgentWeb()
.ready()//设置 WebSettings。
//WebView载入该url地址的页面并显示。
.go(getUrl());
if (MyApp.isDebug()) {
AgentWebConfig.debug();
}
// 得到 AgentWeb 最底层的控件
addBackgroundChild(mAgentWeb.getWebCreator().getWebParentLayout());
initView(view);
// AgentWeb 没有把WebView的功能全面覆盖 ,所以某些设置 AgentWeb 没有提供请从WebView方面入手设置。
mAgentWeb.getWebCreator().getWebView().setOverScrollMode(WebView.OVER_SCROLL_NEVER);
}
protected IWebLayout getWebLayout() {
return new WebLayout(getActivity());
}
protected void initView(View view) {
mBackImageView = view.findViewById(R.id.iv_back);
mLineView = view.findViewById(R.id.view_line);
mFinishImageView = view.findViewById(R.id.iv_finish);
mTitleTextView = view.findViewById(R.id.toolbar_title);
mBackImageView.setOnClickListener(mOnClickListener);
mFinishImageView.setOnClickListener(mOnClickListener);
mMoreImageView = view.findViewById(R.id.iv_more);
mMoreImageView.setOnClickListener(mOnClickListener);
pageNavigator(View.GONE);
}
protected void addBackgroundChild(FrameLayout frameLayout) {
TextView textView = new TextView(frameLayout.getContext());
textView.setText("技术由 AgentWeb 提供");
textView.setTextSize(16);
textView.setTextColor(Color.parseColor("#727779"));
frameLayout.setBackgroundColor(Color.parseColor("#272b2d"));
FrameLayout.LayoutParams params = new FrameLayout.LayoutParams(-2, -2);
params.gravity = Gravity.CENTER_HORIZONTAL;
final float scale = frameLayout.getContext().getResources().getDisplayMetrics().density;
params.topMargin = (int) (15 * scale + 0.5f);
frameLayout.addView(textView, 0, params);
}
private void pageNavigator(int tag) {
mBackImageView.setVisibility(tag);
mLineView.setVisibility(tag);
}
private View.OnClickListener mOnClickListener = new View.OnClickListener() {
@Override
public void onClick(View v) {
switch (v.getId()) {
case R.id.iv_back:
// true表示AgentWeb处理了该事件
if (!mAgentWeb.back()) {
AgentWebFragment.this.getActivity().finish();
}
break;
case R.id.iv_finish:
AgentWebFragment.this.getActivity().finish();
break;
case R.id.iv_more:
showPoPup(v);
break;
default:
break;
}
}
};
//========================================//
/**
* 权限申请拦截器
*/
protected PermissionInterceptor mPermissionInterceptor = new PermissionInterceptor() {
/**
* PermissionInterceptor 能达到 url1 允许授权, url2 拒绝授权的效果。
* @param url
* @param permissions
* @param action
* @return true 该Url对应页面请求权限进行拦截 false 表示不拦截。
*/
@Override
public boolean intercept(String url, String[] permissions, String action) {
Log.i(TAG, "mUrl:" + url + " permission:" + JsonUtil.toJson(permissions) + " action:" + action);
return false;
}
};
//=====================下载============================//
/**
* 更新于 AgentWeb 4.0.0,下载监听
*/
protected DownloadListenerAdapter mDownloadListenerAdapter = new DownloadListenerAdapter() {
/**
*
* @param url 下载链接
* @param userAgent UserAgent
* @param contentDisposition ContentDisposition
* @param mimetype 资源的媒体类型
* @param contentLength 文件长度
* @param extra 下载配置 用户可以通过 Extra 修改下载icon 关闭进度条 是否强制下载。
* @return true 表示用户处理了该下载事件 false 交给 AgentWeb 下载
*/
@Override
public boolean onStart(String url, String userAgent, String contentDisposition, String mimetype, long contentLength, AgentWebDownloader.Extra extra) {
LogUtils.i(TAG, "onStart:" + url);
// 是否开启断点续传
extra.setOpenBreakPointDownload(true)
//下载通知的icon
.setIcon(R.drawable.ic_file_download_black_24dp)
// 连接的超时时间
.setConnectTimeOut(6000)
// 以8KB位单位默认60s 如果60s内无法从网络流中读满8KB数据则抛出异常
.setBlockMaxTime(10 * 60 * 1000)
// 下载的超时时间
.setDownloadTimeOut(Long.MAX_VALUE)
// 串行下载更节省资源哦
.setParallelDownload(false)
// false 关闭进度通知
.setEnableIndicator(true)
// 自定义请求头
.addHeader("Cookie", "xx")
// 下载完成自动打开
.setAutoOpen(true)
// 强制下载,不管网络网络类型
.setForceDownload(true);
return false;
}
/**
*
* 不需要暂停或者停止下载该方法可以不必实现
* @param url
* @param downloadingService 用户可以通过 DownloadingService#shutdownNow 终止下载
*/
@Override
public void onBindService(String url, DownloadingService downloadingService) {
super.onBindService(url, downloadingService);
mDownloadingService = downloadingService;
LogUtils.i(TAG, "onBindService:" + url + " DownloadingService:" + downloadingService);
}
/**
* 回调onUnbindService方法让用户释放掉 DownloadingService。
* @param url
* @param downloadingService
*/
@Override
public void onUnbindService(String url, DownloadingService downloadingService) {
super.onUnbindService(url, downloadingService);
mDownloadingService = null;
LogUtils.i(TAG, "onUnbindService:" + url);
}
/**
*
* @param url 下载链接
* @param loaded 已经下载的长度
* @param length 文件的总大小
* @param usedTime 耗时 单位ms
* 注意该方法回调在子线程 ,线程名 AsyncTask #XX 或者 AgentWeb # XX
*/
@Override
public void onProgress(String url, long loaded, long length, long usedTime) {
int mProgress = (int) ((loaded) / Float.valueOf(length) * 100);
LogUtils.i(TAG, "onProgress:" + mProgress);
super.onProgress(url, loaded, length, usedTime);
}
/**
*
* @param path 文件的绝对路径
* @param url 下载地址
* @param throwable 如果异常,返回给用户异常
* @return true 表示用户处理了下载完成后续的事件 false 默认交给AgentWeb 处理
*/
@Override
public boolean onResult(String path, String url, Throwable throwable) {
//下载成功
if (null == throwable) {
//do you work
} else {//下载失败
}
// true 不会发出下载完成的通知 , 或者打开文件
return false;
}
};
/**
* @return IAgentWebSettings
*/
public IAgentWebSettings getSettings() {
return new AbsAgentWebSettings() {
private AgentWeb mAgentWeb;
@Override
protected void bindAgentWebSupport(AgentWeb agentWeb) {
this.mAgentWeb = agentWeb;
}
/**
* AgentWeb 4.0.0 内部删除了 DownloadListener 监听 以及相关API ,将 Download 部分完全抽离出来独立一个库,
* 如果你需要使用 AgentWeb Download 部分 请依赖上 compile 'com.just.agentweb:download:4.0.0
* 如果你需要监听下载结果,请自定义 AgentWebSetting New 出 DefaultDownloadImpl传入DownloadListenerAdapter
* 实现进度或者结果监听,例如下面这个例子,如果你不需要监听进度,或者下载结果,下面 setDownloader 的例子可以忽略。
* @param webView
* @param downloadListener
* @return WebListenerManager
*/
@Override
public WebListenerManager setDownloader(WebView webView, android.webkit.DownloadListener downloadListener) {
return super.setDownloader(webView,
DefaultDownloadImpl
.create(getActivity(),
webView,
mDownloadListenerAdapter,
mDownloadListenerAdapter,
this.mAgentWeb.getPermissionInterceptor()));
}
};
}
//===================WebChromeClient 和 WebViewClient===========================//
/**
* 页面空白请检查scheme是否加上 scheme://host:port/path?query&query 。
*
* @return mUrl
*/
public String getUrl() {
String target = "";
Bundle bundle = getArguments();
if (bundle != null) {
target = bundle.getString(KEY_URL);
}
if (TextUtils.isEmpty(target)) {
target = "https://github.com/xuexiangjys";
}
return target;
}
protected WebChromeClient mWebChromeClient = new WebChromeClient() {
@Override
public void onProgressChanged(WebView view, int newProgress) {
Log.i(TAG, "onProgressChanged:" + newProgress + " view:" + view);
}
@Override
public void onReceivedTitle(WebView view, String title) {
super.onReceivedTitle(view, title);
if (mTitleTextView != null && !TextUtils.isEmpty(title)) {
if (title.length() > 10) {
title = title.substring(0, 10).concat("...");
}
mTitleTextView.setText(title);
}
}
};
protected WebViewClient mWebViewClient = new WebViewClient() {
private HashMap<String, Long> timer = new HashMap<>();
@Override
public void onReceivedError(WebView view, WebResourceRequest request, WebResourceError error) {
super.onReceivedError(view, request, error);
}
@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
@Override
public boolean shouldOverrideUrlLoading(WebView view, WebResourceRequest request) {
return shouldOverrideUrlLoading(view, request.getUrl() + "");
}
@Nullable
@Override
public WebResourceResponse shouldInterceptRequest(WebView view, WebResourceRequest request) {
return super.shouldInterceptRequest(view, request);
}
//
@Override
public boolean shouldOverrideUrlLoading(final WebView view, String url) {
//intent:// scheme的处理 如果返回false 则交给 DefaultWebClient 处理 默认会打开该Activity 如果Activity不存在则跳到应用市场上去. true 表示拦截
//例如优酷视频播放 intent://play?...package=com.youku.phone;end;
//优酷想唤起自己应用播放该视频 下面拦截地址返回 true 则会在应用内 H5 播放 ,禁止优酷唤起播放该视频, 如果返回 false DefaultWebClient 会根据intent 协议处理 该地址 首先匹配该应用存不存在 ,如果存在 唤起该应用播放 如果不存在 则跳到应用市场下载该应用 .
if (url.startsWith("intent://") && url.contains("com.youku.phone")) {
return true;
}
return false;
}
@Override
public void onPageStarted(WebView view, String url, Bitmap favicon) {
Log.i(TAG, "mUrl:" + url + " onPageStarted target:" + getUrl());
timer.put(url, System.currentTimeMillis());
if (url.equals(getUrl())) {
pageNavigator(View.GONE);
} else {
pageNavigator(View.VISIBLE);
}
}
@Override
public void onPageFinished(WebView view, String url) {
super.onPageFinished(view, url);
if (timer.get(url) != null) {
long overTime = System.currentTimeMillis();
Long startTime = timer.get(url);
Log.i(TAG, " page mUrl:" + url + " used time:" + (overTime - startTime));
}
}
@Override
public void onReceivedHttpError(WebView view, WebResourceRequest request, WebResourceResponse errorResponse) {
super.onReceivedHttpError(view, request, errorResponse);
}
@Override
public void onReceivedError(WebView view, int errorCode, String description, String failingUrl) {
super.onReceivedError(view, errorCode, description, failingUrl);
}
};
@Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
}
//========================菜单功能================================//
/**
* 打开浏览器
*
* @param targetUrl 外部浏览器打开的地址
*/
private void openBrowser(String targetUrl) {
if (TextUtils.isEmpty(targetUrl) || targetUrl.startsWith("file://")) {
XToastUtils.toast(targetUrl + " 该链接无法使用浏览器打开。");
return;
}
Intent intent = new Intent();
intent.setAction("android.intent.action.VIEW");
Uri uri = Uri.parse(targetUrl);
intent.setData(uri);
startActivity(intent);
}
/**
* 显示更多菜单
*
* @param view 菜单依附在该View下面
*/
private void showPoPup(View view) {
if (mPopupMenu == null) {
mPopupMenu = new PopupMenu(getContext(), view);
mPopupMenu.inflate(R.menu.menu_toolbar_web);
mPopupMenu.setOnMenuItemClickListener(mOnMenuItemClickListener);
}
mPopupMenu.show();
}
/**
* 菜单事件
*/
private PopupMenu.OnMenuItemClickListener mOnMenuItemClickListener = new PopupMenu.OnMenuItemClickListener() {
@Override
public boolean onMenuItemClick(MenuItem item) {
switch (item.getItemId()) {
case R.id.refresh:
if (mAgentWeb != null) {
mAgentWeb.getUrlLoader().reload(); // 刷新
}
return true;
case R.id.copy:
if (mAgentWeb != null) {
toCopy(getContext(), mAgentWeb.getWebCreator().getWebView().getUrl());
}
return true;
case R.id.default_browser:
if (mAgentWeb != null) {
openBrowser(mAgentWeb.getWebCreator().getWebView().getUrl());
}
return true;
case R.id.share:
if (mAgentWeb != null) {
shareWebUrl(mAgentWeb.getWebCreator().getWebView().getUrl());
}
return true;
default:
return false;
}
}
};
/**
* 分享网页链接
*
* @param url 网页链接
*/
private void shareWebUrl(String url) {
Intent shareIntent = new Intent();
shareIntent.setAction(Intent.ACTION_SEND);
shareIntent.putExtra(Intent.EXTRA_TEXT, url);
shareIntent.setType("text/plain");
//设置分享列表的标题,并且每次都显示分享列表
startActivity(Intent.createChooser(shareIntent, "分享到"));
}
/**
* 复制字符串
*
* @param context
* @param text
*/
private void toCopy(Context context, String text) {
ClipboardManager manager = (ClipboardManager) context.getSystemService(Context.CLIPBOARD_SERVICE);
if (manager == null) {
return;
}
manager.setPrimaryClip(ClipData.newPlainText(null, text));
}
//===================生命周期管理===========================//
@Override
public void onResume() {
mAgentWeb.getWebLifeCycle().onResume();//恢复
super.onResume();
}
@Override
public void onPause() {
mAgentWeb.getWebLifeCycle().onPause(); //暂停应用内所有WebView 调用mWebView.resumeTimers();/mAgentWeb.getWebLifeCycle().onResume(); 恢复。
super.onPause();
}
@Override
public boolean onFragmentKeyDown(int keyCode, KeyEvent event) {
return mAgentWeb.handleKeyEvent(keyCode, event);
}
@Override
public void onDestroyView() {
mAgentWeb.getWebLifeCycle().onDestroy();
super.onDestroyView();
}
//===================中间键===========================//
/**
* MiddlewareWebClientBase 是 AgentWeb 3.0.0 提供一个强大的功能,
* 如果用户需要使用 AgentWeb 提供的功能, 不想重写 WebClientView方
* 法覆盖AgentWeb提供的功能那么 MiddlewareWebClientBase 是一个
* 不错的选择 。
*
* @return
*/
protected MiddlewareWebClientBase getMiddlewareWebClient() {
return new MiddlewareWebViewClient() {
/**
*
* @param view
* @param url
* @return
*/
@Override
public boolean shouldOverrideUrlLoading(WebView view, String url) {
// 拦截 url不执行 DefaultWebClient#shouldOverrideUrlLoading
if (url.startsWith("agentweb")) {
Log.i(TAG, "agentweb scheme ~");
return true;
}
// 执行 DefaultWebClient#shouldOverrideUrlLoading
if (super.shouldOverrideUrlLoading(view, url)) {
return true;
}
// do you work
return false;
}
@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
@Override
public boolean shouldOverrideUrlLoading(WebView view, WebResourceRequest request) {
return super.shouldOverrideUrlLoading(view, request);
}
};
}
protected MiddlewareWebChromeBase getMiddlewareWebChrome() {
return new MiddlewareChromeClient() {
};
}
}

View File

@@ -0,0 +1,66 @@
/*
* Copyright (C) 2021 xuexiangjys(xuexiangjys@163.com)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
package com.kerwin.wumei.core.webview;
import android.view.KeyEvent;
import com.just.agentweb.core.AgentWeb;
import com.kerwin.wumei.core.BaseFragment;
/**
* 基础web
*
* @author xuexiang
* @since 2019/5/28 10:22
*/
public abstract class BaseWebViewFragment extends BaseFragment {
protected AgentWeb mAgentWeb;
//===================生命周期管理===========================//
@Override
public void onResume() {
if (mAgentWeb != null) {
//恢复
mAgentWeb.getWebLifeCycle().onResume();
}
super.onResume();
}
@Override
public void onPause() {
if (mAgentWeb != null) {
//暂停应用内所有WebView 调用mWebView.resumeTimers();/mAgentWeb.getWebLifeCycle().onResume(); 恢复。
mAgentWeb.getWebLifeCycle().onPause();
}
super.onPause();
}
@Override
public boolean onKeyDown(int keyCode, KeyEvent event) {
return mAgentWeb != null && mAgentWeb.handleKeyEvent(keyCode, event);
}
@Override
public void onDestroyView() {
if (mAgentWeb != null) {
mAgentWeb.destroy();
}
super.onDestroyView();
}
}

View File

@@ -0,0 +1,37 @@
/*
* Copyright (C) 2021 xuexiangjys(xuexiangjys@163.com)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
package com.kerwin.wumei.core.webview;
import android.view.KeyEvent;
/**
*
*
* @author xuexiang
* @since 2019/1/4 下午11:32
*/
public interface FragmentKeyDown {
/**
* fragment按键监听
* @param keyCode
* @param event
* @return
*/
boolean onFragmentKeyDown(int keyCode, KeyEvent event);
}

View File

@@ -0,0 +1,66 @@
/*
* Copyright (C) 2021 xuexiangjys(xuexiangjys@163.com)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
package com.kerwin.wumei.core.webview;
import android.annotation.TargetApi;
import android.content.Context;
import android.content.res.Configuration;
import android.os.Build;
import android.util.AttributeSet;
import android.webkit.WebView;
/**
* 修复 Android 5.0 & 5.1 打开 WebView 闪退问题:
* 参阅 https://stackoverflow.com/questions/41025200/android-view-inflateexception-error-inflating-class-android-webkit-webview
*/
@SuppressWarnings("unused")
public class LollipopFixedWebView extends WebView {
public LollipopFixedWebView(Context context) {
super(getFixedContext(context));
}
public LollipopFixedWebView(Context context, AttributeSet attrs) {
super(getFixedContext(context), attrs);
}
public LollipopFixedWebView(Context context, AttributeSet attrs, int defStyleAttr) {
super(getFixedContext(context), attrs, defStyleAttr);
}
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
public LollipopFixedWebView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(getFixedContext(context), attrs, defStyleAttr, defStyleRes);
}
public LollipopFixedWebView(Context context, AttributeSet attrs, int defStyleAttr, boolean privateBrowsing) {
super(getFixedContext(context), attrs, defStyleAttr, privateBrowsing);
}
public static Context getFixedContext(Context context) {
if (isLollipopWebViewBug()) {
// Avoid crashing on Android 5 and 6 (API level 21 to 23)
return context.createConfigurationContext(new Configuration());
}
return context;
}
public static boolean isLollipopWebViewBug() {
return Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP && Build.VERSION.SDK_INT < Build.VERSION_CODES.M;
}
}

View File

@@ -0,0 +1,49 @@
/*
* Copyright (C) 2021 xuexiangjys(xuexiangjys@163.com)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
package com.kerwin.wumei.core.webview;
import android.util.Log;
import android.webkit.JsResult;
import android.webkit.WebView;
import com.just.agentweb.core.client.MiddlewareWebChromeBase;
/**
* WebChromeWebChromeClient主要辅助WebView处理JavaScript的对话框、网站图片、网站title、加载进度等中间件
* 【浏览器】
* @author xuexiang
* @since 2019/1/4 下午11:31
*/
public class MiddlewareChromeClient extends MiddlewareWebChromeBase {
public MiddlewareChromeClient() {
}
@Override
public boolean onJsAlert(WebView view, String url, String message, JsResult result) {
Log.i("Info", "onJsAlert:" + url);
return super.onJsAlert(view, url, message, result);
}
@Override
public void onProgressChanged(WebView view, int newProgress) {
super.onProgressChanged(view, newProgress);
Log.i("Info", "onProgressChanged:");
}
}

View File

@@ -0,0 +1,146 @@
/*
* Copyright (C) 2021 xuexiangjys(xuexiangjys@163.com)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
package com.kerwin.wumei.core.webview;
import android.net.Uri;
import android.os.Build;
import android.util.Log;
import android.webkit.WebResourceRequest;
import android.webkit.WebResourceResponse;
import android.webkit.WebView;
import androidx.annotation.RequiresApi;
import com.just.agentweb.core.client.MiddlewareWebClientBase;
import com.kerwin.wumei.R;
import com.xuexiang.xui.utils.ResUtils;
/**
* 【网络请求、加载】
* WebClientWebViewClient 这个类主要帮助WebView处理各种通知、url加载请求时间的中间件
* <p>
* <p>
* 方法的执行顺序例如下面用了7个中间件一个 WebViewClient
* <p>
* .useMiddlewareWebClient(getMiddlewareWebClient()) // 1
* .useMiddlewareWebClient(getMiddlewareWebClient()) // 2
* .useMiddlewareWebClient(getMiddlewareWebClient()) // 3
* .useMiddlewareWebClient(getMiddlewareWebClient()) // 4
* .useMiddlewareWebClient(getMiddlewareWebClient()) // 5
* .useMiddlewareWebClient(getMiddlewareWebClient()) // 6
* .useMiddlewareWebClient(getMiddlewareWebClient()) // 7
* DefaultWebClient // 8
* .setWebViewClient(mWebViewClient) // 9
* <p>
* <p>
* 典型的洋葱模型
* 对象内部的方法执行顺序: 1->2->3->4->5->6->7->8->9->8->7->6->5->4->3->2->1
* <p>
* <p>
* 中断中间件的执行, 删除super.methodName(...) 这行即可
* <p>
* 这里主要是做去广告的工作
*/
public class MiddlewareWebViewClient extends MiddlewareWebClientBase {
public MiddlewareWebViewClient() {
}
private static int count = 1;
@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
@Override
public boolean shouldOverrideUrlLoading(WebView view, WebResourceRequest request) {
Log.i("Info", "MiddlewareWebViewClient -- > shouldOverrideUrlLoading:" + request.getUrl().toString() + " c:" + (count++));
if (shouldOverrideUrlLoadingByApp(view, request.getUrl().toString())) {
return true;
}
return super.shouldOverrideUrlLoading(view, request);
}
@Override
public boolean shouldOverrideUrlLoading(WebView view, String url) {
Log.i("Info", "MiddlewareWebViewClient -- > shouldOverrideUrlLoading:" + url + " c:" + (count++));
if (shouldOverrideUrlLoadingByApp(view, url)) {
return true;
}
return super.shouldOverrideUrlLoading(view, url);
}
@Override
public WebResourceResponse shouldInterceptRequest(WebView view, String url) {
url = url.toLowerCase();
if (!hasAdUrl(url)) {
//正常加载
return super.shouldInterceptRequest(view, url);
} else {
//含有广告资源屏蔽请求
return new WebResourceResponse(null, null, null);
}
}
@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
@Override
public WebResourceResponse shouldInterceptRequest(WebView view, WebResourceRequest request) {
String url = request.getUrl().toString().toLowerCase();
if (!hasAdUrl(url)) {
//正常加载
return super.shouldInterceptRequest(view, request);
} else {
//含有广告资源屏蔽请求
return new WebResourceResponse(null, null, null);
}
}
/**
* 判断是否存在广告的链接
*
* @param url
* @return
*/
private static boolean hasAdUrl(String url) {
String[] adUrls = ResUtils.getStringArray(R.array.adBlockUrl);
for (String adUrl : adUrls) {
if (url.contains(adUrl)) {
return true;
}
}
return false;
}
/**
* 根据url的scheme处理跳转第三方app的业务,true代表拦截false代表不拦截
*/
private boolean shouldOverrideUrlLoadingByApp(WebView webView, final String url) {
if (url.startsWith("http") || url.startsWith("https") || url.startsWith("ftp")) {
//不拦截http, https, ftp的请求
Uri uri = Uri.parse(url);
if (uri != null && !(WebViewInterceptDialog.APP_LINK_HOST.equals(uri.getHost())
//防止xui官网被拦截
&& url.contains("xpage"))) {
return false;
}
}
WebViewInterceptDialog.show(url);
return true;
}
}

View File

@@ -0,0 +1,56 @@
/*
* Copyright (C) 2021 xuexiangjys(xuexiangjys@163.com)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
package com.kerwin.wumei.core.webview;
import android.app.Activity;
import android.os.Handler;
import android.util.Log;
import android.webkit.WebView;
import com.just.agentweb.core.web.AgentWebUIControllerImplBase;
import java.lang.ref.WeakReference;
/**
* 如果你需要修改某一个AgentWeb 内部的某一个弹窗 ,请看下面的例子
* 注意写法一定要参照 DefaultUIController 的写法 因为UI自由定制但是回调的方式是固定的并且一定要回调。
*
* @author xuexiang
* @since 2019-10-30 23:18
*/
public class UIController extends AgentWebUIControllerImplBase {
private WeakReference<Activity> mActivity;
public UIController(Activity activity) {
mActivity = new WeakReference<>(activity);
}
@Override
public void onShowMessage(String message, String from) {
super.onShowMessage(message, from);
Log.i(TAG, "message:" + message);
}
@Override
public void onSelectItemsPrompt(WebView view, String url, String[] items, Handler.Callback callback) {
// 使用默认的UI
super.onSelectItemsPrompt(view, url, items, callback);
}
}

View File

@@ -0,0 +1,61 @@
/*
* Copyright (C) 2021 xuexiangjys(xuexiangjys@163.com)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
package com.kerwin.wumei.core.webview;
import android.app.Activity;
import android.view.LayoutInflater;
import android.view.ViewGroup;
import android.webkit.WebView;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.just.agentweb.widget.IWebLayout;
import com.scwang.smartrefresh.layout.SmartRefreshLayout;
import com.kerwin.wumei.R;
/**
* 定义支持下来回弹的WebView
*
* @author xuexiang
* @since 2019/1/5 上午2:01
*/
public class WebLayout implements IWebLayout {
private final SmartRefreshLayout mSmartRefreshLayout;
private WebView mWebView;
public WebLayout(Activity activity) {
mSmartRefreshLayout = (SmartRefreshLayout) LayoutInflater.from(activity).inflate(R.layout.fragment_pulldown_web, null);
mWebView = mSmartRefreshLayout.findViewById(R.id.webView);
}
@NonNull
@Override
public ViewGroup getLayout() {
return mSmartRefreshLayout;
}
@Nullable
@Override
public WebView getWebView() {
return mWebView;
}
}

View File

@@ -0,0 +1,137 @@
/*
* Copyright (C) 2021 xuexiangjys(xuexiangjys@163.com)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
package com.kerwin.wumei.core.webview;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.net.Uri;
import android.os.Bundle;
import androidx.annotation.Nullable;
import androidx.appcompat.app.AppCompatActivity;
import com.kerwin.wumei.R;
import com.kerwin.wumei.utils.XToastUtils;
import com.xuexiang.xui.utils.ResUtils;
import com.xuexiang.xui.widget.dialog.DialogLoader;
import com.xuexiang.xutil.XUtil;
import com.xuexiang.xutil.app.ActivityUtils;
import java.net.URISyntaxException;
/**
* WebView拦截提示
*
* @author xuexiang
* @since 2019-10-21 9:51
*/
public class WebViewInterceptDialog extends AppCompatActivity implements DialogInterface.OnDismissListener {
private static final String KEY_INTERCEPT_URL = "key_intercept_url";
// TODO: 2019-10-30 这里修改你的applink
public static final String APP_LINK_HOST = "xuexiangjys.club";
public static final String APP_LINK_ACTION = "com.xuexiang.xui.applink";
/**
* 显示WebView拦截提示
*
* @param url 需要拦截处理的url
*/
public static void show(String url) {
ActivityUtils.startActivity(WebViewInterceptDialog.class, KEY_INTERCEPT_URL, url);
}
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
String url = getIntent().getStringExtra(KEY_INTERCEPT_URL);
DialogLoader.getInstance().showConfirmDialog(
this,
getOpenTitle(url),
ResUtils.getString(R.string.lab_yes),
(dialog, which) -> {
dialog.dismiss();
if (isAppLink(url)) {
openAppLink(this, url);
} else {
openApp(url);
}
},
ResUtils.getString(R.string.lab_no),
(dialog, which) -> dialog.dismiss()
).setOnDismissListener(this);
}
private String getOpenTitle(String url) {
String scheme = getScheme(url);
if ("mqqopensdkapi".equals(scheme)) {
return "是否允许页面打开\"QQ\"?";
} else {
return ResUtils.getString(R.string.lab_open_third_app);
}
}
private String getScheme(String url) {
try {
Intent intent = Intent.parseUri(url, Intent.URI_INTENT_SCHEME);
return intent.getScheme();
} catch (URISyntaxException e) {
e.printStackTrace();
}
return "";
}
private boolean isAppLink(String url) {
Uri uri = Uri.parse(url);
return uri != null
&& APP_LINK_HOST.equals(uri.getHost())
&& (url.startsWith("http") || url.startsWith("https"));
}
private void openApp(String url) {
Intent intent;
try {
intent = Intent.parseUri(url, Intent.URI_INTENT_SCHEME);
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_SINGLE_TOP);
XUtil.getContext().startActivity(intent);
} catch (Exception e) {
XToastUtils.error("您所打开的第三方App未安装");
}
}
private void openAppLink(Context context, String url) {
try {
Intent intent = new Intent(APP_LINK_ACTION);
intent.setData(Uri.parse(url));
context.startActivity(intent);
} catch (Exception e) {
XToastUtils.error("您所打开的第三方App未安装");
}
}
@Override
public void onDismiss(DialogInterface dialog) {
finish();
}
}

View File

@@ -0,0 +1,677 @@
/*
* Copyright (C) 2021 xuexiangjys(xuexiangjys@163.com)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
package com.kerwin.wumei.core.webview;
import android.content.ClipData;
import android.content.ClipboardManager;
import android.content.Context;
import android.content.Intent;
import android.graphics.Bitmap;
import android.graphics.Color;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.text.TextUtils;
import android.view.Gravity;
import android.view.KeyEvent;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import android.webkit.WebChromeClient;
import android.webkit.WebResourceError;
import android.webkit.WebResourceRequest;
import android.webkit.WebResourceResponse;
import android.webkit.WebView;
import android.webkit.WebViewClient;
import android.widget.FrameLayout;
import android.widget.LinearLayout;
import android.widget.TextView;
import androidx.annotation.Nullable;
import androidx.annotation.RequiresApi;
import androidx.appcompat.widget.AppCompatImageView;
import androidx.appcompat.widget.PopupMenu;
import androidx.fragment.app.Fragment;
import com.just.agentweb.action.PermissionInterceptor;
import com.just.agentweb.core.AgentWeb;
import com.just.agentweb.core.client.DefaultWebClient;
import com.just.agentweb.core.client.MiddlewareWebChromeBase;
import com.just.agentweb.core.client.MiddlewareWebClientBase;
import com.just.agentweb.core.client.WebListenerManager;
import com.just.agentweb.core.web.AbsAgentWebSettings;
import com.just.agentweb.core.web.AgentWebConfig;
import com.just.agentweb.core.web.IAgentWebSettings;
import com.just.agentweb.download.AgentWebDownloader;
import com.just.agentweb.download.DefaultDownloadImpl;
import com.just.agentweb.download.DownloadListenerAdapter;
import com.just.agentweb.download.DownloadingService;
import com.just.agentweb.widget.IWebLayout;
import com.kerwin.wumei.MyApp;
import com.kerwin.wumei.core.BaseFragment;
import com.kerwin.wumei.R;
import com.kerwin.wumei.utils.XToastUtils;
import com.xuexiang.xaop.annotation.SingleClick;
import com.xuexiang.xpage.annotation.Page;
import com.xuexiang.xpage.base.XPageActivity;
import com.xuexiang.xpage.base.XPageFragment;
import com.xuexiang.xpage.core.PageOption;
import com.xuexiang.xui.widget.actionbar.TitleBar;
import com.xuexiang.xutil.common.logger.Logger;
import com.xuexiang.xutil.net.JsonUtil;
import java.util.HashMap;
import butterknife.BindView;
import butterknife.OnClick;
/**
* 使用XPageFragment
*
* @author xuexiang
* @since 2019-05-26 18:15
*/
@Page(params = {AgentWebFragment.KEY_URL})
public class XPageWebViewFragment extends BaseFragment {
@BindView(R.id.iv_back)
AppCompatImageView mIvBack;
@BindView(R.id.view_line)
View mLineView;
@BindView(R.id.toolbar_title)
TextView mTvTitle;
protected AgentWeb mAgentWeb;
private PopupMenu mPopupMenu;
private DownloadingService mDownloadingService;
/**
* 打开网页
*
* @param xPageActivity
* @param url
* @return
*/
public static Fragment openUrl(XPageActivity xPageActivity, String url) {
return PageOption.to(XPageWebViewFragment.class)
.putString(AgentWebFragment.KEY_URL, url)
.open(xPageActivity);
}
/**
* 打开网页
*
* @param fragment
* @param url
* @return
*/
public static Fragment openUrl(XPageFragment fragment, String url) {
return PageOption.to(XPageWebViewFragment.class)
.setNewActivity(true)
.putString(AgentWebFragment.KEY_URL, url)
.open(fragment);
}
@Override
protected TitleBar initTitle() {
return null;
}
/**
* 布局的资源id
*
* @return
*/
@Override
protected int getLayoutId() {
return R.layout.fragment_agentweb;
}
/**
* 初始化控件
*/
@Override
protected void initViews() {
mAgentWeb = AgentWeb.with(this)
//传入AgentWeb的父控件。
.setAgentWebParent((LinearLayout) getRootView(), -1, new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT))
//设置进度条颜色与高度,-1为默认值高度为2单位为dp。
.useDefaultIndicator(-1, 3)
//设置 IAgentWebSettings。
.setAgentWebWebSettings(getSettings())
//WebViewClient 与 WebView 使用一致 但是请勿获取WebView调用setWebViewClient(xx)方法了,会覆盖AgentWeb DefaultWebClient,同时相应的中间件也会失效。
.setWebViewClient(mWebViewClient)
//WebChromeClient
.setWebChromeClient(mWebChromeClient)
//设置WebChromeClient中间件支持多个WebChromeClientAgentWeb 3.0.0 加入。
.useMiddlewareWebChrome(getMiddlewareWebChrome())
//设置WebViewClient中间件支持多个WebViewClient AgentWeb 3.0.0 加入。
.useMiddlewareWebClient(getMiddlewareWebClient())
//权限拦截 2.0.0 加入。
.setPermissionInterceptor(mPermissionInterceptor)
//严格模式 Android 4.2.2 以下会放弃注入对象 使用AgentWebView没影响。
.setSecurityType(AgentWeb.SecurityType.STRICT_CHECK)
//自定义UI AgentWeb3.0.0 加入。
.setAgentWebUIController(new UIController(getActivity()))
//参数1是错误显示的布局参数2点击刷新控件ID -1表示点击整个布局都刷新 AgentWeb 3.0.0 加入。
.setMainFrameErrorView(R.layout.agentweb_error_page, -1)
.setWebLayout(getWebLayout())
//打开其他页面时,弹窗质询用户前往其他应用 AgentWeb 3.0.0 加入。
.setOpenOtherPageWays(DefaultWebClient.OpenOtherPageWays.DISALLOW)
//拦截找不到相关页面的Url AgentWeb 3.0.0 加入。
.interceptUnkownUrl()
//创建AgentWeb。
.createAgentWeb()
.ready()//设置 WebSettings。
//WebView载入该url地址的页面并显示。
.go(getUrl());
if (MyApp.isDebug()) {
AgentWebConfig.debug();
}
pageNavigator(View.GONE);
// 得到 AgentWeb 最底层的控件
addBackgroundChild(mAgentWeb.getWebCreator().getWebParentLayout());
// AgentWeb 没有把WebView的功能全面覆盖 ,所以某些设置 AgentWeb 没有提供请从WebView方面入手设置。
mAgentWeb.getWebCreator().getWebView().setOverScrollMode(WebView.OVER_SCROLL_NEVER);
}
protected IWebLayout getWebLayout() {
return new WebLayout(getActivity());
}
protected void addBackgroundChild(FrameLayout frameLayout) {
TextView textView = new TextView(frameLayout.getContext());
textView.setText("技术由 AgentWeb 提供");
textView.setTextSize(16);
textView.setTextColor(Color.parseColor("#727779"));
frameLayout.setBackgroundColor(Color.parseColor("#272b2d"));
FrameLayout.LayoutParams params = new FrameLayout.LayoutParams(-2, -2);
params.gravity = Gravity.CENTER_HORIZONTAL;
final float scale = frameLayout.getContext().getResources().getDisplayMetrics().density;
params.topMargin = (int) (15 * scale + 0.5f);
frameLayout.addView(textView, 0, params);
}
private void pageNavigator(int tag) {
//返回的导航按钮
mIvBack.setVisibility(tag);
mLineView.setVisibility(tag);
}
@SingleClick
@OnClick({R.id.iv_back, R.id.iv_finish, R.id.iv_more})
public void onViewClicked(View view) {
switch (view.getId()) {
case R.id.iv_back:
// true表示AgentWeb处理了该事件
if (!mAgentWeb.back()) {
popToBack();
}
break;
case R.id.iv_finish:
popToBack();
break;
case R.id.iv_more:
showPoPup(view);
break;
default:
break;
}
}
//=====================下载============================//
/**
* 更新于 AgentWeb 4.0.0,下载监听
*/
protected DownloadListenerAdapter mDownloadListenerAdapter = new DownloadListenerAdapter() {
/**
*
* @param url 下载链接
* @param userAgent UserAgent
* @param contentDisposition ContentDisposition
* @param mimeType 资源的媒体类型
* @param contentLength 文件长度
* @param extra 下载配置 用户可以通过 Extra 修改下载icon 关闭进度条 是否强制下载。
* @return true 表示用户处理了该下载事件 false 交给 AgentWeb 下载
*/
@Override
public boolean onStart(String url, String userAgent, String contentDisposition, String mimeType, long contentLength, AgentWebDownloader.Extra extra) {
Logger.i("onStart:" + url);
// 是否开启断点续传
extra.setOpenBreakPointDownload(true)
//下载通知的icon
.setIcon(R.drawable.ic_file_download_black_24dp)
// 连接的超时时间
.setConnectTimeOut(6000)
// 以8KB位单位默认60s 如果60s内无法从网络流中读满8KB数据则抛出异常
.setBlockMaxTime(10 * 60 * 1000)
// 下载的超时时间
.setDownloadTimeOut(Long.MAX_VALUE)
// 串行下载更节省资源哦
.setParallelDownload(false)
// false 关闭进度通知
.setEnableIndicator(true)
// 自定义请求头
.addHeader("Cookie", "xx")
// 下载完成自动打开
.setAutoOpen(true)
// 强制下载,不管网络网络类型
.setForceDownload(true);
return false;
}
/**
*
* 不需要暂停或者停止下载该方法可以不必实现
* @param url
* @param downloadingService 用户可以通过 DownloadingService#shutdownNow 终止下载
*/
@Override
public void onBindService(String url, DownloadingService downloadingService) {
super.onBindService(url, downloadingService);
mDownloadingService = downloadingService;
Logger.i("onBindService:" + url + " DownloadingService:" + downloadingService);
}
/**
* 回调onUnbindService方法让用户释放掉 DownloadingService。
* @param url
* @param downloadingService
*/
@Override
public void onUnbindService(String url, DownloadingService downloadingService) {
super.onUnbindService(url, downloadingService);
mDownloadingService = null;
Logger.i("onUnbindService:" + url);
}
/**
*
* @param url 下载链接
* @param loaded 已经下载的长度
* @param length 文件的总大小
* @param usedTime 耗时 单位ms
* 注意该方法回调在子线程 ,线程名 AsyncTask #XX 或者 AgentWeb # XX
*/
@Override
public void onProgress(String url, long loaded, long length, long usedTime) {
int mProgress = (int) ((loaded) / (float) length * 100);
Logger.i("onProgress:" + mProgress);
super.onProgress(url, loaded, length, usedTime);
}
/**
*
* @param path 文件的绝对路径
* @param url 下载地址
* @param throwable 如果异常,返回给用户异常
* @return true 表示用户处理了下载完成后续的事件 false 默认交给AgentWeb 处理
*/
@Override
public boolean onResult(String path, String url, Throwable throwable) {
//下载成功
if (null == throwable) {
//do you work
} else {//下载失败
}
// true 不会发出下载完成的通知 , 或者打开文件
return false;
}
};
/**
* 下载服务设置
*
* @return IAgentWebSettings
*/
public IAgentWebSettings getSettings() {
return new AbsAgentWebSettings() {
private AgentWeb mAgentWeb;
@Override
protected void bindAgentWebSupport(AgentWeb agentWeb) {
this.mAgentWeb = agentWeb;
}
/**
* AgentWeb 4.0.0 内部删除了 DownloadListener 监听 以及相关API ,将 Download 部分完全抽离出来独立一个库,
* 如果你需要使用 AgentWeb Download 部分 请依赖上 compile 'com.just.agentweb:download:4.0.0
* 如果你需要监听下载结果,请自定义 AgentWebSetting New 出 DefaultDownloadImpl传入DownloadListenerAdapter
* 实现进度或者结果监听,例如下面这个例子,如果你不需要监听进度,或者下载结果,下面 setDownloader 的例子可以忽略。
* @param webView
* @param downloadListener
* @return WebListenerManager
*/
@Override
public WebListenerManager setDownloader(WebView webView, android.webkit.DownloadListener downloadListener) {
return super.setDownloader(webView,
DefaultDownloadImpl
.create(getActivity(),
webView,
mDownloadListenerAdapter,
mDownloadListenerAdapter,
mAgentWeb.getPermissionInterceptor()));
}
};
}
//===================WebChromeClient 和 WebViewClient===========================//
/**
* 页面空白请检查scheme是否加上 scheme://host:port/path?query&query 。
*
* @return mUrl
*/
public String getUrl() {
String target = "";
Bundle bundle = getArguments();
if (bundle != null) {
target = bundle.getString(AgentWebFragment.KEY_URL);
}
if (TextUtils.isEmpty(target)) {
target = "https://github.com/xuexiangjys";
}
return target;
}
/**
* 和浏览器相关包括和JS的交互
*/
protected WebChromeClient mWebChromeClient = new WebChromeClient() {
@Override
public void onProgressChanged(WebView view, int newProgress) {
super.onProgressChanged(view, newProgress);
//网页加载进度
}
@Override
public void onReceivedTitle(WebView view, String title) {
super.onReceivedTitle(view, title);
if (mTvTitle != null && !TextUtils.isEmpty(title)) {
if (title.length() > 10) {
title = title.substring(0, 10).concat("...");
}
mTvTitle.setText(title);
}
}
};
/**
* 和网页url加载相关统计加载时间
*/
protected WebViewClient mWebViewClient = new WebViewClient() {
private HashMap<String, Long> mTimer = new HashMap<>();
@Override
public void onReceivedError(WebView view, WebResourceRequest request, WebResourceError error) {
super.onReceivedError(view, request, error);
}
@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
@Override
public boolean shouldOverrideUrlLoading(WebView view, WebResourceRequest request) {
return shouldOverrideUrlLoading(view, request.getUrl() + "");
}
@Nullable
@Override
public WebResourceResponse shouldInterceptRequest(WebView view, WebResourceRequest request) {
return super.shouldInterceptRequest(view, request);
}
@Override
public boolean shouldOverrideUrlLoading(final WebView view, String url) {
//intent:// scheme的处理 如果返回false 则交给 DefaultWebClient 处理 默认会打开该Activity 如果Activity不存在则跳到应用市场上去. true 表示拦截
//例如优酷视频播放 intent://play?...package=com.youku.phone;end;
//优酷想唤起自己应用播放该视频 下面拦截地址返回 true 则会在应用内 H5 播放 ,禁止优酷唤起播放该视频, 如果返回 false DefaultWebClient 会根据intent 协议处理 该地址 首先匹配该应用存不存在 ,如果存在 唤起该应用播放 如果不存在 则跳到应用市场下载该应用 .
if (url.startsWith("intent://") && url.contains("com.youku.phone")) {
return true;
}
return false;
}
@Override
public void onPageStarted(WebView view, String url, Bitmap favicon) {
mTimer.put(url, System.currentTimeMillis());
if (url.equals(getUrl())) {
pageNavigator(View.GONE);
} else {
pageNavigator(View.VISIBLE);
}
}
@Override
public void onPageFinished(WebView view, String url) {
super.onPageFinished(view, url);
if (mTimer.get(url) != null) {
long overTime = System.currentTimeMillis();
Long startTime = mTimer.get(url);
//统计页面的使用时长
Logger.i(" page mUrl:" + url + " used time:" + (overTime - startTime));
}
}
@Override
public void onReceivedHttpError(WebView view, WebResourceRequest request, WebResourceResponse errorResponse) {
super.onReceivedHttpError(view, request, errorResponse);
}
@Override
public void onReceivedError(WebView view, int errorCode, String description, String failingUrl) {
super.onReceivedError(view, errorCode, description, failingUrl);
}
};
//=====================菜单========================//
/**
* 显示更多菜单
*
* @param view 菜单依附在该View下面
*/
private void showPoPup(View view) {
if (mPopupMenu == null) {
mPopupMenu = new PopupMenu(getContext(), view);
mPopupMenu.inflate(R.menu.menu_toolbar_web);
mPopupMenu.setOnMenuItemClickListener(mOnMenuItemClickListener);
}
mPopupMenu.show();
}
/**
* 菜单事件
*/
private PopupMenu.OnMenuItemClickListener mOnMenuItemClickListener = new PopupMenu.OnMenuItemClickListener() {
@Override
public boolean onMenuItemClick(MenuItem item) {
switch (item.getItemId()) {
case R.id.refresh:
if (mAgentWeb != null) {
mAgentWeb.getUrlLoader().reload(); // 刷新
}
return true;
case R.id.copy:
if (mAgentWeb != null) {
toCopy(getContext(), mAgentWeb.getWebCreator().getWebView().getUrl());
}
return true;
case R.id.default_browser:
if (mAgentWeb != null) {
openBrowser(mAgentWeb.getWebCreator().getWebView().getUrl());
}
return true;
case R.id.share:
if (mAgentWeb != null) {
shareWebUrl(mAgentWeb.getWebCreator().getWebView().getUrl());
}
return true;
default:
return false;
}
}
};
/**
* 打开浏览器
*
* @param targetUrl 外部浏览器打开的地址
*/
private void openBrowser(String targetUrl) {
if (TextUtils.isEmpty(targetUrl) || targetUrl.startsWith("file://")) {
XToastUtils.toast(targetUrl + " 该链接无法使用浏览器打开。");
return;
}
Intent intent = new Intent();
intent.setAction("android.intent.action.VIEW");
Uri uri = Uri.parse(targetUrl);
intent.setData(uri);
startActivity(intent);
}
/**
* 分享网页链接
*
* @param url 网页链接
*/
private void shareWebUrl(String url) {
Intent shareIntent = new Intent();
shareIntent.setAction(Intent.ACTION_SEND);
shareIntent.putExtra(Intent.EXTRA_TEXT, url);
shareIntent.setType("text/plain");
//设置分享列表的标题,并且每次都显示分享列表
startActivity(Intent.createChooser(shareIntent, "分享到"));
}
/**
* 复制字符串
*
* @param context
* @param text
*/
private void toCopy(Context context, String text) {
ClipboardManager manager = (ClipboardManager) context.getSystemService(Context.CLIPBOARD_SERVICE);
if (manager == null) {
return;
}
manager.setPrimaryClip(ClipData.newPlainText(null, text));
}
//===================生命周期管理===========================//
@Override
public void onResume() {
if (mAgentWeb != null) {
mAgentWeb.getWebLifeCycle().onResume();//恢复
}
super.onResume();
}
@Override
public void onPause() {
if (mAgentWeb != null) {
mAgentWeb.getWebLifeCycle().onPause(); //暂停应用内所有WebView 调用mWebView.resumeTimers();/mAgentWeb.getWebLifeCycle().onResume(); 恢复。
}
super.onPause();
}
@Override
public boolean onKeyDown(int keyCode, KeyEvent event) {
return mAgentWeb != null && mAgentWeb.handleKeyEvent(keyCode, event);
}
@Override
public void onDestroyView() {
if (mAgentWeb != null) {
mAgentWeb.destroy();
}
super.onDestroyView();
}
//===================中间键===========================//
/**
* MiddlewareWebClientBase 是 AgentWeb 3.0.0 提供一个强大的功能,
* 如果用户需要使用 AgentWeb 提供的功能, 不想重写 WebClientView方
* 法覆盖AgentWeb提供的功能那么 MiddlewareWebClientBase 是一个
* 不错的选择 。
*
* @return
*/
protected MiddlewareWebClientBase getMiddlewareWebClient() {
return new MiddlewareWebViewClient() {
/**
*
* @param view
* @param url
* @return
*/
@Override
public boolean shouldOverrideUrlLoading(WebView view, String url) {
// 拦截 url不执行 DefaultWebClient#shouldOverrideUrlLoading
if (url.startsWith("agentweb")) {
return true;
}
// 执行 DefaultWebClient#shouldOverrideUrlLoading
if (super.shouldOverrideUrlLoading(view, url)) {
return true;
}
// do you work
return false;
}
@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
@Override
public boolean shouldOverrideUrlLoading(WebView view, WebResourceRequest request) {
return super.shouldOverrideUrlLoading(view, request);
}
};
}
protected MiddlewareWebChromeBase getMiddlewareWebChrome() {
return new MiddlewareChromeClient() {
};
}
/**
* 权限申请拦截器
*/
protected PermissionInterceptor mPermissionInterceptor = new PermissionInterceptor() {
/**
* PermissionInterceptor 能达到 url1 允许授权, url2 拒绝授权的效果。
* @param url
* @param permissions
* @param action
* @return true 该Url对应页面请求权限进行拦截 false 表示不拦截。
*/
@Override
public boolean intercept(String url, String[] permissions, String action) {
Logger.i("mUrl:" + url + " permission:" + JsonUtil.toJson(permissions) + " action:" + action);
return false;
}
};
}

View File

@@ -0,0 +1,66 @@
/*
* Copyright (C) 2021 xuexiangjys(xuexiangjys@163.com)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
package com.kerwin.wumei.fragment;
import android.widget.TextView;
import com.kerwin.wumei.core.BaseFragment;
import com.kerwin.wumei.core.webview.AgentWebActivity;
import com.kerwin.wumei.R;
import com.kerwin.wumei.utils.XToastUtils;
import com.xuexiang.xpage.annotation.Page;
import com.xuexiang.xui.widget.grouplist.XUIGroupListView;
import com.xuexiang.xutil.app.AppUtils;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Locale;
import butterknife.BindView;
@Page(name = "关于")
public class AboutFragment extends BaseFragment {
@BindView(R.id.tv_version)
TextView mVersionTextView;
@BindView(R.id.about_list)
XUIGroupListView mAboutGroupListView;
@BindView(R.id.tv_copyright)
TextView mCopyrightTextView;
@Override
protected int getLayoutId() {
return R.layout.fragment_about;
}
@Override
protected void initViews() {
mVersionTextView.setText(String.format("版本号:%s", AppUtils.getAppVersionName()));
XUIGroupListView.newSection(getContext())
.addItemView(mAboutGroupListView.createItemView(getResources().getString(R.string.about_item_homepage)), v -> AgentWebActivity.goWeb(getContext(), getString(R.string.url_project_github)))
.addItemView(mAboutGroupListView.createItemView(getResources().getString(R.string.about_item_author_github)), v -> AgentWebActivity.goWeb(getContext(), getString(R.string.url_author_github)))
.addItemView(mAboutGroupListView.createItemView("版本"), v -> XToastUtils.toast("版本升级"))
.addTo(mAboutGroupListView);
SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy", Locale.CHINA);
String currentYear = dateFormat.format(new Date());
mCopyrightTextView.setText(String.format(getResources().getString(R.string.about_copyright), currentYear));
}
}

View File

@@ -0,0 +1,30 @@
package com.kerwin.wumei.fragment;
import android.widget.TextView;
import com.kerwin.wumei.R;
import com.kerwin.wumei.core.BaseFragment;
import com.kerwin.wumei.core.webview.AgentWebActivity;
import com.xuexiang.xpage.annotation.Page;
import com.xuexiang.xui.widget.grouplist.XUIGroupListView;
import com.xuexiang.xutil.app.AppUtils;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Locale;
import butterknife.BindView;
@Page(name = "意见反馈")
public class FeedbackFragment extends BaseFragment {
@Override
protected int getLayoutId() {
return R.layout.fragment_feedback;
}
@Override
protected void initViews() {
}
}

View File

@@ -0,0 +1,171 @@
/*
* Copyright (C) 2021 xuexiangjys(xuexiangjys@163.com)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
package com.kerwin.wumei.fragment;
import android.graphics.Color;
import android.view.View;
import com.kerwin.wumei.activity.MainActivity;
import com.kerwin.wumei.core.BaseFragment;
import com.kerwin.wumei.R;
import com.kerwin.wumei.utils.RandomUtils;
import com.kerwin.wumei.utils.SettingUtils;
import com.kerwin.wumei.utils.TokenUtils;
import com.kerwin.wumei.utils.Utils;
import com.kerwin.wumei.utils.XToastUtils;
import com.xuexiang.xaop.annotation.SingleClick;
import com.xuexiang.xpage.annotation.Page;
import com.xuexiang.xpage.enums.CoreAnim;
import com.xuexiang.xui.utils.CountDownButtonHelper;
import com.xuexiang.xui.utils.ResUtils;
import com.xuexiang.xui.utils.ThemeUtils;
import com.xuexiang.xui.widget.actionbar.TitleBar;
import com.xuexiang.xui.widget.button.roundbutton.RoundButton;
import com.xuexiang.xui.widget.edittext.materialedittext.MaterialEditText;
import com.xuexiang.xutil.app.ActivityUtils;
import butterknife.BindView;
import butterknife.OnClick;
/**
* 登录页面
*
* @author xuexiang
* @since 2019-11-17 22:15
*/
@Page(anim = CoreAnim.none)
public class LoginFragment extends BaseFragment {
@BindView(R.id.et_phone_number)
MaterialEditText etPhoneNumber;
@BindView(R.id.et_verify_code)
MaterialEditText etVerifyCode;
@BindView(R.id.btn_get_verify_code)
RoundButton btnGetVerifyCode;
private CountDownButtonHelper mCountDownHelper;
@Override
protected int getLayoutId() {
return R.layout.fragment_login;
}
@Override
protected TitleBar initTitle() {
TitleBar titleBar = super.initTitle()
.setImmersive(true);
titleBar.setBackgroundColor(Color.TRANSPARENT);
titleBar.setTitle("");
titleBar.setLeftImageDrawable(ResUtils.getVectorDrawable(getContext(), R.drawable.ic_login_close));
titleBar.setActionTextColor(ThemeUtils.resolveColor(getContext(), R.attr.colorAccent));
titleBar.addAction(new TitleBar.TextAction(R.string.title_jump_login) {
@Override
public void performAction(View view) {
onLoginSuccess();
}
});
return titleBar;
}
@Override
protected void initViews() {
mCountDownHelper = new CountDownButtonHelper(btnGetVerifyCode, 60);
//隐私政策弹窗
if (!SettingUtils.isAgreePrivacy()) {
Utils.showPrivacyDialog(getContext(), (dialog, which) -> {
dialog.dismiss();
SettingUtils.setIsAgreePrivacy(true);
});
}
}
@SingleClick
@OnClick({R.id.btn_get_verify_code, R.id.btn_login, R.id.tv_other_login, R.id.tv_forget_password, R.id.tv_user_protocol, R.id.tv_privacy_protocol})
public void onViewClicked(View view) {
switch (view.getId()) {
case R.id.btn_get_verify_code:
if (etPhoneNumber.validate()) {
getVerifyCode(etPhoneNumber.getEditValue());
}
break;
case R.id.btn_login:
if (etPhoneNumber.validate()) {
if (etVerifyCode.validate()) {
loginByVerifyCode(etPhoneNumber.getEditValue(), etVerifyCode.getEditValue());
}
}
break;
case R.id.tv_other_login:
XToastUtils.info("其他登录方式");
break;
case R.id.tv_forget_password:
XToastUtils.info("忘记密码");
break;
case R.id.tv_user_protocol:
XToastUtils.info("用户协议");
break;
case R.id.tv_privacy_protocol:
XToastUtils.info("隐私政策");
break;
default:
break;
}
}
/**
* 获取验证码
*/
private void getVerifyCode(String phoneNumber) {
// TODO: 2020/8/29 这里只是界面演示而已
XToastUtils.warning("只是演示,验证码请随便输");
mCountDownHelper.start();
}
/**
* 根据验证码登录
*
* @param phoneNumber 手机号
* @param verifyCode 验证码
*/
private void loginByVerifyCode(String phoneNumber, String verifyCode) {
// TODO: 2020/8/29 这里只是界面演示而已
onLoginSuccess();
}
/**
* 登录成功的处理
*/
private void onLoginSuccess() {
String token = RandomUtils.getRandomNumbersAndLetters(16);
if (TokenUtils.handleLoginSuccess(token)) {
popToBack();
ActivityUtils.startActivity(MainActivity.class);
}
}
@Override
public void onDestroyView() {
if (mCountDownHelper != null) {
mCountDownHelper.recycle();
}
super.onDestroyView();
}
}

View File

@@ -0,0 +1,54 @@
package com.kerwin.wumei.fragment;
import android.view.View;
import android.widget.TextView;
import com.kerwin.wumei.R;
import com.kerwin.wumei.core.BaseFragment;
import com.kerwin.wumei.core.webview.AgentWebActivity;
import com.xuexiang.xaop.annotation.SingleClick;
import com.xuexiang.xpage.annotation.Page;
import com.xuexiang.xui.widget.actionbar.TitleBar;
import com.xuexiang.xui.widget.grouplist.XUIGroupListView;
import com.xuexiang.xutil.app.AppUtils;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Locale;
import butterknife.BindView;
@Page(name = "消息")
public class MessageFragment extends BaseFragment {
@Override
protected int getLayoutId() {
return R.layout.fragment_message;
}
@Override
protected TitleBar initTitle() {
com.xuexiang.xui.widget.actionbar.TitleBar titleBar = super.initTitle();
titleBar.setCenterClickListener(new View.OnClickListener() {
@SingleClick
@Override
public void onClick(View view) {
}
});
titleBar.addAction(new com.xuexiang.xui.widget.actionbar.TitleBar.TextAction("菜单") {
@SingleClick
@Override
public void performAction(View view) {
}
});
return titleBar;
}
@Override
protected void initViews() {
}
}

View File

@@ -0,0 +1,98 @@
/*
* Copyright (C) 2021 xuexiangjys(xuexiangjys@163.com)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
package com.kerwin.wumei.fragment;
import com.kerwin.wumei.core.BaseFragment;
import com.kerwin.wumei.R;
import com.kerwin.wumei.utils.TokenUtils;
import com.kerwin.wumei.utils.XToastUtils;
import com.xuexiang.xaop.annotation.SingleClick;
import com.xuexiang.xpage.annotation.Page;
import com.xuexiang.xui.widget.dialog.DialogLoader;
import com.xuexiang.xui.widget.textview.supertextview.SuperTextView;
import com.xuexiang.xutil.XUtil;
import butterknife.BindView;
/**
* @author xuexiang
* @since 2019-10-15 22:38
*/
@Page(name = "设置")
public class SettingsFragment extends BaseFragment implements SuperTextView.OnSuperTextViewClickListener {
@BindView(R.id.menu_common)
SuperTextView menuCommon;
@BindView(R.id.menu_privacy)
SuperTextView menuPrivacy;
@BindView(R.id.menu_push)
SuperTextView menuPush;
@BindView(R.id.menu_helper)
SuperTextView menuHelper;
@BindView(R.id.menu_change_account)
SuperTextView menuChangeAccount;
@BindView(R.id.menu_logout)
SuperTextView menuLogout;
@Override
protected int getLayoutId() {
return R.layout.fragment_settings;
}
@Override
protected void initViews() {
menuCommon.setOnSuperTextViewClickListener(this);
menuPrivacy.setOnSuperTextViewClickListener(this);
menuPush.setOnSuperTextViewClickListener(this);
menuHelper.setOnSuperTextViewClickListener(this);
menuChangeAccount.setOnSuperTextViewClickListener(this);
menuLogout.setOnSuperTextViewClickListener(this);
}
@SingleClick
@Override
public void onClick(SuperTextView superTextView) {
switch (superTextView.getId()) {
case R.id.menu_common:
case R.id.menu_privacy:
case R.id.menu_push:
case R.id.menu_helper:
XToastUtils.toast(superTextView.getLeftString());
break;
case R.id.menu_change_account:
XToastUtils.toast(superTextView.getCenterString());
break;
case R.id.menu_logout:
DialogLoader.getInstance().showConfirmDialog(
getContext(),
getString(R.string.lab_logout_confirm),
getString(R.string.lab_yes),
(dialog, which) -> {
dialog.dismiss();
XUtil.getActivityLifecycleHelper().exit();
TokenUtils.handleLogoutSuccess();
},
getString(R.string.lab_no),
(dialog, which) -> dialog.dismiss()
);
break;
default:
break;
}
}
}

View File

@@ -0,0 +1,142 @@
package com.kerwin.wumei.fragment.device;
import android.Manifest;
import android.os.Build;
import android.text.method.HideReturnsTransformationMethod;
import android.text.method.PasswordTransformationMethod;
import android.util.Log;
import android.view.View;
import android.widget.FrameLayout;
import android.widget.LinearLayout;
import androidx.appcompat.widget.AppCompatImageView;
import com.kerwin.wumei.MyApp;
import com.kerwin.wumei.R;
import com.kerwin.wumei.activity.AddDeviceActivity;
import com.kerwin.wumei.activity.MainActivity;
import com.kerwin.wumei.adapter.entity.EspTouchViewModel;
import com.kerwin.wumei.core.BaseFragment;
import com.xuexiang.xpage.annotation.Page;
import com.xuexiang.xpage.core.PageOption;
import com.xuexiang.xpage.enums.CoreAnim;
import com.xuexiang.xui.widget.spinner.materialspinner.MaterialSpinner;
import java.util.List;
import butterknife.BindView;
@Page(name = "WIFI网络配置")
public class AddDeviceFragment extends BaseFragment {
@BindView(R.id.advance_frame_layout)
FrameLayout advanceFrameLayout;
@BindView(R.id.advance_linear_layout)
LinearLayout advanceLinearLayout;
@BindView(R.id.advance_icon)
AppCompatImageView advanceIcon;
@BindView(R.id.wifi_password_icon)
AppCompatImageView wifiPasswordIcon;
private static final String TAG = AddDeviceFragment.class.getSimpleName();
private static final int REQUEST_PERMISSION = 0x01;
private EspTouchViewModel mViewModel;
/**
* 布局的资源id
*
* @return
*/
@Override
protected int getLayoutId() {
return R.layout.fragment_add_device;
}
/**
* 初始化控件
*/
@Override
protected void initViews() {
//智能配网
mViewModel = ((AddDeviceActivity)this.getActivity()).GetMViewModel();
mViewModel.ssidSpinner = findViewById(R.id.ssid_spinner);
mViewModel.apPasswordEdit = findViewById(R.id.wifi_password_txt);
mViewModel.packageModeGroup = findViewById(R.id.packageModeGroup);
mViewModel.messageView = findViewById(R.id.messageView);
mViewModel.confirmBtn = findViewById(R.id.add_device_next_btn);
mViewModel.confirmBtn.setOnClickListener(v ->
{
// ((AddDeviceActivity)this.getActivity()).executeEsptouch();
PageOption.to(AddDeviceTwoFragment.class) //跳转的fragment
.setAnim(CoreAnim.slide) //页面转场动画
.setRequestCode(100) //请求码,用于返回结果
.setAddToBackStack(true) //是否加入堆栈
.putString("device_mac","0908070605040306")
.open(this); //打开页面进行跳转
});
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
String[] permissions = {Manifest.permission.ACCESS_FINE_LOCATION,Manifest.permission.ACCESS_COARSE_LOCATION};
requestPermissions(permissions, REQUEST_PERMISSION);
}
MyApp.getInstance().observeBroadcast(this, broadcast -> {
Log.d(TAG, "onCreate: Broadcast=" + broadcast);
((AddDeviceActivity)this.getActivity()).onWifiChanged();
List<String> ssids=((AddDeviceActivity)this.getActivity()).GetSsids();
if(ssids!=null && ssids.size()>0){
Log.e(TAG, "进入数据绑定 " );
mViewModel.ssidSpinner.setItems(ssids);
// ssidSpinner.setOnItemSelectedListener((spinner, position, id, item) -> SnackbarUtils.Long(spinner, "Clicked " + item).show());
// ssidSpinner.setOnNothingSelectedListener(spinner -> SnackbarUtils.Long(spinner, "Nothing selected").show());
String ssid=((AddDeviceActivity)this.getActivity()).GetSelectedSSID();
if(ssid!=null && ssid.length()>0 && ssids.contains(ssid)) {
mViewModel.ssidSpinner.setSelectedItem(ssid);
}
}
});
}
@Override
protected void initListeners() {
//单击高级设置项
advanceFrameLayout.setOnClickListener(new View.OnClickListener(){
@Override
public void onClick(View view) {
int visible=advanceLinearLayout.getVisibility();
if(visible!=0) {
advanceLinearLayout.setVisibility(View.VISIBLE);
advanceIcon.setImageDrawable(getResources().getDrawable((R.drawable.up)));
}else{
advanceLinearLayout.setVisibility(View.GONE);
advanceIcon.setImageDrawable(getResources().getDrawable((R.drawable.down)));
}
}
});
//显示和隐藏密码
wifiPasswordIcon.setOnClickListener(new View.OnClickListener(){
@Override
public void onClick(View view){
if(wifiPasswordIcon.getTag()==null) return;
if(wifiPasswordIcon.getTag().toString().equals("show")){
wifiPasswordIcon.setImageDrawable(getResources().getDrawable((R.drawable.hide)));
wifiPasswordIcon.setTag("hide");
mViewModel.apPasswordEdit.setTransformationMethod(PasswordTransformationMethod.getInstance());
}else{
wifiPasswordIcon.setImageDrawable(getResources().getDrawable((R.drawable.show)));
wifiPasswordIcon.setTag("show");
mViewModel.apPasswordEdit.setTransformationMethod(HideReturnsTransformationMethod.getInstance());
}
}
});
}
}

View File

@@ -0,0 +1,64 @@
package com.kerwin.wumei.fragment.device;
import android.Manifest;
import android.os.Build;
import android.os.Bundle;
import android.text.method.HideReturnsTransformationMethod;
import android.text.method.PasswordTransformationMethod;
import android.util.Log;
import android.view.View;
import android.widget.FrameLayout;
import android.widget.LinearLayout;
import androidx.appcompat.widget.AppCompatImageView;
import com.kerwin.wumei.MyApp;
import com.kerwin.wumei.R;
import com.kerwin.wumei.activity.AddDeviceActivity;
import com.kerwin.wumei.activity.MainActivity;
import com.kerwin.wumei.adapter.entity.EspTouchViewModel;
import com.kerwin.wumei.core.BaseFragment;
import com.kerwin.wumei.utils.XToastUtils;
import com.xuexiang.xpage.annotation.Page;
import com.xuexiang.xui.widget.spinner.materialspinner.MaterialSpinner;
import java.util.List;
import butterknife.BindView;
@Page(name = "设备信息")
public class AddDeviceTwoFragment extends BaseFragment {
/**
* 布局的资源id
* @return
*/
@Override
protected int getLayoutId() {
return R.layout.fragment_add_device_two;
}
/**
* 初始化控件
*/
@Override
protected void initViews() {
Bundle arguments = getArguments();
String mac = arguments.getString("device_mac");
XToastUtils.toast("设备MAC:" + mac);
}
@Override
protected void initListeners() {
}
}

View File

@@ -0,0 +1,100 @@
package com.kerwin.wumei.fragment.device;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.AnimatorSet;
import android.animation.ObjectAnimator;
import android.view.View;
import android.widget.TextView;
import androidx.appcompat.widget.AppCompatImageView;
import androidx.viewpager2.widget.ViewPager2;
import com.google.android.material.tabs.TabLayout;
import com.google.android.material.tabs.TabLayoutMediator;
import com.kerwin.wumei.core.BaseFragment;
import com.kerwin.wumei.R;
import com.kerwin.wumei.utils.XToastUtils;
import com.xuexiang.xaop.annotation.SingleClick;
import com.xuexiang.xpage.annotation.Page;
import com.xuexiang.xui.adapter.simple.AdapterItem;
import com.xuexiang.xui.utils.WidgetUtils;
import com.xuexiang.xui.widget.actionbar.TitleBar;
import com.xuexiang.xui.widget.popupwindow.popup.XUISimplePopup;
import com.xuexiang.xutil.display.ViewUtils;
import butterknife.BindView;
import butterknife.OnClick;
import static com.google.android.material.tabs.TabLayout.MODE_SCROLLABLE;
@Page(name = "设备")
public class DeviceFragment extends BaseFragment implements TabLayout.OnTabSelectedListener{
@BindView(R.id.tab_layout)
TabLayout tabLayout;
@BindView(R.id.view_pager)
ViewPager2 viewPager;
private boolean mIsShowNavigationView;
private FragmentStateViewPager2Adapter mAdapter;
/**
* @return 返回为 null意为不需要导航栏
*/
@Override
protected TitleBar initTitle() {
// mAdapter.addFragment(2, SimpleTabFragment.newInstance("动态加入"), "动态加入");
// mAdapter.removeFragment(2);
// mAdapter.notifyDataSetChanged();
return null;
}
/**
* 布局的资源id
*
* @return
*/
@Override
protected int getLayoutId() {
return R.layout.fragment_device;
}
/**
* 初始化控件
*/
@Override
protected void initViews() {
mAdapter = new FragmentStateViewPager2Adapter(this);
tabLayout.setTabMode(MODE_SCROLLABLE);
tabLayout.addOnTabSelectedListener(this);
viewPager.setAdapter(mAdapter);
// 设置缓存的数量
viewPager.setOffscreenPageLimit(1);
new TabLayoutMediator(tabLayout, viewPager, (tab, position) -> tab.setText(mAdapter.getPageTitle(position))).attach();
// 动态加载选项卡内容
for (String page : MultiPage.getPageNames()) {
mAdapter.addFragment(SimpleTabFragment.newInstance(page), page);
}
mAdapter.notifyDataSetChanged();
viewPager.setCurrentItem(0, false);
WidgetUtils.setTabLayoutTextFont(tabLayout);
}
@Override
public void onTabSelected(TabLayout.Tab tab) {
XToastUtils.toast("选中了:" + tab.getText());
}
@Override
public void onTabUnselected(TabLayout.Tab tab) {
}
@Override
public void onTabReselected(TabLayout.Tab tab) {
}
}

View File

@@ -0,0 +1,27 @@
package com.kerwin.wumei.fragment.device;
import com.kerwin.wumei.R;
import com.kerwin.wumei.core.BaseFragment;
import com.xuexiang.xpage.annotation.Page;
@Page(name = "分享设备")
public class EditDeviceFragment extends BaseFragment {
/**
* 布局的资源id
*
* @return
*/
@Override
protected int getLayoutId() {
return R.layout.fragment_edit_device;
}
/**
* 初始化控件
*/
@Override
protected void initViews() {
}
}

View File

@@ -0,0 +1,91 @@
package com.kerwin.wumei.fragment.device;
import androidx.annotation.NonNull;
import androidx.fragment.app.Fragment;
import androidx.viewpager2.adapter.FragmentStateAdapter;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.atomic.AtomicLong;
/**
* @author xuexiang
* @since 2020/5/21 1:27 AM
*/
public class FragmentStateViewPager2Adapter extends FragmentStateAdapter {
private List<Fragment> mFragmentList = new ArrayList<>();
private List<String> mTitleList = new ArrayList<>();
private List<Long> mIds = new ArrayList<>();
private AtomicLong mAtomicLong = new AtomicLong(0);
public FragmentStateViewPager2Adapter(@NonNull Fragment fragment) {
super(fragment);
}
@NonNull
@Override
public Fragment createFragment(int position) {
return mFragmentList.get(position);
}
public FragmentStateViewPager2Adapter addFragment(Fragment fragment, String title) {
if (fragment != null) {
mFragmentList.add(fragment);
mTitleList.add(title);
mIds.add(getAtomicGeneratedId());
}
return this;
}
public FragmentStateViewPager2Adapter addFragment(int index, Fragment fragment, String title) {
if (fragment != null && index >= 0 && index <= mFragmentList.size()) {
mFragmentList.add(index, fragment);
mTitleList.add(index, title);
mIds.add(index, getAtomicGeneratedId());
}
return this;
}
public FragmentStateViewPager2Adapter removeFragment(int index) {
if (index >= 0 && index < mFragmentList.size()) {
mFragmentList.remove(index);
mTitleList.remove(index);
mIds.remove(index);
}
return this;
}
private long getAtomicGeneratedId() {
return mAtomicLong.incrementAndGet();
}
@Override
public int getItemCount() {
return mFragmentList.size();
}
public void clear() {
mFragmentList.clear();
mTitleList.clear();
mIds.clear();
notifyDataSetChanged();
}
public CharSequence getPageTitle(int position) {
return mTitleList.get(position);
}
@Override
public long getItemId(int position) {
return mIds.get(position);
}
@Override
public boolean containsItem(long itemId) {
return mIds.contains(itemId);
}
}

View File

@@ -0,0 +1,27 @@
package com.kerwin.wumei.fragment.device;
import com.kerwin.wumei.R;
import com.kerwin.wumei.core.BaseFragment;
import com.xuexiang.xpage.annotation.Page;
@Page(name = "分组管理")
public class GroupFragment extends BaseFragment {
/**
* 布局的资源id
*
* @return
*/
@Override
protected int getLayoutId() {
return R.layout.fragment_group;
}
/**
* 初始化控件
*/
@Override
protected void initViews() {
}
}

View File

@@ -0,0 +1,44 @@
package com.kerwin.wumei.fragment.device;/*
/**
* @author xuexiang
* @since 2018/12/26 下午11:49
*/
public enum MultiPage {
全部(0),
浇灌(1),
一楼(2),
二楼(3),
三楼(4),
走廊(5);
private final int position;
MultiPage(int pos) {
position = pos;
}
public static MultiPage getPage(int position) {
return MultiPage.values()[position];
}
public static int size() {
return MultiPage.values().length;
}
public static String[] getPageNames() {
MultiPage[] pages = MultiPage.values();
String[] pageNames = new String[pages.length];
for (int i = 0; i < pages.length; i++) {
pageNames[i] = pages[i].name();
}
return pageNames;
}
public int getPosition() {
return position;
}
}

View File

@@ -0,0 +1,59 @@
package com.kerwin.wumei.fragment.device;
import android.Manifest;
import android.os.Build;
import android.text.method.HideReturnsTransformationMethod;
import android.text.method.PasswordTransformationMethod;
import android.util.Log;
import android.view.View;
import android.widget.FrameLayout;
import android.widget.LinearLayout;
import androidx.appcompat.widget.AppCompatImageView;
import com.kerwin.wumei.MyApp;
import com.kerwin.wumei.R;
import com.kerwin.wumei.activity.MainActivity;
import com.kerwin.wumei.adapter.entity.EspTouchViewModel;
import com.kerwin.wumei.core.BaseFragment;
import com.xuexiang.xpage.annotation.Page;
import com.xuexiang.xpage.enums.CoreAnim;
import com.xuexiang.xui.widget.actionbar.TitleBar;
import com.xuexiang.xui.widget.spinner.materialspinner.MaterialSpinner;
import java.util.List;
import butterknife.BindView;
@Page(anim = CoreAnim.none)
public class SceneFragment extends BaseFragment {
/**
* @return 返回为 null意为不需要导航栏
*/
@Override
protected TitleBar initTitle() {
return null;
}
/**
* 布局的资源id
*
* @return
*/
@Override
protected int getLayoutId() {
return R.layout.fragment_scene;
}
/**
* 初始化控件
*/
@Override
protected void initViews() { }
@Override
protected void initListeners() { }
}

View File

@@ -0,0 +1,29 @@
package com.kerwin.wumei.fragment.device;
import com.kerwin.wumei.R;
import com.kerwin.wumei.core.BaseFragment;
import com.xuexiang.xpage.annotation.Page;
import com.xuexiang.xpage.enums.CoreAnim;
import com.xuexiang.xui.widget.actionbar.TitleBar;
@Page(name = "分享设备")
public class ShareDeviceFragment extends BaseFragment {
/**
* 布局的资源id
*
* @return
*/
@Override
protected int getLayoutId() {
return R.layout.fragment_share_device;
}
/**
* 初始化控件
*/
@Override
protected void initViews() {
}
}

View File

@@ -0,0 +1,255 @@
package com.kerwin.wumei.fragment.device;
import android.content.Context;
import android.graphics.Point;
import android.graphics.drawable.Drawable;
import android.os.Bundle;
import android.os.Vibrator;
import android.text.method.HideReturnsTransformationMethod;
import android.text.method.PasswordTransformationMethod;
import android.util.DisplayMetrics;
import android.util.Log;
import android.view.Display;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.view.WindowManager;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.appcompat.widget.AppCompatImageView;
import androidx.cardview.widget.CardView;
import androidx.core.content.ContextCompat;
import androidx.core.graphics.drawable.DrawableCompat;
import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentActivity;
import androidx.recyclerview.widget.RecyclerView;
import com.alibaba.android.vlayout.DelegateAdapter;
import com.alibaba.android.vlayout.LayoutHelper;
import com.alibaba.android.vlayout.VirtualLayoutManager;
import com.alibaba.android.vlayout.layout.GridLayoutHelper;
import com.alibaba.android.vlayout.layout.LinearLayoutHelper;
import com.alibaba.android.vlayout.layout.StaggeredGridLayoutHelper;
import com.alibaba.android.vlayout.layout.StickyLayoutHelper;
import com.kerwin.wumei.R;
import com.kerwin.wumei.adapter.base.broccoli.BroccoliSimpleDelegateAdapter;
import com.kerwin.wumei.adapter.base.delegate.SimpleDelegateAdapter;
import com.kerwin.wumei.adapter.base.delegate.SingleDelegateAdapter;
import com.kerwin.wumei.adapter.entity.NewInfo;
import com.kerwin.wumei.utils.DemoDataProvider;
import com.kerwin.wumei.utils.RandomUtils;
import com.kerwin.wumei.utils.Utils;
import com.kerwin.wumei.utils.XToastUtils;
import com.scwang.smartrefresh.layout.SmartRefreshLayout;
import com.xuexiang.xrouter.annotation.AutoWired;
import com.xuexiang.xrouter.launcher.XRouter;
import com.xuexiang.xui.adapter.recyclerview.RecyclerViewHolder;
import com.xuexiang.xui.adapter.simple.AdapterItem;
import com.xuexiang.xui.widget.banner.widget.banner.SimpleImageBanner;
import com.xuexiang.xui.widget.button.SwitchIconView;
import com.xuexiang.xui.widget.imageview.ImageLoader;
import com.xuexiang.xui.widget.imageview.RadiusImageView;
import com.xuexiang.xui.widget.textview.supertextview.SuperButton;
import java.util.ArrayList;
import java.util.Collection;
import butterknife.BindView;
import butterknife.ButterKnife;
import butterknife.Unbinder;
import me.samlss.broccoli.Broccoli;
import static com.xuexiang.xui.utils.Utils.getScreenWidth;
import static com.xuexiang.xutil.display.DensityUtils.dip2px;
/**
* @author xuexiang
* @since 2020/4/21 12:24 AM
*/
public class SimpleTabFragment extends Fragment {
private static final String TAG = "SimpleTabFragment";
private static final String KEY_TITLE = "title";
// @BindView(R.id.tv_title)
// TextView tvTitle;
// @BindView(R.id.tv_explain)
// TextView tvExplain;
@BindView(R.id.recyclerView)
RecyclerView recyclerView;
@BindView(R.id.refreshLayout)
SmartRefreshLayout refreshLayout;
// @BindView(R.id.item_card_view)
// CardView itemCardView;
private Unbinder mUnbinder;
private SimpleDelegateAdapter<NewInfo> mNewsAdapter;
@AutoWired(name = KEY_TITLE)
String title;
public static SimpleTabFragment newInstance(String title) {
Bundle args = new Bundle();
args.putString(KEY_TITLE, title);
SimpleTabFragment fragment = new SimpleTabFragment();
fragment.setArguments(args);
return fragment;
}
@Override
public void onAttach(@NonNull Context context) {
super.onAttach(context);
Log.e(TAG, "onAttach:" + title);
}
@Override
public void onDetach() {
super.onDetach();
Log.e(TAG, "onDetach:" + title);
}
@Override
public void onResume() {
super.onResume();
Log.e(TAG, "onResume:" + title);
}
@Override
public void onStop() {
super.onStop();
Log.e(TAG, "onStop:" + title);
}
@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
XRouter.getInstance().inject(this);
}
@Nullable
@Override
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.fragment_simple_tab, container, false);
mUnbinder = ButterKnife.bind(this, view);
initView();
return view;
}
private void initView() {
// int randomNumber = RandomUtils.getRandom(10, 100);
// Log.e(TAG, "initView, random number:" + randomNumber + ", " + title);
// tvTitle.setText(String.format("这个是%s页面的内容", title));
// tvExplain.setText(String.format("这个是页面随机生成的数字:%d", randomNumber));
VirtualLayoutManager virtualLayoutManager = new VirtualLayoutManager(getContext());
recyclerView.setLayoutManager(virtualLayoutManager);
RecyclerView.RecycledViewPool viewPool = new RecyclerView.RecycledViewPool();
recyclerView.setRecycledViewPool(viewPool);
viewPool.setMaxRecycledViews(0, 10);
//顶部按钮
// SingleDelegateAdapter buttonAdapter = new SingleDelegateAdapter(R.layout.adapter_button_top_item) {
// @Override
// public void onBindViewHolder(@NonNull RecyclerViewHolder holder, int position) {
// SuperButton superButton = holder.findViewById(R.id.device_item_all_open);
// }
// };
// 设备
FragmentActivity activity=this.getActivity();
mNewsAdapter = new BroccoliSimpleDelegateAdapter<NewInfo>(R.layout.adapter_device_card_view_list_item, new StaggeredGridLayoutHelper(2,0), DemoDataProvider.getEmptyNewInfo()) {
@Override
protected void onBindData(RecyclerViewHolder holder, NewInfo model, int position) {
//设置item宽度适配屏幕分辨率
// CardView view=holder.findViewById(R.id.device_item_card_view);
// int widthPixels = getScreenWidth(activity);
// int space=dip2px(40); //间隙=左边距+右边距+中间间隔
// ViewGroup.LayoutParams cardViewParams=view.getLayoutParams();
// cardViewParams.width=(widthPixels-space)/2;
//设置开关按钮
SwitchIconView switchIconView=holder.findViewById(R.id.device_item_switch_button);
holder.click(R.id.device_item_switch_button, v -> {
Vibrator vibrator = (Vibrator) activity.getSystemService(activity.VIBRATOR_SERVICE);
vibrator.vibrate(100);
switchIconView.switchState();
});
AppCompatImageView stateView=holder.findViewById(R.id.device_item_state_icon);
stateView.setImageDrawable(getResources().getDrawable((R.drawable.state_a)));
if (model != null) {
// holder.text(R.id.tv_user_name, model.getUserName());
// holder.text(R.id.tv_tag, model.getTag());
// holder.text(R.id.tv_title, model.getTitle());
// holder.text(R.id.tv_summary, model.getSummary());
// holder.text(R.id.tv_praise, model.getPraise() == 0 ? "点赞" : String.valueOf(model.getPraise()));
// holder.text(R.id.tv_comment, model.getComment() == 0 ? "评论" : String.valueOf(model.getComment()));
// holder.text(R.id.tv_read, "阅读量 " + model.getRead());
// holder.image(R.id.iv_image, model.getImageUrl());
//
// holder.click(R.id.card_view, v -> Utils.goWeb(getContext(), model.getDetailUrl()));
}
}
@Override
protected void onBindBroccoli(RecyclerViewHolder holder, Broccoli broccoli) {
// broccoli.addPlaceholders(
// holder.findView(R.id.device_item_title),
// holder.findView(R.id.device_item_title_icon),
// holder.findView(R.id.device_item_time),
// holder.findView(R.id.device_item_time_icon),
// holder.findView(R.id.device_item_temperature),
// holder.findView(R.id.device_item_humidity),
// holder.findView(R.id.device_item_wifi),
// holder.findView(R.id.device_item_wifi_icon),
// holder.findView(R.id.device_item_state),
// holder.findView(R.id.device_item_state_icon),
// holder.findView(R.id.device_item_switch_button)
// );
}
};
DelegateAdapter delegateAdapter = new DelegateAdapter(virtualLayoutManager);
delegateAdapter.addAdapter(mNewsAdapter);
recyclerView.setAdapter(delegateAdapter);
//下拉刷新
refreshLayout.setOnRefreshListener(refreshLayout -> {
// TODO: 2020-02-25 这里只是模拟了网络请求
refreshLayout.getLayout().postDelayed(() -> {
mNewsAdapter.refresh(DemoDataProvider.getDemoNewInfos());
refreshLayout.finishRefresh();
}, 1000);
});
//上拉加载
refreshLayout.setOnLoadMoreListener(refreshLayout -> {
// TODO: 2020-02-25 这里只是模拟了网络请求
refreshLayout.getLayout().postDelayed(() -> {
mNewsAdapter.loadMore(DemoDataProvider.getDemoNewInfos());
refreshLayout.finishLoadMore();
}, 1000);
});
refreshLayout.autoRefresh();//第一次进入触发自动刷新,演示效果
}
@Override
public void onDestroyView() {
if (mUnbinder != null) {
mUnbinder.unbind();
}
super.onDestroyView();
Log.e(TAG, "onDestroyView:" + title);
}
}

View File

@@ -0,0 +1,173 @@
package com.kerwin.wumei.fragment.news;
import androidx.annotation.NonNull;
import androidx.recyclerview.widget.RecyclerView;
import com.alibaba.android.vlayout.DelegateAdapter;
import com.alibaba.android.vlayout.VirtualLayoutManager;
import com.alibaba.android.vlayout.layout.GridLayoutHelper;
import com.alibaba.android.vlayout.layout.LinearLayoutHelper;
import com.kerwin.wumei.adapter.base.broccoli.BroccoliSimpleDelegateAdapter;
import com.kerwin.wumei.adapter.base.delegate.SimpleDelegateAdapter;
import com.kerwin.wumei.adapter.base.delegate.SingleDelegateAdapter;
import com.kerwin.wumei.adapter.entity.NewInfo;
import com.kerwin.wumei.core.BaseFragment;
import com.scwang.smartrefresh.layout.SmartRefreshLayout;
import com.kerwin.wumei.R;
import com.kerwin.wumei.utils.DemoDataProvider;
import com.kerwin.wumei.utils.Utils;
import com.kerwin.wumei.utils.XToastUtils;
import com.xuexiang.xpage.annotation.Page;
import com.xuexiang.xpage.enums.CoreAnim;
import com.xuexiang.xui.adapter.recyclerview.RecyclerViewHolder;
import com.xuexiang.xui.adapter.simple.AdapterItem;
import com.xuexiang.xui.widget.actionbar.TitleBar;
import com.xuexiang.xui.widget.banner.widget.banner.SimpleImageBanner;
import com.xuexiang.xui.widget.imageview.ImageLoader;
import com.xuexiang.xui.widget.imageview.RadiusImageView;
import butterknife.BindView;
import me.samlss.broccoli.Broccoli;
@Page(anim = CoreAnim.none)
public class NewsFragment extends BaseFragment {
@BindView(R.id.recyclerView)
RecyclerView recyclerView;
@BindView(R.id.refreshLayout)
SmartRefreshLayout refreshLayout;
private SimpleDelegateAdapter<NewInfo> mNewsAdapter;
/**
* @return 返回为 null意为不需要导航栏
*/
@Override
protected TitleBar initTitle() {
return null;
}
/**
* 布局的资源id
*
* @return
*/
@Override
protected int getLayoutId() {
return R.layout.fragment_news;
}
/**
* 初始化控件
*/
@Override
protected void initViews() {
VirtualLayoutManager virtualLayoutManager = new VirtualLayoutManager(getContext());
recyclerView.setLayoutManager(virtualLayoutManager);
RecyclerView.RecycledViewPool viewPool = new RecyclerView.RecycledViewPool();
recyclerView.setRecycledViewPool(viewPool);
viewPool.setMaxRecycledViews(0, 10);
//轮播条
SingleDelegateAdapter bannerAdapter = new SingleDelegateAdapter(R.layout.include_head_view_banner) {
@Override
public void onBindViewHolder(@NonNull RecyclerViewHolder holder, int position) {
SimpleImageBanner banner = holder.findViewById(R.id.sib_simple_usage);
banner.setSource(DemoDataProvider.getBannerList())
.setOnItemClickListener((view, item, position1) -> XToastUtils.toast("headBanner position--->" + position1)).startScroll();
}
};
//九宫格菜单
GridLayoutHelper gridLayoutHelper = new GridLayoutHelper(4);
gridLayoutHelper.setPadding(0, 16, 0, 0);
gridLayoutHelper.setVGap(10);
gridLayoutHelper.setHGap(0);
SimpleDelegateAdapter<AdapterItem> commonAdapter = new SimpleDelegateAdapter<AdapterItem>(R.layout.adapter_common_grid_item, gridLayoutHelper, DemoDataProvider.getGridItems(getContext())) {
@Override
protected void bindData(@NonNull RecyclerViewHolder holder, int position, AdapterItem item) {
if (item != null) {
RadiusImageView imageView = holder.findViewById(R.id.riv_item);
imageView.setCircle(true);
ImageLoader.get().loadImage(imageView, item.getIcon());
holder.text(R.id.device_item_title, item.getTitle().toString().substring(0, 1));
holder.text(R.id.tv_sub_title, item.getTitle());
holder.click(R.id.ll_container, v -> XToastUtils.toast("点击了:" + item.getTitle()));
}
}
};
//动态的标题
SingleDelegateAdapter titleAdapter = new SingleDelegateAdapter(R.layout.adapter_title_item) {
@Override
public void onBindViewHolder(@NonNull RecyclerViewHolder holder, int position) {
holder.text(R.id.device_item_title, "动态");
holder.text(R.id.tv_action, "更多");
holder.click(R.id.tv_action, v -> XToastUtils.toast("更多"));
}
};
// 动态
mNewsAdapter = new BroccoliSimpleDelegateAdapter<NewInfo>(R.layout.adapter_news_card_view_list_item, new LinearLayoutHelper(), DemoDataProvider.getEmptyNewInfo()) {
@Override
protected void onBindData(RecyclerViewHolder holder, NewInfo model, int position) {
if (model != null) {
holder.text(R.id.tv_user_name, model.getUserName());
holder.text(R.id.tv_tag, model.getTag());
holder.text(R.id.device_item_title, model.getTitle());
holder.text(R.id.tv_summary, model.getSummary());
holder.text(R.id.tv_praise, model.getPraise() == 0 ? "点赞" : String.valueOf(model.getPraise()));
holder.text(R.id.tv_comment, model.getComment() == 0 ? "评论" : String.valueOf(model.getComment()));
holder.text(R.id.tv_read, "阅读量 " + model.getRead());
holder.image(R.id.iv_image, model.getImageUrl());
holder.click(R.id.card_view, v -> Utils.goWeb(getContext(), model.getDetailUrl()));
}
}
@Override
protected void onBindBroccoli(RecyclerViewHolder holder, Broccoli broccoli) {
broccoli.addPlaceholders(
holder.findView(R.id.tv_user_name),
holder.findView(R.id.tv_tag),
holder.findView(R.id.device_item_title),
holder.findView(R.id.tv_summary),
holder.findView(R.id.tv_praise),
holder.findView(R.id.tv_comment),
holder.findView(R.id.tv_read),
holder.findView(R.id.iv_image)
);
}
};
DelegateAdapter delegateAdapter = new DelegateAdapter(virtualLayoutManager);
delegateAdapter.addAdapter(bannerAdapter);
delegateAdapter.addAdapter(commonAdapter);
delegateAdapter.addAdapter(titleAdapter);
delegateAdapter.addAdapter(mNewsAdapter);
recyclerView.setAdapter(delegateAdapter);
}
@Override
protected void initListeners() {
//下拉刷新
refreshLayout.setOnRefreshListener(refreshLayout -> {
// TODO: 2020-02-25 这里只是模拟了网络请求
refreshLayout.getLayout().postDelayed(() -> {
mNewsAdapter.refresh(DemoDataProvider.getDemoNewInfos());
refreshLayout.finishRefresh();
}, 1000);
});
//上拉加载
refreshLayout.setOnLoadMoreListener(refreshLayout -> {
// TODO: 2020-02-25 这里只是模拟了网络请求
refreshLayout.getLayout().postDelayed(() -> {
mNewsAdapter.loadMore(DemoDataProvider.getDemoNewInfos());
refreshLayout.finishLoadMore();
}, 1000);
});
refreshLayout.autoRefresh();//第一次进入触发自动刷新,演示效果
}
}

View File

@@ -0,0 +1,88 @@
package com.kerwin.wumei.fragment.profile;
import android.graphics.drawable.ColorDrawable;
import com.kerwin.wumei.core.BaseFragment;
import com.kerwin.wumei.R;
import com.kerwin.wumei.fragment.AboutFragment;
import com.kerwin.wumei.fragment.FeedbackFragment;
import com.kerwin.wumei.fragment.MessageFragment;
import com.kerwin.wumei.fragment.SettingsFragment;
import com.xuexiang.xaop.annotation.SingleClick;
import com.xuexiang.xpage.annotation.Page;
import com.xuexiang.xpage.enums.CoreAnim;
import com.xuexiang.xpage.utils.Utils;
import com.xuexiang.xui.widget.actionbar.TitleBar;
import com.xuexiang.xui.widget.imageview.RadiusImageView;
import com.xuexiang.xui.widget.textview.supertextview.SuperTextView;
import butterknife.BindView;
@Page(anim = CoreAnim.none)
public class ProfileFragment extends BaseFragment implements SuperTextView.OnSuperTextViewClickListener {
@BindView(R.id.riv_head_pic)
RadiusImageView rivHeadPic;
@BindView(R.id.menu_settings)
SuperTextView menuSettings;
@BindView(R.id.menu_about)
SuperTextView menuAbout;
@BindView(R.id.menu_feedback)
SuperTextView menuFeedback;
@BindView(R.id.menu_message)
SuperTextView menuMessage;
/**
* @return 返回为 null意为不需要导航栏
*/
@Override
protected TitleBar initTitle() {
return null;
}
/**
* 布局的资源id
*
* @return
*/
@Override
protected int getLayoutId() {
return R.layout.fragment_profile;
}
/**
* 初始化控件
*/
@Override
protected void initViews() {
}
@Override
protected void initListeners() {
menuSettings.setOnSuperTextViewClickListener(this);
menuAbout.setOnSuperTextViewClickListener(this);
menuFeedback.setOnSuperTextViewClickListener(this);
menuMessage.setOnSuperTextViewClickListener(this);
}
@SingleClick
@Override
public void onClick(SuperTextView view) {
switch(view.getId()) {
case R.id.menu_settings:
openNewPage(SettingsFragment.class);
break;
case R.id.menu_about:
openNewPage(AboutFragment.class);
break;
case R.id.menu_message:
openNewPage(MessageFragment.class);
break;
case R.id.menu_feedback:
openNewPage(FeedbackFragment.class);
break;
default:
break;
}
}
}

View File

@@ -0,0 +1,154 @@
/*
* Copyright (C) 2021 xuexiangjys(xuexiangjys@163.com)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
package com.kerwin.wumei.utils;
import android.content.Context;
import android.graphics.drawable.Drawable;
import com.kerwin.wumei.adapter.entity.NewInfo;
import com.kerwin.wumei.R;
import com.xuexiang.xaop.annotation.MemoryCache;
import com.xuexiang.xui.adapter.simple.AdapterItem;
import com.xuexiang.xui.utils.ResUtils;
import com.xuexiang.xui.widget.banner.widget.banner.BannerItem;
import java.util.ArrayList;
import java.util.List;
/**
* 演示数据
*
* @author xuexiang
* @since 2018/11/23 下午5:52
*/
public class DemoDataProvider {
public static String[] titles = new String[]{
"伪装者:胡歌演绎'痞子特工'",
"无心法师:生死离别!月牙遭虐杀",
"花千骨:尊上沦为花千骨",
"综艺饭:胖轩偷看夏天洗澡掀波澜",
"碟中谍4:阿汤哥高塔命悬一线,超越不可能",
};
public static String[] urls = new String[]{//640*360 360/640=0.5625
"http://photocdn.sohu.com/tvmobilemvms/20150907/144160323071011277.jpg",//伪装者:胡歌演绎"痞子特工"
"http://photocdn.sohu.com/tvmobilemvms/20150907/144158380433341332.jpg",//无心法师:生死离别!月牙遭虐杀
"http://photocdn.sohu.com/tvmobilemvms/20150907/144160286644953923.jpg",//花千骨:尊上沦为花千骨
"http://photocdn.sohu.com/tvmobilemvms/20150902/144115156939164801.jpg",//综艺饭:胖轩偷看夏天洗澡掀波澜
"http://photocdn.sohu.com/tvmobilemvms/20150907/144159406950245847.jpg",//碟中谍4:阿汤哥高塔命悬一线,超越不可能
};
@MemoryCache
public static List<BannerItem> getBannerList() {
List<BannerItem> list = new ArrayList<>();
for (int i = 0; i < urls.length; i++) {
BannerItem item = new BannerItem();
item.imgUrl = urls[i];
item.title = titles[i];
list.add(item);
}
return list;
}
/**
* 用于占位的空信息
*
* @return
*/
@MemoryCache
public static List<NewInfo> getDemoNewInfos() {
List<NewInfo> list = new ArrayList<>();
list.add(new NewInfo("源码", "Android源码分析--Android系统启动")
.setSummary("其实Android系统的启动最主要的内容无非是init、Zygote、SystemServer这三个进程的启动他们一起构成的铁三角是Android系统的基础。")
.setDetailUrl("https://juejin.im/post/5c6fc0cdf265da2dda694f05")
.setImageUrl("https://user-gold-cdn.xitu.io/2019/2/22/16914891cd8a950a?imageView2/0/w/1280/h/960/format/webp/ignore-error/1"));
list.add(new NewInfo("Android UI", "XUI 一个简洁而优雅的Android原生UI框架解放你的双手")
.setSummary("涵盖绝大部分的UI组件TextView、Button、EditText、ImageView、Spinner、Picker、Dialog、PopupWindow、ProgressBar、LoadingView、StateLayout、FlowLayout、Switch、Actionbar、TabBar、Banner、GuideView、BadgeView、MarqueeView、WebView、SearchView等一系列的组件和丰富多彩的样式主题。\n")
.setDetailUrl("https://juejin.im/post/5c3ed1dae51d4543805ea48d")
.setImageUrl("https://user-gold-cdn.xitu.io/2019/1/16/1685563ae5456408?imageView2/0/w/1280/h/960/format/webp/ignore-error/1"));
list.add(new NewInfo("面试", "写给即将面试的你")
.setSummary("最近由于公司业务发展,需要招聘技术方面的人才,由于我在技术方面比较熟悉,技术面的任务就交给我了。今天我要分享的就和面试有关,主要包含技术面的流程、经验和建议,避免大家在今后的面试过程中走一些弯路,帮助即将需要跳槽面试的人。")
.setDetailUrl("https://juejin.im/post/5ca4df966fb9a05e4e58320c")
.setImageUrl("https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1554629219186&di=6cdab5cfceaae1f7e6d78dbe79104c9f&imgtype=0&src=http%3A%2F%2Fimg.qinxue365.com%2Fuploads%2Fallimg%2F1902%2F4158-1Z22FZ64E00.jpg"));
list.add(new NewInfo("Android", "XUpdate 一个轻量级、高可用性的Android版本更新框架")
.setSummary("XUpdate 一个轻量级、高可用性的Android版本更新框架。本框架借鉴了AppUpdate中的部分思想和UI界面将版本更新中的各部分环节抽离出来形成了如下几个部分")
.setDetailUrl("https://juejin.im/post/5b480b79e51d45190905ef44")
.setImageUrl("https://user-gold-cdn.xitu.io/2018/7/13/16492d9b7877dc21?imageView2/0/w/1280/h/960/format/webp/ignore-error/1"));
list.add(new NewInfo("Android/HTTP", "XHttp2 一个功能强悍的网络请求库使用RxJava2 + Retrofit2 + OKHttp进行组装")
.setSummary("一个功能强悍的网络请求库使用RxJava2 + Retrofit2 + OKHttp组合进行封装。还不赶紧点击使用说明文档体验一下吧")
.setDetailUrl("https://juejin.im/post/5b6b9b49e51d4576b828978d")
.setImageUrl("https://user-gold-cdn.xitu.io/2018/8/9/1651c568a7e30e02?imageView2/0/w/1280/h/960/format/webp/ignore-error/1"));
list.add(new NewInfo("Android/HTTP", "XHttp2 一个功能强悍的网络请求库使用RxJava2 + Retrofit2 + OKHttp进行组装")
.setSummary("一个功能强悍的网络请求库使用RxJava2 + Retrofit2 + OKHttp组合进行封装。还不赶紧点击使用说明文档体验一下吧")
.setDetailUrl("https://juejin.im/post/5b6b9b49e51d4576b828978d")
.setImageUrl("https://user-gold-cdn.xitu.io/2018/8/9/1651c568a7e30e02?imageView2/0/w/1280/h/960/format/webp/ignore-error/1"));
list.add(new NewInfo("Android/HTTP", "XHttp2 一个功能强悍的网络请求库使用RxJava2 + Retrofit2 + OKHttp进行组装")
.setSummary("一个功能强悍的网络请求库使用RxJava2 + Retrofit2 + OKHttp组合进行封装。还不赶紧点击使用说明文档体验一下吧")
.setDetailUrl("https://juejin.im/post/5b6b9b49e51d4576b828978d")
.setImageUrl("https://user-gold-cdn.xitu.io/2018/8/9/1651c568a7e30e02?imageView2/0/w/1280/h/960/format/webp/ignore-error/1"));
list.add(new NewInfo("Android/HTTP", "XHttp2 一个功能强悍的网络请求库使用RxJava2 + Retrofit2 + OKHttp进行组装")
.setSummary("一个功能强悍的网络请求库使用RxJava2 + Retrofit2 + OKHttp组合进行封装。还不赶紧点击使用说明文档体验一下吧")
.setDetailUrl("https://juejin.im/post/5b6b9b49e51d4576b828978d")
.setImageUrl("https://user-gold-cdn.xitu.io/2018/8/9/1651c568a7e30e02?imageView2/0/w/1280/h/960/format/webp/ignore-error/1"));
list.add(new NewInfo("Android/HTTP", "XHttp2 一个功能强悍的网络请求库使用RxJava2 + Retrofit2 + OKHttp进行组装")
.setSummary("一个功能强悍的网络请求库使用RxJava2 + Retrofit2 + OKHttp组合进行封装。还不赶紧点击使用说明文档体验一下吧")
.setDetailUrl("https://juejin.im/post/5b6b9b49e51d4576b828978d")
.setImageUrl("https://user-gold-cdn.xitu.io/2018/8/9/1651c568a7e30e02?imageView2/0/w/1280/h/960/format/webp/ignore-error/1"));
list.add(new NewInfo("Android/HTTP", "XHttp2 一个功能强悍的网络请求库使用RxJava2 + Retrofit2 + OKHttp进行组装")
.setSummary("一个功能强悍的网络请求库使用RxJava2 + Retrofit2 + OKHttp组合进行封装。还不赶紧点击使用说明文档体验一下吧")
.setDetailUrl("https://juejin.im/post/5b6b9b49e51d4576b828978d")
.setImageUrl("https://user-gold-cdn.xitu.io/2018/8/9/1651c568a7e30e02?imageView2/0/w/1280/h/960/format/webp/ignore-error/1"));
return list;
}
public static List<AdapterItem> getGridItems(Context context) {
return getGridItems(context, R.array.grid_titles_entry, R.array.grid_icons_entry);
}
private static List<AdapterItem> getGridItems(Context context, int titleArrayId, int iconArrayId) {
List<AdapterItem> list = new ArrayList<>();
String[] titles = ResUtils.getStringArray(titleArrayId);
Drawable[] icons = ResUtils.getDrawableArray(context, iconArrayId);
for (int i = 0; i < titles.length; i++) {
list.add(new AdapterItem(titles[i], icons[i]));
}
return list;
}
/**
* 用于占位的空信息
*
* @return
*/
@MemoryCache
public static List<NewInfo> getEmptyNewInfo() {
List<NewInfo> list = new ArrayList<>();
for (int i = 0; i < 10; i++) {
list.add(new NewInfo());
}
return list;
}
}

View File

@@ -0,0 +1,270 @@
/*
* Copyright (C) 2021 xuexiangjys(xuexiangjys@163.com)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
package com.kerwin.wumei.utils;
import android.content.Context;
import android.os.Parcelable;
import com.tencent.mmkv.MMKV;
import java.util.Set;
/**
* MMKV工具类
*
* @author xuexiang
* @since 2019-07-04 10:20
*/
public final class MMKVUtils {
private MMKVUtils() {
throw new UnsupportedOperationException("u can't instantiate me...");
}
private static MMKV sMMKV;
/**
* 初始化
*
* @param context
*/
public static void init(Context context) {
MMKV.initialize(context.getApplicationContext());
sMMKV = MMKV.defaultMMKV();
}
public static MMKV getsMMKV() {
if (sMMKV == null) {
sMMKV = MMKV.defaultMMKV();
}
return sMMKV;
}
//=======================================键值保存==================================================//
/**
* 保存键值
*
* @param key
* @param value
* @return
*/
public static boolean put(String key, Object value) {
if (value instanceof Integer) {
return getsMMKV().encode(key, (Integer) value);
} else if (value instanceof Float) {
return getsMMKV().encode(key, (Float) value);
} else if (value instanceof String) {
return getsMMKV().encode(key, (String) value);
} else if (value instanceof Boolean) {
return getsMMKV().encode(key, (Boolean) value);
} else if (value instanceof Long) {
return getsMMKV().encode(key, (Long) value);
} else if (value instanceof Double) {
return getsMMKV().encode(key, (Double) value);
} else if (value instanceof Parcelable) {
return getsMMKV().encode(key, (Parcelable) value);
} else if (value instanceof byte[]) {
return getsMMKV().encode(key, (byte[]) value);
} else if (value instanceof Set) {
return getsMMKV().encode(key, (Set<String>) value);
}
return false;
}
//=======================================键值获取==================================================//
/**
* 获取键值
*
* @param key
* @param defaultValue
* @return
*/
public static Object get(String key, Object defaultValue) {
if (defaultValue instanceof Integer) {
return getsMMKV().decodeInt(key, (Integer) defaultValue);
} else if (defaultValue instanceof Float) {
return getsMMKV().decodeFloat(key, (Float) defaultValue);
} else if (defaultValue instanceof String) {
return getsMMKV().decodeString(key, (String) defaultValue);
} else if (defaultValue instanceof Boolean) {
return getsMMKV().decodeBool(key, (Boolean) defaultValue);
} else if (defaultValue instanceof Long) {
return getsMMKV().decodeLong(key, (Long) defaultValue);
} else if (defaultValue instanceof Double) {
return getsMMKV().decodeDouble(key, (Double) defaultValue);
} else if (defaultValue instanceof byte[]) {
return getsMMKV().decodeBytes(key);
} else if (defaultValue instanceof Set) {
return getsMMKV().decodeStringSet(key, (Set<String>) defaultValue);
}
return null;
}
/**
* 根据key获取boolean值
*
* @param key
* @param defValue
* @return
*/
public static boolean getBoolean(String key, boolean defValue) {
try {
return getsMMKV().getBoolean(key, defValue);
} catch (Exception e) {
e.printStackTrace();
}
return defValue;
}
/**
* 根据key获取long值
*
* @param key
* @param defValue
* @return
*/
public static long getLong(String key, long defValue) {
try {
return getsMMKV().getLong(key, defValue);
} catch (Exception e) {
e.printStackTrace();
}
return defValue;
}
/**
* 根据key获取float值
*
* @param key
* @param defValue
* @return
*/
public static float getFloat(String key, float defValue) {
try {
return getsMMKV().getFloat(key, defValue);
} catch (Exception e) {
e.printStackTrace();
}
return defValue;
}
/**
* 根据key获取String值
*
* @param key
* @param defValue
* @return
*/
public static String getString(String key, String defValue) {
try {
return getsMMKV().getString(key, defValue);
} catch (Exception e) {
e.printStackTrace();
}
return defValue;
}
/**
* 根据key获取int值
*
* @param key
* @param defValue
* @return
*/
public static int getInt(String key, int defValue) {
try {
return getsMMKV().getInt(key, defValue);
} catch (Exception e) {
e.printStackTrace();
}
return defValue;
}
/**
* 根据key获取double值
*
* @param key
* @param defValue
* @return
*/
public static double getDouble(String key, double defValue) {
try {
return getsMMKV().decodeDouble(key, defValue);
} catch (Exception e) {
e.printStackTrace();
}
return defValue;
}
/**
* 获取对象
*
* @param key
* @param tClass 类型
* @param <T>
* @return
*/
public static <T extends Parcelable> T getObject(String key, Class<T> tClass) {
return getsMMKV().decodeParcelable(key, tClass);
}
/**
* 获取对象
*
* @param key
* @param tClass 类型
* @param <T>
* @return
*/
public static <T extends Parcelable> T getObject(String key, Class<T> tClass, T defValue) {
try {
return getsMMKV().decodeParcelable(key, tClass, defValue);
} catch (Exception e) {
e.printStackTrace();
}
return defValue;
}
/**
* 判断键值对是否存在
*
* @param key 键
* @return 键值对是否存在
*/
public static boolean containsKey(String key) {
return getsMMKV().containsKey(key);
}
/**
* 清除指定键值对
*
* @param key 键
*/
public static void remove(String key) {
getsMMKV().remove(key).apply();
}
}

View File

@@ -0,0 +1,147 @@
package com.kerwin.wumei.utils;
import android.net.DhcpInfo;
import android.net.wifi.WifiInfo;
import android.net.wifi.WifiManager;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.net.Inet4Address;
import java.net.Inet6Address;
import java.net.InetAddress;
import java.net.NetworkInterface;
import java.net.SocketException;
import java.net.UnknownHostException;
import java.util.Enumeration;
public class NetUtils {
public static boolean isWifiConnected(WifiManager wifiManager) {
WifiInfo wifiInfo = wifiManager.getConnectionInfo();
return wifiInfo != null
&& wifiInfo.getNetworkId() != -1
&& !"<unknown ssid>".equals(wifiInfo.getSSID());
}
public static byte[] getRawSsidBytes(WifiInfo info) {
try {
Method method = info.getClass().getMethod("getWifiSsid");
method.setAccessible(true);
Object wifiSsid = method.invoke(info);
if (wifiSsid == null) {
return null;
}
method = wifiSsid.getClass().getMethod("getOctets");
method.setAccessible(true);
return (byte[]) method.invoke(wifiSsid);
} catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException | NullPointerException e) {
e.printStackTrace();
}
return null;
}
public static byte[] getRawSsidBytesOrElse(WifiInfo info, byte[] orElse) {
byte[] raw = getRawSsidBytes(info);
return raw != null ? raw : orElse;
}
public static String getSsidString(WifiInfo info) {
String ssid = info.getSSID();
if (ssid.startsWith("\"") && ssid.endsWith("\"")) {
ssid = ssid.substring(1, ssid.length() - 1);
}
return ssid;
}
public static InetAddress getBroadcastAddress(WifiManager wifi) {
DhcpInfo dhcp = wifi.getDhcpInfo();
if (dhcp != null) {
int broadcast = (dhcp.ipAddress & dhcp.netmask) | ~dhcp.netmask;
byte[] quads = new byte[4];
for (int k = 0; k < 4; k++) {
quads[k] = (byte) ((broadcast >> k * 8) & 0xFF);
}
try {
return InetAddress.getByAddress(quads);
} catch (UnknownHostException e) {
e.printStackTrace();
}
}
try {
return InetAddress.getByName("255.255.255.255");
} catch (UnknownHostException e) {
e.printStackTrace();
}
// Impossible arrive here
return null;
}
public static boolean is5G(int frequency) {
return frequency > 4900 && frequency < 5900;
}
public static InetAddress getAddress(int ipAddress) {
byte[] ip = new byte[]{
(byte) (ipAddress & 0xff),
(byte) ((ipAddress >> 8) & 0xff),
(byte) ((ipAddress >> 16) & 0xff),
(byte) ((ipAddress >> 24) & 0xff)
};
try {
return InetAddress.getByAddress(ip);
} catch (UnknownHostException e) {
e.printStackTrace();
// Impossible arrive here
return null;
}
}
private static InetAddress getAddress(boolean isIPv4) {
try {
Enumeration<NetworkInterface> enums = NetworkInterface.getNetworkInterfaces();
while (enums.hasMoreElements()) {
NetworkInterface ni = enums.nextElement();
Enumeration<InetAddress> addrs = ni.getInetAddresses();
while (addrs.hasMoreElements()) {
InetAddress address = addrs.nextElement();
if (!address.isLoopbackAddress()) {
if (isIPv4 && address instanceof Inet4Address) {
return address;
}
if (!isIPv4 && address instanceof Inet6Address) {
return address;
}
}
}
}
} catch (SocketException e) {
e.printStackTrace();
}
return null;
}
public static InetAddress getIPv4Address() {
return getAddress(true);
}
public static InetAddress getIPv6Address() {
return getAddress(false);
}
/**
* @param bssid the bssid like aa:bb:cc:dd:ee:ff
* @return byte array converted from bssid
*/
public static byte[] convertBssid2Bytes(String bssid) {
String[] bssidSplits = bssid.split(":");
if (bssidSplits.length != 6) {
throw new IllegalArgumentException("Invalid bssid format");
}
byte[] result = new byte[bssidSplits.length];
for (int i = 0; i < bssidSplits.length; i++) {
result[i] = (byte) Integer.parseInt(bssidSplits[i], 16);
}
return result;
}
}

View File

@@ -0,0 +1,285 @@
/*
* Copyright (C) 2021 xuexiangjys(xuexiangjys@163.com)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
package com.kerwin.wumei.utils;
import android.graphics.Color;
import android.text.TextUtils;
import java.util.Random;
/**
* <pre>
* desc : Random Utils
* author : xuexiang
* time : 2018/4/28 上午12:41
* </pre>
* <ul>
* Shuffling algorithm
* <li>{@link #shuffle(Object[])} Shuffling algorithm, Randomly permutes the specified array using a default source of
* randomness</li>
* <li>{@link #shuffle(Object[], int)} Shuffling algorithm, Randomly permutes the specified array</li>
* <li>{@link #shuffle(int[])} Shuffling algorithm, Randomly permutes the specified int array using a default source of
* randomness</li>
* <li>{@link #shuffle(int[], int)} Shuffling algorithm, Randomly permutes the specified int array</li>
* </ul>
* <ul>
* get random int
* <li>{@link #getRandom(int)} get random int between 0 and max</li>
* <li>{@link #getRandom(int, int)} get random int between min and max</li>
* </ul>
* <ul>
* get random numbers or letters
* <li>{@link #getRandomCapitalLetters(int)} get a fixed-length random string, its a mixture of uppercase letters</li>
* <li>{@link #getRandomLetters(int)} get a fixed-length random string, its a mixture of uppercase and lowercase letters
* </li>
* <li>{@link #getRandomLowerCaseLetters(int)} get a fixed-length random string, its a mixture of lowercase letters</li>
* <li>{@link #getRandomNumbers(int)} get a fixed-length random string, its a mixture of numbers</li>
* <li>{@link #getRandomNumbersAndLetters(int)} get a fixed-length random string, its a mixture of uppercase, lowercase
* letters and numbers</li>
* <li>{@link #getRandom(String, int)} get a fixed-length random string, its a mixture of chars in source</li>
* <li>{@link #getRandom(char[], int)} get a fixed-length random string, its a mixture of chars in sourceChar</li>
* </ul>
*
*/
public final class RandomUtils {
public static final String NUMBERS_AND_LETTERS = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
public static final String NUMBERS = "0123456789";
public static final String LETTERS = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
public static final String CAPITAL_LETTERS = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
public static final String LOWER_CASE_LETTERS = "abcdefghijklmnopqrstuvwxyz";
/**
* Don't let anyone instantiate this class.
*/
private RandomUtils() {
throw new Error("Do not need instantiate!");
}
/**
* 在数字和英文字母中获取一个定长的随机字符串
*
* @param length 长度
* @return 随机字符串
* @see RandomUtils#getRandom(String source, int length)
*/
public static String getRandomNumbersAndLetters(int length) {
return getRandom(NUMBERS_AND_LETTERS, length);
}
/**
* 在数字中获取一个定长的随机字符串
*
* @param length 长度
* @return 随机数字符串
* @see RandomUtils#getRandom(String source, int length)
*/
public static String getRandomNumbers(int length) {
return getRandom(NUMBERS, length);
}
/**
* 在英文字母中获取一个定长的随机字符串
*
* @param length 长度
* @return 随机字母字符串
* @see RandomUtils#getRandom(String source, int length)
*/
public static String getRandomLetters(int length) {
return getRandom(LETTERS, length);
}
/**
* 在大写英文字母中获取一个定长的随机字符串
*
* @param length 长度
* @return 随机字符串 只包含大写字母
* @see RandomUtils#getRandom(String source, int length)
*/
public static String getRandomCapitalLetters(int length) {
return getRandom(CAPITAL_LETTERS, length);
}
/**
* 在小写英文字母中获取一个定长的随机字符串
*
* @param length 长度
* @return 随机字符串 只包含小写字母
* @see RandomUtils#getRandom(String source, int length)
*/
public static String getRandomLowerCaseLetters(int length) {
return getRandom(LOWER_CASE_LETTERS, length);
}
/**
* 在一个字符数组源中获取一个定长的随机字符串
*
* @param source 源字符串
* @param length 长度
* @return <ul>
* <li>if source is null or empty, return null</li>
* <li>else see {@link RandomUtils#getRandom(char[] sourceChar, int length)}</li>
* </ul>
*/
public static String getRandom(String source, int length) {
return TextUtils.isEmpty(source) ? null : getRandom(source.toCharArray(), length);
}
/**
* 在一个字符数组源中获取一个定长的随机字符串
*
* @param sourceChar 字符数组源
* @param length 长度
* @return <ul>
* <li>if sourceChar is null or empty, return null</li>
* <li>if length less than 0, return null</li>
* </ul>
*/
public static String getRandom(char[] sourceChar, int length) {
if (sourceChar == null || sourceChar.length == 0 || length < 0) {
return null;
}
StringBuilder str = new StringBuilder(length);
Random random = new Random();
for (int i = 0; i < length; i++) {
str.append(sourceChar[random.nextInt(sourceChar.length)]);
}
return str.toString();
}
/**
* get random int between 0 and max
*
* @param max 最大随机数
* @return <ul>
* <li>if max <= 0, return 0</li>
* <li>else return random int between 0 and max</li>
* </ul>
*/
public static int getRandom(int max) {
return getRandom(0, max);
}
/**
* get random int between min and max
*
* @param min 最小随机数
* @param max 最大随机数
* @return <ul>
* <li>if min > max, return 0</li>
* <li>if min == max, return min</li>
* <li>else return random int between min and max</li>
* </ul>
*/
public static int getRandom(int min, int max) {
if (min > max) {
return 0;
}
if (min == max) {
return min;
}
return min + new Random().nextInt(max - min);
}
/**
* 获取随机颜色
*
* @return
*/
public static int getRandomColor() {
Random random = new Random();
int r = random.nextInt(256);
int g = random.nextInt(256);
int b = random.nextInt(256);
return Color.rgb(r, g, b);
}
/**
* 随机打乱数组中的内容
*
* @param objArray
* @return
*/
public static boolean shuffle(Object[] objArray) {
if (objArray == null) {
return false;
}
return shuffle(objArray, getRandom(objArray.length));
}
/**
* 随机打乱数组中的内容
*
* @param objArray
* @param shuffleCount
* @return
*/
public static boolean shuffle(Object[] objArray, int shuffleCount) {
int length;
if (objArray == null || shuffleCount < 0 || (length = objArray.length) < shuffleCount) {
return false;
}
for (int i = 1; i <= shuffleCount; i++) {
int random = getRandom(length - i);
Object temp = objArray[length - i];
objArray[length - i] = objArray[random];
objArray[random] = temp;
}
return true;
}
/**
* 随机打乱数组中的内容
*
* @param intArray
* @return
*/
public static int[] shuffle(int[] intArray) {
if (intArray == null) {
return null;
}
return shuffle(intArray, getRandom(intArray.length));
}
/**
* 随机打乱数组中的内容
*
* @param intArray
* @param shuffleCount
* @return
*/
public static int[] shuffle(int[] intArray, int shuffleCount) {
int length;
if (intArray == null || shuffleCount < 0 || (length = intArray.length) < shuffleCount) {
return null;
}
int[] out = new int[shuffleCount];
for (int i = 1; i <= shuffleCount; i++) {
int random = getRandom(length - i);
out[i - 1] = intArray[random];
int temp = intArray[length - i];
intArray[length - i] = intArray[random];
intArray[random] = temp;
}
return out;
}
}

View File

@@ -0,0 +1,63 @@
/*
* Copyright (C) 2021 xuexiangjys(xuexiangjys@163.com)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
package com.kerwin.wumei.utils;
/**
* SharedPreferences管理工具基类
*
* @author xuexiang
* @since 2018/11/27 下午5:16
*/
public final class SettingUtils {
private SettingUtils() {
throw new UnsupportedOperationException("u can't instantiate me...");
}
private static final String IS_FIRST_OPEN_KEY = "is_first_open_key";
private static final String IS_AGREE_PRIVACY_KEY = "is_agree_privacy_key";
/**
* 是否是第一次启动
*/
public static boolean isFirstOpen() {
return MMKVUtils.getBoolean(IS_FIRST_OPEN_KEY, true);
}
/**
* 设置是否是第一次启动
*/
public static void setIsFirstOpen(boolean isFirstOpen) {
MMKVUtils.put(IS_FIRST_OPEN_KEY, isFirstOpen);
}
/**
* @return 是否同意隐私政策
*/
public static boolean isAgreePrivacy() {
return MMKVUtils.getBoolean(IS_AGREE_PRIVACY_KEY, false);
}
public static void setIsAgreePrivacy(boolean isAgreePrivacy) {
MMKVUtils.put(IS_AGREE_PRIVACY_KEY, isAgreePrivacy);
}
}

View File

@@ -0,0 +1,100 @@
/*
* Copyright (C) 2021 xuexiangjys(xuexiangjys@163.com)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
package com.kerwin.wumei.utils;
import android.content.Context;
import com.kerwin.wumei.activity.LoginActivity;
import com.umeng.analytics.MobclickAgent;
import com.xuexiang.xutil.app.ActivityUtils;
import com.xuexiang.xutil.common.StringUtils;
/**
* Token管理工具
*
* @author xuexiang
* @since 2019-11-17 22:37
*/
public final class TokenUtils {
private static String sToken;
private static final String KEY_TOKEN = "com.xuexiang.templateproject.utils.KEY_TOKEN";
private TokenUtils() {
throw new UnsupportedOperationException("u can't instantiate me...");
}
private static final String KEY_PROFILE_CHANNEL = "github";
/**
* 初始化Token信息
*/
public static void init(Context context) {
MMKVUtils.init(context);
sToken = MMKVUtils.getString(KEY_TOKEN, "");
}
public static void setToken(String token) {
sToken = token;
MMKVUtils.put(KEY_TOKEN, token);
}
public static void clearToken() {
sToken = null;
MMKVUtils.remove(KEY_TOKEN);
}
public static String getToken() {
return sToken;
}
public static boolean hasToken() {
return MMKVUtils.containsKey(KEY_TOKEN);
}
/**
* 处理登录成功的事件
*
* @param token 账户信息
*/
public static boolean handleLoginSuccess(String token) {
if (!StringUtils.isEmpty(token)) {
XToastUtils.success("登录成功!");
MobclickAgent.onProfileSignIn(KEY_PROFILE_CHANNEL, token);
setToken(token);
return true;
} else {
XToastUtils.error("登录失败!");
return false;
}
}
/**
* 处理登出的事件
*/
public static void handleLogoutSuccess() {
MobclickAgent.onProfileSignOff();
//登出时,清除账号信息
clearToken();
XToastUtils.success("登出成功!");
//跳转到登录页
ActivityUtils.startActivity(LoginActivity.class);
}
}

View File

@@ -0,0 +1,175 @@
/*
* Copyright (C) 2021 xuexiangjys(xuexiangjys@163.com)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
package com.kerwin.wumei.utils;
import android.app.Dialog;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.graphics.Color;
import android.text.SpannableString;
import android.text.SpannableStringBuilder;
import android.text.Spanned;
import android.text.method.LinkMovementMethod;
import android.text.style.ClickableSpan;
import android.view.View;
import androidx.annotation.ColorInt;
import androidx.annotation.NonNull;
import com.kerwin.wumei.core.webview.AgentWebActivity;
import com.kerwin.wumei.core.webview.AgentWebFragment;
import com.kerwin.wumei.R;
import com.xuexiang.xui.utils.ResUtils;
import com.xuexiang.xui.widget.dialog.DialogLoader;
import com.xuexiang.xui.widget.dialog.materialdialog.DialogAction;
import com.xuexiang.xui.widget.dialog.materialdialog.MaterialDialog;
import com.xuexiang.xutil.XUtil;
/**
* 工具类
*
* @author xuexiang
* @since 2020-02-23 15:12
*/
public final class Utils {
private Utils() {
throw new UnsupportedOperationException("u can't instantiate me...");
}
/**
* 这里填写你的应用隐私政策网页地址
*/
private static final String PRIVACY_URL = "https://gitee.com/xuexiangjys/TemplateAppProject/raw/master/LICENSE";
/**
* 显示隐私政策的提示
*
* @param context
* @param submitListener 同意的监听
* @return
*/
public static Dialog showPrivacyDialog(Context context, MaterialDialog.SingleButtonCallback submitListener) {
MaterialDialog dialog = new MaterialDialog.Builder(context).title(R.string.title_reminder).autoDismiss(false).cancelable(false)
.positiveText(R.string.lab_agree).onPositive((dialog1, which) -> {
if (submitListener != null) {
submitListener.onClick(dialog1, which);
} else {
dialog1.dismiss();
}
})
.negativeText(R.string.lab_disagree).onNegative(new MaterialDialog.SingleButtonCallback() {
@Override
public void onClick(@NonNull MaterialDialog dialog, @NonNull DialogAction which) {
dialog.dismiss();
DialogLoader.getInstance().showConfirmDialog(context, ResUtils.getString(R.string.title_reminder), String.format(ResUtils.getString(R.string.content_privacy_explain_again), ResUtils.getString(R.string.app_name)), ResUtils.getString(R.string.lab_look_again), new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
dialog.dismiss();
showPrivacyDialog(context, submitListener);
}
}, ResUtils.getString(R.string.lab_still_disagree), new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
dialog.dismiss();
DialogLoader.getInstance().showConfirmDialog(context, ResUtils.getString(R.string.content_think_about_it_again), ResUtils.getString(R.string.lab_look_again), new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
dialog.dismiss();
showPrivacyDialog(context, submitListener);
}
}, ResUtils.getString(R.string.lab_exit_app), new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
dialog.dismiss();
XUtil.exitApp();
}
});
}
});
}
}).build();
dialog.setContent(getPrivacyContent(context));
//开始响应点击事件
dialog.getContentView().setMovementMethod(LinkMovementMethod.getInstance());
dialog.show();
return dialog;
}
/**
* @return 隐私政策说明
*/
private static SpannableStringBuilder getPrivacyContent(Context context) {
SpannableStringBuilder stringBuilder = new SpannableStringBuilder()
.append(" 欢迎来到").append(ResUtils.getString(R.string.app_name)).append("!\n")
.append(" 我们深知个人信息对你的重要性,也感谢你对我们的信任。\n")
.append(" 为了更好地保护你的权益,同时遵守相关监管的要求,我们将通过");
stringBuilder.append(getPrivacyLink(context, PRIVACY_URL))
.append("向你说明我们会如何收集、存储、保护、使用及对外提供你的信息,并说明你享有的权利。\n")
.append(" 更多详情,敬请查阅")
.append(getPrivacyLink(context, PRIVACY_URL))
.append("全文。");
return stringBuilder;
}
/**
* @param context 隐私政策的链接
* @return
*/
private static SpannableString getPrivacyLink(Context context, String privacyUrl) {
String privacyName = String.format(ResUtils.getString(R.string.lab_privacy_name), ResUtils.getString(R.string.app_name));
SpannableString spannableString = new SpannableString(privacyName);
spannableString.setSpan(new ClickableSpan() {
@Override
public void onClick(@NonNull View widget) {
goWeb(context, privacyUrl);
}
}, 0, privacyName.length(), Spanned.SPAN_MARK_MARK);
return spannableString;
}
/**
* 请求浏览器
*
* @param url
*/
public static void goWeb(Context context, final String url) {
Intent intent = new Intent(context, AgentWebActivity.class);
intent.putExtra(AgentWebFragment.KEY_URL, url);
context.startActivity(intent);
}
/**
* 是否是深色的颜色
*
* @param color
* @return
*/
public static boolean isColorDark(@ColorInt int color) {
double darkness =
1
- (0.299 * Color.red(color) + 0.587 * Color.green(color) + 0.114 * Color.blue(color))
/ 255;
return darkness >= 0.382;
}
}

View File

@@ -0,0 +1,161 @@
/*
* Copyright (C) 2021 xuexiangjys(xuexiangjys@163.com)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
package com.kerwin.wumei.utils;
import androidx.annotation.MainThread;
import androidx.annotation.NonNull;
import androidx.annotation.StringRes;
import com.xuexiang.xui.XUI;
import com.xuexiang.xui.widget.toast.XToast;
/**
* xtoast 工具类
*
* @author xuexiang
* @since 2019-06-30 19:04
*/
public final class XToastUtils {
private XToastUtils() {
throw new UnsupportedOperationException("u can't instantiate me...");
}
static {
XToast.Config.get()
.setAlpha(200)
.allowQueue(false);
}
//======普通土司=======//
@MainThread
public static void toast(@NonNull CharSequence message) {
XToast.normal(XUI.getContext(), message).show();
}
@MainThread
public static void toast(@StringRes int message) {
XToast.normal(XUI.getContext(), message).show();
}
@MainThread
public static void toast(@NonNull CharSequence message, int duration) {
XToast.normal(XUI.getContext(), message, duration).show();
}
@MainThread
public static void toast(@StringRes int message, int duration) {
XToast.normal(XUI.getContext(), message, duration).show();
}
//======错误【红色】=======//
@MainThread
public static void error(@NonNull Throwable throwable) {
XToast.error(XUI.getContext(), throwable.getMessage()).show();
}
@MainThread
public static void error(@NonNull CharSequence message) {
XToast.error(XUI.getContext(), message).show();
}
@MainThread
public static void error(@StringRes int message) {
XToast.error(XUI.getContext(), message).show();
}
@MainThread
public static void error(@NonNull CharSequence message, int duration) {
XToast.error(XUI.getContext(), message, duration).show();
}
@MainThread
public static void error(@StringRes int message, int duration) {
XToast.error(XUI.getContext(), message, duration).show();
}
//======成功【绿色】=======//
@MainThread
public static void success(@NonNull CharSequence message) {
XToast.success(XUI.getContext(), message).show();
}
@MainThread
public static void success(@StringRes int message) {
XToast.success(XUI.getContext(), message).show();
}
@MainThread
public static void success(@NonNull CharSequence message, int duration) {
XToast.success(XUI.getContext(), message, duration).show();
}
@MainThread
public static void success(@StringRes int message, int duration) {
XToast.success(XUI.getContext(), message, duration).show();
}
//======信息【蓝色】=======//
@MainThread
public static void info(@NonNull CharSequence message) {
XToast.info(XUI.getContext(), message).show();
}
@MainThread
public static void info(@StringRes int message) {
XToast.info(XUI.getContext(), message).show();
}
@MainThread
public static void info(@NonNull CharSequence message, int duration) {
XToast.info(XUI.getContext(), message, duration).show();
}
@MainThread
public static void info(@StringRes int message, int duration) {
XToast.info(XUI.getContext(), message, duration).show();
}
//=======警告【黄色】======//
@MainThread
public static void warning(@NonNull CharSequence message) {
XToast.warning(XUI.getContext(), message).show();
}
@MainThread
public static void warning(@StringRes int message) {
XToast.warning(XUI.getContext(), message).show();
}
@MainThread
public static void warning(@NonNull CharSequence message, int duration) {
XToast.warning(XUI.getContext(), message, duration).show();
}
@MainThread
public static void warning(@StringRes int message, int duration) {
XToast.warning(XUI.getContext(), message, duration).show();
}
}

View File

@@ -0,0 +1,79 @@
/*
* Copyright (C) 2021 xuexiangjys(xuexiangjys@163.com)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
package com.kerwin.wumei.utils.sdkinit;
import com.github.anrwatchdog.ANRWatchDog;
import com.xuexiang.xutil.common.logger.Logger;
/**
* ANR看门狗监听器初始化
*
* @author xuexiang
* @since 2020-02-18 15:08
*/
public final class ANRWatchDogInit {
private static final String TAG = "ANRWatchDog";
private ANRWatchDogInit() {
throw new UnsupportedOperationException("u can't instantiate me...");
}
/**
* ANR看门狗
*/
private static ANRWatchDog sANRWatchDog;
/**
* ANR监听触发的时间
*/
private static final int ANR_DURATION = 4000;
/**
* ANR静默处理【就是不处理直接记录一下日志】
*/
private final static ANRWatchDog.ANRListener SILENT_LISTENER = error -> Logger.eTag(TAG, error);
/**
* ANR自定义处理【可以是记录日志用于上传】
*/
private final static ANRWatchDog.ANRListener CUSTOM_LISTENER = error -> {
Logger.eTag(TAG, "Detected Application Not Responding!", error);
//这里进行ANR的捕获后的操作
throw error;
};
public static void init() {
//这里设置监听的间隔为2秒
sANRWatchDog = new ANRWatchDog(2000);
sANRWatchDog.setANRInterceptor(duration -> {
long ret = ANR_DURATION - duration;
if (ret > 0) {
Logger.wTag(TAG, "Intercepted ANR that is too short (" + duration + " ms), postponing for " + ret + " ms.");
}
//当返回是0或者负数时就会触发ANR监听回调
return ret;
}).setANRListener(SILENT_LISTENER).start();
}
public static ANRWatchDog getANRWatchDog() {
return sANRWatchDog;
}
}

View File

@@ -0,0 +1,69 @@
/*
* Copyright (C) 2021 xuexiangjys(xuexiangjys@163.com)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
package com.kerwin.wumei.utils.sdkinit;
import android.app.Application;
import android.content.Context;
import com.kerwin.wumei.BuildConfig;
import com.kerwin.wumei.MyApp;
import com.meituan.android.walle.WalleChannelReader;
import com.umeng.analytics.MobclickAgent;
import com.umeng.commonsdk.UMConfigure;
/**
* UMeng 统计 SDK初始化
*
* @author xuexiang
* @since 2019-06-18 15:49
*/
public final class UMengInit {
private UMengInit() {
throw new UnsupportedOperationException("u can't instantiate me...");
}
private static String DEFAULT_CHANNEL_ID = "github";
/**
* 初始化UmengSDK
*/
public static void init(Application application) {
//设置LOG开关默认为false
UMConfigure.setLogEnabled(MyApp.isDebug());
//初始化组件化基础库, 注意: 即使您已经在AndroidManifest.xml中配置过appkey和channel值也需要在App代码中调用初始化接口如需要使用AndroidManifest.xml中配置好的appkey和channel值UMConfigure.init调用中appkey和channel参数请置为null
//第二个参数是appkey最后一个参数是pushSecret
//这里BuildConfig.APP_ID_UMENG是根据local.properties中定义的APP_ID_UMENG生成的只是运行看效果的话可以不初始化该SDK
UMConfigure.init(application, BuildConfig.APP_ID_UMENG, getChannel(application), UMConfigure.DEVICE_TYPE_PHONE, "");
//统计SDK是否支持采集在子进程中打点的自定义事件默认不支持
//支持多进程打点
UMConfigure.setProcessEvent(true);
MobclickAgent.setPageCollectionMode(MobclickAgent.PageMode.AUTO);
}
/**
* 获取渠道信息
*
* @param context
* @return
*/
public static String getChannel(final Context context) {
return WalleChannelReader.getChannel(context, DEFAULT_CHANNEL_ID);
}
}

View File

@@ -0,0 +1,137 @@
/*
* Copyright (C) 2021 xuexiangjys(xuexiangjys@163.com)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
package com.kerwin.wumei.utils.sdkinit;
import android.app.Application;
import com.kerwin.wumei.MyApp;
import com.kerwin.wumei.core.BaseActivity;
import com.kerwin.wumei.utils.TokenUtils;
import com.kerwin.wumei.utils.XToastUtils;
import com.xuexiang.xaop.XAOP;
import com.xuexiang.xhttp2.XHttpSDK;
import com.xuexiang.xpage.PageConfig;
import com.xuexiang.xrouter.launcher.XRouter;
import com.xuexiang.xui.XUI;
import com.xuexiang.xutil.XUtil;
import com.xuexiang.xutil.common.StringUtils;
/**
* X系列基础库初始化
*
* @author xuexiang
* @since 2019-06-30 23:54
*/
public final class XBasicLibInit {
private XBasicLibInit() {
throw new UnsupportedOperationException("u can't instantiate me...");
}
/**
* 初始化基础库SDK
*/
public static void init(Application application) {
//工具类
initXUtil(application);
//网络请求框架
initXHttp2(application);
//页面框架
initXPage(application);
//切片框架
initXAOP(application);
//UI框架
initXUI(application);
//路由框架
initRouter(application);
}
/**
* 初始化XUtil工具类
*/
private static void initXUtil(Application application) {
XUtil.init(application);
XUtil.debug(MyApp.isDebug());
TokenUtils.init(application);
}
/**
* 初始化XHttp2
*/
private static void initXHttp2(Application application) {
//初始化网络请求框架,必须首先执行
XHttpSDK.init(application);
//需要调试的时候执行
if (MyApp.isDebug()) {
XHttpSDK.debug();
}
// XHttpSDK.debug(new CustomLoggingInterceptor()); //设置自定义的日志打印拦截器
//设置网络请求的全局基础地址
XHttpSDK.setBaseUrl("https://gitee.com/");
// //设置动态参数添加拦截器
// XHttpSDK.addInterceptor(new CustomDynamicInterceptor());
// //请求失效校验拦截器
// XHttpSDK.addInterceptor(new CustomExpiredInterceptor());
}
/**
* 初始化XPage页面框架
*/
private static void initXPage(Application application) {
PageConfig.getInstance()
.debug(MyApp.isDebug() ? "PageLog" : null)
.setContainActivityClazz(BaseActivity.class)
.init(application);
}
/**
* 初始化XAOP
*/
private static void initXAOP(Application application) {
XAOP.init(application);
XAOP.debug(MyApp.isDebug());
//设置动态申请权限切片 申请权限被拒绝的事件响应监听
XAOP.setOnPermissionDeniedListener(permissionsDenied -> XToastUtils.error("权限申请被拒绝:" + StringUtils.listToString(permissionsDenied, ",")));
}
/**
* 初始化XUI框架
*/
private static void initXUI(Application application) {
XUI.init(application);
XUI.debug(MyApp.isDebug());
}
/**
* 初始化路由框架
*/
private static void initRouter(Application application) {
// 这两行必须写在init之前否则这些配置在init过程中将无效
if (MyApp.isDebug()) {
XRouter.openLog(); // 打印日志
XRouter.openDebug(); // 开启调试模式(如果在InstantRun模式下运行必须开启调试模式线上版本需要关闭,否则有安全风险)
}
XRouter.init(application);
}
}

View File

@@ -0,0 +1,73 @@
/*
* Copyright (C) 2021 xuexiangjys(xuexiangjys@163.com)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
package com.kerwin.wumei.utils.sdkinit;
import android.app.Application;
import android.content.Context;
import com.kerwin.wumei.MyApp;
import com.kerwin.wumei.utils.update.CustomUpdateDownloader;
import com.kerwin.wumei.utils.update.CustomUpdateFailureListener;
import com.kerwin.wumei.utils.update.XHttpUpdateHttpServiceImpl;
import com.xuexiang.xupdate.XUpdate;
import com.xuexiang.xupdate.utils.UpdateUtils;
/**
* XUpdate 版本更新 SDK 初始化
*
* @author xuexiang
* @since 2019-06-18 15:51
*/
public final class XUpdateInit {
private XUpdateInit() {
throw new UnsupportedOperationException("u can't instantiate me...");
}
/**
* 应用版本更新的检查地址
*/
private static final String KEY_UPDATE_URL = "";
public static void init(Application application) {
XUpdate.get()
.debug(MyApp.isDebug())
//默认设置只在wifi下检查版本更新
.isWifiOnly(false)
//默认设置使用get请求检查版本
.isGet(true)
//默认设置非自动模式,可根据具体使用配置
.isAutoMode(false)
//设置默认公共请求参数
.param("versionCode", UpdateUtils.getVersionCode(application))
.param("appKey", application.getPackageName())
//这个必须设置!实现网络请求功能。
.setIUpdateHttpService(new XHttpUpdateHttpServiceImpl())
.setIUpdateDownLoader(new CustomUpdateDownloader())
//这个必须初始化
.init(application);
}
/**
* 进行版本更新检查
*/
public static void checkUpdate(Context context, boolean needErrorTip) {
XUpdate.newBuild(context).updateUrl(KEY_UPDATE_URL).update();
XUpdate.get().setOnUpdateFailureListener(new CustomUpdateFailureListener(needErrorTip));
}
}

View File

@@ -0,0 +1,66 @@
/*
* Copyright (C) 2021 xuexiangjys(xuexiangjys@163.com)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
package com.kerwin.wumei.utils.service;
import android.content.Context;
import com.xuexiang.xrouter.annotation.Router;
import com.xuexiang.xrouter.facade.service.SerializationService;
import com.xuexiang.xutil.net.JsonUtil;
import java.lang.reflect.Type;
/**
* @author XUE
* @since 2019/3/27 16:39
*/
@Router(path = "/service/json")
public class JsonSerializationService implements SerializationService {
/**
* 对象序列化为json
*
* @param instance obj
* @return json string
*/
@Override
public String object2Json(Object instance) {
return JsonUtil.toJson(instance);
}
/**
* json反序列化为对象
*
* @param input json string
* @param clazz object type
* @return instance of object
*/
@Override
public <T> T parseObject(String input, Type clazz) {
return JsonUtil.fromJson(input, clazz);
}
/**
* 进程初始化的方法
*
* @param context 上下文
*/
@Override
public void init(Context context) {
}
}

View File

@@ -0,0 +1,54 @@
/*
* Copyright (C) 2021 xuexiangjys(xuexiangjys@163.com)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
package com.kerwin.wumei.utils.update;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.xuexiang.xupdate.entity.UpdateEntity;
import com.xuexiang.xupdate.proxy.impl.DefaultUpdateDownloader;
import com.xuexiang.xupdate.service.OnFileDownloadListener;
import com.xuexiang.xutil.app.ActivityUtils;
/**
* 重写DefaultUpdateDownloader在取消下载时弹出提示
*
* @author xuexiang
* @since 2019-06-14 23:47
*/
public class CustomUpdateDownloader extends DefaultUpdateDownloader {
private boolean mIsStartDownload;
@Override
public void startDownload(@NonNull UpdateEntity updateEntity, @Nullable OnFileDownloadListener downloadListener) {
super.startDownload(updateEntity, downloadListener);
mIsStartDownload = true;
}
@Override
public void cancelDownload() {
super.cancelDownload();
if (mIsStartDownload) {
mIsStartDownload = false;
ActivityUtils.startActivity(UpdateTipDialog.class);
}
}
}

View File

@@ -0,0 +1,59 @@
/*
* Copyright (C) 2021 xuexiangjys(xuexiangjys@163.com)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
package com.kerwin.wumei.utils.update;
import com.kerwin.wumei.utils.XToastUtils;
import com.xuexiang.xupdate.entity.UpdateError;
import com.xuexiang.xupdate.listener.OnUpdateFailureListener;
/**
* 自定义版本更新提示
*
* @author xuexiang
* @since 2019/4/15 上午12:01
*/
public class CustomUpdateFailureListener implements OnUpdateFailureListener {
/**
* 是否需要错误提示
*/
private boolean mNeedErrorTip;
public CustomUpdateFailureListener() {
this(true);
}
public CustomUpdateFailureListener(boolean needErrorTip) {
mNeedErrorTip = needErrorTip;
}
/**
* 更新失败
*
* @param error 错误
*/
@Override
public void onFailure(UpdateError error) {
if (mNeedErrorTip) {
XToastUtils.error(error);
}
if (error.getCode() == UpdateError.ERROR.DOWNLOAD_FAILED) {
UpdateTipDialog.show("Github被墙无法下载是否考虑切换蒲公英下载");
}
}
}

View File

@@ -0,0 +1,37 @@
/*
* Copyright (C) 2021 xuexiangjys(xuexiangjys@163.com)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
package com.kerwin.wumei.utils.update;
import com.xuexiang.xupdate.entity.UpdateEntity;
import com.xuexiang.xupdate.proxy.impl.AbstractUpdateParser;
/**
* 版本更新信息自定义json解析器
*
* @author xuexiang
* @since 2020-02-18 13:01
*/
public class CustomUpdateParser extends AbstractUpdateParser {
@Override
public UpdateEntity parseJson(String json) throws Exception {
// TODO: 2020-02-18 这里填写你需要自定义的json格式
return null;
}
}

View File

@@ -0,0 +1,75 @@
/*
* Copyright (C) 2021 xuexiangjys(xuexiangjys@163.com)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
package com.kerwin.wumei.utils.update;
import android.content.DialogInterface;
import android.content.Intent;
import android.os.Bundle;
import android.text.TextUtils;
import androidx.annotation.Nullable;
import androidx.appcompat.app.AppCompatActivity;
import com.xuexiang.xui.widget.dialog.DialogLoader;
import com.xuexiang.xupdate.XUpdate;
/**
* 版本更新提示弹窗
*
* @author xuexiang
* @since 2019-06-15 00:06
*/
public class UpdateTipDialog extends AppCompatActivity implements DialogInterface.OnDismissListener {
public static final String KEY_CONTENT = "com.xuexiang.templateproject.utils.update.KEY_CONTENT";
/**
* 显示版本更新重试提示弹窗
*
* @param content
*/
public static void show(String content) {
Intent intent = new Intent(XUpdate.getContext(), UpdateTipDialog.class);
intent.putExtra(KEY_CONTENT, content);
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
XUpdate.getContext().startActivity(intent);
}
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
String content = getIntent().getStringExtra(KEY_CONTENT);
if (TextUtils.isEmpty(content)) {
content = "Github下载速度太慢了是否考虑切换蒲公英下载";
}
DialogLoader.getInstance().showConfirmDialog(this, content, "", (dialog, which) -> {
dialog.dismiss();
// Utils.goWeb(UpdateTipDialog.this, "这里填写你应用下载页面的链接");
}, "")
.setOnDismissListener(this);
}
@Override
public void onDismiss(DialogInterface dialog) {
finish();
}
}

View File

@@ -0,0 +1,111 @@
/*
* Copyright (C) 2021 xuexiangjys(xuexiangjys@163.com)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
package com.kerwin.wumei.utils.update;
import androidx.annotation.NonNull;
import com.kerwin.wumei.utils.XToastUtils;
import com.xuexiang.xhttp2.XHttp;
import com.xuexiang.xhttp2.XHttpSDK;
import com.xuexiang.xhttp2.callback.DownloadProgressCallBack;
import com.xuexiang.xhttp2.callback.SimpleCallBack;
import com.xuexiang.xhttp2.exception.ApiException;
import com.xuexiang.xupdate.proxy.IUpdateHttpService;
import com.xuexiang.xutil.file.FileUtils;
import com.xuexiang.xutil.net.JsonUtil;
import java.util.Map;
/**
* XHttp2实现的请求更新
*
* @author xuexiang
* @since 2018/8/12 上午11:46
*/
public class XHttpUpdateHttpServiceImpl implements IUpdateHttpService {
@Override
public void asyncGet(@NonNull String url, @NonNull Map<String, Object> params, @NonNull final IUpdateHttpService.Callback callBack) {
XHttp.get(url)
.params(params)
.keepJson(true)
.execute(new SimpleCallBack<String>() {
@Override
public void onSuccess(String response) throws Throwable {
callBack.onSuccess(response);
}
@Override
public void onError(ApiException e) {
callBack.onError(e);
}
});
}
@Override
public void asyncPost(@NonNull String url, @NonNull Map<String, Object> params, @NonNull final IUpdateHttpService.Callback callBack) {
XHttp.post(url)
.upJson(JsonUtil.toJson(params))
.keepJson(true)
.execute(new SimpleCallBack<String>() {
@Override
public void onSuccess(String response) throws Throwable {
callBack.onSuccess(response);
}
@Override
public void onError(ApiException e) {
callBack.onError(e);
}
});
}
@Override
public void download(@NonNull String url, @NonNull String path, @NonNull String fileName, @NonNull final IUpdateHttpService.DownloadCallback callback) {
XHttpSDK.addRequest(url, XHttp.downLoad(url)
.savePath(path)
.saveName(fileName)
.isUseBaseUrl(false)
.execute(new DownloadProgressCallBack<String>() {
@Override
public void onStart() {
callback.onStart();
}
@Override
public void onError(ApiException e) {
callback.onError(e);
}
@Override
public void update(long downLoadSize, long totalSize, boolean done) {
callback.onProgress(downLoadSize / (float) totalSize, totalSize);
}
@Override
public void onComplete(String path) {
callback.onSuccess(FileUtils.getFileByPath(path));
}
}));
}
@Override
public void cancelDownload(@NonNull String url) {
XToastUtils.info("已取消更新");
XHttpSDK.cancelRequest(url);
}
}

View File

@@ -0,0 +1,208 @@
/*
* Copyright (C) 2021 xuexiangjys(xuexiangjys@163.com)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
package com.kerwin.wumei.widget;
import android.content.Context;
import android.view.View;
import android.widget.CompoundButton;
import android.widget.ImageView;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.appcompat.widget.AppCompatCheckBox;
import com.kerwin.wumei.core.http.api.ApiService;
import com.kerwin.wumei.core.http.callback.NoTipCallBack;
import com.kerwin.wumei.core.http.entity.TipInfo;
import com.kerwin.wumei.utils.MMKVUtils;
import com.xuexiang.constant.TimeConstants;
import com.kerwin.wumei.R;
import com.xuexiang.xaop.annotation.SingleClick;
import com.xuexiang.xhttp2.XHttp;
import com.xuexiang.xhttp2.cache.model.CacheMode;
import com.xuexiang.xhttp2.request.CustomRequest;
import com.xuexiang.xui.widget.dialog.BaseDialog;
import com.xuexiang.xutil.app.AppUtils;
import com.zzhoujay.richtext.RichText;
import java.util.List;
/**
* 小贴士弹窗
*
* @author xuexiang
* @since 2019-08-22 17:02
*/
public class GuideTipsDialog extends BaseDialog implements View.OnClickListener, CompoundButton.OnCheckedChangeListener {
private static final String KEY_IS_IGNORE_TIPS = "com.xuexiang.templateproject.widget.key_is_ignore_tips_";
private List<TipInfo> mTips;
private int mIndex = -1;
private TextView mTvPrevious;
private TextView mTvNext;
private TextView mTvTitle;
private TextView mTvContent;
/**
* 显示提示
*
* @param context 上下文
*/
public static void showTips(final Context context) {
if (!isIgnoreTips()) {
CustomRequest request = XHttp.custom().cacheMode(CacheMode.FIRST_CACHE).cacheTime(TimeConstants.DAY).cacheKey("getTips");
request.apiCall(request.create(ApiService.IGetService.class).getTips(), new NoTipCallBack<List<TipInfo>>() {
@Override
public void onSuccess(List<TipInfo> response) throws Throwable {
if (response != null && response.size() > 0) {
new GuideTipsDialog(context, response).show();
}
}
});
}
}
public GuideTipsDialog(Context context, @NonNull List<TipInfo> tips) {
super(context, R.layout.dialog_guide_tips);
initViews();
updateTips(tips);
}
/**
* 初始化弹窗
*/
private void initViews() {
mTvTitle = findViewById(R.id.device_item_title);
mTvContent = findViewById(R.id.tv_content);
AppCompatCheckBox cbIgnore = findViewById(R.id.cb_ignore);
ImageView ivClose = findViewById(R.id.iv_close);
mTvPrevious = findViewById(R.id.tv_previous);
mTvNext = findViewById(R.id.tv_next);
if (cbIgnore != null) {
cbIgnore.setOnCheckedChangeListener(this);
}
if (ivClose != null) {
ivClose.setOnClickListener(this);
}
mTvPrevious.setOnClickListener(this);
mTvNext.setOnClickListener(this);
mTvPrevious.setEnabled(false);
mTvNext.setEnabled(true);
setCancelable(false);
setCanceledOnTouchOutside(true);
}
/**
* 更新提示信息
*
* @param tips 提示信息
*/
private void updateTips(List<TipInfo> tips) {
mTips = tips;
if (mTips != null && mTips.size() > 0 && mTvContent != null) {
mIndex = 0;
showRichText(mTips.get(mIndex));
}
}
/**
* 切换提示信息
*
* @param index 索引
*/
private void switchTipInfo(int index) {
if (mTips != null && mTips.size() > 0 && mTvContent != null) {
if (index >= 0 && index <= mTips.size() - 1) {
showRichText(mTips.get(index));
if (index == 0) {
mTvPrevious.setEnabled(false);
mTvNext.setEnabled(true);
} else if (index == mTips.size() - 1) {
mTvPrevious.setEnabled(true);
mTvNext.setEnabled(false);
} else {
mTvPrevious.setEnabled(true);
mTvNext.setEnabled(true);
}
}
}
}
/**
* 显示富文本
*
* @param tipInfo 提示信息
*/
private void showRichText(TipInfo tipInfo) {
mTvTitle.setText(tipInfo.getTitle());
RichText.fromHtml(tipInfo.getContent())
.bind(this)
.into(mTvContent);
}
@SingleClick(300)
@Override
public void onClick(View view) {
switch (view.getId()) {
case R.id.iv_close:
dismiss();
break;
case R.id.tv_previous:
if (mIndex > 0) {
mIndex--;
switchTipInfo(mIndex);
}
break;
case R.id.tv_next:
if (mIndex < mTips.size() - 1) {
mIndex++;
switchTipInfo(mIndex);
}
break;
default:
break;
}
}
@Override
public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
setIsIgnoreTips(isChecked);
}
@Override
public void onDetachedFromWindow() {
RichText.clear(this);
super.onDetachedFromWindow();
}
public static boolean setIsIgnoreTips(boolean isIgnore) {
return MMKVUtils.put(KEY_IS_IGNORE_TIPS + AppUtils.getAppVersionCode(), isIgnore);
}
public static boolean isIgnoreTips() {
return MMKVUtils.getBoolean(KEY_IS_IGNORE_TIPS + AppUtils.getAppVersionCode(), false);
}
}

View File

@@ -0,0 +1,124 @@
/*
* Copyright (C) 2021 xuexiangjys(xuexiangjys@163.com)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
package com.kerwin.wumei.widget;
import android.content.Context;
import android.util.AttributeSet;
import android.view.View;
import android.widget.FrameLayout;
import android.widget.ProgressBar;
import androidx.annotation.NonNull;
import com.scwang.smartrefresh.layout.api.RefreshFooter;
import com.scwang.smartrefresh.layout.api.RefreshKernel;
import com.scwang.smartrefresh.layout.api.RefreshLayout;
import com.scwang.smartrefresh.layout.constant.RefreshState;
import com.scwang.smartrefresh.layout.constant.SpinnerStyle;
import com.scwang.smartrefresh.layout.util.DensityUtil;
/**
* Material风格的上拉加载
*
* @author xuexiang
* @since 2019-08-03 11:14
*/
public class MaterialFooter extends ProgressBar implements RefreshFooter {
public MaterialFooter(Context context) {
this(context, null);
}
public MaterialFooter(Context context, AttributeSet attrs) {
super(context, attrs);
initView();
}
private void initView() {
setVisibility(GONE);
FrameLayout.LayoutParams params = new FrameLayout.LayoutParams(FrameLayout.LayoutParams.MATCH_PARENT, FrameLayout.LayoutParams.WRAP_CONTENT);
setPadding(0, DensityUtil.dp2px(10), 0, DensityUtil.dp2px(10));
setLayoutParams(params);
}
@Override
public boolean setNoMoreData(boolean noMoreData) {
return false;
}
@NonNull
@Override
public View getView() {
return this;
}
@NonNull
@Override
public SpinnerStyle getSpinnerStyle() {
//指定为平移不能null
return SpinnerStyle.Translate;
}
@Override
public void onStartAnimator(@NonNull RefreshLayout refreshLayout, int height, int maxDragHeight) {
setVisibility(VISIBLE);
}
@Override
public int onFinish(@NonNull RefreshLayout refreshLayout, boolean success) {
setVisibility(GONE);
return 100;
}
@Override
public void onStateChanged(@NonNull RefreshLayout refreshLayout, @NonNull RefreshState oldState, @NonNull RefreshState newState) {
}
@Override
public void setPrimaryColors(int... colors) {
}
@Override
public void onInitialized(@NonNull RefreshKernel kernel, int height, int maxDragHeight) {
}
@Override
public void onMoving(boolean isDragging, float percent, int offset, int height, int maxDragHeight) {
}
@Override
public void onReleased(@NonNull RefreshLayout refreshLayout, int height, int maxDragHeight) {
}
@Override
public void onHorizontalDrag(float percentX, int offsetX, int offsetMax) {
}
@Override
public boolean isSupportHorizontalDrag() {
return false;
}
}

View File

@@ -0,0 +1,24 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
~ Copyright (C) 2019 xuexiangjys(xuexiangjys@163.com)
~
~ Licensed under the Apache License, Version 2.0 (the "License");
~ you may not use this file except in compliance with the License.
~ You may obtain a copy of the License at
~
~ http://www.apache.org/licenses/LICENSE-2.0
~
~ Unless required by applicable law or agreed to in writing, software
~ distributed under the License is distributed on an "AS IS" BASIS,
~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
~ See the License for the specific language governing permissions and
~ limitations under the License.
~
-->
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:state_pressed="true" android:color="?attr/colorAccent" />
<item android:state_selected="true" android:color="?attr/colorAccent" />
<item android:state_enabled="false" android:color="@color/xui_btn_disable_color" />
<item android:color="?attr/colorAccent" />
</selector>

View File

@@ -0,0 +1,22 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
~ Copyright (C) 2018 xuexiangjys(xuexiangjys@163.com)
~
~ Licensed under the Apache License, Version 2.0 (the "License");
~ you may not use this file except in compliance with the License.
~ You may obtain a copy of the License at
~
~ http://www.apache.org/licenses/LICENSE-2.0
~
~ Unless required by applicable law or agreed to in writing, software
~ distributed under the License is distributed on an "AS IS" BASIS,
~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
~ See the License for the specific language governing permissions and
~ limitations under the License.
-->
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:state_pressed="true" android:color="?attr/colorAccent" />
<item android:state_selected="true" android:color="?attr/colorAccent" />
<item android:color="@color/xui_config_color_gray_6" />
</selector>

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 154 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 978 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 927 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

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