整理:Code Review

pull/286/head
pppscn 1 year ago
parent 6571775a0f
commit 9c6f404190

@ -291,3 +291,10 @@
-keep interface * implements com.xuexiang.xrouter.facade.template.IProvider
# 如果使用了 单类注入,即不定义接口实现 IProvider,需添加下面规则,保护实现
-keep class * implements com.xuexiang.xrouter.facade.template.IProvider
-dontwarn com.alipay.sdk.**
-dontwarn com.android.org.conscrypt.**
-dontwarn java.awt.image.**
-dontwarn javax.lang.model.**
-dontwarn javax.naming.**
-dontwarn javax.naming.directory.**

@ -8,6 +8,7 @@
tools:ignore="QueryAllPackagesPermission" />
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_BACKGROUND_LOCATION" />
<uses-permission android:name="android.permission.CHANGE_WIFI_STATE" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
@ -55,9 +56,6 @@
<uses-permission
android:name="android.permission.READ_LOGS"
tools:ignore="ProtectedPermissions" />
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_BACKGROUND_LOCATION" />
<application
android:name=".App"

@ -1,269 +1,269 @@
package com.idormy.sms.forwarder.adapter.base.delegate
import android.annotation.SuppressLint
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.annotation.LayoutRes
import androidx.recyclerview.widget.RecyclerView
import com.alibaba.android.vlayout.DelegateAdapter
/**
* 基础DelegateAdapter
*
* @author xuexiang
* @since 2020/3/20 12:17 AM
*/
@Suppress("unused")
abstract class XDelegateAdapter<T, V : RecyclerView.ViewHolder?> : DelegateAdapter.Adapter<V> {
/**
* 数据源
*/
private val mData: MutableList<T> = ArrayList()
/**
* @return 当前列表的选中项
*/
/**
* 当前点击的条目
*/
private var selectPosition = -1
constructor()
constructor(list: Collection<T>?) {
if (list != null) {
mData.addAll(list)
}
}
constructor(data: Array<T>?) {
if (data != null && data.isNotEmpty()) {
mData.addAll(listOf(*data))
}
}
/**
* 构建自定义的ViewHolder
*
* @param parent
* @param viewType
* @return
*/
protected abstract fun getViewHolder(parent: ViewGroup, viewType: Int): V
/**
* 绑定数据
*
* @param holder
* @param position 索引
* @param item 列表项
*/
protected abstract fun bindData(holder: V, position: Int, item: T)
/**
* 加载布局获取控件
*
* @param parent 父布局
* @param layoutId 布局ID
* @return
*/
protected fun inflateView(parent: ViewGroup, @LayoutRes layoutId: Int): View {
return LayoutInflater.from(parent.context).inflate(layoutId, parent, false)
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): V {
return getViewHolder(parent, viewType)
}
override fun onBindViewHolder(holder: V, position: Int) {
bindData(holder, position, mData[position])
}
/**
* 获取列表项
*
* @param position
* @return
*/
private fun getItem(position: Int): T? {
return if (checkPosition(position)) mData[position] else null
}
private fun checkPosition(position: Int): Boolean {
return position >= 0 && position <= mData.size - 1
}
val isEmpty: Boolean
get() = itemCount == 0
override fun getItemCount(): Int {
return mData.size
}
/**
* @return 数据源
*/
val data: List<T>
get() = mData
/**
* 给指定位置添加一项
*
* @param pos
* @param item
* @return
*/
fun add(pos: Int, item: T): XDelegateAdapter<*, *> {
mData.add(pos, item)
notifyItemInserted(pos)
return this
}
/**
* 在列表末端增加一项
*
* @param item
* @return
*/
fun add(item: T): XDelegateAdapter<*, *> {
mData.add(item)
notifyItemInserted(mData.size - 1)
return this
}
/**
* 删除列表中指定索引的数据
*
* @param pos
* @return
*/
fun delete(pos: Int): XDelegateAdapter<*, *> {
mData.removeAt(pos)
notifyItemRemoved(pos)
return this
}
/**
* 刷新列表中指定位置的数据
*
* @param pos
* @param item
* @return
*/
fun refresh(pos: Int, item: T): XDelegateAdapter<*, *> {
mData[pos] = item
notifyItemChanged(pos)
return this
}
/**
* 刷新列表数据
*
* @param collection
* @return
*/
@SuppressLint("NotifyDataSetChanged")
open fun refresh(collection: Collection<T>?): XDelegateAdapter<*, *> {
if (collection != null) {
mData.clear()
mData.addAll(collection)
selectPosition = -1
notifyDataSetChanged()
}
return this
}
/**
* 刷新列表数据
*
* @param array
* @return
*/
@SuppressLint("NotifyDataSetChanged")
fun refresh(array: Array<T>?): XDelegateAdapter<*, *> {
if (array != null && array.isNotEmpty()) {
mData.clear()
mData.addAll(listOf(*array))
selectPosition = -1
notifyDataSetChanged()
}
return this
}
/**
* 加载更多
*
* @param collection
* @return
*/
@SuppressLint("NotifyDataSetChanged")
fun loadMore(collection: Collection<T>?): XDelegateAdapter<*, *> {
if (collection != null) {
mData.addAll(collection)
notifyDataSetChanged()
}
return this
}
/**
* 加载更多
*
* @param array
* @return
*/
@SuppressLint("NotifyDataSetChanged")
fun loadMore(array: Array<T>?): XDelegateAdapter<*, *> {
if (array != null && array.isNotEmpty()) {
mData.addAll(listOf(*array))
notifyDataSetChanged()
}
return this
}
/**
* 添加一个
*
* @param item
* @return
*/
@SuppressLint("NotifyDataSetChanged")
fun load(item: T?): XDelegateAdapter<*, *> {
if (item != null) {
mData.add(item)
notifyDataSetChanged()
}
return this
}
/**
* 设置当前列表的选中项
*
* @param selectPosition
* @return
*/
@SuppressLint("NotifyDataSetChanged")
fun setSelectPosition(selectPosition: Int): XDelegateAdapter<*, *> {
this.selectPosition = selectPosition
notifyDataSetChanged()
return this
}
/**
* 获取当前列表选中项
*
* @return 当前列表选中项
*/
val selectItem: T?
get() = getItem(selectPosition)
/**
* 清除数据
*/
@SuppressLint("NotifyDataSetChanged")
fun clear() {
if (!isEmpty) {
mData.clear()
selectPosition = -1
notifyDataSetChanged()
}
}
package com.idormy.sms.forwarder.adapter.base.delegate
import android.annotation.SuppressLint
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.annotation.LayoutRes
import androidx.recyclerview.widget.RecyclerView
import com.alibaba.android.vlayout.DelegateAdapter
/**
* 基础DelegateAdapter
*
* @author xuexiang
* @since 2020/3/20 12:17 AM
*/
@Suppress("unused", "WRONG_TYPE_PARAMETER_NULLABILITY_FOR_JAVA_OVERRIDE")
abstract class XDelegateAdapter<T, V : RecyclerView.ViewHolder?> : DelegateAdapter.Adapter<V> {
/**
* 数据源
*/
private val mData: MutableList<T> = ArrayList()
/**
* @return 当前列表的选中项
*/
/**
* 当前点击的条目
*/
private var selectPosition = -1
constructor()
constructor(list: Collection<T>?) {
if (list != null) {
mData.addAll(list)
}
}
constructor(data: Array<T>?) {
if (data != null && data.isNotEmpty()) {
mData.addAll(listOf(*data))
}
}
/**
* 构建自定义的ViewHolder
*
* @param parent
* @param viewType
* @return
*/
protected abstract fun getViewHolder(parent: ViewGroup, viewType: Int): V
/**
* 绑定数据
*
* @param holder
* @param position 索引
* @param item 列表项
*/
protected abstract fun bindData(holder: V, position: Int, item: T)
/**
* 加载布局获取控件
*
* @param parent 父布局
* @param layoutId 布局ID
* @return
*/
protected fun inflateView(parent: ViewGroup, @LayoutRes layoutId: Int): View {
return LayoutInflater.from(parent.context).inflate(layoutId, parent, false)
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): V {
return getViewHolder(parent, viewType)
}
override fun onBindViewHolder(holder: V, position: Int) {
bindData(holder, position, mData[position])
}
/**
* 获取列表项
*
* @param position
* @return
*/
private fun getItem(position: Int): T? {
return if (checkPosition(position)) mData[position] else null
}
private fun checkPosition(position: Int): Boolean {
return position >= 0 && position <= mData.size - 1
}
val isEmpty: Boolean
get() = itemCount == 0
override fun getItemCount(): Int {
return mData.size
}
/**
* @return 数据源
*/
val data: List<T>
get() = mData
/**
* 给指定位置添加一项
*
* @param pos
* @param item
* @return
*/
fun add(pos: Int, item: T): XDelegateAdapter<*, *> {
mData.add(pos, item)
notifyItemInserted(pos)
return this
}
/**
* 在列表末端增加一项
*
* @param item
* @return
*/
fun add(item: T): XDelegateAdapter<*, *> {
mData.add(item)
notifyItemInserted(mData.size - 1)
return this
}
/**
* 删除列表中指定索引的数据
*
* @param pos
* @return
*/
fun delete(pos: Int): XDelegateAdapter<*, *> {
mData.removeAt(pos)
notifyItemRemoved(pos)
return this
}
/**
* 刷新列表中指定位置的数据
*
* @param pos
* @param item
* @return
*/
fun refresh(pos: Int, item: T): XDelegateAdapter<*, *> {
mData[pos] = item
notifyItemChanged(pos)
return this
}
/**
* 刷新列表数据
*
* @param collection
* @return
*/
@SuppressLint("NotifyDataSetChanged")
open fun refresh(collection: Collection<T>?): XDelegateAdapter<*, *> {
if (collection != null) {
mData.clear()
mData.addAll(collection)
selectPosition = -1
notifyDataSetChanged()
}
return this
}
/**
* 刷新列表数据
*
* @param array
* @return
*/
@SuppressLint("NotifyDataSetChanged")
fun refresh(array: Array<T>?): XDelegateAdapter<*, *> {
if (array != null && array.isNotEmpty()) {
mData.clear()
mData.addAll(listOf(*array))
selectPosition = -1
notifyDataSetChanged()
}
return this
}
/**
* 加载更多
*
* @param collection
* @return
*/
@SuppressLint("NotifyDataSetChanged")
fun loadMore(collection: Collection<T>?): XDelegateAdapter<*, *> {
if (collection != null) {
mData.addAll(collection)
notifyDataSetChanged()
}
return this
}
/**
* 加载更多
*
* @param array
* @return
*/
@SuppressLint("NotifyDataSetChanged")
fun loadMore(array: Array<T>?): XDelegateAdapter<*, *> {
if (array != null && array.isNotEmpty()) {
mData.addAll(listOf(*array))
notifyDataSetChanged()
}
return this
}
/**
* 添加一个
*
* @param item
* @return
*/
@SuppressLint("NotifyDataSetChanged")
fun load(item: T?): XDelegateAdapter<*, *> {
if (item != null) {
mData.add(item)
notifyDataSetChanged()
}
return this
}
/**
* 设置当前列表的选中项
*
* @param selectPosition
* @return
*/
@SuppressLint("NotifyDataSetChanged")
fun setSelectPosition(selectPosition: Int): XDelegateAdapter<*, *> {
this.selectPosition = selectPosition
notifyDataSetChanged()
return this
}
/**
* 获取当前列表选中项
*
* @return 当前列表选中项
*/
val selectItem: T?
get() = getItem(selectPosition)
/**
* 清除数据
*/
@SuppressLint("NotifyDataSetChanged")
fun clear() {
if (!isEmpty) {
mData.clear()
selectPosition = -1
notifyDataSetChanged()
}
}
}

@ -1,157 +1,157 @@
package com.idormy.sms.forwarder.core
import android.content.Context
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import androidx.viewbinding.ViewBinding
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 io.github.inflationx.viewpump.ViewPumpContextWrapper
/**
* 基础容器Activity
*
* @author XUE
* @since 2019/3/22 11:21
*/
@Suppress("MemberVisibilityCanBePrivate", "UNCHECKED_CAST")
open class BaseActivity<Binding : ViewBinding?> : XPageActivity() {
/**
* 获取Binding
*
* @return Binding
*/
/**
* ViewBinding
*/
var binding: Binding? = null
protected set
override fun attachBaseContext(newBase: Context) {
//注入字体
super.attachBaseContext(ViewPumpContextWrapper.wrap(newBase))
}
override fun getCustomRootView(): View? {
binding = viewBindingInflate(layoutInflater)
return if (binding != null) binding!!.root else null
}
override fun onCreate(savedInstanceState: Bundle?) {
initStatusBarStyle()
super.onCreate(savedInstanceState)
registerSlideBack()
}
/**
* 构建ViewBinding
*
* @param inflater inflater
* @return ViewBinding
*/
protected open fun viewBindingInflate(inflater: LayoutInflater?): Binding? {
return null
}
/**
* 初始化状态栏的样式
*/
protected open fun initStatusBarStyle() {}
/**
* 打开fragment
*
* @param clazz 页面类
* @param addToBackStack 是否添加到栈中
* @return 打开的fragment对象
*/
fun <T : XPageFragment?> openPage(clazz: Class<T>?, addToBackStack: Boolean): T {
val page = CoreSwitchBean(clazz)
.setAddToBackStack(addToBackStack)
return openPage(page) as T
}
/**
* 打开fragment
*
* @return 打开的fragment对象
*/
fun <T : XPageFragment?> openNewPage(clazz: Class<T>?): T {
val page = CoreSwitchBean(clazz)
.setNewActivity(true)
return openPage(page) as T
}
/**
* 切换fragment
*
* @param clazz 页面类
* @return 打开的fragment对象
*/
fun <T : XPageFragment?> switchPage(clazz: Class<T>?): T {
return openPage(clazz, false)
}
/**
* 序列化对象
*
* @param object
* @return
*/
fun serializeObject(`object`: Any?): String {
return XRouter.getInstance().navigation(SerializationService::class.java)
.object2Json(`object`)
}
override fun onRelease() {
unregisterSlideBack()
super.onRelease()
}
/**
* 注册侧滑回调
*/
protected fun registerSlideBack() {
if (isSupportSlideBack) {
SlideBack.with(this)
.haveScroll(true)
.edgeMode(if (ResUtils.isRtl()) SlideBack.EDGE_RIGHT else SlideBack.EDGE_LEFT)
.callBack { popPage() }
.register()
}
}
/**
* 注销侧滑回调
*/
protected fun unregisterSlideBack() {
if (isSupportSlideBack) {
SlideBack.unregister(this)
}
}
/**
* @return 是否支持侧滑返回
*/
protected open val isSupportSlideBack: Boolean
get() {
val page: CoreSwitchBean? = intent.getParcelableExtra(CoreSwitchBean.KEY_SWITCH_BEAN)
return page == null || page.bundle == null || page.bundle.getBoolean(
KEY_SUPPORT_SLIDE_BACK,
true
)
}
companion object {
/**
* 是否支持侧滑返回
*/
const val KEY_SUPPORT_SLIDE_BACK = "key_support_slide_back"
}
package com.idormy.sms.forwarder.core
import android.content.Context
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import androidx.viewbinding.ViewBinding
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 io.github.inflationx.viewpump.ViewPumpContextWrapper
/**
* 基础容器Activity
*
* @author XUE
* @since 2019/3/22 11:21
*/
@Suppress("MemberVisibilityCanBePrivate", "UNCHECKED_CAST", "DEPRECATION")
open class BaseActivity<Binding : ViewBinding?> : XPageActivity() {
/**
* 获取Binding
*
* @return Binding
*/
/**
* ViewBinding
*/
var binding: Binding? = null
protected set
override fun attachBaseContext(newBase: Context) {
//注入字体
super.attachBaseContext(ViewPumpContextWrapper.wrap(newBase))
}
override fun getCustomRootView(): View? {
binding = viewBindingInflate(layoutInflater)
return if (binding != null) binding!!.root else null
}
override fun onCreate(savedInstanceState: Bundle?) {
initStatusBarStyle()
super.onCreate(savedInstanceState)
registerSlideBack()
}
/**
* 构建ViewBinding
*
* @param inflater inflater
* @return ViewBinding
*/
protected open fun viewBindingInflate(inflater: LayoutInflater?): Binding? {
return null
}
/**
* 初始化状态栏的样式
*/
protected open fun initStatusBarStyle() {}
/**
* 打开fragment
*
* @param clazz 页面类
* @param addToBackStack 是否添加到栈中
* @return 打开的fragment对象
*/
fun <T : XPageFragment?> openPage(clazz: Class<T>?, addToBackStack: Boolean): T {
val page = CoreSwitchBean(clazz)
.setAddToBackStack(addToBackStack)
return openPage(page) as T
}
/**
* 打开fragment
*
* @return 打开的fragment对象
*/
fun <T : XPageFragment?> openNewPage(clazz: Class<T>?): T {
val page = CoreSwitchBean(clazz)
.setNewActivity(true)
return openPage(page) as T
}
/**
* 切换fragment
*
* @param clazz 页面类
* @return 打开的fragment对象
*/
fun <T : XPageFragment?> switchPage(clazz: Class<T>?): T {
return openPage(clazz, false)
}
/**
* 序列化对象
*
* @param object
* @return
*/
fun serializeObject(`object`: Any?): String {
return XRouter.getInstance().navigation(SerializationService::class.java)
.object2Json(`object`)
}
override fun onRelease() {
unregisterSlideBack()
super.onRelease()
}
/**
* 注册侧滑回调
*/
protected fun registerSlideBack() {
if (isSupportSlideBack) {
SlideBack.with(this)
.haveScroll(true)
.edgeMode(if (ResUtils.isRtl()) SlideBack.EDGE_RIGHT else SlideBack.EDGE_LEFT)
.callBack { popPage() }
.register()
}
}
/**
* 注销侧滑回调
*/
protected fun unregisterSlideBack() {
if (isSupportSlideBack) {
SlideBack.unregister(this)
}
}
/**
* @return 是否支持侧滑返回
*/
protected open val isSupportSlideBack: Boolean
get() {
val page: CoreSwitchBean? = intent.getParcelableExtra(CoreSwitchBean.KEY_SWITCH_BEAN)
return page == null || page.bundle == null || page.bundle.getBoolean(
KEY_SUPPORT_SLIDE_BACK,
true
)
}
companion object {
/**
* 是否支持侧滑返回
*/
const val KEY_SUPPORT_SLIDE_BACK = "key_support_slide_back"
}
}

@ -1,87 +1,87 @@
package com.idormy.sms.forwarder.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
/**
* 修改列表样式为主副标题显示
*
* @author xuexiang
* @since 2018/11/22 上午11:26
*/
@Suppress("unused")
abstract class BaseContainerFragment : XPageContainerListFragment() {
override fun initPage() {
initTitle()
initViews()
initListeners()
}
protected fun initTitle(): TitleBar {
return TitleUtils.addTitleBarDynamic(
rootView as ViewGroup,
pageTitle
) { popToBack() }
}
override fun initData() {
mSimpleData = initSimpleData(mSimpleData)
val data: MutableList<Map<String?, String?>?> = ArrayList()
for (content in mSimpleData) {
val item: MutableMap<String?, String?> = HashMap()
val index = content.indexOf("\n")
if (index > 0) {
item[SimpleListAdapter.KEY_TITLE] = content.subSequence(0, index).toString()
item[SimpleListAdapter.KEY_SUB_TITLE] =
content.subSequence(index + 1, content.length).toString()
} else {
item[SimpleListAdapter.KEY_TITLE] = content
item[SimpleListAdapter.KEY_SUB_TITLE] = ""
}
data.add(item)
}
listView.adapter = SimpleListAdapter(context, data)
initSimply()
}
override fun onItemClick(adapterView: AdapterView<*>?, view: View, position: Int, id: Long) {
onItemClick(view, position)
}
@SingleClick
private fun onItemClick(view: View, position: Int) {
onItemClick(position)
}
override fun onDestroyView() {
listView.onItemClickListener = null
super.onDestroyView()
}
override fun onConfigurationChanged(newConfig: Configuration) {
//屏幕旋转时刷新一下title
super.onConfigurationChanged(newConfig)
val root = rootView as ViewGroup
if (root.getChildAt(0) is TitleBar) {
root.removeViewAt(0)
initTitle()
}
}
override fun onResume() {
super.onResume()
MobclickAgent.onPageStart(pageName)
}
override fun onPause() {
super.onPause()
MobclickAgent.onPageEnd(pageName)
}
package com.idormy.sms.forwarder.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
/**
* 修改列表样式为主副标题显示
*
* @author xuexiang
* @since 2018/11/22 上午11:26
*/
@Suppress("unused", "UNUSED_PARAMETER")
abstract class BaseContainerFragment : XPageContainerListFragment() {
override fun initPage() {
initTitle()
initViews()
initListeners()
}
protected fun initTitle(): TitleBar {
return TitleUtils.addTitleBarDynamic(
rootView as ViewGroup,
pageTitle
) { popToBack() }
}
override fun initData() {
mSimpleData = initSimpleData(mSimpleData)
val data: MutableList<Map<String?, String?>?> = ArrayList()
for (content in mSimpleData) {
val item: MutableMap<String?, String?> = HashMap()
val index = content.indexOf("\n")
if (index > 0) {
item[SimpleListAdapter.KEY_TITLE] = content.subSequence(0, index).toString()
item[SimpleListAdapter.KEY_SUB_TITLE] =
content.subSequence(index + 1, content.length).toString()
} else {
item[SimpleListAdapter.KEY_TITLE] = content
item[SimpleListAdapter.KEY_SUB_TITLE] = ""
}
data.add(item)
}
listView.adapter = SimpleListAdapter(context, data)
initSimply()
}
override fun onItemClick(adapterView: AdapterView<*>?, view: View, position: Int, id: Long) {
onItemClick(view, position)
}
@SingleClick
private fun onItemClick(view: View, position: Int) {
onItemClick(position)
}
override fun onDestroyView() {
listView.onItemClickListener = null
super.onDestroyView()
}
override fun onConfigurationChanged(newConfig: Configuration) {
//屏幕旋转时刷新一下title
super.onConfigurationChanged(newConfig)
val root = rootView as ViewGroup
if (root.getChildAt(0) is TitleBar) {
root.removeViewAt(0)
initTitle()
}
}
override fun onResume() {
super.onResume()
MobclickAgent.onPageStart(pageName)
}
override fun onPause() {
super.onPause()
MobclickAgent.onPageEnd(pageName)
}
}

@ -1,52 +1,52 @@
package com.idormy.sms.forwarder.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
*/
@Suppress("unused")
class LollipopFixedWebView : WebView {
constructor(context: Context) : super(getFixedContext(context))
constructor(context: Context, attrs: AttributeSet?) : super(getFixedContext(context), attrs)
constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int) : super(
getFixedContext(context), attrs, defStyleAttr
)
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
constructor(
context: Context,
attrs: AttributeSet?,
defStyleAttr: Int,
defStyleRes: Int,
) : super(
getFixedContext(context), attrs, defStyleAttr, defStyleRes
)
constructor(
context: Context,
attrs: AttributeSet?,
defStyleAttr: Int,
privateBrowsing: Boolean,
) : super(
getFixedContext(context), attrs, defStyleAttr, privateBrowsing
)
companion object {
fun getFixedContext(context: Context): Context {
return if (isLollipopWebViewBug) {
// Avoid crashing on Android 5 and 6 (API level 21 to 23)
context.createConfigurationContext(Configuration())
} else context
}
private val isLollipopWebViewBug: Boolean
get() = Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP && Build.VERSION.SDK_INT < Build.VERSION_CODES.M
}
package com.idormy.sms.forwarder.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
*/
@Suppress("unused", "DEPRECATION")
class LollipopFixedWebView : WebView {
constructor(context: Context) : super(getFixedContext(context))
constructor(context: Context, attrs: AttributeSet?) : super(getFixedContext(context), attrs)
constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int) : super(
getFixedContext(context), attrs, defStyleAttr
)
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
constructor(
context: Context,
attrs: AttributeSet?,
defStyleAttr: Int,
defStyleRes: Int,
) : super(
getFixedContext(context), attrs, defStyleAttr, defStyleRes
)
constructor(
context: Context,
attrs: AttributeSet?,
defStyleAttr: Int,
privateBrowsing: Boolean,
) : super(
getFixedContext(context), attrs, defStyleAttr, privateBrowsing
)
companion object {
fun getFixedContext(context: Context): Context {
return if (isLollipopWebViewBug) {
// Avoid crashing on Android 5 and 6 (API level 21 to 23)
context.createConfigurationContext(Configuration())
} else context
}
private val isLollipopWebViewBug: Boolean
get() = Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP && Build.VERSION.SDK_INT < Build.VERSION_CODES.M
}
}

@ -1,132 +1,133 @@
package com.idormy.sms.forwarder.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.idormy.sms.forwarder.R
import com.idormy.sms.forwarder.core.webview.WebViewInterceptDialog.Companion.show
import com.just.agentweb.core.client.MiddlewareWebClientBase
import com.xuexiang.xui.utils.ResUtils
import java.util.*
/**
* 网络请求加载
* WebClientWebViewClient 这个类主要帮助WebView处理各种通知url加载请求时间的中间件
*
*
*
*
* 方法的执行顺序例如下面用了7个中间件一个 WebViewClient
*
*
* .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
*
*
*
*
* 典型的洋葱模型
* 对象内部的方法执行顺序: 1->2->3->4->5->6->7->8->9->8->7->6->5->4->3->2->1
*
*
*
*
* 中断中间件的执行 删除super.methodName(...) 这行即可
*
*
* 这里主要是做去广告的工作
*/
open class MiddlewareWebViewClient : MiddlewareWebClientBase() {
@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
override fun shouldOverrideUrlLoading(view: WebView, request: WebResourceRequest): Boolean {
Log.i(
"Info",
"MiddlewareWebViewClient -- > shouldOverrideUrlLoading:" + request.url.toString() + " c:" + count++
)
return if (shouldOverrideUrlLoadingByApp(view, request.url.toString())) {
true
} else super.shouldOverrideUrlLoading(view, request)
}
override fun shouldOverrideUrlLoading(view: WebView, url: String): Boolean {
Log.i(
"Info",
"MiddlewareWebViewClient -- > shouldOverrideUrlLoading:" + url + " c:" + count++
)
return if (shouldOverrideUrlLoadingByApp(view, url)) {
true
} else super.shouldOverrideUrlLoading(view, url)
}
override fun shouldInterceptRequest(view: WebView, url: String): WebResourceResponse? {
val tUrl = url.lowercase(Locale.ROOT)
return if (!hasAdUrl(tUrl)) {
//正常加载
super.shouldInterceptRequest(view, tUrl)
} else {
//含有广告资源屏蔽请求
WebResourceResponse(null, null, null)
}
}
@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
override fun shouldInterceptRequest(
view: WebView,
request: WebResourceRequest,
): WebResourceResponse? {
val url = request.url.toString().lowercase(Locale.ROOT)
return if (!hasAdUrl(url)) {
//正常加载
super.shouldInterceptRequest(view, request)
} else {
//含有广告资源屏蔽请求
WebResourceResponse(null, null, null)
}
}
/**
* 根据url的scheme处理跳转第三方app的业务,true代表拦截false代表不拦截
*/
private fun shouldOverrideUrlLoadingByApp(webView: WebView, url: String): Boolean {
if (url.startsWith("http") || url.startsWith("https") || url.startsWith("ftp")) {
//不拦截http, https, ftp的请求
val uri = Uri.parse(url)
if (uri != null && !(WebViewInterceptDialog.APP_LINK_HOST == uri.host && url.contains("xpage"))) {
return false
}
}
show(url)
return true
}
companion object {
private var count = 1
/**
* 判断是否存在广告的链接
*
* @param url
* @return
*/
private fun hasAdUrl(url: String): Boolean {
val adUrls = ResUtils.getStringArray(R.array.adBlockUrl)
for (adUrl in adUrls) {
if (url.contains(adUrl)) {
return true
}
}
return false
}
}
package com.idormy.sms.forwarder.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.idormy.sms.forwarder.R
import com.idormy.sms.forwarder.core.webview.WebViewInterceptDialog.Companion.show
import com.just.agentweb.core.client.MiddlewareWebClientBase
import com.xuexiang.xui.utils.ResUtils
import java.util.*
/**
* 网络请求加载
* WebClientWebViewClient 这个类主要帮助WebView处理各种通知url加载请求时间的中间件
*
*
*
*
* 方法的执行顺序例如下面用了7个中间件一个 WebViewClient
*
*
* .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
*
*
*
*
* 典型的洋葱模型
* 对象内部的方法执行顺序: 1->2->3->4->5->6->7->8->9->8->7->6->5->4->3->2->1
*
*
*
*
* 中断中间件的执行 删除super.methodName(...) 这行即可
*
*
* 这里主要是做去广告的工作
*/
@Suppress("UNUSED_PARAMETER", "DEPRECATION", "OVERRIDE_DEPRECATION")
open class MiddlewareWebViewClient : MiddlewareWebClientBase() {
@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
override fun shouldOverrideUrlLoading(view: WebView, request: WebResourceRequest): Boolean {
Log.i(
"Info",
"MiddlewareWebViewClient -- > shouldOverrideUrlLoading:" + request.url.toString() + " c:" + count++
)
return if (shouldOverrideUrlLoadingByApp(view, request.url.toString())) {
true
} else super.shouldOverrideUrlLoading(view, request)
}
override fun shouldOverrideUrlLoading(view: WebView, url: String): Boolean {
Log.i(
"Info",
"MiddlewareWebViewClient -- > shouldOverrideUrlLoading:" + url + " c:" + count++
)
return if (shouldOverrideUrlLoadingByApp(view, url)) {
true
} else super.shouldOverrideUrlLoading(view, url)
}
override fun shouldInterceptRequest(view: WebView, url: String): WebResourceResponse? {
val tUrl = url.lowercase(Locale.ROOT)
return if (!hasAdUrl(tUrl)) {
//正常加载
super.shouldInterceptRequest(view, tUrl)
} else {
//含有广告资源屏蔽请求
WebResourceResponse(null, null, null)
}
}
@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
override fun shouldInterceptRequest(
view: WebView,
request: WebResourceRequest,
): WebResourceResponse? {
val url = request.url.toString().lowercase(Locale.ROOT)
return if (!hasAdUrl(url)) {
//正常加载
super.shouldInterceptRequest(view, request)
} else {
//含有广告资源屏蔽请求
WebResourceResponse(null, null, null)
}
}
/**
* 根据url的scheme处理跳转第三方app的业务,true代表拦截false代表不拦截
*/
private fun shouldOverrideUrlLoadingByApp(webView: WebView, url: String): Boolean {
if (url.startsWith("http") || url.startsWith("https") || url.startsWith("ftp")) {
//不拦截http, https, ftp的请求
val uri = Uri.parse(url)
if (uri != null && !(WebViewInterceptDialog.APP_LINK_HOST == uri.host && url.contains("xpage"))) {
return false
}
}
show(url)
return true
}
companion object {
private var count = 1
/**
* 判断是否存在广告的链接
*
* @param url
* @return
*/
private fun hasAdUrl(url: String): Boolean {
val adUrls = ResUtils.getStringArray(R.array.adBlockUrl)
for (adUrl in adUrls) {
if (url.contains(adUrl)) {
return true
}
}
return false
}
}
}

@ -1,88 +1,88 @@
package com.idormy.sms.forwarder.entity
import android.graphics.Rect
import android.os.Parcel
import android.os.Parcelable.Creator
import com.idormy.sms.forwarder.R
import com.xuexiang.xui.utils.ResUtils
import com.xuexiang.xui.widget.imageview.preview.enitity.IPreviewInfo
/**
* 图片预览实体类
*
* @author xuexiang
* @since 2018/12/7 下午5:34
*/
@Suppress("unused")
data class ImageInfo(
//图片地址
var mUrl: String,
//记录坐标
var mBounds: Rect? = null,
var mVideoUrl: String? = null,
var description: String? = ResUtils.getString(R.string.description),
) : IPreviewInfo {
constructor(url: String) : this(mUrl = url)
constructor(url: String, bounds: Rect?) : this(mUrl = url, mBounds = bounds)
constructor(videoUrl: String?, url: String) : this(mUrl = url, mVideoUrl = videoUrl)
override fun getUrl(): String { //将你的图片地址字段返回
return mUrl
}
fun setUrl(url: String) {
mUrl = url
}
override fun getBounds(): Rect? { //将你的图片显示坐标字段返回
return mBounds
}
override fun getVideoUrl(): String? {
return mVideoUrl
}
fun setBounds(bounds: Rect) {
mBounds = bounds
}
fun setVideoUrl(videoUrl: String) {
mVideoUrl = videoUrl
}
override fun describeContents(): Int {
return 0
}
override fun writeToParcel(dest: Parcel, flags: Int) {
dest.writeString(mUrl)
dest.writeParcelable(mBounds, flags)
dest.writeString(description)
dest.writeString(mVideoUrl)
}
constructor(`in`: Parcel) : this(
mUrl = `in`.readString()!!,
mBounds = `in`.readParcelable(Rect::class.java.classLoader),
description = `in`.readString(),
mVideoUrl = `in`.readString()
)
companion object CREATOR : Creator<ImageInfo> {
fun newInstance(url: String, bounds: Rect): List<ImageInfo> {
return listOf(ImageInfo(url, bounds))
}
override fun createFromParcel(parcel: Parcel): ImageInfo {
return ImageInfo(parcel)
}
override fun newArray(size: Int): Array<ImageInfo?> {
return arrayOfNulls(size)
}
}
package com.idormy.sms.forwarder.entity
import android.graphics.Rect
import android.os.Parcel
import android.os.Parcelable.Creator
import com.idormy.sms.forwarder.R
import com.xuexiang.xui.utils.ResUtils
import com.xuexiang.xui.widget.imageview.preview.enitity.IPreviewInfo
/**
* 图片预览实体类
*
* @author xuexiang
* @since 2018/12/7 下午5:34
*/
@Suppress("unused", "DEPRECATION")
data class ImageInfo(
//图片地址
var mUrl: String,
//记录坐标
var mBounds: Rect? = null,
var mVideoUrl: String? = null,
var description: String? = ResUtils.getString(R.string.description),
) : IPreviewInfo {
constructor(url: String) : this(mUrl = url)
constructor(url: String, bounds: Rect?) : this(mUrl = url, mBounds = bounds)
constructor(videoUrl: String?, url: String) : this(mUrl = url, mVideoUrl = videoUrl)
override fun getUrl(): String { //将你的图片地址字段返回
return mUrl
}
fun setUrl(url: String) {
mUrl = url
}
override fun getBounds(): Rect? { //将你的图片显示坐标字段返回
return mBounds
}
override fun getVideoUrl(): String? {
return mVideoUrl
}
fun setBounds(bounds: Rect) {
mBounds = bounds
}
fun setVideoUrl(videoUrl: String) {
mVideoUrl = videoUrl
}
override fun describeContents(): Int {
return 0
}
override fun writeToParcel(dest: Parcel, flags: Int) {
dest.writeString(mUrl)
dest.writeParcelable(mBounds, flags)
dest.writeString(description)
dest.writeString(mVideoUrl)
}
constructor(`in`: Parcel) : this(
mUrl = `in`.readString()!!,
mBounds = `in`.readParcelable(Rect::class.java.classLoader),
description = `in`.readString(),
mVideoUrl = `in`.readString()
)
companion object CREATOR : Creator<ImageInfo> {
fun newInstance(url: String, bounds: Rect): List<ImageInfo> {
return listOf(ImageInfo(url, bounds))
}
override fun createFromParcel(parcel: Parcel): ImageInfo {
return ImageInfo(parcel)
}
override fun newArray(size: Int): Array<ImageInfo?> {
return arrayOfNulls(size)
}
}
}

@ -3,6 +3,7 @@ package com.idormy.sms.forwarder.entity.setting
import com.idormy.sms.forwarder.R
import java.io.Serializable
@Suppress("SENSELESS_COMPARISON")
data class FeishuAppSetting(
var appId: String = "",
val appSecret: String = "",

@ -1,146 +1,146 @@
package com.idormy.sms.forwarder.fragment
import android.annotation.SuppressLint
import android.graphics.Color
import android.text.TextUtils
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.fragment.app.viewModels
import com.idormy.sms.forwarder.R
import com.idormy.sms.forwarder.core.BaseFragment
import com.idormy.sms.forwarder.database.entity.Frpc
import com.idormy.sms.forwarder.database.viewmodel.BaseViewModelFactory
import com.idormy.sms.forwarder.database.viewmodel.FrpcViewModel
import com.idormy.sms.forwarder.databinding.FragmentFrpcEditBinding
import com.idormy.sms.forwarder.utils.EVENT_FRPC_UPDATE_CONFIG
import com.idormy.sms.forwarder.utils.INTENT_FRPC_EDIT_FILE
import com.idormy.sms.forwarder.utils.XToastUtils
import com.jeremyliao.liveeventbus.LiveEventBus
import com.xuexiang.xaop.annotation.SingleClick
import com.xuexiang.xpage.annotation.Page
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.switchbutton.SwitchButton
import com.xuexiang.xui.widget.dialog.materialdialog.DialogAction
import com.xuexiang.xui.widget.dialog.materialdialog.MaterialDialog
import com.xuexiang.xui.widget.edittext.materialedittext.MaterialEditText
@Suppress("PrivatePropertyName")
@Page(name = "Frp内网穿透·编辑配置")
class FrpcEditFragment : BaseFragment<FragmentFrpcEditBinding?>() {
var titleBar: TitleBar? = null
var frpc: Frpc? = null
private val viewModel by viewModels<FrpcViewModel> { BaseViewModelFactory(context) }
override fun initViews() {
val pairCompleteMap: MutableMap<Char, Char> = HashMap()
pairCompleteMap['{'] = '}'
pairCompleteMap['['] = ']'
pairCompleteMap['('] = ')'
pairCompleteMap['<'] = '>'
pairCompleteMap['"'] = '"'
binding!!.editText.enablePairComplete(true)
binding!!.editText.enablePairCompleteCenterCursor(true)
binding!!.editText.setPairCompleteMap(pairCompleteMap)
binding!!.editText.setEnableLineNumber(true)
binding!!.editText.setLineNumberTextColor(Color.LTGRAY)
binding!!.editText.setLineNumberTextSize(24f)
binding!!.editText.textSize = 14f
}
override fun viewBindingInflate(inflater: LayoutInflater, container: ViewGroup): FragmentFrpcEditBinding {
return FragmentFrpcEditBinding.inflate(inflater, container, false)
}
override fun initTitle(): TitleBar? {
titleBar = super.initTitle()!!.setImmersive(false)
titleBar!!.setTitle(R.string.menu_frpc)
titleBar!!.setActionTextColor(ThemeUtils.resolveColor(context, R.attr.colorAccent))
titleBar!!.addAction(object : TitleBar.ImageAction(R.drawable.ic_save) {
@SuppressLint("ResourceAsColor")
@SingleClick
override fun performAction(view: View) {
if (frpc == null) return
val dialogFrpc = View.inflate(requireContext(), R.layout.dialog_frpc_save, null)
val tvName = dialogFrpc.findViewById<MaterialEditText>(R.id.tv_name)
val sbAutorun = dialogFrpc.findViewById<SwitchButton>(R.id.sb_autorun)
tvName.setText(frpc!!.name)
sbAutorun.setCheckedImmediately(frpc!!.autorun == 1)
frpc!!.config = binding!!.editText.text.toString()
if (TextUtils.isEmpty(frpc!!.config)) {
XToastUtils.error(R.string.tips_input_config_content)
return
}
MaterialDialog.Builder(context!!)
.iconRes(R.drawable.ic_menu_frpc)
.title(R.string.title_save_config)
.customView(dialogFrpc, true)
.cancelable(false)
.autoDismiss(false)
.neutralText(R.string.action_quit)
.neutralColor(ResUtils.getColors(R.color.red))
.onNeutral { dialog: MaterialDialog?, _: DialogAction? ->
dialog?.dismiss()
activity?.onBackPressed()
}
.negativeText(R.string.action_back)
.negativeColor(ResUtils.getColors(R.color.colorBlueGrey))
.onNegative { dialog: MaterialDialog?, _: DialogAction? ->
dialog?.dismiss()
}
.positiveText(R.string.action_save)
.onPositive { dialog: MaterialDialog?, _: DialogAction? ->
try {
frpc!!.autorun = if (sbAutorun.isChecked) 1 else 0
frpc!!.name = tvName.text.toString()
if (TextUtils.isEmpty(frpc!!.name)) {
XToastUtils.error(R.string.tips_input_config_name)
return@onPositive
}
if (TextUtils.isEmpty(frpc!!.uid)) {
viewModel.insert(frpc!!)
} else {
viewModel.update(frpc!!)
}
dialog?.dismiss()
LiveEventBus.get<Frpc>(EVENT_FRPC_UPDATE_CONFIG).post(frpc)
XToastUtils.success(R.string.tipSaveSuccess)
activity?.onBackPressed()
} catch (e: Exception) {
XToastUtils.error(e.message.toString())
}
}.show()
}
})
titleBar!!.addAction(object : TitleBar.ImageAction(R.drawable.ic_restore) {
@SingleClick
override fun performAction(view: View) {
binding!!.editText.setText(frpc?.config!!)
XToastUtils.success(R.string.tipRestoreSuccess)
}
})
return titleBar
}
override fun initListeners() {
LiveEventBus.get(INTENT_FRPC_EDIT_FILE, Frpc::class.java).observeSticky(this) { value: Frpc ->
frpc = value
binding!!.editText.setText(value.config)
titleBar!!.setTitle(if (TextUtils.isEmpty(value.name)) getString(R.string.noName) else value.name)
}
}
package com.idormy.sms.forwarder.fragment
import android.annotation.SuppressLint
import android.graphics.Color
import android.text.TextUtils
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.fragment.app.viewModels
import com.idormy.sms.forwarder.R
import com.idormy.sms.forwarder.core.BaseFragment
import com.idormy.sms.forwarder.database.entity.Frpc
import com.idormy.sms.forwarder.database.viewmodel.BaseViewModelFactory
import com.idormy.sms.forwarder.database.viewmodel.FrpcViewModel
import com.idormy.sms.forwarder.databinding.FragmentFrpcEditBinding
import com.idormy.sms.forwarder.utils.EVENT_FRPC_UPDATE_CONFIG
import com.idormy.sms.forwarder.utils.INTENT_FRPC_EDIT_FILE
import com.idormy.sms.forwarder.utils.XToastUtils
import com.jeremyliao.liveeventbus.LiveEventBus
import com.xuexiang.xaop.annotation.SingleClick
import com.xuexiang.xpage.annotation.Page
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.switchbutton.SwitchButton
import com.xuexiang.xui.widget.dialog.materialdialog.DialogAction
import com.xuexiang.xui.widget.dialog.materialdialog.MaterialDialog
import com.xuexiang.xui.widget.edittext.materialedittext.MaterialEditText
@Suppress("PrivatePropertyName", "DEPRECATION")
@Page(name = "Frp内网穿透·编辑配置")
class FrpcEditFragment : BaseFragment<FragmentFrpcEditBinding?>() {
var titleBar: TitleBar? = null
var frpc: Frpc? = null
private val viewModel by viewModels<FrpcViewModel> { BaseViewModelFactory(context) }
override fun initViews() {
val pairCompleteMap: MutableMap<Char, Char> = HashMap()
pairCompleteMap['{'] = '}'
pairCompleteMap['['] = ']'
pairCompleteMap['('] = ')'
pairCompleteMap['<'] = '>'
pairCompleteMap['"'] = '"'
binding!!.editText.enablePairComplete(true)
binding!!.editText.enablePairCompleteCenterCursor(true)
binding!!.editText.setPairCompleteMap(pairCompleteMap)
binding!!.editText.setEnableLineNumber(true)
binding!!.editText.setLineNumberTextColor(Color.LTGRAY)
binding!!.editText.setLineNumberTextSize(24f)
binding!!.editText.textSize = 14f
}
override fun viewBindingInflate(inflater: LayoutInflater, container: ViewGroup): FragmentFrpcEditBinding {
return FragmentFrpcEditBinding.inflate(inflater, container, false)
}
override fun initTitle(): TitleBar? {
titleBar = super.initTitle()!!.setImmersive(false)
titleBar!!.setTitle(R.string.menu_frpc)
titleBar!!.setActionTextColor(ThemeUtils.resolveColor(context, R.attr.colorAccent))
titleBar!!.addAction(object : TitleBar.ImageAction(R.drawable.ic_save) {
@SuppressLint("ResourceAsColor")
@SingleClick
override fun performAction(view: View) {
if (frpc == null) return
val dialogFrpc = View.inflate(requireContext(), R.layout.dialog_frpc_save, null)
val tvName = dialogFrpc.findViewById<MaterialEditText>(R.id.tv_name)
val sbAutorun = dialogFrpc.findViewById<SwitchButton>(R.id.sb_autorun)
tvName.setText(frpc!!.name)
sbAutorun.setCheckedImmediately(frpc!!.autorun == 1)
frpc!!.config = binding!!.editText.text.toString()
if (TextUtils.isEmpty(frpc!!.config)) {
XToastUtils.error(R.string.tips_input_config_content)
return
}
MaterialDialog.Builder(context!!)
.iconRes(R.drawable.ic_menu_frpc)
.title(R.string.title_save_config)
.customView(dialogFrpc, true)
.cancelable(false)
.autoDismiss(false)
.neutralText(R.string.action_quit)
.neutralColor(ResUtils.getColors(R.color.red))
.onNeutral { dialog: MaterialDialog?, _: DialogAction? ->
dialog?.dismiss()
activity?.onBackPressed()
}
.negativeText(R.string.action_back)
.negativeColor(ResUtils.getColors(R.color.colorBlueGrey))
.onNegative { dialog: MaterialDialog?, _: DialogAction? ->
dialog?.dismiss()
}
.positiveText(R.string.action_save)
.onPositive { dialog: MaterialDialog?, _: DialogAction? ->
try {
frpc!!.autorun = if (sbAutorun.isChecked) 1 else 0
frpc!!.name = tvName.text.toString()
if (TextUtils.isEmpty(frpc!!.name)) {
XToastUtils.error(R.string.tips_input_config_name)
return@onPositive
}
if (TextUtils.isEmpty(frpc!!.uid)) {
viewModel.insert(frpc!!)
} else {
viewModel.update(frpc!!)
}
dialog?.dismiss()
LiveEventBus.get<Frpc>(EVENT_FRPC_UPDATE_CONFIG).post(frpc)
XToastUtils.success(R.string.tipSaveSuccess)
activity?.onBackPressed()
} catch (e: Exception) {
XToastUtils.error(e.message.toString())
}
}.show()
}
})
titleBar!!.addAction(object : TitleBar.ImageAction(R.drawable.ic_restore) {
@SingleClick
override fun performAction(view: View) {
binding!!.editText.setText(frpc?.config!!)
XToastUtils.success(R.string.tipRestoreSuccess)
}
})
return titleBar
}
override fun initListeners() {
LiveEventBus.get(INTENT_FRPC_EDIT_FILE, Frpc::class.java).observeSticky(this) { value: Frpc ->
frpc = value
binding!!.editText.setText(value.config)
titleBar!!.setTitle(if (TextUtils.isEmpty(value.name)) getString(R.string.noName) else value.name)
}
}
}

@ -39,7 +39,7 @@ import io.reactivex.schedulers.Schedulers
import java.util.*
@Page(name = "URL Scheme")
@Suppress("PrivatePropertyName")
@Suppress("PrivatePropertyName", "DEPRECATION")
class UrlSchemeFragment : BaseFragment<FragmentSendersUrlSchemeBinding?>(), View.OnClickListener {
private val TAG: String = UrlSchemeFragment::class.java.simpleName

@ -1,105 +1,106 @@
package com.idormy.sms.forwarder.utils
import android.content.Context
import android.os.Environment
import java.io.File
import java.math.BigDecimal
class CacheUtils private constructor() {
companion object {
/**
* 获取缓存大小
*
* @param context 上下文
* @return 缓存大小
*/
fun getTotalCacheSize(context: Context): String {
return try {
var cacheSize = getFolderSize(context.cacheDir)
if (Environment.getExternalStorageState() == Environment.MEDIA_MOUNTED) {
cacheSize += getFolderSize(context.externalCacheDir)
}
getFormatSize(cacheSize.toDouble())
} catch (e: Exception) {
e.printStackTrace()
"0KB"
}
}
/***
* 清理所有缓存
* @param context 上下文
*/
fun clearAllCache(context: Context) {
deleteDir(context.cacheDir)
if (Environment.getExternalStorageState() == Environment.MEDIA_MOUNTED) {
deleteDir(context.externalCacheDir)
}
}
private fun deleteDir(dir: File?): Boolean {
if (dir != null && dir.isDirectory) {
val children = dir.list()!!
for (child in children) {
val success = deleteDir(File(dir, child))
if (!success) {
return false
}
}
}
assert(dir != null)
return dir!!.delete()
}
// 获取文件
//Context.getExternalFilesDir() --> SDCard/Android/data/你的应用的包名/files/ 目录,一般放一些长时间保存的数据
//Context.getExternalCacheDir() --> SDCard/Android/data/你的应用包名/cache/目录,一般存放临时缓存数据
private fun getFolderSize(file: File?): Long {
var size: Long = 0
try {
val fileList = file!!.listFiles()!!
for (value in fileList) {
// 如果下面还有文件
size = if (value.isDirectory) {
size + getFolderSize(value)
} else {
size + value.length()
}
}
} catch (e: Exception) {
e.printStackTrace()
}
return size
}
/**
* 格式化单位
*
* @param size 文件大小
* @return 结果
*/
private fun getFormatSize(size: Double): String {
val kiloByte = size / 1024
if (kiloByte < 1) {
return "0KB"
}
val megaByte = kiloByte / 1024
if (megaByte < 1) {
val result1 = BigDecimal(kiloByte.toString())
return result1.setScale(2, BigDecimal.ROUND_HALF_UP).toPlainString() + "KB"
}
val gigaByte = megaByte / 1024
if (gigaByte < 1) {
val result2 = BigDecimal(megaByte.toString())
return result2.setScale(2, BigDecimal.ROUND_HALF_UP).toPlainString() + "MB"
}
val teraBytes = gigaByte / 1024
if (teraBytes < 1) {
val result3 = BigDecimal(gigaByte.toString())
return result3.setScale(2, BigDecimal.ROUND_HALF_UP).toPlainString() + "GB"
}
val result4 = BigDecimal(teraBytes)
return result4.setScale(2, BigDecimal.ROUND_HALF_UP).toPlainString() + "TB"
}
}
package com.idormy.sms.forwarder.utils
import android.content.Context
import android.os.Environment
import java.io.File
import java.math.BigDecimal
@Suppress("DEPRECATION")
class CacheUtils private constructor() {
companion object {
/**
* 获取缓存大小
*
* @param context 上下文
* @return 缓存大小
*/
fun getTotalCacheSize(context: Context): String {
return try {
var cacheSize = getFolderSize(context.cacheDir)
if (Environment.getExternalStorageState() == Environment.MEDIA_MOUNTED) {
cacheSize += getFolderSize(context.externalCacheDir)
}
getFormatSize(cacheSize.toDouble())
} catch (e: Exception) {
e.printStackTrace()
"0KB"
}
}
/***
* 清理所有缓存
* @param context 上下文
*/
fun clearAllCache(context: Context) {
deleteDir(context.cacheDir)
if (Environment.getExternalStorageState() == Environment.MEDIA_MOUNTED) {
deleteDir(context.externalCacheDir)
}
}
private fun deleteDir(dir: File?): Boolean {
if (dir != null && dir.isDirectory) {
val children = dir.list()!!
for (child in children) {
val success = deleteDir(File(dir, child))
if (!success) {
return false
}
}
}
assert(dir != null)
return dir!!.delete()
}
// 获取文件
//Context.getExternalFilesDir() --> SDCard/Android/data/你的应用的包名/files/ 目录,一般放一些长时间保存的数据
//Context.getExternalCacheDir() --> SDCard/Android/data/你的应用包名/cache/目录,一般存放临时缓存数据
private fun getFolderSize(file: File?): Long {
var size: Long = 0
try {
val fileList = file!!.listFiles()!!
for (value in fileList) {
// 如果下面还有文件
size = if (value.isDirectory) {
size + getFolderSize(value)
} else {
size + value.length()
}
}
} catch (e: Exception) {
e.printStackTrace()
}
return size
}
/**
* 格式化单位
*
* @param size 文件大小
* @return 结果
*/
private fun getFormatSize(size: Double): String {
val kiloByte = size / 1024
if (kiloByte < 1) {
return "0KB"
}
val megaByte = kiloByte / 1024
if (megaByte < 1) {
val result1 = BigDecimal(kiloByte.toString())
return result1.setScale(2, BigDecimal.ROUND_HALF_UP).toPlainString() + "KB"
}
val gigaByte = megaByte / 1024
if (gigaByte < 1) {
val result2 = BigDecimal(megaByte.toString())
return result2.setScale(2, BigDecimal.ROUND_HALF_UP).toPlainString() + "MB"
}
val teraBytes = gigaByte / 1024
if (teraBytes < 1) {
val result3 = BigDecimal(gigaByte.toString())
return result3.setScale(2, BigDecimal.ROUND_HALF_UP).toPlainString() + "GB"
}
val result4 = BigDecimal(teraBytes)
return result4.setScale(2, BigDecimal.ROUND_HALF_UP).toPlainString() + "TB"
}
}
}

@ -1,51 +1,52 @@
package com.idormy.sms.forwarder.utils
import android.annotation.SuppressLint
import android.app.Activity
import android.content.Context
import android.content.Intent
import android.content.pm.ResolveInfo
import android.net.Uri
import android.os.Build
import android.os.PowerManager
import android.provider.Settings
import androidx.annotation.RequiresApi
import com.idormy.sms.forwarder.R
class KeepAliveUtils private constructor() {
companion object {
fun isIgnoreBatteryOptimization(activity: Activity): Boolean {
//安卓6.0以下没有忽略电池优化
return if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) {
true
} else try {
val powerManager: PowerManager = activity.getSystemService(Context.POWER_SERVICE) as PowerManager
powerManager.isIgnoringBatteryOptimizations(activity.packageName)
} catch (e: Exception) {
XToastUtils.error(R.string.unsupport)
false
}
}
@RequiresApi(api = Build.VERSION_CODES.M)
fun ignoreBatteryOptimization(activity: Activity) {
try {
if (isIgnoreBatteryOptimization(activity)) {
return
}
@SuppressLint("BatteryLife") val intent = Intent(Settings.ACTION_REQUEST_IGNORE_BATTERY_OPTIMIZATIONS)
intent.data = Uri.parse("package:" + activity.packageName)
val resolveInfo: ResolveInfo? = activity.packageManager.resolveActivity(intent, 0)
if (resolveInfo != null) {
activity.startActivity(intent)
} else {
XToastUtils.error(R.string.unsupport)
}
} catch (e: Exception) {
XToastUtils.error(R.string.unsupport)
}
}
}
package com.idormy.sms.forwarder.utils
import android.annotation.SuppressLint
import android.app.Activity
import android.content.Context
import android.content.Intent
import android.content.pm.ResolveInfo
import android.net.Uri
import android.os.Build
import android.os.PowerManager
import android.provider.Settings
import androidx.annotation.RequiresApi
import com.idormy.sms.forwarder.R
@Suppress("DEPRECATION")
class KeepAliveUtils private constructor() {
companion object {
fun isIgnoreBatteryOptimization(activity: Activity): Boolean {
//安卓6.0以下没有忽略电池优化
return if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) {
true
} else try {
val powerManager: PowerManager = activity.getSystemService(Context.POWER_SERVICE) as PowerManager
powerManager.isIgnoringBatteryOptimizations(activity.packageName)
} catch (e: Exception) {
XToastUtils.error(R.string.unsupport)
false
}
}
@RequiresApi(api = Build.VERSION_CODES.M)
fun ignoreBatteryOptimization(activity: Activity) {
try {
if (isIgnoreBatteryOptimization(activity)) {
return
}
@SuppressLint("BatteryLife") val intent = Intent(Settings.ACTION_REQUEST_IGNORE_BATTERY_OPTIMIZATIONS)
intent.data = Uri.parse("package:" + activity.packageName)
val resolveInfo: ResolveInfo? = activity.packageManager.resolveActivity(intent, 0)
if (resolveInfo != null) {
activity.startActivity(intent)
} else {
XToastUtils.error(R.string.unsupport)
}
} catch (e: Exception) {
XToastUtils.error(R.string.unsupport)
}
}
}
}

@ -32,7 +32,7 @@ import com.xuexiang.xutil.resource.ResUtils
import java.text.SimpleDateFormat
import java.util.*
@Suppress("PropertyName")
@Suppress("PropertyName", "DEPRECATION")
class PhoneUtils private constructor() {
companion object {

@ -7,6 +7,7 @@ import java.io.*
import kotlin.properties.ReadWriteProperty
import kotlin.reflect.KProperty
@Suppress("unused", "UNCHECKED_CAST")
class SharedPreference<T>(private val name: String, private val default: T) : ReadWriteProperty<Any?, T> {
companion object {

@ -1,47 +1,49 @@
package com.idormy.sms.forwarder.utils.mail
import android.util.Log
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.async
import kotlinx.coroutines.launch
import javax.mail.Transport
/**
* 邮件发送器
*/
object MailSender {
/**
* 获取单例
*/
@JvmStatic
fun getInstance() = this
/**
* 发送邮件
*/
fun sendMail(mail: Mail, onMailSendListener: OnMailSendListener? = null) {
val send = GlobalScope.async(Dispatchers.IO) {
Transport.send(MailUtil.createMailMessage(mail))
}
GlobalScope.launch(Dispatchers.Main) {
runCatching {
send.await()
onMailSendListener?.onSuccess()
}.onFailure {
Log.e("MailSender", it.message.toString())
onMailSendListener?.onError(it)
}
}
}
/**
* 发送回调
*/
interface OnMailSendListener {
fun onSuccess()
fun onError(e: Throwable)
}
package com.idormy.sms.forwarder.utils.mail
import android.util.Log
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.async
import kotlinx.coroutines.launch
import javax.mail.Transport
/**
* 邮件发送器
*/
object MailSender {
/**
* 获取单例
*/
@JvmStatic
fun getInstance() = this
/**
* 发送邮件
*/
fun sendMail(mail: Mail, onMailSendListener: OnMailSendListener? = null) {
@Suppress("OPT_IN_USAGE")
val send = GlobalScope.async(Dispatchers.IO) {
Transport.send(MailUtil.createMailMessage(mail))
}
@Suppress("OPT_IN_USAGE")
GlobalScope.launch(Dispatchers.Main) {
runCatching {
send.await()
onMailSendListener?.onSuccess()
}.onFailure {
Log.e("MailSender", it.message.toString())
onMailSendListener?.onError(it)
}
}
}
/**
* 发送回调
*/
interface OnMailSendListener {
fun onSuccess()
fun onError(e: Throwable)
}
}

@ -41,7 +41,7 @@ class UMengInit private constructor() {
return
}
UMConfigure.setLogEnabled(false)
UMConfigure.preInit(application, BuildConfig.APP_ID_UMENG, getChannel(application))
UMConfigure.preInit(application, BuildConfig.APP_ID_UMENG, getChannel()) //getChannel(application)
// 用户同意了隐私协议
if (isAgreePrivacy) {
realInit(application)
@ -62,7 +62,7 @@ class UMengInit private constructor() {
UMConfigure.init(
application,
BuildConfig.APP_ID_UMENG,
getChannel(application),
getChannel(), //getChannel(application)
UMConfigure.DEVICE_TYPE_PHONE,
""
)
@ -78,7 +78,7 @@ class UMengInit private constructor() {
* @param context
* @return
*/
private fun getChannel(context: Context?): String {
private fun getChannel(): String { //context: Context?
//return WalleChannelReader.getChannel(context!!, DEFAULT_CHANNEL_ID)
return DEFAULT_CHANNEL_ID
}

@ -4,7 +4,7 @@ buildscript {
apply from: './versions.gradle'
addRepos(repositories) //
dependencies {
classpath "com.android.tools.build:gradle:$versions.android_gradle_plugin"
classpath deps.android_gradle_plugin
classpath deps.android_maven_gradle_plugin
//
classpath 'com.chenenyu:img-optimizer:1.2.0'
@ -58,9 +58,9 @@ allprojects {
task clean(type: Delete) {
delete rootProject.buildDir
FileTree tree = fileTree(dir: rootProject.getRootDir())
tree.each { File file ->
if (file.toString().contains("ajcore") && file.toString().endsWith(".txt")) {
FileTree rootTree = fileTree(dir: rootDir)
rootTree.each { File file ->
if ((file.toString().contains("ajcore") || file.toString().contains("mapping") || file.toString().contains("seeds") || file.toString().contains("unused")) && file.toString().endsWith(".txt")) {
delete file
}
}

Loading…
Cancel
Save