diff --git a/.gitignore b/.gitignore index a8b0d1d..d4c3a57 100644 --- a/.gitignore +++ b/.gitignore @@ -1,35 +1,16 @@ -# ---> Android -# Gradle files -.gradle/ -build/ - -# Local configuration file (sdk path, etc) -local.properties - -# Log/OS Files -*.log - -# Android Studio generated files and folders -captures/ -.externalNativeBuild/ -.cxx/ -*.apk -output.json - -# IntelliJ *.iml -.idea/ -misc.xml -deploymentTargetDropDown.xml -render.experimental.xml - -# Keystore files -*.jks -*.keystore - -# Google Services (e.g. APIs or Firebase) -google-services.json - -# Android Profiling -*.hprof - +.gradle +/local.properties +/.idea/caches +/.idea/libraries +/.idea/modules.xml +/.idea/workspace.xml +/.idea/navEditor.xml +/.idea/assetWizardSettings.xml +.DS_Store +/build +/captures +.externalNativeBuild +.cxx +local.properties +/.idea/ diff --git a/BaseLibrary/.gitignore b/BaseLibrary/.gitignore new file mode 100644 index 0000000..42afabf --- /dev/null +++ b/BaseLibrary/.gitignore @@ -0,0 +1 @@ +/build \ No newline at end of file diff --git a/BaseLibrary/build.gradle b/BaseLibrary/build.gradle new file mode 100644 index 0000000..2f635b5 --- /dev/null +++ b/BaseLibrary/build.gradle @@ -0,0 +1,60 @@ +apply plugin: 'com.android.library' + +android { + namespace "com.tfq.library" + compileSdkVersion 34 + + defaultConfig { + minSdkVersion 21 + targetSdkVersion 34 + } + + buildTypes { + release { + minifyEnabled false + proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' + } + } +} + +dependencies { +// api 'androidx.constraintlayout:constraintlayout:1.1.3' + api 'androidx.appcompat:appcompat:1.7.0' + api 'com.google.android.material:material:1.11.0' +// api 'androidx.swiperefreshlayout:swiperefreshlayout:1.0.0' +// api 'androidx.core:core-ktx:1.10.1' + testImplementation 'junit:junit:4.13.2' + androidTestImplementation 'androidx.test.ext:junit:1.1.5' + androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.1' + + //沉浸式 + api 'com.gyf.immersionbar:immersionbar:3.0.0-beta05' + + //权限请求 + api 'com.github.getActivity:XXPermissions:18.63' + + api 'com.github.CymChad:BaseRecyclerViewAdapterHelper:2.9.28' + + api 'androidx.activity:activity:1.8.0' + + api 'com.google.code.gson:gson:2.10.1' + api 'com.alibaba:fastjson:1.2.55' + + //网络请求 + api 'com.squareup.okhttp3:okhttp:3.4.2' + api 'com.squareup.okhttp3:logging-interceptor:3.5.0' + + //图片加载 + api 'com.github.bumptech.glide:glide:3.7.0' + api 'com.google.android:flexbox:0.3.1' + api 'com.github.bumptech.glide:glide:4.14.1' + annotationProcessor 'com.github.bumptech.glide:compiler:4.14.1' + // Glide图形转换工具 + api 'jp.wasabeef:glide-transformations:2.0.1' + + // 吐司框架:https://github.com/getActivity/Toaster + api 'com.github.getActivity:Toaster:12.8' + + //高斯模糊效果 + api 'com.github.centerzx:ShapeBlurView:1.0.5' +} \ No newline at end of file diff --git a/BaseLibrary/consumer-rules.pro b/BaseLibrary/consumer-rules.pro new file mode 100644 index 0000000..e69de29 diff --git a/BaseLibrary/proguard-rules.pro b/BaseLibrary/proguard-rules.pro new file mode 100644 index 0000000..481bb43 --- /dev/null +++ b/BaseLibrary/proguard-rules.pro @@ -0,0 +1,21 @@ +# Add project specific ProGuard rules here. +# You can control the set of applied configuration files using the +# proguardFiles setting in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} + +# Uncomment this to preserve the line number information for +# debugging stack traces. +#-keepattributes SourceFile,LineNumberTable + +# If you keep the line number information, uncomment this to +# hide the original source file name. +#-renamesourcefileattribute SourceFile \ No newline at end of file diff --git a/BaseLibrary/src/androidTest/java/com/tfq/library/ExampleInstrumentedTest.java b/BaseLibrary/src/androidTest/java/com/tfq/library/ExampleInstrumentedTest.java new file mode 100644 index 0000000..7572ee8 --- /dev/null +++ b/BaseLibrary/src/androidTest/java/com/tfq/library/ExampleInstrumentedTest.java @@ -0,0 +1,26 @@ +package com.tfq.library; + +import android.content.Context; + +import androidx.test.platform.app.InstrumentationRegistry; +import androidx.test.ext.junit.runners.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 Testing documentation + */ +@RunWith(AndroidJUnit4.class) +public class ExampleInstrumentedTest { + @Test + public void useAppContext() { + // Context of the app under test. + Context appContext = InstrumentationRegistry.getInstrumentation().getTargetContext(); + assertEquals("com.tfq.library.test", appContext.getPackageName()); + } +} \ No newline at end of file diff --git a/BaseLibrary/src/main/AndroidManifest.xml b/BaseLibrary/src/main/AndroidManifest.xml new file mode 100644 index 0000000..6fefa6c --- /dev/null +++ b/BaseLibrary/src/main/AndroidManifest.xml @@ -0,0 +1,11 @@ + + + + + + + + + \ No newline at end of file diff --git a/BaseLibrary/src/main/java/com/tfq/library/adapter/MyFragmentStateAdapter.java b/BaseLibrary/src/main/java/com/tfq/library/adapter/MyFragmentStateAdapter.java new file mode 100644 index 0000000..3ab1937 --- /dev/null +++ b/BaseLibrary/src/main/java/com/tfq/library/adapter/MyFragmentStateAdapter.java @@ -0,0 +1,30 @@ +package com.tfq.library.adapter; + +import androidx.annotation.NonNull; +import androidx.fragment.app.Fragment; +import androidx.fragment.app.FragmentManager; +import androidx.lifecycle.Lifecycle; +import androidx.viewpager2.adapter.FragmentStateAdapter; + +import java.util.List; + +public class MyFragmentStateAdapter extends FragmentStateAdapter { + + private final List mFragmentList; + + public MyFragmentStateAdapter(@NonNull FragmentManager fragmentManager, @NonNull Lifecycle lifecycle, List fragments) { + super(fragmentManager, lifecycle); + mFragmentList = fragments; + } + + @NonNull + @Override + public Fragment createFragment(int position) { + return mFragmentList.get(position); + } + + @Override + public int getItemCount() { + return mFragmentList.size(); + } +} diff --git a/BaseLibrary/src/main/java/com/tfq/library/adapter/TabFragmentPagerAdapter.java b/BaseLibrary/src/main/java/com/tfq/library/adapter/TabFragmentPagerAdapter.java new file mode 100644 index 0000000..07129e1 --- /dev/null +++ b/BaseLibrary/src/main/java/com/tfq/library/adapter/TabFragmentPagerAdapter.java @@ -0,0 +1,33 @@ +package com.tfq.library.adapter; + +import android.view.ViewGroup; + +import java.util.List; + +import androidx.fragment.app.Fragment; +import androidx.fragment.app.FragmentManager; +import androidx.fragment.app.FragmentPagerAdapter; + +public class TabFragmentPagerAdapter extends FragmentPagerAdapter { + private final List mlist; + + public TabFragmentPagerAdapter(FragmentManager fm, List list) { + super(fm); + this.mlist = list; + } + + @Override + public void setPrimaryItem(ViewGroup container, int position, Object object) { + super.setPrimaryItem(container, position, object); + } + + @Override + public Fragment getItem(int arg0) { + return mlist.get(arg0);//显示第几个页面 + } + + @Override + public int getCount() { + return mlist.size();//有几个页面 + } +} diff --git a/BaseLibrary/src/main/java/com/tfq/library/app/BaseConstants.java b/BaseLibrary/src/main/java/com/tfq/library/app/BaseConstants.java new file mode 100644 index 0000000..c6dd871 --- /dev/null +++ b/BaseLibrary/src/main/java/com/tfq/library/app/BaseConstants.java @@ -0,0 +1,51 @@ +package com.tfq.library.app; + +public class BaseConstants { + + public static final int URL_REQUEST_ERROR = 401; + public static boolean BASE_APP_DEBUG_PRINT; + public static String APP_ID = ""; + public static String CSJ_ID = ""; + public static String APP_NAME = ""; + public static String CHANNEL = ""; + + public static String csjIdFeed1; + public static String csjIdFeed2; + + public static boolean _isShow; + + // 1.开屏 2.激励视频 3.信息流 4.插全屏 5.banner 6.draw信息流 + public static boolean AD_SPLASH = false;//开屏广告 + public static boolean AD_REWARD = false;//激励视频 + public static boolean AD_CQP = false;//插全屏广告 + public static boolean AD_NATIVE = false;//原生信息流广告 + public static boolean AD_BANNER = false;//banner广告 + public static boolean AD_DRAW = false;//draw信息流广告 + public static int ADV_Wait = 60;//插全屏两个相差时间 + public static boolean adv_csj = true;//穿山甲正常 + public static boolean NO_AD = true;//穿山甲正常 + + public static long request_splash_time = 30; + public static boolean AD_Switch_Requested = false;//请求过广告开关 + public static String CODE_AD_SPLASH; + public static String CODE_AD_CQP; + public static String CODE_AD_FEED1; + public static String CODE_AD_FEED2; + public static String CODE_AD_FEED3; + public static String CODE_AD_BANNER; + public static String CODE_AD_REWARD; + public static String CODE_AD_DRAW; + + public static boolean PRE_AD = true; + public static int appSplash; + + /** + * 底部导航栏颜色 默认白色 + */ + public static String navigationBarColor = "#FFFFFF"; + + /** + * 自定义dialog使用的layout类型 + */ + public static int dialog_layout = 2; +} diff --git a/BaseLibrary/src/main/java/com/tfq/library/app/IConstituteApp.java b/BaseLibrary/src/main/java/com/tfq/library/app/IConstituteApp.java new file mode 100644 index 0000000..07c0ada --- /dev/null +++ b/BaseLibrary/src/main/java/com/tfq/library/app/IConstituteApp.java @@ -0,0 +1,8 @@ +package com.tfq.library.app; + +import android.app.Application; + +public interface IConstituteApp { + //初始化 + void onCreate(Application application); +} diff --git a/BaseLibrary/src/main/java/com/tfq/library/app/LibraryApp.java b/BaseLibrary/src/main/java/com/tfq/library/app/LibraryApp.java new file mode 100644 index 0000000..383a028 --- /dev/null +++ b/BaseLibrary/src/main/java/com/tfq/library/app/LibraryApp.java @@ -0,0 +1,116 @@ +package com.tfq.library.app; + +import android.app.Activity; +import android.app.Application; +import android.content.Context; +import android.content.pm.ApplicationInfo; +import android.content.pm.PackageManager; +import android.os.Bundle; + +import com.hjq.toast.Toaster; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; + +/** + * Created by Administrator on 2016/10/28. + */ +public class LibraryApp implements IConstituteApp { + public static Activity activityTop; + private static Context mContext; + private static LibraryApp instance; + public Application application; + + public static LibraryApp getInstance() { + return instance; + } + + public static Context getContext() { + return mContext; + } + + public static Activity getActivityTop() { + return activityTop; + } + + public static String getChannel() { + try { + PackageManager pm = mContext.getPackageManager(); + ApplicationInfo appInfo = pm.getApplicationInfo(mContext.getPackageName(), PackageManager.GET_META_DATA); + return appInfo.metaData.getString("UMENG_CHANNEL"); + } catch (PackageManager.NameNotFoundException ignored) { + } + return "other"; + } + + public static String getJson(String fileName, Context context) { + StringBuilder stringBuilder = new StringBuilder(); + try { + InputStream is = context.getAssets().open(fileName); + BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(is)); + String line; + while ((line = bufferedReader.readLine()) != null) { + stringBuilder.append(line); + } + } catch (IOException e) { + e.printStackTrace(); + } + return stringBuilder.toString(); + } + + @Override + public void onCreate(Application application) { + mContext = application.getApplicationContext(); + instance = this; + this.application = application; + register(); + + // 初始化 Toast 框架 + Toaster.init(application); + } + + private void register() { + application.registerActivityLifecycleCallbacks(new Application.ActivityLifecycleCallbacks() { + @Override + public void onActivityCreated(@NonNull Activity activity, @Nullable Bundle bundle) { + + } + + @Override + public void onActivityStarted(@NonNull Activity activity) { + activityTop = activity; + } + + @Override + public void onActivityResumed(@NonNull Activity activity) { + + } + + @Override + public void onActivityPaused(@NonNull Activity activity) { + + } + + @Override + public void onActivityStopped(@NonNull Activity activity) { + + } + + @Override + public void onActivitySaveInstanceState(@NonNull Activity activity, @NonNull Bundle bundle) { + + } + + @Override + public void onActivityDestroyed(@NonNull Activity activity) { + + } + }); + } + +} diff --git a/BaseLibrary/src/main/java/com/tfq/library/base/BaseActivity.java b/BaseLibrary/src/main/java/com/tfq/library/base/BaseActivity.java new file mode 100644 index 0000000..4a8a2b8 --- /dev/null +++ b/BaseLibrary/src/main/java/com/tfq/library/base/BaseActivity.java @@ -0,0 +1,85 @@ +package com.tfq.library.base; + +import android.app.Activity; +import android.content.Intent; +import android.os.Bundle; + +import com.gyf.immersionbar.ImmersionBar; +import com.tfq.library.app.BaseConstants; +import com.tfq.library.utils.AppUtil; + +import androidx.annotation.LayoutRes; +import androidx.annotation.Nullable; +import androidx.appcompat.app.AppCompatActivity; + +/** + * Created by JiangKe on 2025/02/20. + */ +public abstract class BaseActivity extends AppCompatActivity { + + /****************************abstract area*************************************/ + + @LayoutRes + protected abstract int getLayoutId(); + + /** + * 初始化零件 + */ + protected abstract void initView(); + + protected abstract void initData(Bundle savedInstanceState); + + /** + * 初始化点击事件 + */ + protected void initClick() { + } + + /** + * 初始化沉浸式 + */ + protected void initImmersionBar() { + } + + /** + * 逻辑使用区 + */ + protected void processLogic() { + } + + private boolean statusBarDarkFont; + public void setStatusBarDarkFont(boolean statusBarDarkFont){ + this.statusBarDarkFont = statusBarDarkFont; + } + /*************************lifecycle area*****************************************************/ + + @Override + protected void onCreate(@Nullable Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(getLayoutId()); + initView(); + initData(savedInstanceState); + initClick(); + processLogic(); + ImmersionBar.with((Activity) this) + .transparentStatusBar() //不写也可以,默认就是透明色 + .statusBarDarkFont(statusBarDarkFont) + .navigationBarColor(BaseConstants.navigationBarColor) +// .keyboardEnable(true) + .init(); + initImmersionBar(); + } + + /**************************used method area*******************************************/ + + protected void startActivity(Class activity) { + Intent intent = new Intent(this, activity); + startActivity(intent); + } + + @Override + protected void onPause() { + super.onPause(); + AppUtil.closeKeyBoard(this); + } +} diff --git a/BaseLibrary/src/main/java/com/tfq/library/base/BaseFragment.java b/BaseLibrary/src/main/java/com/tfq/library/base/BaseFragment.java new file mode 100644 index 0000000..246ca3c --- /dev/null +++ b/BaseLibrary/src/main/java/com/tfq/library/base/BaseFragment.java @@ -0,0 +1,177 @@ +package com.tfq.library.base; + +import android.os.Bundle; +import android.text.TextUtils; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; + +import com.hjq.permissions.OnPermissionCallback; +import com.hjq.permissions.XXPermissions; +import com.tfq.library.utils.PermissionDialog; + +import java.util.List; + +import androidx.annotation.LayoutRes; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.fragment.app.Fragment; + +/** + * Created by JiangKe on 2025/02/20. + */ + +public abstract class BaseFragment extends Fragment { + + private View root = null; + private String content = "当前操作需要您授权相应权限,否则可能无法正常使用此功能"; + + @LayoutRes + protected abstract int getLayoutId(); + + /** + * 初始化零件 + */ + protected abstract void initView(); + + protected abstract void initData(Bundle savedInstanceState); + + /** + * 初始化点击事件 + */ + protected void initClick() { + } + + /** + * 逻辑使用区 + */ + protected void processLogic() { + } + + /** + * 权限请求 + */ + protected void requestPermission(String[] per, Listener listener) { + requestPermission(per, content, listener); + } + + protected void requestPermission(String[] per, String content, Listener listener) { + if (!TextUtils.isEmpty(content)) { + this.content = content; + } + requestPermission(per, content, true, false, listener); + } + + protected void requestPermission(String[] per, String content, boolean show_doNotAskAgain, Listener listener) { + if (!TextUtils.isEmpty(content)) { + this.content = content; + } + requestPermission(per, content, true, show_doNotAskAgain, listener); + } + + protected void requestPermission(String[] per, String content, boolean req_permission, boolean show_doNotAskAgain, Listener listener) { + if (!TextUtils.isEmpty(content)) { + this.content = content; + } + requestPermission(per, req_permission, show_doNotAskAgain, listener); + } + + /** + * 申请权限 + * + * @param permission 权限集合 + * @param req_permission 申请权限之前是否提示,默认false,不提示 + * @param show_doNotAskAgain 权限申请失败是否调整到设置页,默认false,不关闭 + * @param listener 监听 + */ + protected void requestPermission(String[] permission, boolean req_permission, boolean show_doNotAskAgain, Listener listener) { + if (!req_permission) {//不弹窗直接申请权限 + requestPermission(permission, show_doNotAskAgain, listener); + } else {//先弹窗,再去申请 + PermissionDialog permissionDialog = new PermissionDialog(getActivity(), "request_per", "权限申请" + , content, new PermissionDialog.Listener_Result() { + @Override + public void success(boolean b) { + if (b) {//同意申请权限,去申请 + requestPermission(permission, show_doNotAskAgain, listener); + } else {//不同意申请权限 + + } + } + }); + permissionDialog.setCanceledOnTouchOutside(false); + permissionDialog.setCancelable(false); + permissionDialog.show(); + } + } + + private void requestPermission(String[] permission, boolean show_doNotAskAgain, Listener listener) { + XXPermissions.with(getActivity()) + .permission(permission) + .request(new OnPermissionCallback() { + @Override + public void onGranted(@NonNull List permissions, boolean allGranted) { + if (!allGranted) { + //获取部分权限成功,但部分权限未正常授予 + return; + } + listener.success(); + } + + @Override + public void onDenied(@NonNull List permissions, boolean doNotAskAgain) { + if (show_doNotAskAgain) { + if (doNotAskAgain) { + //如果是被永久拒绝就跳转到应用权限系统设置页面 + XXPermissions.startPermissionActivity(getActivity(), permissions); + } else { + //获取权限失败 + } + } + } + }); + } + + /******************************lifecycle area*****************************************/ + @Nullable + @Override + public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { + int resId = getLayoutId(); + root = inflater.inflate(resId, container, false); + return root; + } + + @Override + public void onViewCreated(View view, @Nullable Bundle savedInstanceState) { + super.onViewCreated(view, savedInstanceState); + initView(); + initData(savedInstanceState); + initClick(); + processLogic(); + + + } + + @Override + public void onDetach() { + super.onDetach(); + } + + /**************************公共类*******************************************/ + public String getName() { + return getClass().getName(); + } + + protected VT findViewBy_Id(int id) { + if (root == null) { + return null; + } + return (VT) root.findViewById(id); + } + + public interface Listener { + void success(); + } +} + + diff --git a/BaseLibrary/src/main/java/com/tfq/library/utils/AddTextDialog.java b/BaseLibrary/src/main/java/com/tfq/library/utils/AddTextDialog.java new file mode 100644 index 0000000..c94be8f --- /dev/null +++ b/BaseLibrary/src/main/java/com/tfq/library/utils/AddTextDialog.java @@ -0,0 +1,177 @@ +package com.tfq.library.utils; + +import static androidx.core.content.ContextCompat.getSystemService; + +import android.annotation.SuppressLint; +import android.app.Activity; +import android.app.AlertDialog; +import android.content.Context; +import android.content.DialogInterface; +import android.os.Handler; +import android.text.InputFilter; +import android.text.InputType; +import android.text.TextUtils; +import android.view.LayoutInflater; +import android.view.View; +import android.view.inputmethod.InputMethodManager; +import android.widget.Button; +import android.widget.EditText; + +import com.tfq.library.R; + +public class AddTextDialog { + boolean isClick = false; + private String name; + private String title; + private String type; + private Context mContext; + private Listener listener; + private ListenerFail listenerFail; + private AlertDialog dialog; + + public AddTextDialog(Context context, Listener listener) { + this.mContext = context; + this.listener = listener; + } + + public AddTextDialog(Context context, ListenerFail listenerFail) { + this.mContext = context; + this.listenerFail = listenerFail; + } + + public AddTextDialog(Context context, String name, Listener listener) { + this.mContext = context; + this.listener = listener; + this.name = name; + } + + public AddTextDialog(Context context, String name, String title, String type, Listener listener) { + this.mContext = context; + this.listener = listener; + this.name = name; + this.title = title; + this.type = type; + } + + @SuppressLint("ClickableViewAccessibility") + public void show() { + // 创建编辑框视图 + LayoutInflater inflater = LayoutInflater.from(mContext); + View inputView = inflater.inflate(R.layout.dialog_add_text_layout, null); + EditText et_text = inputView.findViewById(R.id.et_name); + + if (!TextUtils.isEmpty(name)) { + et_text.setText(name); + et_text.setSelection(et_text.getText().toString().length()); + } + if (!TextUtils.isEmpty(type) && type.equals("budgets")){ + // 设置输入类型为带小数点的数字 + et_text.setInputType(InputType.TYPE_CLASS_NUMBER | InputType.TYPE_NUMBER_FLAG_DECIMAL); + // 添加输入过滤器 + et_text.setFilters(new InputFilter[]{new DecimalInputFilter()}); + + // 焦点丢失时格式化显示 + et_text.setOnFocusChangeListener((v, hasFocus) -> { + if (!hasFocus) { + String text = et_text.getText().toString(); + if (!text.isEmpty()) { + try { + double value = Double.parseDouble(text); + et_text.setText(String.format("%.2f", value)); + } catch (NumberFormatException e) { + et_text.setText(""); + } + } + } + }); + }else if (!TextUtils.isEmpty(type) && type.equals("nickname")){ + et_text.setFilters(new InputFilter[]{new InputFilter.LengthFilter(20)}); + } + + et_text.requestFocus(); + + et_text.postDelayed(() -> { + InputMethodManager imm = (InputMethodManager) mContext.getSystemService(Context.INPUT_METHOD_SERVICE); + // 强制显示软键盘 + imm.showSoftInput(et_text, InputMethodManager.SHOW_FORCED); + }, 100); // 延迟200ms等待布局渲染完成 + + + // 创建对话框 + dialog = new AlertDialog.Builder(mContext) + .setTitle(TextUtils.isEmpty(title) ? "创建记账项目" : title) + .setView(inputView) + .setPositiveButton("创建", null) + .setNegativeButton("取消", new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialogInterface, int i) { + AppUtil.closeKeyBoard(et_text); + if (listenerFail != null) { + listenerFail.callCancel(); + } + } + }) + .create(); + + // 设置对话框所属的Activity + if (mContext instanceof Activity) { + dialog.setOwnerActivity((Activity) mContext); + } + + // 使用工具类设置模糊背景和圆角 + DialogUtils.setBlurBackground(dialog); + + dialog.show(); + + Button confirmButton = dialog.getButton(AlertDialog.BUTTON_POSITIVE); + confirmButton.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + String trim = et_text.getText().toString().trim(); + if (TextUtils.isEmpty(trim)) { + if (TextUtils.isEmpty(type)) { + ToasterUtil.show("请先输入记账名称"); + } else if (type.equals("budgets")){ + ToasterUtil.show("请先输入本月预算金额"); + }else if (type.equals("nickname")){ + ToasterUtil.show("用户昵称不能为空"); + } + } else { + if (listener != null && !isClick) { + AppUtil.closeKeyBoard(et_text); + isClick = true; + listener.success(trim); + dialog.dismiss(); + } + if (listenerFail != null && !isClick) { + AppUtil.closeKeyBoard(et_text); + isClick = true; + listenerFail.success(trim); + dialog.dismiss(); + } + } + } + }); + + dialog.setOnCancelListener(dialog -> { + new Handler().postDelayed(() -> { + InputMethodManager imm2 = (InputMethodManager) mContext.getSystemService(Context.INPUT_METHOD_SERVICE); + imm2.hideSoftInputFromWindow(((Activity)mContext).getWindow().getDecorView().getWindowToken(), 0); + }, 100); // 延迟100ms避免窗口令牌销毁 + if (listenerFail != null) { + listenerFail.callCancel(); + } + }); + + } + + public interface Listener { + void success(String name); + } + + public interface ListenerFail { + void success(String name); + + void callCancel(); + } +} diff --git a/BaseLibrary/src/main/java/com/tfq/library/utils/AppSigning.java b/BaseLibrary/src/main/java/com/tfq/library/utils/AppSigning.java new file mode 100644 index 0000000..15af522 --- /dev/null +++ b/BaseLibrary/src/main/java/com/tfq/library/utils/AppSigning.java @@ -0,0 +1,146 @@ +package com.tfq.library.utils; + +import android.content.Context; +import android.content.pm.PackageInfo; +import android.content.pm.PackageManager; +import android.content.pm.Signature; + +import com.tfq.library.utils.LogK; + +import java.security.MessageDigest; +import java.util.ArrayList; +import java.util.HashMap; + +public class AppSigning { + public final static String MD5 = "MD5"; + public final static String SHA1 = "SHA1"; + public final static String SHA256 = "SHA256"; + private static final HashMap> mSignMap = new HashMap<>(); + + /** + * 返回一个签名的对应类型的字符串 + * + * @param context + * @param type + * @return 因为一个安装包可以被多个签名文件签名,所以返回一个签名信息的list + */ + public static ArrayList getSignInfo(Context context, String type) { + if (context == null || type == null) { + return null; + } + String packageName = context.getPackageName(); + if (packageName == null) { + return null; + } + if (mSignMap.get(type) != null) { + return mSignMap.get(type); + } + ArrayList mList = new ArrayList(); + try { + Signature[] signs = getSignatures(context, packageName); + for (Signature sig : signs) { + String tmp = "error!"; + if (MD5.equals(type)) { + tmp = getSignatureByteString(sig, MD5); + } else if (SHA1.equals(type)) { + tmp = getSignatureByteString(sig, SHA1); + } else if (SHA256.equals(type)) { + tmp = getSignatureByteString(sig, SHA256); + } + mList.add(tmp); + } + } catch (Exception e) { + LogK.e(e.toString()); + } + mSignMap.put(type, mList); + return mList; + } + + /** + * 获取签名sha1值 + * + * @param context + * @return + */ + public static String getSha1(Context context) { + String res = ""; + ArrayList mlist = getSignInfo(context, SHA1); + if (mlist != null && mlist.size() != 0) { + res = mlist.get(0); + } + return res; + } + + /** + * 返回对应包的签名信息 + * + * @param context + * @param packageName + * @return + */ + private static Signature[] getSignatures(Context context, String packageName) { + PackageInfo packageInfo = null; + try { + packageInfo = context.getPackageManager().getPackageInfo(packageName, PackageManager.GET_SIGNATURES); + return packageInfo.signatures; + } catch (Exception e) { + LogK.e(e.toString()); + } + return null; + } + + /** + * 获取相应的类型的字符串(把签名的byte[]信息转换成16进制) + * + * @param sig + * @param type + * @return + */ + private static String getSignatureString(Signature sig, String type) { + byte[] hexBytes = sig.toByteArray(); + String fingerprint = "error!"; + try { + MessageDigest digest = MessageDigest.getInstance(type); + if (digest != null) { + byte[] digestBytes = digest.digest(hexBytes); + StringBuilder sb = new StringBuilder(); + for (byte digestByte : digestBytes) { + sb.append((Integer.toHexString((digestByte & 0xFF) | 0x100)).substring(1, 3)); + } + fingerprint = sb.toString(); + } + } catch (Exception e) { + LogK.e(e.toString()); + } + + return fingerprint; + } + + /** + * 获取相应的类型的字符串(把签名的byte[]信息转换成 95:F4:D4:FG 这样的字符串形式) + * + * @param sig + * @param type + * @return + */ + private static String getSignatureByteString(Signature sig, String type) { + byte[] hexBytes = sig.toByteArray(); + String fingerprint = "error!"; + try { + MessageDigest digest = MessageDigest.getInstance(type); + if (digest != null) { + byte[] digestBytes = digest.digest(hexBytes); + StringBuilder sb = new StringBuilder(); + for (byte digestByte : digestBytes) { + sb.append(((Integer.toHexString((digestByte & 0xFF) | 0x100)).substring(1, 3)).toUpperCase()); + sb.append(":"); + } + fingerprint = sb.substring(0, sb.length() - 1); + } + } catch (Exception e) { + LogK.e(e.toString()); + } + + return fingerprint; + } +} diff --git a/BaseLibrary/src/main/java/com/tfq/library/utils/AppUtil.java b/BaseLibrary/src/main/java/com/tfq/library/utils/AppUtil.java new file mode 100644 index 0000000..78e51b2 --- /dev/null +++ b/BaseLibrary/src/main/java/com/tfq/library/utils/AppUtil.java @@ -0,0 +1,722 @@ +package com.tfq.library.utils; + +import android.app.Activity; +import android.app.ActivityManager; +import android.app.AlertDialog; +import android.content.Context; +import android.content.ContextWrapper; +import android.content.DialogInterface; +import android.content.Intent; +import android.content.pm.ActivityInfo; +import android.content.pm.PackageInfo; +import android.content.pm.PackageManager; +import android.net.ConnectivityManager; +import android.net.NetworkCapabilities; +import android.net.Uri; +import android.os.Build; +import android.os.Environment; +import android.os.StatFs; +import android.text.TextUtils; +import android.view.View; +import android.view.WindowManager; +import android.view.inputmethod.InputMethodManager; +import android.widget.EditText; +import android.widget.TextView; + +import androidx.core.content.FileProvider; + +import com.gyf.immersionbar.BarHide; +import com.gyf.immersionbar.ImmersionBar; +import com.tfq.library.app.LibraryApp; + +import java.io.ByteArrayInputStream; +import java.io.File; +import java.lang.reflect.Field; +import java.security.cert.CertificateFactory; +import java.security.cert.X509Certificate; +import java.util.Iterator; +import java.util.List; +import java.util.Locale; +import java.util.Map; + +public class AppUtil { + /** + * 获取屏幕分辨率? + * + * @param context + * @return + */ + public static int[] getScreenDispaly(Context context) { + WindowManager windowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE); + @SuppressWarnings("deprecation") int width = windowManager.getDefaultDisplay().getWidth(); + @SuppressWarnings("deprecation") int height = windowManager.getDefaultDisplay().getHeight(); + int[] result = {width, height}; + return result; + } + + /** + * 获取app的名称 + * + * @param context + * @return + */ + public static String getAppName(Context context) { + String appName = ""; + try { + PackageManager packageManager = context.getPackageManager(); + PackageInfo packageInfo = packageManager.getPackageInfo(context.getPackageName(), 0); + int labelRes = packageInfo.applicationInfo.labelRes; + appName = context.getResources().getString(labelRes); + } catch (Throwable e) { + e.printStackTrace(); + } + return appName; + } + + /** + * 获取app的名称 + * + * @param context + * @return + */ + public static String getAppVersionNameCode(Context context) { + String appCode = ""; + try { + PackageManager packageManager = context.getPackageManager(); + PackageInfo packageInfo = packageManager.getPackageInfo(context.getPackageName(), 0); + String versionName = packageInfo.versionName; + String versionCode = packageInfo.versionCode + ""; + appCode = "versionName=" + versionName + " versionCode=" + versionCode; + } catch (Throwable e) { + e.printStackTrace(); + } + return appCode; + } + + /** + * 获取app的名称 + * + * @param context + * @return + */ + public static String getPackageName(Context context) { + String name = ""; + try { + PackageManager packageManager = context.getPackageManager(); + PackageInfo packageInfo = packageManager.getPackageInfo(context.getPackageName(), 0); + name = packageInfo.applicationInfo.packageName; + } catch (Throwable e) { + e.printStackTrace(); + } + return name; + } + + /** + * 获取app的名称 + * + * @return + */ + public static String getPackageName() { + String name = ""; + try { + PackageManager packageManager = LibraryApp.getContext().getPackageManager(); + PackageInfo packageInfo = packageManager.getPackageInfo(LibraryApp.getContext().getPackageName(), 0); + name = packageInfo.applicationInfo.packageName; + } catch (Throwable e) { + e.printStackTrace(); + } + return name; + } + + /** + * @param mContext 上下文 + * @param title title + * @param message message + * @param ok 确定按钮 + * @param cancel 返回按钮 + * @param orNull 是否可以为空,false设置为默认title message ok cancel + */ + public static void securitySD(Activity mContext, String title, String message, String ok, String cancel, boolean orNull) { + if (mContext != null) { + mContext.runOnUiThread(new Runnable() { + @Override + public void run() { + String s_title = title; + String s_message = message; + String s_ok = ok; + String s_cancel = cancel; + if (!orNull) { + s_title = "温馨提示"; + if (!TextUtils.isEmpty(title)) { + s_title = title; + } + s_message = "请在应用详情页面权限列表进行设置。"; + if (!TextUtils.isEmpty(message)) { + s_message = message; + } + s_ok = "确定"; + if (!TextUtils.isEmpty(ok)) { + s_ok = ok; + } + s_cancel = "返回"; + if (!TextUtils.isEmpty(cancel)) { + s_cancel = cancel; + } + } + + AlertDialog alertDialog = new AlertDialog.Builder(mContext).setTitle(s_title).setMessage(s_message).setPositiveButton(s_ok, new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + + } + }).setNegativeButton(s_cancel, new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + + } + }).setCancelable(false).create(); + alertDialog.show(); + } + }); + } + } + + public static void securitySD(Activity mContext, String title, String message, String ok, String cancel) { + if (mContext != null) { + mContext.runOnUiThread(new Runnable() { + @Override + public void run() { + String s_title = "温馨提示"; + if (!TextUtils.isEmpty(title)) { + s_title = title; + } + String s_message = "请在应用详情页面权限列表进行设置。"; + if (!TextUtils.isEmpty(message)) { + s_message = message; + } + String s_ok = "确定"; + if (!TextUtils.isEmpty(ok)) { + s_ok = ok; + } + String s_cancel = "返回"; + if (!TextUtils.isEmpty(cancel)) { + s_cancel = cancel; + } + AlertDialog alertDialog = new AlertDialog.Builder(mContext).setTitle(s_title).setMessage(s_message).setPositiveButton(s_ok, new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + Intent intent = new Intent(); + intent.setAction("android.settings.APPLICATION_DETAILS_SETTINGS"); + intent.setData(Uri.fromParts("package", mContext.getPackageName(), null)); + mContext.startActivity(intent); + } + }).setNegativeButton(s_cancel, new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + + } + }).setCancelable(false).create(); + alertDialog.show(); + } + }); + } + } + + public static void securitySD(Activity mContext, String title, String message, String ok) { + if (mContext != null) { + mContext.runOnUiThread(new Runnable() { + @Override + public void run() { + String s_title = "温馨提示"; + if (!TextUtils.isEmpty(title)) { + s_title = title; + } + String s_message = ""; + if (!TextUtils.isEmpty(message)) { + s_message = message; + } + String s_ok = ""; + if (!TextUtils.isEmpty(ok)) { + s_ok = ok; + } + AlertDialog alertDialog = new AlertDialog.Builder(mContext).setTitle(s_title).setMessage(s_message).setPositiveButton(s_ok, new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + mContext.finish(); + } + }).setCancelable(false).create(); + alertDialog.show(); + } + }); + } + } + + public static void installApk(Context mContext, String downloadApk) { + Intent intent = new Intent(Intent.ACTION_VIEW); + File file = new File(downloadApk); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { + //这里要注意FileProvider一定要在AndroidManifest中去声明哦 + //同时com.mc.mcplatform代表你自己的包名 + Uri apkUri = FileProvider.getUriForFile(mContext, mContext.getPackageName() + ".provider", file); + intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); + intent.setDataAndType(apkUri, "application/vnd.android.package-archive"); + } else { + intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + Uri uri = Uri.fromFile(file); + intent.setDataAndType(uri, "application/vnd.android.package-archive"); + } + mContext.startActivity(intent); + } + + public static boolean connectStatus(Context context) { + boolean isConnect = true; + try { + ConnectivityManager manager = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { + NetworkCapabilities networkCapabilities = manager.getNetworkCapabilities(manager.getActiveNetwork()); + if (networkCapabilities != null) { + isConnect = networkCapabilities.hasCapability(NetworkCapabilities.NET_CAPABILITY_VALIDATED); + } + } + } catch (Exception e) { + e.printStackTrace(); + + } + return isConnect; + + } + + public static boolean connectStatus() { + boolean isConnect = true; + try { + ConnectivityManager manager = (ConnectivityManager) LibraryApp.getContext().getSystemService(Context.CONNECTIVITY_SERVICE); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { + NetworkCapabilities networkCapabilities = manager.getNetworkCapabilities(manager.getActiveNetwork()); + if (networkCapabilities != null) { + isConnect = networkCapabilities.hasCapability(NetworkCapabilities.NET_CAPABILITY_VALIDATED); + } + } + } catch (Exception e) { + e.printStackTrace(); + + } + return isConnect; + + } + + public static boolean isRunningForeground(Context mContext) { + try { + ActivityManager activityManager = (ActivityManager) mContext.getSystemService(Context.ACTIVITY_SERVICE); + List appProcesses = activityManager.getRunningAppProcesses(); + for (ActivityManager.RunningAppProcessInfo appProcess : appProcesses) { + if (appProcess.processName.equals(mContext.getPackageName())) { + return appProcess.importance != ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND; + } + } + } catch (Exception e) { + e.printStackTrace(); + return false; + } + return false; + } + + public static ActivityInfo[] getAllActivity(Context mContext) { + ActivityInfo[] activities = null; + PackageManager packageManager = mContext.getPackageManager(); + PackageInfo packageInfo = null; + try { + packageInfo = packageManager.getPackageInfo(mContext.getPackageName(), PackageManager.GET_ACTIVITIES); + //所有的Activity + activities = packageInfo.activities; + +// for (ActivityInfo activity : activities) { +// Class aClass = Class.forName(activity.name); +// } + } catch (Exception e) { + e.printStackTrace(); + } + return activities; + } + + public static void exitActivity(Context mContext) { + ActivityInfo[] activities = getAllActivity(mContext); + if (activities != null && activities.length > 0) { + for (int i = 0; i < activities.length; i++) { + ActivityInfo activity = activities[i]; + String name = activity.name; + LogK.e("mContext.getPackageName()=" + mContext.getPackageName()); + if (name.startsWith(mContext.getPackageName())) { + Activity currentActivity = getCurrentActivity(name); + if (currentActivity != null) currentActivity.finish(); + LogK.e("name=" + name); + } + } + } + } + + private static Activity getCurrentActivity(String name) { + try { + Class activityThreadClass = Class.forName(name); + Object activityThread = activityThreadClass.getMethod("currentActivityThread").invoke(null); + Field activitiesField = activityThreadClass.getDeclaredField("mActivities"); + activitiesField.setAccessible(true); + Map activities = (Map) activitiesField.get(activityThread); + + for (Object activityRecord : activities.values()) { + Class activityRecordClass = activityRecord.getClass(); + Field pausedField = activityRecordClass.getDeclaredField("paused"); + pausedField.setAccessible(true); + if (!pausedField.getBoolean(activityRecord)) { + Field activityField = activityRecordClass.getDeclaredField("activity"); + activityField.setAccessible(true); + Activity activity = (Activity) activityField.get(activityRecord); + return activity; + } + } + return null; + } catch (Exception e) { + e.printStackTrace(); + } + return null; + } + + /** + * 获取app的VersionName + * + * @param context + * @return + */ + public static String getAppVersionName(Context context) { + String appVersionName = "1"; + try { + PackageManager packageManager = context.getPackageManager(); + PackageInfo packageInfo = packageManager.getPackageInfo(context.getPackageName(), 0); + appVersionName = packageInfo.versionName; + } catch (Throwable e) { + e.printStackTrace(); + } + return appVersionName; + } + + /** + * 获取app的VersionName + * + * @return + */ + public static String getAppVersionName() { + String appVersionName = "1"; + try { + PackageManager packageManager = LibraryApp.getContext().getPackageManager(); + PackageInfo packageInfo = packageManager.getPackageInfo(LibraryApp.getContext().getPackageName(), 0); + appVersionName = packageInfo.versionName; + } catch (Throwable e) { + e.printStackTrace(); + } + return appVersionName; + } + + /** + * 获取app的VersionCode + * + * @param context + * @return + */ + public static String getAppVersionCode(Context context) { + String appVersionCode = "1"; + try { + PackageManager packageManager = context.getPackageManager(); + PackageInfo packageInfo = packageManager.getPackageInfo(context.getPackageName(), 0); + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.P) { + appVersionCode = packageInfo.versionCode + ""; + } else { + appVersionCode = packageInfo.getLongVersionCode() + ""; + } + } catch (Exception e) { + e.printStackTrace(); + } + return appVersionCode; + } + + /** + * 获取app的VersionCode + * + * @return + */ + public static String getAppVersionCode() { + String appVersionCode = "1"; + try { + PackageManager packageManager = LibraryApp.getContext().getPackageManager(); + PackageInfo packageInfo = packageManager.getPackageInfo(LibraryApp.getContext().getPackageName(), 0); + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.P) { + appVersionCode = packageInfo.versionCode + ""; + } else { + appVersionCode = packageInfo.getLongVersionCode() + ""; + } + } catch (Exception e) { + e.printStackTrace(); + } + return appVersionCode; + } + + public static boolean regexPhoneNum(String phone) { + String telRegex = "^1[3-9]\\d{9}$"; + if (TextUtils.isEmpty(phone)) { + return false; + } else { + return phone.matches(telRegex); + } + } + + /** + * 正则是不是纯数字 + * + * @param string + * @return + */ + public static boolean regexIsNumber(String string) { + String str = "[0-9]*"; + boolean matches = string.matches(str); + return matches; + } + + /** + * 判断字符串是否为整数或小数(含正负数) + * @param input 待检测字符串 + * @return true-是数字,false-不是数字 + */ + public static boolean isNumeric(String input) { + if (input == null || input.isEmpty()) return false; + // 正则解释: + // ^-? : 可选负号开头 + // \\d+ : 至少一位整数 + // (\\.\\d+)?: 可选小数部分 + return input.matches("^-?\\d+(\\.\\d+)?$"); + } + + // 扩展方法:区分整数和小数 + public static boolean isInteger(String input) { + return input != null && input.matches("^-?\\d+$"); + } + + public static boolean isDecimal(String input) { + return input != null && input.matches("^-?\\d+\\.\\d+$"); + } + + + public static void getActivityName(Context context) { + Activity activityByContext = getActivityByContext(context); + if (activityByContext != null) { + String localClassName = activityByContext.getLocalClassName(); + LogK.e("localClassName=" + localClassName); + } + } + + private static Activity getActivityByContext(Context context) { + while (context instanceof ContextWrapper) { + if (context instanceof Activity) { + return (Activity) context; + } + context = ((ContextWrapper) context).getBaseContext(); + } + return null; + } + + public static String getTopActivityName(Context context) { + ActivityManager activityManager = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE); + List listTask = activityManager.getRunningTasks(0); + String activityName = ""; + if (listTask != null && !listTask.isEmpty()) { + ActivityManager.RunningTaskInfo runningTaskInfo = listTask.get(1); + activityName = runningTaskInfo.topActivity.getClassName(); + } + return activityName; + } + + public static String getTopActivity(Context context) { + ActivityManager manager = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE); + List runningTaskInfos = manager.getRunningTasks(1); + + if (runningTaskInfos != null) { + return (runningTaskInfos.get(0).topActivity.getClassName()); + } else return null; + } + + public static String getPublicKey(byte[] signature) { + try { + CertificateFactory certFactory = CertificateFactory.getInstance("X.509"); + X509Certificate cert = (X509Certificate) certFactory.generateCertificate(new ByteArrayInputStream(signature)); + LogK.i("监听2" + " 22"); + String publickey = cert.getPublicKey().toString(); + LogK.d("-------密码TRACK " + publickey); +// publickey = publickey.substring(publickey.indexOf("modulus:") + 9,publickey.indexOf("\n", publickey.indexOf("modulus:"))); + + LogK.d("-------密码TRACK " + publickey); + return publickey; + } catch (Exception e) { + e.printStackTrace(); + } + return null; + } + + public static byte[] getSign(Context context) { + PackageManager pm = context.getPackageManager(); + List apps = pm.getInstalledPackages(PackageManager.GET_SIGNATURES); + Iterator iter = apps.iterator(); + LogK.i("签名1 " + iter + ""); + LogK.i("监听1 " + "----------"); + while (iter.hasNext()) { + PackageInfo info = iter.next(); + String packageName = info.packageName; + //按包名 取签名 + if (packageName.equals(AppUtil.getPackageName(context))) { + LogK.i("监听1 " + info.signatures[0].toByteArray() + "----------"); + return info.signatures[0].toByteArray(); + + } + } + + return null; + } + + /** + * 格式化时间 + */ + public static String stringForTime(int timeMs) { + int totalSeconds = timeMs / 1000; + + int seconds = totalSeconds % 60; + int minutes = (totalSeconds / 60) % 60; + int hours = totalSeconds / 3600; + + if (hours > 0) { + return String.format(Locale.getDefault(), "%d:%02d:%02d", hours, minutes, seconds); + } else { + return String.format(Locale.getDefault(), "%02d:%02d", minutes, seconds); + } + } + + /** + * 根据给定的大小返回合适的单位(KB、MB、GB) + * + * @param sizeInBytes 字节数 + * @return 格式化后的字符串 + */ + public static String getSizeInUnit(long sizeInBytes) { + if (sizeInBytes < 1024) { + return sizeInBytes + " B"; + } else if (sizeInBytes < 1024 * 1024) { + return String.format("%.2f KB", sizeInBytes / 1024.0); + } else if (sizeInBytes < 1024 * 1024 * 1024) { + return String.format("%.2f MB", sizeInBytes / (1024.0 * 1024.0)); + } else { + return String.format("%.2f GB", sizeInBytes / (1024.0 * 1024.0 * 1024.0)); + } + } + + /** + * 清除缓存 + * + * @param context + */ + public static void clearApplicationCache(Context context) { + File cache = context.getCacheDir(); + if (cache != null && cache.isDirectory()) { + deleteDir(cache); + } + } + + /** + * 执行循环清除 + * + * @param dir + * @return + */ + private static boolean deleteDir(File dir) { + if (dir != null && dir.isDirectory()) { + String[] children = dir.list(); + for (int i = 0; i < children.length; i++) { + boolean success = deleteDir(new File(dir, children[i])); + if (!success) { + return false; + } + } + } + + // The directory is now empty so delete it + return dir.delete(); + } + + + /** + * 手机存储百分比 + */ + public static void queryStorage(TextView textView) { + StatFs statFs = new StatFs(Environment.getExternalStorageDirectory().getPath()); + //存储块总数量 + long blockCount = statFs.getBlockCount(); + //块大小 + long blockSize = statFs.getBlockSize(); + //可用块数量 + long availableCount = statFs.getAvailableBlocks(); + //剩余块数量,注:这个包含保留块(including reserved blocks)即应用无法使用的空间 + long freeBlocks = statFs.getFreeBlocks(); + //这两个方法是直接输出总内存和可用空间,也有getFreeBytes//API level 18(JELLY_BEAN_MR2)引入 + long totalSize = statFs.getTotalBytes(); + long availableSize = statFs.getAvailableBytes(); + long all = totalSize + availableSize; + int percentage = (int) (totalSize * 100 / (totalSize + availableSize)); + textView.setText(getUnit(totalSize) + "/" + getUnit(all)); + } + + /*** 单位转换*/ + private static String getUnit(float size) { + String[] units = {"B", "KB", "MB", "GB", "TB"}; + int index = 0; + while (size > 1024 && index < 4) { + size = size / 1024; + index++; + } + return String.format(Locale.getDefault(), " %.2f %s", size, units[index]); + } + + /** + * 隐藏状态栏和导航栏 + */ + public static void setHideBar_StatusAndNavigation(Context mContext) { + ImmersionBar.with((Activity) mContext) + .hideBar(BarHide.FLAG_HIDE_STATUS_BAR) + .hideBar(BarHide.FLAG_HIDE_NAVIGATION_BAR) + .init(); + } + + /** + * 关闭软键盘 + */ + public static void closeKeyBoard(View currentFocus) { + try { + if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.M) { + InputMethodManager imm = (InputMethodManager) LibraryApp.getContext().getSystemService(Context.INPUT_METHOD_SERVICE); + imm.hideSoftInputFromWindow(currentFocus.getWindowToken(), 0); + } + } catch (Exception e) { + e.printStackTrace(); + } + } + + /** + * 关闭软键盘 + */ + public static void closeKeyBoard(Activity activity) { + InputMethodManager imm = (InputMethodManager) activity.getSystemService(Activity.INPUT_METHOD_SERVICE); + View focusView = activity.getCurrentFocus(); + // 优先使用当前焦点视图的窗口令牌 + if (focusView != null) { + imm.hideSoftInputFromWindow(focusView.getWindowToken(), 0); + } else { + // 无焦点视图时使用Activity的窗口令牌 + imm.hideSoftInputFromWindow(activity.getWindow().getDecorView().getWindowToken(), 0); + } + } + + +} diff --git a/BaseLibrary/src/main/java/com/tfq/library/utils/BigPhotoDialog.java b/BaseLibrary/src/main/java/com/tfq/library/utils/BigPhotoDialog.java new file mode 100644 index 0000000..7005e0d --- /dev/null +++ b/BaseLibrary/src/main/java/com/tfq/library/utils/BigPhotoDialog.java @@ -0,0 +1,54 @@ +package com.tfq.library.utils; + +import android.app.Dialog; +import android.content.Context; +import android.graphics.BitmapFactory; +import android.os.Bundle; +import android.view.LayoutInflater; +import android.view.View; +import android.widget.ImageView; + +import com.tfq.library.R; + +public class BigPhotoDialog extends Dialog { + private final Context mContext; + private final LayoutInflater inflater; + private final String url; + private View contentView; + + public BigPhotoDialog(Context context, String url) { + super(context, R.style.CustomDialog); + this.mContext = context; + this.url = url; + inflater = LayoutInflater.from(context); + } + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setViews(); + } + + private void setViews() { + contentView = inflater.inflate(R.layout.layout_dialog_bigphoto, null); + setContentView(contentView); + ImageView imageView = contentView.findViewById(R.id.image_view); + try { + imageView.setImageBitmap(BitmapFactory.decodeFile(url)); + } catch (Exception e) { + e.printStackTrace(); + GlideLoadUtils.loadResource(mContext, url, imageView); + } + imageView.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + dismiss(); + } + }); + + } + + public interface Listener { + void callBack(); + } +} diff --git a/BaseLibrary/src/main/java/com/tfq/library/utils/CloseUtils.java b/BaseLibrary/src/main/java/com/tfq/library/utils/CloseUtils.java new file mode 100644 index 0000000..c627a44 --- /dev/null +++ b/BaseLibrary/src/main/java/com/tfq/library/utils/CloseUtils.java @@ -0,0 +1,54 @@ +package com.tfq.library.utils; + +import java.io.Closeable; +import java.io.IOException; + +/** + *
+ *     author: Blankj
+ *     blog  : http://blankj.com
+ *     time  : 2016/10/09
+ *     desc  : 关闭相关工具类
+ * 
+ */ +public final class CloseUtils { + + private CloseUtils() { + throw new UnsupportedOperationException("u can't instantiate me..."); + } + + /** + * 关闭 IO + * + * @param closeables closeables + */ + public static void closeIO(final Closeable... closeables) { + if (closeables == null) return; + for (Closeable closeable : closeables) { + if (closeable != null) { + try { + closeable.close(); + } catch (IOException e) { + e.printStackTrace(); + } + } + } + } + + /** + * 安静关闭 IO + * + * @param closeables closeables + */ + public static void closeIOQuietly(final Closeable... closeables) { + if (closeables == null) return; + for (Closeable closeable : closeables) { + if (closeable != null) { + try { + closeable.close(); + } catch (IOException ignored) { + } + } + } + } +} diff --git a/BaseLibrary/src/main/java/com/tfq/library/utils/CustomLoadMoreView.java b/BaseLibrary/src/main/java/com/tfq/library/utils/CustomLoadMoreView.java new file mode 100644 index 0000000..0b039ce --- /dev/null +++ b/BaseLibrary/src/main/java/com/tfq/library/utils/CustomLoadMoreView.java @@ -0,0 +1,41 @@ +package com.tfq.library.utils; + +import com.chad.library.adapter.base.loadmore.LoadMoreView; +import com.tfq.library.R; + +public class CustomLoadMoreView extends LoadMoreView { + @Override + public int getLayoutId() { + return R.layout.quick_view_load_more; + } + + /** + * 如果返回true,数据全部加载完毕后会隐藏加载更多 + * 如果返回false,数据全部加载完毕后会显示getLoadEndViewId()布局 + * + * @return + */ + @Override + public boolean isLoadEndGone() { + return true; + } + + @Override + protected int getLoadingViewId() { + return R.id.load_more_loading_view; + } + + @Override + protected int getLoadFailViewId() { + return R.id.load_more_load_fail_view; + } + + /** + * isLoadEndGone(为true, 可以返回0 + * isLoadEndGone 0为false,不能返回0 + */ + @Override + protected int getLoadEndViewId() { + return 0; + } +} \ No newline at end of file diff --git a/BaseLibrary/src/main/java/com/tfq/library/utils/DateUtils.java b/BaseLibrary/src/main/java/com/tfq/library/utils/DateUtils.java new file mode 100644 index 0000000..161975d --- /dev/null +++ b/BaseLibrary/src/main/java/com/tfq/library/utils/DateUtils.java @@ -0,0 +1,79 @@ +package com.tfq.library.utils; + +import java.text.SimpleDateFormat; +import java.util.Date; + +public class DateUtils { + /** + * @param l_str 精确到秒的字符串 + * @param format 时间戳转换成日期格式字符串 + * @return + */ + public static String timeStamp2Date(String l_str, String format) { + if (l_str == null || l_str.isEmpty() || l_str.equals("null")) { + return ""; + } + if (format == null || format.isEmpty()) { + format = "yyyy-MM-dd HH:mm:ss"; + } + SimpleDateFormat sdf = new SimpleDateFormat(format); + return sdf.format(new Date(Long.valueOf(l_str + "000"))); + } + + /** + * 日期格式字符串转换成时间戳 + * + * @param date_str 字符串日期 + * @param format 如:yyyy-MM-dd HH:mm:ss + * @return + */ + public static String date2TimeStamp(String date_str, String format) { + try { + SimpleDateFormat sdf = new SimpleDateFormat(format); + return String.valueOf(sdf.parse(date_str).getTime() / 1000); + } catch (Exception e) { + e.printStackTrace(); + } + return ""; + } + + /** + * 取得当前时间戳(精确到秒) + * + * @return + */ + public static String timeStamp() { + long time = System.currentTimeMillis(); + String t = String.valueOf(time / 1000); + return t; + } + + public static void main(String[] args) { + String timeStamp = timeStamp(); + System.out.println("timeStamp=" + timeStamp); //运行输出:timeStamp=1470278082 + System.out.println(System.currentTimeMillis());//运行输出:1470278082980 + //该方法的作用是返回当前的计算机时间,时间的表达格式为当前计算机时间和GMT时间(格林威治时间)1970年1月1号0时0分0秒所差的毫秒数 + + String date = timeStamp2Date(timeStamp, "yyyy-MM-dd HH:mm:ss"); + System.out.println("date=" + date);//运行输出:date=2016-08-04 10:34:42 + + String timeStamp2 = date2TimeStamp(date, "yyyy-MM-dd HH:mm:ss"); + System.out.println(timeStamp2); //运行输出:1470278082 + } + + public static String gapMin(Date start_date, Date end_date) { + if (end_date.getTime() > start_date.getTime()) { + long nd = 1000 * 24 * 60 * 60;//每天毫秒数 + long nh = 1000 * 60 * 60;//每小时毫秒数 + long nm = 1000 * 60;//每分钟毫秒数 + long diff = end_date.getTime() - start_date.getTime(); // 获得两个时间的毫秒时间差异 + long day = diff / nd; // 计算差多少天 + long hour = diff % nd / nh; // 计算差多少小时 + long min = diff % nd % nh / nm; // 计算差多少分钟 +// return day + "天" + hour + "小时" + min + "分钟"; + return diff / nm + ""; + } else { + return "-1"; + } + } +} diff --git a/BaseLibrary/src/main/java/com/tfq/library/utils/DecimalInputFilter.java b/BaseLibrary/src/main/java/com/tfq/library/utils/DecimalInputFilter.java new file mode 100644 index 0000000..8a1544c --- /dev/null +++ b/BaseLibrary/src/main/java/com/tfq/library/utils/DecimalInputFilter.java @@ -0,0 +1,27 @@ +package com.tfq.library.utils; + +import android.text.InputFilter; +import android.text.Spanned; + +import java.util.regex.Pattern; + +public class DecimalInputFilter implements InputFilter { + private final Pattern decimalPattern = Pattern.compile("^-?\\d*\\.?\\d{0,2}$"); + + @Override + public CharSequence filter(CharSequence source, int start, int end, + Spanned dest, int dstart, int dend) { + String newText = dest.toString().substring(0, dstart) + + source.toString() + + dest.toString().substring(dend); + + // 允许空输入或单个负号 + if (newText.isEmpty() || newText.equals("-")) return null; + + // 验证格式并限制小数点后两位 + if (!decimalPattern.matcher(newText).matches()) { + return ""; + } + return null; + } +} diff --git a/BaseLibrary/src/main/java/com/tfq/library/utils/DialogUtils.java b/BaseLibrary/src/main/java/com/tfq/library/utils/DialogUtils.java new file mode 100644 index 0000000..ffdaefc --- /dev/null +++ b/BaseLibrary/src/main/java/com/tfq/library/utils/DialogUtils.java @@ -0,0 +1,89 @@ +package com.tfq.library.utils; + +import android.app.Dialog; +import android.content.Context; +import android.content.res.Configuration; +import android.graphics.Color; +import android.graphics.drawable.GradientDrawable; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; + +import androidx.annotation.NonNull; +import androidx.core.content.ContextCompat; + +import com.tfq.library.R; + +/** + * 对话框工具类 + */ +public class DialogUtils { + + /** + * 为对话框设置模糊背景 + * @param dialog 要设置的对话框 + * @return 返回添加的模糊背景视图,用于后续移除 + */ + public static View setBlurBackground(@NonNull Dialog dialog) { + if (dialog.getOwnerActivity() == null) { + return null; + } + + // 获取根视图 + ViewGroup rootView = (ViewGroup) dialog.getOwnerActivity().getWindow().getDecorView(); + + // 加载模糊背景 + View blurContainer = LayoutInflater.from(dialog.getContext()) + .inflate(R.layout.blur_background, rootView, false); + rootView.addView(blurContainer); + + // 创建圆角背景 + if (dialog.getWindow() != null) { + // 创建圆角矩形 + GradientDrawable shape = new GradientDrawable(); + shape.setShape(GradientDrawable.RECTANGLE); + + // 使用colors.xml中定义的颜色 +// int backgroundColor = ContextCompat.getColor(dialog.getContext(), Color.parseColor("#FFFFFFFF")); + int backgroundColor = Color.parseColor("#FFFFFFFF"); + + shape.setColor(backgroundColor); + + shape.setCornerRadius(25f); // 设置圆角半径 + + // 应用到对话框窗口 + dialog.getWindow().setBackgroundDrawable(shape); + + // 确保窗口外部是透明的 + dialog.getWindow().setDimAmount(0.5f); + } + + // 设置对话框消失监听,自动移除模糊背景 + dialog.setOnDismissListener(dialogInterface -> rootView.removeView(blurContainer)); + + return blurContainer; + } + + /** + * 检查当前是否处于暗黑模式 + * @param context 上下文 + * @return 是否为暗黑模式 + */ + private static boolean isDarkModeEnabled(Context context) { + int nightModeFlags = context.getResources().getConfiguration().uiMode & + Configuration.UI_MODE_NIGHT_MASK; + return nightModeFlags == Configuration.UI_MODE_NIGHT_YES; + } + + /** + * 移除模糊背景 + * @param dialog 对话框 + * @param blurView 之前添加的模糊背景视图 + */ + public static void removeBlurBackground(@NonNull Dialog dialog, View blurView) { + if (dialog.getOwnerActivity() != null && blurView != null) { + ViewGroup rootView = (ViewGroup) dialog.getOwnerActivity().getWindow().getDecorView(); + rootView.removeView(blurView); + } + } +} \ No newline at end of file diff --git a/BaseLibrary/src/main/java/com/tfq/library/utils/FileUtil.java b/BaseLibrary/src/main/java/com/tfq/library/utils/FileUtil.java new file mode 100644 index 0000000..a2e2618 --- /dev/null +++ b/BaseLibrary/src/main/java/com/tfq/library/utils/FileUtil.java @@ -0,0 +1,1160 @@ +package com.tfq.library.utils; + +import android.annotation.SuppressLint; +import android.content.Context; +import android.content.Intent; +import android.database.Cursor; +import android.graphics.Bitmap; +import android.graphics.BitmapFactory; +import android.graphics.Canvas; +import android.graphics.Matrix; +import android.graphics.Paint; +import android.graphics.PorterDuff; +import android.graphics.PorterDuffXfermode; +import android.media.ExifInterface; +import android.media.MediaExtractor; +import android.media.MediaFormat; +import android.net.Uri; +import android.os.Build; +import android.os.Environment; +import android.provider.OpenableColumns; +import android.text.TextUtils; +import android.util.Log; + +import androidx.core.content.FileProvider; + +import com.tfq.library.app.LibraryApp; +import com.tfq.library.utils.AppUtil; +import com.tfq.library.utils.CloseUtils; +import com.tfq.library.utils.LogK; +import com.tfq.library.utils.SDUtils; + +import java.io.BufferedReader; +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.FileFilter; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.OutputStream; +import java.nio.charset.Charset; +import java.nio.file.Files; +import java.text.DecimalFormat; +import java.text.SimpleDateFormat; +import java.util.ArrayList; +import java.util.Calendar; +import java.util.HashMap; +import java.util.List; +import java.util.zip.ZipEntry; +import java.util.zip.ZipInputStream; + +public class FileUtil { + private static final int EOF = -1; + private static final int DEFAULT_BUFFER_SIZE = 1024 * 4; + private static final String LINE_SEP = System.getProperty("line.separator"); + private static final String PHOTO_FILE_NAME = "PMSManagerPhoto"; + private static final String[][] MIME_MapTable = { + //{后缀名, MIME类型} + {".3gp", "video/3gpp"}, + {".apk", "application/vnd.android.package-archive"}, + {".asf", "video/x-ms-asf"}, + {".avi", "video/x-msvideo"}, + {".bin", "application/octet-stream"}, + {".bmp", "image/bmp"}, + {".c", "text/plain"}, + {".class", "application/octet-stream"}, + {".conf", "text/plain"}, + {".cpp", "text/plain"}, + {".doc", "application/msword"}, + {".docx", "application/vnd.openxmlformats-officedocument.wordprocessingml.document"}, + {".xls", "application/vnd.ms-excel"}, + {".xlsx", "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"}, + {".exe", "application/octet-stream"}, + {".gif", "image/gif"}, + {".gtar", "application/x-gtar"}, + {".gz", "application/x-gzip"}, + {".h", "text/plain"}, + {".htm", "text/html"}, + {".html", "text/html"}, + {".jar", "application/java-archive"}, + {".java", "text/plain"}, + {".jpeg", "image/jpeg"}, + {".jpg", "image/jpeg"}, + {".js", "application/x-javascript"}, + {".log", "text/plain"}, + {".m3u", "audio/x-mpegurl"}, + {".m4a", "audio/mp4a-latm"}, + {".m4b", "audio/mp4a-latm"}, + {".m4p", "audio/mp4a-latm"}, + {".m4u", "video/vnd.mpegurl"}, + {".m4v", "video/x-m4v"}, + {".mov", "video/quicktime"}, + {".mp2", "audio/x-mpeg"}, + {".mp3", "audio/x-mpeg"}, + {".mp4", "video/mp4"}, + {".mpc", "application/vnd.mpohun.certificate"}, + {".mpe", "video/mpeg"}, + {".mpeg", "video/mpeg"}, + {".mpg", "video/mpeg"}, + {".mpg4", "video/mp4"}, + {".mpga", "audio/mpeg"}, + {".msg", "application/vnd.ms-outlook"}, + {".ogg", "audio/ogg"}, + {".pdf", "application/pdf"}, + {".png", "image/png"}, + {".pps", "application/vnd.ms-powerpoint"}, + {".ppt", "application/vnd.ms-powerpoint"}, + {".pptx", "application/vnd.openxmlformats-officedocument.presentationml.presentation"}, + {".prop", "text/plain"}, + {".rc", "text/plain"}, + {".rmvb", "audio/x-pn-realaudio"}, + {".rtf", "application/rtf"}, + {".sh", "text/plain"}, + {".tar", "application/x-tar"}, + {".tgz", "application/x-compressed"}, + {".txt", "text/plain"}, + {".wav", "audio/x-wav"}, + {".wma", "audio/x-ms-wma"}, + {".wmv", "audio/x-ms-wmv"}, + {".wps", "application/vnd.ms-works"}, + {".xml", "text/plain"}, + {".z", "application/x-compress"}, + {".zip", "application/x-zip-compressed"}, + {"", "*/*"} + }; + + private FileUtil() { + + } + + public static File from(Context context, Uri uri) throws IOException { + InputStream inputStream = context.getContentResolver().openInputStream(uri); + String fileName = getFileName(context, uri); + String[] splitName = splitFileName(fileName); + File tempFile = File.createTempFile(splitName[0], splitName[1]); + tempFile = rename(tempFile, fileName); + tempFile.deleteOnExit(); + FileOutputStream out = null; + try { + out = new FileOutputStream(tempFile); + } catch (FileNotFoundException e) { + e.printStackTrace(); + } + if (inputStream != null) { + copy(inputStream, out); + inputStream.close(); + } + + if (out != null) { + out.close(); + } + return tempFile; + } + + private static String[] splitFileName(String fileName) { + String name = fileName; + String extension = ""; + int i = fileName.lastIndexOf("."); + if (i != -1) { + name = fileName.substring(0, i); + extension = fileName.substring(i); + } + + return new String[]{name, extension}; + } + + @SuppressLint("Range") + private static String getFileName(Context context, Uri uri) { + String result = null; + if (uri.getScheme().equals("content")) { + Cursor cursor = context.getContentResolver().query(uri, null, null, null, null); + try { + if (cursor != null && cursor.moveToFirst()) { + result = cursor.getString(cursor.getColumnIndex(OpenableColumns.DISPLAY_NAME)); + } + } catch (Exception e) { + e.printStackTrace(); + } finally { + if (cursor != null) { + cursor.close(); + } + } + } + if (result == null) { + result = uri.getPath(); + int cut = result.lastIndexOf(File.separator); + if (cut != -1) { + result = result.substring(cut + 1); + } + } + return result; + } + + public static File rename(File file, String newName) { + File newFile = new File(file.getParent(), newName); + if (!newFile.equals(file)) { + if (newFile.exists() && newFile.delete()) { + Log.d("FileUtil", "Delete old " + newName + " file"); + } + if (file.renameTo(newFile)) { + Log.d("FileUtil", "Rename file to " + newName); + } + } + return newFile; + } + + /** + * 获取本App自定义文件夹下的路径 + * + * @param mContext + * @param filePKGName + * @return + */ + public static String getMyCustomAppPath(Context mContext, String filePKGName) { + String sdCardPath = SDUtils.getSDCardPath(); + String appName = AppUtil.getAppName(mContext); + String dirPath = sdCardPath + "/_" + appName + "/" + filePKGName; + File dir = new File(dirPath); + if (!dir.exists()) { + //创建目录 + dir.mkdirs(); + } + return dirPath; + } + + /** + * 获取本App自定义文件夹下的路径 + * + * @param mContext + * @return + */ + public static String getMyAppPath(Context mContext) { + String sdCardPath = SDUtils.getSDCardPath(); + String appName = AppUtil.getAppName(mContext); + String dirPath = sdCardPath + "/_" + appName; + File dir = new File(dirPath); + if (!dir.exists()) { + //创建目录 + dir.mkdirs(); + } + return dirPath; + } + + /** + * 得到文件名称列表 + * + * @param path 路径 + * @return {@link List}<{@link String}> + */ + public static List getFileNames(String path) { + File file = new File(path); + if (!file.exists()) { + return null; + } + List fileNames = new ArrayList<>(); + return getFileNames(file, fileNames, false); + } + + /** + * 得到文件名称 + * + * @param file 文件 + * @param fileNames 文件名 + * @return {@link List}<{@link String}> + */ + private static List getFileNames(File file, List fileNames, boolean deepDir) { + File[] files = file.listFiles(); + for (File f : files) { + if (f.isDirectory()) { + if (deepDir) { + getFileNames(f, fileNames, deepDir); + } + } else { + fileNames.add(f.getName()); + } + } + return fileNames; + } + + /** + * 创建文件夹 + * + * @param mContext + * @param filePKGName 文件夹名称 + * @return + */ + public static boolean createFliePKG(Context mContext, String filePKGName) { + boolean b = false; + String sdCardPath = SDUtils.getSDCardPath(); + String appName = AppUtil.getAppName(mContext); + String dirPath = sdCardPath + "/_" + appName + "/" + filePKGName; + File dir = new File(dirPath); + if (!dir.exists()) { + //创建目录 + boolean mkdirs = dir.mkdirs(); + if (mkdirs) { + b = true; + } + } + return b; + } + + private static long copy(InputStream input, OutputStream output) throws IOException { + long count = 0; + int n; + byte[] buffer = new byte[DEFAULT_BUFFER_SIZE]; + while (EOF != (n = input.read(buffer))) { + output.write(buffer, 0, n); + count += n; + } + return count; + } + + /** + * 创建密码文件 + * + * @param mContext + * @param text + */ + public static void createPwdFile(Context mContext, String text) { + try { + String sdCardPath = SDUtils.getSDCardPath(); + String appName = AppUtil.getAppName(mContext); + String s_text = appName + "\r" + AppUtil.getAppVersionName(mContext) + "\r" + "_" + text; + String dirPath = sdCardPath + "/_" + appName; + File fs = new File(dirPath); + if (!fs.exists()) { + //创建目录 + fs.mkdirs(); + } + File file = new File(sdCardPath + "/_" + appName + "/" + "pwd" + ""); + FileOutputStream outputStream = new FileOutputStream(file); + outputStream.write(s_text.getBytes()); + outputStream.flush(); + outputStream.close(); + } catch (FileNotFoundException e) { + e.printStackTrace(); + } catch (IOException e) { + e.printStackTrace(); + } catch (Exception e) { + e.printStackTrace(); + } + } + + /** + * 获取本地存储密码 + * + * @param mContext + * @return + */ + public static String getFilePwd(Context mContext) { + String pwd = ""; + String s_pwd = ""; + try { + String sdCardPath = SDUtils.getSDCardPath(); + String appName = AppUtil.getAppName(mContext); + File file = new File(sdCardPath + "/_" + appName + "/" + "pwd" + ""); + if (file.exists()) { + InputStream inputStream = new FileInputStream(file); + byte[] bytes = new byte[1024]; + int n = 0; + while ((n = inputStream.read(bytes)) != -1) { + pwd = new String(bytes, 0, n); + } + inputStream.close(); + } + } catch (FileNotFoundException e) { + e.printStackTrace(); + } catch (IOException e) { + e.printStackTrace(); + } + if (!TextUtils.isEmpty(pwd)) { + int lastIndexOf = pwd.lastIndexOf("_"); + s_pwd = pwd.substring(lastIndexOf + 1); + } + return s_pwd; + } + + /** + * 移动文件或文件夹 + * + * @param source 可以是文件也可以是文件夹 + * @param target 必须是文件夹 + * @param existJump 目标文件已经存在时,true表示跳过,false表示文件重命名(文件名加数字递增的方式)移动 + * @param orMove ture表示是移动过来,false表示移动回原来目录 + * @return 返回所有移动成功后的目标文件路径 + */ + public static List moveData(Context mContext, File source, File target, boolean existJump, boolean orMove) { + if (source == null || target == null) { + return null; + } + + if (!target.exists()) { + target.mkdirs(); + } else { + if (!target.isDirectory()) { + throw new IllegalArgumentException("target must is directory!!!"); + } + } + + String sourceS = source.getPath(); + String targetS = target.getPath(); + String[] paths; + if (source.isDirectory()) { + paths = source.list(); + } else { + sourceS = source.getParent(); + paths = new String[]{source.getName()}; + } + + List successList = new ArrayList<>(); + + File newFile = null; + for (String tmp : paths) { + File tmpFile = new File(sourceS + File.separator + tmp); + newFile = new File(targetS + File.separator + tmp); + if (orMove && !tmp.endsWith("tfq")) { + newFile = new File(targetS + File.separator + tmp + "tfq"); + } else if (!orMove && tmp.endsWith("tfq")) { + String s_tmp = tmp.substring(0, tmp.length() - 3); + newFile = new File(targetS + File.separator + s_tmp); + } + if (tmpFile.isDirectory()) { + List middleList = moveData(mContext, tmpFile, newFile, existJump, orMove); + if (!middleList.isEmpty()) + successList.addAll(middleList); + } else { + if (newFile.exists()) { + //不跳过 + if (!existJump) { + // 递增文件名 + for (int i = 1; ; i++) { + String[] arr = tmp.split("\\."); + String tmp2 = arr[0] + "(" + i + ")"; + if (arr.length > 1) { + tmp2 += "." + arr[1]; + } + newFile = new File(target, tmp2); + if (!newFile.exists()) + break; + } + + String successPath = moveFileCompat(tmpFile, newFile); + if (successPath != null && successPath.length() != 0) { + successList.add(successPath); + } + } + } else { + String successPath = moveFileCompat(tmpFile, newFile); + if (successPath != null && successPath.length() != 0) { + successList.add(successPath); + } + } + } + } + + if (source.isDirectory() && (source.list() == null || source.list().length == 0)) { + source.delete(); + } + if (orMove) { + sendBroadcastFile(mContext, source); + } else if (newFile != null) { + sendBroadcastFile(mContext, target); + } + + return successList; + } + + /** + * 删除指定目录下的文件及目录 + */ + public static void deleteFolderFile(Context mContext, String filePath, boolean deleteThisPath) { + if (!TextUtils.isEmpty(filePath)) { + File file = new File(filePath); + if (file.isDirectory()) { + File[] files = file.listFiles(); + for (int i = 0; i < files.length; i++) { + deleteFolderFile(mContext, files[i].getAbsolutePath(), true); + } + } + if (deleteThisPath) { + if (!file.isDirectory()) { + file.delete(); + } else { + if (file.listFiles().length == 0) { + file.delete(); + } + } + } + sendBroadcastFile(mContext, file); + } + } + + /** + * 通知图库更新 + * + * @param mContext 上下文 + * @param source 文件uri + */ + public static void sendBroadcastFile(Context mContext, File source) { + // 通知图库更新 + mContext.sendBroadcast(new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE, Uri.fromFile(source))); + } + + private static String moveFileCompat(File oldFile, File newFile) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + try { + Files.move(oldFile.toPath(), newFile.toPath()); + return newFile.getPath(); + } catch (IOException e) { + e.printStackTrace(); + } + } else { + boolean isCopySuccess = oldFile.renameTo(newFile); + if (isCopySuccess) { + return newFile.getPath(); + } + } + return null; + } + + /** + * 获取指定文件夹下所有文件,不含文件夹里的文件 + * + * @param dirFile 文件夹 + * @return + */ + public static List getAllFile(File dirFile) { + // 如果文件夹不存在或着不是文件夹,则返回 null + if (dirFile == null || !dirFile.exists() || dirFile.isFile()) + return null; + + File[] childrenFiles = dirFile.listFiles(); + if (childrenFiles == null || childrenFiles.length == 0) + return null; + + List files = new ArrayList<>(); + for (File childFile : childrenFiles) { + // 如果是文件,直接添加到结果集合 + if (childFile.isFile()) { + files.add(childFile); + } + //以下几行代码取消注释后可以将所有子文件夹里的文件也获取到列表里 +// else { +// // 如果是文件夹,则将其内部文件添加进结果集合 +// List cFiles = getAllFile(childFile); +// if (Objects.isNull(cFiles) || cFiles.isEmpty()) continue; +// files.addAll(cFiles); +// } + } + return files; + } + + /** + * 质量压缩 + * + * @param format 图片格式 jpeg,png,webp + * @param quality 图片的质量,0-100,数值越小质量越差 + */ + public static void compress(Bitmap.CompressFormat format, int quality) { + File sdFile = Environment.getExternalStorageDirectory(); + File originFile = new File(sdFile, "originImg.jpg"); + Bitmap originBitmap = BitmapFactory.decodeFile(originFile.getAbsolutePath()); + ByteArrayOutputStream bos = new ByteArrayOutputStream(); + originBitmap.compress(format, quality, bos); + try { + FileOutputStream fos = new FileOutputStream(new File(sdFile, "resultImg.jpg")); + fos.write(bos.toByteArray()); + fos.flush(); + fos.close(); + } catch (FileNotFoundException e) { + e.printStackTrace(); + } catch (IOException e) { + e.printStackTrace(); + } + } + + /** + * 获取图片的旋转角度 + * + * @param filePath + * @return + */ + public static int getRotateAngle(String filePath) { + int rotate_angle = 0; + try { + ExifInterface exifInterface = new ExifInterface(filePath); + int orientation = exifInterface.getAttributeInt(ExifInterface.TAG_ORIENTATION, ExifInterface.ORIENTATION_NORMAL); + switch (orientation) { + case ExifInterface.ORIENTATION_ROTATE_90: + rotate_angle = 90; + break; + case ExifInterface.ORIENTATION_ROTATE_180: + rotate_angle = 180; + break; + case ExifInterface.ORIENTATION_ROTATE_270: + rotate_angle = 270; + break; + } + } catch (IOException e) { + e.printStackTrace(); + } + return rotate_angle; + } + + /** + * 旋转图片角度 + * + * @param angle + * @param bitmap + * @return + */ + public static Bitmap setRotateAngle(int angle, Bitmap bitmap) { + try { + bitmap.getWidth(); + } catch (Exception e) { + e.printStackTrace(); + return bitmap; + } + if (bitmap != null) { + Matrix m = new Matrix(); + m.postRotate(angle); + bitmap = Bitmap.createBitmap(bitmap, 0, 0, bitmap.getWidth(), + bitmap.getHeight(), m, true); + return bitmap; + } + return bitmap; + + } + + //转换为圆形状的bitmap + public static Bitmap createCircleImage(Bitmap source) { + int length = source.getWidth() < source.getHeight() ? source.getWidth() : source.getHeight(); + Paint paint = new Paint(); + paint.setAntiAlias(true); + Bitmap target = Bitmap.createBitmap(length, length, Bitmap.Config.ARGB_8888); + Canvas canvas = new Canvas(target); + canvas.drawCircle(length / 2, length / 2, length / 2, paint); + paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC_IN)); + canvas.drawBitmap(source, 0, 0, paint); + return target; + } + + /** + * 图片压缩-质量压缩 + * + * @param filePath 源图片路径 + * @return 压缩后的路径 + */ + + public static String compressImage(String filePath) { + //原文件 + File oldFile = new File(filePath); + String name = oldFile.getName(); + //压缩文件路径 照片路径/ + LibraryApp.getContext().getExternalFilesDir(Environment.DIRECTORY_PICTURES); + File externalCacheDir = LibraryApp.getContext().getExternalCacheDir(); +// String targetPath = oldFile.getPath()+name+"2.jpg"; + String targetPath = externalCacheDir + name; + int quality = 100;//压缩比例0-100 + Bitmap bm = getSmallBitmap(filePath);//获取一定尺寸的图片 + int degree = getRotateAngle(filePath);//获取相片拍摄角度 + + if (degree != 0) {//旋转照片角度,防止头像横着显示 + bm = setRotateAngle(degree, bm); + } + File outputFile = new File(targetPath); + try { + if (!outputFile.exists()) { + outputFile.getParentFile().mkdirs(); + //outputFile.createNewFile(); + } else { + outputFile.delete(); + } + FileOutputStream out = new FileOutputStream(outputFile); + bm.compress(Bitmap.CompressFormat.JPEG, quality, out); + out.close(); + } catch (Exception e) { + e.printStackTrace(); + return filePath; + } + return outputFile.getPath(); + } + + /** + * 根据路径获得图片信息并按比例压缩,返回bitmap + */ + public static Bitmap getSmallBitmap(String filePath) { + final BitmapFactory.Options options = new BitmapFactory.Options(); + options.inJustDecodeBounds = true;//只解析图片边沿,获取宽高 + BitmapFactory.decodeFile(filePath, options); + // 计算缩放比 + options.inSampleSize = calculateInSampleSize(options, 480, 800); + // 完整解析图片返回bitmap + options.inJustDecodeBounds = false; + return BitmapFactory.decodeFile(filePath, options); + } + + public static int calculateInSampleSize(BitmapFactory.Options options, + int reqWidth, int reqHeight) { + final int height = options.outHeight; + final int width = options.outWidth; + int inSampleSize = 1; + if (height > reqHeight || width > reqWidth) { + final int heightRatio = Math.round((float) height / (float) reqHeight); + final int widthRatio = Math.round((float) width / (float) reqWidth); + inSampleSize = heightRatio < widthRatio ? heightRatio : widthRatio; + } + return inSampleSize; + } + + /** + * 获取图片类型 + * + * @param filePath + * @return + */ + + public static String getFileType(String filePath) { + HashMap mFileTypes = new HashMap(); + mFileTypes.put("FFD8FF", "jpg"); + mFileTypes.put("89504E47", "png"); + mFileTypes.put("47494638", "gif"); + mFileTypes.put("49492A00", "tif"); + mFileTypes.put("424D", "bmp"); + return (String) mFileTypes.get(getFileHeader(filePath)); + } + + /** + * 获取文件头信息 + * + * @param filePath + * @return + */ + public static String getFileHeader(String filePath) { + FileInputStream is = null; + String value = null; + try { + is = new FileInputStream(filePath); + byte[] b = new byte[3]; + is.read(b, 0, b.length); + value = bytesToHexString(b); + } catch (Exception e) { + + } finally { + if (null != is) { + try { + is.close(); + } catch (IOException e) { + + } + } + } + return value; + } + + /** + * 将byte字节转换为十六进制字符串 + * + * @param src + * @return + */ + private static String bytesToHexString(byte[] src) { + StringBuilder builder = new StringBuilder(); + if (src == null || src.length <= 0) { + return null; + } + String hv; + for (int i = 0; i < src.length; i++) { + hv = Integer.toHexString(src[i] & 0xFF).toUpperCase(); + if (hv.length() < 2) { + builder.append(0); + } + builder.append(hv); + } + return builder.toString(); + } + + /** + * 格式化大小 + */ + public static String stringForLength(File file, float f) { + DecimalFormat decimalFormat = new DecimalFormat("###.00"); + String s_result = ""; + long length = file.length(); + if (length > 1000 * 1000) { + float v = length / 1000 / 1000 * f; + s_result = decimalFormat.format(Float.parseFloat(length + "") / 1000 / 1000 * f) + "M"; + } else if (length > 1000) { + float v = length / 1000 * f; + s_result = decimalFormat.format(Float.parseFloat(length + "") / 1000 * f) + "KB"; + } + return s_result; + } + + /** + * 读取文件到字符串中 + * + * @param filePath 文件路径 + * @return 字符串 + */ + public static String readFile2String(final String filePath) { + return readFile2String(getFileByPath(filePath), null); + } + + /** + * 读取文件到字符串中 + * + * @param filePath 文件路径 + * @param charsetName 编码格式 + * @return 字符串 + */ + public static String readFile2String(final String filePath, final String charsetName) { + return readFile2String(getFileByPath(filePath), charsetName); + } + + private static File getFileByPath(final String filePath) { + return isSpace(filePath) ? null : new File(filePath); + } + + /** + * 读取文件到字符串中 + * + * @param file 文件 + * @return 字符串 + */ + public static String readFile2String(final File file) { + return readFile2String(file, null); + } + + /** + * 读取文件到字符串中 + * + * @param file 文件 + * @param charsetName 编码格式 + * @return 字符串 + */ + public static String readFile2String(final File file, final String charsetName) { + if (!isFileExists(file)) return null; + BufferedReader reader = null; + try { + StringBuilder sb = new StringBuilder(); + if (isSpace(charsetName)) { + reader = new BufferedReader(new InputStreamReader(new FileInputStream(file))); + } else { + reader = new BufferedReader( + new InputStreamReader(new FileInputStream(file), charsetName) + ); + } + String line; + if ((line = reader.readLine()) != null) { + sb.append(line); + while ((line = reader.readLine()) != null) { + sb.append(LINE_SEP).append(line); + } + } + return sb.toString(); + } catch (IOException e) { + e.printStackTrace(); + return null; + } finally { + CloseUtils.closeIO(reader); + } + } + + private static boolean isFileExists(final File file) { + return file != null && file.exists(); + } + + private static boolean isSpace(final String s) { + if (s == null) return true; + for (int i = 0, len = s.length(); i < len; ++i) { + if (!Character.isWhitespace(s.charAt(i))) { + return false; + } + } + return true; + } + + /** + * 获取本App自定义文件夹下的路径 + * + * @param mContext + * @return + */ + public static String getAppNamePath(Context mContext) { + String sdCardPath = SDUtils.getSDCardPath(); + String appName = AppUtil.getAppName(mContext); + String dirPath = sdCardPath + "/_" + appName; + File dir = new File(dirPath); + if (!dir.exists()) { + //创建目录 + dir.mkdirs(); + } + return dirPath; + } + + /** + * 获取本App自定义文件夹下的路径 + * + * @param mContext + * @param filePKGName + * @return + */ + public static String getAppNamePath(Context mContext, String filePKGName) { + String sdCardPath = SDUtils.getSDCardPath(); + String appName = AppUtil.getAppName(mContext); + String dirPath = sdCardPath + "/_" + appName + "/" + filePKGName; + if (TextUtils.isEmpty(filePKGName)) { + dirPath = sdCardPath + "/_" + appName; + } + File dir = new File(dirPath); + if (!dir.exists()) { + //创建目录 + dir.mkdirs(); + } + return dirPath; + } + + public static boolean isNoAudioTrack(String filePath) { + //检测是否有无音轨视频 + boolean isNoAudioTrack = false; + MediaExtractor mediaExtractor = new MediaExtractor(); + try { + mediaExtractor.setDataSource(filePath); + } catch (IOException e) { + e.printStackTrace(); + } + int at = selectAudioTrack(mediaExtractor); + if (at == -1) { + isNoAudioTrack = true; + mediaExtractor.release(); + } + mediaExtractor.release(); + + return isNoAudioTrack; + } + + /** + * 查找音频轨道 + * + * @param extractor + * @return + */ + public static int selectAudioTrack(MediaExtractor extractor) { + int numTracks = extractor.getTrackCount(); + for (int i = 0; i < numTracks; i++) { + MediaFormat format = extractor.getTrackFormat(i); + String mime = format.getString(MediaFormat.KEY_MIME); + if (mime.startsWith("audio/")) { + LogK.e("Extractor selected track " + i + " (" + mime + "): " + format); + return i; + } + } + return -1; + } + + /** + * 过滤掉手机中的隐藏文件 + */ + public static File[] fileFilter(File file) { + File[] files = file.listFiles(new FileFilter() { + @Override + public boolean accept(File pathname) { + return true; + } + }); + + return files; + } + + /** + * 读取文件的最后修改时间的方法 + */ + public static String getFileLastModifiedTime(long lastModified) { + Calendar cal = Calendar.getInstance(); + long time = lastModified * 1; + SimpleDateFormat formatter = new + SimpleDateFormat("yyyy-MM-dd"); + cal.setTimeInMillis(time); + return formatter.format(cal.getTime()); + } + + public static String FormetFileSize(long fileS) { + DecimalFormat df = new DecimalFormat("#.0"); + String fileSizeString = ""; + String wrongSize = "0B"; + if (fileS == 0) { + return wrongSize; + } + if (fileS < 1024) { + fileSizeString = df.format((double) fileS) + "B"; + } else if (fileS < 1048576) { + fileSizeString = df.format((double) fileS / 1024) + "KB"; + } else if (fileS < 1073741824) { + fileSizeString = df.format((double) fileS / 1048576) + "MB"; + } else { + fileSizeString = df.format((double) fileS / 1073741824) + "GB"; + } + return fileSizeString; + } + + /** + * 打开文件 + * + * @param filePath + */ + public static void openFile(Context mActivity, String filePath) { + File file = new File(filePath); + Intent intent = new Intent(); + intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + //设置intent的Action属性 + intent.setAction(Intent.ACTION_VIEW); + //获取文件file的MIME类型 + String type = getMIMEType(file); + LogK.e("type=" + type); + //设置intent的data和Type属性。 +// intent.setDataAndType(/*uri*/Uri.fromFile(file), type); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { + intent.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); + Uri uri = FileProvider.getUriForFile(mActivity, mActivity.getPackageName() + ".provider", file); + intent.setDataAndType(uri, type); + } else { + intent.setDataAndType(Uri.fromFile(file), type); + } + //跳转 + mActivity.startActivity(intent); + } + + /** + * 根据文件后缀名获得对应的MIME类型。 + * + * @param file + */ + private static String getMIMEType(File file) { + String type = "*/*"; + String fName = file.getName(); + //获取后缀名前的分隔符"."在fName中的位置。 + int dotIndex = fName.lastIndexOf("."); + if (dotIndex < 0) { + return type; + } + /* 获取文件的后缀名 */ + String end = fName.substring(dotIndex, fName.length()).toLowerCase(); + if (end == "") return type; + //在MIME和文件类型的匹配表中找到对应的MIME类型。 + for (int i = 0; i < MIME_MapTable.length; i++) { //MIME_MapTable??在这里你一定有疑问,这个MIME_MapTable是什么? + if (end.equals(MIME_MapTable[i][0])) + type = MIME_MapTable[i][1]; + } + return type; + } + + /** + * 打开下载好的apk应用 + * + * @param file + */ + public static void openAPKFile(Context context, File file) { + Intent intent = new Intent(Intent.ACTION_VIEW); + if (Build.VERSION.SDK_INT >= 24) { + intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); + Uri contentUri = FileProvider.getUriForFile(context, context.getPackageName() + ".provider", file); + intent.setDataAndType(contentUri, "application/vnd.android.package-archive"); + } else { + intent.setDataAndType(Uri.fromFile(file), "application/vnd.android.package-archive"); + intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + } + if (context.getPackageManager().queryIntentActivities(intent, 0).size() > 0) { + context.startActivity(intent); + } + } + + /** + * 返回不带后缀的文件名称 + * + * @param file + * @return + */ + public static String getSimpleName(File file) { + String toFileName = file.getName(); + if (toFileName.contains(".")) { + int lastIndexOf = toFileName.lastIndexOf("."); + String simpleName = toFileName.substring(0, lastIndexOf); + return simpleName; + } else { + return toFileName; + } + } + + /** + * 返回后缀 + * + * @param file + * @return + */ + public static String getSuffix(File file) { + String toFileName = file.getName(); + if (toFileName.contains(".")) { + int lastIndexOf = toFileName.lastIndexOf("."); + String suffix = toFileName.substring(lastIndexOf); + return suffix; + } else { + return ""; + } + } + + + public static void zipToFile(Context mContext, String zipPath, boolean equalsFileDelete) { + try { + if (equalsFileDelete) { + String filePath0 = zipPath.substring(0, zipPath.indexOf(".")); + File file0 = new File(filePath0); + if (file0.exists()) { + file0.delete(); + deleteFolderFile(mContext, filePath0, true); + } + } + InputStream fileImputStream = new FileInputStream(zipPath); + String encoding = System.getProperty("file.encoding"); + ZipInputStream zipInputStream = null; + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { + zipInputStream = new ZipInputStream(fileImputStream, Charset.forName(encoding)); + } + ZipEntry zipentry = null; + while ((zipentry = zipInputStream.getNextEntry()) != null) { + String filePath = zipPath.substring(0, zipPath.indexOf(".")) + "/" + zipentry.getName(); + File file = new File(filePath); + if (zipentry.isDirectory()) { + if (!file.exists()) { + file.mkdirs(); + } else { + return; + } + } else { + if (!file.exists()) { + File parentFile = file.getParentFile(); + if (!parentFile.exists()) { + parentFile.mkdirs(); + } + FileOutputStream fileOutputStream = new FileOutputStream(filePath); + byte[] bytes = new byte[1024]; + int length = -1; + while ((length = zipInputStream.read(bytes)) != -1) { + fileOutputStream.write(bytes, 0, length); + } + } else { + return; + } + } + } + } catch (Exception e) { + e.printStackTrace(); + } + } + +} \ No newline at end of file diff --git a/BaseLibrary/src/main/java/com/tfq/library/utils/GlideLoadUtils.java b/BaseLibrary/src/main/java/com/tfq/library/utils/GlideLoadUtils.java new file mode 100644 index 0000000..b3faebf --- /dev/null +++ b/BaseLibrary/src/main/java/com/tfq/library/utils/GlideLoadUtils.java @@ -0,0 +1,72 @@ +package com.tfq.library.utils; + +import android.content.Context; +import android.widget.ImageView; + +import com.bumptech.glide.Glide; +import com.tfq.library.R; + +public class GlideLoadUtils { + public static void loadResource(Context context, Object url) { + if (url != null && url.toString() != null) { + new BigPhotoDialog(context, url.toString()).show(); + } + } + + public static void loadResource(Context context, Object url, ImageView imageView) { + try { + if (context != null) { + Glide.with(context) + .load(url) + .placeholder(R.mipmap.ic_loading) +// .error(R.mipmap.image_load_error) + .into(imageView); + } + } catch (Exception e) { + e.printStackTrace(); + } + } + + public static void loadResource(Context context, Object url, ImageView imageView, int id) { + try { + if (context != null) { + Glide.with(context) + .load(url) + .placeholder(id) +// .error(R.mipmap.image_load_error) + .into(imageView); + } + } catch (Exception e) { + e.printStackTrace(); + } + } + + public static void loadResource(Context context, Object url, Object error_url, ImageView imageView) { + try { + if (context != null) { + Glide.with(context) + .load(url) + .error(error_url) + .into(imageView); + } + } catch (Exception e) { + e.printStackTrace(); + } + } + + + public static void loadResource(Context context, Object url, ImageView imageView, int id, boolean anim) { + try { + if (context != null) { + Glide.with(context) + .load(url) + .placeholder(id) +// .error(R.mipmap.image_load_error) +// .transition(GenericTransitionOptions.with(R.anim.slide_right_in)) + .into(imageView); + } + } catch (Exception e) { + e.printStackTrace(); + } + } +} diff --git a/BaseLibrary/src/main/java/com/tfq/library/utils/LogK.java b/BaseLibrary/src/main/java/com/tfq/library/utils/LogK.java new file mode 100644 index 0000000..80ee11c --- /dev/null +++ b/BaseLibrary/src/main/java/com/tfq/library/utils/LogK.java @@ -0,0 +1,33 @@ +package com.tfq.library.utils; + +import android.util.Log; + +import com.tfq.library.app.BaseConstants; + +public class LogK { + + public static void d(String msg) { + if (BaseConstants.BASE_APP_DEBUG_PRINT) + Log.d("LogK", "LogK.d http data: " + msg); + } + + public static void e(String msg) { + if (BaseConstants.BASE_APP_DEBUG_PRINT) + Log.e("LogK", "LogK.e http data: " + msg); + } + + public static void i(String msg) { + if (BaseConstants.BASE_APP_DEBUG_PRINT) + Log.i("LogK", "LogK.i http data: " + msg); + } + + public static void w(String msg) { + if (BaseConstants.BASE_APP_DEBUG_PRINT) + Log.w("LogK", "LogK.w http data: " + msg); + } + + public static void v(String msg) { + if (BaseConstants.BASE_APP_DEBUG_PRINT) + Log.v("LogK", "LogK.v http data: " + msg); + } +} \ No newline at end of file diff --git a/BaseLibrary/src/main/java/com/tfq/library/utils/NetworkUtils.java b/BaseLibrary/src/main/java/com/tfq/library/utils/NetworkUtils.java new file mode 100644 index 0000000..8416b53 --- /dev/null +++ b/BaseLibrary/src/main/java/com/tfq/library/utils/NetworkUtils.java @@ -0,0 +1,185 @@ +package com.tfq.library.utils; + +import android.annotation.SuppressLint; +import android.content.Context; +import android.net.ConnectivityManager; +import android.net.Network; +import android.net.NetworkCapabilities; + +@SuppressLint("NewApi") +public class NetworkUtils { + + /** + * Indicates this network uses a Cellular transport. + */ + public static final int TRANSPORT_CELLULAR = 0; + /** + * Indicates this network uses a Wi-Fi transport. + */ + public static final int TRANSPORT_WIFI = 1; + /** + * Indicates this network uses a Bluetooth transport. + */ + public static final int TRANSPORT_BLUETOOTH = 2; + /** + * Indicates this network uses an Ethernet transport. + */ + public static final int TRANSPORT_ETHERNET = 3; + /** + * Indicates this network uses a VPN transport. + */ + public static final int TRANSPORT_VPN = 4; + /** + * Indicates this network uses a Wi-Fi Aware transport. + */ + public static final int TRANSPORT_WIFI_AWARE = 5; + /** + * Indicates this network uses a LoWPAN transport. + */ + public static final int TRANSPORT_LOWPAN = 6; + /** + * Indicates this network uses a Test-only virtual interface as a transport. + * + * @hide + */ + public static final int TRANSPORT_TEST = 7; + /** + * Indicates this network uses a USB transport. + */ + public static final int TRANSPORT_USB = 8; + private static final String TAG = "ConnectManager"; + + /** + * >= Android 10(Q版本)推荐 + *

+ * 当前使用MOBILE流量上网 + */ + public static boolean isMobileNetwork(Context context) { + ConnectivityManager cm = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE); + + Network network = cm.getActiveNetwork(); + if (null == network) { + return false; + } + + NetworkCapabilities capabilities = cm.getNetworkCapabilities(network); + if (null == capabilities) { + return false; + } + return capabilities.hasTransport(NetworkCapabilities.TRANSPORT_CELLULAR) + && capabilities.hasCapability(NetworkCapabilities.NET_CAPABILITY_VALIDATED); + } + + + /** + * >= Android 10(Q版本)推荐 + *

+ * 当前使用WIFI上网 + */ + + public static boolean isWifiNetwork(Context context) { + ConnectivityManager cm = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE); + Network network = cm.getActiveNetwork(); + if (null == network) { + return false; + } + NetworkCapabilities capabilities = cm.getNetworkCapabilities(network); + if (null == capabilities) { + return false; + } + return capabilities.hasTransport(NetworkCapabilities.TRANSPORT_WIFI) + && capabilities.hasCapability(NetworkCapabilities.NET_CAPABILITY_VALIDATED); + } + + + /** + * >= Android 10(Q版本)推荐 + *

+ * 当前使用以太网上网 + */ + public static boolean isEthernetNetwork(Context context) { + ConnectivityManager cm = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE); + Network network = cm.getActiveNetwork(); + if (null == network) { + return false; + } + NetworkCapabilities capabilities = cm.getNetworkCapabilities(network); + if (null == capabilities) { + return false; + } + return capabilities.hasTransport(NetworkCapabilities.TRANSPORT_ETHERNET) + && capabilities.hasCapability(NetworkCapabilities.NET_CAPABILITY_VALIDATED); + } + + + /** + * >= Android 10(Q版本)推荐 + *

+ * NetworkCapabilities.NET_CAPABILITY_INTERNET,表示此网络应该(maybe)能够访问internet + *

+ * 判断当前网络可以正常上网 + * 表示此连接此网络并且能成功上网。 例如,对于具有NET_CAPABILITY_INTERNET的网络,这意味着已成功检测到INTERNET连接。 + */ + public static boolean isConnectedAvailableNetwork(Context context) { + ConnectivityManager cm = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE); + Network network = cm.getActiveNetwork(); + if (null == network) { + return false; + } + NetworkCapabilities capabilities = cm.getNetworkCapabilities(network); + if (null == capabilities) { + return false; + } + return capabilities.hasCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET) + && capabilities.hasCapability(NetworkCapabilities.NET_CAPABILITY_VALIDATED); + } + + + /** + * >= Android 10(Q版本)推荐 + *

+ * 获取成功上网的网络类型 + * value = { + * TRANSPORT_CELLULAR, 0 表示此网络使用蜂窝传输。 + * TRANSPORT_WIFI, 1 表示此网络使用Wi-Fi传输。 + * TRANSPORT_BLUETOOTH, 2 表示此网络使用蓝牙传输。 + * TRANSPORT_ETHERNET, 3 表示此网络使用以太网传输。 + * TRANSPORT_VPN, 4 表示此网络使用VPN传输。 + * TRANSPORT_WIFI_AWARE, 5 表示此网络使用Wi-Fi感知传输。 + * TRANSPORT_LOWPAN, 6 表示此网络使用LoWPAN传输。 + * TRANSPORT_TEST, 7 指示此网络使用仅限测试的虚拟接口作为传输。 + * TRANSPORT_USB, 8 表示此网络使用USB传输 + * } + */ + public static int getConnectedNetworkType(Context context) { + ConnectivityManager cm = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE); + Network network = cm.getActiveNetwork(); + if (null == network) { + return -1; + } + NetworkCapabilities capabilities = cm.getNetworkCapabilities(network); + if (null == capabilities) { + return -1; + } + if (capabilities.hasCapability(NetworkCapabilities.NET_CAPABILITY_VALIDATED)) { + if (capabilities.hasTransport(NetworkCapabilities.TRANSPORT_CELLULAR)) { + return NetworkCapabilities.TRANSPORT_CELLULAR; + } else if (capabilities.hasTransport(NetworkCapabilities.TRANSPORT_WIFI)) { + return NetworkCapabilities.TRANSPORT_WIFI; + } else if (capabilities.hasTransport(NetworkCapabilities.TRANSPORT_BLUETOOTH)) { + return NetworkCapabilities.TRANSPORT_BLUETOOTH; + } else if (capabilities.hasTransport(NetworkCapabilities.TRANSPORT_ETHERNET)) { + return NetworkCapabilities.TRANSPORT_ETHERNET; + } else if (capabilities.hasTransport(NetworkCapabilities.TRANSPORT_VPN)) { + return NetworkCapabilities.TRANSPORT_VPN; + } else if (capabilities.hasTransport(NetworkCapabilities.TRANSPORT_WIFI_AWARE)) { + return NetworkCapabilities.TRANSPORT_WIFI_AWARE; + } else if (capabilities.hasTransport(NetworkCapabilities.TRANSPORT_LOWPAN)) { + return NetworkCapabilities.TRANSPORT_LOWPAN; + } else if (capabilities.hasTransport(NetworkCapabilities.TRANSPORT_USB)) { + return NetworkCapabilities.TRANSPORT_USB; + } + } + return -1; + } +} \ No newline at end of file diff --git a/BaseLibrary/src/main/java/com/tfq/library/utils/PermissionDialog.java b/BaseLibrary/src/main/java/com/tfq/library/utils/PermissionDialog.java new file mode 100644 index 0000000..7bb4fc4 --- /dev/null +++ b/BaseLibrary/src/main/java/com/tfq/library/utils/PermissionDialog.java @@ -0,0 +1,228 @@ +package com.tfq.library.utils; + +import android.app.Dialog; +import android.content.ActivityNotFoundException; +import android.content.Context; +import android.content.Intent; +import android.net.Uri; +import android.os.Bundle; +import android.provider.Settings; +import android.view.Gravity; +import android.view.LayoutInflater; +import android.view.View; +import android.widget.TextView; + +import com.tfq.library.R; + +public class PermissionDialog extends Dialog { + private final Context mContext; + private final LayoutInflater inflater; + private Listener listener; + private Listener_Location listener_location; + private View contentView; + private String type; + private String content; + private String title; + private Listener_Intent listener_intent; + private Listener_Result listener_result; + + public PermissionDialog(Context context, Listener listener) { + super(context, R.style.CustomDialog); + this.listener = listener; + this.mContext = context; + inflater = LayoutInflater.from(context); + } + + public PermissionDialog(Context context, String type, String title, String content, Listener_Location listener_location) { + super(context, R.style.CustomDialog); + this.listener_location = listener_location; + this.mContext = context; + this.type = type; + this.title = title; + this.content = content; + inflater = LayoutInflater.from(context); + } + + public PermissionDialog(Context context, String type) { + super(context, R.style.CustomDialog); + this.mContext = context; + this.type = type; + this.title = title; + this.content = content; + inflater = LayoutInflater.from(context); + } + + public PermissionDialog(Context context, String title, String content, Listener listener) { + super(context, R.style.CustomDialog); + this.mContext = context; + this.title = title; + this.content = content; + this.listener = listener; + inflater = LayoutInflater.from(context); + } + + public PermissionDialog(Context context, String type, String title, String content) { + super(context, R.style.CustomDialog); + this.mContext = context; + this.type = type; + this.title = title; + this.content = content; + inflater = LayoutInflater.from(context); + } + + public PermissionDialog(Context context, String type, String title, String content, Listener_Result listener_result) { + super(context, R.style.CustomDialog); + this.listener_result = listener_result; + this.mContext = context; + this.type = type; + this.title = title; + this.content = content; + inflater = LayoutInflater.from(context); + } + + public PermissionDialog(Context context, String type, String title, String content, Listener listener) { + super(context, R.style.CustomDialog); + this.listener = listener; + this.mContext = context; + this.type = type; + this.title = title; + this.content = content; + inflater = LayoutInflater.from(context); + } + + public PermissionDialog(Context context, String type, String title, String content, Listener listener, Listener_Intent listener_intent) { + super(context, R.style.CustomDialog); + this.listener = listener; + this.listener_intent = listener_intent; + this.mContext = context; + this.type = type; + this.title = title; + this.content = content; + inflater = LayoutInflater.from(context); + } + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setViews(); + } + + private void setViews() { + contentView = inflater.inflate(R.layout.layout_dialog_permission, null); + setContentView(contentView); + TextView tv_title = contentView.findViewById(R.id.tv_title); + TextView tv_content = contentView.findViewById(R.id.tv_content); + TextView tv_left = contentView.findViewById(R.id.tv_left); + TextView tv_right = contentView.findViewById(R.id.tv_right); + if ("cancel_authorization".equals(type)) { + tv_left.setVisibility(View.GONE); + tv_content.setText(content); + tv_title.setText(title); + tv_content.setGravity(Gravity.CENTER); + } else if ("reject_authorization".equals(type)) { + tv_content.setText(content); + tv_title.setText(title); +// tv_right.setImageDrawable(App.getContext().getDrawable(R.mipmap.ic_confirm)); + } else if ("isLocServiceEnable".equals(type)) { + tv_content.setText(content); + tv_title.setText(title); +// tv_right.setImageDrawable(App.getContext().getDrawable(R.mipmap.ic_confirm)); + } else if ("request_per".equals(type)) { + tv_content.setText(content); + tv_title.setText(title); +// tv_right.setImageDrawable(App.getContext().getDrawable(R.mipmap.ic_confirm)); +// this.setCanceledOnTouchOutside(false); +// this.setCancelable(false); + }else if ("del_project".equals(type)) { + tv_content.setText(content); + tv_title.setText(title); + tv_right.setTextColor(mContext.getResources().getColor(R.color.gold)); + }else if ("export_data".equals(type)) { + tv_content.setText(content); + tv_title.setText(title); + tv_right.setTextColor(mContext.getResources().getColor(R.color.gold)); + } else { + tv_content.setText(content); + tv_title.setText(title); + } + + tv_left.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + if ("isLocServiceEnable".equals(type)) { + listener_location.success(false); + } else if ("reject_authorization".equals(type)) { + if (listener != null) { + listener.success(); + } + if (listener_result != null) { + listener_result.success(false); + } + } else if ("request_per".equals(type)) { + if (listener_result != null) { + listener_result.success(false); + } + } + dismiss(); + } + }); + + tv_right.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + if ("reject_authorization".equals(type)) { + if (listener_intent != null) { + listener_intent.success(); + } else { + Intent intent = new Intent(); + intent.setAction("android.settings.APPLICATION_DETAILS_SETTINGS"); + intent.setData(Uri.fromParts("package", mContext.getPackageName(), null)); + mContext.startActivity(intent); + } + } else if ("cancel_authorization".equals(type)) { + + dismiss(); + } else if ("isLocServiceEnable".equals(type)) { + Intent intent = new Intent(); + intent.setAction(Settings.ACTION_LOCATION_SOURCE_SETTINGS); + intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + try { + mContext.startActivity(intent); + } catch (ActivityNotFoundException ex) { + intent.setAction(Settings.ACTION_SETTINGS); + try { + mContext.startActivity(intent); + } catch (Exception e) { + e.printStackTrace(); + } + } + listener_location.success(true); + dismiss(); + } else if (listener != null) { + listener.success(); + dismiss(); + } else if (listener_result != null) { + listener_result.success(true); + dismiss(); + } + } + }); + } + + public interface Listener { + void success(); + } + + public interface Listener_Result { + void success(boolean b); + } + + public interface Listener_Location { + void success(boolean b); + } + + public interface Listener_Intent { + void success(); + } + +} diff --git a/BaseLibrary/src/main/java/com/tfq/library/utils/RecyclerViewHelper.java b/BaseLibrary/src/main/java/com/tfq/library/utils/RecyclerViewHelper.java new file mode 100644 index 0000000..ffafa20 --- /dev/null +++ b/BaseLibrary/src/main/java/com/tfq/library/utils/RecyclerViewHelper.java @@ -0,0 +1,58 @@ +package com.tfq.library.utils; + + +import android.content.Context; + +import androidx.recyclerview.widget.DefaultItemAnimator; +import androidx.recyclerview.widget.LinearLayoutManager; +import androidx.recyclerview.widget.RecyclerView; + + +/** + * Created by long on 2016/3/30. + * 视图帮助类 + */ +public class RecyclerViewHelper { + + private RecyclerViewHelper() { + throw new RuntimeException("RecyclerViewHelper cannot be initialized!"); + } + + public static void initRecyclerViewV(Context context, RecyclerView view, boolean isDivided, RecyclerView.Adapter adapter) { + LinearLayoutManager layoutManager = new LinearLayoutManager(context); + layoutManager.setOrientation(LinearLayoutManager.VERTICAL); +// view.setHasFixedSize(true); + if (view != null) { + view.setLayoutManager(layoutManager); + view.setItemAnimator(new DefaultItemAnimator()); + if (isDivided) { + // view.addItemDecoration(new DividerItemDecoration(context, DividerItemDecoration.VERTICAL_LIST)); + } + view.setAdapter(adapter); + } + } + + public static void initRecyclerViewV(Context context, RecyclerView view, RecyclerView.Adapter adapter) { + initRecyclerViewV(context, view, false, adapter); + } + + public static void initRecyclerViewH(Context context, RecyclerView view, RecyclerView.Adapter adapter) { + initRecyclerViewH(context, view, false, adapter); + } + + public static void initRecyclerViewH(Context context, RecyclerView view, boolean isDivided, RecyclerView.Adapter adapter) { + LinearLayoutManager layoutManager = new LinearLayoutManager(context); + layoutManager.setOrientation(LinearLayoutManager.HORIZONTAL); +// view.setHasFixedSize(true); + if (view != null) { + view.setLayoutManager(layoutManager); + view.setItemAnimator(new DefaultItemAnimator()); + if (isDivided) { + // view.addItemDecoration(new DividerItemDecoration(context, DividerItemDecoration.VERTICAL_LIST)); + } + view.setAdapter(adapter); + } + } + + +} diff --git a/BaseLibrary/src/main/java/com/tfq/library/utils/SDUtils.java b/BaseLibrary/src/main/java/com/tfq/library/utils/SDUtils.java new file mode 100644 index 0000000..b287098 --- /dev/null +++ b/BaseLibrary/src/main/java/com/tfq/library/utils/SDUtils.java @@ -0,0 +1,44 @@ +package com.tfq.library.utils; + +import android.annotation.TargetApi; +import android.os.Build; +import android.os.Environment; +import android.os.StatFs; + +public class SDUtils { + @TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR2) + public static long getAvailableSize() { + if (isMounted()) { + StatFs stat = new StatFs(getSDCardPath()); + // 获得可用的块的数量 + long count = stat.getAvailableBlocksLong(); + long size = stat.getBlockSizeLong(); + return count * size; + } + return 0; + } + + + /** + * 判断SDCard是否挂载 + * Environment.MEDIA_MOUNTED,表示SDCard已经挂载 + * Environment.getExternalStorageState(),获得当前SDCard的挂载状态 + */ + private static boolean isMounted() { + return Environment.MEDIA_MOUNTED.equals(Environment.getExternalStorageState()); + } + + + /** + * 获得SDCard 的路径,storage/sdcard + * + * @return 路径 + */ + public static String getSDCardPath() { + String path = null; + if (isMounted()) { + path = Environment.getExternalStorageDirectory().getPath(); + } + return path; + } +} diff --git a/BaseLibrary/src/main/java/com/tfq/library/utils/ShapeTextView.java b/BaseLibrary/src/main/java/com/tfq/library/utils/ShapeTextView.java new file mode 100644 index 0000000..748b4b1 --- /dev/null +++ b/BaseLibrary/src/main/java/com/tfq/library/utils/ShapeTextView.java @@ -0,0 +1,158 @@ +package com.tfq.library.utils; + +import android.content.Context; +import android.content.res.TypedArray; +import android.graphics.Canvas; +import android.graphics.Color; +import android.graphics.Paint; +import android.graphics.drawable.GradientDrawable; +import android.text.TextPaint; +import android.util.AttributeSet; +import android.view.MotionEvent; +import android.view.View; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.appcompat.widget.AppCompatTextView; +import androidx.core.content.ContextCompat; + +import com.tfq.library.R; + +/** + * Created by TinyHung@outlook.com + * 2019/5/10 + * 自行指定背景圆角、颜色的BUTTON + */ + +public class ShapeTextView extends AppCompatTextView implements View.OnTouchListener { + + private boolean mTextMarquee; + private float mStrokeWidth = 0.6f; + //圆角、边框 + private int mRadius, mStroke; + //背景颜色 + private int mBackGroundColor = Color.parseColor("#00000000") + //背景按下颜色 + , mBackGroundSelectedColor = Color.parseColor("#00000000") + //边框颜色 + , mStrokeColor = Color.parseColor("#00000000"); + + public ShapeTextView(@NonNull Context context) { + this(context, null); + } + + public ShapeTextView(@NonNull Context context, @Nullable AttributeSet attrs) { + this(context, attrs, 0); + } + + public ShapeTextView(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) { + super(context, attrs, defStyleAttr); + this.setOnTouchListener(this); + if (null != attrs) { + TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.ShapeTextView); + mRadius = typedArray.getDimensionPixelSize(R.styleable.ShapeTextView_shapeRadius, 0); + mStroke = typedArray.getDimensionPixelSize(R.styleable.ShapeTextView_shapeStrokeWidth, 0); + mStrokeColor = typedArray.getColor(R.styleable.ShapeTextView_shapeStrokeColor, + ContextCompat.getColor(getContext(), android.R.color.transparent)); + mBackGroundColor = typedArray.getColor(R.styleable.ShapeTextView_shapeBackgroundColor, + ContextCompat.getColor(getContext(), R.color.colorAccent)); + mBackGroundSelectedColor = typedArray.getColor(R.styleable.ShapeTextView_shapeBackgroundSelectorColor, + ContextCompat.getColor(getContext(), R.color.colorPrimaryDark)); + mStrokeWidth = typedArray.getFloat(R.styleable.ShapeTextView_shapeStorkeWidth, mStrokeWidth); + mTextMarquee = typedArray.getBoolean(R.styleable.ShapeTextView_shapeMarquee, false); + typedArray.recycle(); + } + GradientDrawable gradientDrawable = new GradientDrawable(); + gradientDrawable.setCornerRadius(mRadius); + gradientDrawable.setStroke(mStroke, mStrokeColor); + gradientDrawable.setColor(mBackGroundColor); + this.setBackground(gradientDrawable); + setClickable(true); + } + + + public void setRadius(int radius) { + mRadius = radius; + GradientDrawable gradientDrawable = (GradientDrawable) getBackground(); + if (null != gradientDrawable) { + gradientDrawable.setCornerRadius(mRadius); + } + } + + public void setBackGroundColor(int color) { + this.mBackGroundColor = color; + GradientDrawable gradientDrawable = (GradientDrawable) getBackground(); + if (null != gradientDrawable) { + gradientDrawable.setColor(mBackGroundColor); + } + } + + public void setBackGroundSelectedColor(int color) { + this.mBackGroundSelectedColor = color; + } + + public void setStroke(int stroke) { + mStroke = stroke; + GradientDrawable gradientDrawable = (GradientDrawable) getBackground(); + if (null != gradientDrawable) { + gradientDrawable.setStroke(mStroke, mStrokeColor); + } + } + + public void setStrokeColor(int strokeColor) { + mStrokeColor = strokeColor; + GradientDrawable gradientDrawable = (GradientDrawable) getBackground(); + if (null != gradientDrawable) { + gradientDrawable.setStroke(mStroke, mStrokeColor); + } + } + + @Override + public boolean onTouch(View v, MotionEvent event) { + switch (event.getAction()) { + //用户手指按下,使用按下Color + case MotionEvent.ACTION_DOWN: + GradientDrawable gradientDrawable = (GradientDrawable) getBackground(); + if (null != gradientDrawable) { + gradientDrawable.setColor(mBackGroundSelectedColor); + } + break; + case MotionEvent.ACTION_MOVE: + break; + //用户松手,使用默认背景Color + case MotionEvent.ACTION_CANCEL: + case MotionEvent.ACTION_UP: + GradientDrawable background = (GradientDrawable) getBackground(); + if (null != background) { + background.setColor(mBackGroundColor); + } + break; + } + return super.onTouchEvent(event); + } + + @Override + protected void onDraw(Canvas canvas) { + //获取当前控件的画笔 + TextPaint paint = getPaint(); + //设置画笔的描边宽度值 + paint.setStrokeWidth(mStrokeWidth); + paint.setStyle(Paint.Style.FILL_AND_STROKE); + super.onDraw(canvas); + } + + /** + * 设置描边宽度 + * + * @param strokeWidth 从0.0起 + */ + public void setStrokeWidth(float strokeWidth) { + this.mStrokeWidth = strokeWidth; + invalidate(); + } + + @Override + public boolean isFocused() { + return mTextMarquee; + } +} \ No newline at end of file diff --git a/BaseLibrary/src/main/java/com/tfq/library/utils/SpManager.java b/BaseLibrary/src/main/java/com/tfq/library/utils/SpManager.java new file mode 100644 index 0000000..9008487 --- /dev/null +++ b/BaseLibrary/src/main/java/com/tfq/library/utils/SpManager.java @@ -0,0 +1,34 @@ +package com.tfq.library.utils; + +import android.content.Context; +import android.content.SharedPreferences; + +public class SpManager { + private static SharedPreferences sharedPreferences; + private static SharedPreferences.Editor editor; + + /** + * 写入本地 + * + * @param context + * @param name + * @return + */ + public static SharedPreferences.Editor startWrite(Context context, String name) { + sharedPreferences = context.getSharedPreferences(name, Context.MODE_PRIVATE); + editor = sharedPreferences.edit(); + return editor; + } + + /** + * 从本地读取 + * + * @param context + * @param name + * @return + */ + public static SharedPreferences startRead(Context context, String name) { + sharedPreferences = context.getSharedPreferences(name, Context.MODE_PRIVATE); + return sharedPreferences; + } +} diff --git a/BaseLibrary/src/main/java/com/tfq/library/utils/ToasterUtil.java b/BaseLibrary/src/main/java/com/tfq/library/utils/ToasterUtil.java new file mode 100644 index 0000000..97784d9 --- /dev/null +++ b/BaseLibrary/src/main/java/com/tfq/library/utils/ToasterUtil.java @@ -0,0 +1,92 @@ +package com.tfq.library.utils; + +import android.icu.math.BigDecimal; +import android.view.Gravity; + +import com.hjq.toast.ToastParams; +import com.hjq.toast.Toaster; +import com.hjq.toast.style.CustomToastStyle; +import com.hjq.toast.style.WhiteToastStyle; +import com.tfq.library.R; + +import java.math.BigInteger; + +public class ToasterUtil { + + public static void show(String text) { + Toaster.show(text); + } + + public static void show(Object object) { + Toaster.show(object); + } + + public static void show(CharSequence text) { + Toaster.show(text); + } + + public static void show(ToastParams params) { + Toaster.show(params); + } + + public static void show(int text, int type) { + show(text + "", type); + } + + public static void show(Object text, int type) { + show(text + "", type); + } + + /** + * @param text 显示内容 + * @param type 类型 0:默认黑色;1:通过样式;2:提示样式;3:警告样式;4:错误样式; 10:白色样式;20:自定义弹窗 + */ + public static void show(String text, int type) { + ToastParams params; + switch (type) { + case 0: + show(text); + break; + case 1: + params = new ToastParams(); + params.text = text; + params.style = new CustomToastStyle(R.layout.toast_success); + Toaster.show(params); + break; + case 2: + params = new ToastParams(); + params.text = text; + params.style = new CustomToastStyle(R.layout.toast_info); + Toaster.show(params); + break; + case 3: + params = new ToastParams(); + params.text = text; + params.style = new CustomToastStyle(R.layout.toast_warn); + Toaster.show(params); + break; + case 4: + params = new ToastParams(); + params.text = text; + params.style = new CustomToastStyle(R.layout.toast_error); + Toaster.show(params); + break; + case 10: + params = new ToastParams(); + params.text = text; + params.style = new WhiteToastStyle(); + Toaster.show(params); + break; + case 20: + Toaster.setView(R.layout.toast_custom_view); + Toaster.setGravity(Gravity.CENTER); + Toaster.show(text); + break; + default: + show(text); + break; + } + } + + +} \ No newline at end of file diff --git a/BaseLibrary/src/main/java/com/tfq/library/utils/style/BlackToastStyle.java b/BaseLibrary/src/main/java/com/tfq/library/utils/style/BlackToastStyle.java new file mode 100644 index 0000000..a016256 --- /dev/null +++ b/BaseLibrary/src/main/java/com/tfq/library/utils/style/BlackToastStyle.java @@ -0,0 +1,96 @@ +package com.tfq.library.utils.style; + +import android.content.Context; +import android.graphics.drawable.Drawable; +import android.graphics.drawable.GradientDrawable; +import android.os.Build; +import android.util.TypedValue; +import android.view.Gravity; +import android.view.View; +import android.view.ViewGroup; +import android.widget.TextView; + +import com.hjq.toast.config.IToastStyle; + +/** + * author : Android 轮子哥 + * github : https://github.com/getActivity/Toaster + * time : 2018/09/01 + * desc : 默认黑色样式实现 + */ +@SuppressWarnings({"unused", "deprecation"}) +public class BlackToastStyle implements IToastStyle { + + @Override + public View createView(Context context) { + TextView textView = new TextView(context); + textView.setId(android.R.id.message); + textView.setGravity(getTextGravity(context)); + textView.setTextColor(getTextColor(context)); + textView.setTextSize(TypedValue.COMPLEX_UNIT_PX, getTextSize(context)); + + int horizontalPadding = getHorizontalPadding(context); + int verticalPadding = getVerticalPadding(context); + + // 适配布局反方向特性 + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) { + textView.setPaddingRelative(horizontalPadding, verticalPadding, horizontalPadding, verticalPadding); + } else { + textView.setPadding(horizontalPadding, verticalPadding, horizontalPadding, verticalPadding); + } + + textView.setLayoutParams(new ViewGroup.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT)); + + Drawable backgroundDrawable = getBackgroundDrawable(context); + // 设置背景 + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) { + textView.setBackground(backgroundDrawable); + } else { + textView.setBackgroundDrawable(backgroundDrawable); + } + + // 设置 Z 轴阴影 + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { + textView.setZ(getTranslationZ(context)); + } + + return textView; + } + + protected int getTextGravity(Context context) { + return Gravity.CENTER; + } + + protected int getTextColor(Context context) { + return 0XEEFFFFFF; + } + + protected float getTextSize(Context context) { + return TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP, + 14, context.getResources().getDisplayMetrics()); + } + + protected int getHorizontalPadding(Context context) { + return (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, + 24, context.getResources().getDisplayMetrics()); + } + + protected int getVerticalPadding(Context context) { + return (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, + 16, context.getResources().getDisplayMetrics()); + } + + protected Drawable getBackgroundDrawable(Context context) { + GradientDrawable drawable = new GradientDrawable(); + // 设置颜色 + drawable.setColor(0XB3000000); + // 设置圆角 + drawable.setCornerRadius(TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, + 7, context.getResources().getDisplayMetrics())); + return drawable; + } + + protected float getTranslationZ(Context context) { + return TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 3, context.getResources().getDisplayMetrics()); + } +} \ No newline at end of file diff --git a/BaseLibrary/src/main/java/com/tfq/library/utils/style/WhiteToastStyle.java b/BaseLibrary/src/main/java/com/tfq/library/utils/style/WhiteToastStyle.java new file mode 100644 index 0000000..02d9212 --- /dev/null +++ b/BaseLibrary/src/main/java/com/tfq/library/utils/style/WhiteToastStyle.java @@ -0,0 +1,33 @@ +package com.tfq.library.utils.style; + +import android.content.Context; +import android.graphics.drawable.Drawable; +import android.graphics.drawable.GradientDrawable; +import android.util.TypedValue; + +import com.hjq.toast.style.BlackToastStyle; + +/** + * author : Android 轮子哥 + * github : https://github.com/getActivity/Toaster + * time : 2018/09/01 + * desc : 默认白色样式实现 + */ +public class WhiteToastStyle extends BlackToastStyle { + + @Override + protected int getTextColor(Context context) { + return 0XBB000000; + } + + @Override + protected Drawable getBackgroundDrawable(Context context) { + GradientDrawable drawable = new GradientDrawable(); + // 设置颜色 + drawable.setColor(0XFFEAEAEA); + // 设置圆角 + drawable.setCornerRadius(TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, + 7, context.getResources().getDisplayMetrics())); + return drawable; + } +} \ No newline at end of file diff --git a/BaseLibrary/src/main/java/com/tfq/library/view/AuthDialog.java b/BaseLibrary/src/main/java/com/tfq/library/view/AuthDialog.java new file mode 100644 index 0000000..005adf1 --- /dev/null +++ b/BaseLibrary/src/main/java/com/tfq/library/view/AuthDialog.java @@ -0,0 +1,146 @@ +package com.tfq.library.view; + +import android.app.Dialog; +import android.content.Context; +import android.content.Intent; +import android.net.Uri; +import android.os.Bundle; +import android.view.Gravity; +import android.view.LayoutInflater; +import android.view.View; +import android.widget.TextView; + +import com.tfq.library.R; +import com.tfq.library.app.BaseConstants; + + +public class AuthDialog extends Dialog { + private final Context mContext; + private final LayoutInflater inflater; + private Listener listener; + private View contentView; + private String type; + + public AuthDialog(Context context, Listener listener) { + super(context, R.style.CustomDialog); + this.listener = listener; + this.mContext = context; + inflater = LayoutInflater.from(context); + } + + public AuthDialog(Context context, String type) { + super(context, R.style.CustomDialog); + this.mContext = context; + this.type = type; + inflater = LayoutInflater.from(context); + } + + public AuthDialog(Context context, String type, Listener listener) { + super(context, R.style.CustomDialog); + this.listener = listener; + this.mContext = context; + this.type = type; + inflater = LayoutInflater.from(context); + } + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setViews(); + } + + private void setViews() { + int layout = BaseConstants.dialog_layout == 0 ? R.layout.layout_dialog_auth : BaseConstants.dialog_layout == 1 ? R.layout.layout_dialog_auth2 : R.layout.layout_dialog_auth3; + contentView = inflater.inflate(layout, null); + setContentView(contentView); + TextView tv_left = contentView.findViewById(R.id.tv_left); + TextView tv_right = contentView.findViewById(R.id.tv_right); + TextView tv_content = contentView.findViewById(R.id.tv_content); + TextView tv_title = contentView.findViewById(R.id.tv_title); + + if ("privacy".equals(type)) { + tv_title.setText("隐私权限管理"); + tv_content.setText("请在本应用的详情页面的权限列表找到'权限'并打开进行设置。"); + tv_right.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + if (listener != null) { + listener.callBack(); + } else { + Intent intent = new Intent(); + intent.setAction("android.settings.APPLICATION_DETAILS_SETTINGS"); + intent.setData(Uri.fromParts("package", mContext.getPackageName(), null)); + mContext.startActivity(intent); + } + dismiss(); + } + }); + + tv_left.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + dismiss(); + } + }); + } else if ("authorizatio".equals(type)) { + tv_title.setText("撤回隐私授权"); + tv_content.setText("若您撤回本App的隐私授权,我们将会停止收集您的个人信息,并且不再为您提供相应服务,谨慎进行此步操作。"); + tv_left.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + dismiss(); + } + }); + + tv_right.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + if (listener != null) { + listener.callBack(); + } + dismiss(); + } + }); + } else if ("playUrl".equals(type)) { + tv_title.setText("此链接无效"); + tv_content.setText("请输入正确地址"); + tv_content.setGravity(Gravity.CENTER); + tv_left.setVisibility(View.GONE); +// tv_right.setImageDrawable(mContext.getDrawable(R.mipmap.ic_confirm)); + tv_right.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + dismiss(); + if (listener != null) { + listener.callBack(); + } + } + }); + this.setCanceledOnTouchOutside(false); + this.setCancelable(false); + } else { + tv_right.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + if (listener != null) listener.callBack(); + dismiss(); + } + }); + + tv_left.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + dismiss(); + } + }); + + + } + + } + + public interface Listener { + void callBack(); + } + +} diff --git a/BaseLibrary/src/main/java/com/tfq/library/view/SafeBlurView.java b/BaseLibrary/src/main/java/com/tfq/library/view/SafeBlurView.java new file mode 100644 index 0000000..29e59d4 --- /dev/null +++ b/BaseLibrary/src/main/java/com/tfq/library/view/SafeBlurView.java @@ -0,0 +1,34 @@ +package com.tfq.library.view; + +import android.content.Context; +import android.util.AttributeSet; +import android.view.View; + +import net.center.blurview.ShapeBlurView; + +/** + * 安全的模糊视图包装类,修复测量问题 + */ +public class SafeBlurView extends ShapeBlurView { + + public SafeBlurView(Context context, AttributeSet attrs) { + super(context, attrs); + } + + @Override + protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + try { + super.onMeasure(widthMeasureSpec, heightMeasureSpec); + } catch (Exception e) { + // 确保在原始测量失败时设置默认尺寸 + int width = View.MeasureSpec.getSize(widthMeasureSpec); + int height = View.MeasureSpec.getSize(heightMeasureSpec); + + // 确保宽高至少为1px + width = Math.max(width, 1); + height = Math.max(height, 1); + + setMeasuredDimension(width, height); + } + } +} \ No newline at end of file diff --git a/BaseLibrary/src/main/java/com/tfq/library/view/SlideDownView.java b/BaseLibrary/src/main/java/com/tfq/library/view/SlideDownView.java new file mode 100644 index 0000000..b55fe45 --- /dev/null +++ b/BaseLibrary/src/main/java/com/tfq/library/view/SlideDownView.java @@ -0,0 +1,424 @@ +package com.tfq.library.view; + +import android.animation.Animator; +import android.animation.AnimatorSet; +import android.animation.ObjectAnimator; +import android.content.Context; +import android.content.res.TypedArray; +import android.graphics.Canvas; +import android.graphics.Color; +import android.graphics.Typeface; +import android.os.Handler; +import android.os.Looper; +import android.text.TextUtils; +import android.util.AttributeSet; +import android.util.TypedValue; +import android.view.Gravity; +import android.view.View; +import android.view.animation.AccelerateDecelerateInterpolator; +import android.view.animation.AccelerateInterpolator; +import android.widget.ImageView; +import android.widget.LinearLayout; +import android.widget.TextView; + +import com.tfq.library.R; +import com.tfq.library.utils.LogK; + +import java.util.concurrent.Executors; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.ScheduledFuture; +import java.util.concurrent.TimeUnit; + +import androidx.annotation.Nullable; +import androidx.core.content.ContextCompat; + +/** + * Description: + * Created by JiangKe . + */ +public class SlideDownView extends LinearLayout { + + // 动画相关属性 + private final boolean isExpanded = false; + private float mTextTitleSize = 15f; + private float mTextContentSize = 12f; + private float targetTranslationY; // 目标位置的Y轴偏移量 + private float overshootTranslationY; // 超过目标位置的Y轴偏移量 + private String mTextTitle = ""; + private String mTextContent = ""; + private int mMILLISECONDS; + private boolean mViewCenter; + private int mImageView; + private int view_type;//0:通过(默认);1:error + private int view_bg = Color.parseColor("#4CB050"); + + private DelayedTaskManager taskManager; + + public SlideDownView(Context context) { + super(context); + initView(null); + } + + public SlideDownView(Context context, @Nullable AttributeSet attrs) { + super(context, attrs); + initView(attrs); + } + + public SlideDownView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) { + super(context, attrs, defStyleAttr); + initView(attrs); + } + + private void initView(AttributeSet attrs) { + if (null != attrs) { + //获取到自定义xml的数据 + TypedArray typedArray = getContext().obtainStyledAttributes(attrs, R.styleable.SlideDownView); + mImageView = typedArray.getResourceId(R.styleable.SlideDownView_custom_image, 0); + mTextTitle = typedArray.getString(R.styleable.SlideDownView_text_title); + mTextTitleSize = typedArray.getFloat(R.styleable.SlideDownView_text_title_size, mTextTitleSize); + mTextContent = typedArray.getString(R.styleable.SlideDownView_text_content); + mTextContentSize = typedArray.getFloat(R.styleable.SlideDownView_text_content_size, mTextContentSize); +// mTextMarquee = typedArray.getBoolean(R.styleable.SlideDownView_boldMarquee, false); + mMILLISECONDS = typedArray.getInteger(R.styleable.SlideDownView_hide_milliseconds, 300); + mViewCenter = typedArray.getBoolean(R.styleable.SlideDownView_view_center, true); + typedArray.recycle(); + } + + addLinearLayoutView(); + + initThreadPool(); + + setLayerType(LAYER_TYPE_SOFTWARE, null); + + setWillNotDraw(false); + } + + private void initThreadPool() { + taskManager = new DelayedTaskManager(); + } + + // 在Activity的onCreate或其他适当位置调用此方法 + private void addLinearLayoutView() { + // 获取应用上下文 + Context context = getContext(); + + // 创建外层水平LinearLayout + LinearLayout mainLayout = new LinearLayout(context); + + // 设置LayoutParams并添加marginTop + LinearLayout.LayoutParams mainParams = new LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT, LinearLayout.LayoutParams.MATCH_PARENT); + + // 转换30dp为像素值 + mainLayout.setLayoutParams(mainParams); + mainLayout.setOnClickListener(new OnClickListener() { + @Override + public void onClick(View view) { + hideFunctionBar(); + } + }); + + // 保留原有其他属性设置 + mainLayout.setOrientation(LinearLayout.HORIZONTAL); +// mainLayout.setGravity(Gravity.CENTER_VERTICAL); + if (mViewCenter) { + mainLayout.setGravity(Gravity.CENTER); + } else { + mainLayout.setGravity(Gravity.CENTER_VERTICAL); + } + int paddingHorizontal = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 20, getResources().getDisplayMetrics()); + int paddingLeft = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 10, getResources().getDisplayMetrics()); + int paddingTop = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 35, getResources().getDisplayMetrics()); + mainLayout.setPadding(paddingLeft, paddingTop, paddingHorizontal, 0); + mainLayout.setBackgroundColor(view_bg); + mainLayout.setId(R.id.main_layout); + + + // 创建ImageView + ImageView imageView = new ImageView(context); + LinearLayout.LayoutParams imageParams = new LinearLayout.LayoutParams((int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 70, getResources().getDisplayMetrics()), (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 70, getResources().getDisplayMetrics())); + imageView.setLayoutParams(imageParams); + imageView.setPadding((int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 15, getResources().getDisplayMetrics()), (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 15, getResources().getDisplayMetrics()), (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 15, getResources().getDisplayMetrics()), (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 15, getResources().getDisplayMetrics())); + imageView.setImageResource(R.drawable.ic_place); + imageView.setId(R.id.image_view); // 确保在ids.xml中定义了此ID + if (mImageView != 0) { + imageView.setVisibility(VISIBLE); + } else { + imageView.setVisibility(GONE); + } + + // 创建内层垂直LinearLayout + LinearLayout innerLayout = new LinearLayout(context); + LinearLayout.LayoutParams innerParams = new LinearLayout.LayoutParams(LayoutParams.WRAP_CONTENT, LinearLayout.LayoutParams.WRAP_CONTENT); + int marginLeft = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 15, getResources().getDisplayMetrics()); + innerParams.setMargins(0, 0, 0, 0); + innerLayout.setLayoutParams(innerParams); + innerLayout.setOrientation(LinearLayout.VERTICAL); + + // 创建标题TextView + TextView tvTitle = new TextView(context); + tvTitle.setLayoutParams(new LinearLayout.LayoutParams(LinearLayout.LayoutParams.WRAP_CONTENT, LinearLayout.LayoutParams.WRAP_CONTENT)); + // 新增省略号和单行设置 + tvTitle.setEllipsize(TextUtils.TruncateAt.END); // 末尾显示省略号 + tvTitle.setMaxLines(1); // 限制为单行 + tvTitle.setSingleLine(true); // 旧版兼容方案(可选) + tvTitle.setHorizontallyScrolling(true); // 防止自动换行 + tvTitle.setText(mTextTitle); + tvTitle.setTextColor(ContextCompat.getColor(context, R.color.white)); + tvTitle.setTextSize(TypedValue.COMPLEX_UNIT_DIP, mTextTitleSize); + tvTitle.setTypeface(null, Typeface.BOLD); + tvTitle.setId(R.id.tv_title); + if (!TextUtils.isEmpty(mTextTitle)) { + tvTitle.setVisibility(VISIBLE); + } else { + tvTitle.setVisibility(GONE); + } + + // 创建分隔线View + View divider = new View(context); + LinearLayout.LayoutParams dividerParams = new LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT, (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 5, getResources().getDisplayMetrics())); + divider.setLayoutParams(dividerParams); + divider.setBackgroundColor(Color.TRANSPARENT); // 根据实际颜色设置 + divider.setId(R.id.view_space); + + // 创建内容TextView + TextView tvContent = new TextView(context); + tvContent.setLayoutParams(new LinearLayout.LayoutParams(LinearLayout.LayoutParams.WRAP_CONTENT, LinearLayout.LayoutParams.WRAP_CONTENT)); + // 新增省略号和单行设置 + tvContent.setEllipsize(TextUtils.TruncateAt.END); // 末尾显示省略号 + tvContent.setMaxLines(1); // 限制为单行 + tvContent.setSingleLine(true); // 旧版兼容方案(可选) + tvContent.setHorizontallyScrolling(true); // 防止自动换行 + tvContent.setText(mTextContent); + tvContent.setTextColor(ContextCompat.getColor(context, R.color.white)); + tvContent.setTextSize(TypedValue.COMPLEX_UNIT_DIP, mTextContentSize); + tvContent.setId(R.id.tv_content); + if (!TextUtils.isEmpty(mTextContent)) { + tvContent.setVisibility(VISIBLE); + } else { + tvContent.setVisibility(GONE); + } + if (!TextUtils.isEmpty(mTextTitle) && !TextUtils.isEmpty(mTextContent)) { + divider.setVisibility(VISIBLE); + } else { + divider.setVisibility(GONE); + } + + innerLayout.setGravity(Gravity.CENTER); + // 组装内层布局 + innerLayout.addView(tvTitle); + innerLayout.addView(divider); + innerLayout.addView(tvContent); + + // 组装外层布局 + mainLayout.addView(imageView); + mainLayout.addView(innerLayout); + + // 设置为内容视图 + addView(mainLayout); + } + + @Override + protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + // 1. 测量所有子视图 + for (int i = 0; i < getChildCount(); i++) { + View child = getChildAt(i); + measureChild(child, widthMeasureSpec, heightMeasureSpec); + } + + // 2. 根据子视图计算父容器尺寸(此处以最大子宽度和累加高度为例) + int maxChildWidth = 0; + int totalHeight = 0; + for (int i = 0; i < getChildCount(); i++) { + View child = getChildAt(i); + maxChildWidth = Math.max(maxChildWidth, child.getMeasuredWidth()); + totalHeight += child.getMeasuredHeight(); + } + + // 3. 设置父容器最终尺寸 + setMeasuredDimension(resolveSize(maxChildWidth, widthMeasureSpec), resolveSize(totalHeight, heightMeasureSpec)); + } + + @Override + protected void onLayout(boolean changed, int left, int top, int right, int bottom) { + int currentTop = 0; + for (int i = 0; i < getChildCount(); i++) { + View child = getChildAt(i); + // 将子视图纵向排列(示例布局) + child.layout(0, currentTop, child.getMeasuredWidth(), currentTop + child.getMeasuredHeight()); + currentTop += child.getMeasuredHeight(); + } + } + + public void hideFunctionBar() { + animate().translationY(-getHeight() * 1.5f) // 向上移动 1.5 倍高度 + .setDuration(mMILLISECONDS).setInterpolator(new AccelerateInterpolator()).start(); + } + + /** + * n毫秒后执行隐藏 + * + * @param MILLISECONDS 毫秒 + */ + public void hideFunctionBar(long MILLISECONDS) { + animate().translationY(-getHeight() * 1.5f) // 向上移动 1.5 倍高度 + .setDuration(MILLISECONDS).setInterpolator(new AccelerateInterpolator()).start(); + } + + public void showFunctionBar() { + hideFunctionBar(0); + // 将15dp转换为像素值 + final float overshoot = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 20, getResources().getDisplayMetrics()); + + AnimatorSet set = new AnimatorSet(); + set.playSequentially(ObjectAnimator + .ofFloat(this, "translationY", overshoot) + .setDuration(300), ObjectAnimator.ofFloat(this, "translationY", 0) + .setDuration(200) + ); + set.setInterpolator(new AccelerateDecelerateInterpolator()); + set.addListener(new Animator.AnimatorListener() { + + @Override + public void onAnimationStart(Animator animation) { + // 动画开始时触发 + LogK.e("Animation 动画开始"); + + taskManager.scheduleTask(3000, () -> { + // 延迟2秒后执行的实际操作(如显示Toast) + LogK.e("执行任务=" + System.currentTimeMillis()); + hideFunctionBar(); + }); + } + + @Override + public void onAnimationEnd(Animator animation) { + // 动画完成时触发回调(整个AnimatorSet执行完毕) + LogK.e("Animation 动画完成"); + } + + @Override + public void onAnimationCancel(Animator animation) { + // 动画被取消时触发 + LogK.e("Animation 动画被取消"); + } + + @Override + public void onAnimationRepeat(Animator animation) { + // 动画重复时触发(仅适用于循环动画) + LogK.e("Animation 循环动画"); + } + }); + set.start(); + } + + public void setTextTitle(String mTextTitle) { + this.mTextTitle = mTextTitle; + invalidate(); + } + + public void setTextTitleSize(int mTextTitleSize) { + this.mTextTitleSize = mTextTitleSize; + invalidate(); + } + + public void setTextContent(String mTextContent) { + this.mTextContent = mTextContent; + invalidate(); + } + + public void setTextContentSize(int mTextContentSize) { + this.mTextContentSize = mTextContentSize; + invalidate(); + } + + public void setCustomImage(int mImageView) { + this.mImageView = mImageView; + invalidate(); + } + + public void setHide_milliseconds(int hide_milliseconds) { + this.mMILLISECONDS = hide_milliseconds; + } + + public void setBackgroundColor(int view_bg) { + this.view_bg = view_bg; + invalidate(); + } + + public void setBackgroundColor(String view_bg) { + this.view_bg = Color.parseColor(view_bg); + invalidate(); + } + + public void setType(int view_type) { + this.view_type = view_type; + invalidate(); + } + + @Override + protected void onDraw(Canvas canvas) { + super.onDraw(canvas); + + TextView tv_title = findViewById(R.id.tv_title); + tv_title.setText(mTextTitle); + tv_title.setTextSize(mTextTitleSize); + TextView tv_content = findViewById(R.id.tv_content); + tv_content.setText(mTextContent); + tv_content.setTextSize(mTextContentSize); + ImageView image_view = findViewById(R.id.image_view); + View view_space = findViewById(R.id.view_space); + if (!TextUtils.isEmpty(mTextTitle) && !TextUtils.isEmpty(mTextContent)) { + view_space.setVisibility(VISIBLE); + tv_title.setVisibility(VISIBLE); + tv_content.setVisibility(VISIBLE); + } else { + view_space.setVisibility(GONE); + if (TextUtils.isEmpty(mTextTitle)) { + tv_title.setVisibility(GONE); + } else { + tv_title.setVisibility(VISIBLE); + } + if (TextUtils.isEmpty(mTextContent)) { + tv_content.setVisibility(GONE); + } else { + tv_content.setVisibility(GONE); + } + } + if (mImageView != 0) { + image_view.setVisibility(VISIBLE); + image_view.setImageResource(mImageView); + } else { + image_view.setVisibility(GONE); + } + + view_bg = view_type==0? Color.parseColor("#4CB050") : Color.parseColor("#FF4443"); + LinearLayout main_layout = findViewById(R.id.main_layout); + main_layout.setBackgroundColor(view_bg); + + } + +} + +class DelayedTaskManager { + private final ScheduledExecutorService executor = Executors.newSingleThreadScheduledExecutor(); + private final Handler mainHandler = new Handler(Looper.getMainLooper()); + private ScheduledFuture pendingTask; + + // 提交延迟任务(UI线程调用) + public void scheduleTask(final long time, final Runnable task) { + // 取消之前的任务 + if (pendingTask != null) { + pendingTask.cancel(true); + } + // 提交新任务,延迟2秒执行 + pendingTask = executor.schedule(() -> { + // 切换到主线程执行实际任务(如更新UI) + mainHandler.post(task); + }, time, TimeUnit.MILLISECONDS); + } + + // 关闭线程池,避免内存泄漏 + public void shutdown() { + executor.shutdownNow(); + } +} diff --git a/BaseLibrary/src/main/res/anim/actionsheet_dialog_in.xml b/BaseLibrary/src/main/res/anim/actionsheet_dialog_in.xml new file mode 100644 index 0000000..cfd58a9 --- /dev/null +++ b/BaseLibrary/src/main/res/anim/actionsheet_dialog_in.xml @@ -0,0 +1,5 @@ + + diff --git a/BaseLibrary/src/main/res/anim/actionsheet_dialog_out.xml b/BaseLibrary/src/main/res/anim/actionsheet_dialog_out.xml new file mode 100644 index 0000000..5439a7a --- /dev/null +++ b/BaseLibrary/src/main/res/anim/actionsheet_dialog_out.xml @@ -0,0 +1,5 @@ + + diff --git a/BaseLibrary/src/main/res/anim/activity_in_animation.xml b/BaseLibrary/src/main/res/anim/activity_in_animation.xml new file mode 100644 index 0000000..5f704aa --- /dev/null +++ b/BaseLibrary/src/main/res/anim/activity_in_animation.xml @@ -0,0 +1,7 @@ + + + + \ No newline at end of file diff --git a/BaseLibrary/src/main/res/anim/activity_out_animation.xml b/BaseLibrary/src/main/res/anim/activity_out_animation.xml new file mode 100644 index 0000000..3eb5162 --- /dev/null +++ b/BaseLibrary/src/main/res/anim/activity_out_animation.xml @@ -0,0 +1,7 @@ + + + + \ No newline at end of file diff --git a/BaseLibrary/src/main/res/anim/anim_float_window_enter.xml b/BaseLibrary/src/main/res/anim/anim_float_window_enter.xml new file mode 100644 index 0000000..5790fe7 --- /dev/null +++ b/BaseLibrary/src/main/res/anim/anim_float_window_enter.xml @@ -0,0 +1,13 @@ + + + + + \ No newline at end of file diff --git a/BaseLibrary/src/main/res/anim/anim_float_window_exit.xml b/BaseLibrary/src/main/res/anim/anim_float_window_exit.xml new file mode 100644 index 0000000..04818af --- /dev/null +++ b/BaseLibrary/src/main/res/anim/anim_float_window_exit.xml @@ -0,0 +1,13 @@ + + + + + \ No newline at end of file diff --git a/BaseLibrary/src/main/res/anim/bottom_menu_enter.xml b/BaseLibrary/src/main/res/anim/bottom_menu_enter.xml new file mode 100644 index 0000000..01c4795 --- /dev/null +++ b/BaseLibrary/src/main/res/anim/bottom_menu_enter.xml @@ -0,0 +1,5 @@ + + \ No newline at end of file diff --git a/BaseLibrary/src/main/res/anim/bottom_menu_exit.xml b/BaseLibrary/src/main/res/anim/bottom_menu_exit.xml new file mode 100644 index 0000000..e197528 --- /dev/null +++ b/BaseLibrary/src/main/res/anim/bottom_menu_exit.xml @@ -0,0 +1,5 @@ + + diff --git a/BaseLibrary/src/main/res/anim/dialog_show_enter.xml b/BaseLibrary/src/main/res/anim/dialog_show_enter.xml new file mode 100644 index 0000000..b4b0204 --- /dev/null +++ b/BaseLibrary/src/main/res/anim/dialog_show_enter.xml @@ -0,0 +1,6 @@ + + + + \ No newline at end of file diff --git a/BaseLibrary/src/main/res/anim/dialog_show_exis.xml b/BaseLibrary/src/main/res/anim/dialog_show_exis.xml new file mode 100644 index 0000000..bcc4d03 --- /dev/null +++ b/BaseLibrary/src/main/res/anim/dialog_show_exis.xml @@ -0,0 +1,6 @@ + + + + \ No newline at end of file diff --git a/BaseLibrary/src/main/res/drawable-xhdpi/ic_place.png b/BaseLibrary/src/main/res/drawable-xhdpi/ic_place.png new file mode 100644 index 0000000..75ad7aa Binary files /dev/null and b/BaseLibrary/src/main/res/drawable-xhdpi/ic_place.png differ diff --git a/BaseLibrary/src/main/res/drawable/ll_radio_white.xml b/BaseLibrary/src/main/res/drawable/ll_radio_white.xml new file mode 100644 index 0000000..f64e68c --- /dev/null +++ b/BaseLibrary/src/main/res/drawable/ll_radio_white.xml @@ -0,0 +1,8 @@ + + + + + + \ No newline at end of file diff --git a/BaseLibrary/src/main/res/drawable/round_button_blue.xml b/BaseLibrary/src/main/res/drawable/round_button_blue.xml new file mode 100644 index 0000000..23a4b71 --- /dev/null +++ b/BaseLibrary/src/main/res/drawable/round_button_blue.xml @@ -0,0 +1,6 @@ + + + + + \ No newline at end of file diff --git a/BaseLibrary/src/main/res/drawable/round_button_green.xml b/BaseLibrary/src/main/res/drawable/round_button_green.xml new file mode 100644 index 0000000..27ee800 --- /dev/null +++ b/BaseLibrary/src/main/res/drawable/round_button_green.xml @@ -0,0 +1,6 @@ + + + + + \ No newline at end of file diff --git a/BaseLibrary/src/main/res/drawable/shape_gradient.xml b/BaseLibrary/src/main/res/drawable/shape_gradient.xml new file mode 100644 index 0000000..b32af2b --- /dev/null +++ b/BaseLibrary/src/main/res/drawable/shape_gradient.xml @@ -0,0 +1,10 @@ + + + + + + \ No newline at end of file diff --git a/BaseLibrary/src/main/res/drawable/toast_error_bg.xml b/BaseLibrary/src/main/res/drawable/toast_error_bg.xml new file mode 100644 index 0000000..da7e431 --- /dev/null +++ b/BaseLibrary/src/main/res/drawable/toast_error_bg.xml @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file diff --git a/BaseLibrary/src/main/res/drawable/toast_error_ic.xml b/BaseLibrary/src/main/res/drawable/toast_error_ic.xml new file mode 100644 index 0000000..4fd5942 --- /dev/null +++ b/BaseLibrary/src/main/res/drawable/toast_error_ic.xml @@ -0,0 +1,11 @@ + + + + diff --git a/BaseLibrary/src/main/res/drawable/toast_hint_bg.xml b/BaseLibrary/src/main/res/drawable/toast_hint_bg.xml new file mode 100644 index 0000000..9e176e5 --- /dev/null +++ b/BaseLibrary/src/main/res/drawable/toast_hint_bg.xml @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file diff --git a/BaseLibrary/src/main/res/drawable/toast_info_ic.xml b/BaseLibrary/src/main/res/drawable/toast_info_ic.xml new file mode 100644 index 0000000..d8a77f2 --- /dev/null +++ b/BaseLibrary/src/main/res/drawable/toast_info_ic.xml @@ -0,0 +1,12 @@ + + + + + diff --git a/BaseLibrary/src/main/res/drawable/toast_success_bg.xml b/BaseLibrary/src/main/res/drawable/toast_success_bg.xml new file mode 100644 index 0000000..66603a3 --- /dev/null +++ b/BaseLibrary/src/main/res/drawable/toast_success_bg.xml @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file diff --git a/BaseLibrary/src/main/res/drawable/toast_success_ic.xml b/BaseLibrary/src/main/res/drawable/toast_success_ic.xml new file mode 100644 index 0000000..a5e1257 --- /dev/null +++ b/BaseLibrary/src/main/res/drawable/toast_success_ic.xml @@ -0,0 +1,12 @@ + + + + + diff --git a/BaseLibrary/src/main/res/drawable/toast_warn_bg.xml b/BaseLibrary/src/main/res/drawable/toast_warn_bg.xml new file mode 100644 index 0000000..ba9e1f2 --- /dev/null +++ b/BaseLibrary/src/main/res/drawable/toast_warn_bg.xml @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file diff --git a/BaseLibrary/src/main/res/drawable/toast_warn_ic.xml b/BaseLibrary/src/main/res/drawable/toast_warn_ic.xml new file mode 100644 index 0000000..bd5389c --- /dev/null +++ b/BaseLibrary/src/main/res/drawable/toast_warn_ic.xml @@ -0,0 +1,12 @@ + + + + + diff --git a/BaseLibrary/src/main/res/layout/blur_background.xml b/BaseLibrary/src/main/res/layout/blur_background.xml new file mode 100644 index 0000000..8246e83 --- /dev/null +++ b/BaseLibrary/src/main/res/layout/blur_background.xml @@ -0,0 +1,18 @@ + + + + + + diff --git a/BaseLibrary/src/main/res/layout/dialog_add_text_layout.xml b/BaseLibrary/src/main/res/layout/dialog_add_text_layout.xml new file mode 100644 index 0000000..f36d68c --- /dev/null +++ b/BaseLibrary/src/main/res/layout/dialog_add_text_layout.xml @@ -0,0 +1,20 @@ + + + + + + \ No newline at end of file diff --git a/BaseLibrary/src/main/res/layout/layout_111.xml b/BaseLibrary/src/main/res/layout/layout_111.xml new file mode 100644 index 0000000..1ce81a3 --- /dev/null +++ b/BaseLibrary/src/main/res/layout/layout_111.xml @@ -0,0 +1,69 @@ + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/BaseLibrary/src/main/res/layout/layout_custom_slide_down_view.xml b/BaseLibrary/src/main/res/layout/layout_custom_slide_down_view.xml new file mode 100644 index 0000000..77da485 --- /dev/null +++ b/BaseLibrary/src/main/res/layout/layout_custom_slide_down_view.xml @@ -0,0 +1,15 @@ + + + + + \ No newline at end of file diff --git a/BaseLibrary/src/main/res/layout/layout_dialog_auth.xml b/BaseLibrary/src/main/res/layout/layout_dialog_auth.xml new file mode 100644 index 0000000..3fc7442 --- /dev/null +++ b/BaseLibrary/src/main/res/layout/layout_dialog_auth.xml @@ -0,0 +1,84 @@ + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/BaseLibrary/src/main/res/layout/layout_dialog_auth2.xml b/BaseLibrary/src/main/res/layout/layout_dialog_auth2.xml new file mode 100644 index 0000000..1bc2fe3 --- /dev/null +++ b/BaseLibrary/src/main/res/layout/layout_dialog_auth2.xml @@ -0,0 +1,84 @@ + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/BaseLibrary/src/main/res/layout/layout_dialog_auth3.xml b/BaseLibrary/src/main/res/layout/layout_dialog_auth3.xml new file mode 100644 index 0000000..47cd94c --- /dev/null +++ b/BaseLibrary/src/main/res/layout/layout_dialog_auth3.xml @@ -0,0 +1,88 @@ + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/BaseLibrary/src/main/res/layout/layout_dialog_bigphoto.xml b/BaseLibrary/src/main/res/layout/layout_dialog_bigphoto.xml new file mode 100644 index 0000000..b79bbba --- /dev/null +++ b/BaseLibrary/src/main/res/layout/layout_dialog_bigphoto.xml @@ -0,0 +1,12 @@ + + + + + + \ No newline at end of file diff --git a/BaseLibrary/src/main/res/layout/layout_dialog_permission.xml b/BaseLibrary/src/main/res/layout/layout_dialog_permission.xml new file mode 100644 index 0000000..9b380a8 --- /dev/null +++ b/BaseLibrary/src/main/res/layout/layout_dialog_permission.xml @@ -0,0 +1,83 @@ + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/BaseLibrary/src/main/res/layout/quick_view_load_more.xml b/BaseLibrary/src/main/res/layout/quick_view_load_more.xml new file mode 100644 index 0000000..08b3bc8 --- /dev/null +++ b/BaseLibrary/src/main/res/layout/quick_view_load_more.xml @@ -0,0 +1,60 @@ + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/BaseLibrary/src/main/res/layout/toast_custom_view.xml b/BaseLibrary/src/main/res/layout/toast_custom_view.xml new file mode 100644 index 0000000..6d0b91e --- /dev/null +++ b/BaseLibrary/src/main/res/layout/toast_custom_view.xml @@ -0,0 +1,26 @@ + + + + + + + \ No newline at end of file diff --git a/BaseLibrary/src/main/res/layout/toast_error.xml b/BaseLibrary/src/main/res/layout/toast_error.xml new file mode 100644 index 0000000..e2441ce --- /dev/null +++ b/BaseLibrary/src/main/res/layout/toast_error.xml @@ -0,0 +1,30 @@ + + + + + + + + \ No newline at end of file diff --git a/BaseLibrary/src/main/res/layout/toast_info.xml b/BaseLibrary/src/main/res/layout/toast_info.xml new file mode 100644 index 0000000..4dcc477 --- /dev/null +++ b/BaseLibrary/src/main/res/layout/toast_info.xml @@ -0,0 +1,30 @@ + + + + + + + + \ No newline at end of file diff --git a/BaseLibrary/src/main/res/layout/toast_success.xml b/BaseLibrary/src/main/res/layout/toast_success.xml new file mode 100644 index 0000000..b928236 --- /dev/null +++ b/BaseLibrary/src/main/res/layout/toast_success.xml @@ -0,0 +1,30 @@ + + + + + + + + \ No newline at end of file diff --git a/BaseLibrary/src/main/res/layout/toast_warn.xml b/BaseLibrary/src/main/res/layout/toast_warn.xml new file mode 100644 index 0000000..779e6b1 --- /dev/null +++ b/BaseLibrary/src/main/res/layout/toast_warn.xml @@ -0,0 +1,30 @@ + + + + + + + + \ No newline at end of file diff --git a/BaseLibrary/src/main/res/mipmap-hdpi/ic_loading.png b/BaseLibrary/src/main/res/mipmap-hdpi/ic_loading.png new file mode 100644 index 0000000..271c15f Binary files /dev/null and b/BaseLibrary/src/main/res/mipmap-hdpi/ic_loading.png differ diff --git a/BaseLibrary/src/main/res/mipmap-hdpi/ic_place.png b/BaseLibrary/src/main/res/mipmap-hdpi/ic_place.png new file mode 100644 index 0000000..75ad7aa Binary files /dev/null and b/BaseLibrary/src/main/res/mipmap-hdpi/ic_place.png differ diff --git a/BaseLibrary/src/main/res/mipmap-hdpi/ic_toast.png b/BaseLibrary/src/main/res/mipmap-hdpi/ic_toast.png new file mode 100644 index 0000000..ca5e17b Binary files /dev/null and b/BaseLibrary/src/main/res/mipmap-hdpi/ic_toast.png differ diff --git a/BaseLibrary/src/main/res/values/attrs.xml b/BaseLibrary/src/main/res/values/attrs.xml new file mode 100644 index 0000000..03a1532 --- /dev/null +++ b/BaseLibrary/src/main/res/values/attrs.xml @@ -0,0 +1,39 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/BaseLibrary/src/main/res/values/colors.xml b/BaseLibrary/src/main/res/values/colors.xml new file mode 100644 index 0000000..7ab014a --- /dev/null +++ b/BaseLibrary/src/main/res/values/colors.xml @@ -0,0 +1,269 @@ + + + #FFBB86FC + #FF6200EE + #FF3700B3 + #FF03DAC5 + #FF018786 + #000000 + #FFFFFF + + #FAFAFA + #9C78FF + + + #3F51B5 + #303F9F + #4285F6 + #ffcf06 + #0000FF + #55999999 + #A1000000 + #00000000 + #22050505 + #B0EEEEEE + #999999 + #AD1A1818 + #e9e9e9 + #363636 + #e5e5e5 + #FF0000 + #333333 + #99333333 + #00bfff + #1541AE + #1B97F7 + + #cccccc + + #ECE1E1 + + #7ecf89 + + #eb9224 + + #777777 + + #f5f7fc + + #035496 + + #C91204 + + #D9D9D9 + #686667 + + #AE9301 + + #E65425 + #FF8C00 + #0C7CD8 + #f5f5f5 + #333333 + #87ceeb + + #FFE2C59B + #FFFFFFF0 + #FFFFFFE0 + #FFFFFF00 + #FFFFFAFA + #FFFFFAF0 + #FFFFFACD + #FFFFF8DC + #FFFFF5EE + #FFFFF0F5 + #FFFFEFD5 + #FFFFEBCD + #FFFFE4E1 + #FFFFE4C4 + #FFFFE4B5 + #FFFFDEAD + #FFFFDAB9 + #FFFFD700 + #FFFFC0CB + #FFFFB6C1 + #FFFFA500 + #FFFFA07A + #FFFF8C00 + #FFFF7F50 + #FFFF69B4 + #FFFF6347 + #FFFF4500 + #FFFF1493 + #FFFF00FF + #FFFDF5E6 + #FFFAFAD2 + #FFFAF0E6 + #FFFAEBD7 + #FFFA8072 + #FFF8F8FF + #FFF5FFFA + #FFF5F5F5 + #FFF5F5DC + #FFF5DEB3 + #FFF4A460 + #FFF0FFFF + #FFF0F8FF + #FFF0E68C + #FFF08080 + #FFEEE8AA + #FFEE82EE + #FFE9967A + #FFE6E6FA + #FFE0FFFF + #FFDEB887 + #FFDDA0DD + #FFDCDCDC + #FFDC143C + #FFDB7093 + #FFDAA520 + #FFDA70D6 + #FFD8BFD8 + #FFD3D3D3 + #FFD2B48C + #FFD2691E + #FFCD853F + #FFCD5C5C + #FFC71585 + #FFC0C0C0 + #FFBDB76B + #FFBC8F8F + #FFBA55D3 + #FFB8860B + #FFB22222 + #FFB0E0E6 + #FFB0C4DE + #FFAFEEEE + #FFADFF2F + #FFADD8E6 + #FFA9A9A9 + #FFA52A2A + #FFA0522D + #FF9932CC + #FF98FB98 + #FF9400D3 + #FF9370DB + #FF90EE90 + #FF8FBC8F + #FF8B4513 + #FF8B008B + #FF8B0000 + #FF8A2BE2 + #FF87CEFA + #FF87CEEB + #FF808080 + #FF808000 + #FF800080 + #FF800000 + #FF7FFFD4 + #FF7FFF00 + #FF7CFC00 + #FF7B68EE + #FF778899 + #FF708090 + #FF6B8E23 + #FF6A5ACD + #FF696969 + #FF66CDAA + #FF6495ED + #FF5F9EA0 + #FF556B2F + #FF4B0082 + #FF48D1CC + #FF483D8B + #FF4682B4 + #FF4169E1 + #FF40E0D0 + #FF3CB371 + #FF32CD32 + #FF2F4F4F + #FF2E8B57 + #FF228B22 + #FF20B2AA + #FF1E90FF + #FF191970 + #FF00FFFF + #FF00FF7F + #FF00FF00 + #FF00FA9A + #FF00CED1 + #FF00BFFF + #FF008B8B + #FF008080 + #FF008000 + #FF006400 + #FF0000CD + #FF00008B + #FF000080 + #FF2B2B2B + + + #ffffff + #111111 + #666666 + #999999 + #bbbbbb + #828282 + #0099ff + #a9dbfc + #006ec1 + #4b83a9 + #e5533b + #c2001e + #19aa49 + #ebebe9 + #d1d1d1 + #ff0000 + #f6f6f5 + #f5f5f5 + #e0e0e0 + #FF222222 + #FFEEEEEE + #00cd56 + #99E0260C + + #45C01A + #A80375c2 + + #d9d9d9 + #ffffff + #ebebeb + #ebebe9 + + #fff + #e5e5e5 + #666 + #f2f2f2 + #999 + #000 + #222 + #f22121 + #0099cc + #7A0099cc + #333 + + #66000000 + #333333 + #666666 + + + #333333 + #72747B + #666666 + #888888 + #4DFFFFFF + #33E5E5E5 + #FFFFFF + #F2F6FA + + #CC3DA6EC + + + + #3F51B5 + #388E3C + #FFA900 + #ffcf06 + #D50000 + + diff --git a/BaseLibrary/src/main/res/values/ids.xml b/BaseLibrary/src/main/res/values/ids.xml new file mode 100644 index 0000000..bec5a22 --- /dev/null +++ b/BaseLibrary/src/main/res/values/ids.xml @@ -0,0 +1,9 @@ + + + + + + + + + \ No newline at end of file diff --git a/BaseLibrary/src/main/res/values/styles.xml b/BaseLibrary/src/main/res/values/styles.xml new file mode 100644 index 0000000..ac65023 --- /dev/null +++ b/BaseLibrary/src/main/res/values/styles.xml @@ -0,0 +1,222 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/BaseLibrary/src/test/java/com/tfq/library/ExampleUnitTest.java b/BaseLibrary/src/test/java/com/tfq/library/ExampleUnitTest.java new file mode 100644 index 0000000..d3724b1 --- /dev/null +++ b/BaseLibrary/src/test/java/com/tfq/library/ExampleUnitTest.java @@ -0,0 +1,17 @@ +package com.tfq.library; + +import org.junit.Test; + +import static org.junit.Assert.*; + +/** + * Example local unit test, which will execute on the development machine (host). + * + * @see Testing documentation + */ +public class ExampleUnitTest { + @Test + public void addition_isCorrect() { + assertEquals(4, 2 + 2); + } +} \ No newline at end of file diff --git a/LICENSE b/LICENSE index a7107b1..261eeb9 100644 --- a/LICENSE +++ b/LICENSE @@ -1,73 +1,201 @@ -Apache License -Version 2.0, January 2004 -http://www.apache.org/licenses/ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ -TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION -1. Definitions. + 1. Definitions. -"License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. -"Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. -"Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. -"You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. -"Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. -"Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. -"Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). -"Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. -"Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." -"Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. -2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. -3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. -4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: - (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and - (b) You must cause any modified files to carry prominent notices stating that You changed the files; and + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and - (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and - (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. - You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. -5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. -6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. -7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. -8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. -9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. -END OF TERMS AND CONDITIONS + END OF TERMS AND CONDITIONS -APPENDIX: How to apply the Apache License to your work. + APPENDIX: How to apply the Apache License to your work. -To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "[]" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives. + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. -Copyright 2025 app-lib + Copyright [yyyy] [name of copyright owner] -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 + 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 + 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. + 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. diff --git a/LibraryAd/.gitignore b/LibraryAd/.gitignore new file mode 100644 index 0000000..42afabf --- /dev/null +++ b/LibraryAd/.gitignore @@ -0,0 +1 @@ +/build \ No newline at end of file diff --git a/LibraryAd/build.gradle b/LibraryAd/build.gradle new file mode 100644 index 0000000..ce5235c --- /dev/null +++ b/LibraryAd/build.gradle @@ -0,0 +1,115 @@ +plugins { + id 'com.android.library' // 替换 application 插件 + id 'maven-publish' // 添加发布插件 +} + +//apply plugin: 'com.kezong.fat-aar' + +android { + namespace 'com.tfq.libraryad' + compileSdkVersion 34 + + defaultConfig { + minSdkVersion 21 + targetSdkVersion 34 + } + + buildTypes { + release { + + } + } + + sourceSets { + main { + jniLibs.srcDirs = ['libs'] +// assets.srcDirs += ['../android_data/csj_config'] + } + } + +} + +repositories { + flatDir { + dirs 'libs' + } +} + +dependencies { +// implementation fileTree(include: ['*.aar', '*.jar'], dir: 'libs') + compileOnly fileTree(include: ['*.aar', '*.jar'], dir: 'libs') + + +// implementation name: 'GDTSDK.unionNormal.4.610.1480', ext: 'aar' +// implementation name: 'mediation_gdt_adapter_4.610.1480.0', ext: 'aar' +// implementation name: 'open_ad_sdk_6.6.0.7', ext: 'aar' + +// +// implementation 'androidx.appcompat:appcompat:1.4.1' +// implementation 'com.google.android.material:material:1.5.0' +// testImplementation 'junit:junit:4.13.2' +// androidTestImplementation 'androidx.test.ext:junit:1.1.3' +// androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0' + + //沉浸式 +// api 'com.gyf.immersionbar:immersionbar:3.0.0-beta05' + //权限请求 +// api 'com.github.getActivity:XXPermissions:18.63' +// api 'com.github.CymChad:BaseRecyclerViewAdapterHelper:2.9.28' +// api 'androidx.activity:activity:1.3.0' + + api project(':BaseLibrary') +// implementation project(':LocalAarModules:AdCSJSdk') +// implementation project(':LocalAarModules:AdGDTSdk') +// implementation project(':LocalAarModules:AdGDTSdk_Adapter') + + //noinspection Aligned16KB +// implementation 'com.pangle.cn:mediation-sdk:6.6.0.7' //穿山甲融合SDK +// implementation 'com.pangle.cn:GDTSDK-unionNormal:4.610.1480' //GDT 优量汇 +// implementation 'com.pangle.cn:mediation-gdt-adapter:4.610.1480.0' //GDT 优量汇 adapter + +} + + +afterEvaluate { + publishing { + publications { + release(MavenPublication) { + groupId = 'com.jiangke.group' // 自定义组织标识 + artifactId = 'JKBaseLib' // 库名称 + version = '1.0.0' + artifact("$buildDir/outputs/aar/${project.name}-release.aar")// 指定 AAR 文件路径 + } + } + repositories { + maven { +// url = "file://${projectDir.parent}/maven-repo" // 指向本地目录 + url = "file://${projectDir.parent}/maven" // 指向本地目录 + } + /*maven { + url = "https://gitee.com/jiangke/JKBaseLib/raw/master/maven/" + credentials { + username = project.findProperty("gitee.user") ?: "" + password = project.findProperty("gitee.token") ?: "" + } + }*/ + } + } +} + +/*publishing { + publications { + maven(MavenPublication) { + groupId = 'com.jiangke.group' // 自定义组织标识 + artifactId = 'lib-name' // 库名称 + version = '1.0.0' // 版本号 + // 指定 AAR 文件路径 + artifact("$buildDir/outputs/aar/${project.name}-release.aar") + } + } + repositories { + maven { + url "file://D:/Android/Code/tfq/JKBaseLib" // 本地临时目录 + } + } +}*/ diff --git a/LibraryAd/libs/GDTSDK.unionNormal.4.610.1480.aar b/LibraryAd/libs/GDTSDK.unionNormal.4.610.1480.aar new file mode 100644 index 0000000..2a568ec Binary files /dev/null and b/LibraryAd/libs/GDTSDK.unionNormal.4.610.1480.aar differ diff --git a/LibraryAd/libs/mediation_gdt_adapter_4.610.1480.0.aar b/LibraryAd/libs/mediation_gdt_adapter_4.610.1480.0.aar new file mode 100644 index 0000000..cc3b94b Binary files /dev/null and b/LibraryAd/libs/mediation_gdt_adapter_4.610.1480.0.aar differ diff --git a/LibraryAd/libs/open_ad_sdk_6.6.0.7.aar b/LibraryAd/libs/open_ad_sdk_6.6.0.7.aar new file mode 100644 index 0000000..90e2a42 Binary files /dev/null and b/LibraryAd/libs/open_ad_sdk_6.6.0.7.aar differ diff --git a/LibraryAd/src/main/AndroidManifest.xml b/LibraryAd/src/main/AndroidManifest.xml new file mode 100644 index 0000000..a46ed54 --- /dev/null +++ b/LibraryAd/src/main/AndroidManifest.xml @@ -0,0 +1,121 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/LibraryAd/src/main/java/com/tfq/ad/ad/ADUtils.java b/LibraryAd/src/main/java/com/tfq/ad/ad/ADUtils.java new file mode 100644 index 0000000..4a2515c --- /dev/null +++ b/LibraryAd/src/main/java/com/tfq/ad/ad/ADUtils.java @@ -0,0 +1,107 @@ +package com.tfq.ad.ad; + +import android.app.Activity; +import android.content.Context; + +import com.google.gson.Gson; +import com.tfq.ad.bean.AdvBean; +import com.tfq.ad.bean.UserInfo; +import com.tfq.library.app.BaseConstants; +import com.tfq.library.utils.LogK; + +public class ADUtils { + private static Activity mContext; + private static String type; + + public ADUtils(String type, Context mContext) { + this.mContext = (Activity) mContext; + this.type = type; + regexSwitch(); + } + + public boolean regexSwitch() { + BaseConstants._isShow = false; + boolean b = false; + if (BaseConstants.AD_Switch_Requested) { + boolean _type; + switch (type) { + case "GMSplashAd"://开屏 + _type = BaseConstants.AD_SPLASH; + break; + case "GMRewardAd"://激励 + _type = BaseConstants.AD_REWARD; + break; + case "GMInterstitialFullAd"://插全屏 + _type = BaseConstants.AD_CQP; + break; + case "GMNativeAd"://信息流 + _type = BaseConstants.AD_NATIVE; + break; + case "GMBannerAd"://banner + _type = BaseConstants.AD_BANNER; + break; + default: + _type = false; + break; + } + LogK.e("广告类型=" + type + + " 穿山甲是否正常=" + BaseConstants.adv_csj + + " 强制无广告=" + BaseConstants.NO_AD + + " 当前广告类型是开=" + _type); + if (BaseConstants.adv_csj && !BaseConstants.NO_AD && _type) { + b = true; + } + return b; + } else { + getPlatform(); + } + return b; + } + + private void getPlatform() { + new UserInfo().getAdvertising(new UserInfo.Success() { + @Override + public void Success(String data, String msg) { + try { + Gson gson = new Gson(); + AdvBean entity = gson.fromJson(data, AdvBean.class); + + if (entity.getData() != null) { + AdvBean.DataDTO entityData = entity.getData(); + if ("1".equals(entityData.getAdv1Flag())) { + BaseConstants.AD_SPLASH = true; + } + if ("1".equals(entityData.getAdv2Flag())) { + BaseConstants.AD_REWARD = true; + } + if ("1".equals(entityData.getAdv3Flag())) { + BaseConstants.AD_NATIVE = true; + } + if ("1".equals(entityData.getAdv4Flag())) { + BaseConstants.AD_CQP = true; + } + if ("1".equals(entityData.getAdv5Flag())) { + BaseConstants.AD_BANNER = true; + } + if ("1".equals(entityData.getAdv6Flag())) { + BaseConstants.AD_DRAW = true; + } + if (entityData.getAdv4Wait() > 0) { + BaseConstants.ADV_Wait = entityData.getAdv4Wait(); + } + } else { + BaseConstants.adv_csj = false; + } + } catch (Exception e) { + e.printStackTrace(); + } + BaseConstants.AD_Switch_Requested = true; + } + + @Override + public void fail(int num, String msg) { + } + }); + } + +} \ No newline at end of file diff --git a/LibraryAd/src/main/java/com/tfq/ad/ad/AdBannerUtils.java b/LibraryAd/src/main/java/com/tfq/ad/ad/AdBannerUtils.java new file mode 100644 index 0000000..2524550 --- /dev/null +++ b/LibraryAd/src/main/java/com/tfq/ad/ad/AdBannerUtils.java @@ -0,0 +1,209 @@ +package com.tfq.ad.ad; + +import android.app.Activity; +import android.content.Context; +import android.view.View; +import android.widget.FrameLayout; +import android.widget.RelativeLayout; + +import com.bytedance.sdk.openadsdk.AdSlot; +import com.bytedance.sdk.openadsdk.TTAdDislike; +import com.bytedance.sdk.openadsdk.TTAdNative; +import com.bytedance.sdk.openadsdk.TTFeedAd; +import com.bytedance.sdk.openadsdk.TTNativeExpressAd; +import com.tfq.library.app.BaseConstants; +import com.tfq.library.utils.LogK; + +import java.util.List; + +public class AdBannerUtils { + private static Context mContext; + private static String mAdUnitId = BaseConstants.CODE_AD_BANNER; + private static int width = 0; + private static Listener listener; + //判断大于多少秒 + private static int interval = 10; + private static long lastTime = 0; + private static TTNativeExpressAd mBannerAd; // Banner广告对象 + private static TTAdNative.NativeExpressAdListener mBannerListener; // 广告加载监听器 + private static TTNativeExpressAd.ExpressAdInteractionListener mBannerInteractionListener; // 广告展示监听器 + private static TTAdDislike.DislikeInteractionCallback mDislikeCallback; // 接受广告关闭回调 + private static FrameLayout mBannerContainer; // banner广告容器view + + public static void show_ad(Context context, FrameLayout layout) { + mContext = context; + mBannerContainer = layout; + + if (new ADUtils("GMBannerAd", mContext).regexSwitch()) { + loadAD(); + } + } + + public static void show_ad(Context mContext, FrameLayout mBannerContainer, int i_width) { + width = i_width; + show_ad(mContext, mBannerContainer); + } + + public static void show_ad(Context mContext, String adUnitId, FrameLayout mBannerContainer, int i_width) { + width = i_width; + mAdUnitId = adUnitId; + show_ad(mContext, mBannerContainer); + } + + public static void show_ad(Context mContext, FrameLayout mBannerContainer, int width, Listener thislistener) { + listener = thislistener; + show_ad(mContext, mBannerContainer, width); + } + + private static boolean interval() { + if (System.currentTimeMillis() - lastTime > (100 * interval)) { + return true; + } else { + return false; + } + } + + private static void loadAD() { + /** 1、创建AdSlot对象 */ + + int width = (int) (UIUtils.getScreenWidthDp(mContext) - AdBannerUtils.width); + int height = (int) ((UIUtils.getScreenWidthDp(mContext) - AdBannerUtils.width) / 4); +// height = 0; + LogK.e("AdBannerUtils mAdUnitId=" + mAdUnitId); + AdSlot adSlot = new AdSlot.Builder() + .setCodeId(mAdUnitId) + .setImageAcceptedSize(UIUtils.dp2px(mContext, width), UIUtils.dp2px(mContext, height)) // 单位px + .build(); + + /** 2、创建TTAdNative对象 */ + + TTAdNative adNativeLoader = TTAdManagerHolder.get().createAdNative(mContext); + + /** 3、创建加载、展示监听器 */ + initListeners(); + + /** 4、加载广告 */ + + adNativeLoader.loadBannerExpressAd(adSlot, mBannerListener); + } + + /** + * 5、广告加载成功后,设置监听器,展示广告 + */ + private static void showBannerAd() { + if (mBannerAd != null) { + mBannerAd.setExpressInteractionListener(mBannerInteractionListener); + mBannerAd.setDislikeCallback((Activity) mContext, mDislikeCallback); + mBannerAd.uploadDislikeEvent("mediation_dislike_event"); + /** 注意:使用融合功能时,load成功后可直接调用getExpressAdView获取广告view展示,而无需调用render等onRenderSuccess后 */ + + View bannerView = mBannerAd.getExpressAdView(); + if (bannerView != null && mBannerContainer != null) { + mBannerContainer.removeAllViews(); + mBannerContainer.addView(bannerView); + } + + mBannerAd.setSlideIntervalTime(5); + } else { + LogK.e("请先加载广告或等待广告加载完毕后再展示广告"); + } + } + + private static void initListeners() { + // 广告加载监听器 + + mBannerListener = new TTAdNative.NativeExpressAdListener() { + @Override + public void onError(int i, String s) { + LogK.e("banner load fail: errCode: " + i + ", errMsg: " + s); + } + + @Override + + public void onNativeExpressAdLoad(List list) { + if (list != null && list.size() > 0) { + LogK.e("banner load success"); + mBannerAd = list.get(0); + + showBannerAd(); + } else { + LogK.e("banner load success, but list is null"); + } + } + }; + // 广告展示监听器 + + mBannerInteractionListener = new TTNativeExpressAd.ExpressAdInteractionListener() { + @Override + + public void onAdClicked(View view, int i) { + LogK.e("banner clicked"); + } + + @Override + + public void onAdShow(View view, int i) { + LogK.e("banner showed"); + } + + @Override + public void onRenderFail(View view, String s, int i) { + // 注意:使用融合功能时,无需调用render,load成功后可调用mBannerAd.getExpressAdView()进行展示。 + } + + @Override + public void onRenderSuccess(View view, float v, float v1) { + // 注意:使用融合功能时,无需调用render,load成功后可调用mBannerAd.getExpressAdView()获取view进行展示。 + // 如果调用了render,则会直接回调onRenderSuccess,***** 参数view为null,请勿使用。***** + } + }; + // dislike监听器,广告关闭时会回调onSelected + + mDislikeCallback = new TTAdDislike.DislikeInteractionCallback() { + @Override + public void onShow() { + } + + @Override + public void onSelected(int i, String s, boolean b) { + if (mBannerContainer != null) + mBannerContainer.removeAllViews(); + LogK.e("banner closed"); + } + + @Override + public void onCancel() { + } + }; + } + + private static void removeAdView() { + if (mBannerAd != null) { + mBannerAd.destroy(); + } + if (mBannerContainer != null) { + mBannerContainer.removeAllViews(); + } + } + + public static View getBannerAdView(Context mContext) { + FrameLayout drawAdView = new FrameLayout(mContext); + RelativeLayout.LayoutParams layoutParams = new RelativeLayout.LayoutParams( + RelativeLayout.LayoutParams.MATCH_PARENT, // 宽度 + RelativeLayout.LayoutParams.WRAP_CONTENT // 高度 + ); + layoutParams.addRule(RelativeLayout.ALIGN_PARENT_BOTTOM, RelativeLayout.TRUE); + + AdBannerUtils.show_ad(mContext, drawAdView, 10); + return drawAdView; + } + + public interface Listener { + void success(boolean b); + } + + public interface ListenerAD { + void success(TTFeedAd ad); + } + +} diff --git a/LibraryAd/src/main/java/com/tfq/ad/ad/AdCQPUtils.java b/LibraryAd/src/main/java/com/tfq/ad/ad/AdCQPUtils.java new file mode 100644 index 0000000..d9a94d4 --- /dev/null +++ b/LibraryAd/src/main/java/com/tfq/ad/ad/AdCQPUtils.java @@ -0,0 +1,176 @@ +package com.tfq.ad.ad; + +import android.app.Activity; +import android.content.Context; + +import com.bytedance.sdk.openadsdk.AdSlot; +import com.bytedance.sdk.openadsdk.TTAdConstant; +import com.bytedance.sdk.openadsdk.TTAdNative; +import com.bytedance.sdk.openadsdk.TTAdSdk; +import com.bytedance.sdk.openadsdk.TTFullScreenVideoAd; +import com.tfq.library.app.BaseConstants; +import com.tfq.library.utils.LogK; + +public class AdCQPUtils { + + private CQP listener; + private CQP_Load_Success cqp_load_success; + private String mAdUnitId = BaseConstants.CODE_AD_CQP; + + public interface CQP { + void success(boolean b); + } + + public interface CQP_Load_Success { + void success(boolean b); + } + + //判断大于多少秒可以下一次开启 + int interval = BaseConstants.ADV_Wait; + static long lastTime = 0; + + private boolean interval() { + if (System.currentTimeMillis() - lastTime > (1000 * interval)) { + return true; + } else { + return false; + } + } + + public AdCQPUtils() { + + } + + public AdCQPUtils(Context mContext, boolean load, CQP listener) { + this.listener = listener; + init(mContext, load); + } + + public AdCQPUtils(Context mContext, boolean load, String mAdUnitId, CQP_Load_Success cqp_load_success) { + this.cqp_load_success = cqp_load_success; + this.mAdUnitId = mAdUnitId; + init(mContext, load); + } + + public void init(Context mContext) { + init(mContext, true); + } + + public void init(Context mContext, boolean load) { + boolean csj = true; + if (new ADUtils("GMInterstitialFullAd", mContext).regexSwitch() && csj && interval()) { + loadInterstitialFullAd(mContext); + } else { + if (listener != null) { + listener.success(false); + listener = null; + } + if (cqp_load_success != null) { + cqp_load_success.success(false); + cqp_load_success = null; + } + } + } + + private void loadInterstitialFullAd(Context mContext) { + TTAdNative adNativeLoader = TTAdSdk.getAdManager().createAdNative(mContext); + AdSlot adslot = new AdSlot.Builder() + .setCodeId(mAdUnitId) + .setOrientation(TTAdConstant.VERTICAL) + .setUserID("user121") + .build(); + adNativeLoader.loadFullScreenVideoAd(adslot, new TTAdNative.FullScreenVideoAdListener() { + @Override + public void onError(int code, String message) { + LogK.e("Screen onError code = " + code + " message = " + message); + if (listener != null) { + listener.success(false); + listener = null; + } + if (cqp_load_success != null) { + cqp_load_success.success(false); + cqp_load_success = null; + } + } + + @Override + public void onFullScreenVideoAdLoad(TTFullScreenVideoAd ttFullScreenVideoAd) { + showInterstitialFullAd(ttFullScreenVideoAd, mContext); + } + + @Override + public void onFullScreenVideoCached() { + LogK.e("onFullScreenVideoCached"); + } + + @Override + public void onFullScreenVideoCached(TTFullScreenVideoAd ttFullScreenVideoAd) { + LogK.e("onFullScreenVideoCached"); + showInterstitialFullAd(ttFullScreenVideoAd, mContext); + } + }); + } + + private void showInterstitialFullAd(TTFullScreenVideoAd ttFullScreenVideoAd, Context mContext) { + if (ttFullScreenVideoAd == null) { + return; + } else { + ttFullScreenVideoAd.setFullScreenVideoAdInteractionListener(new TTFullScreenVideoAd.FullScreenVideoAdInteractionListener() { + @Override + public void onAdShow() { + LogK.e("InterstitialFullActivity onAdShow"); + if (cqp_load_success != null) { + cqp_load_success.success(true); + cqp_load_success = null; + } + lastTime = System.currentTimeMillis(); + } + + @Override + public void onAdVideoBarClick() { + LogK.e("InterstitialFullActivity onAdVideoBarClick"); + } + + @Override + public void onAdClose() { + LogK.e("InterstitialFullActivity onAdClose"); + if (listener != null) { + listener.success(false); + listener = null; + } + } + + @Override + public void onVideoComplete() { + LogK.e("InterstitialFullActivity onVideoComplete"); + } + + @Override + public void onSkippedVideo() { + LogK.e("InterstitialFullActivity onSkippedVideo"); + } + }); + ttFullScreenVideoAd.showFullScreenVideoAd((Activity) mContext); + } + } + + /** + * 展示广告 + */ + public void showInterFullAd(Context mContext, CQP listene) { + this.listener = listene; + boolean csj = true; + if (new ADUtils("GMInterstitialFullAd", mContext).regexSwitch() && csj) { + loadInterstitialFullAd(mContext); + } else { + if (listener != null) { + listener.success(false); + listener = null; + } + if (cqp_load_success != null) { + cqp_load_success.success(false); + cqp_load_success = null; + } + } + } +} diff --git a/LibraryAd/src/main/java/com/tfq/ad/ad/AdFeedUtils.java b/LibraryAd/src/main/java/com/tfq/ad/ad/AdFeedUtils.java new file mode 100644 index 0000000..5581cd5 --- /dev/null +++ b/LibraryAd/src/main/java/com/tfq/ad/ad/AdFeedUtils.java @@ -0,0 +1,343 @@ +package com.tfq.ad.ad; + +import android.app.Activity; +import android.content.Context; +import android.text.TextUtils; +import android.view.View; +import android.view.ViewGroup; +import android.widget.FrameLayout; + +import com.bytedance.sdk.openadsdk.AdSlot; +import com.bytedance.sdk.openadsdk.TTAdDislike; +import com.bytedance.sdk.openadsdk.TTAdNative; +import com.bytedance.sdk.openadsdk.TTAdSdk; +import com.bytedance.sdk.openadsdk.TTFeedAd; +import com.bytedance.sdk.openadsdk.mediation.ad.MediationExpressRenderListener; +import com.tfq.library.app.BaseConstants; +import com.tfq.library.utils.LogK; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +public class AdFeedUtils { + public static Map map = new HashMap<>(); + public static Map locationMap; + private static Context mContext; + private static String mAdUnitId = LoopAd.getCurrentFeedID(); + private static String tag; + private static int width = 0; + private static TTAdNative adNativeLoader; + private static Listener listener; + private static ListenerAD listenerAD; + private static boolean mIsLoadedAndShow; + + private static TTFeedAd mTTFeedAd; + private static FrameLayout mFeedContainer; + //判断大于多少秒 + private static int interval = 10; + private static long lastTime = 0; + private static boolean reload = false; + /** + * @param mContext + * @param mFeedContainer + * @param i_width + * @param tag + * @param page_type 1:代表一级页面;2:代表二级页面;3:代表三级页面 + */ + private static int page_type = 1; + + public static void setTag(String tag) { + map.put(tag, true); + } + + public static void cancelTag(String tag) { + map.put(tag, false); + } + + private static void addSkip(String tag) { + if (locationMap == null) { + locationMap = new HashMap<>(); + } + if (!TextUtils.isEmpty(tag)) { + locationMap.put(tag, true); + } + } + + /** + * 返回true:请求,false:不请求 + * + * @param tag + * @return + */ + private static boolean isNoSkip(String tag) { + /*if (locationMap == null || TextUtils.isEmpty(tag) || locationMap.size() == 0) { + return true; + } else { + return !locationMap.containsKey(tag) || !locationMap.get(tag); + }*/ +// if (new AdPreAndLevelUtils().getPage_Interval(page_type)) { + if (new AdPreAndLevelUtils().getPage_Interval(tag, page_type)) { + return true; + } else { + return false; + } + } + + public static void preAD(Context mContext) { + preAD(mContext, 20); + } + + public static void preAD(Context context, int i_width) { + if (new ADUtils("GMNativeAd", context).regexSwitch()) { + width = i_width; + mContext = context; + mIsLoadedAndShow = false; + + setTag("tag"); + loadAD(); + } + } + + private static void show_ad(Context context, FrameLayout feedContainer, String s_tag) { + if (new ADUtils("GMNativeAd", context).regexSwitch() && isNoSkip(s_tag)) { + mContext = context; + mFeedContainer = feedContainer; + mIsLoadedAndShow = true; + tag = s_tag; + + setTag(s_tag); + loadAD(); + } + } + + public static void show_ad(Context mContext, String adUnitId, FrameLayout mFeedContainer, int i_width, String tag, int mPage_type) { + page_type = mPage_type; + mAdUnitId = adUnitId; + + width = i_width; + show_ad(mContext, mFeedContainer, tag); + } + + private static void loadAD() { + setTag(tag); + if (adNativeLoader == null) { + GMNativeADBean bean = CacheADManager.requestHaveAD("feed", mAdUnitId); + if (bean == null) { + LogK.e("bean == null"); + load(); + } else { + LogK.e("bean != null"); + if (mIsLoadedAndShow) { + mTTFeedAd = bean.getmGMNativeAD(); + show(); + } else if (listenerAD != null) { + listenerAD.success(bean.getmGMNativeAD()); + listenerAD = null; + } + } + } else { + + } + } + + private static boolean interval() { + if (System.currentTimeMillis() - lastTime > (100 * interval)) { + return true; + } else { + return false; + } + } + + private static void load() { + LogK.e("mAdUnitId=" + mAdUnitId); + if (adNativeLoader == null && interval()) { + lastTime = System.currentTimeMillis(); + adNativeLoader = TTAdSdk.getAdManager().createAdNative(mContext); + AdSlot adSlot = new AdSlot.Builder().setCodeId(mAdUnitId).setExpressViewAcceptedSize((int) (UIUtils.getScreenWidthDp(mContext) - width), 0).setAdCount(1)//请求广告数量为1到3条 (优先采用平台配置的数量) + .build(); + + adNativeLoader.loadFeedAd(adSlot, new TTAdNative.FeedAdListener() { + @Override + public void onError(int i, String s) { + LogK.e("onError code = " + i + " msg = " + s); + removeAD(); + if (adNativeLoader != null) { + adNativeLoader = null; + } + } + + @Override + public void onFeedAdLoad(List ads) { + if (ads == null || ads.isEmpty()) { + removeAD(); + if (adNativeLoader != null) { + adNativeLoader = null; + } + return; + } + + mTTFeedAd = ads.get(0); + if (listenerAD != null && map.get(tag)) { + listenerAD.success(ads.get(0)); + listenerAD = null; + } + CacheADManager.addAdLoaded("feed", mAdUnitId, ads.get(0)); + + LogK.e("map.get(tag)=" + map.get(tag)); + if (mIsLoadedAndShow && map.get(tag)) { + if (mFeedContainer != null) { +// new AdPreAndLevelUtils().setThisTime(page_type); + AdPreAndLevelUtils.setTagTime(tag, System.currentTimeMillis()); + show(); + } + } + if (adNativeLoader != null) { + adNativeLoader = null; + } + } + }); + } + } + + private static void show() { + if (mTTFeedAd == null || mTTFeedAd.getMediationManager() == null) { + LogK.e("请先加载广告或等待广告加载完毕后再调用show方法"); +// loadAD(); + if (adNativeLoader != null) { + adNativeLoader = null; + } + return; + } + if (mTTFeedAd.getMediationManager().isExpress() && mFeedContainer != null && map.get(tag)) { //模板 + mFeedContainer.setVisibility(View.VISIBLE); + showExpressView(mFeedContainer, mTTFeedAd); + } else { + if (adNativeLoader != null) { + adNativeLoader = null; + } + } + } + + private static void showExpressView(FrameLayout mFeedContainer, TTFeedAd mTTFeedAd) { + mTTFeedAd.setExpressRenderListener(new MediationExpressRenderListener() { + @Override + public void onRenderFail(View view, String s, int i) { + LogK.e("onRenderFail"); + } + + @Override + public void onAdClick() { + LogK.e("onAdClick"); + } + + @Override + public void onAdShow() { + LogK.e("onAdShow"); + + if (listener != null) { + listener.success(true); + listener = null; + } + if (mFeedContainer != null) { + mFeedContainer.setVisibility(View.VISIBLE); + } + if (adNativeLoader != null) { + adNativeLoader = null; + } + + if (new AdPreAndLevelUtils().getPre(page_type)) { + LogK.e("pre();"); + pre(); + } + } + + @Override + public void onRenderSuccess(View view, float v, float v1, boolean b) { + LogK.e("onRenderSuccess"); + mTTFeedAd.setDislikeCallback((Activity) mContext, new TTAdDislike.DislikeInteractionCallback() { + @Override + public void onShow() { + LogK.e("express dislike 点击show"); + } + + @Override + public void onSelected(int i, String s, boolean b) { + LogK.e("\"express 点击 " + s); + removeAdView(); + if (mFeedContainer != null) { + mFeedContainer.setVisibility(View.GONE); + } + addSkip(tag); + } + + @Override + public void onCancel() { + LogK.e("express dislike 点击了取消"); + } + }); + + removeAdView(); + View adView = mTTFeedAd.getAdView(); + if (adView.getParent() != null) { + ViewGroup viewGroup = (ViewGroup) adView.getParent(); + viewGroup.removeView(adView); + } + mFeedContainer.addView(adView); + + if (listener != null) { + listener.success(true); + listener = null; + } + if (adNativeLoader != null) { + adNativeLoader = null; + } + } + }); + + removeAD(); + mTTFeedAd.render(); + LogK.e("render"); + } + + private static void pre() { + if (BaseConstants.PRE_AD) { + String currentFeedID = LoopAd.getCurrentFeedID(); + if (currentFeedID != null) { + if (!CacheADManager.getContentID(currentFeedID)) { + if (false) {//如果轮播就是true + LoopAd.AdFeedIdAdd(); + } + mAdUnitId = LoopAd.getCurrentFeedID(); + preAD(mContext, 20); + } + } + } + } + + private static void removeAD() { + if (mAdUnitId != null) { + CacheADManager.removeAD("feed", mAdUnitId); + } + if (listenerAD != null) { + listenerAD.success(mTTFeedAd); + listenerAD = null; + } + } + + private static void removeAdView() { + LogK.e("removeAdView"); + if (mFeedContainer != null) { + mFeedContainer.removeAllViews(); + } + } + + public interface Listener { + void success(boolean b); + } + + public interface ListenerAD { + void success(TTFeedAd ad); + } + +} diff --git a/LibraryAd/src/main/java/com/tfq/ad/ad/AdPreAndLevelUtils.java b/LibraryAd/src/main/java/com/tfq/ad/ad/AdPreAndLevelUtils.java new file mode 100644 index 0000000..de44b54 --- /dev/null +++ b/LibraryAd/src/main/java/com/tfq/ad/ad/AdPreAndLevelUtils.java @@ -0,0 +1,132 @@ +package com.tfq.ad.ad; + +import android.text.TextUtils; + +import com.tfq.library.utils.LogK; + +import java.util.HashMap; +import java.util.Map; + +public class AdPreAndLevelUtils { + /** + * page是几级页面 + * pre是预加载 + * interval是请求间隔 + */ + public static boolean page1_pre = false; + public static boolean page2_pre = false; + public static boolean page3_pre = false; + public static int page1_interval = 5; + public static int page2_interval = 5; + public static int page3_interval = 5; + public static Map map = new HashMap<>(); + //判断大于多少秒可以下一次开启 + private static long lastTime_page1 = 0; + private static long lastTime_page2 = 0; + private static long lastTime_page3 = 0; + + public static void setTagTime(String tag, Long time) { + map.put(tag, time); + } + + public boolean getPage_Interval(int page) { + LogK.e("page1_interval=" + page1_interval); + LogK.e("page2_interval=" + page2_interval); + LogK.e("page3_interval=" + page3_interval); + if (page == 1) { + return get_interval_page1(); + } else if (page == 2) { + return get_interval_page2(); + } else if (page == 3) { + return get_interval_page3(); + } + return true; + } + + public boolean getPage_Interval(String tag, int page_level) { + /*LogK.e("page1_interval=" + page1_interval); + LogK.e("page2_interval=" + page2_interval); + LogK.e("page3_interval=" + page3_interval); + if (page == 1) { + return get_interval_page1(); + } else if (page == 2) { + return get_interval_page2(); + } else if (page == 3) { + return get_interval_page3(); + } + return true;*/ + if (!TextUtils.isEmpty(tag)) { + Long aLong = map.get(tag); + if (aLong != null) { + if (page_level == 1) { + if (System.currentTimeMillis() - aLong > (1000L * page1_interval)) { + return true; + } + return false; + } else if (page_level == 2) { + if (System.currentTimeMillis() - aLong > (1000L * page2_interval)) { + return true; + } + return false; + } else { + return true; + } + } else { + return true; + } + } else { + return true; + } + } + + private boolean get_interval_page1() { + if (lastTime_page1 == 0) { + return true; + } else if (System.currentTimeMillis() - lastTime_page1 > (1000L * page1_interval)) { + return true; + } else { + return false; + } + } + + private boolean get_interval_page2() { + if (lastTime_page2 == 0) { + return true; + } else if (System.currentTimeMillis() - lastTime_page2 > (1000L * page2_interval)) { + return true; + } else { + return false; + } + } + + private boolean get_interval_page3() { + if (lastTime_page3 == 0) { + return true; + } else if (System.currentTimeMillis() - lastTime_page3 > (1000L * page3_interval)) { + return true; + } else { + return false; + } + } + + public boolean getPre(int page) { + if (page == 1) { + return page1_pre; + } else if (page == 2) { + return page2_pre; + } else if (page == 3) { + return page3_pre; + } + return false; + } + + public void setThisTime(int page_type) { + if (page_type == 1) { + lastTime_page1 = System.currentTimeMillis(); + } else if (page_type == 2) { + lastTime_page2 = System.currentTimeMillis(); + } else if (page_type == 3) { + lastTime_page3 = System.currentTimeMillis(); + } + } +} diff --git a/LibraryAd/src/main/java/com/tfq/ad/ad/AdRewardUtils.java b/LibraryAd/src/main/java/com/tfq/ad/ad/AdRewardUtils.java new file mode 100644 index 0000000..6b1e103 --- /dev/null +++ b/LibraryAd/src/main/java/com/tfq/ad/ad/AdRewardUtils.java @@ -0,0 +1,251 @@ +package com.tfq.ad.ad; + +import android.app.Activity; +import android.content.Context; +import android.os.Bundle; +import android.os.Handler; + +import com.bytedance.sdk.openadsdk.AdSlot; +import com.bytedance.sdk.openadsdk.TTAdConstant; +import com.bytedance.sdk.openadsdk.TTAdNative; +import com.bytedance.sdk.openadsdk.TTRewardVideoAd; +import com.bytedance.sdk.openadsdk.mediation.MediationConstant; +import com.bytedance.sdk.openadsdk.mediation.ad.MediationAdSlot; +import com.bytedance.sdk.openadsdk.mediation.manager.MediationAdEcpmInfo; +import com.bytedance.sdk.openadsdk.mediation.manager.MediationBaseManager; +import com.tfq.library.app.BaseConstants; +import com.tfq.library.utils.LogK; +import com.tfq.library.utils.ToasterUtil; + +public class AdRewardUtils { + + static long lastTime = 0; + private static Boolean requestAD = false; + //判断大于多少秒可以下一次开启 + int interval = BaseConstants.ADV_Wait; + private Listener listener; + private String mAdUnitId = BaseConstants.CODE_AD_CQP; + private TTRewardVideoAd mTTRewardVideoAd; // 插全屏广告对象 + private TTAdNative.RewardVideoAdListener mRewardVideoListener; // 广告加载监听器 + private TTRewardVideoAd.RewardAdInteractionListener mRewardVideoAdInteractionListener; // 广告展示监听器 + private LoadingDialog loadingDialog; + + private boolean isRewardValid;//是否获取奖励 + + public AdRewardUtils() { + + } + + public AdRewardUtils(Context mContext, Listener listener) { + this.listener = listener; + init(mContext); + } + + public AdRewardUtils(Context mContext, boolean load, Listener listener) { + this.listener = listener; + init(mContext, load); + } + + public static void printShowInfo(MediationBaseManager adInfo) { + MediationBaseManager mediationManager = adInfo; + if (mediationManager != null) { + MediationAdEcpmInfo showEcpm = mediationManager.getShowEcpm(); + if (showEcpm != null) { + logEcpmInfo(showEcpm); + } + } + } + + public static void logEcpmInfo(MediationAdEcpmInfo item) { + LogK.e("EcpmInfo: \n" + + "SdkName: " + item.getSdkName() + ",\n" + + "CustomSdkName: " + item.getCustomSdkName() + ",\n" + + "SlotId: " + item.getSlotId() + ",\n" + + // 单位:分 + "Ecpm: " + item.getEcpm() + ",\n" + + "ReqBiddingType: " + item.getReqBiddingType() + ",\n" + + "ErrorMsg: " + item.getErrorMsg() + ",\n" + + "RequestId: " + item.getRequestId() + ",\n" + + "RitType: " + item.getRitType() + ",\n" + + "AbTestId: " + item.getAbTestId() + ",\n" + + "ScenarioId: " + item.getScenarioId() + ",\n" + + "SegmentId: " + item.getSegmentId() + ",\n" + + "Channel: " + item.getChannel() + ",\n" + + "SubChannel: " + item.getSubChannel() + ",\n" + + "customData: " + item.getCustomData() + ); + } + + public void init(Context mContext) { + if (!requestAD) { + requestAD = true; + new Handler().postDelayed(new Runnable() { + @Override + public void run() { + requestAD = false; + loadingDialog.cancel(); + } + }, 5000); + + loadingDialog = new LoadingDialog(mContext); + loadingDialog.show(); + init(mContext, true); + } + } + + public void init(Context mContext, boolean load) { + boolean csj = true; + if (new ADUtils("GMRewardAd", mContext).regexSwitch() && csj) { + loadRewardVideoAd(mContext); + } else { + if (listener != null) { + listener.success(false); + listener = null; + } + } + } + + private void loadRewardVideoAd(Context mContext) { + /** 1、创建AdSlot对象 */ + + AdSlot adslot = new AdSlot.Builder() + .setCodeId(BaseConstants.CODE_AD_REWARD) + .setOrientation(TTAdConstant.ORIENTATION_VERTICAL) + .setMediationAdSlot(new MediationAdSlot + .Builder() + .setExtraObject(MediationConstant.ADN_PANGLE, "pangleRewardCustomData")//服务端奖励验证透传参数 + .setExtraObject(MediationConstant.ADN_GDT, "gdtRewardCustomData") + .setExtraObject(MediationConstant.ADN_BAIDU, "baiduRewardCustomData") + .build()) + .build(); + + /** 2、创建TTAdNative对象 */ + + TTAdNative adNativeLoader = TTAdManagerHolder.get().createAdNative(mContext); + + /** 3、创建加载、展示监听器 */ + initListeners(mContext); + + /** 4、加载广告 */ + + adNativeLoader.loadRewardVideoAd(adslot, mRewardVideoListener); + } + + private void initListeners(Context mContext) { + // 广告加载监听器 + + this.mRewardVideoListener = new TTAdNative.RewardVideoAdListener() { + @Override + public void onError(int i, String s) { + LogK.e("reward load fail: errCode: " + i + ", errMsg: " + s); + } + + @Override + + public void onRewardVideoAdLoad(TTRewardVideoAd ttRewardVideoAd) { + LogK.e("reward load success"); + loadingDialog.cancel(); + mTTRewardVideoAd = ttRewardVideoAd; + showRewardVideoAd(mContext); + } + + @Override + + public void onRewardVideoCached() { + LogK.e("reward cached success"); + } + + @Override + + public void onRewardVideoCached(TTRewardVideoAd ttRewardVideoAd) { + LogK.e("reward cached success 2"); + mTTRewardVideoAd = ttRewardVideoAd; + showRewardVideoAd(mContext); + } + }; + // 广告展示监听器 + + this.mRewardVideoAdInteractionListener = new TTRewardVideoAd.RewardAdInteractionListener() { + @Override + + public void onAdShow() { + LogK.e("reward show"); + } + + @Override + + public void onAdVideoBarClick() { + LogK.e("reward click"); + } + + @Override + public void onAdClose() { + LogK.e("reward close"); + } + + @Override + public void onVideoComplete() { + LogK.e("reward onVideoComplete"); + success(); + } + + @Override + public void onVideoError() { + LogK.e("reward onVideoError"); + } + + @Override + + public void onRewardVerify(boolean b, int i, String s, int i1, String s1) { + LogK.e("reward onRewardVerify"); + } + + @Override + public void onRewardArrived(boolean isReward_Valid, int rewardType, Bundle extraInfo) {//奖励是否发放请依据isRewardValid + // 当用户的观看行为满足了奖励条件 + if (isReward_Valid) { + isRewardValid = true; + + success(); + } + } + + @Override + public void onSkippedVideo() { + LogK.e("reward onSkippedVideo"); + if (!isRewardValid) { + ToasterUtil.show("看完视频才可以获得奖励",3); + } + } + }; + } + + // 广告加载成功后,开始展示广告 + private void showRewardVideoAd(Context mContext) { + if (mTTRewardVideoAd == null) { + LogK.e("请先加载广告或等待广告加载完毕后再调用show方法"); + return; + } + + /** 5、设置展示监听器,展示广告 */ + mTTRewardVideoAd.setRewardAdInteractionListener(mRewardVideoAdInteractionListener); + + mTTRewardVideoAd.showRewardVideoAd((Activity) mContext); + } + + public interface Listener { + void success(boolean b); + } + + private void success() { + if (listener != null) { + if (isRewardValid) { + listener.success(true); + } else { + listener.success(false); + } + listener = null; + } + } + +} diff --git a/LibraryAd/src/main/java/com/tfq/ad/ad/AdSplashUtils.java b/LibraryAd/src/main/java/com/tfq/ad/ad/AdSplashUtils.java new file mode 100644 index 0000000..e08fb2e --- /dev/null +++ b/LibraryAd/src/main/java/com/tfq/ad/ad/AdSplashUtils.java @@ -0,0 +1,150 @@ +package com.tfq.ad.ad; + +import android.content.Context; +import android.os.Handler; +import android.os.Looper; +import android.view.View; +import android.widget.FrameLayout; + +import com.bytedance.sdk.openadsdk.AdSlot; +import com.bytedance.sdk.openadsdk.CSJAdError; +import com.bytedance.sdk.openadsdk.CSJSplashAd; +import com.bytedance.sdk.openadsdk.CSJSplashCloseType; +import com.bytedance.sdk.openadsdk.TTAdNative; +import com.tfq.library.app.BaseConstants; +import com.tfq.library.utils.LogK; + +public class AdSplashUtils { + private Context mContext; + private FrameLayout mSplashContainer; + private CSJSplashAd mCsjSplashAd; + private Listener listener; + + public interface Listener { + void success(long time); + } + + public AdSplashUtils(Context context, FrameLayout mSplashContainer, Listener listener) { + this.mContext = context; + this.mSplashContainer = mSplashContainer; + this.listener = listener; + + loadAndShowSplashAd(); + } + + private TTAdNative.CSJSplashAdListener mCSJSplashAdListener; + + private CSJSplashAd.SplashAdListener mCSJSplashInteractionListener; + + public void loadAndShowSplashAd() { + if (new ADUtils("GMSplashAd", mContext).regexSwitch()) { + LogK.e("init kaiping"); + init(); + } else if (listener != null) { + listener.success(0); + } + + } + + private void init() { + new Handler(Looper.myLooper()).postDelayed(new Runnable() { + @Override + public void run() { + finishAndBack(0); + } + }, 9000); + + AdSlot adSlot = new AdSlot.Builder() + .setCodeId(BaseConstants.CODE_AD_SPLASH) + .setImageAcceptedSize(UIUtils.getScreenWidthInPx(mContext), UIUtils.getAllScreenHeight(mContext)) + .build(); + TTAdNative adNativeLoader = TTAdManagerHolder.get().createAdNative(mContext); + initListeners(); + adNativeLoader.loadSplashAd(adSlot, mCSJSplashAdListener, 3500); + } + + private void initListeners() { + // 广告加载监听器 + mCSJSplashAdListener = new TTAdNative.CSJSplashAdListener() { + @Override + public void onSplashRenderSuccess(CSJSplashAd csjSplashAd) { + /** 5、渲染成功后,展示广告 */ + LogK.e("splash render success"); + mCsjSplashAd = csjSplashAd; + csjSplashAd.setSplashAdListener(mCSJSplashInteractionListener); + View splashView = csjSplashAd.getSplashView(); + UIUtils.removeFromParent(splashView); + mSplashContainer.removeAllViews(); + mSplashContainer.addView(splashView); + } + + public void onSplashLoadSuccess() { + LogK.e("splash load success"); + } + + @Override + + public void onSplashLoadSuccess(CSJSplashAd csjSplashAd) { + + } + + @Override + + public void onSplashLoadFail(CSJAdError csjAdError) { + LogK.e("splash load fail, errCode: " + csjAdError.getCode() + ", errMsg: " + csjAdError.getMsg()); + + finishAndBack(0); + } + + @Override + + public void onSplashRenderFail(CSJSplashAd csjSplashAd, CSJAdError csjAdError) { + LogK.e("splash render fail, errCode: " + csjAdError.getCode() + ", errMsg: " + csjAdError.getMsg()); + + finishAndBack(0); + } + }; + // 广告展示监听器 + + this.mCSJSplashInteractionListener = new CSJSplashAd.SplashAdListener() { + @Override + + public void onSplashAdShow(CSJSplashAd csjSplashAd) { + LogK.d("splash show"); + } + + @Override + + public void onSplashAdClick(CSJSplashAd csjSplashAd) { + LogK.d("splash click"); + } + + @Override + + public void onSplashAdClose(CSJSplashAd csjSplashAd, int closeType) { + if (closeType == CSJSplashCloseType.CLICK_SKIP) { + LogK.d("开屏广告点击跳过"); + finishAndBack(0); + } else if (closeType == CSJSplashCloseType.COUNT_DOWN_OVER) { + LogK.d("开屏广告点击倒计时结束"); + finishAndBack(0); + } else if (closeType == CSJSplashCloseType.CLICK_JUMP) { + LogK.d("点击跳转"); + } + } + }; + } + + private void finishAndBack(int time) { + if (listener != null) { + listener.success(time); + listener = null; + } + } + + public void onDestroy() { + if (mCsjSplashAd != null && mCsjSplashAd.getMediationManager() != null) { + mCsjSplashAd.getMediationManager().destroy(); + } + } +} diff --git a/LibraryAd/src/main/java/com/tfq/ad/ad/CacheADManager.java b/LibraryAd/src/main/java/com/tfq/ad/ad/CacheADManager.java new file mode 100644 index 0000000..dbd2eac --- /dev/null +++ b/LibraryAd/src/main/java/com/tfq/ad/ad/CacheADManager.java @@ -0,0 +1,106 @@ +package com.tfq.ad.ad; + +import com.bytedance.sdk.openadsdk.TTFeedAd; +import com.tfq.library.utils.LogK; + +import java.util.ArrayList; +import java.util.List; + +public class CacheADManager { + private static List mGMNativeADBean; + + public static void init() { + mGMNativeADBean = new ArrayList<>(); + } + + private static int contentID = -1; + + public static List getAdBean() { + return mGMNativeADBean; + } + + public static GMNativeADBean requestHaveAD(String adType, String mAdUnitId) { + GMNativeADBean bean = null; + switch (adType) { + case "feed": + if (getAdBean() != null && getAdBean().size() > 0) { + boolean b = getContentID(mAdUnitId); + if (b) { + try { + bean = getAdBean().get(contentID); + } catch (Exception e) { + e.printStackTrace(); + } + } else { + if (mGMNativeADBean != null && mGMNativeADBean.size() > 0) { + GMNativeADBean gmNativeADBean = mGMNativeADBean.get(0); + if (gmNativeADBean != null && gmNativeADBean.getmGMNativeAD() != null && gmNativeADBean.getmGMNativeAD().getMediationManager().isExpress()) { + bean = gmNativeADBean; + } else if (gmNativeADBean != null) { + mGMNativeADBean.remove(gmNativeADBean); + } + } + } + } + break; + } + return bean; + } + + private static void addAd(String mAdUnitId, long l_time, TTFeedAd o) { + GMNativeADBean bean = new GMNativeADBean(); + bean.setGMNativeADID(mAdUnitId); + bean.setmGMNativeAD(o); + bean.setSaveTime(l_time); + mGMNativeADBean.add(bean); + } + + public static void removeAD(String adType, String mAdUnitId) { + switch (adType) { + case "feed": + while (getContentID(mAdUnitId)) { + mGMNativeADBean.remove(contentID); + LogK.e("移除该广告"); + } + LogK.e("移除广告完成! "); + break; + } + } + + /** + * 有了回调 + */ + public static void addAdLoaded(String adType, String mAdUnitId, TTFeedAd mGMNativeAd) { + switch (adType) { + case "feed": + contentID = -1; + boolean b = getContentID(mAdUnitId); + if (b) { + GMNativeADBean bean = new GMNativeADBean(); + bean.setmGMNativeAD(mGMNativeAd); + bean.setSaveTime(System.currentTimeMillis()); + bean.setGMNativeADID(mAdUnitId); + mGMNativeADBean.set(contentID, bean); + } else { + addAd(mAdUnitId, System.currentTimeMillis(), mGMNativeAd); + } + break; + } + } + + public static boolean getContentID(String mAdUnitId) { + boolean b = false; + if (mGMNativeADBean != null && mGMNativeADBean.size() > 0) { + for (int i = 0; i < mGMNativeADBean.size(); i++) { + if (mAdUnitId.equals(mGMNativeADBean.get(i).getGMNativeADID())) { + contentID = i; + b = true; + } + } + return b; + } else { + return false; + } + } + +} diff --git a/LibraryAd/src/main/java/com/tfq/ad/ad/GMNativeADBean.java b/LibraryAd/src/main/java/com/tfq/ad/ad/GMNativeADBean.java new file mode 100644 index 0000000..bc5d74d --- /dev/null +++ b/LibraryAd/src/main/java/com/tfq/ad/ad/GMNativeADBean.java @@ -0,0 +1,60 @@ +package com.tfq.ad.ad; + +import com.bytedance.sdk.openadsdk.TTFeedAd; + +public class GMNativeADBean { + private String GMNativeADID; + private TTFeedAd mGMNativeAD; + private boolean mGMNativeADShow; + private boolean adUsed; + private long saveTime; + private boolean isList; + + public String getGMNativeADID() { + return GMNativeADID; + } + + public void setGMNativeADID(String GMNativeADID) { + this.GMNativeADID = GMNativeADID; + } + + public TTFeedAd getmGMNativeAD() { + return mGMNativeAD; + } + + public void setmGMNativeAD(TTFeedAd mGMNativeAD) { + this.mGMNativeAD = mGMNativeAD; + } + + public boolean ismGMNativeADShow() { + return mGMNativeADShow; + } + + public void setmGMNativeADShow(boolean mGMNativeADShow) { + this.mGMNativeADShow = mGMNativeADShow; + } + + public boolean isAdUsed() { + return adUsed; + } + + public void setAdUsed(boolean adUsed) { + this.adUsed = adUsed; + } + + public long getSaveTime() { + return saveTime; + } + + public void setSaveTime(long saveTime) { + this.saveTime = saveTime; + } + + public boolean isList() { + return isList; + } + + public void setList(boolean list) { + isList = list; + } +} diff --git a/LibraryAd/src/main/java/com/tfq/ad/ad/LoadingDialog.java b/LibraryAd/src/main/java/com/tfq/ad/ad/LoadingDialog.java new file mode 100644 index 0000000..0054e54 --- /dev/null +++ b/LibraryAd/src/main/java/com/tfq/ad/ad/LoadingDialog.java @@ -0,0 +1,61 @@ +package com.tfq.ad.ad; + +import android.app.Dialog; +import android.content.Context; +import android.graphics.drawable.AnimationDrawable; +import android.os.Bundle; +import android.view.LayoutInflater; +import android.view.View; +import android.view.Window; +import android.view.WindowManager; +import android.widget.ImageView; + +import com.tfq.libraryad.R; + +public class LoadingDialog extends Dialog { + private final Context mContext; + private final LayoutInflater inflater; + private Listener listener; + private View contentView; + + public LoadingDialog(Context context) { + super(context, R.style.CustomDialog); + this.mContext = context; + inflater = LayoutInflater.from(context); + } + + public LoadingDialog(Context context, Listener listener) { + super(context, R.style.CustomDialog); + this.listener = listener; + this.mContext = context; + inflater = LayoutInflater.from(context); + } + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setViews(); + } + + private void setViews() { + contentView = inflater.inflate(R.layout.layout_dialog_loading, null); + setContentView(contentView); + + ImageView loadingImageView = contentView.findViewById(R.id.loading_imageview); + AnimationDrawable loadingAnimation = (AnimationDrawable) loadingImageView.getBackground(); + loadingAnimation.start(); + + Window dialogWindow = getWindow(); + WindowManager.LayoutParams lp = dialogWindow.getAttributes(); + lp.width = UIUtils.dp2px(mContext, 70); + lp.height = UIUtils.dp2px(mContext, 70); + dialogWindow.setAttributes(lp); + setCanceledOnTouchOutside(false); + setCancelable(false); + } + + public interface Listener { + void callBack(); + } + +} diff --git a/LibraryAd/src/main/java/com/tfq/ad/ad/LoopAd.java b/LibraryAd/src/main/java/com/tfq/ad/ad/LoopAd.java new file mode 100644 index 0000000..cd86923 --- /dev/null +++ b/LibraryAd/src/main/java/com/tfq/ad/ad/LoopAd.java @@ -0,0 +1,94 @@ +package com.tfq.ad.ad; + +import android.util.Log; + +import com.tfq.library.app.BaseConstants; +import com.tfq.library.utils.LogK; + +import java.util.ArrayList; +import java.util.List; + +public class LoopAd { + + static List feedAD = new ArrayList(); + static List newFeedAD = new ArrayList(); + static int thisCurrentFeedID = 0; + static List drawAD = new ArrayList(); + static int thisCurrentDrawID = 0; + static List screenAD = new ArrayList(); + static int thisCurrentScreenID = 0; + static List splashAD = new ArrayList(); + static int thisCurrentSplashID = 0; + + public LoopAd() { + init(); + } + + public static void AdFeedIdAdd() { + if (thisCurrentFeedID < newFeedAD.size() - 1) { + thisCurrentFeedID++; + } else { + thisCurrentFeedID = 0; + } + } + + public static String getCurrentFeedID() { + return newFeedAD.get(thisCurrentFeedID); + } + + public static String getNextFeedID() { + int nextFeedID = 0; + if (thisCurrentFeedID < newFeedAD.size() - 1) { + nextFeedID = thisCurrentFeedID + 1; + } else { + nextFeedID = 0; + } + LogK.e("nextFeedID=" + nextFeedID); + return newFeedAD.get(nextFeedID); + } + + private void init() { + newFeedAD = new ArrayList(); + newFeedAD.add(BaseConstants.csjIdFeed1); + LogK.e("TT BaseConstants.csjIdFeed1=" + BaseConstants.csjIdFeed1); + newFeedAD.add(BaseConstants.csjIdFeed2); + LogK.e("TT BaseConstants.csjIdFeed2=" + BaseConstants.csjIdFeed2); + if (feedAD.size() == 0) { + feedAD.add("0"); + } + if (screenAD.size() == 0) { + screenAD.add("0"); + } + if (splashAD.size() == 0) { + splashAD.add("0"); + } + if (drawAD.size() == 0) { + drawAD.add("0"); + } + } + + public String GMNativeAd(int num) { + if (newFeedAD.size() > num) { + if (0 == num) { + String ad = newFeedAD.get(0); + if (ad != null) { + return ad; + } else { + return newFeedAD.get(0); + } + } else if (1 == num) { + String ad = newFeedAD.get(1); + if (ad != null) { + return ad; + } else { + return newFeedAD.get(0); + } + } else { + return newFeedAD.get(0); + } + } else { + return newFeedAD.get(0); + } + } + +} diff --git a/LibraryAd/src/main/java/com/tfq/ad/ad/SplashUtils.java b/LibraryAd/src/main/java/com/tfq/ad/ad/SplashUtils.java new file mode 100644 index 0000000..7c2acc8 --- /dev/null +++ b/LibraryAd/src/main/java/com/tfq/ad/ad/SplashUtils.java @@ -0,0 +1,60 @@ +package com.tfq.ad.ad; + +import android.app.Activity; +import android.content.Intent; + +import com.tfq.ad.ad.activity.AdSplashActivity; +import com.tfq.library.app.BaseConstants; +import com.tfq.library.utils.AppUtil; +import com.tfq.library.utils.DateUtils; +import com.tfq.library.utils.LogK; + +import java.util.Timer; +import java.util.TimerTask; + +public class SplashUtils { + + private TimerTask task = null; + private long time = 1;//秒 + + /** + * 开关打开后,检测后台超过20秒后再回来加载开屏 + * + * @param mActivity + */ + public void startTimerTask(Activity mActivity) { + BaseConstants.request_splash_time = Long.parseLong(DateUtils.timeStamp()); + if (task == null) { + Timer timer = new Timer(); + task = new TimerTask() { + @Override + public void run() { + mActivity.runOnUiThread(new Runnable() { + @Override + public void run() { + long time = Long.parseLong(DateUtils.timeStamp()) - BaseConstants.request_splash_time; + if (!AppUtil.isRunningForeground(mActivity)) {//前台 +// LogK.e("相差time=" + time); + if (Long.parseLong(DateUtils.timeStamp()) - BaseConstants.request_splash_time >= 20) { + LogK.e("应当开启开屏页"); + if (!BaseConstants._isShow) { + BaseConstants._isShow = true; + if (new ADUtils("GMSplashAd", mActivity).regexSwitch()) { + Intent intent = new Intent(mActivity, AdSplashActivity.class); + mActivity.startActivity(intent); + } + } + } + BaseConstants.request_splash_time = Long.parseLong(DateUtils.timeStamp()); + } else { + LogK.e("APP后台状态time=" + time); + } + } + }); + } + }; + timer.schedule(task, 0, 2 * 1000 * time); + } + } + +} diff --git a/LibraryAd/src/main/java/com/tfq/ad/ad/TTAdManagerHolder.java b/LibraryAd/src/main/java/com/tfq/ad/ad/TTAdManagerHolder.java new file mode 100644 index 0000000..cb20c06 --- /dev/null +++ b/LibraryAd/src/main/java/com/tfq/ad/ad/TTAdManagerHolder.java @@ -0,0 +1,276 @@ +package com.tfq.ad.ad; + +import android.content.Context; +import android.os.Handler; + +import com.bytedance.sdk.openadsdk.TTAdConfig; +import com.bytedance.sdk.openadsdk.TTAdManager; +import com.bytedance.sdk.openadsdk.TTAdSdk; +import com.bytedance.sdk.openadsdk.TTCustomController; +import com.bytedance.sdk.openadsdk.mediation.init.MediationConfig; +import com.bytedance.sdk.openadsdk.mediation.init.MediationConfigUserInfoForSegment; +import com.bytedance.sdk.openadsdk.mediation.init.MediationPrivacyConfig; +import com.tfq.ad.app.AdApp; +import com.tfq.library.app.BaseConstants; +import com.tfq.library.utils.LogK; + +import org.json.JSONException; +import org.json.JSONObject; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.util.HashMap; +import java.util.Map; + + +/** + * 可以用一个单例来保存TTAdManager实例,在需要初始化sdk的时候调用 + */ +public class TTAdManagerHolder { + + static AdInitSuccess adSuccess; + private static boolean sInit; + private static boolean sStart; + //未初始化0;成功1;失败2; + private static int adInitState = 0; + + public static void getAdInitState(AdInitSuccess ad_Success) { + adSuccess = ad_Success; + if (adInitState == 1) { + adSuccess.success(); + } else if (adInitState == 2) { + adSuccess.error(); + } + new Handler().postDelayed(new Runnable() { + @Override + public void run() { + if (adInitState != 1) { + adSuccess.error(); + } + } + }, 5000); + } + + public static TTAdManager get() { + return TTAdSdk.getAdManager(); + } + + public static void init(final Context context) { + //初始化穿山甲SDK + doInit(context); + } + + //step1:接入网盟广告sdk的初始化操作,详情见接入文档和穿山甲平台说明 + private static void doInit(Context context) { + if (sInit) { +// Toast.makeText(context, "您已经初始化过了", Toast.LENGTH_LONG).show(); + return; + } + //TTAdSdk.init(context, buildConfig(context)); + //setp1.1:初始化SDK + + TTAdSdk.init(context, buildConfig(context)); + sInit = true; +// Toast.makeText(context, "初始化成功", Toast.LENGTH_LONG).show(); + + start(context); + } + + public static void start(Context context) { + if (!sInit) { +// Toast.makeText(context, "还没初始化SDK,请先进行初始化", Toast.LENGTH_LONG).show(); + return; + } + if (sStart) { + return; + } + //setp1.2:启动SDK + + TTAdSdk.start(new TTAdSdk.Callback() { + @Override + public void success() { + LogK.i("success: " + TTAdSdk.isInitSuccess()); +// startActivity(context); + adInitState = 1; + if (adSuccess != null) { + adSuccess.success(); + } + } + + @Override + public void fail(int code, String msg) { + sStart = false; + LogK.i("fail: code = " + code + " msg = " + msg); + adInitState = 2; + if (adSuccess != null) { + adSuccess.error(); + } + } + }); + sStart = true; + } + + private static TTAdConfig buildConfig(Context context) { + + LogK.e("TT BaseConstants.CSJ_ID="+ BaseConstants.CSJ_ID); + return new TTAdConfig.Builder() + /** + * 注:需要替换成在媒体平台申请的appID ,切勿直接复制 + */.appId(BaseConstants.CSJ_ID).appName(BaseConstants.APP_NAME).paid(true) + /** + * 上线前需要关闭debug开关,否则会影响性能 + */.debug(false) + /** + * 使用聚合功能此开关必须设置为true,默认为false,不会初始化聚合模板,聚合功能会吟唱 + */.useMediation(true).supportMultiProcess(true)//开启多进程 + .customController(getTTCustomController()) //如果您需要设置隐私策略请参考该api + .setMediationConfig(new MediationConfig.Builder() //可设置聚合特有参数详细设置请参考该api + .setMediationConfigUserInfoForSegment(getUserInfoForSegment())//如果您需要配置流量分组信息请参考该api + .setCustomLocalConfig(loadLocalConfig(context)).build()).build(); + } + + private static JSONObject loadLocalConfig(Context context) { + JSONObject jsonObject = null; + //读取json文件,本地缓存的配置 + try { + jsonObject = new JSONObject(getJson("site_config_" + BaseConstants.CSJ_ID, context)); + } catch (JSONException e) { + e.printStackTrace(); + } + return jsonObject; + } + + public static String getJson(String fileName, Context context) { + StringBuilder stringBuilder = new StringBuilder(); + try { + InputStream is = context.getAssets().open(fileName); + BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(is)); + String line; + while ((line = bufferedReader.readLine()) != null) { + stringBuilder.append(line); + } + } catch (IOException e) { + e.printStackTrace(); + } + return stringBuilder.toString(); + } + + private static MediationConfigUserInfoForSegment getUserInfoForSegment() { + MediationConfigUserInfoForSegment userInfo = new MediationConfigUserInfoForSegment(); + userInfo.setUserId("msdk-demo"); + userInfo.setGender(MediationConfigUserInfoForSegment.GENDER_MALE); + userInfo.setChannel(BaseConstants.CHANNEL); + userInfo.setSubChannel("msdk-sub-channel"); + userInfo.setAge(999); + userInfo.setUserValueGroup("msdk-demo-user-value-group"); + + Map customInfos = new HashMap<>(); + customInfos.put("aaaa", "test111"); + customInfos.put("bbbb", "test222"); + userInfo.setCustomInfos(customInfos); + return userInfo; + } + + private static TTCustomController getTTCustomController() { + return new TTCustomController() { + @Override + public boolean isCanUseLocation() { + boolean b = true; + String channel = BaseConstants.CHANNEL; + LogK.d("TT channel=" + channel); + if (channel != null) { + if (channel.equals("oppo")) { + b = false; + } + } + return b; + } + + @Override + public boolean isCanUseWifiState() { + return super.isCanUseWifiState(); + } + + @Override + public String getMacAddress() { + return super.getMacAddress(); + } + + @Override + public boolean isCanUseWriteExternal() { + return super.isCanUseWriteExternal(); + } + + @Override + public String getDevOaid() { + return super.getDevOaid(); + } + + @Override + public boolean isCanUseAndroidId() { + return super.isCanUseAndroidId(); + } + + @Override + public String getAndroidId() { + return super.getAndroidId(); + } + + @Override + public MediationPrivacyConfig getMediationPrivacyConfig() { + return new MediationPrivacyConfig() { + + @Override + public boolean isLimitPersonalAds() { + return super.isLimitPersonalAds(); + } + + @Override + public boolean isProgrammaticRecommend() { + return super.isProgrammaticRecommend(); + } + + @Override + public boolean isCanUseOaid() { + boolean b = true; + String channel = BaseConstants.CHANNEL; + LogK.d("TT channel2=" + channel); + if (channel != null) { + if (channel.equals("honor")) { + b = false; + } + } + return b; + } + }; + } + + @Override + public boolean isCanUsePermissionRecordAudio() { + return super.isCanUsePermissionRecordAudio(); + } + + @Override + public boolean alist() { + boolean b = true; + String channel = BaseConstants.CHANNEL; + LogK.d("TT channel3=" + channel); + if (channel != null) { + if (channel.equals("ali") || channel.equals("oppo") || channel.equals("vivo") || channel.equals("huawei") + || channel.equals("honor") || channel.equals("xiaomi")) { + b = false; + } + } + return b; + } + }; + } + + public interface AdInitSuccess { + void success(); + + void error(); + } +} diff --git a/LibraryAd/src/main/java/com/tfq/ad/ad/UIUtils.java b/LibraryAd/src/main/java/com/tfq/ad/ad/UIUtils.java new file mode 100644 index 0000000..afa435c --- /dev/null +++ b/LibraryAd/src/main/java/com/tfq/ad/ad/UIUtils.java @@ -0,0 +1,348 @@ +package com.tfq.ad.ad; + +import android.app.Activity; +import android.content.Context; +import android.os.Build; +import android.text.TextUtils; +import android.util.DisplayMetrics; +import android.view.Display; +import android.view.DisplayCutout; +import android.view.View; +import android.view.ViewGroup; +import android.view.ViewParent; +import android.view.WindowInsets; +import android.view.WindowManager; +import android.widget.FrameLayout; +import android.widget.LinearLayout; +import android.widget.RelativeLayout; + +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; + +/** + * Created by bytedance on 2019/9/5. + */ + +public class UIUtils { + + public static float getScreenWidthDp(Context context){ + final float scale = context.getResources().getDisplayMetrics().density; + float width = context.getResources().getDisplayMetrics().widthPixels; + return width / (scale <= 0 ? 1 : scale) + 0.5f; + } + + //全面屏、刘海屏适配 + public static float getHeight(Activity activity) { + hideBottomUIMenu(activity); + float height; + int realHeight = getRealHeight(activity); + if (UIUtils.hasNotchScreen(activity)) { + height = px2dip(activity, realHeight - getStatusBarHeight(activity)); + }else { + height = px2dip(activity, realHeight); + } + return height; + } + + public static void hideBottomUIMenu(Activity activity) { + if (activity == null) { + return; + } + try { + //隐藏虚拟按键,并且全屏 + if (Build.VERSION.SDK_INT > 11 && Build.VERSION.SDK_INT < 19) { // lower api + View v = activity.getWindow().getDecorView(); + v.setSystemUiVisibility(View.GONE); + } else if (Build.VERSION.SDK_INT >= 19) { + //for new api versions. + View decorView = activity.getWindow().getDecorView(); + int uiOptions = View.SYSTEM_UI_FLAG_LAYOUT_STABLE + | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION + | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN + | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION // hide nav bar + // | View.SYSTEM_UI_FLAG_FULLSCREEN // hide status bar + | View.SYSTEM_UI_FLAG_IMMERSIVE; + decorView.setSystemUiVisibility(uiOptions); + activity.getWindow().addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION); + } + } catch (Exception e) { + e.printStackTrace(); + } + } + + //获取屏幕真实高度,不包含下方虚拟导航栏 + public static int getRealHeight(Context context) { + WindowManager windowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE); + Display display = windowManager.getDefaultDisplay(); + DisplayMetrics dm = new DisplayMetrics(); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) { + display.getRealMetrics(dm); + } else { + display.getMetrics(dm); + } + int realHeight = dm.heightPixels; + return realHeight; + } + + //获取状态栏高度 + public static float getStatusBarHeight(Context context) { + float height = 0; + int resourceId = context.getApplicationContext().getResources().getIdentifier("status_bar_height", "dimen", "android"); + if (resourceId > 0) { + height = context.getApplicationContext().getResources().getDimensionPixelSize(resourceId); + } + return height; + } + + //获取导航栏高度 + public static float getNavigationBarHeight(Context context) { + float height = 0; + int resourceId = context.getApplicationContext().getResources().getIdentifier("navigation_bar_height", "dimen", "android"); + if (resourceId > 0) { + height = context.getApplicationContext().getResources().getDimensionPixelSize(resourceId); + } + return height; + } + + public static int px2dip(Context context, float pxValue) { + final float scale = context.getResources().getDisplayMetrics().density; + return (int) (pxValue / (scale <= 0 ? 1 : scale) + 0.5f); + } + + public static int dp2px(Context context, float dp) { + final float scale = context.getResources().getDisplayMetrics().density; + return (int) (dp * scale + 0.5f); + } + + /** + * 判断是否是刘海屏 + * @return + */ + public static boolean hasNotchScreen(Activity activity){ + return isAndroidPHasNotch(activity) + || getInt("ro.miui.notch", activity) == 1 + || hasNotchAtHuawei(activity) + || hasNotchAtOPPO(activity) + || hasNotchAtVivo(activity); + } + + /** + * Android P 刘海屏判断 + * @param activity + * @return + */ + public static boolean isAndroidPHasNotch(Activity activity){ + boolean result = false; + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) { + DisplayCutout displayCutout = null; + try { + WindowInsets windowInsets = activity.getWindow().getDecorView().getRootWindowInsets(); + if (windowInsets != null) { + displayCutout = windowInsets.getDisplayCutout(); + } + if (displayCutout != null) { + result = true; + } + } catch (Exception e) { + e.printStackTrace(); + } + } + return result; + } + + /** + * 小米刘海屏判断. + * @return 0 if it is not notch ; return 1 means notch + * @throws IllegalArgumentException if the key exceeds 32 characters + */ + public static int getInt(String key,Activity activity) { + int result = 0; + if (isMiui()){ + try { + ClassLoader classLoader = activity.getClassLoader(); + @SuppressWarnings("rawtypes") + Class SystemProperties = classLoader.loadClass("android.os.SystemProperties"); + //参数类型 + @SuppressWarnings("rawtypes") + Class[] paramTypes = new Class[2]; + paramTypes[0] = String.class; + paramTypes[1] = int.class; + Method getInt = SystemProperties.getMethod("getInt", paramTypes); + //参数 + Object[] params = new Object[2]; + params[0] = new String(key); + params[1] = new Integer(0); + result = (Integer) getInt.invoke(SystemProperties, params); + + } catch (ClassNotFoundException e) { + e.printStackTrace(); + } catch (NoSuchMethodException e) { + e.printStackTrace(); + } catch (IllegalAccessException e) { + e.printStackTrace(); + } catch (IllegalArgumentException e) { + e.printStackTrace(); + } catch (InvocationTargetException e) { + e.printStackTrace(); + } + } + return result; + } + + /** + * 华为刘海屏判断 + * @return + */ + public static boolean hasNotchAtHuawei(Context context) { + boolean ret = false; + try { + ClassLoader classLoader = context.getClassLoader(); + Class HwNotchSizeUtil = classLoader.loadClass("com.huawei.android.util.HwNotchSizeUtil"); + Method get = HwNotchSizeUtil.getMethod("hasNotchInScreen"); + ret = (boolean) get.invoke(HwNotchSizeUtil); + } catch (ClassNotFoundException e) { + } catch (NoSuchMethodException e) { + } catch (Exception e) { + } finally { + return ret; + } + } + + public static final int VIVO_NOTCH = 0x00000020;//是否有刘海 + public static final int VIVO_FILLET = 0x00000008;//是否有圆角 + + /** + * VIVO刘海屏判断 + * @return + */ + public static boolean hasNotchAtVivo(Context context) { + boolean ret = false; + try { + ClassLoader classLoader = context.getClassLoader(); + Class FtFeature = classLoader.loadClass("android.util.FtFeature"); + Method method = FtFeature.getMethod("isFeatureSupport", int.class); + ret = (boolean) method.invoke(FtFeature, VIVO_NOTCH); + } catch (ClassNotFoundException e) { + } catch (NoSuchMethodException e) { + } catch (Exception e) { + } finally { + return ret; + } + } + /** + * O-P-P-O刘海屏判断 + * @return + */ + public static boolean hasNotchAtOPPO(Context context) { + String temp = "com.kllk.feature.screen.heteromorphism"; + String name = getKllkDecryptString(temp); + return context.getPackageManager().hasSystemFeature(name); + } + + public static boolean isMiui() { + boolean sIsMiui = false; + try { + Class clz = Class.forName("miui.os.Build"); + if (clz != null) { + sIsMiui = true; + //noinspection ConstantConditions + return sIsMiui; + } + } catch (Exception e) { + // ignore + } + return sIsMiui; + } + + /** + *用于o-p-p-o 版本隐私协议 + */ + public static String getKllkDecryptString(String encryptionString) { + + if (TextUtils.isEmpty(encryptionString)) { + return ""; + } + String decryptTag = ""; + String decryptCapitalized = "O" + "P" + "P" + "O"; + String decrypt = "o" + "p" + "p" + "o"; + if (encryptionString.contains("KLLK")) { + decryptTag = encryptionString.replace("KLLK", decryptCapitalized); + } else if (encryptionString.contains("kllk")) { + decryptTag = encryptionString.replace("kllk", decrypt); + } + return decryptTag; + + } + + public static void setViewSize(View view, int width, int height) { + if (view.getParent() instanceof FrameLayout) { + FrameLayout.LayoutParams lp = (FrameLayout.LayoutParams) view.getLayoutParams(); + lp.width = width; + lp.height = height; + view.setLayoutParams(lp); + view.requestLayout(); + } else if (view.getParent() instanceof RelativeLayout) { + RelativeLayout.LayoutParams lp = (RelativeLayout.LayoutParams) view.getLayoutParams(); + lp.width = width; + lp.height = height; + view.setLayoutParams(lp); + view.requestLayout(); + } else if (view.getParent() instanceof LinearLayout) { + LinearLayout.LayoutParams lp = (LinearLayout.LayoutParams) view.getLayoutParams(); + lp.width = width; + lp.height = height; + view.setLayoutParams(lp); + view.requestLayout(); + } + } + + public static int getScreenWidthInPx(Context context) { + DisplayMetrics dm = context.getApplicationContext().getResources().getDisplayMetrics(); + return dm.widthPixels; + } + + public static int getScreenHeightInPx(Context context) { + DisplayMetrics dm = context.getApplicationContext().getResources().getDisplayMetrics(); + return dm.heightPixels; + } + + public static int getScreenHeight(Context context) { + return (int) (getScreenHeightInPx(context) + getStatusBarHeight(context)); + } + + public static int getAllScreenHeight(Context context) { + return (int) (getScreenHeightInPx(context) + getStatusBarHeight(context) + getNavigationBarHeight(context)); + } + + public static void removeFromParent(View view) { + if (view != null) { + ViewParent vp = view.getParent(); + if (vp instanceof ViewGroup) { + ((ViewGroup) vp).removeView(view); + } + } + } + + /** + * 获取全面屏宽高 + * @param context + * @return + */ + public static int[] getScreenSize(Context context) { + int[] size = new int[]{0,0}; + if (context == null){ + return size; + } + WindowManager windowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE); + Display display = windowManager.getDefaultDisplay(); + DisplayMetrics dm = new DisplayMetrics(); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) { + display.getRealMetrics(dm); + } else { + display.getMetrics(dm); + } + size[0] = dm.widthPixels; + size[1] = dm.heightPixels; + return size; + } +} diff --git a/LibraryAd/src/main/java/com/tfq/ad/ad/activity/AdSplashActivity.java b/LibraryAd/src/main/java/com/tfq/ad/ad/activity/AdSplashActivity.java new file mode 100644 index 0000000..4c12cf2 --- /dev/null +++ b/LibraryAd/src/main/java/com/tfq/ad/ad/activity/AdSplashActivity.java @@ -0,0 +1,64 @@ +package com.tfq.ad.ad.activity; + +import android.os.Bundle; +import android.view.KeyEvent; +import android.widget.FrameLayout; +import android.widget.ImageView; + +import com.tfq.ad.ad.AdSplashUtils; +import com.tfq.library.app.BaseConstants; +import com.tfq.library.base.BaseActivity; +import com.tfq.library.utils.AppUtil; +import com.tfq.libraryad.R; + +public class AdSplashActivity extends BaseActivity { + FrameLayout mSplashContainer; + ImageView ivSplash; + AdSplashUtils mAdSplashManager; + + @Override + protected int getLayoutId() { + return R.layout.ad_splash; + } + + @Override + protected void initView() { + AppUtil.setHideBar_StatusAndNavigation(this); + setContentView(R.layout.ad_splash); + mSplashContainer = findViewById(R.id.frameLayout); + ivSplash = findViewById(R.id.iv_splash); + + ivSplash.setBackgroundResource(BaseConstants.appSplash); + } + + @Override + protected void initData(Bundle savedInstanceState) { + initData(); + } + + protected void initData() { + BaseConstants._isShow = true; + mAdSplashManager = new AdSplashUtils(this, mSplashContainer, new AdSplashUtils.Listener() { + @Override + public void success(long time) { + finish(); + BaseConstants._isShow = false; + } + }); + } + + @Override + protected void onDestroy() { + super.onDestroy(); + if (mAdSplashManager != null) { + mAdSplashManager.onDestroy(); + } + } + + public boolean onKeyDown(int keyCode, KeyEvent event) { + if (keyCode == KeyEvent.KEYCODE_BACK) { + return true; + } + return super.onKeyDown(keyCode, event); + } +} diff --git a/LibraryAd/src/main/java/com/tfq/ad/ad/gdt/GdtCustomerBanner.java b/LibraryAd/src/main/java/com/tfq/ad/ad/gdt/GdtCustomerBanner.java new file mode 100644 index 0000000..392647f --- /dev/null +++ b/LibraryAd/src/main/java/com/tfq/ad/ad/gdt/GdtCustomerBanner.java @@ -0,0 +1,169 @@ +package com.tfq.ad.ad.gdt; + +import android.app.Activity; +import android.content.Context; +import android.util.Log; +import android.view.View; + +import com.bytedance.sdk.openadsdk.AdSlot; +import com.bytedance.sdk.openadsdk.mediation.MediationConstant; +import com.bytedance.sdk.openadsdk.mediation.bridge.custom.banner.MediationCustomBannerLoader; +import com.bytedance.sdk.openadsdk.mediation.custom.MediationCustomServiceConfig; +import com.qq.e.ads.banner2.UnifiedBannerADListener; +import com.qq.e.ads.banner2.UnifiedBannerView; +import com.qq.e.comm.util.AdError; + +import java.util.concurrent.Callable; +import java.util.concurrent.Future; +import java.util.concurrent.TimeUnit; + +/** + * YLH Banner自定义Adapter + */ +public class GdtCustomerBanner extends MediationCustomBannerLoader { + + private static final String TAG = GdtCustomerBanner.class.getSimpleName(); + + private UnifiedBannerView mUnifiedBannerView; + + + @Override + public void load(Context context, AdSlot adSlot, MediationCustomServiceConfig mediationCustomServiceConfig) { + /** + * 在子线程中进行广告加载 + */ + ThreadUtils.runOnThreadPool(new Runnable() { + @Override + public void run() { + if (context instanceof Activity) { + mUnifiedBannerView = new UnifiedBannerView((Activity) context, mediationCustomServiceConfig.getADNNetworkSlotId(), + new UnifiedBannerADListener() { + @Override + public void onNoAD(AdError adError) { + if (adError != null) { + Log.i(TAG, "onNoAD errorCode = " + adError.getErrorCode() + " errorMessage = " + adError.getErrorMsg()); + callLoadFail(adError.getErrorCode(), adError.getErrorMsg()); + } else { + callLoadFail(99999, "no ad"); + } + } + + @Override + public void onADReceive() { + Log.i(TAG, "onADReceive"); + if (isClientBidding()) {//bidding类型广告 + double ecpm = mUnifiedBannerView.getECPM();//当无权限调用该接口时,SDK会返回错误码-1 + if (ecpm < 0) { + ecpm = 0; + } + Log.e(TAG, "ecpm:" + ecpm); + callLoadSuccess(ecpm); + } else {//普通类型广告 + callLoadSuccess(); + } + } + + @Override + public void onADExposure() { + Log.i(TAG, "onADExposure"); + callBannerAdShow(); + } + + @Override + public void onADClosed() { + Log.i(TAG, "onADClosed"); + callBannerAdClosed(); + } + + @Override + public void onADClicked() { + Log.i(TAG, "onADClicked"); + callBannerAdClick(); + } + + @Override + public void onADLeftApplication() { + Log.i(TAG, "onADLeftApplication"); + } + }); + mUnifiedBannerView.setRefresh(0); // 设置0表示不轮播,m统一处理了轮播无需设置 + mUnifiedBannerView.loadAD(); + } else { + callLoadFail(40000, "context is not Activity"); + } + } + }); + } + + @Override + public View getAdView() { + return mUnifiedBannerView; + } + + @Override + public MediationConstant.AdIsReadyStatus isReadyCondition() { + /** + * 在子线程中进行广告是否可用的判断 + */ + Future future = ThreadUtils.runOnThreadPool(new Callable() { + @Override + public MediationConstant.AdIsReadyStatus call() throws Exception { + if (mUnifiedBannerView != null && mUnifiedBannerView.isValid()) { + return MediationConstant.AdIsReadyStatus.AD_IS_READY; + } else { + return MediationConstant.AdIsReadyStatus.AD_IS_NOT_READY; + } + } + }); + try { + MediationConstant.AdIsReadyStatus result = future.get(500, TimeUnit.MILLISECONDS);//设置500毫秒的总超时,避免线程阻塞 + if (result != null) { + return result; + } else { + return MediationConstant.AdIsReadyStatus.AD_IS_NOT_READY; + } + } catch (Exception e) { + e.printStackTrace(); + } + return MediationConstant.AdIsReadyStatus.AD_IS_NOT_READY; + } + + @Override + public void onPause() { + super.onPause(); + Log.i(TAG, "onPause"); + } + + @Override + public void onResume() { + super.onResume(); + Log.i(TAG, "onResume"); + } + + @Override + public void onDestroy() { + super.onDestroy(); + Log.i(TAG, "onDestroy"); + /** + * 在子线程中进行广告销毁 + */ + ThreadUtils.runOnUIThread(new Runnable() { + @Override + public void run() { + if (mUnifiedBannerView != null) { + mUnifiedBannerView.destroy(); + mUnifiedBannerView = null; + } + } + }); + } + + /** + * 是否是Bidding广告 + * + * @return + */ + public boolean isClientBidding() { + return getBiddingType() == MediationConstant.AD_TYPE_CLIENT_BIDING; + } +} diff --git a/LibraryAd/src/main/java/com/tfq/ad/ad/gdt/GdtCustomerConfig.java b/LibraryAd/src/main/java/com/tfq/ad/ad/gdt/GdtCustomerConfig.java new file mode 100644 index 0000000..6a92b3c --- /dev/null +++ b/LibraryAd/src/main/java/com/tfq/ad/ad/gdt/GdtCustomerConfig.java @@ -0,0 +1,86 @@ +package com.tfq.ad.ad.gdt; + +import android.content.Context; + +import com.bytedance.sdk.openadsdk.mediation.bridge.custom.MediationCustomInitLoader; +import com.bytedance.sdk.openadsdk.mediation.custom.MediationCustomInitConfig; +import com.qq.e.comm.managers.GDTAdSdk; +import com.qq.e.comm.managers.setting.GlobalSetting; +import com.qq.e.comm.managers.status.SDKStatus; + +import java.util.Map; +import java.util.concurrent.Callable; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.Future; + +/** + * YLH 自定义初始化类 + */ +public class GdtCustomerConfig extends MediationCustomInitLoader { + + private static final String TAG = GdtCustomerConfig.class.getSimpleName(); + + @Override + public String getNetworkSdkVersion() { + return SDKStatus.getIntegrationSDKVersion(); + } + + + @Override + public void initializeADN(Context context, MediationCustomInitConfig mediationCustomInitConfig, Map map) { + /** + * 在子线程中进行初始化 + */ + ThreadUtils.runOnThreadPool(new Runnable() { + @Override + public void run() { + GDTAdSdk.init(context, mediationCustomInitConfig.getAppId()); + GlobalSetting.setPersonalizedState(1);//优量汇个性化推荐广告开关,0为开启个性化推荐广告,1为屏蔽个性化推荐广告。建议打开,提升广告收益 + //初始化成功回调 + callInitSuccess(); + } + }); + } + + @Override + public String getBiddingToken(Context context, Map extra) { + ExecutorService executor = Executors.newSingleThreadExecutor(); + Future future = executor.submit(new Callable() { + @Override + public String call() throws Exception { + return GDTAdSdk.getGDTAdManger().getBuyerId(null); //开发者可不传; + } + }); + try { + return future.get(); + } catch (ExecutionException e) { + e.printStackTrace(); + } catch (InterruptedException e) { + e.printStackTrace(); + } + return ""; + } + + @Override + public String getSdkInfo(Context context, Map extra) { + ExecutorService executor = Executors.newSingleThreadExecutor(); + Future future = executor.submit(new Callable() { + @Override + public String call() throws Exception { + String posId = (String) extra.get("slot_id"); + return GDTAdSdk.getGDTAdManger().getSDKInfo(posId); + } + }); + try { + return future.get(); + } catch (ExecutionException e) { + e.printStackTrace(); + } catch (InterruptedException e) { + e.printStackTrace(); + } + return ""; + } + +} diff --git a/LibraryAd/src/main/java/com/tfq/ad/ad/gdt/GdtCustomerDraw.java b/LibraryAd/src/main/java/com/tfq/ad/ad/gdt/GdtCustomerDraw.java new file mode 100644 index 0000000..4d9ccb3 --- /dev/null +++ b/LibraryAd/src/main/java/com/tfq/ad/ad/gdt/GdtCustomerDraw.java @@ -0,0 +1,96 @@ +package com.tfq.ad.ad.gdt; + +import android.content.Context; +import android.util.Log; + +import com.bytedance.sdk.openadsdk.AdSlot; +import com.bytedance.sdk.openadsdk.mediation.MediationConstant; +import com.bytedance.sdk.openadsdk.mediation.bridge.custom.draw.MediationCustomDrawLoader; +import com.bytedance.sdk.openadsdk.mediation.custom.MediationCustomServiceConfig; +import com.qq.e.ads.cfg.VideoOption; +import com.qq.e.ads.nativ.NativeADUnifiedListener; +import com.qq.e.ads.nativ.NativeUnifiedAD; +import com.qq.e.ads.nativ.NativeUnifiedADData; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +/** + * Created by zhy on date + * Usage: + * Doc: + */ +public class GdtCustomerDraw extends MediationCustomDrawLoader { + + private static final String TAG = GdtCustomerDraw.class.getSimpleName(); + private VideoOption videoOption; + private volatile NativeUnifiedAD nativeUnifiedAD; + + @Override + public void load(Context context, AdSlot adSlot, MediationCustomServiceConfig serviceConfig) { + /** + * 在子线程中进行广告加载 + */ + ThreadUtils.runOnThreadPool(new Runnable() { + @Override + public void run() { + NativeADUnifiedListener nativeADUnifiedListener = new NativeADUnifiedListener() { + @Override + public void onADLoaded(List list) { + if (list != null && list.size() > 0) { + List adList = new ArrayList<>(); + for (NativeUnifiedADData adData : list) { + adList.add(new GdtDrawAd(adData, videoOption)); + } + callLoadSuccess(adList); + } else { + callLoadFail(40000, "no ad"); + } + } + + @Override + public void onNoAD(com.qq.e.comm.util.AdError adError) { + if (adError != null) { + Log.i(TAG, "onNoAD errorCode = " + adError.getErrorCode() + " errorMessage = " + adError.getErrorMsg()); + callLoadFail(adError.getErrorCode(), adError.getErrorMsg()); + } else { + callLoadFail(40000, "no ad"); + } + } + }; + + if (isServerBidding()) { + nativeUnifiedAD = new NativeUnifiedAD(context, serviceConfig.getADNNetworkSlotId(), nativeADUnifiedListener, getAdm()); + } else { + nativeUnifiedAD = new NativeUnifiedAD(context, serviceConfig.getADNNetworkSlotId(), nativeADUnifiedListener); + } + + nativeUnifiedAD.loadData(1); + } + }); + } + + /** + * 是否clientBidding广告 + * + * @return + */ + public boolean isClientBidding() { + return getBiddingType() == MediationConstant.AD_TYPE_CLIENT_BIDING; + } + + /** + * 是否serverBidding广告 + * + * @return + */ + public boolean isServerBidding() { + return getBiddingType() == MediationConstant.AD_TYPE_SERVER_BIDING; + } + + @Override + public void receiveBidResult(boolean win, double winnerPrice, int loseReason, Map extra) { + super.receiveBidResult(win, winnerPrice, loseReason, extra); + } +} \ No newline at end of file diff --git a/LibraryAd/src/main/java/com/tfq/ad/ad/gdt/GdtCustomerFullVideo.java b/LibraryAd/src/main/java/com/tfq/ad/ad/gdt/GdtCustomerFullVideo.java new file mode 100644 index 0000000..27a893c --- /dev/null +++ b/LibraryAd/src/main/java/com/tfq/ad/ad/gdt/GdtCustomerFullVideo.java @@ -0,0 +1,285 @@ +package com.tfq.ad.ad.gdt; + +import android.app.Activity; +import android.content.Context; +import android.util.Log; + +import com.bytedance.sdk.openadsdk.AdSlot; +import com.bytedance.sdk.openadsdk.mediation.MediationConstant; +import com.bytedance.sdk.openadsdk.mediation.bridge.custom.fullvideo.MediationCustomFullVideoLoader; +import com.bytedance.sdk.openadsdk.mediation.custom.MediationCustomServiceConfig; +import com.bytedance.sdk.openadsdk.mediation.custom.MediationRewardItem; +import com.qq.e.ads.interstitial2.ADRewardListener; +import com.qq.e.ads.interstitial2.UnifiedInterstitialAD; +import com.qq.e.ads.interstitial2.UnifiedInterstitialADListener; +import com.qq.e.ads.interstitial2.UnifiedInterstitialMediaListener; +import com.qq.e.comm.util.AdError; + +import java.util.Map; +import java.util.concurrent.Callable; +import java.util.concurrent.Future; +import java.util.concurrent.TimeUnit; + +/** + * YLH 全屏广告自定义Adapter + */ +public class GdtCustomerFullVideo extends MediationCustomFullVideoLoader { + + private static final String TAG = GdtCustomerFullVideo.class.getSimpleName(); + + private volatile UnifiedInterstitialAD mUnifiedInterstitialAD; + private boolean isLoadSuccess; + + @Override + public void load(Context context, AdSlot adSlot, MediationCustomServiceConfig serviceConfig) { + /** + * 在子线程中进行广告加载 + */ + ThreadUtils.runOnThreadPool(new Runnable() { + @Override + public void run() { + if (context instanceof Activity) { + mUnifiedInterstitialAD = new UnifiedInterstitialAD((Activity) context, serviceConfig.getADNNetworkSlotId(), new UnifiedInterstitialADListener() { + @Override + public void onADReceive() { + isLoadSuccess = true; + Log.i(TAG, "onADReceive"); + if (isClientBidding()) {//bidding类型广告 + double ecpm = mUnifiedInterstitialAD.getECPM(); //当无权限调用该接口时,SDK会返回错误码-1 + if (ecpm < 0) { + ecpm = 0; + } + Log.e(TAG, "ecpm:" + ecpm); + callLoadSuccess(ecpm); + } else {//普通类型广告 + callLoadSuccess(); + } + } + + @Override + public void onVideoCached() { + Log.i(TAG, "onVideoCached"); + callAdVideoCache(); + } + + @Override + public void onNoAD(AdError adError) { + isLoadSuccess = false; + if (adError != null) { + Log.i(TAG, "onNoAD errorCode = " + adError.getErrorCode() + " errorMessage = " + adError.getErrorMsg()); + callLoadFail(adError.getErrorCode(), adError.getErrorMsg()); + } else { + callLoadFail(40000, "no ad"); + } + } + + @Override + public void onADOpened() { + Log.i(TAG, "onADOpened"); + } + + @Override + public void onADExposure() { + Log.i(TAG, "onADExposure"); + callFullVideoAdShow(); + } + + @Override + public void onADClicked() { + Log.i(TAG, "onADClicked"); + callFullVideoAdClick(); + } + + @Override + public void onADLeftApplication() { + Log.i(TAG, "onADLeftApplication"); + } + + @Override + public void onADClosed() { + Log.i(TAG, "onADClosed"); + callFullVideoAdClosed(); + } + + @Override + public void onRenderSuccess() { + Log.i(TAG, "onRenderSuccess"); + } + + @Override + public void onRenderFail() { + Log.i(TAG, "onRenderFail"); + } + }); + mUnifiedInterstitialAD.setMediaListener(new UnifiedInterstitialMediaListener() { + @Override + public void onVideoInit() { + Log.i(TAG, "onVideoInit"); + } + + @Override + public void onVideoLoading() { + Log.i(TAG, "onVideoLoading"); + } + + @Override + public void onVideoReady(long l) { + Log.i(TAG, "onVideoReady"); + } + + @Override + public void onVideoStart() { + Log.i(TAG, "onVideoStart"); + } + + @Override + public void onVideoPause() { + Log.i(TAG, "onVideoPause"); + } + + @Override + public void onVideoComplete() { + Log.i(TAG, "onVideoComplete"); + callFullVideoComplete(); + } + + @Override + public void onVideoError(AdError adError) { + Log.i(TAG, "onVideoError"); + callFullVideoError(); + } + + @Override + public void onVideoPageOpen() { + Log.i(TAG, "onVideoPageOpen"); + } + + @Override + public void onVideoPageClose() { + Log.i(TAG, "onVideoPageClose"); + } + }); + mUnifiedInterstitialAD.setRewardListener(new ADRewardListener() { + @Override + public void onReward(Map map) { + Log.e(TAG, "onReward"); + float amount = 0f; + String name = ""; + float finalAmount = amount; + String finalName = name; + callFullVideoRewardVerify(new MediationRewardItem() { + @Override + public boolean rewardVerify() { + return true; + } + + @Override + public float getAmount() { + return finalAmount; + } + + @Override + public String getRewardName() { + return finalName; + } + + @Override + public Map getCustomData() { + return map; + } + }); + } + }); + mUnifiedInterstitialAD.loadFullScreenAD(); + } else { + callLoadFail(40000, "context is not Activity"); + } + } + }); + } + + @Override + public void showAd(Activity activity) { + Log.i(TAG, "自定义的showAd"); + /** + * 先切子线程,再在子线程中切主线程进行广告展示 + */ + ThreadUtils.runOnUIThreadByThreadPool(new Runnable() { + @Override + public void run() { + if (mUnifiedInterstitialAD != null) { + mUnifiedInterstitialAD.showFullScreenAD(activity); + } + } + }); + + } + + @Override + public MediationConstant.AdIsReadyStatus isReadyCondition() { + /** + * 在子线程中进行广告是否可用的判断 + */ + Future future = ThreadUtils.runOnThreadPool(new Callable() { + @Override + public MediationConstant.AdIsReadyStatus call() throws Exception { + if (mUnifiedInterstitialAD != null && mUnifiedInterstitialAD.isValid()) { + return MediationConstant.AdIsReadyStatus.AD_IS_READY; + } else { + return MediationConstant.AdIsReadyStatus.AD_IS_NOT_READY; + } + } + }); + try { + MediationConstant.AdIsReadyStatus result = future.get(500, TimeUnit.MILLISECONDS); //设置500毫秒的总超时,避免线程阻塞 + if (result != null) { + return result; + } else { + return MediationConstant.AdIsReadyStatus.AD_IS_NOT_READY; + } + } catch (Exception e) { + e.printStackTrace(); + } + return MediationConstant.AdIsReadyStatus.AD_IS_NOT_READY; + } + + @Override + public void onPause() { + super.onPause(); + Log.i(TAG, "onPause"); + } + + @Override + public void onResume() { + super.onResume(); + Log.i(TAG, "onResume"); + } + + @Override + public void onDestroy() { + super.onDestroy(); + Log.i(TAG, "onDestroy"); + + /** + * 在子线程中进行广告销毁 + */ + ThreadUtils.runOnThreadPool(new Runnable() { + @Override + public void run() { + if (mUnifiedInterstitialAD != null) { + mUnifiedInterstitialAD.destroy(); + mUnifiedInterstitialAD = null; + } + } + }); + } + + /** + * 是否是ClientBidding广告 + * + * @return + */ + public boolean isClientBidding() { + return getBiddingType() == MediationConstant.AD_TYPE_CLIENT_BIDING; + } +} diff --git a/LibraryAd/src/main/java/com/tfq/ad/ad/gdt/GdtCustomerInterstitial.java b/LibraryAd/src/main/java/com/tfq/ad/ad/gdt/GdtCustomerInterstitial.java new file mode 100644 index 0000000..61a11b7 --- /dev/null +++ b/LibraryAd/src/main/java/com/tfq/ad/ad/gdt/GdtCustomerInterstitial.java @@ -0,0 +1,221 @@ +package com.tfq.ad.ad.gdt; + +import android.app.Activity; +import android.content.Context; + +import com.bytedance.sdk.openadsdk.AdSlot; +import com.bytedance.sdk.openadsdk.mediation.MediationConstant; +import com.bytedance.sdk.openadsdk.mediation.bridge.custom.interstitial.MediationCustomInterstitialLoader; +import com.bytedance.sdk.openadsdk.mediation.custom.MediationCustomServiceConfig; +import com.qq.e.ads.interstitial2.UnifiedInterstitialAD; +import com.qq.e.ads.interstitial2.UnifiedInterstitialADListener; +import com.qq.e.comm.util.AdError; +import com.tfq.library.utils.LogK; + +import java.util.concurrent.Callable; +import java.util.concurrent.Future; +import java.util.concurrent.TimeUnit; + +/** + * YLH 插屏广告自定义Adapter + */ +public class GdtCustomerInterstitial extends MediationCustomInterstitialLoader { + + private static final String TAG = GdtCustomerInterstitial.class.getSimpleName(); + + private volatile UnifiedInterstitialAD mUnifiedInterstitialAD; + private boolean isLoadSuccess; + + @Override + public void load(Context context, AdSlot adSlot, MediationCustomServiceConfig serviceConfig) { + /** + * 在子线程中进行广告加载 + */ + ThreadUtils.runOnThreadPool(new Runnable() { + @Override + public void run() { + if (context instanceof Activity) { + UnifiedInterstitialADListener unifiedInterstitialADListener = new UnifiedInterstitialADListener() { + @Override + public void onADReceive() { + isLoadSuccess = true; + LogK.i(TAG + "onADReceive"); + if (isClientBidding()) { //bidding类型广告 + double ecpm = mUnifiedInterstitialAD.getECPM();//当无权限调用该接口时,SDK会返回错误码-1 + if (ecpm < 0) { + ecpm = 0; + } + LogK.e(TAG + " ecpm:" + ecpm); + callLoadSuccess(ecpm); + } else {//普通类型广告 + callLoadSuccess(); + } + } + + @Override + public void onVideoCached() { + LogK.i(TAG + "onVideoCached"); + } + + @Override + public void onNoAD(AdError adError) { + isLoadSuccess = false; + if (adError != null) { + LogK.i(TAG + "onNoAD errorCode = " + adError.getErrorCode() + " errorMessage = " + adError.getErrorMsg()); + callLoadFail(adError.getErrorCode(), adError.getErrorMsg()); + } else { + callLoadFail(40000, "no ad"); + } + } + + @Override + public void onADOpened() { + LogK.i(TAG + "onADOpened"); + callInterstitialAdOpened(); + } + + @Override + public void onADExposure() { + LogK.i(TAG + "onADExposure"); + callInterstitialShow(); + } + + @Override + public void onADClicked() { + LogK.i(TAG + "onADClicked"); + callInterstitialAdClick(); + } + + @Override + public void onADLeftApplication() { + LogK.i(TAG + "onADLeftApplication"); + callInterstitialAdLeftApplication(); + } + + @Override + public void onADClosed() { + LogK.i(TAG + "onADClosed"); + callInterstitialClosed(); + } + + @Override + public void onRenderSuccess() { + + } + + @Override + public void onRenderFail() { + + } + }; + if (isServerBidding()) { + mUnifiedInterstitialAD = new UnifiedInterstitialAD((Activity) context, serviceConfig.getADNNetworkSlotId(), unifiedInterstitialADListener, null, getAdm()); + } else { + mUnifiedInterstitialAD = new UnifiedInterstitialAD((Activity) context, serviceConfig.getADNNetworkSlotId(), unifiedInterstitialADListener); + } + mUnifiedInterstitialAD.loadAD(); + } else { + callLoadFail(40000, "context is not Activity"); + } + + } + }); + } + + @Override + public void showAd(Activity activity) { + LogK.i(TAG + "自定义的showAd"); + /** + * 先切子线程,再在子线程中切主线程进行广告展示 + */ + ThreadUtils.runOnUIThreadByThreadPool(new Runnable() { + @Override + public void run() { + if (mUnifiedInterstitialAD != null) { + if (isServerBidding()) { + mUnifiedInterstitialAD.setBidECPM(mUnifiedInterstitialAD.getECPM()); + } + mUnifiedInterstitialAD.show(activity); + } + } + }); + + } + + @Override + public MediationConstant.AdIsReadyStatus isReadyCondition() { + /** + * 在子线程中进行广告是否可用的判断 + */ + Future future = ThreadUtils.runOnThreadPool(new Callable() { + @Override + public MediationConstant.AdIsReadyStatus call() throws Exception { + if (mUnifiedInterstitialAD != null && mUnifiedInterstitialAD.isValid()) { + return MediationConstant.AdIsReadyStatus.AD_IS_READY; + } else { + return MediationConstant.AdIsReadyStatus.AD_IS_NOT_READY; + } + } + }); + try { + MediationConstant.AdIsReadyStatus result = future.get(500, TimeUnit.MILLISECONDS);//设置500毫秒的总超时,避免线程阻塞 + if (result != null) { + return result; + } else { + return MediationConstant.AdIsReadyStatus.AD_IS_NOT_READY; + } + } catch (Exception e) { + e.printStackTrace(); + } + return MediationConstant.AdIsReadyStatus.AD_IS_NOT_READY; + } + + @Override + public void onPause() { + super.onPause(); + LogK.i(TAG + "onPause"); + } + + @Override + public void onResume() { + super.onResume(); + LogK.i(TAG + "onResume"); + } + + @Override + public void onDestroy() { + super.onDestroy(); + LogK.i(TAG + "onDestroy"); + + /** + * 在子线程中进行广告销毁 + */ + ThreadUtils.runOnThreadPool(new Runnable() { + @Override + public void run() { + if (mUnifiedInterstitialAD != null) { + mUnifiedInterstitialAD.destroy(); + mUnifiedInterstitialAD = null; + } + } + }); + } + + /** + * 是否clientBidding广告 + * + * @return + */ + public boolean isClientBidding() { + return getBiddingType() == MediationConstant.AD_TYPE_CLIENT_BIDING; + } + + /** + * 是否serverBidding广告 + * + * @return + */ + public boolean isServerBidding() { + return getBiddingType() == MediationConstant.AD_TYPE_SERVER_BIDING; + } +} diff --git a/LibraryAd/src/main/java/com/tfq/ad/ad/gdt/GdtCustomerNative.java b/LibraryAd/src/main/java/com/tfq/ad/ad/gdt/GdtCustomerNative.java new file mode 100644 index 0000000..0011bfa --- /dev/null +++ b/LibraryAd/src/main/java/com/tfq/ad/ad/gdt/GdtCustomerNative.java @@ -0,0 +1,235 @@ +package com.tfq.ad.ad.gdt; + +import android.content.Context; + +import com.bytedance.sdk.openadsdk.AdSlot; +import com.bytedance.sdk.openadsdk.mediation.MediationConstant; +import com.bytedance.sdk.openadsdk.mediation.bridge.custom.native_ad.MediationCustomNativeLoader; +import com.bytedance.sdk.openadsdk.mediation.custom.MediationCustomServiceConfig; +import com.qq.e.ads.nativ.ADSize; +import com.qq.e.ads.nativ.NativeADUnifiedListener; +import com.qq.e.ads.nativ.NativeExpressAD; +import com.qq.e.ads.nativ.NativeExpressADView; +import com.qq.e.ads.nativ.NativeUnifiedAD; +import com.qq.e.ads.nativ.NativeUnifiedADData; +import com.qq.e.comm.util.AdError; +import com.tfq.library.utils.LogK; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * YLH 信息流广告自定义Adapter + */ +public class GdtCustomerNative extends MediationCustomNativeLoader { + + private static final String TAG = "TTMediationSDK_" + GdtCustomerNative.class.getSimpleName(); + + @Override + public void load(Context context, AdSlot adSlot, MediationCustomServiceConfig serviceConfig) { + /** + * 在子线程中进行广告加载 + */ + LogK.e(TAG + "load gdt custom native ad-----"); + ThreadUtils.runOnThreadPool(new Runnable() { + @Override + public void run() { + if (isNativeAd()) { + LogK.i(TAG + "自渲染"); + //自渲染类型 + NativeUnifiedAD nativeUnifiedAD = null; + NativeADUnifiedListener nativeADUnifiedListener = new NativeADUnifiedListener() { + @Override + public void onADLoaded(List list) { + List tempList = new ArrayList<>(); + for (NativeUnifiedADData feedAd : list) { + GdtNativeAd gdtNativeAd = new GdtNativeAd(context, feedAd, adSlot); + //添加扩展参数 + Map extraMsg = new HashMap<>(); + extraMsg.put("key1_自渲染", "value1_自渲染"); + extraMsg.put("key2_自渲染", "value2_自渲染"); + extraMsg.put("key3_自渲染", "value3_自渲染"); + gdtNativeAd.setMediaExtraInfo(extraMsg); + + if (isClientBidding()) {//bidding广告类型 + double ecpm = feedAd.getECPM();//当无权限调用该接口时,SDK会返回错误码-1 + if (ecpm < 0) { + ecpm = 0; + } + LogK.e(TAG + "ecpm:" + ecpm); + gdtNativeAd.setBiddingPrice(ecpm); //回传竞价广告价格 + } + tempList.add(gdtNativeAd); + } + callLoadSuccess(tempList); + } + + @Override + public void onNoAD(AdError adError) { + if (adError != null) { + LogK.i(TAG + "onNoAD errorCode = " + adError.getErrorCode() + " errorMessage = " + adError.getErrorMsg()); + callLoadFail(adError.getErrorCode(), adError.getErrorMsg()); + } else { + callLoadFail(40000, "no ad"); + } + } + }; + if (isServerBidding()) { + nativeUnifiedAD = new NativeUnifiedAD(context, serviceConfig.getADNNetworkSlotId(), nativeADUnifiedListener, getAdm()); + } else { + nativeUnifiedAD = new NativeUnifiedAD(context, serviceConfig.getADNNetworkSlotId(), nativeADUnifiedListener); + } + + int maxVideoDuration = GdtUtils.getGDTMaxVideoDuration(adSlot); + int minVideoDuration = GdtUtils.getGDTMinVideoDuration(adSlot); + if (maxVideoDuration > 0) { + nativeUnifiedAD.setMaxVideoDuration(maxVideoDuration); + } + if (minVideoDuration > 0) { + nativeUnifiedAD.setMinVideoDuration(minVideoDuration); + } + nativeUnifiedAD.loadData(1); + } else if (isExpressRender()) { + LogK.i(TAG + "模板"); + //模板类型 + NativeExpressAD nativeExpressAD = null; + NativeExpressAD.NativeExpressADListener nativeExpressADListener = new NativeExpressAD.NativeExpressADListener() { + private final Map mListenerMap = new HashMap<>(); + + @Override + public void onADLoaded(List list) { + List tempList = new ArrayList<>(); + for (NativeExpressADView feedAd : list) { + GdtNativeExpressAd gdtNativeAd = new GdtNativeExpressAd(feedAd, adSlot); + //添加扩展参数 + Map extraMsg = new HashMap<>(); + extraMsg.put("key1_模板", "value1_模板"); + extraMsg.put("key2_模板", "value2_模板"); + extraMsg.put("key3_模板", "value3_模板"); + gdtNativeAd.setMediaExtraInfo(extraMsg); + + if (isClientBidding()) {//bidding广告类型 + double ecpm = feedAd.getECPM();//当无权限调用该接口时,SDK会返回错误码-1 + if (ecpm < 0) { + ecpm = 0; + } + LogK.e(TAG + " ecpm:" + ecpm); + gdtNativeAd.setBiddingPrice(ecpm); //回传竞价广告价格 + } + mListenerMap.put(feedAd, gdtNativeAd); + tempList.add(gdtNativeAd); + } + callLoadSuccess(tempList); + } + + @Override + public void onRenderFail(NativeExpressADView nativeExpressADView) { + LogK.i(TAG + "onRenderFail"); + GdtNativeExpressAd gdtNativeAd = mListenerMap.get(nativeExpressADView); + if (gdtNativeAd != null) { + gdtNativeAd.callRenderFail(nativeExpressADView, 99999, "render fail"); + } + } + + @Override + public void onRenderSuccess(NativeExpressADView nativeExpressADView) { + LogK.i(TAG + "onRenderSuccess"); + GdtNativeExpressAd gdtNativeAd = mListenerMap.get(nativeExpressADView); + if (gdtNativeAd != null) { + gdtNativeAd.callRenderSuccess(ADSize.FULL_WIDTH, ADSize.AUTO_HEIGHT); + } + } + + @Override + public void onADExposure(NativeExpressADView nativeExpressADView) { + LogK.i(TAG + "onADExposure"); + GdtNativeExpressAd gdtNativeAd = mListenerMap.get(nativeExpressADView); + if (gdtNativeAd != null) { + gdtNativeAd.callAdShow(); + } + } + + @Override + public void onADClicked(NativeExpressADView nativeExpressADView) { + LogK.i(TAG + "onADClicked"); + GdtNativeExpressAd gdtNativeAd = mListenerMap.get(nativeExpressADView); + if (gdtNativeAd != null) { + gdtNativeAd.callAdClick(); + } + } + + @Override + public void onADClosed(NativeExpressADView nativeExpressADView) { + LogK.i(TAG + "onADClosed"); + GdtNativeExpressAd gdtNativeAd = mListenerMap.get(nativeExpressADView); + if (gdtNativeAd != null) { + gdtNativeAd.onDestroy(); + } + mListenerMap.remove(nativeExpressADView); + } + + @Override + public void onADLeftApplication(NativeExpressADView nativeExpressADView) { + LogK.i(TAG + "onADLeftApplication"); + } + + @Override + public void onNoAD(AdError adError) { + if (adError != null) { + LogK.i(TAG + "onNoAD errorCode = " + adError.getErrorCode() + " errorMessage = " + adError.getErrorMsg()); + callLoadFail(adError.getErrorCode(), adError.getErrorMsg()); + } else { + callLoadFail(40000, "no ad"); + } + } + }; + + if (isServerBidding()) { + nativeExpressAD = new NativeExpressAD(context, getAdSize(adSlot), serviceConfig.getADNNetworkSlotId(), nativeExpressADListener, getAdm()); + } else { + nativeExpressAD = new NativeExpressAD(context, getAdSize(adSlot), serviceConfig.getADNNetworkSlotId(), nativeExpressADListener); + } + nativeExpressAD.loadAD(1); + } else { + LogK.i(TAG + "其他类型"); + //其他类型,开发者如果有需要,请在平台自行配置json,然后通过 serviceConfig.getCustomAdapterJson() 获取配置 + LogK.i(TAG + "1111111111111111111111111111111111111111111111"); + LogK.i(TAG + "serviceConfig.getCustomAdapterJson()=" + serviceConfig.getCustomAdapterJson()); + } + } + }); + } + + private ADSize getAdSize(AdSlot adSlot) { + ADSize adSize = new ADSize(ADSize.FULL_WIDTH, ADSize.AUTO_HEIGHT); // 消息流中用AUTO_HEIGHT + if (adSlot.getImgAcceptedWidth() > 0) { + adSize = new ADSize(adSlot.getImgAcceptedWidth(), ADSize.AUTO_HEIGHT); + } + return adSize; + } + + /** + * 是否clientBidding广告 + * + * @return + */ + public boolean isClientBidding() { + return getBiddingType() == MediationConstant.AD_TYPE_CLIENT_BIDING; + } + + /** + * 是否serverBidding广告 + * + * @return + */ + public boolean isServerBidding() { + return getBiddingType() == MediationConstant.AD_TYPE_SERVER_BIDING; + } + + @Override + public void receiveBidResult(boolean win, double winnerPrice, int loseReason, Map extra) { + super.receiveBidResult(win, winnerPrice, loseReason, extra); + } +} diff --git a/LibraryAd/src/main/java/com/tfq/ad/ad/gdt/GdtCustomerReward.java b/LibraryAd/src/main/java/com/tfq/ad/ad/gdt/GdtCustomerReward.java new file mode 100644 index 0000000..24d8a56 --- /dev/null +++ b/LibraryAd/src/main/java/com/tfq/ad/ad/gdt/GdtCustomerReward.java @@ -0,0 +1,230 @@ +package com.tfq.ad.ad.gdt; + +import android.app.Activity; +import android.content.Context; +import android.util.Log; + +import com.bytedance.sdk.openadsdk.AdSlot; +import com.bytedance.sdk.openadsdk.mediation.MediationConstant; +import com.bytedance.sdk.openadsdk.mediation.bridge.custom.reward.MediationCustomRewardVideoLoader; +import com.bytedance.sdk.openadsdk.mediation.custom.MediationCustomServiceConfig; +import com.bytedance.sdk.openadsdk.mediation.custom.MediationRewardItem; +import com.qq.e.ads.rewardvideo.RewardVideoAD; +import com.qq.e.ads.rewardvideo.RewardVideoADListener; +import com.qq.e.comm.util.AdError; + +import java.util.Map; +import java.util.concurrent.Callable; +import java.util.concurrent.Future; +import java.util.concurrent.TimeUnit; + +/** + * YLH 激励视频广告自定义Adapter + */ +public class GdtCustomerReward extends MediationCustomRewardVideoLoader { + + private static final String TAG = "TTMediationSDK_" + GdtCustomerReward.class.getSimpleName(); + + private volatile RewardVideoAD mRewardVideoAD; + private boolean isLoadSuccess; + + @Override + public void load(Context context, AdSlot adSlot, MediationCustomServiceConfig serviceConfig) { + + /** + * 在子线程中进行广告加载 + */ + ThreadUtils.runOnThreadPool(new Runnable() { + @Override + public void run() { + RewardVideoADListener rewardVideoADListener = new RewardVideoADListener() { + @Override + public void onADLoad() { + isLoadSuccess = true; + Log.i(TAG, "onADLoad"); + if (isClientBidding()) {//bidding类型广告 + double ecpm = mRewardVideoAD.getECPM(); //当无权限调用该接口时,SDK会返回错误码-1 + if (ecpm < 0) { + ecpm = 0; + } + Log.e(TAG, "ecpm:" + ecpm); + callLoadSuccess(ecpm); + } else {//普通类型广告 + callLoadSuccess(); + } + } + + @Override + public void onVideoCached() { + Log.i(TAG, "onVideoCached"); + callAdVideoCache(); + } + + @Override + public void onADShow() { + Log.i(TAG, "onADShow"); + callRewardVideoAdShow(); + } + + @Override + public void onADExpose() { + Log.i(TAG, "onADExpose"); + } + + @Override + public void onReward(Map map) { + Log.i(TAG, "onReward"); + callRewardVideoRewardVerify(new MediationRewardItem() { + @Override + public boolean rewardVerify() { + return true; + } + + @Override + public float getAmount() { + return 0; + } + + @Override + public String getRewardName() { + return null; + } + + @Override + public Map getCustomData() { + return map; + } + }); + } + + @Override + public void onADClick() { + Log.i(TAG, "onADClick"); + callRewardVideoAdClick(); + } + + @Override + public void onVideoComplete() { + Log.i(TAG, "onVideoComplete"); + callRewardVideoComplete(); + } + + @Override + public void onADClose() { + Log.i(TAG, "onADClose"); + callRewardVideoAdClosed(); + } + + @Override + public void onError(AdError adError) { + isLoadSuccess = false; + if (adError != null) { + Log.i(TAG, "onNoAD errorCode = " + adError.getErrorCode() + " errorMessage = " + adError.getErrorMsg()); + callLoadFail(adError.getErrorCode(), adError.getErrorMsg()); + } else { + callLoadFail(40000, "no ad"); + } + } + }; + + boolean isMuted = adSlot.getMediationAdSlot() == null ? false : adSlot.getMediationAdSlot().isMuted(); + if (isServerBidding()) { + mRewardVideoAD = new RewardVideoAD(context, serviceConfig.getADNNetworkSlotId(), rewardVideoADListener, !isMuted, getAdm()); + } else { + mRewardVideoAD = new RewardVideoAD(context, serviceConfig.getADNNetworkSlotId(), rewardVideoADListener, !isMuted); + } + mRewardVideoAD.loadAD(); + } + }); + } + + @Override + public void showAd(Activity activity) { + Log.i(TAG, "自定义的showAd"); + /** + * 先切子线程,再在子线程中切主线程进行广告展示 + */ + ThreadUtils.runOnUIThreadByThreadPool(new Runnable() { + @Override + public void run() { + if (mRewardVideoAD != null) { + if (isServerBidding()) { + mRewardVideoAD.setBidECPM(mRewardVideoAD.getECPM()); + } + mRewardVideoAD.showAD(activity); + } + } + }); + + } + + @Override + public MediationConstant.AdIsReadyStatus isReadyCondition() { + /** + * 在子线程中进行广告是否可用的判断 + */ + Future future = ThreadUtils.runOnThreadPool(new Callable() { + @Override + public MediationConstant.AdIsReadyStatus call() throws Exception { + if (mRewardVideoAD != null && mRewardVideoAD.isValid()) { + return MediationConstant.AdIsReadyStatus.AD_IS_READY; + } else { + return MediationConstant.AdIsReadyStatus.AD_IS_NOT_READY; + } + } + }); + try { + MediationConstant.AdIsReadyStatus result = future.get(500, TimeUnit.MILLISECONDS);//设置500毫秒的总超时,避免线程阻塞 + if (result != null) { + return result; + } else { + return MediationConstant.AdIsReadyStatus.AD_IS_NOT_READY; + } + } catch (Exception e) { + e.printStackTrace(); + } + return MediationConstant.AdIsReadyStatus.AD_IS_NOT_READY; + } + + @Override + public void onPause() { + super.onPause(); + Log.i(TAG, "onPause"); + } + + @Override + public void onResume() { + super.onResume(); + Log.i(TAG, "onResume"); + } + + @Override + public void onDestroy() { + super.onDestroy(); + Log.i(TAG, "onDestroy"); + mRewardVideoAD = null; + } + + /** + * 是否clientBidding广告 + * + * @return + */ + public boolean isClientBidding() { + return getBiddingType() == MediationConstant.AD_TYPE_CLIENT_BIDING; + } + + /** + * 是否serverBidding广告 + * + * @return + */ + public boolean isServerBidding() { + return getBiddingType() == MediationConstant.AD_TYPE_SERVER_BIDING; + } + + @Override + public void receiveBidResult(boolean win, double winnerPrice, int loseReason, Map extra) { + super.receiveBidResult(win, winnerPrice, loseReason, extra); + } +} diff --git a/LibraryAd/src/main/java/com/tfq/ad/ad/gdt/GdtCustomerSplash.java b/LibraryAd/src/main/java/com/tfq/ad/ad/gdt/GdtCustomerSplash.java new file mode 100644 index 0000000..f0360f4 --- /dev/null +++ b/LibraryAd/src/main/java/com/tfq/ad/ad/gdt/GdtCustomerSplash.java @@ -0,0 +1,195 @@ +package com.tfq.ad.ad.gdt; + +import android.content.Context; +import android.os.SystemClock; +import android.view.ViewGroup; + +import com.bytedance.sdk.openadsdk.AdSlot; +import com.bytedance.sdk.openadsdk.mediation.MediationConstant; +import com.bytedance.sdk.openadsdk.mediation.bridge.custom.splash.MediationCustomSplashLoader; +import com.bytedance.sdk.openadsdk.mediation.custom.MediationCustomServiceConfig; +import com.qq.e.ads.splash.SplashAD; +import com.qq.e.ads.splash.SplashADListener; +import com.qq.e.comm.util.AdError; +import com.tfq.library.utils.LogK; + +import java.util.concurrent.Callable; +import java.util.concurrent.Future; +import java.util.concurrent.TimeUnit; + +/** + * YLH 开屏广告自定义Adapter + */ +public class GdtCustomerSplash extends MediationCustomSplashLoader { + + private static final String TAG = "TTMediationSDK_" + GdtCustomerSplash.class.getSimpleName(); + private volatile SplashAD mSplashAD; + private boolean isLoadSuccess; + + + @Override + public void load(Context context, AdSlot adSlot, MediationCustomServiceConfig serviceConfig) { + /** + * 在子线程中进行广告加载 + */ + LogK.e(TAG + " load gdt custom splash ad-----"); + ThreadUtils.runOnThreadPool(new Runnable() { + @Override + public void run() { + SplashADListener splashADListener = new SplashADListener() { + @Override + public void onADDismissed() { + LogK.i(TAG + "onADDismissed"); + callSplashAdDismiss(); + } + + @Override + public void onNoAD(AdError adError) { + isLoadSuccess = false; + if (adError != null) { + LogK.i(TAG + "onNoAD errorCode = " + adError.getErrorCode() + " errorMessage = " + adError.getErrorMsg()); + callLoadFail(adError.getErrorCode(), adError.getErrorMsg()); + } else { + callLoadFail(40000, "no ad"); + } + } + + @Override + public void onADPresent() { + LogK.i(TAG + "onADPresent"); + } + + @Override + public void onADClicked() { + LogK.i(TAG + "onADClicked"); + callSplashAdClicked(); + } + + @Override + public void onADTick(long l) { + LogK.i(TAG + "onADTick"); + } + + @Override + public void onADExposure() { + LogK.i(TAG + "onADExposure"); + callSplashAdShow(); + } + + @Override + public void onADLoaded(long expireTimestamp) { + LogK.i(TAG + "onADLoaded"); + long timeIntervalSec = expireTimestamp - SystemClock.elapsedRealtime(); + if (timeIntervalSec > 1000) { + isLoadSuccess = true; + if (isClientBidding()) {//bidding类型广告 + double ecpm = mSplashAD.getECPM(); //当无权限调用该接口时,SDK会返回错误码-1 + if (ecpm < 0) { + ecpm = 0; + } + LogK.e(TAG + " ecpm:" + ecpm); + callLoadSuccess(ecpm); //bidding广告成功回调,回传竞价广告价格 + } else { //普通类型广告 + callLoadSuccess(); + } + } else { + isLoadSuccess = false; + callLoadFail(40000, "ad has expired"); + } + } + }; + if (isServerBidding()) { + mSplashAD = new SplashAD(context, serviceConfig.getADNNetworkSlotId(), splashADListener, 3000, getAdm()); + } else { + mSplashAD = new SplashAD(context, serviceConfig.getADNNetworkSlotId(), splashADListener, 3000); + } + mSplashAD.fetchAdOnly(); + } + }); + } + + @Override + public void showAd(ViewGroup container) { + /** + * 先切子线程,再在子线程中切主线程进行广告展示 + */ + ThreadUtils.runOnUIThreadByThreadPool(new Runnable() { + @Override + public void run() { + if (mSplashAD != null && container != null) { + container.removeAllViews(); + if (isServerBidding()) { + mSplashAD.setBidECPM(mSplashAD.getECPM()); + } + mSplashAD.showAd(container); + } + } + }); + } + + @Override + public void onPause() { + super.onPause(); + LogK.i(TAG + "onPause"); + } + + @Override + public void onResume() { + super.onResume(); + LogK.i(TAG + "onResume"); + } + + @Override + public void onDestroy() { + super.onDestroy(); + LogK.i(TAG + "onDestroy"); + mSplashAD = null; + } + + @Override + public MediationConstant.AdIsReadyStatus isReadyCondition() { + + /** + * 在子线程中进行广告是否可用的判断 + */ + Future future = ThreadUtils.runOnThreadPool(new Callable() { + @Override + public MediationConstant.AdIsReadyStatus call() throws Exception { + if (mSplashAD != null && mSplashAD.isValid()) { + return MediationConstant.AdIsReadyStatus.AD_IS_READY; + } else { + return MediationConstant.AdIsReadyStatus.AD_IS_NOT_READY; + } + } + }); + try { + MediationConstant.AdIsReadyStatus result = future.get(500, TimeUnit.MILLISECONDS);//设置500毫秒的总超时,避免线程阻塞 + if (result != null) { + return result; + } else { + return MediationConstant.AdIsReadyStatus.AD_IS_NOT_READY; + } + } catch (Exception e) { + e.printStackTrace(); + } + return MediationConstant.AdIsReadyStatus.AD_IS_NOT_READY; + } + + /** + * 是否clientBidding广告 + * + * @return + */ + public boolean isClientBidding() { + return getBiddingType() == MediationConstant.AD_TYPE_CLIENT_BIDING; + } + + /** + * 是否serverBidding广告 + * + * @return + */ + public boolean isServerBidding() { + return getBiddingType() == MediationConstant.AD_TYPE_SERVER_BIDING; + } +} diff --git a/LibraryAd/src/main/java/com/tfq/ad/ad/gdt/GdtDrawAd.java b/LibraryAd/src/main/java/com/tfq/ad/ad/gdt/GdtDrawAd.java new file mode 100644 index 0000000..a0e8ab4 --- /dev/null +++ b/LibraryAd/src/main/java/com/tfq/ad/ad/gdt/GdtDrawAd.java @@ -0,0 +1,318 @@ +package com.tfq.ad.ad.gdt; + + +import android.app.Activity; +import android.content.Context; +import android.text.TextUtils; +import android.util.Log; +import android.view.View; +import android.view.ViewGroup; +import android.widget.FrameLayout; + +import androidx.annotation.NonNull; + +import com.bytedance.sdk.openadsdk.TTAdConstant; +import com.bytedance.sdk.openadsdk.mediation.MediationConstant; +import com.bytedance.sdk.openadsdk.mediation.ad.MediationNativeAdAppInfo; +import com.bytedance.sdk.openadsdk.mediation.ad.MediationViewBinder; +import com.bytedance.sdk.openadsdk.mediation.bridge.custom.draw.MediationCustomDrawAd; +import com.qq.e.ads.cfg.VideoOption; +import com.qq.e.ads.nativ.MediaView; +import com.qq.e.ads.nativ.NativeADEventListener; +import com.qq.e.ads.nativ.NativeADMediaListener; +import com.qq.e.ads.nativ.NativeUnifiedADAppMiitInfo; +import com.qq.e.ads.nativ.NativeUnifiedADData; +import com.qq.e.ads.nativ.widget.NativeAdContainer; +import com.qq.e.comm.constants.AdPatternType; + +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.Callable; +import java.util.concurrent.Future; +import java.util.concurrent.TimeUnit; + +/** + * Created by zhy on date + * Usage: + * Doc: + */ +public class GdtDrawAd extends MediationCustomDrawAd { + public static final String TT_GDT_NATIVE_VIEW_TAG = "tt_gdt_developer_view"; + public static final String TT_GDT_NATIVE_ROOT_VIEW_TAG = "tt_gdt_developer_view_root"; + public static final String TT_GDT_NATIVE_LOGO_VIEW_TAG = "tt_gdt_developer_view_logo"; + private static final String TAG = GdtDrawAd.class.getSimpleName(); + NativeADMediaListener mGdtNativeADMediaListener = new NativeADMediaListener() { + @Override + public void onVideoInit() { + Log.d(TAG, "onVideoInit: "); + } + + @Override + public void onVideoLoading() { + Log.d(TAG, "onVideoLoading: "); + } + + @Override + public void onVideoReady() { + Log.d(TAG, "onVideoReady"); + } + + @Override + public void onVideoLoaded(int videoDuration) { + Log.d(TAG, "onVideoLoaded: "); + } + + @Override + public void onVideoStart() { + Log.d(TAG, "onVideoStart"); + callVideoStart(); + } + + @Override + public void onVideoPause() { + callVideoPause(); + } + + @Override + public void onVideoResume() { + callVideoResume(); + } + + @Override + public void onVideoCompleted() { + callVideoCompleted(); + } + + @Override + public void onVideoError(com.qq.e.comm.util.AdError error) { + if (error != null) { + callVideoError(error.getErrorCode(), error.getErrorMsg()); + } + } + + @Override + public void onVideoStop() { + Log.d(TAG, "onVideoStop"); + } + + @Override + public void onVideoClicked() { + Log.d(TAG, "onVideoClicked"); + callAdClick(); + } + }; + private NativeUnifiedADData mNativeAdData; + private VideoOption mVideoOption; + + public GdtDrawAd(NativeUnifiedADData data, VideoOption videoOption) { + this.mNativeAdData = data; + mVideoOption = videoOption; + NativeUnifiedADAppMiitInfo info = mNativeAdData.getAppMiitInfo(); + MediationNativeAdAppInfo nativeAdAppInfo = new MediationNativeAdAppInfo(); + if (info != null) { + nativeAdAppInfo.setAppName(info.getAppName()); + nativeAdAppInfo.setAuthorName(info.getAuthorName()); + nativeAdAppInfo.setPackageSizeBytes(info.getPackageSizeBytes()); + nativeAdAppInfo.setPermissionsUrl(info.getPermissionsUrl()); + nativeAdAppInfo.setPrivacyAgreement(info.getPrivacyAgreement()); + nativeAdAppInfo.setVersionName(info.getVersionName()); + } + setNativeAdAppInfo(nativeAdAppInfo); + this.setTitle(mNativeAdData.getTitle()); + this.setDescription(mNativeAdData.getDesc()); + this.setActionText(mNativeAdData.getCTAText()); + this.setIconUrl(mNativeAdData.getIconUrl()); + this.setImageUrl(mNativeAdData.getImgUrl()); + this.setImageWidth(mNativeAdData.getPictureWidth()); + this.setImageHeight(mNativeAdData.getPictureHeight()); + this.setImageList(mNativeAdData.getImgList()); + this.setStarRating(mNativeAdData.getAppScore()); + this.setSource(mNativeAdData.getTitle()); + this.setExpressAd(false); + if (isClientBidding()) { + int ecpm = mNativeAdData.getECPM();//当无权限调用该接口时,SDK会返回错误码-1 + if (ecpm < 0) { + ecpm = 0; + } + setBiddingPrice(ecpm);//获取本条广告实时的eCPM价格,单位是分; + Log.d(TAG, "GDT_clientBidding draw 返回的 cpm价格:" + mNativeAdData.getECPM()); + } + + if (mNativeAdData.getAdPatternType() == AdPatternType.NATIVE_VIDEO) { + this.setAdImageMode(TTAdConstant.IMAGE_MODE_VIDEO); + } else if (mNativeAdData.getAdPatternType() == AdPatternType.NATIVE_1IMAGE_2TEXT + || mNativeAdData.getAdPatternType() == AdPatternType.NATIVE_2IMAGE_2TEXT) { + this.setAdImageMode(TTAdConstant.IMAGE_MODE_LARGE_IMG); + } else if (mNativeAdData.getAdPatternType() == AdPatternType.NATIVE_3IMAGE) { + this.setAdImageMode(TTAdConstant.IMAGE_MODE_GROUP_IMG); + } + + if (mNativeAdData.isAppAd()) { + setInteractionType(TTAdConstant.INTERACTION_TYPE_DOWNLOAD); + } else { + setInteractionType(TTAdConstant.INTERACTION_TYPE_LANDING_PAGE); + } + } + + @Override + public void onResume() { + super.onResume(); + Log.i(TAG, "onResume"); + } + + @Override + public void onPause() { + super.onPause(); + Log.i(TAG, "onPause"); + } + + private void registerView(Context context, @NonNull ViewGroup container, List clickViews, List creativeViews, MediationViewBinder viewBinder) { + if (mNativeAdData != null && container instanceof FrameLayout) { + FrameLayout nativeAdView = (FrameLayout) container; + + + NativeAdContainer gdtNativeAdContainer; + + /** + * + * 防止gdtNativeAdContainer重复添加的问题 + * if true 表示 gdtNativeAdContainer已经添加到了FrameLayout ,就无须重复常见 ,但需要移除gdt往gdtNativeAdContainer添加的view(广点通的logo) + * if false 表示 gdtNativeAdContainer没有添加到FrameLayout ,需要创建 gdtNativeAdContainer + * + */ + if (nativeAdView.getChildAt(0) instanceof NativeAdContainer) { + gdtNativeAdContainer = (NativeAdContainer) nativeAdView.getChildAt(0); + for (int i = 0; i < gdtNativeAdContainer.getChildCount(); ) { + View view = gdtNativeAdContainer.getChildAt(i); + if (view != null) { + Object tag = view.getTag(com.bytedance.msdk.adapter.gdt.R.id.tt_mediation_gdt_developer_view_tag_key); + if (tag != null && (tag instanceof String) && ((String) tag).equals(TT_GDT_NATIVE_VIEW_TAG)) { + i++; + } else { + gdtNativeAdContainer.removeView(view); + } + } else { + i++; + } + } + } else { + gdtNativeAdContainer = new NativeAdContainer(context); + gdtNativeAdContainer.setTag(com.bytedance.msdk.adapter.gdt.R.id.tt_mediation_gdt_developer_view_root_tag_key, TT_GDT_NATIVE_ROOT_VIEW_TAG); + for (int i = 0; i < nativeAdView.getChildCount(); ) { + View view = nativeAdView.getChildAt(i); + view.setTag(com.bytedance.msdk.adapter.gdt.R.id.tt_mediation_gdt_developer_view_tag_key, TT_GDT_NATIVE_VIEW_TAG); + final int index = nativeAdView.indexOfChild(view); + nativeAdView.removeViewInLayout(view); + if (view != null) { + final ViewGroup.LayoutParams layoutParams = view.getLayoutParams(); + gdtNativeAdContainer.addView(view, index, layoutParams); + } + + } + nativeAdView.removeAllViews(); + nativeAdView.addView(gdtNativeAdContainer, ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT); + } + + + //找出视频容器 + FrameLayout ttMediaView = nativeAdView.findViewById(viewBinder.mediaViewId); + //绑定点击事件 + mNativeAdData.bindAdToView(context, gdtNativeAdContainer, null, clickViews, creativeViews); + + if (ttMediaView != null && mNativeAdData.getAdPatternType() == AdPatternType.NATIVE_VIDEO) { + MediaView gdtMediaView = new MediaView(context); + ttMediaView.removeAllViews(); + ttMediaView.addView(gdtMediaView, ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT); + mNativeAdData.bindMediaView(gdtMediaView, mVideoOption, mGdtNativeADMediaListener); + } + + if (!TextUtils.isEmpty(mNativeAdData.getCTAText())) { + View view = nativeAdView.findViewById(viewBinder.callToActionId); + List CTAViews = new ArrayList<>(); + CTAViews.add(view); + mNativeAdData.bindCTAViews(CTAViews); + } + + + mNativeAdData.setNativeAdEventListener(new NativeADEventListener() { + @Override + public void onADExposed() { + callAdShow(); + Log.d(TAG, "draw GDT --- onADExposed。。。。"); + } + + @Override + public void onADClicked() { + Log.d(TAG, "draw GDT --- onADClicked。。。。"); + callAdClick(); + } + + @Override + public void onADError(com.qq.e.comm.util.AdError error) { + Log.d(TAG, "GDT --- onADError error code :" + error.getErrorCode() + + " error msg: " + error.getErrorMsg()); + } + + @Override + public void onADStatusChanged() { + //app 下载状态改变 + } + }); + + } + } + + @Override + public void registerView(Activity activity, + ViewGroup container, + List clickViews, + List creativeViews, + List directDownloadViews, + MediationViewBinder viewBinder) { + super.registerView(activity, container, clickViews, creativeViews, directDownloadViews, viewBinder); + ThreadUtils.runOnUIThreadByThreadPool(new Runnable() { + @Override + public void run() { + if (creativeViews != null && directDownloadViews != null) { + creativeViews.addAll(directDownloadViews); + } + registerView(activity, container, clickViews, creativeViews, viewBinder); + } + }); + } + + @Override + public void onDestroy() { + super.onDestroy(); + Log.i(TAG, "onDestroy"); + } + + @Override + public MediationConstant.AdIsReadyStatus isReadyCondition() { + /** + * 在子线程中进行广告是否可用的判断 + */ + Future future = ThreadUtils.runOnThreadPool(new Callable() { + @Override + public MediationConstant.AdIsReadyStatus call() throws Exception { + if (mNativeAdData != null && mNativeAdData.isValid()) { + return MediationConstant.AdIsReadyStatus.AD_IS_READY; + } else { + return MediationConstant.AdIsReadyStatus.AD_IS_NOT_READY; + } + } + }); + try { + MediationConstant.AdIsReadyStatus result = future.get(500, TimeUnit.MILLISECONDS);//设置500毫秒的总超时,避免线程阻塞 + if (result != null) { + return result; + } else { + return MediationConstant.AdIsReadyStatus.AD_IS_NOT_READY; + } + } catch (Exception e) { + e.printStackTrace(); + } + return MediationConstant.AdIsReadyStatus.AD_IS_NOT_READY; + } +} + diff --git a/LibraryAd/src/main/java/com/tfq/ad/ad/gdt/GdtNativeAd.java b/LibraryAd/src/main/java/com/tfq/ad/ad/gdt/GdtNativeAd.java new file mode 100644 index 0000000..d36bcd7 --- /dev/null +++ b/LibraryAd/src/main/java/com/tfq/ad/ad/gdt/GdtNativeAd.java @@ -0,0 +1,334 @@ +package com.tfq.ad.ad.gdt; + +import android.app.Activity; +import android.content.Context; +import android.text.TextUtils; +import android.util.Log; +import android.view.View; +import android.view.ViewGroup; +import android.widget.FrameLayout; + +import com.bytedance.sdk.openadsdk.AdSlot; +import com.bytedance.sdk.openadsdk.TTAdConstant; +import com.bytedance.sdk.openadsdk.mediation.MediationConstant; +import com.bytedance.sdk.openadsdk.mediation.ad.MediationNativeAdAppInfo; +import com.bytedance.sdk.openadsdk.mediation.ad.MediationViewBinder; +import com.bytedance.sdk.openadsdk.mediation.bridge.custom.native_ad.MediationCustomNativeAd; +import com.qq.e.ads.nativ.MediaView; +import com.qq.e.ads.nativ.NativeADEventListener; +import com.qq.e.ads.nativ.NativeADMediaListener; +import com.qq.e.ads.nativ.NativeUnifiedADAppMiitInfo; +import com.qq.e.ads.nativ.NativeUnifiedADData; +import com.qq.e.ads.nativ.widget.NativeAdContainer; +import com.qq.e.comm.constants.AdPatternType; +import com.qq.e.comm.util.AdError; + +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.Callable; +import java.util.concurrent.Future; +import java.util.concurrent.TimeUnit; + +/** + * YLH 信息流 开发者自渲染(自渲染)广告对象 + */ +public class GdtNativeAd extends MediationCustomNativeAd { + + private static final String TAG = GdtNativeAd.class.getSimpleName(); + + private NativeUnifiedADData mNativeUnifiedADData; + private AdSlot mAdSlot; + private Context mContext; + private String VIEW_TAG = "view_tag"; + private boolean statusFlag = true; //app下载状态记录标识 + + public GdtNativeAd(Context context, NativeUnifiedADData feedAd, AdSlot adSlot) { + mContext = context; + mNativeUnifiedADData = feedAd; + mAdSlot = adSlot; + NativeUnifiedADAppMiitInfo info = mNativeUnifiedADData.getAppMiitInfo(); + MediationNativeAdAppInfo nativeAdAppInfo = new MediationNativeAdAppInfo(); + if (info != null) { + nativeAdAppInfo.setAppName(info.getAppName()); + nativeAdAppInfo.setAuthorName(info.getAuthorName()); + nativeAdAppInfo.setPackageSizeBytes(info.getPackageSizeBytes()); + nativeAdAppInfo.setPermissionsUrl(info.getPermissionsUrl()); + nativeAdAppInfo.setPrivacyAgreement(info.getPrivacyAgreement()); + nativeAdAppInfo.setVersionName(info.getVersionName()); + } + setNativeAdAppInfo(nativeAdAppInfo); + setTitle(mNativeUnifiedADData.getTitle()); + setDescription(mNativeUnifiedADData.getDesc()); + setActionText(mNativeUnifiedADData.getCTAText()); + setIconUrl(mNativeUnifiedADData.getIconUrl()); + setImageUrl(mNativeUnifiedADData.getImgUrl()); + setImageWidth(mNativeUnifiedADData.getPictureWidth()); + setImageHeight(mNativeUnifiedADData.getPictureHeight()); + setImageList(mNativeUnifiedADData.getImgList()); + setStarRating(mNativeUnifiedADData.getAppScore()); + setSource(mNativeUnifiedADData.getTitle()); + + if (mNativeUnifiedADData.getAdPatternType() == AdPatternType.NATIVE_VIDEO) { + setAdImageMode(TTAdConstant.IMAGE_MODE_VIDEO); + } else if (mNativeUnifiedADData.getAdPatternType() == AdPatternType.NATIVE_1IMAGE_2TEXT + || mNativeUnifiedADData.getAdPatternType() == AdPatternType.NATIVE_2IMAGE_2TEXT) { + setAdImageMode(TTAdConstant.IMAGE_MODE_LARGE_IMG); + } else if (mNativeUnifiedADData.getAdPatternType() == AdPatternType.NATIVE_3IMAGE) { + setAdImageMode(TTAdConstant.IMAGE_MODE_GROUP_IMG); + } + + if (mNativeUnifiedADData.isAppAd()) { + setInteractionType(TTAdConstant.INTERACTION_TYPE_DOWNLOAD); + } else { + setInteractionType(TTAdConstant.INTERACTION_TYPE_LANDING_PAGE); + } + } + + + @Override + public void registerView(Activity activity, + ViewGroup container, + List clickViews, + List creativeViews, + List directDownloadViews, + MediationViewBinder viewBinder) { + /** + * 先切子线程,再在子线程中切主线程进行广告展示 + */ + ThreadUtils.runOnUIThreadByThreadPool(new Runnable() { + @Override + public void run() { + if (isServerBidding()) { //曝光扣费, 单位分,若优量汇竞胜,在广告曝光时回传,必传 + mNativeUnifiedADData.setBidECPM(mNativeUnifiedADData.getECPM()); + } + + if (mNativeUnifiedADData != null && container instanceof FrameLayout) { + FrameLayout nativeAdView = (FrameLayout) container; + NativeAdContainer nativeAdContainer; + + if (nativeAdView.getChildAt(0) instanceof NativeAdContainer) { + //gdt会自动添加logo,会出现重复添加,需要把logo移除 + nativeAdContainer = (NativeAdContainer) nativeAdView.getChildAt(0); + for (int i = 0; i < nativeAdContainer.getChildCount(); ) { + View view = nativeAdContainer.getChildAt(i); + if (view != null) { + Object tag = view.getTag(); + if (tag != null && (tag instanceof String) && ((String) tag).equals(VIEW_TAG)) { + i++; + } else { + nativeAdContainer.removeView(view); + } + } else { + i++; + } + } + } else { + nativeAdContainer = new NativeAdContainer(mContext); + for (int i = 0; i < nativeAdView.getChildCount(); ) { + View view = nativeAdView.getChildAt(i); + view.setTag(VIEW_TAG); + final int index = nativeAdView.indexOfChild(view); + nativeAdView.removeViewInLayout(view); + final ViewGroup.LayoutParams layoutParams = view.getLayoutParams(); + nativeAdContainer.addView(view, index, layoutParams); + } + nativeAdView.removeAllViews(); + nativeAdView.addView(nativeAdContainer, ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT); + } + + if (creativeViews != null && directDownloadViews != null) { //若传入的参数中有directView,需要做处理 + creativeViews.addAll(directDownloadViews); + } + + + mNativeUnifiedADData.bindAdToView(activity, nativeAdContainer, GdtUtils.getNativeAdLogoParams(mAdSlot), clickViews, creativeViews); + + + FrameLayout ttMediaView = nativeAdView.findViewById(viewBinder.mediaViewId); + + if (ttMediaView != null && mNativeUnifiedADData.getAdPatternType() == AdPatternType.NATIVE_VIDEO) { + MediaView gdtMediaView = new MediaView(mContext); + ttMediaView.removeAllViews(); + ttMediaView.addView(gdtMediaView, ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT); + mNativeUnifiedADData.bindMediaView(gdtMediaView, GdtUtils.getGMVideoOption(mAdSlot), new NativeADMediaListener() { + @Override + public void onVideoInit() { + Log.d(TAG, "onVideoInit"); + } + + @Override + public void onVideoLoading() { + Log.d(TAG, "onVideoLoading"); + } + + @Override + public void onVideoReady() { + Log.d(TAG, "onVideoReady"); + } + + @Override + public void onVideoLoaded(int i) { + Log.d(TAG, "onVideoLoaded"); + } + + @Override + public void onVideoStart() { + Log.d(TAG, "onVideoStart"); + callVideoStart(); + } + + @Override + public void onVideoPause() { + Log.d(TAG, "onVideoPause"); + callVideoPause(); + } + + @Override + public void onVideoResume() { + Log.d(TAG, "onVideoResume"); + callVideoResume(); + } + + @Override + public void onVideoCompleted() { + Log.d(TAG, "onVideoCompleted"); + callVideoCompleted(); + } + + @Override + public void onVideoError(AdError adError) { + if (adError != null) { + Log.i(TAG, "onVideoError errorCode = " + adError.getErrorCode() + " errorMessage = " + adError.getErrorMsg()); + callVideoError(adError.getErrorCode(), adError.getErrorMsg()); + } else { + callVideoError(99999, "video error"); + } + } + + @Override + public void onVideoStop() { + Log.d(TAG, "onVideoStop"); + } + + @Override + public void onVideoClicked() { + Log.d(TAG, "onVideoClicked"); + callAdClick(); + } + }); + } + if (!TextUtils.isEmpty(mNativeUnifiedADData.getCTAText())) { + View view = nativeAdView.findViewById(viewBinder.callToActionId); + List CTAViews = new ArrayList<>(); + CTAViews.add(view); + mNativeUnifiedADData.bindCTAViews(CTAViews); + } + mNativeUnifiedADData.setNativeAdEventListener(new NativeADEventListener() { + @Override + public void onADExposed() { + Log.d(TAG, "onADExposed"); + callAdShow(); + } + + @Override + public void onADClicked() { + Log.d(TAG, "onADClicked"); + callAdClick(); + } + + @Override + public void onADError(AdError adError) { + Log.d(TAG, "onADError"); + } + + @Override + public void onADStatusChanged() { + + } + }); + } + } + }); + } + + @Override + public void onPause() { + super.onPause(); + /** + * 先切子线程,再在子线程中切主线程进行onPause操作 + */ + ThreadUtils.runOnUIThreadByThreadPool(new Runnable() { + @Override + public void run() { + Log.i(TAG, "onPause"); + if (mNativeUnifiedADData != null) { + mNativeUnifiedADData.pauseVideo(); + } + } + }); + } + + @Override + public void onResume() { + super.onResume(); + /** + * 先切子线程,再在子线程中切主线程进行onResume操作 + */ + ThreadUtils.runOnUIThreadByThreadPool(new Runnable() { + @Override + public void run() { + Log.i(TAG, "onResume"); + if (mNativeUnifiedADData != null) { + mNativeUnifiedADData.resume(); + } + } + }); + } + + @Override + public void onDestroy() { + super.onDestroy(); + /** + * 在子线程进行onDestroy操作 + */ + ThreadUtils.runOnUIThread(new Runnable() { + @Override + public void run() { + Log.i(TAG, "onDestroy"); + if (mNativeUnifiedADData != null) { + mNativeUnifiedADData.destroy(); + mNativeUnifiedADData = null; + } + } + }); + } + + + @Override + public MediationConstant.AdIsReadyStatus isReadyCondition() { + /** + * 在子线程中进行广告是否可用的判断 + */ + Future future = ThreadUtils.runOnThreadPool(new Callable() { + @Override + public MediationConstant.AdIsReadyStatus call() throws Exception { + if (mNativeUnifiedADData != null && mNativeUnifiedADData.isValid()) { + return MediationConstant.AdIsReadyStatus.AD_IS_READY; + } else { + return MediationConstant.AdIsReadyStatus.AD_IS_NOT_READY; + } + } + }); + try { + MediationConstant.AdIsReadyStatus result = future.get(500, TimeUnit.MILLISECONDS);//设置500毫秒的总超时,避免线程阻塞 + if (result != null) { + return result; + } else { + return MediationConstant.AdIsReadyStatus.AD_IS_NOT_READY; + } + } catch (Exception e) { + e.printStackTrace(); + } + return MediationConstant.AdIsReadyStatus.AD_IS_NOT_READY; + } +} diff --git a/LibraryAd/src/main/java/com/tfq/ad/ad/gdt/GdtNativeExpressAd.java b/LibraryAd/src/main/java/com/tfq/ad/ad/gdt/GdtNativeExpressAd.java new file mode 100644 index 0000000..905b90a --- /dev/null +++ b/LibraryAd/src/main/java/com/tfq/ad/ad/gdt/GdtNativeExpressAd.java @@ -0,0 +1,200 @@ +package com.tfq.ad.ad.gdt; + +import android.util.Log; +import android.view.View; + +import com.bytedance.sdk.openadsdk.AdSlot; +import com.bytedance.sdk.openadsdk.TTAdConstant; +import com.bytedance.sdk.openadsdk.mediation.MediationConstant; +import com.bytedance.sdk.openadsdk.mediation.bridge.custom.native_ad.MediationCustomNativeAd; +import com.qq.e.ads.nativ.NativeExpressADView; +import com.qq.e.ads.nativ.NativeExpressMediaListener; +import com.qq.e.comm.constants.AdPatternType; +import com.qq.e.comm.pi.AdData; +import com.qq.e.comm.util.AdError; + +import java.util.concurrent.Callable; +import java.util.concurrent.Future; +import java.util.concurrent.TimeUnit; + +/** + * YLH 信息流 ADN提供渲染(模板渲染)广告对象 + */ +public class GdtNativeExpressAd extends MediationCustomNativeAd { + private static final String TAG = GdtNativeExpressAd.class.getSimpleName(); + private NativeExpressADView mNativeExpressADView; + + public GdtNativeExpressAd(NativeExpressADView mNativeExpressADView) { + this.mNativeExpressADView = mNativeExpressADView; + } + + public GdtNativeExpressAd() { + } + + public GdtNativeExpressAd(NativeExpressADView feedAd, AdSlot adSlot) { + setExpressAd(true); + mNativeExpressADView = feedAd; + AdData adData = mNativeExpressADView.getBoundData(); + if (adData.getAdPatternType() == AdPatternType.NATIVE_VIDEO) { + mNativeExpressADView.preloadVideo(); + mNativeExpressADView.setMediaListener(new NativeExpressMediaListener() { + @Override + public void onVideoInit(NativeExpressADView nativeExpressADView) { + Log.i(TAG, "onVideoInit"); + } + + @Override + public void onVideoLoading(NativeExpressADView nativeExpressADView) { + Log.i(TAG, "onVideoLoading"); + } + + @Override + public void onVideoCached(NativeExpressADView nativeExpressADView) { + Log.i(TAG, "onVideoCached"); + } + + @Override + public void onVideoReady(NativeExpressADView nativeExpressADView, long l) { + Log.i(TAG, "onVideoReady"); + } + + @Override + public void onVideoStart(NativeExpressADView nativeExpressADView) { + Log.i(TAG, "onVideoStart"); + callVideoStart(); + } + + @Override + public void onVideoPause(NativeExpressADView nativeExpressADView) { + Log.i(TAG, "onVideoPause"); + callVideoPause(); + } + + @Override + public void onVideoComplete(NativeExpressADView nativeExpressADView) { + Log.i(TAG, "onVideoComplete"); + callVideoCompleted(); + } + + @Override + public void onVideoError(NativeExpressADView nativeExpressADView, AdError adError) { + Log.i(TAG, "onVideoError"); + if (adError != null) { + callVideoError(adError.getErrorCode(), adError.getErrorMsg()); + } else { + callVideoError(99999, "video error"); + } + } + + @Override + public void onVideoPageOpen(NativeExpressADView nativeExpressADView) { + Log.i(TAG, "onVideoPageOpen"); + } + + @Override + public void onVideoPageClose(NativeExpressADView nativeExpressADView) { + Log.i(TAG, "onVideoPageClose"); + } + }); + setAdImageMode(TTAdConstant.IMAGE_MODE_VIDEO); + } else if (adData.getAdPatternType() == AdPatternType.NATIVE_1IMAGE_2TEXT || adData.getAdPatternType() == AdPatternType.NATIVE_2IMAGE_2TEXT) { + setAdImageMode(TTAdConstant.IMAGE_MODE_LARGE_IMG); + } else if (adData.getAdPatternType() == AdPatternType.NATIVE_3IMAGE) { + setAdImageMode(TTAdConstant.IMAGE_MODE_GROUP_IMG); + } else { + setAdImageMode(TTAdConstant.IMAGE_MODE_LARGE_IMG); + } + setTitle(adData.getTitle()); + setDescription(adData.getDesc()); + setInteractionType(TTAdConstant.INTERACTION_TYPE_LANDING_PAGE); + } + + /** + * 如果Adn 有dislike接口需要返回true + */ + @Override + public boolean hasDislike() { + return true; + } + + @Override + public void render() { + /** + * 先切子线程,再在子线程中切主线程进行广告展示 + */ + ThreadUtils.runOnUIThreadByThreadPool(new Runnable() { + @Override + public void run() { + if (mNativeExpressADView != null) { + mNativeExpressADView.render(); + } + } + }); + } + + @Override + public View getExpressView() { + if (isServerBidding()) { //曝光扣费, 单位分,若优量汇竞胜,在广告曝光时回传,必传 + mNativeExpressADView.setBidECPM(mNativeExpressADView.getECPM()); + } + return mNativeExpressADView; + } + + @Override + public void onPause() { + super.onPause(); + Log.i(TAG, "onPause"); + } + + @Override + public void onResume() { + super.onResume(); + Log.i(TAG, "onResume"); + } + + @Override + public void onDestroy() { + super.onDestroy(); + /** + * 在子线程进行onDestroy操作 + */ + ThreadUtils.runOnUIThread(new Runnable() { + @Override + public void run() { + Log.i(TAG, "onDestroy"); + if (mNativeExpressADView != null) { + mNativeExpressADView.destroy(); + mNativeExpressADView = null; + } + } + }); + } + + @Override + public MediationConstant.AdIsReadyStatus isReadyCondition() { + /** + * 在子线程中进行广告是否可用的判断 + */ + Future future = ThreadUtils.runOnThreadPool(new Callable() { + @Override + public MediationConstant.AdIsReadyStatus call() throws Exception { + if (mNativeExpressADView != null && mNativeExpressADView.isValid()) { + return MediationConstant.AdIsReadyStatus.AD_IS_READY; + } else { + return MediationConstant.AdIsReadyStatus.AD_IS_NOT_READY; + } + } + }); + try { + MediationConstant.AdIsReadyStatus result = future.get(500, TimeUnit.MILLISECONDS);//设置500毫秒的总超时,避免线程阻塞 + if (result != null) { + return result; + } else { + return MediationConstant.AdIsReadyStatus.AD_IS_NOT_READY; + } + } catch (Exception e) { + e.printStackTrace(); + } + return MediationConstant.AdIsReadyStatus.AD_IS_NOT_READY; + } +} \ No newline at end of file diff --git a/LibraryAd/src/main/java/com/tfq/ad/ad/gdt/GdtUtils.java b/LibraryAd/src/main/java/com/tfq/ad/ad/gdt/GdtUtils.java new file mode 100644 index 0000000..34d5ea2 --- /dev/null +++ b/LibraryAd/src/main/java/com/tfq/ad/ad/gdt/GdtUtils.java @@ -0,0 +1,114 @@ +package com.tfq.ad.ad.gdt; + +import android.widget.FrameLayout; + +import com.bytedance.sdk.openadsdk.AdSlot; +import com.bytedance.sdk.openadsdk.mediation.MediationConstant; +import com.qq.e.ads.cfg.VideoOption; + +import java.util.Map; + +public class GdtUtils { + public static VideoOption getGMVideoOption(AdSlot adSlot) { +/* 注: + 1:如下所示 如果需要"gdt_auto_play_policy"、"gdt_auto_play_muted"等自定义相关参数需要再加载广告的时候传入 + 加载广告的时候传入的示例如下 (各个广告类型都适配) 通过MediationAdSlot.Builder().setExtraObject传入自定义参数 + + val adslot = AdSlot.Builder() + .setMediationAdSlot( + MediationAdSlot.Builder() + .setExtraObject("gdt_auto_play_policy", 1) + .setExtraObject(""gdt_auto_play_muted"", true) + .build() + ) + .build() +*/ + + + VideoOption.Builder builder = new VideoOption.Builder(); + if (adSlot != null && adSlot.getMediationAdSlot() != null && adSlot.getMediationAdSlot().getExtraObject() != null) { + Map extra = adSlot.getMediationAdSlot().getExtraObject(); + if (extra.get("gdt_auto_play_policy") instanceof Integer) { + builder.setAutoPlayPolicy((Integer) extra.get("gdt_auto_play_policy")); + } + + if (extra.get("gdt_auto_play_muted") instanceof Boolean) { + builder.setAutoPlayMuted((Boolean) extra.get("gdt_auto_play_muted")); + } + + if (extra.get("gdt_detail_page_muted") instanceof Boolean) { + builder.setDetailPageMuted((Boolean) extra.get("gdt_detail_page_muted")); + } + + if (extra.get("gdt_enable_detail_page") instanceof Boolean) { + builder.setEnableDetailPage((Boolean) extra.get("gdt_enable_detail_page")); + } + + if (extra.get("gdt_enable_user_control") instanceof Boolean) { + builder.setEnableUserControl((Boolean) extra.get("gdt_enable_user_control")); + } + } + return builder.build(); + } + + + public static FrameLayout.LayoutParams getNativeAdLogoParams(AdSlot adSlot) { + if (adSlot == null) { + return null; + } + + if (adSlot.getMediationAdSlot() == null) { + return null; + } + + Map extra = adSlot.getMediationAdSlot().getExtraObject(); + if (extra != null) { + Object o = extra.get(MediationConstant.KEY_GDT_NATIVE_LOGO_PARAMS); + if (o instanceof FrameLayout.LayoutParams) { + return (FrameLayout.LayoutParams) o; + } + } + + return null; + } + + public static int getGDTMaxVideoDuration(AdSlot adSlot) { + if (adSlot == null) { + return 0; + } + + if (adSlot.getMediationAdSlot() == null) { + return 0; + } + + Map extra = adSlot.getMediationAdSlot().getExtraObject(); + if (extra != null) { + Object o = extra.get(MediationConstant.KEY_GDT_MAX_VIDEO_DURATION); + if (o instanceof Integer) { + return (Integer) o; + } + } + return 0; + } + + public static int getGDTMinVideoDuration(AdSlot adSlot) { + if (adSlot == null) { + return 0; + } + + if (adSlot.getMediationAdSlot() == null) { + return 0; + } + + Map extra = adSlot.getMediationAdSlot().getExtraObject(); + if (extra != null) { + Object o = extra.get(MediationConstant.KEY_GDT_MIN_VIDEO_DURATION); + if (o instanceof Integer) { + return (Integer) o; + } + } + return 0; + } + + +} diff --git a/LibraryAd/src/main/java/com/tfq/ad/ad/gdt/ThreadUtils.java b/LibraryAd/src/main/java/com/tfq/ad/ad/gdt/ThreadUtils.java new file mode 100644 index 0000000..30f7733 --- /dev/null +++ b/LibraryAd/src/main/java/com/tfq/ad/ad/gdt/ThreadUtils.java @@ -0,0 +1,72 @@ +package com.tfq.ad.ad.gdt; + +import android.os.Handler; +import android.os.Looper; + +import java.util.concurrent.Callable; +import java.util.concurrent.Future; +import java.util.concurrent.LinkedBlockingQueue; +import java.util.concurrent.ThreadPoolExecutor; +import java.util.concurrent.TimeUnit; + +public class ThreadUtils { + private static final int CPU_COUNT = Runtime.getRuntime().availableProcessors(); + private static final int CORE_POOL_SIZE = Math.max(CPU_COUNT, 5); // 至少允许5个线程 + private static final int THREAD_KEEP_LIVE_TIME = 30; // 线程如果30秒不用,允许超时 + private static final int TASK_QUEUE_MAX_COUNT = 128; + + private static ThreadPoolExecutor mThreadPoolExecutor = new ThreadPoolExecutor(CORE_POOL_SIZE, + CORE_POOL_SIZE, + THREAD_KEEP_LIVE_TIME, + TimeUnit.SECONDS, + new LinkedBlockingQueue(TASK_QUEUE_MAX_COUNT)); + + public static Handler mMainHandler = new Handler(Looper.getMainLooper()); + + + /** + * 在子线程中运行任务 + * + * @param runnable + */ + public static void runOnThreadPool(Runnable runnable) { + mThreadPoolExecutor.execute(runnable); + } + + /** + * 在主线程中运行任务 + * + * @param runnable + */ + public static void runOnUIThread(Runnable runnable) { + if (Thread.currentThread() == Looper.getMainLooper().getThread()) { + runnable.run(); + } else { + mMainHandler.post(runnable); + } + } + + /** + * 在子线程中切主线程运行任务(切两次线程,先切子线程,再在子线程中切主线程) + * + * @param runnable + */ + public static void runOnUIThreadByThreadPool(Runnable runnable) { + mThreadPoolExecutor.execute(new Runnable() { + @Override + public void run() { + mMainHandler.post(runnable); + } + }); + } + + /** + * 在子线程中运行任务 + * + * @param callable + * @return + */ + public static Future runOnThreadPool(Callable callable) { + return mThreadPoolExecutor.submit(callable); + } +} diff --git a/LibraryAd/src/main/java/com/tfq/ad/app/AdApp.java b/LibraryAd/src/main/java/com/tfq/ad/app/AdApp.java new file mode 100644 index 0000000..33b3b2d --- /dev/null +++ b/LibraryAd/src/main/java/com/tfq/ad/app/AdApp.java @@ -0,0 +1,75 @@ +package com.tfq.ad.app; + +import android.app.Activity; +import android.app.Application; +import android.content.Context; +import android.content.pm.ApplicationInfo; +import android.content.pm.PackageManager; +import android.os.Bundle; + +import com.tfq.ad.ad.LoopAd; +import com.tfq.library.app.IConstituteApp; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + +/** + * Created by Administrator on 2016/10/28. + */ +public class AdApp implements IConstituteApp { + public static Activity activityTop; + private static Context mContext; + private static AdApp instance; + public Application application; + + public static AdApp getInstance() { + return instance; + } + + public static Context getContext() { + return mContext; + } + + public static Activity getActivityTop() { + return activityTop; + } + + public static String getChannel() { + try { + PackageManager pm = mContext.getPackageManager(); + ApplicationInfo appInfo = pm.getApplicationInfo(mContext.getPackageName(), PackageManager.GET_META_DATA); + return appInfo.metaData.getString("UMENG_CHANNEL"); + } catch (PackageManager.NameNotFoundException ignored) { + } + return "other"; + } + + public static String getJson(String fileName, Context context) { + StringBuilder stringBuilder = new StringBuilder(); + try { + InputStream is = context.getAssets().open(fileName); + BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(is)); + String line; + while ((line = bufferedReader.readLine()) != null) { + stringBuilder.append(line); + } + } catch (IOException e) { + e.printStackTrace(); + } + return stringBuilder.toString(); + } + + @Override + public void onCreate(Application application) { + mContext = application.getApplicationContext(); + instance = this; + this.application = application; + + new LoopAd(); + } +} diff --git a/LibraryAd/src/main/java/com/tfq/ad/app/AdUrlCenter.java b/LibraryAd/src/main/java/com/tfq/ad/app/AdUrlCenter.java new file mode 100644 index 0000000..1d98525 --- /dev/null +++ b/LibraryAd/src/main/java/com/tfq/ad/app/AdUrlCenter.java @@ -0,0 +1,11 @@ +package com.tfq.ad.app; + +public class AdUrlCenter { + //base url + public static String BASE_URL = "http://www.90000li.com/app-api/"; + //根据广告平台id获取开关 + public static String advFlag = BASE_URL + "jwl/app-advertisement/flag"; + //问题反馈 + public static String feedback = BASE_URL + "jwl/feedback/add"; + +} diff --git a/LibraryAd/src/main/java/com/tfq/ad/app/HttpLog.java b/LibraryAd/src/main/java/com/tfq/ad/app/HttpLog.java new file mode 100644 index 0000000..9b6453f --- /dev/null +++ b/LibraryAd/src/main/java/com/tfq/ad/app/HttpLog.java @@ -0,0 +1,12 @@ +package com.tfq.ad.app; + +import android.util.Log; + +import okhttp3.logging.HttpLoggingInterceptor; + +public class HttpLog implements HttpLoggingInterceptor.Logger { + @Override + public void log(String message) { + Log.i("HttpLogInfo", message); + } +} diff --git a/LibraryAd/src/main/java/com/tfq/ad/app/MyOkHttp.java b/LibraryAd/src/main/java/com/tfq/ad/app/MyOkHttp.java new file mode 100644 index 0000000..5d08731 --- /dev/null +++ b/LibraryAd/src/main/java/com/tfq/ad/app/MyOkHttp.java @@ -0,0 +1,36 @@ +package com.tfq.ad.app; + +import android.widget.Toast; + +import com.tfq.library.app.BaseConstants; +import com.tfq.library.app.LibraryApp; +import com.tfq.library.utils.AppUtil; + +import okhttp3.Interceptor; +import okhttp3.OkHttpClient; +import okhttp3.logging.HttpLoggingInterceptor; + +public class MyOkHttp { + + private static OkHttpClient client; + + public static OkHttpClient myClient() { + Interceptor logInterceptor; + if (BaseConstants.BASE_APP_DEBUG_PRINT) { + logInterceptor = new HttpLoggingInterceptor(new HttpLog()).setLevel(HttpLoggingInterceptor.Level.BODY); + } else { + logInterceptor = new HttpLoggingInterceptor(new HttpLog()).setLevel(HttpLoggingInterceptor.Level.NONE); + } + + if (client == null) { + client = new OkHttpClient() + .newBuilder() + .addNetworkInterceptor(logInterceptor) + .build(); + } + if (!AppUtil.connectStatus()) { + Toast.makeText(LibraryApp.getContext(), "无网络连接", Toast.LENGTH_SHORT).show(); + } + return client; + } +} diff --git a/LibraryAd/src/main/java/com/tfq/ad/bean/AdvBean.java b/LibraryAd/src/main/java/com/tfq/ad/bean/AdvBean.java new file mode 100644 index 0000000..68f2932 --- /dev/null +++ b/LibraryAd/src/main/java/com/tfq/ad/bean/AdvBean.java @@ -0,0 +1,98 @@ +package com.tfq.ad.bean; + +public class AdvBean { + + private int code; + private DataDTO data; + private String msg; + + public int getCode() { + return code; + } + + public void setCode(int code) { + this.code = code; + } + + public DataDTO getData() { + return data; + } + + public void setData(DataDTO data) { + this.data = data; + } + + public String getMsg() { + return msg; + } + + public void setMsg(String msg) { + this.msg = msg; + } + + public static class DataDTO { + private String adv1Flag; + private String adv2Flag; + private String adv3Flag; + private String adv4Flag; + private String adv5Flag; + private String adv6Flag; + private int adv4Wait; + + public String getAdv1Flag() { + return adv1Flag; + } + + public void setAdv1Flag(String adv1Flag) { + this.adv1Flag = adv1Flag; + } + + public String getAdv2Flag() { + return adv2Flag; + } + + public void setAdv2Flag(String adv2Flag) { + this.adv2Flag = adv2Flag; + } + + public String getAdv3Flag() { + return adv3Flag; + } + + public void setAdv3Flag(String adv3Flag) { + this.adv3Flag = adv3Flag; + } + + public String getAdv4Flag() { + return adv4Flag; + } + + public void setAdv4Flag(String adv4Flag) { + this.adv4Flag = adv4Flag; + } + + public String getAdv5Flag() { + return adv5Flag; + } + + public void setAdv5Flag(String adv5Flag) { + this.adv5Flag = adv5Flag; + } + + public String getAdv6Flag() { + return adv6Flag; + } + + public void setAdv6Flag(String adv6Flag) { + this.adv6Flag = adv6Flag; + } + + public int getAdv4Wait() { + return adv4Wait; + } + + public void setAdv4Wait(int adv4Wait) { + this.adv4Wait = adv4Wait; + } + } +} diff --git a/LibraryAd/src/main/java/com/tfq/ad/bean/UserInfo.java b/LibraryAd/src/main/java/com/tfq/ad/bean/UserInfo.java new file mode 100644 index 0000000..8cb4292 --- /dev/null +++ b/LibraryAd/src/main/java/com/tfq/ad/bean/UserInfo.java @@ -0,0 +1,105 @@ +package com.tfq.ad.bean; + +import android.text.TextUtils; + +import com.tfq.ad.app.AdUrlCenter; +import com.tfq.ad.app.MyOkHttp; +import com.tfq.library.app.BaseConstants; +import com.tfq.library.utils.AppUtil; +import com.tfq.library.utils.LogK; + +import org.json.JSONObject; + +import java.io.IOException; + +import okhttp3.Call; +import okhttp3.Callback; +import okhttp3.MediaType; +import okhttp3.OkHttpClient; +import okhttp3.Request; +import okhttp3.RequestBody; +import okhttp3.Response; + +public class UserInfo { + + private final OkHttpClient client = MyOkHttp.myClient(); + + private void regexDate(Response response, Success listener) { + try { + String data = response.body().string(); + LogK.e(data); + JSONObject jsonObject = new JSONObject(data); + String code = jsonObject.getString("code"); + String msg = ""; + try { + msg = jsonObject.getString("message"); + } catch (Exception e) { + e.printStackTrace(); + try { + msg = jsonObject.getString("msg"); + } catch (Exception e1) { + e1.printStackTrace(); + msg = ""; + } + } + if (!TextUtils.isEmpty(code)) { + switch (code) { + case "200": + case "0": + listener.Success(data, msg); + break; + default: + listener.fail(Integer.parseInt(code), msg); + break; + } + } else { + listener.fail(BaseConstants.URL_REQUEST_ERROR, msg); + } + } catch (Exception e) { + e.printStackTrace(); + listener.fail(BaseConstants.URL_REQUEST_ERROR, BaseConstants.URL_REQUEST_ERROR + ""); + } + } + + public void getAdvertising(Success listener) { + new Thread() { + @Override + public void run() { + super.run(); + String marketKey = ""; + if (!TextUtils.isEmpty(BaseConstants.CHANNEL)) { + marketKey = BaseConstants.CHANNEL; + } + String url = AdUrlCenter.advFlag; + String json = "{" + + " \"appId\":\"" + BaseConstants.APP_ID + "\"," + + " \"appPacketname\": \"" + AppUtil.getPackageName() + "\"," + + " \"channel\": \"" + marketKey + "\"," + + " \"appVersion\": \"" + AppUtil.getAppVersionCode() + "\"" + + "}"; + final Request request = new Request.Builder() + .url(url) + .post(RequestBody.create(MediaType.parse("application/json; charset=utf-8"), json)) + .build(); + Call call = client.newCall(request); + call.enqueue(new Callback() { + @Override + public void onFailure(Call call, IOException e) { + listener.fail(BaseConstants.URL_REQUEST_ERROR, ""); + } + + @Override + public void onResponse(Call call, Response response) { + regexDate(response, listener); + } + }); + } + }.start(); + } + + public interface Success { + void Success(String data, String msg); + + void fail(int num, String msg); + } +} diff --git a/LibraryAd/src/main/res/anim/actionsheet_dialog_in.xml b/LibraryAd/src/main/res/anim/actionsheet_dialog_in.xml new file mode 100644 index 0000000..cfd58a9 --- /dev/null +++ b/LibraryAd/src/main/res/anim/actionsheet_dialog_in.xml @@ -0,0 +1,5 @@ + + diff --git a/LibraryAd/src/main/res/anim/actionsheet_dialog_out.xml b/LibraryAd/src/main/res/anim/actionsheet_dialog_out.xml new file mode 100644 index 0000000..5439a7a --- /dev/null +++ b/LibraryAd/src/main/res/anim/actionsheet_dialog_out.xml @@ -0,0 +1,5 @@ + + diff --git a/LibraryAd/src/main/res/anim/activity_in_animation.xml b/LibraryAd/src/main/res/anim/activity_in_animation.xml new file mode 100644 index 0000000..5f704aa --- /dev/null +++ b/LibraryAd/src/main/res/anim/activity_in_animation.xml @@ -0,0 +1,7 @@ + + + + \ No newline at end of file diff --git a/LibraryAd/src/main/res/anim/activity_out_animation.xml b/LibraryAd/src/main/res/anim/activity_out_animation.xml new file mode 100644 index 0000000..3eb5162 --- /dev/null +++ b/LibraryAd/src/main/res/anim/activity_out_animation.xml @@ -0,0 +1,7 @@ + + + + \ No newline at end of file diff --git a/LibraryAd/src/main/res/anim/anim_float_window_enter.xml b/LibraryAd/src/main/res/anim/anim_float_window_enter.xml new file mode 100644 index 0000000..5790fe7 --- /dev/null +++ b/LibraryAd/src/main/res/anim/anim_float_window_enter.xml @@ -0,0 +1,13 @@ + + + + + \ No newline at end of file diff --git a/LibraryAd/src/main/res/anim/anim_float_window_exit.xml b/LibraryAd/src/main/res/anim/anim_float_window_exit.xml new file mode 100644 index 0000000..04818af --- /dev/null +++ b/LibraryAd/src/main/res/anim/anim_float_window_exit.xml @@ -0,0 +1,13 @@ + + + + + \ No newline at end of file diff --git a/LibraryAd/src/main/res/anim/bottom_menu_enter.xml b/LibraryAd/src/main/res/anim/bottom_menu_enter.xml new file mode 100644 index 0000000..01c4795 --- /dev/null +++ b/LibraryAd/src/main/res/anim/bottom_menu_enter.xml @@ -0,0 +1,5 @@ + + \ No newline at end of file diff --git a/LibraryAd/src/main/res/anim/bottom_menu_exit.xml b/LibraryAd/src/main/res/anim/bottom_menu_exit.xml new file mode 100644 index 0000000..e197528 --- /dev/null +++ b/LibraryAd/src/main/res/anim/bottom_menu_exit.xml @@ -0,0 +1,5 @@ + + diff --git a/LibraryAd/src/main/res/anim/dialog_show_enter.xml b/LibraryAd/src/main/res/anim/dialog_show_enter.xml new file mode 100644 index 0000000..b4b0204 --- /dev/null +++ b/LibraryAd/src/main/res/anim/dialog_show_enter.xml @@ -0,0 +1,6 @@ + + + + \ No newline at end of file diff --git a/LibraryAd/src/main/res/anim/dialog_show_exis.xml b/LibraryAd/src/main/res/anim/dialog_show_exis.xml new file mode 100644 index 0000000..bcc4d03 --- /dev/null +++ b/LibraryAd/src/main/res/anim/dialog_show_exis.xml @@ -0,0 +1,6 @@ + + + + \ No newline at end of file diff --git a/LibraryAd/src/main/res/drawable-hdpi/loading_1.png b/LibraryAd/src/main/res/drawable-hdpi/loading_1.png new file mode 100644 index 0000000..9613dda Binary files /dev/null and b/LibraryAd/src/main/res/drawable-hdpi/loading_1.png differ diff --git a/LibraryAd/src/main/res/drawable-hdpi/loading_10.png b/LibraryAd/src/main/res/drawable-hdpi/loading_10.png new file mode 100644 index 0000000..caffae9 Binary files /dev/null and b/LibraryAd/src/main/res/drawable-hdpi/loading_10.png differ diff --git a/LibraryAd/src/main/res/drawable-hdpi/loading_11.png b/LibraryAd/src/main/res/drawable-hdpi/loading_11.png new file mode 100644 index 0000000..221088d Binary files /dev/null and b/LibraryAd/src/main/res/drawable-hdpi/loading_11.png differ diff --git a/LibraryAd/src/main/res/drawable-hdpi/loading_12.png b/LibraryAd/src/main/res/drawable-hdpi/loading_12.png new file mode 100644 index 0000000..12b9d2a Binary files /dev/null and b/LibraryAd/src/main/res/drawable-hdpi/loading_12.png differ diff --git a/LibraryAd/src/main/res/drawable-hdpi/loading_2.png b/LibraryAd/src/main/res/drawable-hdpi/loading_2.png new file mode 100644 index 0000000..41da98b Binary files /dev/null and b/LibraryAd/src/main/res/drawable-hdpi/loading_2.png differ diff --git a/LibraryAd/src/main/res/drawable-hdpi/loading_3.png b/LibraryAd/src/main/res/drawable-hdpi/loading_3.png new file mode 100644 index 0000000..9027a9e Binary files /dev/null and b/LibraryAd/src/main/res/drawable-hdpi/loading_3.png differ diff --git a/LibraryAd/src/main/res/drawable-hdpi/loading_4.png b/LibraryAd/src/main/res/drawable-hdpi/loading_4.png new file mode 100644 index 0000000..9236147 Binary files /dev/null and b/LibraryAd/src/main/res/drawable-hdpi/loading_4.png differ diff --git a/LibraryAd/src/main/res/drawable-hdpi/loading_5.png b/LibraryAd/src/main/res/drawable-hdpi/loading_5.png new file mode 100644 index 0000000..9518c5c Binary files /dev/null and b/LibraryAd/src/main/res/drawable-hdpi/loading_5.png differ diff --git a/LibraryAd/src/main/res/drawable-hdpi/loading_6.png b/LibraryAd/src/main/res/drawable-hdpi/loading_6.png new file mode 100644 index 0000000..76a42c0 Binary files /dev/null and b/LibraryAd/src/main/res/drawable-hdpi/loading_6.png differ diff --git a/LibraryAd/src/main/res/drawable-hdpi/loading_7.png b/LibraryAd/src/main/res/drawable-hdpi/loading_7.png new file mode 100644 index 0000000..c752853 Binary files /dev/null and b/LibraryAd/src/main/res/drawable-hdpi/loading_7.png differ diff --git a/LibraryAd/src/main/res/drawable-hdpi/loading_8.png b/LibraryAd/src/main/res/drawable-hdpi/loading_8.png new file mode 100644 index 0000000..ce9a390 Binary files /dev/null and b/LibraryAd/src/main/res/drawable-hdpi/loading_8.png differ diff --git a/LibraryAd/src/main/res/drawable-hdpi/loading_9.png b/LibraryAd/src/main/res/drawable-hdpi/loading_9.png new file mode 100644 index 0000000..c1946eb Binary files /dev/null and b/LibraryAd/src/main/res/drawable-hdpi/loading_9.png differ diff --git a/LibraryAd/src/main/res/drawable/anim_loading.xml b/LibraryAd/src/main/res/drawable/anim_loading.xml new file mode 100644 index 0000000..0fe34bd --- /dev/null +++ b/LibraryAd/src/main/res/drawable/anim_loading.xml @@ -0,0 +1,53 @@ + + + + + + + + + + + + + + \ No newline at end of file diff --git a/LibraryAd/src/main/res/drawable/ll_radio_loading.xml b/LibraryAd/src/main/res/drawable/ll_radio_loading.xml new file mode 100644 index 0000000..7da1be1 --- /dev/null +++ b/LibraryAd/src/main/res/drawable/ll_radio_loading.xml @@ -0,0 +1,6 @@ + + + + + \ No newline at end of file diff --git a/LibraryAd/src/main/res/layout/ad_splash.xml b/LibraryAd/src/main/res/layout/ad_splash.xml new file mode 100644 index 0000000..147a30c --- /dev/null +++ b/LibraryAd/src/main/res/layout/ad_splash.xml @@ -0,0 +1,16 @@ + + + + + + + \ No newline at end of file diff --git a/LibraryAd/src/main/res/layout/layout_dialog_loading.xml b/LibraryAd/src/main/res/layout/layout_dialog_loading.xml new file mode 100644 index 0000000..73d523f --- /dev/null +++ b/LibraryAd/src/main/res/layout/layout_dialog_loading.xml @@ -0,0 +1,17 @@ + + + + + + \ No newline at end of file diff --git a/LibraryAd/src/main/res/layout/layout_dialog_permission.xml b/LibraryAd/src/main/res/layout/layout_dialog_permission.xml new file mode 100644 index 0000000..8bb6f71 --- /dev/null +++ b/LibraryAd/src/main/res/layout/layout_dialog_permission.xml @@ -0,0 +1,107 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/LibraryAd/src/main/res/values/colors.xml b/LibraryAd/src/main/res/values/colors.xml new file mode 100644 index 0000000..d781ec5 --- /dev/null +++ b/LibraryAd/src/main/res/values/colors.xml @@ -0,0 +1,4 @@ + + + + diff --git a/LibraryAd/src/main/res/values/styles.xml b/LibraryAd/src/main/res/values/styles.xml new file mode 100644 index 0000000..ac65023 --- /dev/null +++ b/LibraryAd/src/main/res/values/styles.xml @@ -0,0 +1,222 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/LibraryAd/src/main/res/xml/file_paths.xml b/LibraryAd/src/main/res/xml/file_paths.xml new file mode 100644 index 0000000..78bbff7 --- /dev/null +++ b/LibraryAd/src/main/res/xml/file_paths.xml @@ -0,0 +1,47 @@ + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/LibraryAd/src/main/res/xml/gdt_file_path.xml b/LibraryAd/src/main/res/xml/gdt_file_path.xml new file mode 100644 index 0000000..d268726 --- /dev/null +++ b/LibraryAd/src/main/res/xml/gdt_file_path.xml @@ -0,0 +1,10 @@ + + + + + + diff --git a/README.md b/README.md index 07f5aa2..41ce09e 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,32 @@ -# JKBaseLib - -我的AD的base仓库,其他app的引用仓库 \ No newline at end of file +com.xxx.xxx.xxx.app +├── core/ # 核心基础模块 +│ ├── constants/ # 常量(如路由、API地址等) +│ ├── enums/ # 枚举类 +│ ├── extensions/ # Kotlin扩展函数 +│ ├── utils/ # 工具类(可分日期、文件、屏幕工具等子包) +│ └── di/ # 核心依赖注入 +│ +├── network/ # 网络通信模块 +│ ├── api/ # 后端接口定义(按业务线分文件) +│ ├── model/ # 网络实体类 +│ ├── interceptor/ # 拦截器(Token刷新、日志等) +│ └── retrofit/ # Retrofit配置 +│ +├── feature_home/ # 首页功能模块(示例) +│ ├── ui/ # 表现层 +│ │ ├── HomeActivity.kt +│ │ ├── HomeFragment.kt +│ │ └── HomeViewModel.kt +│ │ +│ ├── domain/ # 领域层(可选) +│ └── data/ # 数据层(模块专属仓库) +│ +├── feature_auth/ # 认证模块(示例) +│ ├── ui/ +│ ├── domain/ +│ └── data/ +│ +└── shared/ # 共享模块 +├── components/ # 公共UI组件(Dialog、自定义View等) +├── model/ # 跨模块共享的领域模型 +└── resources/ # 公共资源(如字符串常量) \ No newline at end of file diff --git a/android_data/csj_config/site_config_5666885 b/android_data/csj_config/site_config_5666885 new file mode 100644 index 0000000..d78c9f6 --- /dev/null +++ b/android_data/csj_config/site_config_5666885 @@ -0,0 +1 @@ +{"cypher":2,"message":"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"} \ No newline at end of file diff --git a/android_data/data.gradle b/android_data/data.gradle new file mode 100644 index 0000000..d74df78 --- /dev/null +++ b/android_data/data.gradle @@ -0,0 +1,47 @@ +ext { + base = [ + //基本信息 + applicationId : "com.tfq.finances.jzrcj", + versionCode : 100, + versionName : "1.0.0", + company_name : "济宁同风起网络科技有限公司", + noAD : "false",//无广告true + APP_DEBUG_PRINT : "false",//true: 打印log + need_login : "true",//临时用来判断需要登录等功能 + + appinfoId : "48", // 新增参数,后台逻辑id + appId : "5666885", + csjId : "5666885", + csjIdSplash : "103509305", + csjIdCQP : "103508062", + csjIdFeed1 : "103507590", + csjIdFeed2 : "103507765", + csjIdFeed3 : "103507590", + csjIdReward : "103508260", + csjIdBanner : "103507868", + csjIdDraw : "103507868", + + umId : "684282abbc47b67d837db577", + + APP_RECORD_CODE : "鲁ICP备2023038270号-23A", + APP_RECORD_URL : "https://beian.miit.gov.cn/", + + signFile : "../android_data/jzrcj.keystore", + signAlias : "key", + signPassword : "7cb2abb68a0abeb25e29a663f451b608", + + appTag : "jz_", + + activityCountPerPackage: 2.82, + otherCountPerPackage : 1.85, + methodCountPerClass : 1.71, + drawableCount : 1.51, + stringCount : 1.56, + + ] + channel = [ + appName : "记账日常记", + appLogo : "@drawable/app_logo", + appKaiPing: "@drawable/app_splash", + ] +} diff --git a/android_data/flavors.gradle b/android_data/flavors.gradle new file mode 100644 index 0000000..43fc4d0 --- /dev/null +++ b/android_data/flavors.gradle @@ -0,0 +1,174 @@ +apply plugin: 'android-junk-code' + +androidJunkCode { + def config = { + packageBase = rootProject.ext.base["applicationId"] + packageCount = 18 + activityCountPerPackage = rootProject.ext.base["activityCountPerPackage"] * 4 + excludeActivityJavaFile = false + otherCountPerPackage = rootProject.ext.base["otherCountPerPackage"] * 26 + methodCountPerClass = rootProject.ext.base["methodCountPerClass"] * 11 + resPrefix = rootProject.ext.base["appTag"] + drawableCount = rootProject.ext.base["drawableCount"] * 74 + stringCount = rootProject.ext.base["stringCount"] * 158 + } + variantConfig { +// xiaomiRelease config + vivoRelease config +// baiduRelease config + oppoRelease config +// huaweiRelease config +// tengxunRelease config +// otherRelease config +// ali config +// qihoo config + } +} + +android { + + flavorDimensions "versionCode" + + // 多渠道打包 + productFlavors { + xiaomi {} + vivo {} + oppo {} + huawei {} + other {} + a_other {} + ali {} + tengxun {} + baidu {} + qihu360 {} + honor {} + } + // 签名配置 + signingConfigs { + release { + keyAlias rootProject.ext.base["signAlias"] + keyPassword rootProject.ext.base["signPassword"] + storeFile file(rootProject.ext.base["signFile"]) + storePassword rootProject.ext.base["signPassword"] + } + } + + productFlavors.all { flavor -> + print(flavor) + + if (!name.contains('tengxun')) { + ndk { + //noinspection ChromeOsAbiSupport + abiFilters "arm64-v8a" + } + } + if (name.contains("other")) { + flavor.manifestPlaceholders = [UMENG_CHANNEL_VALUE: "other", app_icon: rootProject.ext.channel["appLogo"]] + resValue "bool", "APP_DEBUG_PRINT", "true" + } else { + resValue "bool", "APP_DEBUG_PRINT", rootProject.ext.base["APP_DEBUG_PRINT"] + flavor.manifestPlaceholders = [UMENG_CHANNEL_VALUE: name, app_icon: rootProject.ext.channel["appLogo"]] + } + + resValue "bool", "noAD", rootProject.ext.base["noAD"] + resValue "string", "company_name", rootProject.ext.base["company_name"] + + resValue "string", "appid", rootProject.ext.base["appId"] //后台appid + resValue "string", "csjId", rootProject.ext.base["csjId"] //穿山甲id + resValue "string", "appinfoId", rootProject.ext.base["appinfoId"] //后台app逻辑id + resValue "string", "umId", rootProject.ext.base["umId"] //友盟id + + resValue "string", "csjIdSplash", rootProject.ext.base["csjIdSplash"] //开屏 + resValue "string", "csjIdCQP", rootProject.ext.base["csjIdCQP"] //插全屏 + resValue "string", "csjIdFeed1", rootProject.ext.base["csjIdFeed1"] //信息流 + resValue "string", "csjIdFeed2", rootProject.ext.base["csjIdFeed2"] //信息流2 + resValue "string", "csjIdFeed3", rootProject.ext.base["csjIdFeed3"] //信息流3 + resValue "string", "csjIdReward", rootProject.ext.base["csjIdReward"] //激励视频 + resValue "string", "csjIdBanner", rootProject.ext.base["csjIdBanner"] //banner + resValue "string", "csjIdDraw", rootProject.ext.base["csjIdDraw"] //draw + + resValue "string", "app_name", rootProject.ext.channel["appName"] + resValue "string", "app_logo", rootProject.ext.channel["appLogo"] //logo + resValue "string", "app_splash", rootProject.ext.channel["appKaiPing"] //splash + + resValue "string", "update_date", "2023年03月28日" + + resValue "string", "APP_RECORD_CODE", rootProject.ext.base["APP_RECORD_CODE"] //备案号 + resValue "string", "APP_RECORD_URL", rootProject.ext.base["APP_RECORD_URL"] //备案地址 + + resValue "bool", "need_login", rootProject.ext.base["need_login"] //要求登录 + } + + buildTypes { + release { + signingConfig signingConfigs.release + } + } + + android.applicationVariants.all { variant -> + variant.outputs.configureEach { output -> + // 1. 定义 flavor 名称与中文映射 + def flavorNameMap = [ + "baidu" : "百度", + "huawei" : "华为", + "oppo" : "oppo", + "other" : "其他", + "a_other": "其他", + "tengxun": "腾讯", + "vivo" : "vivo", + "xiaomi" : "小米", + "ali" : "阿里", + "qihu360": "360", + "honor" : "荣耀" + ] + def displayName = flavorNameMap[variant.flavorName] ?: variant.flavorName + + // 2. 生成文件名 + def fileName + + if (variant.buildType.name != "release") { + // Debug 模式文件名 + fileName = "${project.name}-${variant.buildType.name}-${variant.flavorName}-${variant.versionName}-${new Date().format('yyyyMMdd_HHmm')}.apk" + + } else { + // Release 模式文件名 + fileName = "${displayName}-${rootProject.ext.channel["appName"]}-${variant.versionName}.apk" + + print("APP打包名称= " + fileName + "\n") + } + + // 3. 设置输出文件名(仅文件名,不含路径) + output.outputFileName = fileName + + /* print("fileName=" + fileName + " name="+name + " project.buildDir="+project.buildDir + " \n") + + // 4. 设置输出路径(通过 outputDirectory) + // def outputDir = new File(project.buildDir, "app/apk/${variant.flavorName}/${variant.buildType.name}") + // output.outputDirectory = outputDir + + + // 4. 设置输出路径(通过 outputDirectory) + // def outputDir = new File(project.buildDir, "app/apk/${variant.flavorName}/${variant.buildType.name}") + // output.outputFileName = outputDir + // output.outputFileName = new File(outputDir, name) + // print("outputDir1="+ project.buildDir) + // print("outputDir2="+ "app/apk/${variant.flavorName}/${variant.buildType.name}") + // print("outputDir="+outputDir.getAbsolutePath())*/ + + + } + + // 5. 复制 APK 到自定义目录(仅在非 Debug 模式) + if (variant.buildType.name == "release") { + variant.assembleProvider.get().doLast { + def targetDir = new File("${project.rootDir}/app/apk") + copy { + from variant.outputs*.outputFile + into targetDir + } + } + } + } + + +} diff --git a/android_data/jzrcj.keystore b/android_data/jzrcj.keystore new file mode 100644 index 0000000..292ae68 Binary files /dev/null and b/android_data/jzrcj.keystore differ diff --git a/app/.gitignore b/app/.gitignore new file mode 100644 index 0000000..42afabf --- /dev/null +++ b/app/.gitignore @@ -0,0 +1 @@ +/build \ No newline at end of file diff --git a/app/build.gradle b/app/build.gradle new file mode 100644 index 0000000..3bc6294 --- /dev/null +++ b/app/build.gradle @@ -0,0 +1,156 @@ +apply plugin: 'com.android.application' +apply plugin: 'org.jetbrains.kotlin.android' +apply plugin: 'org.greenrobot.greendao' +//apply plugin: 'kotlin-android' + +android { + //添加这两行 + aaptOptions.cruncherEnabled = false +// aaptOptions.useNewCruncher = false + // 签名配置 + signingConfigs { + debug { + keyAlias rootProject.ext.base["signAlias"] + keyPassword rootProject.ext.base["signPassword"] + storeFile file(rootProject.ext.base["signFile"]) + storePassword rootProject.ext.base["signPassword"] + } + release { + keyAlias rootProject.ext.base["signAlias"] + keyPassword rootProject.ext.base["signPassword"] + storeFile file(rootProject.ext.base["signFile"]) + storePassword rootProject.ext.base["signPassword"] + } + } + + compileSdkVersion 34 + + namespace rootProject.ext.base.applicationId // 添加这一行 + defaultConfig { + applicationId rootProject.ext.base.applicationId + minSdkVersion 21 + targetSdkVersion 34 + multiDexEnabled true + versionCode rootProject.ext.base.versionCode + versionName rootProject.ext.base.versionName + resConfigs "zh" + } + + buildTypes { + release { + apply from: rootProject.file('android_data/flavors.gradle') + } + } + + compileOptions { + sourceCompatibility JavaVersion.VERSION_17 + targetCompatibility JavaVersion.VERSION_17 + } + sourceSets { + main { + jniLibs.srcDirs = ['libs'] + assets.srcDirs += ['../android_data/csj_config'] +// res.srcDirs += ['../resource/logo', '../resource/module/视频A001'] + } + } + kotlinOptions { + jvmTarget = '17' + } +} + +repositories { + flatDir { + dirs 'libs' + } +} + +greendao { + schemaVersion 1 //当前数据库版本 + // 生成数据库文件的目录 + targetGenDir 'src/main/java' + // 生成的数据库相关文件的包名 + daoPackage 'com.tfq.finances.db' +} + +tasks.configureEach { task -> + if(task.name.matches("\\w*compile\\w*Kotlin")) { + task.dependsOn('greendao') // 如果有greendao任务 + } +} + + +dependencies { + implementation fileTree(include: ['*.aar', '*.jar'], dir: 'libs') + + implementation 'androidx.constraintlayout:constraintlayout:2.1.4' + api 'androidx.swiperefreshlayout:swiperefreshlayout:1.1.0' + implementation 'androidx.core:core-ktx:1.10.1' + testImplementation 'junit:junit:4.+' + + //---------- 友盟 start ---------- + // 下面各SDK根据宿主App是否使用相关业务按需引入。 + // 友盟统计SDK + implementation 'com.umeng.umsdk:common:9.6.5'// 必选 + implementation 'com.umeng.umsdk:asms:1.4.1'// 必选 + implementation 'com.umeng.umsdk:apm:1.7.0' // 错误分析模块改为独立库,看crash和性能数据请一定集成 + implementation 'com.umeng.umsdk:abtest:1.0.0'//使用U-App中ABTest能力,可选 + //---------- 友盟 end ---------- + + implementation 'com.github.CymChad:BaseRecyclerViewAdapterHelper:2.9.28' + +// implementation 'io.github.youth5201314:banner:2.2.2' +// implementation 'com.github.yujinzhao123:DoubleHeadedDragonBar:1.0.4' + + + /*//进度条 + implementation 'com.daimajia.numberprogressbar:library:1.4@aar' + //播放器(无UI交互) + implementation 'com.github.hty527.iPlayer:iplayer:2.1.26.1' + //SDK默认UI交互组件 + implementation 'com.github.hty527.iPlayer:widget:2.1.26.1' + //ijk音视频解码器 + implementation 'com.github.hty527.iPlayer:ijk:2.1.26.1' + //exo音视频解码器 + implementation 'com.github.hty527.iPlayer:exo:2.1.26.1' + + //音乐播放器 + implementation 'com.github.hty527.iMusic:music-player:1.2.0' + //视频播放器 + implementation 'com.github.hty527.iMusic:video-player:1.2.0'*/ + + implementation 'io.reactivex:rxjava:1.0.14' + implementation 'io.reactivex:rxandroid:1.0.1' + + //BasePopup +// implementation 'io.github.razerdp:BasePopup:3.2.1' + + //文件管理 +// implementation 'com.jiajunhui.xapp.medialoader:medialoader:1.2.1' + + + // 替代原有的 jdk7/jdk8 分离依赖 + implementation "org.jetbrains.kotlin:kotlin-stdlib:2.0.0" + // 合并后的标准库:ml-citation{ref="6,8" data="citationList"} + + + implementation project(':BaseLibrary') + + implementation fileTree(dir: '../LibraryAd/libs', include: ['*.aar']) + implementation project(':LibraryAd') +// implementation 'com.tfq:libraryad:1.0.0' + + // build.gradle 高颜值日期选择器 +// implementation 'com.prolificinteractive:material-calendarview:2.0.1' + implementation 'com.github.gzu-liyujiang.AndroidPicker:WheelPicker:4.1.12' + + //数据库 + implementation 'org.greenrobot:greendao:3.3.0' + + //JSON动画 + implementation "com.airbnb.android:lottie:6.6.6" + + //PAG动画 +// implementation 'org.libpag:pag:4.4.31' +// implementation(name: 'libpag_4.4.31_android_armeabi_armv7a_arm64v8a.aar', ext: 'aar') + implementation("androidx.exifinterface:exifinterface:1.3.3") +} diff --git a/app/libs/libpag_4.4.31_android_armeabi_armv7a_arm64v8a.aar b/app/libs/libpag_4.4.31_android_armeabi_armv7a_arm64v8a.aar new file mode 100644 index 0000000..54063b9 Binary files /dev/null and b/app/libs/libpag_4.4.31_android_armeabi_armv7a_arm64v8a.aar differ diff --git a/app/proguard-rules.pro b/app/proguard-rules.pro new file mode 100644 index 0000000..21f19e4 --- /dev/null +++ b/app/proguard-rules.pro @@ -0,0 +1,23 @@ +# Add project specific ProGuard rules here. +# You can control the set of applied configuration files using the +# proguardFiles setting in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} + +# Uncomment this to preserve the line number information for +# debugging stack traces. +#-keepattributes SourceFile,LineNumberTable + +# If you keep the line number information, uncomment this to +# hide the original source file name. +#-renamesourcefileattribute SourceFile +-dontwarn com.lxj.xpopup.widget.** +-keep class com.lxj.xpopup.widget.**{*;} \ No newline at end of file diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml new file mode 100644 index 0000000..2779f37 --- /dev/null +++ b/app/src/main/AndroidManifest.xml @@ -0,0 +1,139 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/assets/url_protocol_share.html b/app/src/main/assets/url_protocol_share.html new file mode 100644 index 0000000..f39fbb5 --- /dev/null +++ b/app/src/main/assets/url_protocol_share.html @@ -0,0 +1,167 @@ + + + + + 第三方共享数据清单 + + + + + + +

第三方共享数据清单


+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
三方公司名称产品/类型共享信息名称使用目的使用场景共享方式第三方个人信息处理规则第三方数据安全能力描述
友盟同欣(北京)科技有限公司友盟+移动统计SDK + 设备MAC地址、唯一设备标识码(IDFA、IMEI、OAID、Android_ID、IMSI)、App内用户操作记录、运行中进程信息 + + 提供提供用户行为数据、App运营数据的采集与可视化分析,帮助更好的分析决策实现业务增长 + 数据分析嵌入第三方SDK,SDK收集传输个人信息https://www.umeng.com/policy获得了ISO/IEC 27001:2013 信息安全管理体系认证、ISO/IEC + 27018:2019公有云个人信息保护管理体系认证。并拥有公安部颁发的信息系统安全三级等保证书。承诺其信息安全管理体系满足国内外高标准的个人信息保护法律法规要求。 +
友盟同欣(北京)科技有限公司友盟+智能认证SDK设备标识符(IMEI/Mac/android ID/IDFA/OPENUDID/GUID、SIM 卡 IMSI + 信息)以及手机号码,用于提供手机号码一键登录服务;智能认证具备反作弊功能,通过采集地理位置提供反作弊服务,剔除作弊设备 + + 整合三大运营商号码认证能力,为您提供手机号码校验技术能力,方便您的用户一键通过验证,无需号码手动输入和短信验证,通过安全极速的登录体验帮助您提升用户注册率。同时,借助大数据能力,为您提供更高级的反作弊功能 + 一键登录嵌入第三方sdk,用于集成中国移动、中国联通、中国电信免密认证产品能力https://www.umeng.com/policy获得了ISO/IEC 27001:2013 信息安全管理体系认证、ISO/IEC + 27018:2019公有云个人信息保护管理体系认证。并拥有公安部颁发的信息系统安全三级等保证书。承诺其信息安全管理体系满足国内外高标准的个人信息保护法律法规要求。 +
湖北今日头条科技有限公司穿山甲iOS/Android SDK + 设备MAC地址、唯一设备标识码(IDFA、IMEI、OAID、Android_ID、IMSI)、位置信息、运行中进程信息、硬件序列号 + 提供商业化效果广告投放资源展示效果广告嵌入第三方SDK,SDK收集传输个人信息https://www.pangle.cn/privacy进行去标识化处理
深圳市腾讯计算机系统有限公司优量汇SDK + 设备MAC地址、唯一设备标识码(IDFA、IMEI、OAID、Android_ID、IMSI)、位置信息、运行中进程信息、电话号码 + 提供商业化效果广告投放资源。展示效果广告嵌入第三方SDK,SDK收集传输个人信息 + https://qzs.gdtimg.com/union/res/union_cdn/page/dev_rules/ylh_sdk_privacy_statement.html + 通过信息安全等级保护(三级)
上海亦拓广告有限公司MMA 中国广告监测通用SDK设备标识信息(OAID、IMEI、Android ID等)、网络信息(连接的WIFI、MAC地址等)广告监测广告投放过程中嵌入第三方SDK,SDK收集传输个人信息https://mmachina.cn/sdk-privacy/ + 通过信息安全等级保护(三级)
支付宝(中国)网络技术有限公司“App支付”SDK + 设备MAC地址、唯一设备标识码(IDFA、IMEI、OAID、Android_ID、IMSI)、位置信息、运行中进程信息、电话号码 + 提供第三方支付服务会员订单支付嵌入第三方SDK,SDK收集传输个人信息https://render.alipay.com/p/c/183i5h7fnt4w + 通过中国网络安全审查技术与认证中心(CCRC)个人信息安全管理体系认证
中移互联网有限公司移动SDK + 网络类型、网络地址、运营商类型、本机号码、国际移动用户识别码、sim卡数量识别卡状态、手机设备类型、手机操作系统、硬件厂商 + 以手机号为基础进行一键式登录一键登录嵌入第三方SDK用于集成中国移动免密认证产品能力http://wap.cmpassport.com/resources/html/contract.html + 获得信息系统等级保护第三级
中国联合网络通信有限公司北京市分公司/小沃科技有限公司联通SDKIMSI、首选卡、IP地址、本机号码以手机号为基础进行一键式登录一键登录 + <嵌入第三方SDK用于集成中国联通免密认证产品> + 能力 + https://ms.zzx9.cn/html/oauth/protocol2.html + 获得信息系统等级保护第三级
世纪龙信息网络有限责任公司电信SDK网络IP地址、网络类型、网络连接状态、本机号码以手机号为基础进行一键式登录一键登录嵌入第三方SDK用于集成中国电信免密认证产品能力 + https://e.189.cn/sdk/agreement/detail.do?isWap=true&hidetop=true&appKey= + 获得信息系统等级保护第三级
+ 共享个人信息字段定义及穷举
设备MAC地址:设备的 + MAC 地址,确认网上设备位置的地址
唯一设备识别码:IDFA、IMEI、OAID、Android_ID、IMSI、MEID
位置信息:设备地理位置信息,如经纬度
设备IP地址:移动互联网连接协议,确认网络连接的运营商服务
设备信息:设备品牌、设备型号、操作系统、操作系统版本
网络信息:所处网络环境,如Wi-Fi、5G、4G、3G、2G
运营商信息:移动、联通、电信 +
+
+
+ + \ No newline at end of file diff --git a/app/src/main/java/com/tfq/finances/app/App.java b/app/src/main/java/com/tfq/finances/app/App.java new file mode 100644 index 0000000..fe8ed94 --- /dev/null +++ b/app/src/main/java/com/tfq/finances/app/App.java @@ -0,0 +1 @@ +package com.tfq.finances.app; import android.app.Application; import android.content.Context; import android.content.SharedPreferences; import android.content.pm.ApplicationInfo; import android.content.pm.PackageManager; import android.database.sqlite.SQLiteDatabase; import android.text.TextUtils; import com.tfq.ad.ad.CacheADManager; import com.tfq.ad.ad.TTAdManagerHolder; import com.tfq.finances.core.constants.Constants; import com.tfq.finances.jzrcj.R; import com.tfq.library.app.BaseConstants; import com.tfq.library.app.IConstituteApp; import com.tfq.finances.db.DaoMaster; import com.tfq.finances.db.DaoSession; import com.tfq.finances.db.db.GDOpenHelper; import com.tfq.library.utils.AppSigning; import com.tfq.library.utils.AppUtil; import com.tfq.library.utils.LogK; import com.tfq.library.utils.SpManager; import com.umeng.commonsdk.UMConfigure; import java.util.ArrayList; import java.util.List; public class App extends Application { public static Context mContext; public static App instance; public static DaoSession mSession; private String appid; //新增参数,固定值,后台app逻辑id,优化接口调用使用 private String appinfoId; private String csjId; private String umId; private boolean noAD; private String csjIdSplash, csjIdCQP, csjIdFeed1, csjIdFeed2, csjIdFeed3, csjIdReward, csjIdBanner, csjIdDraw; private List applications; private boolean need_login; public static App getInstances() { return instance; } public static String getChannel() { try { PackageManager pm = mContext.getPackageManager(); ApplicationInfo appInfo = pm.getApplicationInfo(mContext.getPackageName(), PackageManager.GET_META_DATA); String umeng_channel = appInfo.metaData.getString("UMENG_CHANNEL"); if (umeng_channel != null) { return umeng_channel; } return "other"; } catch (PackageManager.NameNotFoundException ignored) { } return "other"; } public static Context getContext() { return mContext; } public static void setNavigationBarColor(String navigationBarColor) { BaseConstants.navigationBarColor = navigationBarColor; } @Override public void onCreate() { super.onCreate(); instance = this; mContext = getApplicationContext(); UMConfigure.preInit(mContext, getUmId(), getChannel()); LogK.e("sha1= " + AppSigning.getSha1(this)); LogK.e("build " + "\n" + AppUtil.getPackageName(mContext) + "\n" + getChannel() + "\n" + getCsjId() + "\n" + mContext.getResources().getString(R.string.app_name) + "\n" + AppUtil.getAppVersionName(mContext) + "\n" + AppUtil.getAppVersionCode(mContext) + "\n" ); // AppUtil.getPublicKey(AppUtil.getSign(mContext)); // LogK.e(GetPublicKey.getSignInfo(mContext)); CacheADManager.init(); initDb(); // setNavigationBarColor("#ffffff"); agreeSercurity(); } private void initDb() { try { GDOpenHelper gdOpenHelper = new GDOpenHelper(this, "tfq.db"); SQLiteDatabase db = gdOpenHelper.getWritableDatabase(); DaoMaster daoMaster = new DaoMaster(db); mSession = daoMaster.newSession(); } catch (Exception e) { e.printStackTrace(); } } public DaoSession getDaoSession() { return mSession; } public String getUmId() { if (TextUtils.isEmpty(umId)) { umId = mContext.getResources().getString(R.string.umId); } return umId; } public String getAppid() { if (TextUtils.isEmpty(appid)) { appid = mContext.getResources().getString(R.string.appid); } return appid; } /** * 获取appinfoId * @return */ public String getAppinfoId() { if (TextUtils.isEmpty(appinfoId)) { appinfoId = mContext.getResources().getString(R.string.appinfoId); } return appinfoId; } public String getCsjId() { if (TextUtils.isEmpty(csjId)) { csjId = mContext.getResources().getString(R.string.csjId); } return csjId; } public boolean getNeedLogin() { return need_login = mContext.getResources().getBoolean(R.bool.need_login); } public void agreeSercurity() { initApplications(); SharedPreferences sharedPreferences = SpManager.startRead(mContext, Constants.SP_NAME); boolean openNoFirst = sharedPreferences.getBoolean("no_first_open", false); if (openNoFirst) { initCSJ(); UMConfigure.init(this, getUmId(), getChannel(), UMConfigure.DEVICE_TYPE_PHONE, ""); } } private void initApplications() { BaseConstants.BASE_APP_DEBUG_PRINT = Constants.APP_DEBUG_PRINT; BaseConstants.APP_NAME = Constants.APP_NAME; BaseConstants.APP_ID = Constants.APP_ID; BaseConstants.CSJ_ID = App.getInstances().getCsjId(); BaseConstants.CHANNEL = getChannel(); BaseConstants.csjIdFeed1 = getCsjIdFeed1(); BaseConstants.csjIdFeed2 = getCsjIdFeed2(); BaseConstants.NO_AD = getNoAd(); BaseConstants.CODE_AD_SPLASH = getCsjIdSplash(); BaseConstants.CODE_AD_CQP = getCsjIdCQP(); BaseConstants.CODE_AD_FEED1 = getCsjIdFeed1(); BaseConstants.CODE_AD_FEED2 = getCsjIdFeed2(); BaseConstants.CODE_AD_FEED3 = getCsjIdFeed3(); BaseConstants.CODE_AD_BANNER = getCsjIdBanner(); BaseConstants.CODE_AD_REWARD = getCsjIdReward(); BaseConstants.CODE_AD_DRAW = getCsjIdDraw(); BaseConstants.appSplash = getAppSplash(); applications = new ArrayList<>(); applications.add("com.tfq.library.app.LibraryApp"); applications.add("com.tfq.ad.app.AdApp"); for (String item : applications) { try { Class clazz = Class.forName(item); if (clazz != null) { Object obj = clazz.newInstance(); if (obj instanceof IConstituteApp) { IConstituteApp a = (IConstituteApp) obj; a.onCreate(this); } } } catch (Exception e) { e.printStackTrace(); } } LogK.e("BaseConstants.BASE_APP_DEBUG_PRINT=" + BaseConstants.BASE_APP_DEBUG_PRINT + "\n" + "BaseConstants.APP_NAME=" + BaseConstants.APP_NAME + "\n" + "BaseConstants.APP_ID=" + BaseConstants.APP_ID + "\n" + "BaseConstants.CHANNEL=" + BaseConstants.CHANNEL + "\n" + "BaseConstants.csjIdFeed1=" + BaseConstants.csjIdFeed1 + "\n" + "BaseConstants.csjIdFeed2=" + BaseConstants.csjIdFeed2 + "\n" + "BaseConstants.NO_AD=" + BaseConstants.NO_AD + "\n" + "BaseConstants.CODE_AD_SPLASH=" + BaseConstants.CODE_AD_SPLASH + "\n" + "BaseConstants.CODE_AD_CQP=" + BaseConstants.CODE_AD_CQP + "\n" + "BaseConstants.CODE_AD_FEED1=" + BaseConstants.CODE_AD_FEED1 + "\n" + "BaseConstants.CODE_AD_FEED2=" + BaseConstants.CODE_AD_FEED2 + "\n" + "BaseConstants.appSplash=" + BaseConstants.appSplash + "\n" ); } private void initCSJ() { if (!getNoAd()) { TTAdManagerHolder.init(mContext); } } @Override protected void attachBaseContext(Context base) { super.attachBaseContext(base); } public boolean getNoAd() { noAD = mContext.getResources().getBoolean(R.bool.noAD); return noAD; } public String getCsjIdSplash() { if (TextUtils.isEmpty(csjIdSplash)) { csjIdSplash = mContext.getResources().getString(R.string.csjIdSplash); } return csjIdSplash; } public String getCsjIdCQP() { if (TextUtils.isEmpty(csjIdCQP)) { csjIdCQP = mContext.getResources().getString(R.string.csjIdCQP); } return csjIdCQP; } public String getCsjIdFeed1() { if (TextUtils.isEmpty(csjIdFeed1)) { csjIdFeed1 = mContext.getResources().getString(R.string.csjIdFeed1); } return csjIdFeed1; } public String getCsjIdFeed2() { if (TextUtils.isEmpty(csjIdFeed2)) { csjIdFeed2 = mContext.getResources().getString(R.string.csjIdFeed2); } return csjIdFeed2; } public String getCsjIdFeed3() { if (TextUtils.isEmpty(csjIdFeed3)) { csjIdFeed3 = mContext.getResources().getString(R.string.csjIdFeed3); } return csjIdFeed3; } public String getCsjIdReward() { if (TextUtils.isEmpty(csjIdReward)) { csjIdReward = mContext.getResources().getString(R.string.csjIdReward); } return csjIdReward; } public String getCsjIdBanner() { if (TextUtils.isEmpty(csjIdBanner)) { csjIdBanner = mContext.getResources().getString(R.string.csjIdBanner); } return csjIdBanner; } public String getCsjIdDraw() { if (TextUtils.isEmpty(csjIdDraw)) { csjIdDraw = mContext.getResources().getString(R.string.csjIdDraw); } return csjIdDraw; } public int getAppSplash() { return R.drawable.app_splash; } } \ No newline at end of file diff --git a/app/src/main/java/com/tfq/finances/core/constants/Constants.java b/app/src/main/java/com/tfq/finances/core/constants/Constants.java new file mode 100644 index 0000000..8e9d434 --- /dev/null +++ b/app/src/main/java/com/tfq/finances/core/constants/Constants.java @@ -0,0 +1,48 @@ +package com.tfq.finances.core.constants; + +import com.tfq.finances.app.App; +import com.tfq.finances.jzrcj.R; + +public class Constants { + /** + * SharedPreferences + */ + public static final String SP_NAME = App.getContext().getResources().getString(R.string.sp_name); + /** + * 是否打印日志 + */ + public static boolean APP_DEBUG_PRINT = App.getContext().getResources().getBoolean(R.bool.APP_DEBUG_PRINT); + /** + * 请求失败 + */ + public static int URL_REQUEST_ERROR = 401; + /** + * 应用名称 + */ + public static String APP_NAME = App.getContext().getResources().getString(R.string.app_name); + /** + * 应用ID + */ + public static String APP_ID = App.getInstances().getAppid(); + /** + * 穿山甲ID + */ + public static String CSJ_ID = App.getInstances().getAppid(); + /** + * 应用信息ID + */ + public static String APP_INFO_ID = App.getInstances().getAppinfoId(); + /** + * 用户ID + */ + public static long USER_ID = -1; + /** + * 应用渠道 + */ + public static String CHANNEL = App.getChannel(); + /** + * 应用类型 固定值 9 + */ + public static String APP_TYPE = "9"; + +} diff --git a/app/src/main/java/com/tfq/finances/core/enums/AdvFlagEnum.java b/app/src/main/java/com/tfq/finances/core/enums/AdvFlagEnum.java new file mode 100644 index 0000000..3904720 --- /dev/null +++ b/app/src/main/java/com/tfq/finances/core/enums/AdvFlagEnum.java @@ -0,0 +1,25 @@ +package com.tfq.finances.core.enums; + +/** + * 广告开关枚举 + * 0关闭1开启 + */ +public enum AdvFlagEnum { + OFF("关闭", "0"), ON("开启", "1"); + + private final String name; + private final String value; + + AdvFlagEnum(String name, String value) { + this.name = name; + this.value = value; + } + + public String getName() { + return name; + } + + public String getValue() { + return value; + } +} diff --git a/app/src/main/java/com/tfq/finances/core/enums/AvatarEnum.java b/app/src/main/java/com/tfq/finances/core/enums/AvatarEnum.java new file mode 100644 index 0000000..756dc6c --- /dev/null +++ b/app/src/main/java/com/tfq/finances/core/enums/AvatarEnum.java @@ -0,0 +1,137 @@ +package com.tfq.finances.core.enums; + +import com.tfq.finances.jzrcj.R; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +public enum AvatarEnum { + // 枚举实例 + AVATAR1(1, "头像1", R.mipmap.avatar_01, R.drawable.ll_boder_head, R.drawable.ll_boder_head_null), + AVATAR2(2, "头像2", R.mipmap.avatar_02, R.drawable.ll_boder_head, R.drawable.ll_boder_head_null), + AVATAR3(3, "头像3", R.mipmap.avatar_03, R.drawable.ll_boder_head, R.drawable.ll_boder_head_null), + AVATAR4(4, "头像4", R.mipmap.avatar_04, R.drawable.ll_boder_head, R.drawable.ll_boder_head_null), + AVATAR5(5, "头像5", R.mipmap.avatar_05, R.drawable.ll_boder_head, R.drawable.ll_boder_head_null), + AVATAR6(6, "头像6", R.mipmap.avatar_06, R.drawable.ll_boder_head, R.drawable.ll_boder_head_null), + AVATAR7(7, "头像7", R.mipmap.avatar_07, R.drawable.ll_boder_head, R.drawable.ll_boder_head_null), + AVATAR8(8, "头像8", R.mipmap.avatar_08, R.drawable.ll_boder_head, R.drawable.ll_boder_head_null), + AVATAR9(9, "头像9", R.mipmap.avatar_09, R.drawable.ll_boder_head, R.drawable.ll_boder_head_null), + AVATAR10(10, "头像10", R.mipmap.avatar_10, R.drawable.ll_boder_head, R.drawable.ll_boder_head_null), + AVATAR11(11, "头像11", R.mipmap.avatar_11, R.drawable.ll_boder_head, R.drawable.ll_boder_head_null), + AVATAR12(12, "头像12", R.mipmap.avatar_12, R.drawable.ll_boder_head, R.drawable.ll_boder_head_null), + AVATAR13(13, "头像13", R.mipmap.avatar_13, R.drawable.ll_boder_head, R.drawable.ll_boder_head_null), + AVATAR14(14, "头像14", R.mipmap.avatar_14, R.drawable.ll_boder_head, R.drawable.ll_boder_head_null), + AVATAR15(15, "头像15", R.mipmap.avatar_15, R.drawable.ll_boder_head, R.drawable.ll_boder_head_null), + AVATAR16(16, "头像16", R.mipmap.avatar_16, R.drawable.ll_boder_head, R.drawable.ll_boder_head_null), + AVATAR17(17, "头像17", R.mipmap.avatar_17, R.drawable.ll_boder_head, R.drawable.ll_boder_head_null), + AVATAR18(18, "头像18", R.mipmap.avatar_18, R.drawable.ll_boder_head, R.drawable.ll_boder_head_null), + AVATAR19(19, "头像19", R.mipmap.avatar_19, R.drawable.ll_boder_head, R.drawable.ll_boder_head_null), + AVATAR20(20, "头像20", R.mipmap.avatar_20, R.drawable.ll_boder_head, R.drawable.ll_boder_head_null); + + private final int id; + private final String name; + private final int avatar; + private final int icUnOnChecked; + private final int icOnChecked; + + // 缓存映射 + private static final Map ID_MAP = new HashMap<>(); + private static final Map NAME_MAP = new HashMap<>(); + + static { + for (AvatarEnum category : values()) { + ID_MAP.put(category.id, category); + NAME_MAP.put(category.name, category); + } + } + + AvatarEnum(int id, String name, int avatar, int icOnChecked, int icUnOnChecked) { + this.id = id; + this.name = name; + this.avatar = avatar; + this.icOnChecked = icOnChecked; + this.icUnOnChecked = icUnOnChecked; + } + + public int getId() { + return id; + } + + public String getName() { + return name; + } + + public int getAvatar() { + return avatar; + } + + public int getIcUnOnChecked() { + return icUnOnChecked; + } + + public int getIcOnChecked() { + return icOnChecked; + } + + /** + * 获取所有枚举的列表 + * @return 包含所有枚举的列表 + */ + public static List getAll() { + List list = new ArrayList<>(); + for (AvatarEnum category : values()) { + list.add(category); + } + return list; + } + + /** + * 根据ID获取枚举 + * @param id 枚举ID + * @return 对应的枚举实例,未找到返回null + */ + public static AvatarEnum getById(int id) { + return ID_MAP.get(id); + } + + /** + * 根据名称获取枚举 + * @param name 枚举名称 + * @return 对应的枚举实例,未找到返回null + */ + public static AvatarEnum getByName(String name) { + return NAME_MAP.get(name); + } + + /** + * 根据ID获取名称 + * @param id 枚举ID + * @return 对应的名称,未找到返回null + */ + public static String getNameById(int id) { + AvatarEnum category = ID_MAP.get(id); + return category != null ? category.name : null; + } + + /** + * 根据ID获取头像 + * @param id 枚举ID + * @return 对应的名称,未找到返回null + */ + public static int getAvatarById(int id) { + AvatarEnum category = ID_MAP.get(id); + return category != null ? category.avatar : 0; + } + + /** + * 根据名称获取ID + * @param name 枚举名称 + * @return 对应的ID,未找到返回-1 + */ + public static int getIdByName(String name) { + AvatarEnum category = NAME_MAP.get(name); + return category != null ? category.id : -1; + } +} diff --git a/app/src/main/java/com/tfq/finances/core/enums/CommonFlagEnum.java b/app/src/main/java/com/tfq/finances/core/enums/CommonFlagEnum.java new file mode 100644 index 0000000..57b3f12 --- /dev/null +++ b/app/src/main/java/com/tfq/finances/core/enums/CommonFlagEnum.java @@ -0,0 +1,54 @@ +package com.tfq.finances.core.enums; + +/** + * 通用开关枚举类,用于表示开关状态。 + */ +public enum CommonFlagEnum { + /** + * 表示开启状态。 + */ + YES("YES", "1"), + /** + * 表示关闭状态。 + */ + NO("NO", "0"); + + /** + * 开关的名称。 + */ + private final String name; + /** + * 开关的值。 + */ + private final String value; + + /** + * 构造函数,初始化开关的名称和值。 + * + * @param name 开关的名称 + * @param value 开关的值 + */ + CommonFlagEnum(String name, String value) { + this.name = name; + this.value = value; + } + + /** + * 获取开关的名称。 + * + * @return 开关的名称 + */ + public String getName() { + return name; + } + + /** + * 获取开关的值。 + * + * @return 开关的值 + */ + public String getValue() { + return value; + } + +} diff --git a/app/src/main/java/com/tfq/finances/core/enums/CommonTypeOriginEnum.java b/app/src/main/java/com/tfq/finances/core/enums/CommonTypeOriginEnum.java new file mode 100644 index 0000000..c981ff4 --- /dev/null +++ b/app/src/main/java/com/tfq/finances/core/enums/CommonTypeOriginEnum.java @@ -0,0 +1,50 @@ +package com.tfq.finances.core.enums; + +public enum CommonTypeOriginEnum { + /** + * 系统。 + */ + SYSTEM("system", "系统"), + /** + * 用户自定义。 + */ + USER("user", "用户自定义"); + + /** + * 名称。 + */ + private final String name; + /** + * 值。 + */ + private final String value; + + /** + * 构造函数 + * + * @param name 名称 + * @param value 值 + */ + CommonTypeOriginEnum(String name, String value) { + this.name = name; + this.value = value; + } + + /** + * 获取名称。 + * + * @return 开关的名称 + */ + public String getName() { + return name; + } + + /** + * 获取值。 + * + * @return 开关的值 + */ + public String getValue() { + return value; + } +} diff --git a/app/src/main/java/com/tfq/finances/core/enums/SystemExpensesCategoriesEnum.java b/app/src/main/java/com/tfq/finances/core/enums/SystemExpensesCategoriesEnum.java new file mode 100644 index 0000000..fc33c90 --- /dev/null +++ b/app/src/main/java/com/tfq/finances/core/enums/SystemExpensesCategoriesEnum.java @@ -0,0 +1,148 @@ +package com.tfq.finances.core.enums; + +import com.tfq.finances.jzrcj.R; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +public enum SystemExpensesCategoriesEnum { + // 枚举实例 + RESTAURANT(31325, "餐饮", R.mipmap.ic_cy_check, R.mipmap.ic_cy_checked), + FRUIT(31326, "水果", R.mipmap.ic_sg_check, R.mipmap.ic_sg_checked), + SNACK(31327, "零食", R.mipmap.ic_ls_check, R.mipmap.ic_ls_checked), + GROCERY(31328, "买菜", R.mipmap.ic_mc_check, R.mipmap.ic_mc_checked), + CHILD(31329, "孩子", R.mipmap.ic_hz_check, R.mipmap.ic_hz_checked), + MEMBERSHIP(31330, "会员", R.mipmap.ic_hy_check, R.mipmap.ic_hy_checked), + COURIER(31331, "快递", R.mipmap.ic_kd_check, R.mipmap.ic_kd_checked), + BEAUTY(31332, "美容", R.mipmap.ic_mr_check, R.mipmap.ic_mr_checked), + STUDY(31333, "学习", R.mipmap.ic_xx_check, R.mipmap.ic_xx_checked), + DAILY_NECESSITIES(31334, "日用品", R.mipmap.ic_ryp_check, R.mipmap.ic_ryp_checked), + UTILITIES(31335, "水电煤", R.mipmap.ic_sdm_check, R.mipmap.ic_sdm_checked), + COMMUNICATION(31336, "通讯", R.mipmap.ic_tx_check, R.mipmap.ic_tx_checked), + MAINTENANCE(31337, "维修", R.mipmap.ic_wx_check, R.mipmap.ic_wx_checked), + TRANSPORTATION(31338, "交通", R.mipmap.ic_jt_check, R.mipmap.ic_jt_checked), + PET(31339, "宠物", R.mipmap.ic_cw_check, R.mipmap.ic_cw_checked), + LOTTERY(31340, "彩票", R.mipmap.ic_cp_check, R.mipmap.ic_cp_checked), + CLOTHING(31341, "服饰", R.mipmap.ic_fs_check, R.mipmap.ic_fs_checked), + GIFT(31342, "礼物", R.mipmap.ic_lw_check, R.mipmap.ic_lw_checked), + DIGITAL(31343, "数码", R.mipmap.ic_sm_check, R.mipmap.ic_sm_checked), + ENTERTAINMENT(31344, "娱乐", R.mipmap.ic_yl_check, R.mipmap.ic_yl_checked), + MEDICINE(31345, "医药", R.mipmap.ic_yy_check, R.mipmap.ic_yy_checked), + HOUSING(31346, "住房", R.mipmap.ic_zf_check, R.mipmap.ic_zf_checked), + CAR(31347, "汽车", R.mipmap.ic_qc_check, R.mipmap.ic_qc_checked), + ELECTRICAL_APPLIANCE(31348, "电器", R.mipmap.ic_dq_check, R.mipmap.ic_dq_checked), + OFFICE(31349, "办公", R.mipmap.ic_bg_check, R.mipmap.ic_bg_checked), + INSURANCE(31350, "保险", R.mipmap.ic_bx_check, R.mipmap.ic_bx_checked), + FURNITURE(31351, "家具", R.mipmap.ic_jj_check, R.mipmap.ic_jj_checked), + DONATION(31352, "捐赠", R.mipmap.ic_jzeng_check, R.mipmap.ic_jzeng_checked), + SPORTS(31353, "运动", R.mipmap.ic_yd_check, R.mipmap.ic_yd_checked), + TRAVEL(31354, "旅行", R.mipmap.ic_lx_check, R.mipmap.ic_lx_checked), + INVESTMENT(31355, "投资", R.mipmap.ic_tz_check, R.mipmap.ic_tz_checked), + GAME(31356, "游戏", R.mipmap.ic_yx_check, R.mipmap.ic_yx_checked), + CASH_GIFT(31357, "礼金", R.mipmap.ic_lj_check, R.mipmap.ic_lj_checked), + JEWELRY(31358, "珠宝", R.mipmap.ic_zb_check, R.mipmap.ic_zb_checked), + ELDERS(31359, "长辈", R.mipmap.ic_zbei_check, R.mipmap.ic_zbei_checked), + TOBACCO_ALCOHOL(31360, "烟酒", R.mipmap.ic_yj_check, R.mipmap.ic_yj_checked); + + private final int id; + private final String name; + private final int iconUnchecked; + private final int iconChecked; + + // 缓存映射 + private static final Map ID_MAP = new HashMap<>(); + private static final Map NAME_MAP = new HashMap<>(); + + static { + for (SystemExpensesCategoriesEnum category : values()) { + ID_MAP.put(category.id, category); + NAME_MAP.put(category.name, category); + } + } + + SystemExpensesCategoriesEnum(int id, String name, int iconUnchecked, int iconChecked) { + this.id = id; + this.name = name; + this.iconUnchecked = iconUnchecked; + this.iconChecked = iconChecked; + } + + public int getId() { + return id; + } + + public String getName() { + return name; + } + + public int getIconUnchecked() { + return iconUnchecked; + } + + public int getIconChecked() { + return iconChecked; + } + + /** + * 获取所有枚举的列表 + * @return 包含所有枚举的列表 + */ + public static List getAll() { + List list = new ArrayList<>(); + for (SystemExpensesCategoriesEnum category : values()) { + list.add(category); + } + return list; + } + + /** + * 根据ID获取枚举 + * @param id 枚举ID + * @return 对应的枚举实例,未找到返回null + */ + public static SystemExpensesCategoriesEnum getById(int id) { + return ID_MAP.get(id); + } + + /** + * 根据名称获取枚举 + * @param name 枚举名称 + * @return 对应的枚举实例,未找到返回null + */ + public static SystemExpensesCategoriesEnum getByName(String name) { + return NAME_MAP.get(name); + } + + /** + * 根据ID获取名称 + * @param id 枚举ID + * @return 对应的名称,未找到返回null + */ + public static String getNameById(int id) { + SystemExpensesCategoriesEnum category = ID_MAP.get(id); + return category != null ? category.name : null; + } + + /** + * 根据ID获取选中的图标 + * @param id 枚举ID + * @return 对应的名称,未找到返回null + */ + public static int getCheckedIconById(int id) { + SystemExpensesCategoriesEnum category = ID_MAP.get(id); + return category != null ? category.iconChecked : -1; + } + + /** + * 根据名称获取ID + * @param name 枚举名称 + * @return 对应的ID,未找到返回-1 + */ + public static int getIdByName(String name) { + SystemExpensesCategoriesEnum category = NAME_MAP.get(name); + return category != null ? category.id : -1; + } +} diff --git a/app/src/main/java/com/tfq/finances/core/enums/SystemIncomeCategoriesEnum.java b/app/src/main/java/com/tfq/finances/core/enums/SystemIncomeCategoriesEnum.java new file mode 100644 index 0000000..5a88ad0 --- /dev/null +++ b/app/src/main/java/com/tfq/finances/core/enums/SystemIncomeCategoriesEnum.java @@ -0,0 +1,118 @@ +package com.tfq.finances.core.enums; + +import com.tfq.finances.jzrcj.R; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +public enum SystemIncomeCategoriesEnum { + // 枚举实例 + RED_PACKET(31361, "收红包", R.mipmap.ic_shb_check, R.mipmap.ic_shb_checked), + SALARY(31362, "工资", R.mipmap.ic_gz_check, R.mipmap.ic_gz_checked), + PART_TIME_JOB(31363, "兼职", R.mipmap.ic_jz_check, R.mipmap.ic_jz_checked), + FINANCIAL_MANAGEMENT(31364, "理财", R.mipmap.ic_lc_check, R.mipmap.ic_lc_checked), + INSURANCE(31365, "保险", R.mipmap.ic_bx_check, R.mipmap.ic_bx_checked), + BONUS(31366, "奖金", R.mipmap.ic_jjin_check, R.mipmap.ic_jjin_checked), + REIMBURSEMENT(31367, "报销", R.mipmap.ic_bxiao_check, R.mipmap.ic_bxiao_checked); + + private final int id; + private final String name; + private final int mipmapCheck; + private final int mipmapChecked; + + // 缓存映射 + private static final Map ID_MAP = new HashMap<>(); + private static final Map NAME_MAP = new HashMap<>(); + + static { + for (SystemIncomeCategoriesEnum category : values()) { + ID_MAP.put(category.id, category); + NAME_MAP.put(category.name, category); + } + } + + SystemIncomeCategoriesEnum(int id, String name, int mipmapCheck, int mipmapChecked) { + this.id = id; + this.name = name; + this.mipmapCheck = mipmapCheck; + this.mipmapChecked = mipmapChecked; + } + + public int getId() { + return id; + } + + public String getName() { + return name; + } + + public int getIconUnchecked() { + return mipmapCheck; + } + + public int getIconChecked() { + return mipmapChecked; + } + + /** + * 获取所有枚举的列表 + * @return 包含所有枚举的列表 + */ + public static List getAll() { + List list = new ArrayList<>(); + for (SystemIncomeCategoriesEnum category : values()) { + list.add(category); + } + return list; + } + + /** + * 根据ID获取枚举 + * @param id 枚举ID + * @return 对应的枚举实例,未找到返回null + */ + public static SystemIncomeCategoriesEnum getById(int id) { + return ID_MAP.get(id); + } + + /** + * 根据名称获取枚举 + * @param name 枚举名称 + * @return 对应的枚举实例,未找到返回null + */ + public static SystemIncomeCategoriesEnum getByName(String name) { + return NAME_MAP.get(name); + } + + /** + * 根据ID获取名称 + * @param id 枚举ID + * @return 对应的名称,未找到返回null + */ + public static String getNameById(int id) { + SystemIncomeCategoriesEnum category = ID_MAP.get(id); + return category != null ? category.name : null; + } + + /** + * 根据ID获取选中的图标 + * @param id 枚举ID + * @return 对应的名称,未找到返回null + */ + public static int getCheckedIconById(int id) { + SystemIncomeCategoriesEnum category = ID_MAP.get(id); + return category != null ? category.mipmapChecked : -1; + } + + /** + * 根据名称获取ID + * @param name 枚举名称 + * @return 对应的ID,未找到返回-1 + */ + public static int getIdByName(String name) { + SystemIncomeCategoriesEnum category = NAME_MAP.get(name); + return category != null ? category.id : -1; + } +} diff --git a/app/src/main/java/com/tfq/finances/core/enums/TokenRefreshLock.java b/app/src/main/java/com/tfq/finances/core/enums/TokenRefreshLock.java new file mode 100644 index 0000000..1937541 --- /dev/null +++ b/app/src/main/java/com/tfq/finances/core/enums/TokenRefreshLock.java @@ -0,0 +1,11 @@ +package com.tfq.finances.core.enums; + +/** + * 枚举类TokenRefreshLock用作单例模式的实现,用于令牌刷新的同步控制 + * 选择使用枚举类型来实现单例模式,因为它提供了内在的线程安全性和序列化机制,可以防止多次实例化 + * 这种模式特别适合那些需要管理单一实例并且对性能要求不高的场景 + */ +public enum TokenRefreshLock { + // 单例实例,名称为INSTANCE,用于全局访问该单例对象 + INSTANCE +} diff --git a/app/src/main/java/com/tfq/finances/core/utils/AESUtils.java b/app/src/main/java/com/tfq/finances/core/utils/AESUtils.java new file mode 100644 index 0000000..cfe71fc --- /dev/null +++ b/app/src/main/java/com/tfq/finances/core/utils/AESUtils.java @@ -0,0 +1,88 @@ +package com.tfq.finances.core.utils; + +import android.os.Build; + +import java.nio.charset.StandardCharsets; +import java.util.Base64; + +import javax.crypto.Cipher; +import javax.crypto.spec.SecretKeySpec; + +/** + * AES工具类,提供AES加密和解密的功能。 + * AES是一种对称加密算法,用于保障数据的安全性。 + */ +public class AESUtils { + // AES加密算法名称 + private static final String ALGORITHM = "AES"; + // AES密钥长度,必须是128、192或256位 + private static final int KEY_SIZE = 128; + /** + * 密钥,写死。 + */ + private static final String KEY = "e840b13a75debadc40da731a3e6d2410"; + + /** + * 对给定的字符串进行AES加密。 + * + * @param data 需要加密的字符串 + * @return 加密后的字符串,使用Base64编码表示 + * @throws Exception 如果加密过程中发生错误 + */ + public static String encrypt(String data) throws Exception { + // 根据密钥生成SecretKeySpec对象,用于AES加密 + SecretKeySpec secretKeySpec = new SecretKeySpec(KEY.getBytes(StandardCharsets.UTF_8), ALGORITHM); + // 获取AES加密实例 + Cipher cipher = Cipher.getInstance(ALGORITHM); + // 初始化加密模式 + cipher.init(Cipher.ENCRYPT_MODE, secretKeySpec); + // 执行加密操作 + byte[] encryptedBytes = cipher.doFinal(data.getBytes()); + // 使用Base64编码加密后的字节数据,得到加密后的字符串 +// if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { +// return Base64.getEncoder().encodeToString(encryptedBytes); +// } + + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + // 使用 java.util.Base64 + return Base64.getEncoder().encodeToString(encryptedBytes); + } else { + // 使用 android.util.Base64 + return android.util.Base64.encodeToString(encryptedBytes, android.util.Base64.DEFAULT); + } + } + + /** + * 对给定的加密字符串进行AES解密。 + * + * @param encryptedData 加密后的字符串,使用Base64编码表示 + * @return 解密后的字符串 + * @throws Exception 如果解密过程中发生错误 + */ + public static String decrypt(String encryptedData) throws Exception { + // 根据密钥生成SecretKeySpec对象,用于AES解密 + SecretKeySpec secretKeySpec = new SecretKeySpec(KEY.getBytes(StandardCharsets.UTF_8), ALGORITHM); + // 获取AES加密实例 + Cipher cipher = Cipher.getInstance(ALGORITHM); + // 初始化解密模式 + cipher.init(Cipher.DECRYPT_MODE, secretKeySpec); + // 使用Base64解码加密字符串,得到加密后的字节数据 + byte[] originalBytes = new byte[0]; + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + originalBytes = cipher.doFinal(Base64.getDecoder().decode(encryptedData)); + } else { + originalBytes = cipher.doFinal(android.util.Base64.decode(encryptedData, android.util.Base64.DEFAULT)); + } + // 将解密后的字节数据转换为字符串 + return new String(originalBytes); + } + + + public static void main(String[] args) throws Exception { + String encrypted = "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"; + + String decrypted = decrypt(encrypted); + + System.out.println("Decrypted: " + decrypted); + } +} diff --git a/app/src/main/java/com/tfq/finances/core/utils/PolicyUtils.java b/app/src/main/java/com/tfq/finances/core/utils/PolicyUtils.java new file mode 100644 index 0000000..244c7c1 --- /dev/null +++ b/app/src/main/java/com/tfq/finances/core/utils/PolicyUtils.java @@ -0,0 +1,25 @@ +package com.tfq.finances.core.utils; + +import com.tfq.finances.network.config.TfqConfig; +import com.tfq.library.utils.AppUtil; +import com.tfq.finances.core.constants.Constants; +import com.tfq.finances.app.App; + +/** + * 隐私政策 + */ +public class PolicyUtils { + /** + * 隐私政策链接 + * + * @return + */ + public static String getPrivacyPolicyUrl() { + return String.format("%s%s%s/%s/%s.html", + TfqConfig.BASE_DOMAIN, + "tfqys/", + Constants.APP_ID, + Constants.CHANNEL, + AppUtil.getAppVersionCode(App.getInstances())); + } +} diff --git a/app/src/main/java/com/tfq/finances/core/utils/TfqMyOkHttp.java b/app/src/main/java/com/tfq/finances/core/utils/TfqMyOkHttp.java new file mode 100644 index 0000000..6375123 --- /dev/null +++ b/app/src/main/java/com/tfq/finances/core/utils/TfqMyOkHttp.java @@ -0,0 +1,40 @@ +package com.tfq.finances.core.utils; + +import android.widget.Toast; + +import com.tfq.ad.app.HttpLog; +import com.tfq.library.utils.AppUtil; +import com.tfq.finances.core.constants.Constants; +import com.tfq.finances.app.App; + +import java.util.concurrent.TimeUnit; + +import okhttp3.Interceptor; +import okhttp3.OkHttpClient; +import okhttp3.logging.HttpLoggingInterceptor; + +public class TfqMyOkHttp { + + private static OkHttpClient client; + + public static OkHttpClient myClient() { + Interceptor logInterceptor; + if (Constants.APP_DEBUG_PRINT) { + logInterceptor = new HttpLoggingInterceptor(new HttpLog()).setLevel(HttpLoggingInterceptor.Level.BODY); + } else { + logInterceptor = new HttpLoggingInterceptor(new HttpLog()).setLevel(HttpLoggingInterceptor.Level.NONE); + } + + if (client == null) { + client = new OkHttpClient() + .newBuilder() + .addNetworkInterceptor(logInterceptor) + .connectTimeout(5, TimeUnit.SECONDS) + .build(); + } + if (!AppUtil.connectStatus(App.getContext())) { + Toast.makeText(App.getContext(), "无网络连接", Toast.LENGTH_SHORT).show(); + } + return client; + } +} diff --git a/app/src/main/java/com/tfq/finances/db/DaoMaster.java b/app/src/main/java/com/tfq/finances/db/DaoMaster.java new file mode 100644 index 0000000..be21149 --- /dev/null +++ b/app/src/main/java/com/tfq/finances/db/DaoMaster.java @@ -0,0 +1,96 @@ +package com.tfq.finances.db; + +import android.content.Context; +import android.database.sqlite.SQLiteDatabase; +import android.database.sqlite.SQLiteDatabase.CursorFactory; +import android.util.Log; + +import org.greenrobot.greendao.AbstractDaoMaster; +import org.greenrobot.greendao.database.StandardDatabase; +import org.greenrobot.greendao.database.Database; +import org.greenrobot.greendao.database.DatabaseOpenHelper; +import org.greenrobot.greendao.identityscope.IdentityScopeType; + + +// THIS CODE IS GENERATED BY greenDAO, DO NOT EDIT. +/** + * Master of DAO (schema version 1): knows all DAOs. + */ +public class DaoMaster extends AbstractDaoMaster { + public static final int SCHEMA_VERSION = 1; + + /** Creates underlying database table using DAOs. */ + public static void createAllTables(Database db, boolean ifNotExists) { + FavoriteVideoDao.createTable(db, ifNotExists); + } + + /** Drops underlying database table using DAOs. */ + public static void dropAllTables(Database db, boolean ifExists) { + FavoriteVideoDao.dropTable(db, ifExists); + } + + /** + * WARNING: Drops all table on Upgrade! Use only during development. + * Convenience method using a {@link DevOpenHelper}. + */ + public static DaoSession newDevSession(Context context, String name) { + Database db = new DevOpenHelper(context, name).getWritableDb(); + DaoMaster daoMaster = new DaoMaster(db); + return daoMaster.newSession(); + } + + public DaoMaster(SQLiteDatabase db) { + this(new StandardDatabase(db)); + } + + public DaoMaster(Database db) { + super(db, SCHEMA_VERSION); + registerDaoClass(FavoriteVideoDao.class); + } + + public DaoSession newSession() { + return new DaoSession(db, IdentityScopeType.Session, daoConfigMap); + } + + public DaoSession newSession(IdentityScopeType type) { + return new DaoSession(db, type, daoConfigMap); + } + + /** + * Calls {@link #createAllTables(Database, boolean)} in {@link #onCreate(Database)} - + */ + public static abstract class OpenHelper extends DatabaseOpenHelper { + public OpenHelper(Context context, String name) { + super(context, name, SCHEMA_VERSION); + } + + public OpenHelper(Context context, String name, CursorFactory factory) { + super(context, name, factory, SCHEMA_VERSION); + } + + @Override + public void onCreate(Database db) { + Log.i("greenDAO", "Creating tables for schema version " + SCHEMA_VERSION); + createAllTables(db, false); + } + } + + /** WARNING: Drops all table on Upgrade! Use only during development. */ + public static class DevOpenHelper extends OpenHelper { + public DevOpenHelper(Context context, String name) { + super(context, name); + } + + public DevOpenHelper(Context context, String name, CursorFactory factory) { + super(context, name, factory); + } + + @Override + public void onUpgrade(Database db, int oldVersion, int newVersion) { + Log.i("greenDAO", "Upgrading schema from version " + oldVersion + " to " + newVersion + " by dropping all tables"); + dropAllTables(db, true); + onCreate(db); + } + } + +} diff --git a/app/src/main/java/com/tfq/finances/db/DaoSession.java b/app/src/main/java/com/tfq/finances/db/DaoSession.java new file mode 100644 index 0000000..565d98a --- /dev/null +++ b/app/src/main/java/com/tfq/finances/db/DaoSession.java @@ -0,0 +1,48 @@ +package com.tfq.finances.db; + +import java.util.Map; + +import org.greenrobot.greendao.AbstractDao; +import org.greenrobot.greendao.AbstractDaoSession; +import org.greenrobot.greendao.database.Database; +import org.greenrobot.greendao.identityscope.IdentityScopeType; +import org.greenrobot.greendao.internal.DaoConfig; + +import com.tfq.finances.dbbean.FavoriteVideo; + +import com.tfq.finances.db.FavoriteVideoDao; + +// THIS CODE IS GENERATED BY greenDAO, DO NOT EDIT. + +/** + * {@inheritDoc} + * + * @see org.greenrobot.greendao.AbstractDaoSession + */ +public class DaoSession extends AbstractDaoSession { + + private final DaoConfig favoriteVideoDaoConfig; + + private final FavoriteVideoDao favoriteVideoDao; + + public DaoSession(Database db, IdentityScopeType type, Map>, DaoConfig> + daoConfigMap) { + super(db); + + favoriteVideoDaoConfig = daoConfigMap.get(FavoriteVideoDao.class).clone(); + favoriteVideoDaoConfig.initIdentityScope(type); + + favoriteVideoDao = new FavoriteVideoDao(favoriteVideoDaoConfig, this); + + registerDao(FavoriteVideo.class, favoriteVideoDao); + } + + public void clear() { + favoriteVideoDaoConfig.clearIdentityScope(); + } + + public FavoriteVideoDao getFavoriteVideoDao() { + return favoriteVideoDao; + } + +} diff --git a/app/src/main/java/com/tfq/finances/db/FavoriteVideoDao.java b/app/src/main/java/com/tfq/finances/db/FavoriteVideoDao.java new file mode 100644 index 0000000..9e06276 --- /dev/null +++ b/app/src/main/java/com/tfq/finances/db/FavoriteVideoDao.java @@ -0,0 +1,216 @@ +package com.tfq.finances.db; + +import android.database.Cursor; +import android.database.sqlite.SQLiteStatement; + +import org.greenrobot.greendao.AbstractDao; +import org.greenrobot.greendao.Property; +import org.greenrobot.greendao.internal.DaoConfig; +import org.greenrobot.greendao.database.Database; +import org.greenrobot.greendao.database.DatabaseStatement; + +import com.tfq.finances.dbbean.FavoriteVideo; + +// THIS CODE IS GENERATED BY greenDAO, DO NOT EDIT. +/** + * DAO for table "FAVORITE_VIDEO". +*/ +public class FavoriteVideoDao extends AbstractDao { + + public static final String TABLENAME = "FAVORITE_VIDEO"; + + /** + * Properties of entity FavoriteVideo.
+ * Can be used for QueryBuilder and for referencing column names. + */ + public static class Properties { + public final static Property Id = new Property(0, Long.class, "id", true, "_id"); + public final static Property Favorite_id = new Property(1, Integer.class, "favorite_id", false, "FAVORITE_ID"); + public final static Property Type = new Property(2, String.class, "type", false, "TYPE"); + public final static Property Path = new Property(3, String.class, "path", false, "PATH"); + public final static Property MimeType = new Property(4, String.class, "mimeType", false, "MIME_TYPE"); + public final static Property Date = new Property(5, String.class, "date", false, "DATE"); + public final static Property DisplayName = new Property(6, String.class, "displayName", false, "DISPLAY_NAME"); + public final static Property Duration = new Property(7, String.class, "duration", false, "DURATION"); + } + + + public FavoriteVideoDao(DaoConfig config) { + super(config); + } + + public FavoriteVideoDao(DaoConfig config, DaoSession daoSession) { + super(config, daoSession); + } + + /** Creates the underlying database table. */ + public static void createTable(Database db, boolean ifNotExists) { + String constraint = ifNotExists? "IF NOT EXISTS ": ""; + db.execSQL("CREATE TABLE " + constraint + "\"FAVORITE_VIDEO\" (" + // + "\"_id\" INTEGER PRIMARY KEY AUTOINCREMENT ," + // 0: id + "\"FAVORITE_ID\" INTEGER," + // 1: favorite_id + "\"TYPE\" TEXT," + // 2: type + "\"PATH\" TEXT," + // 3: path + "\"MIME_TYPE\" TEXT," + // 4: mimeType + "\"DATE\" TEXT," + // 5: date + "\"DISPLAY_NAME\" TEXT," + // 6: displayName + "\"DURATION\" TEXT);"); // 7: duration + // Add Indexes + db.execSQL("CREATE UNIQUE INDEX " + constraint + "IDX_FAVORITE_VIDEO_FAVORITE_ID ON \"FAVORITE_VIDEO\"" + + " (\"FAVORITE_ID\" ASC);"); + } + + /** Drops the underlying database table. */ + public static void dropTable(Database db, boolean ifExists) { + String sql = "DROP TABLE " + (ifExists ? "IF EXISTS " : "") + "\"FAVORITE_VIDEO\""; + db.execSQL(sql); + } + + @Override + protected final void bindValues(DatabaseStatement stmt, FavoriteVideo entity) { + stmt.clearBindings(); + + Long id = entity.getId(); + if (id != null) { + stmt.bindLong(1, id); + } + + Integer favorite_id = entity.getFavorite_id(); + if (favorite_id != null) { + stmt.bindLong(2, favorite_id); + } + + String type = entity.getType(); + if (type != null) { + stmt.bindString(3, type); + } + + String path = entity.getPath(); + if (path != null) { + stmt.bindString(4, path); + } + + String mimeType = entity.getMimeType(); + if (mimeType != null) { + stmt.bindString(5, mimeType); + } + + String date = entity.getDate(); + if (date != null) { + stmt.bindString(6, date); + } + + String displayName = entity.getDisplayName(); + if (displayName != null) { + stmt.bindString(7, displayName); + } + + String duration = entity.getDuration(); + if (duration != null) { + stmt.bindString(8, duration); + } + } + + @Override + protected final void bindValues(SQLiteStatement stmt, FavoriteVideo entity) { + stmt.clearBindings(); + + Long id = entity.getId(); + if (id != null) { + stmt.bindLong(1, id); + } + + Integer favorite_id = entity.getFavorite_id(); + if (favorite_id != null) { + stmt.bindLong(2, favorite_id); + } + + String type = entity.getType(); + if (type != null) { + stmt.bindString(3, type); + } + + String path = entity.getPath(); + if (path != null) { + stmt.bindString(4, path); + } + + String mimeType = entity.getMimeType(); + if (mimeType != null) { + stmt.bindString(5, mimeType); + } + + String date = entity.getDate(); + if (date != null) { + stmt.bindString(6, date); + } + + String displayName = entity.getDisplayName(); + if (displayName != null) { + stmt.bindString(7, displayName); + } + + String duration = entity.getDuration(); + if (duration != null) { + stmt.bindString(8, duration); + } + } + + @Override + public Long readKey(Cursor cursor, int offset) { + return cursor.isNull(offset + 0) ? null : cursor.getLong(offset + 0); + } + + @Override + public FavoriteVideo readEntity(Cursor cursor, int offset) { + FavoriteVideo entity = new FavoriteVideo( // + cursor.isNull(offset + 0) ? null : cursor.getLong(offset + 0), // id + cursor.isNull(offset + 1) ? null : cursor.getInt(offset + 1), // favorite_id + cursor.isNull(offset + 2) ? null : cursor.getString(offset + 2), // type + cursor.isNull(offset + 3) ? null : cursor.getString(offset + 3), // path + cursor.isNull(offset + 4) ? null : cursor.getString(offset + 4), // mimeType + cursor.isNull(offset + 5) ? null : cursor.getString(offset + 5), // date + cursor.isNull(offset + 6) ? null : cursor.getString(offset + 6), // displayName + cursor.isNull(offset + 7) ? null : cursor.getString(offset + 7) // duration + ); + return entity; + } + + @Override + public void readEntity(Cursor cursor, FavoriteVideo entity, int offset) { + entity.setId(cursor.isNull(offset + 0) ? null : cursor.getLong(offset + 0)); + entity.setFavorite_id(cursor.isNull(offset + 1) ? null : cursor.getInt(offset + 1)); + entity.setType(cursor.isNull(offset + 2) ? null : cursor.getString(offset + 2)); + entity.setPath(cursor.isNull(offset + 3) ? null : cursor.getString(offset + 3)); + entity.setMimeType(cursor.isNull(offset + 4) ? null : cursor.getString(offset + 4)); + entity.setDate(cursor.isNull(offset + 5) ? null : cursor.getString(offset + 5)); + entity.setDisplayName(cursor.isNull(offset + 6) ? null : cursor.getString(offset + 6)); + entity.setDuration(cursor.isNull(offset + 7) ? null : cursor.getString(offset + 7)); + } + + @Override + protected final Long updateKeyAfterInsert(FavoriteVideo entity, long rowId) { + entity.setId(rowId); + return rowId; + } + + @Override + public Long getKey(FavoriteVideo entity) { + if(entity != null) { + return entity.getId(); + } else { + return null; + } + } + + @Override + public boolean hasKey(FavoriteVideo entity) { + return entity.getId() != null; + } + + @Override + protected final boolean isEntityUpdateable() { + return true; + } + +} diff --git a/app/src/main/java/com/tfq/finances/db/db/GDOpenHelper.java b/app/src/main/java/com/tfq/finances/db/db/GDOpenHelper.java new file mode 100644 index 0000000..048b933 --- /dev/null +++ b/app/src/main/java/com/tfq/finances/db/db/GDOpenHelper.java @@ -0,0 +1 @@ +package com.tfq.finances.db.db; import android.content.Context; import com.tfq.finances.db.DaoMaster; import com.tfq.finances.db.FavoriteVideoDao; import org.greenrobot.greendao.database.Database; public class GDOpenHelper extends DaoMaster.OpenHelper { public GDOpenHelper(Context context, String name) { super(context, name); } @Override public void onUpgrade(Database db, int oldVersion, int newVersion) { super.onUpgrade(db, oldVersion, newVersion); MigrationHelper.migrate(db, new MigrationHelper.ReCreateAllTableListener() { @Override public void onCreateAllTables(Database db, boolean ifNotExists) { DaoMaster.createAllTables(db, ifNotExists); } @Override public void onDropAllTables(Database db, boolean ifExists) { DaoMaster.dropAllTables(db, ifExists); } } , FavoriteVideoDao.class ); } } \ No newline at end of file diff --git a/app/src/main/java/com/tfq/finances/db/db/MigrationHelper.java b/app/src/main/java/com/tfq/finances/db/db/MigrationHelper.java new file mode 100644 index 0000000..cfb2f84 --- /dev/null +++ b/app/src/main/java/com/tfq/finances/db/db/MigrationHelper.java @@ -0,0 +1,312 @@ +package com.tfq.finances.db.db; + +import android.database.Cursor; +import android.database.SQLException; +import android.database.sqlite.SQLiteDatabase; +import android.text.TextUtils; +import android.util.Log; + +import org.greenrobot.greendao.AbstractDao; +import org.greenrobot.greendao.database.Database; +import org.greenrobot.greendao.database.StandardDatabase; +import org.greenrobot.greendao.internal.DaoConfig; + +import java.lang.ref.WeakReference; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +import androidx.annotation.NonNull; + +public class MigrationHelper { + + public static boolean DEBUG = true; + private static String TAG = "MigrationHelper"; + private static final String SQLITE_MASTER = "sqlite_master"; + private static final String SQLITE_TEMP_MASTER = "sqlite_temp_master"; + + private static WeakReference weakListener; + + public interface ReCreateAllTableListener{ + void onCreateAllTables(Database db, boolean ifNotExists); + void onDropAllTables(Database db, boolean ifExists); + } + + public static void migrate(SQLiteDatabase db, Class>... daoClasses) { + printLog("【The Old Database Version】" + db.getVersion()); + Database database = new StandardDatabase(db); + migrate(database, daoClasses); + } + + public static void migrate(SQLiteDatabase db, ReCreateAllTableListener listener, Class>... daoClasses) { + weakListener = new WeakReference<>(listener); + migrate(db, daoClasses); + } + + public static void migrate(Database database, ReCreateAllTableListener listener, Class>... daoClasses) { + weakListener = new WeakReference<>(listener); + migrate(database, daoClasses); + } + + public static void migrate(Database database, Class>... daoClasses) { + printLog("【Generate temp table】start"); + generateTempTables(database, daoClasses); + printLog("【Generate temp table】complete"); + + ReCreateAllTableListener listener = null; + if (weakListener != null) { + listener = weakListener.get(); + } + + if (listener != null) { + listener.onDropAllTables(database, true); + printLog("【Drop all table by listener】"); + listener.onCreateAllTables(database, false); + printLog("【Create all table by listener】"); + } else { + dropAllTables(database, true, daoClasses); + createAllTables(database, false, daoClasses); + } + printLog("【Restore data】start"); + restoreData(database, daoClasses); + printLog("【Restore data】complete"); + } + + private static void generateTempTables(Database db, Class>... daoClasses) { + for (int i = 0; i < daoClasses.length; i++) { + String tempTableName = null; + + DaoConfig daoConfig = new DaoConfig(db, daoClasses[i]); + String tableName = daoConfig.tablename; + if (!isTableExists(db, false, tableName)) { + printLog("【New Table】" + tableName); + continue; + } + try { + tempTableName = daoConfig.tablename.concat("_TEMP"); + StringBuilder dropTableStringBuilder = new StringBuilder(); + dropTableStringBuilder.append("DROP TABLE IF EXISTS ").append(tempTableName).append(";"); + db.execSQL(dropTableStringBuilder.toString()); + + StringBuilder insertTableStringBuilder = new StringBuilder(); + insertTableStringBuilder.append("CREATE TEMPORARY TABLE ").append(tempTableName); + insertTableStringBuilder.append(" AS SELECT * FROM `").append(tableName).append("`;"); + db.execSQL(insertTableStringBuilder.toString()); + printLog("【Table】" + tableName +"\n ---Columns-->"+getColumnsStr(daoConfig)); + printLog("【Generate temp table】" + tempTableName); + } catch (SQLException e) { + Log.e(TAG, "【Failed to generate temp table】" + tempTableName, e); + } + } + } + + private static boolean isTableExists(Database db, boolean isTemp, String tableName) { + if (db == null || TextUtils.isEmpty(tableName)) { + return false; + } + String dbName = isTemp ? SQLITE_TEMP_MASTER : SQLITE_MASTER; + String sql = "SELECT COUNT(*) FROM `" + dbName + "` WHERE type = ? AND name = ?"; + Cursor cursor=null; + int count = 0; + try { + cursor = db.rawQuery(sql, new String[]{"table", tableName}); + if (cursor == null || !cursor.moveToFirst()) { + return false; + } + count = cursor.getInt(0); + } catch (Exception e) { + e.printStackTrace(); + } finally { + if (cursor != null) + cursor.close(); + } + return count > 0; + } + + + private static String getColumnsStr(DaoConfig daoConfig) { + if (daoConfig == null) { + return "no columns"; + } + StringBuilder builder = new StringBuilder(); + for (int i = 0; i < daoConfig.allColumns.length; i++) { + builder.append(daoConfig.allColumns[i]); + builder.append(","); + } + if (builder.length() > 0) { + builder.deleteCharAt(builder.length() - 1); + } + return builder.toString(); + } + + + private static void dropAllTables(Database db, boolean ifExists, @NonNull Class>... daoClasses) { + reflectMethod(db, "dropTable", ifExists, daoClasses); + printLog("【Drop all table by reflect】"); + } + + private static void createAllTables(Database db, boolean ifNotExists, @NonNull Class>... daoClasses) { + reflectMethod(db, "createTable", ifNotExists, daoClasses); + printLog("【Create all table by reflect】"); + } + + /** + * dao class already define the sql exec method, so just invoke it + */ + private static void reflectMethod(Database db, String methodName, boolean isExists, @NonNull Class>... daoClasses) { + if (daoClasses.length < 1) { + return; + } + try { + for (Class cls : daoClasses) { + Method method = cls.getDeclaredMethod(methodName, Database.class, boolean.class); + method.invoke(null, db, isExists); + } + } catch (NoSuchMethodException e) { + e.printStackTrace(); + } catch (InvocationTargetException e) { + e.printStackTrace(); + } catch (IllegalAccessException e) { + e.printStackTrace(); + } + } + + private static void restoreData(Database db, Class>... daoClasses) { + for (int i = 0; i < daoClasses.length; i++) { + DaoConfig daoConfig = new DaoConfig(db, daoClasses[i]); + String tableName = daoConfig.tablename; + String tempTableName = daoConfig.tablename.concat("_TEMP"); + + if (!isTableExists(db, true, tempTableName)) { + continue; + } + + try { + // get all columns from tempTable, take careful to use the columns list + List newTableInfos = TableInfo.getTableInfo(db, tableName); + List tempTableInfos = TableInfo.getTableInfo(db, tempTableName); + ArrayList selectColumns = new ArrayList<>(newTableInfos.size()); + ArrayList intoColumns = new ArrayList<>(newTableInfos.size()); + for (TableInfo tableInfo : tempTableInfos) { + if (newTableInfos.contains(tableInfo)) { + String column = '`' + tableInfo.name + '`'; + intoColumns.add(column); + selectColumns.add(column); + } + } + // NOT NULL columns list + for (TableInfo tableInfo : newTableInfos) { + if (tableInfo.notnull && !tempTableInfos.contains(tableInfo)) { + String column = '`' + tableInfo.name + '`'; + intoColumns.add(column); + + String value; + if (tableInfo.dfltValue != null) { + value = "'" + tableInfo.dfltValue + "' AS "; + } else { + value = "'' AS "; + } + selectColumns.add(value + column); + } + } + + if (intoColumns.size() != 0) { + StringBuilder insertTableStringBuilder = new StringBuilder(); + insertTableStringBuilder.append("REPLACE INTO `").append(tableName).append("` ("); + insertTableStringBuilder.append(TextUtils.join(",", intoColumns)); + insertTableStringBuilder.append(") SELECT "); + insertTableStringBuilder.append(TextUtils.join(",", selectColumns)); + insertTableStringBuilder.append(" FROM ").append(tempTableName).append(";"); + db.execSQL(insertTableStringBuilder.toString()); + printLog("【Restore data】 to " + tableName); + } + StringBuilder dropTableStringBuilder = new StringBuilder(); + dropTableStringBuilder.append("DROP TABLE ").append(tempTableName); + db.execSQL(dropTableStringBuilder.toString()); + printLog("【Drop temp table】" + tempTableName); + } catch (SQLException e) { + Log.e(TAG, "【Failed to restore data from temp table 】" + tempTableName, e); + } + } + } + + private static List getColumns(Database db, String tableName) { + List columns = null; + Cursor cursor = null; + try { + cursor = db.rawQuery("SELECT * FROM " + tableName + " limit 0", null); + if (null != cursor && cursor.getColumnCount() > 0) { + columns = Arrays.asList(cursor.getColumnNames()); + } + } catch (Exception e) { + e.printStackTrace(); + } finally { + if (cursor != null) + cursor.close(); + if (null == columns) + columns = new ArrayList<>(); + } + return columns; + } + + private static void printLog(String info){ + if(DEBUG){ + Log.d(TAG, info); + } + } + + private static class TableInfo { + int cid; + String name; + String type; + boolean notnull; + String dfltValue; + boolean pk; + + @Override + public boolean equals(Object o) { + return this == o + || o != null + && getClass() == o.getClass() + && name.equals(((TableInfo) o).name); + } + + @Override + public String toString() { + return "TableInfo{" + + "cid=" + cid + + ", name='" + name + '\'' + + ", type='" + type + '\'' + + ", notnull=" + notnull + + ", dfltValue='" + dfltValue + '\'' + + ", pk=" + pk + + '}'; + } + + private static List getTableInfo(Database db, String tableName) { + String sql = "PRAGMA table_info(`" + tableName + "`)"; + printLog(sql); + Cursor cursor = db.rawQuery(sql, null); + if (cursor == null) + return new ArrayList<>(); + + TableInfo tableInfo; + List tableInfos = new ArrayList<>(); + while (cursor.moveToNext()) { + tableInfo = new TableInfo(); + tableInfo.cid = cursor.getInt(0); + tableInfo.name = cursor.getString(1); + tableInfo.type = cursor.getString(2); + tableInfo.notnull = cursor.getInt(3) == 1; + tableInfo.dfltValue = cursor.getString(4); + tableInfo.pk = cursor.getInt(5) == 1; + tableInfos.add(tableInfo); + // printLog(tableName + ":" + tableInfo); + } + cursor.close(); + return tableInfos; + } + } +} diff --git a/app/src/main/java/com/tfq/finances/dbbean/FavoriteVideo.java b/app/src/main/java/com/tfq/finances/dbbean/FavoriteVideo.java new file mode 100644 index 0000000..37c4787 --- /dev/null +++ b/app/src/main/java/com/tfq/finances/dbbean/FavoriteVideo.java @@ -0,0 +1,85 @@ +package com.tfq.finances.dbbean; + +import org.greenrobot.greendao.annotation.Entity; +import org.greenrobot.greendao.annotation.Generated; +import org.greenrobot.greendao.annotation.Id; +import org.greenrobot.greendao.annotation.Index; + +@Entity +public class FavoriteVideo { + @Id(autoincrement = true) + private Long id; + @Index(unique = true) + private Integer favorite_id; + + private String type; + private String path; + private String mimeType; + private String date; + private String displayName; + private String duration; + @Generated(hash = 857494104) + public FavoriteVideo(Long id, Integer favorite_id, String type, String path, + String mimeType, String date, String displayName, String duration) { + this.id = id; + this.favorite_id = favorite_id; + this.type = type; + this.path = path; + this.mimeType = mimeType; + this.date = date; + this.displayName = displayName; + this.duration = duration; + } + @Generated(hash = 59092039) + public FavoriteVideo() { + } + public Long getId() { + return this.id; + } + public void setId(Long id) { + this.id = id; + } + public Integer getFavorite_id() { + return this.favorite_id; + } + public void setFavorite_id(Integer favorite_id) { + this.favorite_id = favorite_id; + } + public String getType() { + return this.type; + } + public void setType(String type) { + this.type = type; + } + public String getPath() { + return this.path; + } + public void setPath(String path) { + this.path = path; + } + public String getMimeType() { + return this.mimeType; + } + public void setMimeType(String mimeType) { + this.mimeType = mimeType; + } + public String getDate() { + return this.date; + } + public void setDate(String date) { + this.date = date; + } + public String getDisplayName() { + return this.displayName; + } + public void setDisplayName(String displayName) { + this.displayName = displayName; + } + public String getDuration() { + return this.duration; + } + public void setDuration(String duration) { + this.duration = duration; + } + +} diff --git a/app/src/main/java/com/tfq/finances/finances/activity/Activity_Budgets.java b/app/src/main/java/com/tfq/finances/finances/activity/Activity_Budgets.java new file mode 100644 index 0000000..12fcc40 --- /dev/null +++ b/app/src/main/java/com/tfq/finances/finances/activity/Activity_Budgets.java @@ -0,0 +1,163 @@ +package com.tfq.finances.finances.activity; + +import android.annotation.SuppressLint; +import android.os.Bundle; +import android.view.View; +import android.widget.FrameLayout; +import android.widget.ImageView; +import android.widget.TextView; + +import com.tfq.ad.ad.AdFeedUtils; +import com.tfq.finances.finances.view.CircleProgressBarWithAnimation; +import com.tfq.finances.jzrcj.R; +import com.tfq.finances.network.api.finances.BudgetsService; +import com.tfq.finances.network.config.ApiCallback; +import com.tfq.finances.network.model.response.finances.budgets.BudgetsMonthResp; +import com.tfq.finances.network.model.response.finances.budgets.BudgetsResp; +import com.tfq.library.app.BaseConstants; +import com.tfq.library.base.BaseActivity; +import com.tfq.library.utils.AddTextDialog; +import com.tfq.library.utils.AppUtil; +import com.tfq.library.utils.LogK; +import com.tfq.library.utils.ToasterUtil; + +import java.math.BigDecimal; +import java.math.RoundingMode; +import java.text.SimpleDateFormat; +import java.util.Date; +import java.util.Locale; + +public class Activity_Budgets extends BaseActivity { + + private CircleProgressBarWithAnimation progressBar; + private TextView tv_month; + private TextView tv_residue; + private TextView tv_current; + private TextView tv_expenditure; + private FrameLayout fl_content; + + @Override + protected int getLayoutId() { + return R.layout.activity_budgets_layout; + } + + @Override + protected void initView() { + ImageView iv_back = findViewById(R.id.iv_back); + iv_back.setOnClickListener(this::onClick); + TextView tv_edit = findViewById(R.id.tv_edit); + tv_edit.setOnClickListener(this::onClick); + tv_residue = findViewById(R.id.tv_residue); + tv_current = findViewById(R.id.tv_current); + tv_expenditure = findViewById(R.id.tv_expenditure); + tv_month = findViewById(R.id.tv_month); + + progressBar = findViewById(R.id.progressBar); + fl_content = findViewById(R.id.fl_content); + + } + + @SuppressLint("SetTextI18n") + @Override + protected void initData(Bundle savedInstanceState) { + setStatusBarDarkFont(true); + + getMonthBudgetsInfo(); + } + + @Override + protected void onResume() { + super.onResume(); + initAd(); + } + + private void initAd() { + AdFeedUtils.show_ad(this, BaseConstants.CODE_AD_FEED1, fl_content, 20, "budgets", 3); + } + + @Override + protected void onPause() { + super.onPause(); + + AdFeedUtils.cancelTag("budgets"); + } + + @SuppressLint("SetTextI18n") + private void getMonthBudgetsInfo( ) { + // 方式1:使用SimpleDateFormat(兼容所有API版本) + SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM", Locale.getDefault()); + // 初始化 + String currentDate = sdf.format(new Date()); + tv_month.setText(currentDate.split("-")[1] + "月总预算"); + new BudgetsService(this).getMonthBudgetsInfo(currentDate, new ApiCallback() { + @Override + public void onSuccess(BudgetsMonthResp response) { + if (response!=null) { + tv_residue.setText(String.valueOf(response.getRemainingAmount())); + tv_current.setText(String.valueOf(response.getAmount())); + tv_expenditure.setText(String.valueOf(response.getTotalExpenses())); + + // 随机生成0-100的进度值 + float randomProgress = 0; + if (response.getTotalExpenses().compareTo(response.getAmount()) > 0) {//支出大于预算 + randomProgress = 100; + } else { + BigDecimal divide = response.getTotalExpenses().divide(response.getAmount(), 2, RoundingMode.HALF_UP); + randomProgress = divide.multiply(new BigDecimal(100)).floatValue(); + } + + // 随机生成0-100的进度值 +// float randomProgress = (float) (Math.random() * 100); + // 带动画设置新进度 + progressBar.setProgress(randomProgress, true); + }else { + // 带动画设置新进度 + progressBar.setProgress(0, true); + } + } + + @Override + public void onFailure(int errorCode, String errorMessage) { + + } + }); + } + + public void onClick(View view) { + int viewId = view.getId(); + if (viewId == R.id.iv_back) { + finish(); + } else if (viewId == R.id.tv_edit) { + addBudgetsDialog(); + } + } + + + private void addBudgetsDialog() { + new AddTextDialog(this, "", "设置预算", "budgets", new AddTextDialog.Listener() { + @Override + public void success(String name) { + LogK.e("name=" + name); + if (AppUtil.isNumeric(name)) { + // 方式1:使用SimpleDateFormat(兼容所有API版本) + SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM", Locale.getDefault()); + // 初始化 + String currentDate = sdf.format(new Date()); + new BudgetsService(Activity_Budgets.this).setBudgetsInfo(new BigDecimal(name),currentDate, new ApiCallback() { + @Override + public void onSuccess(BudgetsResp response) { + getMonthBudgetsInfo(); + } + + @Override + public void onFailure(int errorCode, String errorMessage) { + ToasterUtil.show(errorMessage); + } + }); + }else { + ToasterUtil.show("输入不准确"); + } + } + }).show(); + } +} diff --git a/app/src/main/java/com/tfq/finances/finances/activity/Activity_Detail_Add.java b/app/src/main/java/com/tfq/finances/finances/activity/Activity_Detail_Add.java new file mode 100644 index 0000000..8454dbf --- /dev/null +++ b/app/src/main/java/com/tfq/finances/finances/activity/Activity_Detail_Add.java @@ -0,0 +1,129 @@ +package com.tfq.finances.finances.activity; + +import static android.view.View.GONE; +import static android.view.View.VISIBLE; + +import android.app.Activity; +import android.content.Intent; +import android.os.Bundle; +import android.view.View; +import android.widget.ImageView; +import android.widget.TextView; + +import androidx.fragment.app.Fragment; +import androidx.viewpager2.widget.ViewPager2; + +import com.tfq.finances.jzrcj.R; +import com.tfq.finances.finances.fragment.Fm_Page_Expenditure; +import com.tfq.finances.finances.fragment.Fm_Page_Income; +import com.tfq.library.adapter.MyFragmentStateAdapter; +import com.tfq.library.base.BaseActivity; +import com.tfq.library.utils.AppUtil; + +import java.util.ArrayList; +import java.util.List; + +public class Activity_Detail_Add extends BaseActivity { + + private ViewPager2 viewPager; + private boolean isReqSuccessRefreshRv; + private View view1, view2; + + @Override + protected int getLayoutId() { + return R.layout.activity_detail_add; + } + + @Override + protected void initView() { + ImageView iv_back = findViewById(R.id.iv_back); + iv_back.setOnClickListener(this::onClick); + TextView tv_left = findViewById(R.id.tv_left); + tv_left.setOnClickListener(this::onClick); + TextView tv_right = findViewById(R.id.tv_right); + tv_right.setOnClickListener(this::onClick); + viewPager = findViewById(R.id.viewPager); + view1 = findViewById(R.id.view1); + view2 = findViewById(R.id.view2); + + List fragmentList = new ArrayList<>(); + fragmentList.add(new Fm_Page_Expenditure()); + fragmentList.add(new Fm_Page_Income()); + + viewPager.setAdapter(new MyFragmentStateAdapter(getSupportFragmentManager(), getLifecycle(), fragmentList)); + viewPager.registerOnPageChangeCallback(PagerChangeListener()); + viewPager.setUserInputEnabled(false); + + } + + @Override + protected void initData(Bundle savedInstanceState) { + setStatusBarDarkFont(true); + } + + public void onClick(View view) { + int viewId = view.getId(); + if (viewId == R.id.iv_back) { + toFinish(); + } else if (viewId == R.id.tv_left) { + viewPager.setCurrentItem(0); + view1.setVisibility(VISIBLE); + view2.setVisibility(GONE); + } else if (viewId == R.id.tv_right) { + viewPager.setCurrentItem(1); + view2.setVisibility(VISIBLE); + view1.setVisibility(GONE); + } + } + + private void toFinish() { + if (isReqSuccessRefreshRv) { + isReqSuccessRefreshRv = false; + Intent intent = new Intent(); + setResult(Activity.RESULT_OK, intent); + finish(); + } else { + finish(); + } + } + + @Override + protected void onPause() { + super.onPause(); + +// AppUtil.closeKeyBoard(this); + } + + public void setReqSuccessRefreshRv(boolean isReqSuccessRefreshRv) { + this.isReqSuccessRefreshRv = isReqSuccessRefreshRv; + } + + @Override + public void onBackPressed() { + if (true) { + // 自定义拦截逻辑(如显示确认对话框) + toFinish(); + } else { + super.onBackPressed(); // 默认返回行为 + } + } + + + private ViewPager2.OnPageChangeCallback PagerChangeListener() { + return new ViewPager2.OnPageChangeCallback() { + @Override + public void onPageSelected(int position) { + super.onPageSelected(position); + switch (position) { + case 0: + + break; + case 1: + + break; + } + } + }; + } + +} diff --git a/app/src/main/java/com/tfq/finances/finances/activity/Activity_Login.java b/app/src/main/java/com/tfq/finances/finances/activity/Activity_Login.java new file mode 100644 index 0000000..ac7334a --- /dev/null +++ b/app/src/main/java/com/tfq/finances/finances/activity/Activity_Login.java @@ -0,0 +1,245 @@ +package com.tfq.finances.finances.activity; + +import android.annotation.SuppressLint; +import android.content.Intent; +import android.graphics.Color; +import android.os.Bundle; +import android.os.CountDownTimer; +import android.text.Spannable; +import android.text.SpannableStringBuilder; +import android.text.TextPaint; +import android.text.TextUtils; +import android.text.method.LinkMovementMethod; +import android.text.style.ClickableSpan; +import android.text.style.ForegroundColorSpan; +import android.view.View; +import android.widget.EditText; +import android.widget.ImageView; +import android.widget.TextView; + +import androidx.annotation.NonNull; + +import com.tfq.finances.core.constants.Constants; +import com.tfq.finances.jzrcj.R; +import com.tfq.finances.main.Activity_Main; +import com.tfq.finances.main.activity.Activity_Tw; +import com.tfq.finances.main.activity.Activity_WebView; +import com.tfq.finances.network.api.member.MemberService; +import com.tfq.finances.network.config.ApiCallback; +import com.tfq.finances.network.config.token.TokenManager; +import com.tfq.finances.network.model.response.member.MemberLoginResp; +import com.tfq.library.base.BaseActivity; +import com.tfq.library.utils.AppUtil; +import com.tfq.library.utils.ToasterUtil; + +public class Activity_Login extends BaseActivity { + + private TextView tv_welcome; + private boolean lookAndCheck; + private ImageView iv_check; + private EditText et_phone; + private EditText et_code; + private TextView tv_getcode; + private TextView tv_auth; + + @Override + protected int getLayoutId() { + return R.layout.activity_login; + } + + @Override + protected void initView() { + tv_welcome = findViewById(R.id.tv_welcome); + tv_getcode = findViewById(R.id.tv_getcode); + tv_getcode.setOnClickListener(this::onClick); + iv_check = findViewById(R.id.iv_check); + iv_check.setOnClickListener(this::onClick); + et_phone = findViewById(R.id.et_phone); + et_code = findViewById(R.id.et_code); + tv_auth = findViewById(R.id.tv_auth); + tv_auth.setOnClickListener(this::onClick); + ImageView iv_back = findViewById(R.id.iv_back); + iv_back.setOnClickListener(this::onClick); + ImageView iv_login = findViewById(R.id.iv_login); + iv_login.setOnClickListener(this::onClick); + + } + + @SuppressLint("SetTextI18n") + @Override + protected void initData(Bundle savedInstanceState) { + setStatusBarDarkFont(true); + tv_welcome.setText("欢迎登录" + AppUtil.getAppName(this)); + + showAuth(); + } + + private void onClick(View view) { + int viewId = view.getId(); + + if (viewId == R.id.iv_back) { + finish(); + } else if (viewId == R.id.tv_getcode) { + getCode(); + } else if (viewId == R.id.iv_check) { + if (lookAndCheck) { + iv_check.setImageResource(R.mipmap.ic_check); + } else { + iv_check.setImageResource(R.mipmap.ic_checked); + } + lookAndCheck = !lookAndCheck; + } else if (viewId == R.id.iv_login) { + loginPhone(); + } else if (viewId == R.id.tv_auth) { + // 预留授权逻辑 + } + } + + + private void loginPhone() { + if (!AppUtil.connectStatus(this)) { + ToasterUtil.show("请检查网络链接"); + return; + } + String phone = et_phone.getText().toString().trim(); + if (TextUtils.isEmpty(phone)) { + ToasterUtil.show("手机号码不能为空"); + return; + } + if (!AppUtil.regexPhoneNum(phone)) { + ToasterUtil.show("手机号码不正确"); + return; + } + String code = et_code.getText().toString().trim(); + if (TextUtils.isEmpty(code)) { + ToasterUtil.show("验证码不能为空"); + return; + } + if (!lookAndCheck) { + ToasterUtil.show("请先查看并同意用户协议以及隐私政策"); + return; + } + + new MemberService(this).smsLogin(phone, code, new ApiCallback() { + @Override + public void onSuccess(MemberLoginResp response) { + TokenManager instance = TokenManager.getInstance(Activity_Login.this); + instance.saveTokens(response.getAccessToken(), response.getRefreshToken(), response.getExpiresTime(), response.getUserId()); + Constants.USER_ID = instance.getUserId(); + startActivity(new Intent(Activity_Login.this, Activity_Main.class)); + finish(); + } + + @Override + public void onFailure(int errorCode, String errorMessage) { + ToasterUtil.show(errorMessage); + } + }); + } + + private void getCode() { + if (!AppUtil.connectStatus(this)) { + ToasterUtil.show("请检查网络链接"); + return; + } + String phone = et_phone.getText().toString().trim(); + if (TextUtils.isEmpty(phone)) { + ToasterUtil.show("手机号码不能为空"); + return; + } + if (!AppUtil.regexPhoneNum(phone)) { + ToasterUtil.show("验证码不能为空"); + return; + } + new MemberService(this).sendSmsCode(phone, new ApiCallback() { + @Override + public void onSuccess(Boolean response) { + try { + runOnUiThread(new Runnable() { + @Override + public void run() { + final MyCountDownTimer myCountDownTimer = new MyCountDownTimer(60000, 1000); + myCountDownTimer.start(); + } + }); + } catch (Exception e) { + e.printStackTrace(); + } + } + + @Override + public void onFailure(int errorCode, String errorMessage) { + ToasterUtil.show(errorMessage); + } + }); + } + + private void showAuth() { + SpannableStringBuilder spannable = new SpannableStringBuilder(tv_auth.getText()); + //《用户协议》和《隐私政策》 + int i = tv_auth.getText().toString().indexOf("《用户协议》"); + int i2 = tv_auth.getText().toString().indexOf("《隐私政策》"); + spannable.setSpan(new ForegroundColorSpan(Color.parseColor("#0C7CD8")), i, i + 6, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); + spannable.setSpan(new ForegroundColorSpan(Color.parseColor("#0C7CD8")), i2, i2 + 6, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); + tv_auth.setMovementMethod(LinkMovementMethod.getInstance()); + spannable.setSpan(new Activity_Login.TextClick(), i, i + 6, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); + spannable.setSpan(new Activity_Login.TextClick2(), i2, i2 + 6, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); + tv_auth.setText(spannable); + tv_auth.setHighlightColor(Color.parseColor("#00ffffff")); + } + + /** + * 验证码获取倒计时 + */ + private class MyCountDownTimer extends CountDownTimer { + + public MyCountDownTimer(long millisInFuture, long countDownInterval) { + super(millisInFuture, countDownInterval); + } + + //计时过程 + @Override + public void onTick(long l) { + //防止计时过程中重复点击 + tv_getcode.setClickable(false); + tv_getcode.setText("重发 " + l / 1000 + " S"); + } + + //计时完毕的方法 + @Override + public void onFinish() { + //重新给Button设置文字 + tv_getcode.setText("获取验证码"); + //设置可点击 + tv_getcode.setClickable(true); + } + } + + private class TextClick extends ClickableSpan { + @Override + public void onClick(View widget) { //在此处理点击事件 + Intent intent = new Intent(Activity_Login.this, Activity_Tw.class); + startActivity(intent); + } + + @Override + public void updateDrawState(@NonNull TextPaint ds) { + ds.setColor(Color.parseColor("#0C7CD8")); + } + } + + private class TextClick2 extends ClickableSpan { + @Override + public void onClick(View widget) { //在此处理点击事件 + Intent intent = new Intent(Activity_Login.this, Activity_WebView.class); + intent.putExtra("type", "assets"); + intent.putExtra("title", "隐私政策"); + startActivity(intent); + } + + @Override + public void updateDrawState(@NonNull TextPaint ds) { + ds.setColor(Color.parseColor("#0C7CD8")); + } + } +} diff --git a/app/src/main/java/com/tfq/finances/finances/activity/Activity_Setting_More.java b/app/src/main/java/com/tfq/finances/finances/activity/Activity_Setting_More.java new file mode 100644 index 0000000..20a6653 --- /dev/null +++ b/app/src/main/java/com/tfq/finances/finances/activity/Activity_Setting_More.java @@ -0,0 +1,240 @@ +package com.tfq.finances.finances.activity; + +import android.app.Activity; +import android.content.Intent; +import android.os.Bundle; +import android.os.Handler; +import android.text.TextUtils; +import android.view.View; +import android.widget.ImageView; +import android.widget.LinearLayout; +import android.widget.TextView; + +import com.tfq.ad.ad.AdCQPUtils; +import com.tfq.finances.app.App; +import com.tfq.finances.core.enums.AvatarEnum; +import com.tfq.finances.jzrcj.R; +import com.tfq.finances.main.Activity_Main; +import com.tfq.finances.network.api.feedback.FeedbackService; +import com.tfq.finances.network.api.userinfo.UserInfoService; +import com.tfq.finances.network.config.ApiCallback; +import com.tfq.finances.network.config.token.TokenManager; +import com.tfq.finances.utils.ChooseHeadDialog; +import com.tfq.library.base.BaseActivity; +import com.tfq.library.utils.AddTextDialog; +import com.tfq.library.utils.AppUtil; +import com.tfq.library.utils.LogK; +import com.tfq.library.utils.PermissionDialog; +import com.tfq.library.utils.ToasterUtil; + +import androidx.annotation.Nullable; + +public class Activity_Setting_More extends BaseActivity { + + + // 常量 + private static final String FEEDBACK_TITLE = "账号设置"; + /** + * 用户头像是否更新 + */ + public static int avatar_Id = -1; + /** + * 用户昵称是否更新 + */ + public static String activity_nickname = ""; + /** + * 提交反馈 + */ + private final FeedbackService feedbackService = new FeedbackService(App.getContext()); + // UI组件 + private ImageView ivBack; + private TextView tvTitle; + private ImageView iv_avatar; + private TextView tv_nickname; + private TextView tv_logout; + private LinearLayout ll_head; + private LinearLayout ll_nickname; + private LinearLayout ll_cancel_account; + private TextView tv_phone; + /** + * 用户头像资源id + */ + private int avatarById; + /** + * 用户头像索引 + */ + private String avatar; + private ImageView iv_logout; + + @Override + protected void onCreate(@Nullable Bundle savedInstanceState) { + setStatusBarDarkFont(true); + super.onCreate(savedInstanceState); + } + + @Override + protected int getLayoutId() { + return R.layout.activity_setting_more_layout; + } + + @Override + protected void initView() { + findViews(); + setupViews(); + } + + private void findViews() { + ivBack = findViewById(R.id.iv_back); + tvTitle = findViewById(R.id.tv_title); + ll_head = findViewById(R.id.ll_head); + iv_avatar = findViewById(R.id.iv_avatar); + ll_nickname = findViewById(R.id.ll_nickname); + tv_nickname = findViewById(R.id.tv_nickname); + ll_cancel_account = findViewById(R.id.ll_cancel_account); + tv_logout = findViewById(R.id.tv_logout); + tv_phone = findViewById(R.id.tv_phone); + iv_logout = findViewById(R.id.iv_logout); + } + + private void setupViews() { + ivBack.setOnClickListener(this::onBackClicked); + ll_head.setOnClickListener(this::onClick); + ll_nickname.setOnClickListener(this::onClick); + ll_cancel_account.setOnClickListener(this::onClick); + tv_logout.setOnClickListener(this::onClick); + iv_logout.setOnClickListener(this::onClick); + } + + @Override + protected void initData(Bundle savedInstanceState) { + tvTitle.setText(FEEDBACK_TITLE); + String nickname = Activity_Main.memberUser.getNickname(); + tv_nickname.setText(!TextUtils.isEmpty(nickname) ? nickname : ""); + String mobile = maskPhoneNumber(Activity_Main.memberUser.getMobile()); + tv_phone.setText(!TextUtils.isEmpty(mobile) ? mobile : ""); + + try { + avatar = Activity_Main.memberUser.getAvatar(); + LogK.e("avatar="+avatar); + if (AppUtil.isInteger(avatar) && Integer.parseInt(avatar) > 0) { + avatarById = AvatarEnum.getAvatarById(Integer.parseInt(avatar)); + iv_avatar.setImageResource(avatarById); + } else { + avatar = "-1"; + } + } catch (Exception e) { + e.printStackTrace(); + avatar = "-1"; + } + + } + + @Override + protected void onResume() { + super.onResume(); + initializeAd(); + } + + private void initializeAd() { + new AdCQPUtils().init(this); + } + + public void onClick(View view) { + // 保留这个方法以满足可能的其他点击需求 + if (view.getId() == R.id.ll_head) { + new ChooseHeadDialog(this, "choose_head", "选择头像", "", new ChooseHeadDialog.Listener() { + @Override + public void success(int avatarId) { + avatar_Id = avatarId; + avatar = String.valueOf(avatarId); + String nickname = tv_nickname.getText().toString().trim(); + setInfo(true, avatarId, nickname); + } + }).show(); + } else if (view.getId() == R.id.ll_nickname) {//昵称 + String nickname = tv_nickname.getText().toString().trim(); + new AddTextDialog(this, nickname, "修改用户昵称", "nickname", new AddTextDialog.Listener() { + @Override + public void success(String name) { + LogK.e("name=" + name); + activity_nickname = name; + tv_nickname.setText(name); + setInfo(false, Integer.parseInt(avatar), name); + } + }).show(); + } else if (view.getId() == R.id.ll_cancel_account) {//注销 + new PermissionDialog(this, "注销账号", "注意: 注销账号后所有数据将不可恢复,请谨慎操作", new PermissionDialog.Listener() { + @Override + public void success() { + cancelAccount(); + } + }).show(); + } else if (view.getId() == R.id.tv_logout || view.getId() == R.id.iv_logout) {//退出 + clearTokenAndToLogin(); + } + } + + private void clearTokenAndToLogin() { + TokenManager.getInstance(this).clearTokens(); + Intent intent = new Intent(); + setResult(Activity.RESULT_OK, intent); + finish(); + } + + /** + * 注销账户 + */ + private void cancelAccount() { + new UserInfoService(Activity_Setting_More.this).deleteAccount(new ApiCallback() { + @Override + public void onSuccess(Boolean response) { + ToasterUtil.show("注销成功, 即将返回登录页"); + new Handler().postDelayed(new Runnable() { + @Override + public void run() { + clearTokenAndToLogin(); + } + }, 800); + } + + @Override + public void onFailure(int errorCode, String errorMessage) { + ToasterUtil.show(errorCode); + } + }); + } + + private void setInfo(boolean isAvatar, int avatarId, String nickname) { + int avatarById = AvatarEnum.getAvatarById(avatarId); + LogK.e("avatarById="+avatarById); + if (avatarById > 0) { + iv_avatar.setImageResource(avatarById); + } + new UserInfoService(Activity_Setting_More.this).setInfo(nickname, String.valueOf(avatarId), new ApiCallback() { + @Override + public void onSuccess(Boolean response) { + ToasterUtil.show(isAvatar ? "头像更新成功" : "昵称更新成功"); + if (isAvatar) { + Activity_Main.memberUser.setAvatar(String.valueOf(avatarId)); + } + Activity_Main.memberUser.setNickname(nickname); + } + + @Override + public void onFailure(int errorCode, String errorMessage) { + ToasterUtil.show(errorCode); + } + }); + } + + private void onBackClicked(View view) { + finish(); + } + + public String maskPhoneNumber(String phone) { + if (phone == null || phone.length() != 11) { + return phone; // 如果不是11位手机号则原样返回 + } + return phone.substring(0, 3) + "****" + phone.substring(7); + } +} diff --git a/app/src/main/java/com/tfq/finances/finances/activity/Activity_Transactions.java b/app/src/main/java/com/tfq/finances/finances/activity/Activity_Transactions.java new file mode 100644 index 0000000..b195dcb --- /dev/null +++ b/app/src/main/java/com/tfq/finances/finances/activity/Activity_Transactions.java @@ -0,0 +1,245 @@ +package com.tfq.finances.finances.activity; + +import android.annotation.SuppressLint; +import android.graphics.Color; +import android.os.Bundle; +import android.os.Handler; +import android.view.View; +import android.widget.FrameLayout; +import android.widget.ImageView; +import android.widget.LinearLayout; +import android.widget.TextView; + +import com.tfq.ad.ad.AdFeedUtils; +import com.tfq.finances.finances.adapter.TransactionsAdapter; +import com.tfq.finances.jzrcj.R; +import com.tfq.finances.network.api.finances.BillsService; +import com.tfq.finances.network.config.ApiCallback; +import com.tfq.finances.network.model.response.finances.transactions.BillsResp; +import com.tfq.finances.utils.BigDecimalUtils; +import com.tfq.finances.utils.androidpicker.CalculateUtils; +import com.tfq.finances.utils.androidpicker.DateMode; +import com.tfq.finances.utils.animation.AnimationClick; +import com.tfq.library.app.BaseConstants; +import com.tfq.library.base.BaseActivity; +import com.tfq.library.utils.RecyclerViewHelper; +import com.tfq.library.utils.ToasterUtil; + +import java.math.BigDecimal; +import java.text.SimpleDateFormat; +import java.util.ArrayList; +import java.util.Date; +import java.util.List; +import java.util.Locale; + +import androidx.recyclerview.widget.RecyclerView; + +public class Activity_Transactions extends BaseActivity { + + private TextView tv_month; + private TextView tv_year; + private CalculateUtils calculateUtils; + private TextView tv_choose_year; + private RecyclerView recycler_view; + private TransactionsAdapter mAdapter; + private boolean isReqServer; + private LinearLayout ll_choose_year; + private TextView tv_balance, tv_income, tv_expenditure; + private TextView tv01, tv02, tv03, tv04, tv05, tv06, tv07; + private FrameLayout fl_content; + + @Override + protected int getLayoutId() { + return R.layout.activity_transactions_layout; + } + + @Override + protected void initView() { + ImageView iv_back = findViewById(R.id.iv_back); + iv_back.setOnClickListener(this::onClick); + tv_month = findViewById(R.id.tv_month); + tv_month.setOnClickListener(this::onClick); + tv_year = findViewById(R.id.tv_year); + tv_year.setOnClickListener(this::onClick); + ll_choose_year = findViewById(R.id.ll_choose_year); + ll_choose_year.setOnClickListener(this::onClick); + tv_choose_year = findViewById(R.id.tv_choose_year); + + recycler_view = findViewById(R.id.recycler_view); + + fl_content = findViewById(R.id.fl_content); + + tv_balance = findViewById(R.id.tv_balance); + tv_income = findViewById(R.id.tv_income); + tv_expenditure = findViewById(R.id.tv_expenditure); + tv01 = findViewById(R.id.tv_01); + tv02 = findViewById(R.id.tv_02); + tv03 = findViewById(R.id.tv_03); + tv04 = findViewById(R.id.tv_04); + tv05 = findViewById(R.id.tv_05); + tv06 = findViewById(R.id.tv_06); + tv07 = findViewById(R.id.tv_07); + + initRecyclerView(); + } + + @SuppressLint("SetTextI18n") + @Override + protected void initData(Bundle savedInstanceState) { + setStatusBarDarkFont(true); + + checkLeft(); + } + + private void initAd() { + AdFeedUtils.show_ad(this, BaseConstants.CODE_AD_FEED1, fl_content, 20, "budgets", 3); + } + + @Override + protected void onPause() { + super.onPause(); + + AdFeedUtils.cancelTag("budgets"); + } + + private void getMonthBillsInfo(String currentDate) { + new BillsService(this).getMonthBillsInfo(currentDate, new ApiCallback>() { + @Override + public void onSuccess(List response) { + setAdapter(response); + } + + @Override + public void onFailure(int errorCode, String errorMessage) { + ToasterUtil.show(errorMessage); + } + }); + } + + private void setAdapter(List response) { + if (response != null && !response.isEmpty()) { + mAdapter.setNewData(response); + List balance = new ArrayList<>(); + List expenditure = new ArrayList<>(); + List income = new ArrayList<>(); + for (int i = 0; i < response.size(); i++) { + balance.add(response.get(i).getBalance()); + expenditure.add(response.get(i).getExpenditure()); + income.add(response.get(i).getIncome()); + } + + BigDecimal sum = BigDecimalUtils.sum(expenditure); + tv_expenditure.setText(String.valueOf(sum)); + BigDecimal sum2 = BigDecimalUtils.sum(income); + tv_income.setText(String.valueOf(sum2)); + BigDecimal sum3 = BigDecimalUtils.sum(balance); + tv_balance.setText(String.valueOf(sum3)); + + AnimationClick.startScaleAnimation(recycler_view); + } else { + mAdapter.setNewData(new ArrayList<>()); + tv_expenditure.setText("0"); + tv_income.setText("0"); + tv_balance.setText("0"); + } + } + + private void initRecyclerView() { + mAdapter = new TransactionsAdapter(R.layout.item_transactions_layout); + recycler_view.setAdapter(mAdapter); + RecyclerViewHelper.initRecyclerViewV(this, recycler_view, mAdapter); + + } + + public void onClick(View view) { + int viewId = view.getId(); + if (viewId == R.id.iv_back) { + finish(); + } else if (viewId == R.id.tv_month) { + checkLeft(); + } else if (viewId == R.id.tv_year) { + checkRight(); + } else if (viewId == R.id.ll_choose_year) { + if (!isReqServer) { + isReqServer = true; + new Handler().postDelayed(new Runnable() { + @Override + public void run() { + isReqServer = false; + } + }, 500); + calculateUtils.yearMonthDay(DateMode.YEAR); + calculateUtils.choose_date(tv_choose_year); + calculateUtils.setListener(new CalculateUtils.Listener() { + @Override + public void success(String year) { + getMonthBillsInfo(year); + } + }); + } + } + } + + private void checkLeft() { + tv_month.setTextColor(Color.parseColor("#FFFFFF")); + tv_year.setTextColor(Color.parseColor("#000000")); + tv_month.setSelected(true); + tv_year.setSelected(false); + + ll_choose_year.setVisibility(View.VISIBLE); + + // 方式1:使用SimpleDateFormat(兼容所有API版本) + SimpleDateFormat sdf = new SimpleDateFormat("yyyy", Locale.getDefault()); + // 初始化 + calculateUtils = new CalculateUtils(this); + String currentDate = sdf.format(new Date()); + tv_choose_year.setText(currentDate + "年"); + getMonthBillsInfo(currentDate); + + tv01.setText("月份"); + tv02.setText("月收入"); + tv03.setText("月支出"); + tv04.setText("月结余"); + tv05.setText("月结余"); + tv06.setText("月收入"); + tv07.setText("月支出"); + + initAd(); + } + + private void checkRight() { + tv_month.setTextColor(Color.parseColor("#000000")); + tv_year.setTextColor(Color.parseColor("#FFFFFF")); + tv_month.setSelected(false); + tv_year.setSelected(true); + + ll_choose_year.setVisibility(View.GONE); + + getYearBillsInfo(); + + tv01.setText("年份"); + tv02.setText("年收入"); + tv03.setText("年支出"); + tv04.setText("年结余"); + tv05.setText("年结余"); + tv06.setText("年收入"); + tv07.setText("年支出"); + + initAd(); + } + + private void getYearBillsInfo() { + new BillsService(this).getYearBillsInfo(new ApiCallback>() { + @Override + public void onSuccess(List response) { + setAdapter(response); + } + + @Override + public void onFailure(int errorCode, String errorMessage) { + ToasterUtil.show(errorMessage); + } + }); + } + +} diff --git a/app/src/main/java/com/tfq/finances/finances/adapter/HeadAdapter.java b/app/src/main/java/com/tfq/finances/finances/adapter/HeadAdapter.java new file mode 100644 index 0000000..d503757 --- /dev/null +++ b/app/src/main/java/com/tfq/finances/finances/adapter/HeadAdapter.java @@ -0,0 +1,28 @@ +package com.tfq.finances.finances.adapter; + +import com.chad.library.adapter.base.BaseQuickAdapter; +import com.chad.library.adapter.base.BaseViewHolder; +import com.tfq.finances.app.App; +import com.tfq.finances.core.enums.CommonTypeOriginEnum; +import com.tfq.finances.finances.model.HeadConfig; +import com.tfq.finances.jzrcj.R; +import com.tfq.finances.main.model.TypeOriginConfig; +import com.tfq.finances.utils.animation.AnimationClick; + +public class HeadAdapter extends BaseQuickAdapter { + + public HeadAdapter(int layoutID) { + super(layoutID); + } + + @Override + protected void convert(BaseViewHolder helper, HeadConfig entity) { + helper.setImageResource(R.id.iv_head, entity.getHeadResource()!=-1?entity.getHeadResource(): R.mipmap.ic_head); + if (entity.getItemCheck()){ + helper.setBackgroundRes(R.id.iv_head, entity.getHeadChecked()); + AnimationClick.startScaleAnimation(helper.getView(R.id.iv_head)); + }else { + helper.setBackgroundRes(R.id.iv_head, entity.getHeadCheck()); + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/tfq/finances/finances/adapter/TransactionsAdapter.java b/app/src/main/java/com/tfq/finances/finances/adapter/TransactionsAdapter.java new file mode 100644 index 0000000..3579b8e --- /dev/null +++ b/app/src/main/java/com/tfq/finances/finances/adapter/TransactionsAdapter.java @@ -0,0 +1,32 @@ +package com.tfq.finances.finances.adapter; + +import android.text.TextUtils; + +import com.chad.library.adapter.base.BaseQuickAdapter; +import com.chad.library.adapter.base.BaseViewHolder; +import com.tfq.finances.jzrcj.R; +import com.tfq.finances.network.model.response.finances.transactions.BillsResp; + +public class TransactionsAdapter extends BaseQuickAdapter { + + public TransactionsAdapter(int layoutID) { + super(layoutID); + } + + @Override + protected void convert(BaseViewHolder helper, BillsResp entity) { + if (!TextUtils.isEmpty(entity.getTimeKey())) { + if (entity.getTimeKey().length() > 2) { + helper.setText(R.id.tv_timeKey, entity.getTimeKey() + "年"); + } else { + helper.setText(R.id.tv_timeKey, entity.getTimeKey() + "月"); + } + } else { + helper.setText(R.id.tv_timeKey, "未知"); + } + helper.setText(R.id.tv_expenditure, String.valueOf(entity.getExpenditure())); + helper.setText(R.id.tv_income, String.valueOf(entity.getIncome())); + helper.setText(R.id.tv_balance, String.valueOf(entity.getBalance())); + + } +} \ No newline at end of file diff --git a/app/src/main/java/com/tfq/finances/finances/fragment/Fm_Page_Expenditure.java b/app/src/main/java/com/tfq/finances/finances/fragment/Fm_Page_Expenditure.java new file mode 100644 index 0000000..911ffbe --- /dev/null +++ b/app/src/main/java/com/tfq/finances/finances/fragment/Fm_Page_Expenditure.java @@ -0,0 +1,396 @@ +package com.tfq.finances.finances.fragment; + +import android.annotation.SuppressLint; +import android.os.Bundle; +import android.view.View; +import android.widget.EditText; +import android.widget.ImageView; +import android.widget.LinearLayout; +import android.widget.TextView; + +import com.chad.library.adapter.base.BaseQuickAdapter; +import com.github.gzuliyujiang.wheelpicker.annotation.DateMode; +import com.gyf.immersionbar.ImmersionBar; +import com.gyf.immersionbar.OnKeyboardListener; +import com.tfq.finances.core.enums.CommonTypeOriginEnum; +import com.tfq.finances.core.enums.SystemExpensesCategoriesEnum; +import com.tfq.finances.jzrcj.R; +import com.tfq.finances.main.adapter.TypeOriginAdapter; +import com.tfq.finances.main.model.TypeOriginConfig; +import com.tfq.finances.network.api.finances.CategoriesService; +import com.tfq.finances.network.config.ApiCallback; +import com.tfq.finances.network.config.token.TokenManager; +import com.tfq.finances.network.model.response.finances.categories.CategoriesAllResp; +import com.tfq.finances.network.model.response.finances.categories.CategoriesData; +import com.tfq.finances.utils.androidpicker.CalculateUtils; +import com.tfq.finances.utils.animation.AnimationClick; +import com.tfq.library.app.BaseConstants; +import com.tfq.library.base.BaseFragment; +import com.tfq.library.utils.AddTextDialog; +import com.tfq.library.utils.AppUtil; +import com.tfq.library.utils.LogK; +import com.tfq.library.utils.RecyclerViewHelper; +import com.tfq.library.utils.ToasterUtil; + +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; + +import androidx.annotation.NonNull; +import androidx.recyclerview.widget.GridLayoutManager; +import androidx.recyclerview.widget.LinearLayoutManager; +import androidx.recyclerview.widget.RecyclerView; + +import static android.view.View.VISIBLE; + +/** + * 支出 + */ +public class Fm_Page_Expenditure extends BaseFragment { + + private LinearLayout ll_keyboard; + private LinearLayout ll, ll_today; + private EditText et_remark; + private TextView tv_0, tv_1, tv_2, tv_3, tv_4, tv_5, tv_6, tv_7, tv_8, tv_9, tv_dot; + private TextView tv_num1, tv_num2, tv_symbol1, tv_result, tv_add, tv_subtract, tv_complete, tv_today; + private ImageView iv_delete, iv_today, iv_hide_keybord; + private long lastEventTime = 0; + private CalculateUtils calculateUtils; + private RecyclerView recycler_view; + private int old_checked_item = 0; + private TypeOriginAdapter mAdapter; + + @Override + protected int getLayoutId() { + return R.layout.fm_page_expenditure; + } + + @Override + protected void initView() { + ll_keyboard = findViewBy_Id(R.id.ll_keyboard); + ll = findViewBy_Id(R.id.ll); + + tv_result = findViewBy_Id(R.id.tv_result); + et_remark = findViewBy_Id(R.id.et_remark); + tv_7 = findViewBy_Id(R.id.tv_7); + tv_8 = findViewBy_Id(R.id.tv_8); + tv_9 = findViewBy_Id(R.id.tv_9); + ll_today = findViewBy_Id(R.id.ll_today); + tv_today = findViewBy_Id(R.id.tv_today); + iv_today = findViewBy_Id(R.id.iv_today); + tv_4 = findViewBy_Id(R.id.tv_4); + tv_5 = findViewBy_Id(R.id.tv_5); + tv_6 = findViewBy_Id(R.id.tv_6); + tv_add = findViewBy_Id(R.id.tv_add); + tv_1 = findViewBy_Id(R.id.tv_1); + tv_2 = findViewBy_Id(R.id.tv_2); + tv_3 = findViewBy_Id(R.id.tv_3); + tv_subtract = findViewBy_Id(R.id.tv_subtract); + tv_dot = findViewBy_Id(R.id.tv_dot); + tv_0 = findViewBy_Id(R.id.tv_0); + iv_delete = findViewBy_Id(R.id.iv_delete); + tv_complete = findViewBy_Id(R.id.tv_complete); + tv_num1 = findViewBy_Id(R.id.tv_num1); + tv_num2 = findViewBy_Id(R.id.tv_num2); + tv_symbol1 = findViewBy_Id(R.id.tv_symbol1); + iv_hide_keybord = findViewBy_Id(R.id.iv_hide_keybord); + ll.setVisibility(View.GONE); + + recycler_view = findViewBy_Id(R.id.recycler_view); + + initListener(); + } + + private void initListener() { + tv_7.setOnClickListener(this::onClick); + tv_8.setOnClickListener(this::onClick); + tv_9.setOnClickListener(this::onClick); + ll_today.setOnClickListener(this::onClick); + tv_4.setOnClickListener(this::onClick); + tv_5.setOnClickListener(this::onClick); + tv_6.setOnClickListener(this::onClick); + tv_add.setOnClickListener(this::onClick); + tv_1.setOnClickListener(this::onClick); + tv_2.setOnClickListener(this::onClick); + tv_3.setOnClickListener(this::onClick); + tv_subtract.setOnClickListener(this::onClick); + tv_dot.setOnClickListener(this::onClick); + tv_0.setOnClickListener(this::onClick); + iv_delete.setOnClickListener(this::onClick); + tv_complete.setOnClickListener(this::onClick); + iv_hide_keybord.setOnClickListener(this::onClick); + + } + + @Override + protected void initData(Bundle savedInstanceState) { + ImmersionBar.with(this) + .statusBarDarkFont(true) + .navigationBarColor(BaseConstants.navigationBarColor) + .keyboardEnable(true) + .setOnKeyboardListener(new OnKeyboardListener() { + @Override + public void onKeyboardChange(boolean isPopup, int keyboardHeight) { + LogK.e("isPopup=" + isPopup); //isPopup为true,软键盘弹出,为false,软键盘关闭 + if (System.currentTimeMillis() - lastEventTime > 100) { // 100ms间隔阈值 + lastEventTime = System.currentTimeMillis(); + if (isPopup) { + ll_keyboard.setVisibility(View.GONE); + } else { + ll_keyboard.setVisibility(View.VISIBLE); + } + } + + } + }).init(); + + // 初始化 + calculateUtils = new CalculateUtils(getActivity()); + calculateUtils.initialize(1, tv_complete, tv_num1, tv_num2, tv_symbol1, tv_result, tv_today, et_remark); + + initRecyclerView(); + + reqCategoriesAll(); + + } + + private void initRecyclerView() { + mAdapter = new TypeOriginAdapter(R.layout.item_type_origin_layout); + recycler_view.setAdapter(mAdapter); + GridLayoutManager manager = new GridLayoutManager(getActivity(), 4, RecyclerView.VERTICAL, false); + RecyclerViewHelper.initRecyclerViewV(getActivity(), recycler_view, mAdapter); + recycler_view.setLayoutManager(manager); + List typeOriginConfigs = getTypeOriginConfigs(); + mAdapter.setNewData(typeOriginConfigs); + mAdapter.setOnItemClickListener(new BaseQuickAdapter.OnItemClickListener() { + @SuppressLint("NotifyDataSetChanged") + @Override + public void onItemClick(BaseQuickAdapter adapter, View view, int position) { + if (getOldCheckedItem() != position) { + if (mAdapter.getData().get(position).getTypeOriginId() == -1) {//点击自定义应该弹窗并且选中,若取消弹窗,应该返回之前的老按钮 + addTypeDialog(); + } else { + setAndRefresh(position); + calculateUtils.setOriginBean(mAdapter.getData().get(position)); + + refreshRv(position); + + ll.setVisibility(View.VISIBLE); + } + } else { + refreshRv(position); + + ll.setVisibility(View.VISIBLE); + } + } + }); + AnimationClick.startScaleAnimation(recycler_view); + + calculateUtils.setOriginBean(mAdapter.getData().get(0)); + } + + private void refreshRv(int position) { + RecyclerView.LayoutManager layoutManager = recycler_view.getLayoutManager(); + // 或者方法3:使用scrollToPositionWithOffset + if (layoutManager instanceof LinearLayoutManager) { + View view = layoutManager.findViewByPosition(position); + if (view != null) { + LogK.e("recycler_view.getHeight()1=" + recycler_view.getHeight()); + int offset = 200; + if (ll.getVisibility() == VISIBLE) { + offset = recycler_view.getHeight() / 2 - view.getHeight() / 2; + } /*else { + ll.setVisibility(VISIBLE); + // 或者方法3:使用scrollToPositionWithOffset + offset = recycler_view.getHeight() / 2 - ll.getHeight() / 2 - view.getHeight() / 2; + }*/ + ((LinearLayoutManager) layoutManager).scrollToPositionWithOffset(position, offset); + } + } + } + + private void reqCategoriesAll() { + TokenManager instance = TokenManager.getInstance(getActivity()); + new CategoriesService(getActivity()).getCategoriesList(instance.getUserId(), 1, new ApiCallback() { + @Override + public void onSuccess(CategoriesAllResp response) { + List list = new ArrayList<>(); + if (response.getUser() != null && !response.getUser().isEmpty()) { + for (CategoriesData categoriesData : Objects.requireNonNull(response.getUser())) { + String name = categoriesData.getName(); + int id = categoriesData.getId(); + + TypeOriginConfig config = new TypeOriginConfig(); + config.setType(CommonTypeOriginEnum.USER.getName()); + config.setTypeOriginId(id); + config.setTypeOriginName(name); + config.setTypeOriginIdCheck(R.mipmap.ic_zdy_check); + config.setTypeOriginIdChecked(R.mipmap.ic_zdy_checked); + list.add(config); + } + } + + addZdyType(list); + if (getActivity() != null) { + getActivity().runOnUiThread(() -> { + mAdapter.addData(list); + }); + } + + } + + @Override + public void onFailure(int errorCode, String errorMessage) { + ToasterUtil.show(errorMessage); + } + }); + } + + public void onClick(View view) { + int viewId = view.getId(); + + if (viewId == R.id.tv_7) { + calculateUtils.press("7", tv_7); + } else if (viewId == R.id.tv_8) { + calculateUtils.press("8", tv_8); + } else if (viewId == R.id.tv_9) { + calculateUtils.press("9", tv_9); + } else if (viewId == R.id.ll_today) { + calculateUtils.yearMonthDay(DateMode.YEAR_MONTH); + calculateUtils.choose_date(tv_today, iv_today); + } else if (viewId == R.id.tv_4) { + calculateUtils.press("4", tv_4); + } else if (viewId == R.id.tv_5) { + calculateUtils.press("5", tv_5); + } else if (viewId == R.id.tv_6) { + calculateUtils.press("6", tv_6); + } else if (viewId == R.id.tv_add) { + calculateUtils.symbol("+"); + } else if (viewId == R.id.tv_1) { + calculateUtils.press("1", tv_1); + } else if (viewId == R.id.tv_2) { + calculateUtils.press("2", tv_2); + } else if (viewId == R.id.tv_3) { + calculateUtils.press("3", tv_3); + } else if (viewId == R.id.tv_subtract) { + calculateUtils.symbol("-"); + } else if (viewId == R.id.tv_dot) { + calculateUtils.press(".", tv_dot); + } else if (viewId == R.id.tv_0) { + calculateUtils.press("0", tv_0); + } else if (viewId == R.id.iv_delete) { + calculateUtils.onClickDelete(); + } else if (viewId == R.id.tv_complete) { + if (tv_complete.getText().toString().equals("完成")){ + ll.setVisibility(View.GONE); + AppUtil.closeKeyBoard(ll); + } + calculateUtils.onClickFinish(); + } else if (viewId == R.id.iv_hide_keybord) { + ll.setVisibility(View.GONE); + AppUtil.closeKeyBoard(ll); + } + } + + private void addZdyType(List list) { + TypeOriginConfig config = new TypeOriginConfig(); + config.setType(CommonTypeOriginEnum.USER.getName()); + config.setTypeOriginId(-1); + config.setTypeOriginName("自定义"); + config.setTypeOriginIdCheck(R.mipmap.ic_zdy_check); + config.setTypeOriginIdChecked(R.mipmap.ic_zdy_checked); + list.add(config); + } + + private void addZdyResponse(String response, String name) { + List data = mAdapter.getData(); + + TypeOriginConfig config = new TypeOriginConfig(); + config.setType(CommonTypeOriginEnum.USER.getName()); + config.setTypeOriginId(Integer.parseInt(response)); + config.setTypeOriginName(name); + config.setTypeOriginIdCheck(R.mipmap.ic_zdy_check); + config.setTypeOriginIdChecked(R.mipmap.ic_zdy_checked); + data.add(data.size() - 1, config); + + setAndRefreshNoCheck(data.size() - 2); + +// refreshRv(getOldCheckedItem()); + } + + @SuppressLint("NotifyDataSetChanged") + private void setAndRefresh(int position) { + mAdapter.getData().get(position).setItemCheck(true); + mAdapter.getData().get(getOldCheckedItem()).setItemCheck(false); + setOldCheckedItem(position); + mAdapter.notifyDataSetChanged(); + + calculateUtils.setOriginBean(mAdapter.getData().get(position)); + } + + @SuppressLint("NotifyDataSetChanged") + private void setAndRefreshNoCheck(int position) { + mAdapter.notifyDataSetChanged(); + } + + private void addTypeDialog() { + new AddTextDialog(getActivity(), new AddTextDialog.ListenerFail() { + @Override + public void success(String name) { + LogK.e("success"); + new CategoriesService(getActivity()).addCategories(1, name, "", new ApiCallback() { + @Override + public void onSuccess(String response) {//response 31370 是id + if (getActivity() != null) { + getActivity().runOnUiThread(() -> { + addZdyResponse(response, name); + }); + } + } + + @Override + public void onFailure(int errorCode, String errorMessage) { + ToasterUtil.show(errorMessage); + } + }); + } + + @SuppressLint("NotifyDataSetChanged") + @Override + public void callCancel() { + LogK.e("callCancel"); + + } + }).show(); + } + + @NonNull + private List getTypeOriginConfigs() { + List typeOriginConfigs = new ArrayList<>(); + + List categoriesEnumList = SystemExpensesCategoriesEnum.getAll(); + for (int i = 0; i < categoriesEnumList.size(); i++) { + SystemExpensesCategoriesEnum systemExpensesCategoriesEnum = categoriesEnumList.get(i); + TypeOriginConfig config = new TypeOriginConfig(); + config.setType(CommonTypeOriginEnum.SYSTEM.getName()); + config.setTypeOriginId(systemExpensesCategoriesEnum.getId()); + config.setTypeOriginName(systemExpensesCategoriesEnum.getName()); + config.setTypeOriginIdCheck(systemExpensesCategoriesEnum.getIconUnchecked()); + config.setTypeOriginIdChecked(systemExpensesCategoriesEnum.getIconChecked()); + if (i == 0) { + config.setItemCheck(true); + } + typeOriginConfigs.add(config); + } + return typeOriginConfigs; + } + + public int getOldCheckedItem() { + return old_checked_item; + } + + public void setOldCheckedItem(int old_checked_item) { + this.old_checked_item = old_checked_item; + } + +} diff --git a/app/src/main/java/com/tfq/finances/finances/fragment/Fm_Page_Income.java b/app/src/main/java/com/tfq/finances/finances/fragment/Fm_Page_Income.java new file mode 100644 index 0000000..77be351 --- /dev/null +++ b/app/src/main/java/com/tfq/finances/finances/fragment/Fm_Page_Income.java @@ -0,0 +1,384 @@ +package com.tfq.finances.finances.fragment; + +import android.annotation.SuppressLint; +import android.os.Bundle; +import android.view.View; +import android.widget.EditText; +import android.widget.ImageView; +import android.widget.LinearLayout; +import android.widget.TextView; + +import com.chad.library.adapter.base.BaseQuickAdapter; +import com.github.gzuliyujiang.wheelpicker.annotation.DateMode; +import com.gyf.immersionbar.ImmersionBar; +import com.gyf.immersionbar.OnKeyboardListener; +import com.tfq.finances.core.enums.CommonTypeOriginEnum; +import com.tfq.finances.core.enums.SystemIncomeCategoriesEnum; +import com.tfq.finances.jzrcj.R; +import com.tfq.finances.main.adapter.TypeOriginAdapter; +import com.tfq.finances.main.model.TypeOriginConfig; +import com.tfq.finances.network.api.finances.CategoriesService; +import com.tfq.finances.network.config.ApiCallback; +import com.tfq.finances.network.config.token.TokenManager; +import com.tfq.finances.network.model.response.finances.categories.CategoriesAllResp; +import com.tfq.finances.network.model.response.finances.categories.CategoriesData; +import com.tfq.finances.utils.androidpicker.CalculateUtils; +import com.tfq.finances.utils.animation.AnimationClick; +import com.tfq.library.app.BaseConstants; +import com.tfq.library.base.BaseFragment; +import com.tfq.library.utils.AddTextDialog; +import com.tfq.library.utils.AppUtil; +import com.tfq.library.utils.LogK; +import com.tfq.library.utils.RecyclerViewHelper; +import com.tfq.library.utils.ToasterUtil; + +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; + +import androidx.annotation.NonNull; +import androidx.recyclerview.widget.GridLayoutManager; +import androidx.recyclerview.widget.LinearLayoutManager; +import androidx.recyclerview.widget.RecyclerView; + +import static android.view.View.VISIBLE; + +/** + * 支出 + */ +public class Fm_Page_Income extends BaseFragment { + + private LinearLayout ll, ll_today; + private EditText et_remark; + private TextView tv_0, tv_1, tv_2, tv_3, tv_4, tv_5, tv_6, tv_7, tv_8, tv_9, tv_dot; + private TextView tv_num1, tv_num2, tv_symbol1, tv_result, tv_add, tv_subtract, tv_complete, tv_today; + private ImageView iv_delete, iv_today, iv_hide_keybord; + private LinearLayout ll_keyboard; + private long lastEventTime = 0; + + private CalculateUtils calculateUtils; + private TypeOriginAdapter mAdapter; + private RecyclerView recycler_view; + private int old_checked_item = 0; + + @Override + protected int getLayoutId() { + return R.layout.fm_page_income; + } + + @Override + protected void initView() { + ll_keyboard = findViewBy_Id(R.id.ll_keyboard); + ll = findViewBy_Id(R.id.ll); + + tv_result = findViewBy_Id(R.id.tv_result); + et_remark = findViewBy_Id(R.id.et_remark); + tv_7 = findViewBy_Id(R.id.tv_7); + tv_8 = findViewBy_Id(R.id.tv_8); + tv_9 = findViewBy_Id(R.id.tv_9); + ll_today = findViewBy_Id(R.id.ll_today); + tv_today = findViewBy_Id(R.id.tv_today); + iv_today = findViewBy_Id(R.id.iv_today); + tv_4 = findViewBy_Id(R.id.tv_4); + tv_5 = findViewBy_Id(R.id.tv_5); + tv_6 = findViewBy_Id(R.id.tv_6); + tv_add = findViewBy_Id(R.id.tv_add); + tv_1 = findViewBy_Id(R.id.tv_1); + tv_2 = findViewBy_Id(R.id.tv_2); + tv_3 = findViewBy_Id(R.id.tv_3); + tv_subtract = findViewBy_Id(R.id.tv_subtract); + tv_dot = findViewBy_Id(R.id.tv_dot); + tv_0 = findViewBy_Id(R.id.tv_0); + iv_delete = findViewBy_Id(R.id.iv_delete); + tv_complete = findViewBy_Id(R.id.tv_complete); + tv_num1 = findViewBy_Id(R.id.tv_num1); + tv_num2 = findViewBy_Id(R.id.tv_num2); + tv_symbol1 = findViewBy_Id(R.id.tv_symbol1); + iv_hide_keybord = findViewBy_Id(R.id.iv_hide_keybord); + ll.setVisibility(View.GONE); + + recycler_view = findViewBy_Id(R.id.recycler_view); + + initListener(); + } + + private void initListener() { + tv_7.setOnClickListener(this::onClick); + tv_8.setOnClickListener(this::onClick); + tv_9.setOnClickListener(this::onClick); + ll_today.setOnClickListener(this::onClick); + tv_4.setOnClickListener(this::onClick); + tv_5.setOnClickListener(this::onClick); + tv_6.setOnClickListener(this::onClick); + tv_add.setOnClickListener(this::onClick); + tv_1.setOnClickListener(this::onClick); + tv_2.setOnClickListener(this::onClick); + tv_3.setOnClickListener(this::onClick); + tv_subtract.setOnClickListener(this::onClick); + tv_dot.setOnClickListener(this::onClick); + tv_0.setOnClickListener(this::onClick); + iv_delete.setOnClickListener(this::onClick); + tv_complete.setOnClickListener(this::onClick); + iv_hide_keybord.setOnClickListener(this::onClick); + + } + + @Override + protected void initData(Bundle savedInstanceState) { + ImmersionBar.with(this) + .statusBarDarkFont(true) + .navigationBarColor(BaseConstants.navigationBarColor) + .keyboardEnable(true) + .setOnKeyboardListener(new OnKeyboardListener() { + @Override + public void onKeyboardChange(boolean isPopup, int keyboardHeight) { + LogK.e("isPopup=" + isPopup); //isPopup为true,软键盘弹出,为false,软键盘关闭 + if (System.currentTimeMillis() - lastEventTime > 100) { // 100ms间隔阈值 + lastEventTime = System.currentTimeMillis(); + if (isPopup) { + ll_keyboard.setVisibility(View.GONE); + } else { + ll_keyboard.setVisibility(View.VISIBLE); + } + } + + } + }).init(); + + // 初始化 + calculateUtils = new CalculateUtils(getActivity()); + calculateUtils.initialize(2, tv_complete, tv_num1, tv_num2, tv_symbol1, tv_result, tv_today, et_remark); + + initRecyclerView(); + + reqCategoriesAll(); + } + + private void initRecyclerView() { + mAdapter = new TypeOriginAdapter(R.layout.item_type_origin_layout); + recycler_view.setAdapter(mAdapter); + GridLayoutManager manager = new GridLayoutManager(getActivity(), 4, RecyclerView.VERTICAL, false); + RecyclerViewHelper.initRecyclerViewV(getActivity(), recycler_view, mAdapter); + recycler_view.setLayoutManager(manager); + List typeOriginConfigs = getTypeOriginConfigs(); + mAdapter.setNewData(typeOriginConfigs); + mAdapter.setOnItemClickListener(new BaseQuickAdapter.OnItemClickListener() { + @SuppressLint("NotifyDataSetChanged") + @Override + public void onItemClick(BaseQuickAdapter adapter, View view, int position) { + if (getOldCheckedItem() != position) { + if (mAdapter.getData().get(position).getTypeOriginId() == -1) {//点击自定义应该弹窗并且选中,若取消弹窗,应该返回之前的老按钮 + addTypeDialog(); + } else { + setAndRefresh(position); + calculateUtils.setOriginBean(mAdapter.getData().get(position)); + + refreshRv(position); + + ll.setVisibility(View.VISIBLE); + } + } + } + }); + AnimationClick.startScaleAnimation(recycler_view); + + calculateUtils.setOriginBean(mAdapter.getData().get(0)); + } + + private void refreshRv(int position) { + RecyclerView.LayoutManager layoutManager = recycler_view.getLayoutManager(); + // 或者方法3:使用scrollToPositionWithOffset + if (layoutManager instanceof LinearLayoutManager) { + View view = layoutManager.findViewByPosition(position); + if (view != null) { + LogK.e("recycler_view.getHeight()1=" + recycler_view.getHeight()); + int offset = 800; + if (ll.getVisibility() == VISIBLE) { + offset = recycler_view.getHeight() / 2 - view.getHeight() / 2; + } else { + ll.setVisibility(VISIBLE); + // 或者方法3:使用scrollToPositionWithOffset + offset = recycler_view.getHeight() / 2 - ll.getHeight() / 2 - view.getHeight() / 2; + } + ((LinearLayoutManager) layoutManager).scrollToPositionWithOffset(position, offset); + } + } + + } + + private void reqCategoriesAll() { + TokenManager instance = TokenManager.getInstance(getActivity()); + new CategoriesService(getActivity()).getCategoriesList(instance.getUserId(), 2, new ApiCallback() { + @Override + public void onSuccess(CategoriesAllResp response) { + List list = new ArrayList<>(); + if (response.getUser() != null && !response.getUser().isEmpty()) { + for (CategoriesData categoriesData : Objects.requireNonNull(response.getUser())) { + String name = categoriesData.getName(); + int id = categoriesData.getId(); + + TypeOriginConfig config = new TypeOriginConfig(); + config.setType(CommonTypeOriginEnum.USER.getName()); + config.setTypeOriginId(id); + config.setTypeOriginName(name); + config.setTypeOriginIdCheck(R.mipmap.ic_zdy_check); + config.setTypeOriginIdChecked(R.mipmap.ic_zdy_checked); + list.add(config); + } + } + + addZdyType(list); + if (getActivity() != null) { + getActivity().runOnUiThread(() -> { + mAdapter.addData(list); + }); + } + + } + + @Override + public void onFailure(int errorCode, String errorMessage) { + + } + }); + } + + public void onClick(View view) { + int viewId = view.getId(); + + if (viewId == R.id.tv_7) { + calculateUtils.press("7", tv_7); + } else if (viewId == R.id.tv_8) { + calculateUtils.press("8", tv_8); + } else if (viewId == R.id.tv_9) { + calculateUtils.press("9", tv_9); + } else if (viewId == R.id.ll_today) { + calculateUtils.yearMonthDay(DateMode.YEAR_MONTH); + calculateUtils.choose_date(tv_today, iv_today); + } else if (viewId == R.id.tv_4) { + calculateUtils.press("4", tv_4); + } else if (viewId == R.id.tv_5) { + calculateUtils.press("5", tv_5); + } else if (viewId == R.id.tv_6) { + calculateUtils.press("6", tv_6); + } else if (viewId == R.id.tv_add) { + calculateUtils.symbol("+"); + } else if (viewId == R.id.tv_1) { + calculateUtils.press("1", tv_1); + } else if (viewId == R.id.tv_2) { + calculateUtils.press("2", tv_2); + } else if (viewId == R.id.tv_3) { + calculateUtils.press("3", tv_3); + } else if (viewId == R.id.tv_subtract) { + calculateUtils.symbol("-"); + } else if (viewId == R.id.tv_dot) { + calculateUtils.press(".", tv_dot); + } else if (viewId == R.id.tv_0) { + calculateUtils.press("0", tv_0); + } else if (viewId == R.id.iv_delete) { + calculateUtils.onClickDelete(); + } else if (viewId == R.id.tv_complete) { + if (tv_complete.getText().toString().equals("完成")){ + ll.setVisibility(View.GONE); + AppUtil.closeKeyBoard(ll); + } + calculateUtils.onClickFinish(); + } else if (viewId == R.id.iv_hide_keybord) { + ll.setVisibility(View.GONE); + AppUtil.closeKeyBoard(ll); + } + } + + private void addZdyType(List list) { + TypeOriginConfig config = new TypeOriginConfig(); + config.setType(CommonTypeOriginEnum.USER.getName()); + config.setTypeOriginId(-1); + config.setTypeOriginName("自定义"); + config.setTypeOriginIdCheck(R.mipmap.ic_zdy_check); + config.setTypeOriginIdChecked(R.mipmap.ic_zdy_checked); + list.add(config); + } + + private void addZdyResponse(String response, String name) { + List data = mAdapter.getData(); + + TypeOriginConfig config = new TypeOriginConfig(); + config.setType(CommonTypeOriginEnum.USER.getName()); + config.setTypeOriginId(Integer.parseInt(response)); + config.setTypeOriginName(name); + config.setTypeOriginIdCheck(R.mipmap.ic_zdy_check); + config.setTypeOriginIdChecked(R.mipmap.ic_zdy_checked); + data.add(data.size() - 1, config); + + setAndRefresh(data.size() - 2); + + refreshRv(getOldCheckedItem()); + } + + @SuppressLint("NotifyDataSetChanged") + private void setAndRefresh(int position) { + mAdapter.getData().get(position).setItemCheck(true); + mAdapter.getData().get(getOldCheckedItem()).setItemCheck(false); + setOldCheckedItem(position); + mAdapter.notifyDataSetChanged(); + + calculateUtils.setOriginBean(mAdapter.getData().get(position)); + } + + private void addTypeDialog() { + new AddTextDialog(getActivity(), new AddTextDialog.ListenerFail() { + @Override + public void success(String name) { + LogK.e("success"); + new CategoriesService(getActivity()).addCategories(2, name, "", new ApiCallback() { + @Override + public void onSuccess(String response) {//response 31370 是id + addZdyResponse(response, name); + } + + @Override + public void onFailure(int errorCode, String errorMessage) { + ToasterUtil.show(errorMessage); + } + }); + } + + @SuppressLint("NotifyDataSetChanged") + @Override + public void callCancel() { + LogK.e("callCancel"); + + } + }).show(); + } + + @NonNull + private List getTypeOriginConfigs() { + List typeOriginConfigs = new ArrayList<>(); + + List categoriesEnumList = SystemIncomeCategoriesEnum.getAll(); + for (int i = 0; i < categoriesEnumList.size(); i++) { + SystemIncomeCategoriesEnum systemIncomeCategoriesEnum = categoriesEnumList.get(i); + TypeOriginConfig config = new TypeOriginConfig(); + config.setType(CommonTypeOriginEnum.SYSTEM.getName()); + config.setTypeOriginId(systemIncomeCategoriesEnum.getId()); + config.setTypeOriginName(systemIncomeCategoriesEnum.getName()); + config.setTypeOriginIdCheck(systemIncomeCategoriesEnum.getIconUnchecked()); + config.setTypeOriginIdChecked(systemIncomeCategoriesEnum.getIconChecked()); + if (i == 0) { + config.setItemCheck(true); + } + typeOriginConfigs.add(config); + } + return typeOriginConfigs; + } + + public int getOldCheckedItem() { + return old_checked_item; + } + + public void setOldCheckedItem(int old_checked_item) { + this.old_checked_item = old_checked_item; + } + +} diff --git a/app/src/main/java/com/tfq/finances/finances/model/HeadConfig.kt b/app/src/main/java/com/tfq/finances/finances/model/HeadConfig.kt new file mode 100644 index 0000000..5778bfd --- /dev/null +++ b/app/src/main/java/com/tfq/finances/finances/model/HeadConfig.kt @@ -0,0 +1,37 @@ +package com.tfq.finances.finances.model + +import com.tfq.finances.jzrcj.R + +data class HeadConfig( + + /** + * 类别id + */ + var headId: Int = -1, + + /** + * 类别名称 + */ + var headName: String = "", + + /** + * 类别名称 + */ + var headResource: Int = -1, + + /** + * 类别未选中资源id + */ + var headChecked: Int = R.drawable.ll_boder_head, + + /** + * 类别未选中资源id + */ + var headCheck: Int = R.drawable.ll_boder_head_null, + + /** + * 类别选中 + */ + var itemCheck: Boolean = false, + + ) \ No newline at end of file diff --git a/app/src/main/java/com/tfq/finances/finances/view/CircleProgressBarWithAnimation.java b/app/src/main/java/com/tfq/finances/finances/view/CircleProgressBarWithAnimation.java new file mode 100644 index 0000000..1150d9b --- /dev/null +++ b/app/src/main/java/com/tfq/finances/finances/view/CircleProgressBarWithAnimation.java @@ -0,0 +1,215 @@ +package com.tfq.finances.finances.view; + +import android.animation.ValueAnimator; +import android.annotation.SuppressLint; +import android.content.Context; +import android.graphics.Canvas; +import android.graphics.Color; +import android.graphics.LinearGradient; +import android.graphics.Matrix; +import android.graphics.Paint; +import android.graphics.RectF; +import android.graphics.Shader; +import android.graphics.Typeface; +import android.util.AttributeSet; +import android.view.View; +import android.view.animation.DecelerateInterpolator; + +import androidx.annotation.NonNull; + +import com.tfq.library.utils.LogK; + + +public class CircleProgressBarWithAnimation extends View { + private Paint bgPaint, progressPaint, textPaint; + private RectF rectF; + private float currentProgress = 0; + private float targetProgress = 0; + private ValueAnimator progressAnimator; + private int[] gradientColors = {Color.GREEN, Color.GREEN};//, Color.YELLOW, Color.RED + + public CircleProgressBarWithAnimation(Context context, AttributeSet attrs) { + super(context, attrs); + initPaints(); + } + + private void initPaints() { + // 背景圆环画笔 + bgPaint = new Paint(Paint.ANTI_ALIAS_FLAG); + bgPaint.setColor(Color.LTGRAY); + bgPaint.setStyle(Paint.Style.STROKE); + bgPaint.setStrokeWidth(20f); + + // 进度条画笔 + progressPaint = new Paint(Paint.ANTI_ALIAS_FLAG); + progressPaint.setStyle(Paint.Style.STROKE); + progressPaint.setStrokeWidth(20f); + progressPaint.setStrokeCap(Paint.Cap.ROUND); + + setPaint(); + + rectF = new RectF(); + } + + private void setPaint() { + // 文本画笔 + textPaint = new Paint(Paint.ANTI_ALIAS_FLAG); + textPaint.setColor(Color.BLACK); + textPaint.setTextSize(40f); + textPaint.setTextAlign(Paint.Align.CENTER); + } + + private void setPaint(float textSize, int color, boolean bold) { + // 文本画笔 + textPaint = new Paint(Paint.ANTI_ALIAS_FLAG); + textPaint.setColor(color); + textPaint.setTextSize(textSize); + textPaint.setTextAlign(Paint.Align.CENTER); + textPaint.setTypeface(bold ? Typeface.create(Typeface.SANS_SERIF, Typeface.BOLD) : Typeface.create(Typeface.SANS_SERIF, Typeface.NORMAL)); + } + + private int color_red = Color.parseColor("#FF0000"); + private int color_dark_orange = Color.parseColor("#FF8C00"); + private int color_green = Color.parseColor("#00FF00"); + + @SuppressLint("DrawAllocation") + @Override + protected void onDraw(@NonNull Canvas canvas) { + super.onDraw(canvas); + float centerX = getWidth() / 2f; + float centerY = getHeight() / 2f; + float radius = Math.min(centerX, centerY) - 20f; + + rectF.set(centerX - radius, centerY - radius, + centerX + radius, centerY + radius); + + // 绘制背景圆环 + canvas.drawCircle(centerX, centerY, radius, bgPaint); + + // 更新渐变着色器 + updateGradientShader(centerX, centerY, radius); + + // 绘制进度圆弧 + canvas.drawArc(rectF, -90, 360 * currentProgress / 100, false, progressPaint); + + LogK.e("text=" + ((int) currentProgress + "%")); + // 绘制进度文本 + String text = (int) currentProgress + "%"; + if (currentProgress > 99) { + // 绘制进度文本 + text = "已超支"; + gradientColors = new int[]{color_red, color_red}; + setPaint(35f, color_red, true); + } else if (currentProgress >= 80) { + // 绘制进度文本 + text = (int) currentProgress + "%"; + gradientColors = new int[]{color_dark_orange, color_dark_orange}; + setPaint(50f, color_dark_orange, true); + } else { + // 绘制进度文本 + text = (int) currentProgress + "%"; +// gradientColors = new int[]{color_green, color_green}; + setPaint(50f, Color.BLACK, false); + } + canvas.drawText(text, centerX, centerY - (textPaint.descent() + textPaint.ascent()) / 2, textPaint); + + } + + /*private void updateGradientShader(float centerX, float centerY, float radius) { + SweepGradient gradient = new SweepGradient(centerX, centerY, gradientColors, null); + Matrix matrix = new Matrix(); + matrix.setRotate(-90, centerX, centerY); + gradient.setLocalMatrix(matrix); + progressPaint.setShader(gradient); + }*/ + +// private void updateGradientShader(float centerX, float centerY, float radius) { + + /// / SweepGradient gradient = new SweepGradient(centerX, centerY, gradientColors, null); + /// / Matrix matrix = new Matrix(); + /// / matrix.setRotate(-90, centerX, centerY); + /// / gradient.setLocalMatrix(matrix); + /// / progressPaint.setShader(gradient); +// +// /*// 在onDraw方法中替换渐变实现 +// float startX = centerX; +// float startY = centerY - radius; // 顶部起点 +// float endX = (float)(centerX + radius * Math.sin(Math.toRadians(360 * currentProgress/100))); +// float endY = (float)(centerY - radius * Math.cos(Math.toRadians(360 * currentProgress/100))); +// +// LinearGradient gradient = new LinearGradient( +// startX, startY, // 渐变起点(顶部中心) +// endX, endY, // 渐变终点(根据进度计算) +// gradientColors, // 颜色数组 +// null, // 位置数组(均匀分布) +// Shader.TileMode.CLAMP +// ); +// progressPaint.setShader(gradient);*/ +// } + + private void updateGradientShader(float centerX, float centerY, float radius) { + // 计算起始点和结束点坐标(从顶部开始顺时针方向) + float startX = centerX; + float startY = centerY - radius; + float endX = centerX; + float endY = centerY + radius; + + // 创建线性渐变(垂直方向) + LinearGradient gradient = new LinearGradient( + startX, startY, + endX, endY, + gradientColors, + null, // 均匀分布颜色 + Shader.TileMode.CLAMP + ); + + // 根据当前进度旋转渐变 + Matrix matrix = new Matrix(); + matrix.setRotate(-90 + (360 * currentProgress / 100), centerX, centerY); + gradient.setLocalMatrix(matrix); + + progressPaint.setShader(gradient); + } + + + public void setProgress(float progress, boolean animate) { + targetProgress = progress; + if (progressAnimator != null && progressAnimator.isRunning()) { + progressAnimator.cancel(); + } + + if (animate) { + startProgressAnimation(); + } else { + currentProgress = targetProgress; + invalidate(); + } + } + + private void startProgressAnimation() { + progressAnimator = ValueAnimator.ofFloat(currentProgress, targetProgress); + progressAnimator.setDuration(1000); + progressAnimator.setInterpolator(new DecelerateInterpolator()); + progressAnimator.addUpdateListener(animation -> { + currentProgress = (float) animation.getAnimatedValue(); + invalidate(); + }); + progressAnimator.start(); + } + + public void setGradientColors(int[] colors) { + this.gradientColors = colors; + if (colors.length == 1) { + gradientColors = new int[]{colors[0], colors[0]}; + } + invalidate(); + } + + public void setGradientColors(int colors) { + if (colors == 0) { + gradientColors = new int[]{colors, colors}; + } + invalidate(); + } +} + diff --git a/app/src/main/java/com/tfq/finances/main/Activity_Main.java b/app/src/main/java/com/tfq/finances/main/Activity_Main.java new file mode 100644 index 0000000..38d82b8 --- /dev/null +++ b/app/src/main/java/com/tfq/finances/main/Activity_Main.java @@ -0,0 +1,147 @@ +package com.tfq.finances.main; + +import android.os.Bundle; +import android.os.Handler; +import android.view.View; +import android.widget.ImageView; + +import androidx.fragment.app.Fragment; +import androidx.viewpager2.widget.ViewPager2; + +import com.google.gson.Gson; +import com.tfq.finances.jzrcj.R; +import com.tfq.finances.network.api.userinfo.UserInfoService; +import com.tfq.finances.network.config.ApiCallback; +import com.tfq.finances.network.model.response.userinfo.UserInfoData; +import com.tfq.finances.network.model.response.userinfo.UserInfoResp; +import com.tfq.library.adapter.MyFragmentStateAdapter; +import com.tfq.library.base.BaseActivity; +import com.tfq.finances.main.fragment.Fm_Page_A; +import com.tfq.finances.main.fragment.Fm_Page_B; +import com.tfq.finances.main.fragment.Fm_Page_S; +import com.tfq.library.utils.LogK; +import com.tfq.library.utils.ToasterUtil; + +import java.util.ArrayList; +import java.util.List; + +public class Activity_Main extends BaseActivity { + + ImageView ivLocal; + ImageView ivPrivate; + ImageView ivSetting; + ViewPager2 viewPager; + private boolean mOnPage = false; + public static UserInfoData memberUser; + + @Override + protected int getLayoutId() { + return R.layout.activity_main; + } + + @Override + protected void initView() { + ivLocal = findViewById(R.id.iv_page01); + ivLocal.setOnClickListener(this::onClick); + ivPrivate = findViewById(R.id.iv_page02); + ivPrivate.setOnClickListener(this::onClick); + ivSetting = findViewById(R.id.iv_page03); + ivSetting.setOnClickListener(this::onClick); + viewPager = findViewById(R.id.viewPager); + + List fragmentList = new ArrayList<>(); + fragmentList.add(new Fm_Page_A()); + fragmentList.add(new Fm_Page_B()); + fragmentList.add(new Fm_Page_S()); + + viewPager.setAdapter(new MyFragmentStateAdapter(getSupportFragmentManager(), getLifecycle(), fragmentList)); + viewPager.registerOnPageChangeCallback(PagerChangeListener()); + viewPager.setUserInputEnabled(false); + + } + + @Override + protected void initData(Bundle savedInstanceState) { + setStatusBarDarkFont(true); + toTime(); + + getUserInfo(); + } + + private void getUserInfo() { + new UserInfoService(this).getInfo(new ApiCallback() { + @Override + public void onSuccess(UserInfoResp response) { + memberUser = response.getMemberUser(); + LogK.e(new Gson().toJson(response)); + } + + @Override + public void onFailure(int errorCode, String errorMessage) { + ToasterUtil.show(errorMessage); + } + }); + } + + public void onClick(View view) { + int viewId = view.getId(); + + if (viewId == R.id.iv_page01 && !mOnPage) { + viewPager.setCurrentItem(0); + toTime(); + } else if (viewId == R.id.iv_page02 && !mOnPage) { + viewPager.setCurrentItem(1); + toTime(); + } else if (viewId == R.id.iv_page03 && !mOnPage) { + viewPager.setCurrentItem(2); + toTime(); + } + } + + + private void toTime() { + mOnPage = true; + new Handler().postDelayed(new Runnable() { + @Override + public void run() { + mOnPage = false; + } + }, 1100); + } + + public void changeViewPager(int CurrentViewPager) { + viewPager.setCurrentItem(CurrentViewPager); + } + + private ViewPager2.OnPageChangeCallback PagerChangeListener() { + return new ViewPager2.OnPageChangeCallback() { + @Override + public void onPageSelected(int position) { + super.onPageSelected(position); + switch (position) { + case 0: + ivLocal.setImageDrawable(getDrawable(R.mipmap.image_page1_2)); + ivPrivate.setImageDrawable(getDrawable(R.mipmap.image_page2_1)); + ivSetting.setImageDrawable(getDrawable(R.mipmap.image_page3_1)); + break; + case 1: + ivLocal.setImageDrawable(getDrawable(R.mipmap.image_page1_1)); + ivPrivate.setImageDrawable(getDrawable(R.mipmap.image_page2_2)); + ivSetting.setImageDrawable(getDrawable(R.mipmap.image_page3_1)); + break; + case 2: + ivLocal.setImageDrawable(getDrawable(R.mipmap.image_page1_1)); + ivPrivate.setImageDrawable(getDrawable(R.mipmap.image_page2_1)); + ivSetting.setImageDrawable(getDrawable(R.mipmap.image_page3_2)); + break; + } + } + }; + } + + @Override + public void onBackPressed() { + moveTaskToBack(true); + } + +} diff --git a/app/src/main/java/com/tfq/finances/main/Activity_Splash.java b/app/src/main/java/com/tfq/finances/main/Activity_Splash.java new file mode 100644 index 0000000..4d516b3 --- /dev/null +++ b/app/src/main/java/com/tfq/finances/main/Activity_Splash.java @@ -0,0 +1,276 @@ +package com.tfq.finances.main; + +import android.app.Activity; +import android.app.Dialog; +import android.content.Intent; +import android.content.SharedPreferences; +import android.graphics.Color; +import android.os.Bundle; +import android.os.Handler; +import android.text.Spannable; +import android.text.SpannableStringBuilder; +import android.text.TextPaint; +import android.text.method.LinkMovementMethod; +import android.text.style.ClickableSpan; +import android.text.style.ForegroundColorSpan; +import android.view.KeyEvent; +import android.view.View; +import android.widget.FrameLayout; +import android.widget.ImageView; +import android.widget.TextView; + +import com.tfq.ad.ad.AdSplashUtils; +import com.tfq.ad.ad.SplashUtils; +import com.tfq.ad.ad.TTAdManagerHolder; +import com.tfq.finances.jzrcj.R; +import com.tfq.finances.finances.activity.Activity_Login; +import com.tfq.finances.network.config.ApiCallback; +import com.tfq.finances.network.config.token.TokenManager; +import com.tfq.finances.network.model.response.adv.AdvFlagResp; +import com.tfq.library.app.BaseConstants; +import com.tfq.library.utils.AppUtil; +import com.tfq.library.utils.LogK; +import com.tfq.library.utils.SpManager; +import com.tfq.finances.network.api.adv.AdService; +import com.tfq.finances.core.enums.AdvFlagEnum; +import com.tfq.finances.core.constants.Constants; +import com.tfq.finances.app.App; +import com.tfq.finances.main.activity.Activity_Tw; +import com.tfq.finances.main.activity.Activity_WebView; + +import androidx.annotation.NonNull; + +public class Activity_Splash extends Activity { + + private final AdService adService = new AdService(App.getContext()); + + + FrameLayout frameLayout; + boolean onResume = true; + AdSplashUtils adSplashUtils; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + AppUtil.setHideBar_StatusAndNavigation(this); + setContentView(R.layout.activity_splash); + frameLayout = findViewById(R.id.frameLayout); + ImageView ivSplash = findViewById(R.id.iv_splash); + ivSplash.setBackgroundResource(App.getInstances().getAppSplash()); + run_start(); + } + + private void run_start() { + SharedPreferences sp = SpManager.startRead(this, Constants.SP_NAME); + boolean openNoFirst = sp.getBoolean("no_first_open", false); + new Handler().postDelayed(new Runnable() { + @Override + public void run() { + if (openNoFirst) {//没有点击过同意隐私政策 + toMain(); + } else { + startSecurity(); + } + } + }, 100); + } + + private void startSecurity() { + //TODO 显示提醒对话框 + Dialog securityDialog = new Dialog(this, R.style.CustomDialog); + securityDialog.setCancelable(false); + securityDialog.setCanceledOnTouchOutside(false); + View view = View.inflate(this, R.layout.dialog_activity_sercurity, null); + TextView tv_msg = view.findViewById(R.id.tv_sercurity); + ImageView iv_agree = view.findViewById(R.id.iv_agree); + ImageView iv_disagree = view.findViewById(R.id.iv_disagree); + SpannableStringBuilder spannable = new SpannableStringBuilder(tv_msg.getText()); + //《用户协议》和《隐私政策》 + int i = tv_msg.getText().toString().indexOf("《用户协议》"); + int i2 = tv_msg.getText().toString().indexOf("《隐私政策》"); + spannable.setSpan(new ForegroundColorSpan(Color.parseColor("#0C7CD8")), i, i + 6, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); + spannable.setSpan(new ForegroundColorSpan(Color.parseColor("#0C7CD8")), i2, i2 + 6, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); + tv_msg.setMovementMethod(LinkMovementMethod.getInstance()); + spannable.setSpan(new TextClick(), i, i + 6, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); + spannable.setSpan(new TextClick2(), i2, i2 + 6, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); + tv_msg.setText(spannable); + tv_msg.setHighlightColor(Color.parseColor("#00ffffff")); + iv_disagree.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + securityDialog.dismiss(); + finish(); + } + }); + iv_agree.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + securityDialog.dismiss(); + SharedPreferences.Editor editor = SpManager.startWrite(Activity_Splash.this, Constants.SP_NAME); + editor.putBoolean("no_first_open", true); + editor.commit(); + App.getInstances().agreeSercurity(); + + toMain(); + } + }); + + securityDialog.setContentView(view); + securityDialog.show(); + } + + private void toMain() { + adService.getAdvFlag(new ApiCallback() { + @Override + public void onSuccess(AdvFlagResp response) { + runOnUiThread(() -> { + try { + // 更新广告开关状态 + updateAdFlags(response); + + // 处理广告等待时间 + if (response.getAdv4Wait() > 0) { + BaseConstants.ADV_Wait = response.getAdv4Wait(); + } + + // 根据是否禁用广告决定后续流程 + if (getResources().getBoolean(R.bool.noAD)) { + toDmMain(500); + } else { + initAdManager(); + } + } catch (Exception e) { + e.printStackTrace(); + toDmMain(500); + } + }); + } + + @Override + public void onFailure(int errorCode, String errorMessage) { + toDmMain(0); + } + }); + } + + private void toDmMain(long delayMillis) { + runOnUiThread(new Runnable() { + @Override + public void run() { + new Handler().postDelayed(new Runnable() { + @Override + public void run() { + to_main(); + } + }, delayMillis); + } + }); + } + + + /** + * 更新广告开关状态 + */ + private void updateAdFlags(AdvFlagResp response) { + BaseConstants.AD_SPLASH = AdvFlagEnum.ON.getValue().equals(response.getAdv1Flag()); + BaseConstants.AD_REWARD = AdvFlagEnum.ON.getValue().equals(response.getAdv2Flag()); + BaseConstants.AD_NATIVE = AdvFlagEnum.ON.getValue().equals(response.getAdv3Flag()); + BaseConstants.AD_CQP = AdvFlagEnum.ON.getValue().equals(response.getAdv4Flag()); + BaseConstants.AD_BANNER = AdvFlagEnum.ON.getValue().equals(response.getAdv5Flag()); + BaseConstants.AD_DRAW = AdvFlagEnum.ON.getValue().equals(response.getAdv6Flag()); + + BaseConstants.AD_Switch_Requested = true; + } + + /** + * 初始化广告管理器 + */ + private void initAdManager() { + TTAdManagerHolder.getAdInitState(new TTAdManagerHolder.AdInitSuccess() { + @Override + public void success() { + runOnUiThread(() -> { + LogK.e("toad()"); + toAd(); + }); + } + @Override + public void error() { + runOnUiThread(() -> toDmMain(500)); + } + }); + } + + + private void to_main() { + TokenManager instance = TokenManager.getInstance(this); + boolean tokenValid = instance.isTokenValid(); + if (!tokenValid){ + startActivity(new Intent(Activity_Splash.this, Activity_Login.class)); + }else { + startActivity(new Intent(Activity_Splash.this, Activity_Main.class)); + } + finish(); + } + + @Override + protected void onResume() { + super.onResume(); + onResume = true; + } + + @Override + protected void onDestroy() { + super.onDestroy(); + onResume = false; + if (frameLayout != null) { + frameLayout.removeAllViews(); + } + } + + public boolean onKeyDown(int keyCode, KeyEvent event) { + if (keyCode == KeyEvent.KEYCODE_BACK) { + return true; + } + return super.onKeyDown(keyCode, event); + } + + private void toAd() { + adSplashUtils = new AdSplashUtils(this, frameLayout, new AdSplashUtils.Listener() { + @Override + public void success(long time) { + toDmMain(time); + } + }); + + new SplashUtils().startTimerTask(this); + } + + private class TextClick extends ClickableSpan { + @Override + public void onClick(View widget) { //在此处理点击事件 + Intent intent = new Intent(Activity_Splash.this, Activity_Tw.class); + startActivity(intent); + } + + @Override + public void updateDrawState(@NonNull TextPaint ds) { + ds.setColor(Color.parseColor("#0C7CD8")); + } + } + + private class TextClick2 extends ClickableSpan { + @Override + public void onClick(View widget) { //在此处理点击事件 + Intent intent = new Intent(Activity_Splash.this, Activity_WebView.class); + intent.putExtra("type", "assets"); + intent.putExtra("title", "隐私政策"); + startActivity(intent); + } + + @Override + public void updateDrawState(@NonNull TextPaint ds) { + ds.setColor(Color.parseColor("#0C7CD8")); + } + } +} diff --git a/app/src/main/java/com/tfq/finances/main/activity/Activity_About.java b/app/src/main/java/com/tfq/finances/main/activity/Activity_About.java new file mode 100644 index 0000000..f85d85b --- /dev/null +++ b/app/src/main/java/com/tfq/finances/main/activity/Activity_About.java @@ -0,0 +1,67 @@ +package com.tfq.finances.main.activity; + +import android.content.Intent; +import android.os.Bundle; +import android.view.View; +import android.widget.ImageView; +import android.widget.TextView; + +import com.tfq.finances.jzrcj.R; +import com.tfq.library.base.BaseActivity; +import com.tfq.library.utils.AppUtil; + +public class Activity_About extends BaseActivity { + private ImageView ivIcon; + private TextView tvAppName; + private TextView tvAppVer; + + @Override + protected int getLayoutId() { + return R.layout.activity_about; + } + + protected void initView() { + ImageView iv_back = findViewById(R.id.iv_back); + iv_back.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + finish(); + } + }); + TextView tv_title = findViewById(R.id.tv_title); + tv_title.setText("关于我们"); + + ivIcon = findViewById(R.id.iv_icon); + tvAppName = findViewById(R.id.tv_app_name); + tvAppVer = findViewById(R.id.tv_app_ver); + TextView tv_code = findViewById(R.id.tv_code); + TextView tv_url = findViewById(R.id.tv_url); + String record_code = getResources().getString(R.string.APP_RECORD_CODE); + String record_url = getResources().getString(R.string.APP_RECORD_URL); + tv_code.setText("APP备案: " + record_code); + tv_url.setText(record_url); + tv_url.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + Intent intent = new Intent(Activity_About.this, Activity_WebView.class); + intent.putExtra("type", "beian"); + intent.putExtra("title", "工信部备案查询"); + intent.putExtra("s_url", record_url); + startActivity(intent); + } + }); + } + + @Override + protected void initData(Bundle savedInstanceState) { + setStatusBarDarkFont(true); + String imgname = getResources().getString(R.string.app_logo); +// String iname = imgname.substring(imgname.indexOf("logo_")); +// String i_name = iname.substring(0, iname.indexOf(".")); +// int imgid = getResources().getIdentifier(i_name, "drawable", getPackageName()); +// ivIcon.setImageDrawable(getDrawable(imgid)); + tvAppName.setText(AppUtil.getAppName(this)); + tvAppVer.setText("版本信息: v" + AppUtil.getAppVersionName(this)); + } + +} diff --git a/app/src/main/java/com/tfq/finances/main/activity/Activity_About_Us.java b/app/src/main/java/com/tfq/finances/main/activity/Activity_About_Us.java new file mode 100644 index 0000000..a081eaf --- /dev/null +++ b/app/src/main/java/com/tfq/finances/main/activity/Activity_About_Us.java @@ -0,0 +1,138 @@ +package com.tfq.finances.main.activity; + +import android.content.Intent; +import android.content.SharedPreferences; +import android.net.Uri; +import android.os.Bundle; +import android.view.View; +import android.widget.FrameLayout; +import android.widget.ImageView; +import android.widget.LinearLayout; +import android.widget.TextView; + +import com.tfq.ad.ad.AdFeedUtils; +import com.tfq.finances.jzrcj.R; +import com.tfq.library.app.BaseConstants; +import com.tfq.library.base.BaseActivity; +import com.tfq.library.utils.AppUtil; +import com.tfq.library.utils.LogK; +import com.tfq.library.utils.SpManager; +import com.tfq.library.view.AuthDialog; +import com.tfq.finances.core.constants.Constants; +import com.tfq.finances.app.App; +import com.tfq.finances.main.Activity_Splash; + +public class Activity_About_Us extends BaseActivity { + + private FrameLayout flContent; + private TextView tv_beian; + + @Override + protected int getLayoutId() { + return R.layout.activity_about_us_layout; + } + + protected void initView() { + ImageView iv_back = findViewById(R.id.iv_back); + iv_back.setOnClickListener(this::onClick); + LinearLayout ll02 = findViewById(R.id.ll_02); + ll02.setOnClickListener(this::onClick); + LinearLayout ll_03 = findViewById(R.id.ll_03); + ll_03.setOnClickListener(this::onClick); + LinearLayout ll04 = findViewById(R.id.ll_04); + ll04.setOnClickListener(this::onClick); + LinearLayout ll05 = findViewById(R.id.ll_05); + ll05.setOnClickListener(this::onClick); + LinearLayout ll06 = findViewById(R.id.ll_06); + ll06.setOnClickListener(this::onClick); + tv_beian = findViewById(R.id.tv_beian); + tv_beian.setOnClickListener(this::onClick); + flContent = findViewById(R.id.fl_content); + + TextView tv_app_ver = findViewById(R.id.tv_app_ver); + tv_app_ver.setText("v" + AppUtil.getAppVersionName(this)); + } + + @Override + protected void initData(Bundle savedInstanceState) { + setStatusBarDarkFont(true); + String record_code = getResources().getString(R.string.APP_RECORD_CODE); + LogK.e("record_code=" + record_code); + tv_beian.setText(record_code); + } + + @Override + public void onResume() { + super.onResume(); + AdFeedUtils.show_ad(this, BaseConstants.CODE_AD_FEED1, flContent, 20, "page_about_us", 2); + } + + @Override + public void onPause() { + super.onPause(); + AdFeedUtils.cancelTag("page_about_us"); + } + + public void onClick(View view) { + Intent intent; + int viewId = view.getId(); + + if (viewId == R.id.iv_back) { + finish(); + } else if (viewId == R.id.ll_01) { + intent = new Intent(this, Activity_PbFeedback.class); + startActivity(intent); + } else if (viewId == R.id.ll_02) { + intent = new Intent(this, Activity_Tw.class); + startActivity(intent); + } else if (viewId == R.id.ll_03) { + String record_url = getResources().getString(R.string.APP_RECORD_URL); + intent = new Intent(Activity_About_Us.this, Activity_WebView.class); + intent.putExtra("type", "beian"); + intent.putExtra("title", "工信部备案查询"); + intent.putExtra("s_url", record_url); + startActivity(intent); + } else if (viewId == R.id.ll_04) { + intent = new Intent(this, Activity_WebView.class); + intent.putExtra("type", "assets"); + intent.putExtra("title", "隐私政策"); + startActivity(intent); + } else if (viewId == R.id.ll_05) { + new AuthDialog(this, "privacy", new AuthDialog.Listener() { + @Override + public void callBack() { + Intent intent = new Intent(); + intent.setAction("android.settings.APPLICATION_DETAILS_SETTINGS"); + intent.setData(Uri.fromParts("package", getPackageName(), null)); + startActivity(intent); + } + }).show(); + } else if (viewId == R.id.ll_06) { + recallAuthorization(); + } + } + + + private void recallAuthorization() { + new AuthDialog(this, "authorizatio", new AuthDialog.Listener() { + @Override + public void callBack() { + BaseConstants.AD_Switch_Requested = false; + SharedPreferences.Editor editor = SpManager.startWrite(Activity_About_Us.this, Constants.SP_NAME); + editor.putBoolean("no_first_open", false); + editor.commit(); + toSplash(); + if (App.getChannel().equals("oppo")) { + System.exit(0); + } + } + }).show(); + } + + private void toSplash() { + Intent intent = new Intent(this, Activity_Splash.class); + startActivity(intent); + finish(); + } + +} diff --git a/app/src/main/java/com/tfq/finances/main/activity/Activity_Export.java b/app/src/main/java/com/tfq/finances/main/activity/Activity_Export.java new file mode 100644 index 0000000..a98472c --- /dev/null +++ b/app/src/main/java/com/tfq/finances/main/activity/Activity_Export.java @@ -0,0 +1,226 @@ +package com.tfq.finances.main.activity; + +import android.os.Bundle; +import android.text.TextUtils; +import android.util.Patterns; +import android.view.View; +import android.widget.EditText; +import android.widget.ImageView; +import android.widget.RelativeLayout; +import android.widget.TextView; + +import com.github.gzuliyujiang.wheelpicker.annotation.DateMode; +import com.hjq.toast.ToastParams; +import com.hjq.toast.Toaster; +import com.hjq.toast.style.CustomToastStyle; +import com.tfq.ad.ad.AdCQPUtils; +import com.tfq.ad.ad.AdRewardUtils; +import com.tfq.finances.jzrcj.R; +import com.tfq.finances.network.api.finances.TransactionsService; +import com.tfq.finances.network.config.ApiCallback; +import com.tfq.finances.utils.DateCalculator; +import com.tfq.finances.utils.androidpicker.CalculateUtils; +import com.tfq.library.base.BaseActivity; +import com.tfq.library.utils.NetworkUtils; +import com.tfq.library.utils.PermissionDialog; +import com.tfq.library.utils.ToasterUtil; + +import androidx.annotation.Nullable; + +public class Activity_Export extends BaseActivity { + + + // 常量 + private static final String FEEDBACK_TITLE = "数据导出"; + private static final String EMPTY_START_MESSAGE = "请选择开始日期"; + private static final String EMPTY_END_MESSAGE = "请选择结束日期"; + private static final String EMPTY_MAIL_MESSAGE = "邮箱地址不能为空"; + private static final String INVALID_MAIL_MESSAGE = "您填写的邮箱地址不正确"; + private static final String SUCCESS_MESSAGE = "数据导出成功"; + /** + * 导出 + */ + private final TransactionsService transactionsService = new TransactionsService(this); + // UI组件 + private ImageView ivBack; + private TextView tvTitle; + private RelativeLayout rl_export; + private EditText et_email; + private TextView tv_timestart; + private TextView tv_timeend; + private CalculateUtils calculateUtils; + + @Override + protected void onCreate(@Nullable Bundle savedInstanceState) { + setStatusBarDarkFont(true); + super.onCreate(savedInstanceState); + } + + @Override + protected int getLayoutId() { + return R.layout.activity_export_layout; + } + + @Override + protected void initView() { + findViews(); + setupViews(); + } + + private void findViews() { + ivBack = findViewById(R.id.iv_back); + tvTitle = findViewById(R.id.tv_title); + rl_export = findViewById(R.id.rl_export); + et_email = findViewById(R.id.et_email); + tv_timestart = findViewById(R.id.tv_timestart); + tv_timeend = findViewById(R.id.tv_timeend); + } + + private void setupViews() { + ivBack.setOnClickListener(this::onBackClicked); + tv_timestart.setOnClickListener(this::onTimeStartClicked); + tv_timeend.setOnClickListener(this::onTimeEndClicked); + rl_export.setOnClickListener(this::onExportClicked); + } + + @Override + protected void initData(Bundle savedInstanceState) { + tvTitle.setText(FEEDBACK_TITLE); + + // 初始化 + calculateUtils = new CalculateUtils(this); + calculateUtils.yearMonthDay(DateMode.YEAR_MONTH_DAY); + + initializeAd(); + } + + @Override + protected void onResume() { + super.onResume(); + } + + private void initializeAd() { + new AdCQPUtils().init(this); + } + + public void onClick(View view) { + // 保留这个方法以满足可能的其他点击需求 + } + + private void onBackClicked(View view) { + finish(); + } + + private void onTimeStartClicked(View view) { + calculateUtils.choose_date(tv_timestart, ""); + calculateUtils.setListener(new CalculateUtils.Listener() { + @Override + public void success(String date) { + DateCalculator.DatePair datePair = DateCalculator.calculateMonthDates(date); + String nextMonth = datePair.nextMonth; + tv_timeend.setText(DateCalculator.isAfterToday(nextMonth) ? DateCalculator.getDefDate() : nextMonth); + } + }); + } + + private void onTimeEndClicked(View view) { + calculateUtils.choose_date(tv_timeend, ""); + calculateUtils.setListener(new CalculateUtils.Listener() { + @Override + public void success(String date) { + DateCalculator.DatePair datePair = DateCalculator.calculateMonthDates(date); + String previousMonth = datePair.previousMonth; + tv_timestart.setText(previousMonth); + } + }); + } + + private void onExportClicked(View view) { + submitFeedback(); + } + + private void submitFeedback() { + String email = et_email.getText().toString().trim(); + String start = tv_timestart.getText().toString().trim(); + String end = tv_timeend.getText().toString().trim(); + + if (!validateInput(start, end, email)) { + return; + } + + if (NetworkUtils.isConnectedAvailableNetwork(this)) { + new PermissionDialog(this, "export_data", "导出数据", "完整观看视频后方可导出数据", new PermissionDialog.Listener() { + @Override + public void success() { + new AdRewardUtils(Activity_Export.this, new AdRewardUtils.Listener() { + @Override + public void success(boolean b) { + exportDataToEmail(start, end, email); + } + }); + } + }).show(); + } else { + ToasterUtil.show("请先连接网络", 3); + } + + } + + private void exportDataToEmail(String start, String end, String email) { + transactionsService.exportTransactions(start, end, email, new ApiCallback() { + @Override + public void onSuccess(Boolean response) { + showSuccessAndFinish(); + } + + @Override + public void onFailure(int errorCode, String errorMessage) { + showErrorToast(errorMessage); + } + }); + } + + private boolean validateInput(String start, String end, String email) { + if (TextUtils.isEmpty(start)) { + showToast(EMPTY_START_MESSAGE); + return false; + } + + if (TextUtils.isEmpty(end)) { + showToast(EMPTY_END_MESSAGE); + return false; + } + + if (TextUtils.isEmpty(email)) { + showToast(EMPTY_MAIL_MESSAGE); + return false; + } + + if (!isValidEmail(email)) { + showToast(INVALID_MAIL_MESSAGE); + return false; + } + + return true; + } + + private void showSuccessAndFinish() { + ToasterUtil.show(SUCCESS_MESSAGE, 1); +// finish(); + } + + private void showErrorToast(String message) { + ToastParams params = new ToastParams(); + params.text = message; + params.style = new CustomToastStyle(com.tfq.library.R.layout.toast_error); + Toaster.show(params); + } + + private void showToast(String message) { + ToasterUtil.show(message, 3); + } + + public boolean isValidEmail(CharSequence target) { + return target != null && Patterns.EMAIL_ADDRESS.matcher(target).matches(); + } +} diff --git a/app/src/main/java/com/tfq/finances/main/activity/Activity_Font.java b/app/src/main/java/com/tfq/finances/main/activity/Activity_Font.java new file mode 100644 index 0000000..5a3600d --- /dev/null +++ b/app/src/main/java/com/tfq/finances/main/activity/Activity_Font.java @@ -0,0 +1,187 @@ +package com.tfq.finances.main.activity; + +import android.Manifest; +import android.content.Intent; +import android.net.Uri; +import android.os.Build; +import android.os.Bundle; +import android.provider.Settings; +import android.view.View; +import android.widget.ImageView; +import android.widget.LinearLayout; +import android.widget.TextView; + +import com.hjq.permissions.XXPermissions; +import com.tfq.finances.jzrcj.R; +import com.tfq.library.base.BaseActivity; +import com.tfq.library.utils.LogK; +import com.tfq.library.utils.ToasterUtil; + +import java.util.ArrayList; +import java.util.List; + +public class Activity_Font extends BaseActivity { + + private LinearLayout ll_perssion; + + private List mMenuScales = new ArrayList<>(); + private float scale = 1.00f; + + @Override + protected int getLayoutId() { + return R.layout.activity_font; + } + + protected void initView() { + ImageView iv_back = findViewById(R.id.iv_back); + iv_back.setOnClickListener(this::onClick); + TextView tv_title = findViewById(R.id.tv_title); + tv_title.setText("系统字体大小调节"); + + View menu_scale_1 = findViewById(R.id.menu_scale_1); + View menu_scale_2 = findViewById(R.id.menu_scale_2); + View menu_scale_3 = findViewById(R.id.menu_scale_3); + View menu_scale_4 = findViewById(R.id.menu_scale_4); + View menu_scale_5 = findViewById(R.id.menu_scale_5); + menu_scale_1.setOnClickListener(this::onClick); + menu_scale_2.setOnClickListener(this::onClick); + menu_scale_3.setOnClickListener(this::onClick); + menu_scale_4.setOnClickListener(this::onClick); + menu_scale_5.setOnClickListener(this::onClick); + mMenuScales.add(menu_scale_1); + mMenuScales.add(menu_scale_2); + mMenuScales.add(menu_scale_3); + mMenuScales.add(menu_scale_4); + mMenuScales.add(menu_scale_5); + + View tv_reset = findViewById(R.id.tv_reset); + tv_reset.setOnClickListener(this::onClick); + View tv_update = findViewById(R.id.tv_update); + tv_update.setOnClickListener(this::onClick); + + ll_perssion = findViewById(R.id.ll_perssion); + ll_perssion.setOnClickListener(this::onClick); + } + + @Override + protected void initData(Bundle savedInstanceState) { + setStatusBarDarkFont(true); + float fontSize = Settings.System.getFloat(getContentResolver(), Settings.System.FONT_SCALE, 1.0f); + LogK.e("fontSize=" + fontSize); + if (fontSize == 0.70f) { + selectedScaleIndex(0); + } else if (fontSize == 0.85f) { + selectedScaleIndex(1); + } else if (fontSize == 1.00f) { + selectedScaleIndex(2); + } else if (fontSize == 1.15f) { + selectedScaleIndex(3); + } else if (fontSize == 1.30f) { + selectedScaleIndex(4); + } + + boolean granted = XXPermissions.isGranted(this, new String[]{Manifest.permission.WRITE_SETTINGS}); + if (!granted) { + ll_perssion.setVisibility(View.VISIBLE); + } + } + + @Override + protected void onResume() { + super.onResume(); + } + + private void onClick(View view) { + boolean granted = XXPermissions.isGranted(this, new String[]{Manifest.permission.WRITE_SETTINGS}); + int viewId = view.getId(); + + if (viewId == R.id.iv_back) { + finish(); + } else if (viewId == R.id.ll_perssion) { + try { + Intent intent = new Intent(Settings.ACTION_MANAGE_WRITE_SETTINGS); + intent.setData(Uri.parse("package:" + getPackageName())); + startActivityForResult(intent, 1001); + } catch (Exception e) { + e.printStackTrace(); + } + } else if (viewId == R.id.menu_scale_1) { + selectedScaleIndex(0); + preFontScale(0.70f); + } else if (viewId == R.id.menu_scale_2) { + selectedScaleIndex(1); + preFontScale(0.85f); + } else if (viewId == R.id.menu_scale_3) { + selectedScaleIndex(2); + preFontScale(1.00f); + } else if (viewId == R.id.menu_scale_4) { + selectedScaleIndex(3); + preFontScale(1.15f); + } else if (viewId == R.id.menu_scale_5) { + selectedScaleIndex(4); + preFontScale(1.30f); + } else if (viewId == R.id.tv_reset) { + if (!granted) { + ToasterUtil.show("请先获取权限后使用"); + } else { + selectedScaleIndex(2); + updateFontScale(1.00f); + } + } else if (viewId == R.id.tv_update) { + if (!granted) { + ToasterUtil.show("请先获取权限后使用"); + } else { + updateFontScale(scale); + } + } + } + + @Override + protected void onActivityResult(int requestCode, int resultCode, Intent data) { + super.onActivityResult(requestCode, resultCode, data); + if (requestCode == 1001) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { + if (Settings.System.canWrite(this)) { + // 用户已授权,可执行系统设置修改操作 + LogK.e("用户已授权,可执行系统设置修改操作"); + ToasterUtil.show("您已获取授权"); + ll_perssion.setVisibility(View.GONE); + } else { + // 用户未授权,需提示或降级处理 + LogK.e("用户未授权,需提示或降级处理"); + ToasterUtil.show("您已经取消授权"); + } + } + } + } + + /** + * 选中 + * + * @param index + */ + private void selectedScaleIndex(int index) { + if (null != mMenuScales && mMenuScales.size() > 0) { + for (View view : mMenuScales) { + view.setSelected(false); + } + if (mMenuScales.size() > index) { + mMenuScales.get(index).setSelected(true); + } + } + } + + public void preFontScale(float scale) { + this.scale = scale; + } + + public void updateFontScale(float scale) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { + if (Settings.System.canWrite(this)) { + // 授权成功,修改系统设置(如字体大小) + Settings.System.putFloat(getContentResolver(), Settings.System.FONT_SCALE, scale); + } + } + } + +} diff --git a/app/src/main/java/com/tfq/finances/main/activity/Activity_PbFeedback.java b/app/src/main/java/com/tfq/finances/main/activity/Activity_PbFeedback.java new file mode 100644 index 0000000..248eeb1 --- /dev/null +++ b/app/src/main/java/com/tfq/finances/main/activity/Activity_PbFeedback.java @@ -0,0 +1,161 @@ +package com.tfq.finances.main.activity; + +import android.os.Bundle; +import android.text.TextUtils; +import android.view.View; +import android.widget.EditText; +import android.widget.ImageView; +import android.widget.RelativeLayout; +import android.widget.TextView; + +import androidx.annotation.Nullable; + +import com.hjq.toast.ToastParams; +import com.hjq.toast.Toaster; +import com.hjq.toast.style.CustomToastStyle; +import com.tfq.ad.ad.AdCQPUtils; +import com.tfq.finances.network.config.ApiCallback; +import com.tfq.library.base.BaseActivity; +import com.tfq.library.utils.AppUtil; +import com.tfq.library.utils.ToasterUtil; +import com.tfq.finances.network.api.feedback.FeedbackService; +import com.tfq.finances.app.App; +import com.tfq.finances.jzrcj.R; + +public class Activity_PbFeedback extends BaseActivity { + + + /** + * 提交反馈 + */ + private final FeedbackService feedbackService = new FeedbackService(this); + + // UI组件 + private ImageView ivBack; + private TextView tvTitle; + private RelativeLayout btnCommit; + private EditText etProblem; + private EditText etTel; + + // 常量 + private static final String FEEDBACK_TITLE = "在线反馈"; + private static final String EMPTY_PROBLEM_MESSAGE = "提交的内容不能为空"; + private static final String EMPTY_PHONE_MESSAGE = "手机号不能为空"; + private static final String INVALID_PHONE_MESSAGE = "您填写的手机号码不正确"; + private static final String SUCCESS_MESSAGE = "反馈提交成功"; + + @Override + protected void onCreate(@Nullable Bundle savedInstanceState) { + setStatusBarDarkFont(true); + super.onCreate(savedInstanceState); + } + + @Override + protected int getLayoutId() { + return R.layout.activity_suggest_layout; + } + + @Override + protected void initView() { + findViews(); + setupViews(); + } + + private void findViews() { + ivBack = findViewById(R.id.iv_back); + tvTitle = findViewById(R.id.tv_title); + btnCommit = findViewById(R.id.btn_commit); + etProblem = findViewById(R.id.et_problem); + etTel = findViewById(R.id.et_tel); + } + + private void setupViews() { + ivBack.setOnClickListener(this::onBackClicked); + btnCommit.setOnClickListener(this::onCommitClicked); + etProblem.setOnClickListener(v -> etProblem.requestFocus()); + } + + @Override + protected void initData(Bundle savedInstanceState) { + tvTitle.setText(FEEDBACK_TITLE); + } + + @Override + protected void onResume() { + super.onResume(); + initializeAd(); + } + + private void initializeAd() { + new AdCQPUtils().init(this); + } + + public void onClick(View view) { + // 保留这个方法以满足可能的其他点击需求 + } + + private void onBackClicked(View view) { + finish(); + } + + private void onCommitClicked(View view) { + submitFeedback(); + } + + private void submitFeedback() { + String problem = etProblem.getText().toString().trim(); + String phone = etTel.getText().toString().trim(); + + if (!validateInput(problem, phone)) { + return; + } + + feedbackService.submitFeedback(problem, phone, new ApiCallback() { + @Override + public void onSuccess(String response) { + showSuccessAndFinish(); + } + + @Override + public void onFailure(int errorCode, String errorMessage) { + showErrorToast(errorMessage); + } + }); + } + + private boolean validateInput(String problem, String phone) { + if (TextUtils.isEmpty(problem)) { + showToast(EMPTY_PROBLEM_MESSAGE); + return false; + } + + if (TextUtils.isEmpty(phone)) { + showToast(EMPTY_PHONE_MESSAGE); + return false; + } + + if (!AppUtil.regexPhoneNum(phone)) { + showToast(INVALID_PHONE_MESSAGE); + return false; + } + + return true; + } + + private void showSuccessAndFinish() { + ToasterUtil.show(SUCCESS_MESSAGE, 1); + finish(); + } + + private void showErrorToast(String message) { + ToastParams params = new ToastParams(); + params.text = message; + params.style = new CustomToastStyle(com.tfq.library.R.layout.toast_error); + Toaster.show(params); + } + + private void showToast(String message) { + ToasterUtil.show(message, 3); + } + +} diff --git a/app/src/main/java/com/tfq/finances/main/activity/Activity_Tw.java b/app/src/main/java/com/tfq/finances/main/activity/Activity_Tw.java new file mode 100644 index 0000000..959ba42 --- /dev/null +++ b/app/src/main/java/com/tfq/finances/main/activity/Activity_Tw.java @@ -0,0 +1,50 @@ +package com.tfq.finances.main.activity; + +import android.content.Context; +import android.os.Bundle; +import android.view.View; +import android.widget.ImageView; +import android.widget.TextView; + +import com.tfq.finances.jzrcj.R; +import com.tfq.library.base.BaseActivity; + +public class Activity_Tw extends BaseActivity { + ImageView ivBack; + TextView tvTitle; + TextView textview; + + @Override + protected int getLayoutId() { + return R.layout.activity_teamwork_layout; + } + + @Override + protected void initView() { + ivBack = findViewById(R.id.iv_back); + ivBack.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + finish(); + } + }); + tvTitle = findViewById(R.id.tv_title); + textview = findViewById(R.id.textview); + tvTitle.setText("用户协议"); + } + + @Override + protected void initData(Bundle savedInstanceState) { + setStatusBarDarkFont(true); + String s = textview.getText().toString(); + String replaceAll = s.replaceAll("APP_NAME", getResources().getString(R.string.app_name)) + .replaceAll("APP_COMPANY", getCompanyName(this)); + textview.setText(replaceAll); + } + + public String getCompanyName(Context mContext) { + String s = mContext.getResources().getString(R.string.company_name); + return s; + } + +} diff --git a/app/src/main/java/com/tfq/finances/main/activity/Activity_WebView.java b/app/src/main/java/com/tfq/finances/main/activity/Activity_WebView.java new file mode 100644 index 0000000..c603dbf --- /dev/null +++ b/app/src/main/java/com/tfq/finances/main/activity/Activity_WebView.java @@ -0,0 +1,160 @@ +package com.tfq.finances.main.activity; + +import android.graphics.Bitmap; +import android.net.http.SslError; +import android.os.Bundle; +import android.text.TextUtils; +import android.view.ContextMenu; +import android.view.MotionEvent; +import android.view.View; +import android.webkit.SslErrorHandler; +import android.webkit.WebSettings; +import android.webkit.WebView; +import android.webkit.WebViewClient; +import android.widget.ImageView; +import android.widget.RelativeLayout; +import android.widget.TextView; + +import com.tfq.library.base.BaseActivity; +import com.tfq.library.utils.LogK; +import com.tfq.finances.core.utils.PolicyUtils; +import com.tfq.finances.jzrcj.R; +public class Activity_WebView extends BaseActivity { + + WebView webView; + ImageView ivBack; + TextView tvTitle; + ImageView ivError; + RelativeLayout rlError; + + @Override + protected int getLayoutId() { + return R.layout.activity_webview; + } + + @Override + protected void initView() { + webView = findViewById(R.id.webView); + ivBack = findViewById(R.id.iv_back); + ivBack.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + finish(); + } + }); + tvTitle = findViewById(R.id.tv_title); + ivError = findViewById(R.id.iv_error); + rlError = findViewById(R.id.rl_error); + } + + @Override + protected void initData(Bundle savedInstanceState) { + setStatusBarDarkFont(true); + String s_url = getIntent().getStringExtra("s_url"); + String type = getIntent().getStringExtra("type"); + String title = getIntent().getStringExtra("title"); + if (!TextUtils.isEmpty(type) && ("assets".equals(type) || "protocol_share".equals(type))) { + tvTitle.setText(title); + initWebView_assets(s_url, type); + } else if (!TextUtils.isEmpty(type) && "beian".equals(type)) { + tvTitle.setText(title); + initWebView(s_url); + } else { + tvTitle.setText("在线播放"); + if (!TextUtils.isEmpty(s_url)) { + initWebView(s_url); + } + } + } + + private void initWebView_assets(String s_url, String type) { + try { + String url = s_url; + if ("assets".equals(type)) { + url = PolicyUtils.getPrivacyPolicyUrl(); + } + LogK.e("url=" + url); + webView.getSettings().setJavaScriptEnabled(true); + webView.getSettings().setAllowFileAccess(true); + webView.loadUrl(url); + } catch (Exception e) { + e.printStackTrace(); + } + } + + private void initWebView(String s_url) { + String url = s_url; + //访问网页 + WebSettings settings = webView.getSettings(); + settings.setJavaScriptEnabled(true); + settings.setDomStorageEnabled(true); + settings.setLoadWithOverviewMode(true); + settings.setUseWideViewPort(true); + settings.setSupportZoom(true); + settings.setTextZoom(100); + settings.setBuiltInZoomControls(true); + settings.setJavaScriptCanOpenWindowsAutomatically(true); + webView.setWebViewClient(new WebViewClient() { + @Override + public boolean shouldOverrideUrlLoading(WebView view, String url) { + return super.shouldOverrideUrlLoading(view, url); + } + + @Override + public void onPageStarted(WebView view, String url, Bitmap favicon) { + + super.onPageStarted(view, url, favicon); + } + + @Override + public void onLoadResource(WebView view, String url) { + super.onLoadResource(view, url); + } + + @Override + public void onReceivedLoginRequest(WebView view, String realm, String account, String args) { + super.onReceivedLoginRequest(view, realm, account, args); + } + + @Override + public void onPageFinished(WebView view, String url) { + + super.onPageFinished(view, url); + } + + @Override + public void onReceivedError(WebView view, int errorCode, String description, String failingUrl) { + super.onReceivedError(view, errorCode, description, failingUrl); + LogK.e("播放失败!"); + rlError.setVisibility(View.VISIBLE); + } + + @Override + public void onReceivedSslError(WebView view, SslErrorHandler handler, SslError error) { + + super.onReceivedSslError(view, handler, error); + } + }); + + webView.setOnGenericMotionListener(new View.OnGenericMotionListener() { + @Override + public boolean onGenericMotion(View view, MotionEvent motionEvent) { + + return false; + } + }); + webView.setOnCreateContextMenuListener(new View.OnCreateContextMenuListener() { + @Override + public void onCreateContextMenu(ContextMenu contextMenu, View view, ContextMenu.ContextMenuInfo contextMenuInfo) { + + } + }); + webView.loadUrl(url); + } + + @Override + protected void onDestroy() { + super.onDestroy(); + webView.destroy(); + } +} diff --git a/app/src/main/java/com/tfq/finances/main/adapter/IndexBillAdapter.java b/app/src/main/java/com/tfq/finances/main/adapter/IndexBillAdapter.java new file mode 100644 index 0000000..c10bb8e --- /dev/null +++ b/app/src/main/java/com/tfq/finances/main/adapter/IndexBillAdapter.java @@ -0,0 +1,59 @@ +package com.tfq.finances.main.adapter; + +import android.view.View; +import android.widget.LinearLayout; + +import com.chad.library.adapter.base.BaseQuickAdapter; +import com.chad.library.adapter.base.BaseViewHolder; +import com.tfq.finances.core.enums.CommonTypeOriginEnum; +import com.tfq.finances.core.enums.SystemExpensesCategoriesEnum; +import com.tfq.finances.core.enums.SystemIncomeCategoriesEnum; +import com.tfq.finances.jzrcj.R; +import com.tfq.finances.network.model.response.finances.transactions.TransactionsResp; +import com.tfq.finances.utils.androidpicker.CalculateUtils; +import com.tfq.library.utils.LogK; + +public class IndexBillAdapter extends BaseQuickAdapter { + + public IndexBillAdapter(int layoutID) { + super(layoutID); + } + + @Override + protected void convert(BaseViewHolder helper, TransactionsResp entity) { + LinearLayout ll_title = helper.getView(R.id.ll_title); + View view_short = helper.getView(R.id.view_short); + View view_long = helper.getView(R.id.view_long); + if (Boolean.TRUE.equals(entity.isShowTitle())) { + ll_title.setVisibility(View.VISIBLE); + view_short.setVisibility(View.GONE); + view_long.setVisibility(View.VISIBLE); + } else { + ll_title.setVisibility(View.GONE); + view_short.setVisibility(View.VISIBLE); + view_long.setVisibility(View.GONE); + } + helper.setText(R.id.tv_title, CalculateUtils.convertToChineseFormat(entity.getTransactionDate())); + helper.setText(R.id.tv_week, CalculateUtils.getWeekDay(entity.getUserDesc())); + //此处是标签名称 + helper.setText(R.id.tv_categoryName, entity.getCategoryName()); + + if (entity.getType() == 1) { + helper.setText(R.id.tv_amount, "-" + entity.getAmount()); + } else { + helper.setText(R.id.tv_amount, entity.getAmount().toString()); + } + + int iconChecked = SystemExpensesCategoriesEnum.getCheckedIconById(entity.getCategoryId()); + if (iconChecked==-1){ + iconChecked = SystemIncomeCategoriesEnum.getCheckedIconById(entity.getCategoryId()); + } + LogK.e("iconChecked="+iconChecked); + if (iconChecked > 0) { + helper.setImageResource(R.id.image_view, iconChecked); + } else { + helper.setImageResource(R.id.image_view, R.mipmap.ic_zdy_checked_text); + } + + } +} \ No newline at end of file diff --git a/app/src/main/java/com/tfq/finances/main/adapter/TypeOriginAdapter.java b/app/src/main/java/com/tfq/finances/main/adapter/TypeOriginAdapter.java new file mode 100644 index 0000000..7101a4e --- /dev/null +++ b/app/src/main/java/com/tfq/finances/main/adapter/TypeOriginAdapter.java @@ -0,0 +1,59 @@ +package com.tfq.finances.main.adapter; + +import com.chad.library.adapter.base.BaseQuickAdapter; +import com.chad.library.adapter.base.BaseViewHolder; +import com.tfq.finances.core.enums.CommonTypeOriginEnum; +import com.tfq.finances.jzrcj.R; +import com.tfq.finances.utils.animation.AnimationClick; +import com.tfq.finances.main.model.TypeOriginConfig; + +public class TypeOriginAdapter extends BaseQuickAdapter { + + public TypeOriginAdapter(int layoutID) { + super(layoutID); + } + + @Override + protected void convert(BaseViewHolder helper, TypeOriginConfig entity) { + /*LinearLayout ll_title = helper.getView(R.id.ll_title); + View view_short = helper.getView(R.id.view_short); + View view_long = helper.getView(R.id.view_long); + if (entity.isShowTitle()) { + ll_title.setVisibility(View.VISIBLE); + view_short.setVisibility(View.GONE); + view_long.setVisibility(View.VISIBLE); + } else { + ll_title.setVisibility(View.GONE); + view_short.setVisibility(View.VISIBLE); + view_long.setVisibility(View.GONE); + } + helper.setText(R.id.tv_title, CalculateUtils.convertToChineseFormat(entity.getTransactionDate())); + helper.setText(R.id.tv_week, CalculateUtils.getWeekDay(entity.getUserDesc())); + helper.setText(R.id.tv_userDesc, entity.getUserDesc()); + + if (entity.getType() == 1) { + helper.setText(R.id.tv_amount, "-" + entity.getAmount()); + } else { + helper.setText(R.id.tv_amount, entity.getAmount().toString()); + }*/ + + helper.setText(R.id.tv_zdy_name, entity.getTypeOriginName()); + if (entity.getType().equals(CommonTypeOriginEnum.SYSTEM.getName())) { + helper.setVisible(R.id.iv_iamge, true); + helper.setGone(R.id.ll_zdy, false); + } else { + helper.setGone(R.id.iv_iamge, false); + helper.setVisible(R.id.ll_zdy, true); + } + if (entity.getItemCheck()) { + helper.setImageResource(R.id.iv_iamge, entity.getTypeOriginIdChecked()); + helper.setImageResource(R.id.iv_iamge_zdy, entity.getTypeOriginIdChecked()); + AnimationClick.startScaleAnimation(helper.getView(R.id.relativeLayout)); + } else { + helper.setImageResource(R.id.iv_iamge, entity.getTypeOriginIdCheck()); + helper.setImageResource(R.id.iv_iamge_zdy, entity.getTypeOriginIdCheck()); +// AnimationClick.startScaleAnimation(helper.getView(R.id.iv_iamge)); + } + + } +} \ No newline at end of file diff --git a/app/src/main/java/com/tfq/finances/main/fragment/Fm_Page_A.java b/app/src/main/java/com/tfq/finances/main/fragment/Fm_Page_A.java new file mode 100644 index 0000000..d681347 --- /dev/null +++ b/app/src/main/java/com/tfq/finances/main/fragment/Fm_Page_A.java @@ -0,0 +1,252 @@ +package com.tfq.finances.main.fragment; + +import android.annotation.SuppressLint; +import android.app.Activity; +import android.content.Intent; +import android.os.Bundle; +import android.text.TextUtils; +import android.view.View; +import android.widget.FrameLayout; +import android.widget.ImageView; +import android.widget.LinearLayout; +import android.widget.TextView; + +import androidx.activity.result.ActivityResultLauncher; +import androidx.activity.result.contract.ActivityResultContracts; +import androidx.recyclerview.widget.RecyclerView; + +import com.alibaba.fastjson.JSON; +import com.chad.library.adapter.base.BaseQuickAdapter; +import com.tfq.ad.ad.AdBannerUtils; +import com.tfq.ad.ad.AdFeedUtils; +import com.tfq.finances.finances.activity.Activity_Budgets; +import com.tfq.finances.finances.activity.Activity_Detail_Add; +import com.tfq.finances.finances.activity.Activity_Transactions; +import com.tfq.finances.jzrcj.R; +import com.tfq.finances.main.adapter.IndexBillAdapter; +import com.tfq.finances.network.api.finances.TransactionsService; +import com.tfq.finances.network.api.index.IndexService; +import com.tfq.finances.network.config.ApiCallback; +import com.tfq.finances.network.config.token.TokenManager; +import com.tfq.finances.network.model.response.finances.index.IndexResp; +import com.tfq.finances.network.model.response.finances.transactions.TransactionsResp; +import com.tfq.finances.utils.androidpicker.CalculateUtils; +import com.tfq.library.base.BaseFragment; +import com.tfq.library.utils.LogK; +import com.tfq.library.utils.PermissionDialog; +import com.tfq.library.utils.RecyclerViewHelper; +import com.tfq.library.utils.ToasterUtil; + +import java.math.BigDecimal; +import java.text.SimpleDateFormat; +import java.util.ArrayList; +import java.util.Date; +import java.util.HashMap; +import java.util.List; +import java.util.Locale; +import java.util.Map; + +public class Fm_Page_A extends BaseFragment { + private FrameLayout flContent; + private TextView tv_year, tv_month, tv_expenditure, tv_income; + private CalculateUtils calculateUtils; + private RecyclerView recycler_view; + private IndexBillAdapter mAdapter; + private Map> old_detailsMap = new HashMap<>(); + private final ActivityResultLauncher detail_add = registerForActivityResult( + new ActivityResultContracts.StartActivityForResult(), + result -> { + if (result.getResultCode() == Activity.RESULT_OK) { + Fm_Page_B.shouldRefresh = true; + getIndex(); + } + } + ); + private boolean mOnReqListener; + + @Override + protected int getLayoutId() { + return R.layout.fm_page_a; + } + + @Override + protected void initView() { + ImageView iv_add = findViewBy_Id(R.id.iv_add); + iv_add.setOnClickListener(this::onClick); + LinearLayout ll_01 = findViewBy_Id(R.id.ll_01); + ll_01.setOnClickListener(this::onClick); + LinearLayout ll_02 = findViewBy_Id(R.id.ll_02); + ll_02.setOnClickListener(this::onClick); + LinearLayout ll_03 = findViewBy_Id(R.id.ll_03); + ll_03.setOnClickListener(this::onClick); + flContent = findViewBy_Id(R.id.fl_content); + + LinearLayout ll_date = findViewBy_Id(R.id.ll_date); + ll_date.setOnClickListener(this::onClick); + tv_year = findViewBy_Id(R.id.tv_year); + tv_month = findViewBy_Id(R.id.tv_month); + tv_expenditure = findViewBy_Id(R.id.tv_expenditure); + tv_income = findViewBy_Id(R.id.tv_income); + recycler_view = findViewBy_Id(R.id.recycler_view); + + mAdapter = new IndexBillAdapter(R.layout.item_index_bill_layout); + recycler_view.setAdapter(mAdapter); + RecyclerViewHelper.initRecyclerViewV(getActivity(), recycler_view, mAdapter); + mAdapter.setOnItemLongClickListener(new BaseQuickAdapter.OnItemLongClickListener() { + @Override + public boolean onItemLongClick(BaseQuickAdapter adapter, View view, int position) { + new PermissionDialog(getActivity(), "del_project","注意", "点击确认将删除当前记账项目", new PermissionDialog.Listener() { + @Override + public void success() { + deleteTransactions(position); + } + }).show(); + return true; + } + }); + } + + private void deleteTransactions(int position) { + Integer id = mAdapter.getData().get(position).getId(); + new TransactionsService(getActivity()).deleteTransactions(id, new ApiCallback() { + @Override + public void onSuccess(String response) { +// mAdapter.remove(position); +// mAdapter.notifyItemChanged(position); + ToasterUtil.show("删除记账项目成功"); + if (!TextUtils.isEmpty(year_Month)) { + getResponseByYearMonth(year_Month); + } + Fm_Page_B.shouldRefresh = true; + } + + @Override + public void onFailure(int errorCode, String errorMessage) { + ToasterUtil.show(errorMessage); + } + }); + } + + @Override + protected void initData(Bundle savedInstanceState) { + getIndex(); + } + + private void getIndex() { + // 方式1:使用SimpleDateFormat(兼容所有API版本) + SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM", Locale.getDefault()); + // 初始化 + calculateUtils = new CalculateUtils(getActivity()); + String currentDate = sdf.format(new Date()); + tv_year.setText(String.valueOf(currentDate.split("-")[0])); + tv_month.setText(String.valueOf(currentDate.split("-")[1])); + + year_Month = currentDate; + getResponseByYearMonth(currentDate); + } + + private void getResponseByYearMonth(String currentDate) { + mAdapter.setNewData(new ArrayList<>()); + new IndexService(getActivity()).index(currentDate, new ApiCallback() { + @Override + public void onSuccess(IndexResp response) { + if (getActivity() != null) { + getActivity().runOnUiThread(new Runnable() { + @SuppressLint("SetTextI18n") + @Override + public void run() { + try { + LogK.e("response=" + response); +// LogK.e("response=" + JSON.toJSONString(response)); + BigDecimal expenditure = response.getExpenditure(); + tv_expenditure.setText(expenditure.toString()); + BigDecimal income = response.getIncome(); + tv_income.setText(income.toString()); + + Map> detailsMap = response.getDetails(); + if (!detailsMap.isEmpty()) { + if (detailsMap != old_detailsMap) { + for (Map.Entry> entry : detailsMap.entrySet()) { + String key = entry.getKey(); + LogK.e("key=" + key); + List value = entry.getValue(); + for (int i = 0; i < value.size(); i++) { + TransactionsResp resp = value.get(i); + //resp.setCategoryName(SystemExpensesCategoriesEnum.getNameById(resp.getCategoryId())); + List list = new ArrayList<>(); + list.add(resp); + if (i == 0) { + resp.setShowTitle(true); + } + if (mAdapter == null) { + mAdapter.setNewData(list); + } else { + mAdapter.addData(list); + } + LogK.e(JSON.toJSONString(list)); + } + } + } + } + old_detailsMap = detailsMap; + } catch (Exception e) { + e.printStackTrace(); + } + + } + }); + } + } + + @Override + public void onFailure(int errorCode, String errorMessage) { + + } + }); + } + + @Override + public void onResume() { + super.onResume(); + + AdBannerUtils.show_ad(getActivity(), flContent); +// AdFeedUtils.show_ad(getActivity(), flContent, 20, "page_01"); + + TokenManager instance = TokenManager.getInstance(getActivity()); + LogK.e("token=" + instance.getAccessToken() + " userId=" + instance.getUserId()); + } + + @Override + public void onPause() { + super.onPause(); +// AdFeedUtils.cancelTag("page_01"); + } + + private String year_Month; + + public void onClick(View view) { + int viewId = view.getId(); + + if (viewId == R.id.iv_add) {//创建记账 + Intent intent = new Intent(getActivity(), Activity_Detail_Add.class); + detail_add.launch(intent); + } else if (viewId == R.id.ll_01) {//账单 + Intent intent = new Intent(getActivity(), Activity_Transactions.class); + startActivity(intent); + } else if (viewId == R.id.ll_02) {//预算 + Intent intent = new Intent(getActivity(), Activity_Budgets.class); + startActivity(intent); + } else if (viewId == R.id.ll_03) { + // 预留处理逻辑 + } else if (viewId == R.id.ll_date || viewId == R.id.tv_year || viewId == R.id.tv_month) { + calculateUtils.choose_date(tv_year, tv_month, new CalculateUtils.Listener() { + @Override + public void success(String yearAndMonth) { + year_Month = yearAndMonth; + getResponseByYearMonth(yearAndMonth); + } + }); + } + } + +} diff --git a/app/src/main/java/com/tfq/finances/main/fragment/Fm_Page_B.java b/app/src/main/java/com/tfq/finances/main/fragment/Fm_Page_B.java new file mode 100644 index 0000000..4830bb3 --- /dev/null +++ b/app/src/main/java/com/tfq/finances/main/fragment/Fm_Page_B.java @@ -0,0 +1,222 @@ +package com.tfq.finances.main.fragment; + +import android.annotation.SuppressLint; +import android.graphics.Color; +import android.graphics.drawable.ColorDrawable; +import android.os.Build; +import android.os.Bundle; +import android.view.Gravity; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.webkit.WebSettings; +import android.webkit.WebView; +import android.webkit.WebViewClient; +import android.widget.FrameLayout; +import android.widget.PopupWindow; +import android.widget.TextView; + +import com.tfq.ad.ad.AdFeedUtils; +import com.tfq.finances.app.App; +import com.tfq.finances.core.constants.Constants; +import com.tfq.finances.jzrcj.R; +import com.tfq.finances.network.config.ApiConfig; +import com.tfq.finances.network.config.token.TokenManager; +import com.tfq.library.base.BaseFragment; + +import androidx.annotation.RequiresApi; +import okhttp3.HttpUrl; + +public class Fm_Page_B extends BaseFragment { + + /** + * 统计页面url + */ + private static final String STATISTICS_URL = ApiConfig.buildH5Url("/finances/statistics/income"); + // 新增成员变量 + public static boolean shouldRefresh = true; + private FrameLayout flContent, bottom_ad_area; + private WebView webView; + //TODO 当前用户ID + private String userId = String.valueOf(TokenManager.getInstance(App.getInstances()).getUserId()); + private String currentType = "1"; // 当前选中的类型,1:支出 2:收入 + private TextView tvType; + private PopupWindow typePopupWindow; + private View dimBackground; // 用于背景变暗的视图 + + @Override + protected int getLayoutId() { + return R.layout.fm_page_b; + } + + @SuppressLint("SetJavaScriptEnabled") + @Override + protected void initView() { + tvType = findViewBy_Id(R.id.tv_type); + View typeSelector = findViewBy_Id(R.id.top_header); // 点击整个顶部区域触发下拉 + + typeSelector.setOnClickListener(this::onClick); + flContent = findViewBy_Id(R.id.fl_content); + bottom_ad_area = findViewBy_Id(R.id.bottom_ad_area); + + // 初始化WebView + webView = new WebView(requireActivity()); + flContent.addView(webView); + + // 配置WebView + WebSettings webSettings = webView.getSettings(); + webSettings.setJavaScriptEnabled(true); + webSettings.setDomStorageEnabled(true); + webSettings.setLoadWithOverviewMode(true); + webSettings.setUseWideViewPort(true); + + webView.setWebViewClient(new WebViewClient() { + @Override + public boolean shouldOverrideUrlLoading(WebView view, String url) { + view.loadUrl(url); + return true; + } + }); + // 初始化下拉菜单 + initTypePopup(); + } + + private void initTypePopup() { + // 创建蒙层背景 + dimBackground = new View(getContext()); + dimBackground.setLayoutParams(new ViewGroup.LayoutParams( + ViewGroup.LayoutParams.MATCH_PARENT, + ViewGroup.LayoutParams.MATCH_PARENT)); + dimBackground.setBackgroundResource(R.drawable.bg_dim); + dimBackground.setVisibility(View.GONE); + dimBackground.setOnClickListener(v -> dismissPopup()); + ((ViewGroup) getView()).addView(dimBackground); + + @SuppressLint("InflateParams") + View popupView = LayoutInflater.from(getContext()).inflate(R.layout.popup_type_menu, null); + typePopupWindow = new PopupWindow( + popupView, + ViewGroup.LayoutParams.MATCH_PARENT, + ViewGroup.LayoutParams.WRAP_CONTENT, + true); + + + // 设置弹出动画 + typePopupWindow.setAnimationStyle(R.style.PopupAnimation); + + // 设置背景透明,这样圆角才能显示 + typePopupWindow.setBackgroundDrawable(new ColorDrawable(Color.TRANSPARENT)); + + // 设置外部可点击 + typePopupWindow.setOutsideTouchable(true); + typePopupWindow.setOnDismissListener(() -> dimBackground.setVisibility(View.GONE)); + + // 设置下拉菜单项点击事件 + popupView.findViewById(R.id.item_income).setOnClickListener(v -> { + selectType("2", "收入"); + dismissPopup(); + }); + + popupView.findViewById(R.id.item_expenditure).setOnClickListener(v -> { + selectType("1", "支出"); + dismissPopup(); + }); + } + + + private void dismissPopup() { + if (typePopupWindow != null && typePopupWindow.isShowing()) { + typePopupWindow.dismiss(); + } + } + + private void showTypePopup(View anchor) { + if (typePopupWindow != null) { + dimBackground.setVisibility(View.VISIBLE); + typePopupWindow.showAtLocation(anchor, Gravity.TOP, 0, getTopHeaderBottom()); + } + } + + private int getTopHeaderBottom() { + View topHeader = findViewBy_Id(R.id.top_header); + int[] location = new int[2]; + topHeader.getLocationOnScreen(location); + return location[1] + topHeader.getHeight(); + } + + private void selectType(String type, String typeName) { + currentType = type; + tvType.setText(typeName); + reloadWebView(); + } + + private void reloadWebView() { + HttpUrl url = HttpUrl.parse(STATISTICS_URL) + .newBuilder() + .addQueryParameter("appinfoId", Constants.APP_INFO_ID) + .addQueryParameter("userId", userId) + .addQueryParameter("type", currentType) + .build(); + webView.loadUrl(url.toString()); + } + + @Override + protected void initData(Bundle savedInstanceState) { + reloadWebView(); + } + + @RequiresApi(api = Build.VERSION_CODES.R) + @Override + public void onResume() { + super.onResume(); +// AdFeedUtils.show_ad(getActivity(), bottom_ad_area, 20, "page_02"); + // 添加此处刷新逻辑 + if (shouldRefresh) { + reloadWebView(); + shouldRefresh = false; + } + + if (webView != null) { + webView.onResume(); + } + } + + @Override + public void onPause() { + super.onPause(); + AdFeedUtils.cancelTag("page_02"); + if (webView != null) { + webView.onPause(); + } + } + + @Override + public void onDestroyView() { + if (dimBackground != null) { + ((ViewGroup) dimBackground.getParent()).removeView(dimBackground); + dimBackground = null; + } + if (webView != null) { + webView.destroy(); + webView = null; + } + if (typePopupWindow != null && typePopupWindow.isShowing()) { + typePopupWindow.dismiss(); + } + typePopupWindow = null; + super.onDestroyView(); + } + + @Override + public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { + View rootView = super.onCreateView(inflater, container, savedInstanceState); + return rootView; + } + + public void onClick(View view) { + if (view.getId() == R.id.top_header) { + showTypePopup(view); + } + } + +} \ No newline at end of file diff --git a/app/src/main/java/com/tfq/finances/main/fragment/Fm_Page_C.java b/app/src/main/java/com/tfq/finances/main/fragment/Fm_Page_C.java new file mode 100644 index 0000000..aa1e30c --- /dev/null +++ b/app/src/main/java/com/tfq/finances/main/fragment/Fm_Page_C.java @@ -0,0 +1,69 @@ +package com.tfq.finances.main.fragment; + +import android.os.Bundle; +import android.view.View; +import android.widget.FrameLayout; +import android.widget.TextView; + +import com.tfq.finances.jzrcj.R; +import com.tfq.library.base.BaseFragment; +import com.tfq.library.utils.LogK; +import com.tfq.library.view.SlideDownView; + +/** + * Description: + * Created by JiangKe . + */ +public class Fm_Page_C extends BaseFragment { + + private SlideDownView slide_down_view; + + @Override + protected int getLayoutId() { + return R.layout.fm_page_c; + } + + @Override + protected void initView() { + FrameLayout fl_content = findViewBy_Id(R.id.fl_content); + TextView tv_onclick = findViewBy_Id(R.id.tv_onclick); + tv_onclick.setOnClickListener(this::onClick); + TextView tv_onclick2 = findViewBy_Id(R.id.tv_onclick2); + tv_onclick2.setOnClickListener(this::onClick); + + /*requestPermission(new String[]{Permission.MANAGE_EXTERNAL_STORAGE}, new Listener() { + @Override + public void success() { + + } + });*/ + + slide_down_view = findViewBy_Id(com.tfq.library.R.id.slide_down_view); + + } + + @Override + public void onResume() { + super.onResume(); + LogK.e("onResume onResume"); + +// ToasterUtil.show("",20); +// Toaster.show(""); + } + + @Override + protected void initData(Bundle savedInstanceState) { + + } + + private void onClick(View view) { + int viewId = view.getId(); + + if (viewId == R.id.tv_onclick) { + slide_down_view.showFunctionBar(); + } else if (viewId == R.id.tv_onclick2) { + slide_down_view.hideFunctionBar(); + } + } + +} diff --git a/app/src/main/java/com/tfq/finances/main/fragment/Fm_Page_S.java b/app/src/main/java/com/tfq/finances/main/fragment/Fm_Page_S.java new file mode 100644 index 0000000..d3d20e6 --- /dev/null +++ b/app/src/main/java/com/tfq/finances/main/fragment/Fm_Page_S.java @@ -0,0 +1,183 @@ +package com.tfq.finances.main.fragment; + +import android.app.Activity; +import android.content.Intent; +import android.os.Build; +import android.os.Bundle; +import android.text.TextUtils; +import android.view.View; +import android.view.WindowManager; +import android.widget.FrameLayout; +import android.widget.ImageView; +import android.widget.LinearLayout; +import android.widget.TextView; + +import com.airbnb.lottie.LottieAnimationView; +import com.airbnb.lottie.RenderMode; +import com.tfq.ad.ad.AdFeedUtils; +import com.tfq.finances.core.enums.AvatarEnum; +import com.tfq.finances.finances.activity.Activity_Setting_More; +import com.tfq.finances.jzrcj.R; +import com.tfq.finances.main.Activity_Main; +import com.tfq.finances.main.Activity_Splash; +import com.tfq.finances.main.activity.Activity_About_Us; +import com.tfq.finances.main.activity.Activity_Export; +import com.tfq.finances.main.activity.Activity_PbFeedback; +import com.tfq.finances.utils.PAGAnimationLoader; +import com.tfq.library.app.BaseConstants; +import com.tfq.library.base.BaseFragment; +import com.tfq.library.utils.AppUtil; +import com.tfq.library.utils.LogK; +import com.tfq.library.view.SlideDownView; + +import org.libpag.PAGView; + +import androidx.activity.result.ActivityResultLauncher; +import androidx.activity.result.contract.ActivityResultContracts; + +import static com.tfq.finances.main.Activity_Main.memberUser; + +public class Fm_Page_S extends BaseFragment { + private final ActivityResultLauncher moreSetting = registerForActivityResult( + new ActivityResultContracts.StartActivityForResult(), + result -> { + if (result.getResultCode() == Activity.RESULT_OK) { + Intent intent = new Intent(getActivity(), Activity_Splash.class); + startActivity(intent); + requireActivity().finish(); + } + } + ); + private FrameLayout flContent; + private SlideDownView slide_down_view; + private ImageView iv_avatar; + private TextView tv_nickname; + + @Override + protected int getLayoutId() { + return R.layout.fm_setting; + } + + @Override + protected void initView() { + iv_avatar = findViewBy_Id(R.id.iv_avatar); + LinearLayout ll01 = findViewBy_Id(R.id.ll_01); + ll01.setOnClickListener(this::onClick); + LinearLayout ll02 = findViewBy_Id(R.id.ll_02); + ll02.setOnClickListener(this::onClick); + LinearLayout ll03 = findViewBy_Id(R.id.ll_03); + ll03.setOnClickListener(this::onClick); + LinearLayout ll04 = findViewBy_Id(R.id.ll_04); + ll04.setOnClickListener(this::onClick); + LinearLayout ll05 = findViewBy_Id(R.id.ll_05); + ll05.setOnClickListener(this::onClick); + + flContent = findViewBy_Id(R.id.fl_content); + + slide_down_view = findViewBy_Id(com.tfq.library.R.id.slide_down_view); + slide_down_view.setTextContent("此功能正在完善中"); + //slide_down_view.showFunctionBar(); + + tv_nickname = findViewBy_Id(R.id.tv_nickname); + tv_nickname.setText(memberUser != null ? memberUser.getNickname() : ""); + + +// setAnimationView(); + +// setPagView(); + } + + private void setPagView() { + PAGView pagView = findViewBy_Id(R.id.pag_view); + PAGAnimationLoader.loadAndPlayAnimation(getActivity(),pagView,"1.pag"); + } + + private void setAnimationView() { + LogK.e("setAnimationView"); + + + LottieAnimationView lottieView = findViewBy_Id(R.id.animation_view); + // 设置自动播放和循环 + lottieView.setAnimation("4.json"); + + + lottieView.playAnimation(); + lottieView.loop(false); + + // 设置动画播放范围,避免全片段播放 + // lottieView.setMinAndMaxFrame(minFrame, maxFrame); + // 启用性能跟踪 + lottieView.setPerformanceTrackingEnabled(true); + + // 在Activity中开启硬件加速 + if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { + getActivity().getWindow().setFlags( + WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED, + WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED + ); + } + + // 根据情况选择合适的渲染模式 + lottieView.setRenderMode(RenderMode.AUTOMATIC); // 或RenderMode.AUTOMATIC + + + + } + + @Override + protected void initData(Bundle savedInstanceState) { + String avatar = Activity_Main.memberUser.getAvatar(); + if (AppUtil.isInteger(avatar) && Integer.parseInt(avatar) > 0) { + int avatarById = AvatarEnum.getAvatarById(Integer.parseInt(avatar)); + iv_avatar.setImageResource(avatarById); + LogK.e("设置页 iv_avatar 设置头像"); + } + } + + @Override + public void onResume() { + super.onResume(); + +// AdFeedUtils.show_ad(getActivity(), flContent, 20, "page_setting"); + AdFeedUtils.show_ad(getActivity(), BaseConstants.CODE_AD_FEED1, flContent, 20, "page_setting", 1); + + if (Activity_Setting_More.avatar_Id != -1) { + LogK.e("设置页 onResume 设置头像"); + int avatarById = AvatarEnum.getAvatarById(Activity_Setting_More.avatar_Id); + iv_avatar.setImageResource(avatarById); + } + if (!TextUtils.isEmpty(Activity_Setting_More.activity_nickname)) { + tv_nickname.setText(Activity_Setting_More.activity_nickname); + } + +// setAnimationView(); + } + + @Override + public void onPause() { + super.onPause(); + AdFeedUtils.cancelTag("page_setting"); + } + + public void onClick(View view) { + Intent intent; + int viewId = view.getId(); + + if (viewId == R.id.ll_01) { + intent = new Intent(getActivity(), Activity_Setting_More.class); + moreSetting.launch(intent); + } else if (viewId == R.id.ll_02) { + + } else if (viewId == R.id.ll_03) { + intent = new Intent(getActivity(), Activity_PbFeedback.class); + startActivity(intent); + } else if (viewId == R.id.ll_04) { + intent = new Intent(getActivity(), Activity_Export.class); + startActivity(intent); + } else if (viewId == R.id.ll_05) { + intent = new Intent(getActivity(), Activity_About_Us.class); + startActivity(intent); + } + } + +} diff --git a/app/src/main/java/com/tfq/finances/main/model/TypeOriginConfig.kt b/app/src/main/java/com/tfq/finances/main/model/TypeOriginConfig.kt new file mode 100644 index 0000000..9da0c29 --- /dev/null +++ b/app/src/main/java/com/tfq/finances/main/model/TypeOriginConfig.kt @@ -0,0 +1,37 @@ +package com.tfq.finances.main.model + +import com.google.gson.annotations.SerializedName +import com.tfq.finances.core.enums.CommonTypeOriginEnum +import com.tfq.finances.jzrcj.R + +data class TypeOriginConfig( + /** + * 类别id + */ + var typeOriginId: Int = -1, + /** + * 类别名称 + */ + var typeOriginName: String = "", + + /** + * 类别未选中资源id + */ + var typeOriginIdCheck: Int = R.mipmap.ic_zdy_check, + + /** + * 类别选中资源id + */ + var typeOriginIdChecked: Int = R.mipmap.ic_zdy_checked, + + /** + * 当前类别是否选中 + */ + var itemCheck: Boolean = false, + + /** + * 当前类别是系统还是用户自定义 + */ + var type: String = CommonTypeOriginEnum.SYSTEM.name + + ) \ No newline at end of file diff --git a/app/src/main/java/com/tfq/finances/network/api/BaseApiClient.java b/app/src/main/java/com/tfq/finances/network/api/BaseApiClient.java new file mode 100644 index 0000000..c4859c5 --- /dev/null +++ b/app/src/main/java/com/tfq/finances/network/api/BaseApiClient.java @@ -0,0 +1,278 @@ +package com.tfq.finances.network.api; + +import android.content.Context; +import android.os.Handler; +import android.os.Looper; +import android.util.Log; + +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; +import com.google.gson.reflect.TypeToken; +import com.tfq.finances.network.config.ApiCallback; +import com.tfq.finances.network.config.TfqConstants; +import com.tfq.finances.network.config.token.TokenManager; +import com.tfq.finances.network.interceptor.AuthInterceptor; +import com.tfq.finances.network.model.response.base.TfqBaseResult; +import com.tfq.finances.core.utils.TfqMyOkHttp; +import com.tfq.library.utils.LogK; + +import java.io.IOException; +import java.lang.reflect.Type; + +import okhttp3.Call; +import okhttp3.Callback; +import okhttp3.OkHttpClient; +import okhttp3.Request; +import okhttp3.Response; +import okhttp3.ResponseBody; + +/** + * base 请求 + */ +public abstract class BaseApiClient { + + /** + * 上下文 + */ + protected final Context appContext; + /** + * 普通客户端 + */ + protected final OkHttpClient normalClient; + /** + * 鉴权客户端 + */ + protected final OkHttpClient authClient; + /** + * Gson 单例 + */ + protected static final Gson gson = new GsonBuilder() + .setDateFormat("yyyy-MM-dd HH:mm:ss") + .create(); + + /** + * 构造方法 + * + * @param context + */ + public BaseApiClient(Context context) { + this.appContext = context; + this.normalClient = TfqMyOkHttp.myClient(); + this.authClient = createAuthClient(); + } + + /** + * 创建鉴权客户端 + * + * @return + */ + private OkHttpClient createAuthClient() { + return normalClient.newBuilder() + .addInterceptor(new AuthInterceptor(appContext)) + .build(); + } + + /** + * 执行普通请求(无需鉴权) + */ + protected void executeNormalRequest(Request request, Class dataType, ApiCallback callback) { + executeRequest(normalClient, request, dataType, callback); + } + /** + * 执行普通请求(无需鉴权) typeToken + */ + protected void executeNormalRequest(Request request, TypeToken typeToken, ApiCallback callback) { + executeRequest(normalClient, request, typeToken, callback); + } + + /** + * 执行鉴权请求(自动处理Token) + */ + protected void executeAuthRequest(Request request, Class dataType, ApiCallback callback) { + executeRequest(authClient, request, dataType, callback); + } + + + /** + * 执行鉴权请求(自动处理Token) typeToken + */ + protected void executeAuthRequest(Request request, TypeToken typeToken, ApiCallback callback) { + executeRequest(authClient, request, typeToken, callback); + } + + + /** + * 通用请求执行方法 + * + * @param client 请求使用的客户端 + * @param request 请求对象 + * @param dataType 接收数据类型 + * @param callback + * @param + */ + private void executeRequest(OkHttpClient client, Request request, + Class dataType, ApiCallback callback) { + client.newCall(request).enqueue(new Callback() { + @Override + public void onResponse(Call call, Response response) { + // 复用原有响应处理逻辑 + try (ResponseBody body = response.body()) { + // HTTP状态检查 + if (!response.isSuccessful()) { + notifyFailure(callback, response.code(), + "HTTP Error: " + response.message()); + return; + } + + String responseData = body.string(); + Log.d("http Network", "Raw Response: " + responseData); + + // 解析基础响应结构 + // 使用TypeToken简化 + Type responseType = TypeToken.getParameterized(TfqBaseResult.class, dataType).getType(); + TfqBaseResult baseResult = gson.fromJson(responseData, responseType); + + if(baseResult == null){ + notifyFailure(callback, TfqConstants.DECRYPTION_ERROR, "返回数据为空"); + return; + } +// if (baseResult.getData() == null) { +// notifyFailure(callback, TfqConstants.DECRYPTION_ERROR, "返回数据为空"); +// return; +// } + // 业务状态码检查 + if (!isSuccessCode(baseResult.getCode())) { + handleBusinessError(baseResult, callback); + return; + } + // 数据解密处理 + T data = decryptIfNeeded(baseResult.getData()); + notifySuccess(callback, data); + } catch (Exception e) { + LogK.e("Data parse error: " + e.getMessage()); + notifyFailure(callback, TfqConstants.PARSE_ERROR, + "Data parse error: " + e.getMessage()); + } + } + + @Override + public void onFailure(Call call, IOException e) { + notifyFailure(callback, TfqConstants.URL_REQUEST_ERROR, + TfqConstants.URL_REQUEST_ERROR_MSG); + } + }); + } + + /** + * 通用请求执行方法 typeToken + * + * @param client 请求使用的客户端 + * @param request 请求对象 + * @param typeToken 接收数据类型 + * @param callback + * @param + */ + private void executeRequest(OkHttpClient client, Request request, + TypeToken typeToken, ApiCallback callback) { + client.newCall(request).enqueue(new Callback() { + @Override + public void onResponse(Call call, Response response) { + // 复用原有响应处理逻辑 + try (ResponseBody body = response.body()) { + if (!response.isSuccessful()) { + notifyFailure(callback, response.code(), + "HTTP Error: " + response.message()); + return; + } + + String responseData = body.string(); + Log.d("Network", "Raw Response: " + responseData); + + // 构建完整的响应类型 TfqBaseResult + Type responseType = TypeToken.getParameterized( + TfqBaseResult.class, + typeToken.getType() + ).getType(); + + TfqBaseResult baseResult = gson.fromJson(responseData, responseType); + + if (baseResult.getData() == null) { + notifyFailure(callback, TfqConstants.DECRYPTION_ERROR, "返回数据为空"); + return; + } + + if (!isSuccessCode(baseResult.getCode())) { + handleBusinessError(baseResult, callback); + return; + } + + T data = decryptIfNeeded(baseResult.getData()); + notifySuccess(callback, data); + } catch (Exception e) { + notifyFailure(callback, TfqConstants.PARSE_ERROR, + "Data parse error: " + e.getMessage()); + } + } + + @Override + public void onFailure(Call call, IOException e) { + notifyFailure(callback, TfqConstants.URL_REQUEST_ERROR, + TfqConstants.URL_REQUEST_ERROR_MSG); + } + }); + } + + /** + * TODO 待定 + * 处理Token失效 + * + * @param callback + * @param + */ + protected void handleTokenInvalid(ApiCallback callback) { + // 清除本地Token + TokenManager.getInstance(appContext).clearTokens(); + + // 跳转登录页面 + //Intent loginIntent = new Intent(appContext, LoginActivity.class) + // .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + //appContext.startActivity(loginIntent); + + // 回调通知 + notifyFailure(callback, TfqConstants.TOKEN_INVALID_ERROR, "登录已过期,请重新登录"); + } + + // 统一错误处理 + private void handleBusinessError(TfqBaseResult result, ApiCallback callback) { + notifyFailure(callback, result.getCode(), result.getMsg()); + } + + + private void notifySuccess(ApiCallback callback, T result) { + new Handler(Looper.getMainLooper()).post(() -> + callback.onSuccess(result)); + } + + private void notifyFailure(ApiCallback callback, int code, String msg) { + new Handler(Looper.getMainLooper()).post(() -> + callback.onFailure(code, msg)); + } + + // 抽象方法由子类实现 + private boolean isSuccessCode(Integer code) { + return code != null && (code.equals(TfqConstants.CODE_SUCCESS) || code.equals(TfqConstants.CODE_SUCCESS_0)); + } + + ; + + /** + * 数据解密方法(需子类实现) + * + * @param data + * @param + * @return + * @throws Exception + */ + protected abstract T decryptIfNeeded(T data) throws Exception; + +} diff --git a/app/src/main/java/com/tfq/finances/network/api/adv/AdService.java b/app/src/main/java/com/tfq/finances/network/api/adv/AdService.java new file mode 100644 index 0000000..4418ac0 --- /dev/null +++ b/app/src/main/java/com/tfq/finances/network/api/adv/AdService.java @@ -0,0 +1,119 @@ +package com.tfq.finances.network.api.adv; + +import android.content.Context; +import android.text.TextUtils; + +import com.tfq.finances.network.config.ApiCallback; +import com.tfq.finances.network.config.ApiConfig; +import com.tfq.finances.network.config.TfqConstants; +import com.tfq.finances.network.model.response.adv.AdvFlagResp; +import com.tfq.library.utils.AppUtil; +import com.tfq.finances.network.api.BaseApiClient; +import com.tfq.finances.network.model.request.base.TfqBaseRequest; +import com.tfq.finances.network.model.response.adv.AdvSettingsResp; +import com.tfq.finances.core.constants.Constants; +import com.tfq.finances.app.App; + +import okhttp3.MediaType; +import okhttp3.Request; +import okhttp3.RequestBody; + +/** + * 广告模块 + */ +public class AdService extends BaseApiClient { + private static final String AD_SETTINGS_URL = ApiConfig.buildUrl("/jwl/adv/settings"); + + private static final String AD_FLAG_URL = ApiConfig.buildUrl("/jwl/app-advertisement/flag"); + + public AdService(Context context) { + super(context); + } + + /** + * 获取广告设置(缓存和页面刷新间隔) + * + * @param callback + */ + public void getAdvSettings(ApiCallback callback) { + TfqBaseRequest request = buildRequest(); + + MediaType jsonType = MediaType.parse("application/json; charset=utf-8"); + RequestBody body = RequestBody.create(jsonType, gson.toJson(request)); + + Request httpRequest = new Request.Builder() + .url(AD_SETTINGS_URL) + .post(body) + .build(); + + executeNormalRequest(httpRequest, AdvSettingsResp.class, callback); + } + + + /** + * 获取广告开关 + * + * @param callback + */ + public void getAdvFlag(ApiCallback callback) { + TfqBaseRequest request = buildRequest(); + + MediaType jsonType = MediaType.parse("application/json; charset=utf-8"); + RequestBody body = RequestBody.create(jsonType, gson.toJson(request)); + + Request httpRequest = new Request.Builder() + .url(AD_FLAG_URL) + .post(body) + .build(); + + executeNormalRequest(httpRequest, AdvFlagResp.class, callback); + } + + private TfqBaseRequest buildRequest() { + Context context = App.getContext(); + return new TfqBaseRequest(Constants.APP_ID, + !TextUtils.isEmpty(Constants.CHANNEL) ? Constants.CHANNEL : "", + AppUtil.getAppVersionCode(context), + AppUtil.getPackageName(context)); + } + + + @Override + protected T decryptIfNeeded(T data) throws Exception { + // 当前接口无需解密,直接返回原始数据 + return data; + } + + /* 使用示例: + public class BusinessLogic { + private final AdService adService = new AdService(); + private final FeedbackService feedbackService = new FeedbackService(); + + void getAdSettings() { + adService.getAdvSettings(new ApiCallback() { + @Override + public void onSuccess(AdvSettings result) { + // 处理成功逻辑 + } + + @Override + public void onFailure(int errorCode, String errorMsg) { + // 处理错误逻辑 + } + }); + } + void submitFeedback(String content, String phone) { + feedbackService.submitFeedback(content, phone, new ApiCallback() { + @Override + public void onSuccess(FeedbackResult result) { + // 处理成功提交 + } + + @Override + public void onFailure(int errorCode, String errorMsg) { + // 处理提交失败 + } + }); + } + */ +} diff --git a/app/src/main/java/com/tfq/finances/network/api/feedback/FeedbackService.java b/app/src/main/java/com/tfq/finances/network/api/feedback/FeedbackService.java new file mode 100644 index 0000000..b9d43f6 --- /dev/null +++ b/app/src/main/java/com/tfq/finances/network/api/feedback/FeedbackService.java @@ -0,0 +1,71 @@ +package com.tfq.finances.network.api.feedback; + +import android.content.Context; +import android.text.TextUtils; + +import com.tfq.finances.app.App; +import com.tfq.finances.core.constants.Constants; +import com.tfq.finances.network.api.BaseApiClient; +import com.tfq.finances.network.config.ApiCallback; +import com.tfq.finances.network.config.ApiConfig; +import com.tfq.finances.network.model.request.base.TfqBaseRequest; +import com.tfq.finances.network.model.request.feedback.FeedbackAddRequest; +import com.tfq.library.utils.AppUtil; +import com.tfq.library.utils.LogK; + +import okhttp3.MediaType; +import okhttp3.Request; +import okhttp3.RequestBody; + +public class FeedbackService extends BaseApiClient { + + private static final String SUBMIT_FEEDBACK_URL = ApiConfig.buildUrl("/jwl/feedback/add"); + + public FeedbackService(Context context) { + super(context); + } + + + /** + * 提交反馈 + * + * @param content + * @param phone + * @param callback + */ + public void submitFeedback(String content, String phone, ApiCallback callback) { + FeedbackAddRequest request = buildRequest(content, phone); + + MediaType jsonType = MediaType.parse("application/json; charset=utf-8"); + RequestBody body = RequestBody.create(jsonType, gson.toJson(request)); + + Request httpRequest = new Request.Builder() + .url(SUBMIT_FEEDBACK_URL) + .post(body) + .build(); + + executeNormalRequest(httpRequest, String.class, callback); + } + + private FeedbackAddRequest buildRequest(String content, String phone) { + Context context = App.getContext(); + FeedbackAddRequest feedbackAddRequest = new FeedbackAddRequest( + content, + phone, + Constants.APP_NAME + ); + feedbackAddRequest.setAppId(Constants.APP_ID); + feedbackAddRequest.setChannel(Constants.CHANNEL); + feedbackAddRequest.setAppVersion(AppUtil.getAppVersionCode(context)); + feedbackAddRequest.setAppPacketname(AppUtil.getPackageName(context)); + return feedbackAddRequest; + } + + + @Override + protected T decryptIfNeeded(T data) throws Exception { + // 当前接口无需解密,直接返回原始数据 + return data; + } + +} diff --git a/app/src/main/java/com/tfq/finances/network/api/finances/BillsService.java b/app/src/main/java/com/tfq/finances/network/api/finances/BillsService.java new file mode 100644 index 0000000..50d1430 --- /dev/null +++ b/app/src/main/java/com/tfq/finances/network/api/finances/BillsService.java @@ -0,0 +1,81 @@ +package com.tfq.finances.network.api.finances; + +import android.content.Context; + +import com.google.gson.reflect.TypeToken; +import com.tfq.finances.core.constants.Constants; +import com.tfq.finances.network.api.BaseApiClient; +import com.tfq.finances.network.config.ApiCallback; +import com.tfq.finances.network.config.ApiConfig; +import com.tfq.finances.network.model.response.finances.transactions.BillsData; +import com.tfq.finances.network.model.response.finances.transactions.BillsResp; + +import java.util.List; + +import okhttp3.HttpUrl; +import okhttp3.Request; + +/** + * 账单服务 + */ +public class BillsService extends BaseApiClient { + + private static final String GET_BILLS_MONTH_URL = ApiConfig.buildUrl("/finances/transactions/month/list"); + private static final String GET_BILLS_YEAR_URL = ApiConfig.buildUrl("/finances/transactions/year/list"); + + + /** + * 获取月度账单详情 + * + * @param callback + */ + public void getMonthBillsInfo(String year, ApiCallback> callback) { + + HttpUrl url = HttpUrl.parse(GET_BILLS_MONTH_URL) + .newBuilder() + .addQueryParameter("appinfoId", Constants.APP_INFO_ID) + .addQueryParameter("year", year) + .build(); + + Request httpRequest = new Request.Builder() + .url(url) + .get() + .build(); + executeAuthRequest(httpRequest,new TypeToken>(){} , callback); + } + + /** + * 获取年度账单详情 + * + * @param callback + */ + public void getYearBillsInfo(ApiCallback> callback) { + HttpUrl url = HttpUrl.parse(GET_BILLS_YEAR_URL) + .newBuilder() + .addQueryParameter("appinfoId", Constants.APP_INFO_ID) + .build(); + + Request httpRequest = new Request.Builder() + .url(url) + .get() + .build(); + executeAuthRequest(httpRequest, new TypeToken>(){}, callback); + } + + /** + * 构造方法 + * + * @param context + */ + public BillsService(Context context) { + super(context); + } + + + @Override + protected T decryptIfNeeded(T data) throws Exception { + // 当前接口无需解密,直接返回原始数据 + return data; + } + +} diff --git a/app/src/main/java/com/tfq/finances/network/api/finances/BudgetsService.java b/app/src/main/java/com/tfq/finances/network/api/finances/BudgetsService.java new file mode 100644 index 0000000..fafac63 --- /dev/null +++ b/app/src/main/java/com/tfq/finances/network/api/finances/BudgetsService.java @@ -0,0 +1,107 @@ +package com.tfq.finances.network.api.finances; + +import android.content.Context; + +import com.tfq.finances.network.api.BaseApiClient; +import com.tfq.finances.network.config.ApiCallback; +import com.tfq.finances.network.config.ApiConfig; +import com.tfq.finances.network.config.TfqConstants; +import com.tfq.finances.network.model.request.finances.budgets.BudgetsRequest; +import com.tfq.finances.network.model.response.finances.budgets.BudgetsMonthResp; +import com.tfq.finances.network.model.response.finances.budgets.BudgetsResp; +import com.tfq.finances.core.constants.Constants; +import com.tfq.finances.app.App; + +import java.math.BigDecimal; + +import okhttp3.MediaType; +import okhttp3.Request; +import okhttp3.RequestBody; + +/** + * 预算模块 + */ +public class BudgetsService extends BaseApiClient { + + private static final String GET_BUDGETS_MONTH_INFO_URL = ApiConfig.buildUrl("/finances/budgets/month/info"); + private static final String GET_BUDGETS_URL = ApiConfig.buildUrl("/finances/budgets/get"); + private static final String SET_BUDGETS_URL = ApiConfig.buildUrl("/finances/budgets/set"); + + + /** + * 获取月度预算详情 + * + * @param callback + */ + public void getMonthBudgetsInfo(String month, ApiCallback callback) { + BudgetsRequest request = new BudgetsRequest(); + request.setAppinfoId(Constants.APP_INFO_ID); + request.setMonth(month); + MediaType jsonType = MediaType.parse("application/json; charset=utf-8"); + RequestBody body = RequestBody.create(jsonType, gson.toJson(request)); + + Request httpRequest = new Request.Builder() + .url(GET_BUDGETS_MONTH_INFO_URL) + .post(body) + .build(); + + executeAuthRequest(httpRequest, BudgetsMonthResp.class, callback); + } + + + /** + * 获取预算信息 + * + * @param callback + */ + public void getBudgetsInfo(ApiCallback callback) { + BudgetsRequest request = new BudgetsRequest(); + request.setAppinfoId(Constants.APP_INFO_ID); + MediaType jsonType = MediaType.parse("application/json; charset=utf-8"); + RequestBody body = RequestBody.create(jsonType, gson.toJson(request)); + + Request httpRequest = new Request.Builder() + .url(GET_BUDGETS_URL) + .post(body) + .build(); + + executeAuthRequest(httpRequest, BudgetsResp.class, callback); + } + + + /** + * 设置用户预算 + * + * @param callback + * @param amount + */ + public void setBudgetsInfo(BigDecimal amount, String month, ApiCallback callback) { + BudgetsRequest request = new BudgetsRequest(Constants.APP_INFO_ID, Constants.USER_ID, amount, month, 1); + MediaType jsonType = MediaType.parse("application/json; charset=utf-8"); + RequestBody body = RequestBody.create(jsonType, gson.toJson(request)); + + Request httpRequest = new Request.Builder() + .url(SET_BUDGETS_URL) + .post(body) + .build(); + + executeAuthRequest(httpRequest, BudgetsResp.class, callback); + } + + /** + * 构造方法 + * + * @param context + */ + public BudgetsService(Context context) { + super(context); + } + + + @Override + protected T decryptIfNeeded(T data) throws Exception { + // 当前接口无需解密,直接返回原始数据 + return data; + } + +} diff --git a/app/src/main/java/com/tfq/finances/network/api/finances/CategoriesService.java b/app/src/main/java/com/tfq/finances/network/api/finances/CategoriesService.java new file mode 100644 index 0000000..b9984ed --- /dev/null +++ b/app/src/main/java/com/tfq/finances/network/api/finances/CategoriesService.java @@ -0,0 +1,131 @@ +package com.tfq.finances.network.api.finances; + +import android.content.Context; + +import com.tfq.finances.core.constants.Constants; +import com.tfq.finances.network.api.BaseApiClient; +import com.tfq.finances.network.config.ApiCallback; +import com.tfq.finances.network.config.ApiConfig; +import com.tfq.finances.network.model.request.finances.budgets.BudgetsRequest; +import com.tfq.finances.network.model.request.finances.categories.CategoriesAllRequest; +import com.tfq.finances.network.model.request.finances.categories.CategoriesRequest; +import com.tfq.finances.network.model.response.finances.categories.CategoriesAllResp; + +import java.math.BigDecimal; + +import okhttp3.HttpUrl; +import okhttp3.MediaType; +import okhttp3.Request; +import okhttp3.RequestBody; + +/** + * 类别 + */ +public class CategoriesService extends BaseApiClient { + + private static final String GET_ALL_URL = ApiConfig.buildUrl("/finances/categories/all"); + private static final String ADD_CATEGORIES_URL = ApiConfig.buildUrl("/finances/categories/create"); + private static final String UPDATE_CATEGORIES_URL = ApiConfig.buildUrl("/finances/categories/update"); + private static final String DELETE_CATEGORIES_URL = ApiConfig.buildUrl("/finances/categories/delete"); + + + /** + * 构造方法 + * + * @param context + */ + public CategoriesService(Context context) { + super(context); + } + + /** + * 新增自定义类别 + * + * @param callback + */ + public void addCategories(Integer type, String name, String icon, ApiCallback callback) { + CategoriesRequest request = new CategoriesRequest(null, Constants.APP_INFO_ID, + icon, + name, + type + ); + MediaType jsonType = MediaType.parse("application/json; charset=utf-8"); + RequestBody body = RequestBody.create(jsonType, gson.toJson(request)); + Request httpRequest = new Request.Builder() + .url(ADD_CATEGORIES_URL) + .post(body) + .build(); + executeAuthRequest(httpRequest, String.class, callback); + } + + /** + * 修改自定义类别 + * + * @param callback + */ + public void updateCategories(Integer categoriesId, Integer type, String name, String icon, ApiCallback callback) { + CategoriesRequest request = new CategoriesRequest(categoriesId, Constants.APP_INFO_ID, + icon, + name, + type + ); + MediaType jsonType = MediaType.parse("application/json; charset=utf-8"); + RequestBody body = RequestBody.create(jsonType, gson.toJson(request)); + Request httpRequest = new Request.Builder() + .url(UPDATE_CATEGORIES_URL) + .post(body) + .build(); + executeAuthRequest(httpRequest, String.class, callback); + } + + /** + * 删除自定义类别 + * + * @param categoriesId + * @param callback + */ + public void deleteCategories(Integer categoriesId, ApiCallback callback) { + + HttpUrl url = HttpUrl.parse(DELETE_CATEGORIES_URL) + .newBuilder() + .addQueryParameter("id", String.valueOf(categoriesId)) + .build(); + Request httpRequest = new Request.Builder() + .url(url) + .get() + .build(); + executeAuthRequest(httpRequest, String.class, callback); + } + + /** + * 获取所有类别 + * 系统+自定义 + * + * @param userId + * @param type + */ + public void getCategoriesList(long userId, int type, ApiCallback callback) { + CategoriesAllRequest request = new CategoriesAllRequest( + Constants.APP_INFO_ID, + userId, + type + ); + + MediaType jsonType = MediaType.parse("application/json; charset=utf-8"); + RequestBody body = RequestBody.create(jsonType, gson.toJson(request)); + + Request httpRequest = new Request.Builder() + .url(GET_ALL_URL) + .post(body) + .build(); + + executeAuthRequest(httpRequest, CategoriesAllResp.class, callback); + } + + @Override + protected T decryptIfNeeded(T data) throws Exception { + // 当前接口无需解密,直接返回原始数据 + return data; + } + +} diff --git a/app/src/main/java/com/tfq/finances/network/api/finances/TransactionsService.java b/app/src/main/java/com/tfq/finances/network/api/finances/TransactionsService.java new file mode 100644 index 0000000..4284fd8 --- /dev/null +++ b/app/src/main/java/com/tfq/finances/network/api/finances/TransactionsService.java @@ -0,0 +1,229 @@ +package com.tfq.finances.network.api.finances; + +import android.content.Context; + +import com.tfq.finances.core.constants.Constants; +import com.tfq.finances.network.api.BaseApiClient; +import com.tfq.finances.network.config.ApiCallback; +import com.tfq.finances.network.config.ApiConfig; +import com.tfq.finances.network.config.TfqConstants; +import com.tfq.finances.network.model.request.finances.categories.CategoriesRequest; +import com.tfq.finances.network.model.request.finances.transactions.TransactionsExportRequest; +import com.tfq.finances.network.model.request.finances.transactions.TransactionsPageRequest; +import com.tfq.finances.network.model.request.finances.transactions.TransactionsRequest; + +import java.math.BigDecimal; +import java.time.LocalDate; + +import okhttp3.HttpUrl; +import okhttp3.MediaType; +import okhttp3.Request; +import okhttp3.RequestBody; + +/** + * 账目 + */ +public class TransactionsService extends BaseApiClient { + + + // 定义添加交易的API URL + private static final String ADD_TRANSACTIONS_URL = ApiConfig.buildUrl("/finances/transactions/create"); + // 定义更新交易的API URL + private static final String UPDATE_TRANSACTIONS_URL = ApiConfig.buildUrl("/finances/transactions/update"); + // 定义删除交易的API URL + private static final String DELETE_TRANSACTIONS_URL = ApiConfig.buildUrl("/finances/transactions/delete"); + // 定义获取单个交易的API URL + private static final String GET_TRANSACTIONS_URL = ApiConfig.buildUrl("/finances/transactions/get"); + // 定义分页获取交易的API URL + private static final String PAGE_TRANSACTIONS_URL = ApiConfig.buildUrl("/finances/transactions/page"); + //导出 + private static final String EXPORT_TRANSACTIONS_URL = ApiConfig.buildUrl("/finances/transactions/export"); + + + /** + * 新增账目 + * + * @param type 账目类型,如1收入或2支出 + * @param categoryId 账目类别ID + * @param amount 金额 + * @param desc 描述 + * @param transactionDate 交易日期 + * @param callback 回调接口,用于处理API请求结果 + */ + public void addTransactions(Integer type, Integer categoryId, BigDecimal amount, String desc, String transactionDate, ApiCallback callback) { + // 创建一个账目请求对象,封装账目相关信息 + TransactionsRequest request = new TransactionsRequest( + null, + Constants.APP_INFO_ID, + type, + categoryId, + amount, + desc, + transactionDate + ); + // 定义JSON内容类型,用于HTTP请求体 + MediaType jsonType = MediaType.parse("application/json; charset=utf-8"); + // 将请求对象序列化为JSON格式,并创建请求体 + RequestBody body = RequestBody.create(jsonType, gson.toJson(request)); + // 构建HTTP请求,指定URL和请求方法 + Request httpRequest = new Request.Builder() + .url(ADD_TRANSACTIONS_URL) + .post(body) + .build(); + // 执行需要身份验证的请求,并处理响应 + executeAuthRequest(httpRequest, String.class, callback); + } + + + /** + * 修改账目 + * + * @param transactionsId 账目ID + * @param type 交易类型 + * @param categoryId 账目类别ID + * @param amount 交易金额 + * @param desc 交易描述 + * @param transactionDate 交易日期 + * @param callback 回调接口,用于处理API请求结果 + */ + public void updateTransactions(Integer transactionsId, Integer type, Integer categoryId, BigDecimal amount, String desc, String transactionDate, ApiCallback callback) { + // 创建一个账目请求对象,封装账目相关信息 + TransactionsRequest request = new TransactionsRequest( + transactionsId, + Constants.APP_INFO_ID, + type, + categoryId, + amount, + desc, + transactionDate + ); + // 定义JSON内容类型,用于HTTP请求体 + MediaType jsonType = MediaType.parse("application/json; charset=utf-8"); + // 将请求对象序列化为JSON格式,并创建请求体 + RequestBody body = RequestBody.create(jsonType, gson.toJson(request)); + // 构建HTTP请求对象,指定请求URL和请求体 + Request httpRequest = new Request.Builder() + .url(UPDATE_TRANSACTIONS_URL) + .post(body) + .build(); + // 执行需要身份验证的请求,并处理响应 + executeAuthRequest(httpRequest, String.class, callback); + } + + /** + * 删除交易记录 + *

+ * 通过发送GET请求到指定URL,请求中携带需要删除的交易记录ID作为查询参数,来删除指定的交易记录 + * + * @param transactionsId 需要删除的交易记录的ID + * @param callback 请求的回调,用于处理请求成功或失败的响应 + */ + public void deleteTransactions(Integer transactionsId, ApiCallback callback) { + + // 构建请求的URL,包含删除交易记录的API endpoint和查询参数 + HttpUrl url = HttpUrl.parse(DELETE_TRANSACTIONS_URL) + .newBuilder() + .addQueryParameter("id", String.valueOf(transactionsId)) + .build(); + + // 创建HTTP请求,指定请求的URL和方法(GET) + Request httpRequest = new Request.Builder() + .url(url) + .get() + .build(); + + // 执行需要身份验证的HTTP请求,并期望返回一个String类型的结果,回调处理结果 + executeAuthRequest(httpRequest, String.class, callback); + } + + + /** + * 分页查询类别信息 + *

+ * 该方法用于根据指定的条件分页查询交易类别信息它构造一个包含查询参数的请求, + * 并将其发送到API服务器处理完成后,通过回调函数返回结果 + * + * @param pageNo 分页查询的页号 + * @param pageSize 每页的记录数 + * @param type 交易类型,用于筛选交易记录 + * @param categoryId 类别ID,用于筛选交易记录 + * @param transactionDateStart 交易日期开始,用于筛选交易记录 + * @param transactionDateEnd 交易日期结束,用于筛选交易记录 + * @param callback API回调,用于处理服务器返回的响应 + */ + public void pageTransactions(Integer pageNo, Integer pageSize, Integer type, + Integer categoryId, LocalDate transactionDateStart, LocalDate transactionDateEnd, + ApiCallback callback) { + + // 创建分页请求对象,封装查询参数 + TransactionsPageRequest request = new TransactionsPageRequest( + pageNo, + pageSize, + Constants.APP_INFO_ID, + type, + categoryId, + transactionDateStart, + transactionDateEnd + ); + + // 定义JSON媒体类型,用于请求体 + MediaType jsonType = MediaType.parse("application/json; charset=utf-8"); + // 将请求对象序列化为JSON,并创建请求体 + RequestBody body = RequestBody.create(jsonType, gson.toJson(request)); + // 构建POST请求 + Request httpRequest = new Request.Builder() + .url(PAGE_TRANSACTIONS_URL) + .post(body) + .build(); + // 执行请求,并通过回调函数处理响应 + executeAuthRequest(httpRequest, String.class, callback); + } + + + /** + * 导出账目 + * + * @param startDate 开始日期 + * @param endDate 结束日期 + * @param email 邮箱地址 + * @param callback 回调接口,用于处理API请求结果 + */ + public void exportTransactions(String startDate, String endDate, String email, ApiCallback callback) { + // 创建一个账目请求对象,封装账目相关信息 + TransactionsExportRequest request = new TransactionsExportRequest( + Constants.APP_INFO_ID, + email, + startDate, + endDate + ); + // 定义JSON内容类型,用于HTTP请求体 + MediaType jsonType = MediaType.parse("application/json; charset=utf-8"); + // 将请求对象序列化为JSON格式,并创建请求体 + RequestBody body = RequestBody.create(jsonType, gson.toJson(request)); + // 构建HTTP请求对象,指定请求URL和请求体 + Request httpRequest = new Request.Builder() + .url(EXPORT_TRANSACTIONS_URL) + .post(body) + .build(); + // 执行需要身份验证的请求,并处理响应 + executeAuthRequest(httpRequest, Boolean.class, callback); + } + + + /** + * 构造方法 + * + * @param context + */ + public TransactionsService(Context context) { + super(context); + } + + + @Override + protected T decryptIfNeeded(T data) throws Exception { + // 当前接口无需解密,直接返回原始数据 + return data; + } + +} diff --git a/app/src/main/java/com/tfq/finances/network/api/index/IndexService.java b/app/src/main/java/com/tfq/finances/network/api/index/IndexService.java new file mode 100644 index 0000000..36637f8 --- /dev/null +++ b/app/src/main/java/com/tfq/finances/network/api/index/IndexService.java @@ -0,0 +1,49 @@ +package com.tfq.finances.network.api.index; + +import android.content.Context; + +import com.tfq.finances.core.constants.Constants; +import com.tfq.finances.network.api.BaseApiClient; +import com.tfq.finances.network.config.ApiCallback; +import com.tfq.finances.network.config.ApiConfig; +import com.tfq.finances.network.config.TfqConstants; +import com.tfq.finances.network.model.response.finances.index.IndexResp; + +import okhttp3.HttpUrl; +import okhttp3.Request; + +public class IndexService extends BaseApiClient { + + private static final String INDEX_URL = ApiConfig.buildUrl("/finances/member/index"); + + public IndexService(Context context) { + super(context); + } + + /** + * 首页数据 + * + * @param callback + */ + public void index(String month, ApiCallback callback) { + + HttpUrl url = HttpUrl.parse(INDEX_URL) + .newBuilder() + .addQueryParameter("appinfoId", Constants.APP_INFO_ID) + .addQueryParameter("month", month) + .build(); + + Request httpRequest = new Request.Builder() + .url(url) + .get() + .build(); + executeAuthRequest(httpRequest, IndexResp.class, callback); + } + + + @Override + protected T decryptIfNeeded(T data) throws Exception { + // 当前接口无需解密,直接返回原始数据 + return data; + } +} diff --git a/app/src/main/java/com/tfq/finances/network/api/member/MemberService.java b/app/src/main/java/com/tfq/finances/network/api/member/MemberService.java new file mode 100644 index 0000000..8c0da8d --- /dev/null +++ b/app/src/main/java/com/tfq/finances/network/api/member/MemberService.java @@ -0,0 +1,116 @@ +package com.tfq.finances.network.api.member; + +import android.content.Context; + +import com.tfq.finances.network.api.BaseApiClient; +import com.tfq.finances.network.config.ApiCallback; +import com.tfq.finances.network.config.ApiConfig; +import com.tfq.finances.network.config.TfqConstants; +import com.tfq.finances.network.model.request.member.LoginRequest; +import com.tfq.finances.network.model.request.member.SendSmsCodeRequest; +import com.tfq.finances.network.model.response.member.MemberLoginResp; +import com.tfq.finances.core.constants.Constants; + +import okhttp3.HttpUrl; +import okhttp3.MediaType; +import okhttp3.Request; +import okhttp3.RequestBody; + +/** + * 用户/会员 模块 + */ +public class MemberService extends BaseApiClient { + + private static final String SMS_LOGIN_URL = ApiConfig.buildUrl("/member/auth/sms-login"); + private static final String SEND_SMS_CODE_URL = ApiConfig.buildUrl("/member/auth/send-sms-code"); + private static final String REFRESH_TOKEN_URL = ApiConfig.buildUrl("/member/auth/refresh-token"); + + + private static final Integer SCENCE_MEMBER_LOGIN = 101; + + public MemberService(Context context) { + super(context); + } + + + /** + * 手机号+验证码登录 + * 登录成功要保存token TokenManager.saveTokens + * + * @param callback + */ + public void smsLogin(String mobile, String code, ApiCallback callback) { + //双重认证 + LoginRequest request = new LoginRequest(Constants.APP_INFO_ID, + Constants.APP_ID, + Constants.APP_TYPE, + SCENCE_MEMBER_LOGIN, + mobile, + code); + + MediaType jsonType = MediaType.parse("application/json; charset=utf-8"); + RequestBody body = RequestBody.create(jsonType, gson.toJson(request)); + + Request httpRequest = new Request.Builder() + .url(SMS_LOGIN_URL) + .post(body) + .build(); + executeAuthRequest(httpRequest, MemberLoginResp.class, callback); + } + + /** + * 发送手机验证码 + * + * @param callback + */ + public void sendSmsCode(String mobile, ApiCallback callback) { + //双重认证 + SendSmsCodeRequest request = new SendSmsCodeRequest(Constants.APP_INFO_ID, + Constants.APP_ID, + mobile, + SCENCE_MEMBER_LOGIN); + + MediaType jsonType = MediaType.parse("application/json; charset=utf-8"); + RequestBody body = RequestBody.create(jsonType, gson.toJson(request)); + + Request httpRequest = new Request.Builder() + .url(SEND_SMS_CODE_URL) + .post(body) + .build(); + executeNormalRequest(httpRequest, Boolean.class, callback); + } + + /** + * 刷新token + * + * @param refreshToken 刷新令牌 + * @return + */ + public void refreshToken(String refreshToken, ApiCallback callback) { + + HttpUrl url = HttpUrl.parse(REFRESH_TOKEN_URL) + .newBuilder() + .addQueryParameter("refreshToken", refreshToken) + //此处为 appId,标记整个应用Oauth2认证的 + .addQueryParameter("clientId", Constants.APP_ID) + .build(); + + MediaType jsonType = MediaType.parse("application/json; charset=utf-8"); + RequestBody body = RequestBody.create(jsonType, ""); + + Request httpRequest = new Request.Builder() + .header("X-Refresh-Request", "true") // 添加标记 + .url(url) + .post(body) + .build(); + executeNormalRequest(httpRequest, MemberLoginResp.class, callback); + } + + + @Override + protected T decryptIfNeeded(T data) throws Exception { + // 当前接口无需解密,直接返回原始数据 + return data; + } + +} diff --git a/app/src/main/java/com/tfq/finances/network/api/userinfo/UserInfoService.java b/app/src/main/java/com/tfq/finances/network/api/userinfo/UserInfoService.java new file mode 100644 index 0000000..06fb509 --- /dev/null +++ b/app/src/main/java/com/tfq/finances/network/api/userinfo/UserInfoService.java @@ -0,0 +1,95 @@ +package com.tfq.finances.network.api.userinfo; + +import android.content.Context; + +import com.tfq.finances.core.constants.Constants; +import com.tfq.finances.network.api.BaseApiClient; +import com.tfq.finances.network.config.ApiCallback; +import com.tfq.finances.network.config.ApiConfig; +import com.tfq.finances.network.model.request.userinfo.UserInfoRequest; +import com.tfq.finances.network.model.response.member.MemberLoginResp; +import com.tfq.finances.network.model.response.userinfo.UserInfoData; +import com.tfq.finances.network.model.response.userinfo.UserInfoResp; + +import okhttp3.HttpUrl; +import okhttp3.MediaType; +import okhttp3.Request; +import okhttp3.RequestBody; + +/** + * 用户/会员 模块 + */ +public class UserInfoService extends BaseApiClient { + + private static final String USERINFO_URL = ApiConfig.buildUrl("/finances/member/user/info"); + private static final String UPDATE_USER_INFO_URL = ApiConfig.buildUrl("/member/user/update"); + private static final String DELETE_USER_URL = ApiConfig.buildUrl("/member/user/delete"); + + public UserInfoService(Context context) { + super(context); + } + + + /** + * 获取用户基本信息 + * @param callback + */ + public void getInfo(ApiCallback callback) { + HttpUrl url = HttpUrl.parse(USERINFO_URL) + .newBuilder() + .addQueryParameter("appinfoId", Constants.APP_INFO_ID) + .build(); + + Request httpRequest = new Request.Builder() + .url(url) + .get() + .build(); + executeAuthRequest(httpRequest, UserInfoResp.class, callback); + } + + /** + * 修改用户基本信息 + * + * @param callback + */ + public void setInfo(String nickname, String avatar, ApiCallback callback) { + UserInfoRequest request = new UserInfoRequest(); + request.setNickname(nickname); + request.setAvatar(avatar); + request.setAppinfoId(Constants.APP_INFO_ID); + MediaType jsonType = MediaType.parse("application/json; charset=utf-8"); + RequestBody body = RequestBody.create(jsonType, gson.toJson(request)); + + Request httpRequest = new Request.Builder() + .url(UPDATE_USER_INFO_URL) + .post(body) + .build(); + executeAuthRequest(httpRequest, Boolean.class, callback); + } + + + /** + * 注销删除账号 ! + * @param callback + */ + public void deleteAccount(ApiCallback callback) { + HttpUrl url = HttpUrl.parse(DELETE_USER_URL) + .newBuilder() + .addQueryParameter("appinfoId", Constants.APP_INFO_ID) + .build(); + + Request httpRequest = new Request.Builder() + .url(url) + .get() + .build(); + executeAuthRequest(httpRequest, Boolean.class, callback); + } + + + @Override + protected T decryptIfNeeded(T data) throws Exception { + // 当前接口无需解密,直接返回原始数据 + return data; + } + +} diff --git a/app/src/main/java/com/tfq/finances/network/config/ApiCallback.java b/app/src/main/java/com/tfq/finances/network/config/ApiCallback.java new file mode 100644 index 0000000..8261d84 --- /dev/null +++ b/app/src/main/java/com/tfq/finances/network/config/ApiCallback.java @@ -0,0 +1,9 @@ +package com.tfq.finances.network.config; + +public interface ApiCallback { + + void onSuccess(T response); + + void onFailure(int errorCode, String errorMessage); + +} diff --git a/app/src/main/java/com/tfq/finances/network/config/ApiConfig.java b/app/src/main/java/com/tfq/finances/network/config/ApiConfig.java new file mode 100644 index 0000000..881ef36 --- /dev/null +++ b/app/src/main/java/com/tfq/finances/network/config/ApiConfig.java @@ -0,0 +1,14 @@ +package com.tfq.finances.network.config; + +public class ApiConfig { + + public static final String BASE_URL = TfqConfig.BASE_URL; + + public static String buildUrl(String pathFormat, Object... params) { + return BASE_URL + String.format(pathFormat, params); + } + + public static String buildH5Url(String pathFormat, Object... params) { + return TfqConfig.H5_BASE_URL + String.format(pathFormat, params); + } +} diff --git a/app/src/main/java/com/tfq/finances/network/config/TfqConfig.java b/app/src/main/java/com/tfq/finances/network/config/TfqConfig.java new file mode 100644 index 0000000..5aeef51 --- /dev/null +++ b/app/src/main/java/com/tfq/finances/network/config/TfqConfig.java @@ -0,0 +1,34 @@ +package com.tfq.finances.network.config; + +public class TfqConfig { + + + private TfqConfig() { + throw new AssertionError("No instances"); + } + + /** + * 域名 + */ + //生产环境 + public static final String BASE_DOMAIN = "http://www.90000li.com/"; + + + /** + * 同风起后端基础URL + */ + //生产环境 + public static final String BASE_URL = "http://www.90000li.com/app-api"; + + //杨煜-本地调试测试环境 +// public static final String BASE_URL = "http://192.168.31.113:48080/app-api"; + + /** + * H5页面基础URL + */ + public static final String H5_BASE_URL = "http://www.90000li.com/tfq-h5"; + //public static final String H5_BASE_URL = "http://192.168.31.113:3000"; + + + +} diff --git a/app/src/main/java/com/tfq/finances/network/config/TfqConstants.java b/app/src/main/java/com/tfq/finances/network/config/TfqConstants.java new file mode 100644 index 0000000..fb8fc21 --- /dev/null +++ b/app/src/main/java/com/tfq/finances/network/config/TfqConstants.java @@ -0,0 +1,109 @@ +package com.tfq.finances.network.config; + +public class TfqConstants { + public static final String CODE_200 = "200"; + public static final Integer CODE_SUCCESS = 200; + public static final Integer CODE_SUCCESS_0 = 0; + public static final String CODE_0 = "0"; + public static final String MESSAGE_KEY = "message"; + public static final String MSG_KEY = "msg"; + + + // 错误码 + public static final int UNKNOWN_ERROR = -1; + public static final int TOKEN_EXPIRED_CODE = 401; + + // 错误消息 + public static final String DEFAULT_ERROR_MSG = "请求失败,请稍后重试"; + + + /** + * 接口请求异常 + */ + public static final Integer URL_REQUEST_ERROR = 401; + public static final String URL_REQUEST_ERROR_MSG = "接口请求异常"; + /** + * 解析异常 + */ + public static final Integer PARSE_ERROR = 501; + public static final String PARSE_ERROR_MSG = "解析异常"; + /** + * 解密异常 + */ + public static final Integer DECRYPTION_ERROR = 502; + public static final String DECRYPTION_ERROR_MSG = "解密异常"; + public static final int TOKEN_INVALID_ERROR = 400; + + + /** + * 开屏是否缓存 + */ + public static boolean advCache_Splash; + /** + * 激励视频是否缓存 + */ + public static boolean advCache_Reward; + /** + * 信息流是否缓存 + */ + public static boolean advCache_Feed; + /** + * 插全屏是否缓存 + */ + public static boolean advCache_CQP; + /** + * banner是否缓存 + */ + public static boolean advCache_Banner; + /** + * draw信息流是否缓存 + */ + public static boolean advCache_Draw; + + /** + * 一级页面刷新时间(一级是指首页中进入的包含首页) + */ + public static Integer page1; + /** + * 二级页面刷新时间(二级是指中间页中进入的包含中间页) + */ + public static Integer page2; + /** + * 三级页面刷新时间(三级是指设置页即最后一个页中进入的包含设置页) + */ + public static Integer page3; + +// +// /** +// * 开屏广告位ID +// */ +// public static String advSpaceId_Splash=App.getContext().getResources().getString(R.string.csjIdSplash); +// /** +// * 插全屏广告位ID +// */ +// public static String advSpaceId_CQP = App.getContext().getResources().getString(R.string.csjIdCQP); +// /** +// * 信息流广告位ID1 +// */ +// public static String advSpaceId_Feed1 = App.getContext().getResources().getString(R.string.csjIdFeed1); +// /** +// * 信息流广告位ID2 +// */ +// public static String advSpaceId_Feed2 = App.getContext().getResources().getString(R.string.csjIdFeed2); +// /** +// * 信息流广告位ID3 +// */ +// public static String advSpaceId_Feed3 = App.getContext().getResources().getString(R.string.csjIdFeed3); +// /** +// * 激励视频广告位ID +// */ +// public static String advSpaceId_Reward = App.getContext().getResources().getString(R.string.csjIdReward); +// /** +// * banner广告位ID +// */ +// public static String advSpaceId_Banner = App.getContext().getResources().getString(R.string.csjIdBanner); +// /** +// * draw广告位ID +// */ +// public static String advSpaceId_Draw = App.getContext().getResources().getString(R.string.csjIdDraw); +} diff --git a/app/src/main/java/com/tfq/finances/network/config/token/TokenManager.java b/app/src/main/java/com/tfq/finances/network/config/token/TokenManager.java new file mode 100644 index 0000000..b8fdde9 --- /dev/null +++ b/app/src/main/java/com/tfq/finances/network/config/token/TokenManager.java @@ -0,0 +1,244 @@ +package com.tfq.finances.network.config.token; + +import android.content.Context; +import android.content.SharedPreferences; +import android.util.Log; + +import com.tfq.finances.app.App; +import com.tfq.finances.core.enums.TokenRefreshLock; +import com.tfq.finances.network.api.adv.AdService; +import com.tfq.finances.network.api.member.MemberService; +import com.tfq.finances.network.config.ApiCallback; +import com.tfq.finances.network.model.response.adv.AdvFlagResp; +import com.tfq.finances.network.model.response.member.MemberLoginResp; + +import java.util.concurrent.atomic.AtomicBoolean; + +public class TokenManager { + + /** + * 会员服务 + */ + //private final MemberService memberService = new MemberService(App.getContext()); + /** + * 刷新Token锁 + */ + private final AtomicBoolean isRefreshing = new AtomicBoolean(false); + + // "userId": 286, + // "accessToken": "38b10a334c244e1ba2688f8dfecc2c58", + // "refreshToken": "c002b31551b84a2792f1bcc820f078dc", + // "expiresTime": 1745732545783, + // "openid": null + + /** + * Token管理类 + */ + private static volatile TokenManager instance; + + /** + * SharedPreferences 实例 存储Token信息 + */ + private final SharedPreferences prefs; + + + /** + * 获取Token管理类实例 + * 双重检查锁实现单例 + * + * @param context + * @return + */ + public static TokenManager getInstance(Context context) { + if (instance == null) { + synchronized (TokenManager.class) { + if (instance == null) { + instance = new TokenManager(context); + } + } + } + return instance; + } + + private TokenManager(Context context) { + prefs = context.getSharedPreferences("auth", Context.MODE_PRIVATE); + } + + /** + * 保存Token信息 + * + * @param accessToken + * @param refreshToken + * @param expiresIn + */ + public void saveTokens(String accessToken, String refreshToken, long expiresIn, long userId) { + prefs.edit() + .putString("accessToken", accessToken) + .putString("refreshToken", refreshToken) + .putLong("userId", userId) + .putLong("expiresTime", System.currentTimeMillis() + expiresIn * 1000) + .apply(); + } + + /** + * 判断Token是否过期 + * + * @return + */ + private boolean isAccessTokenValid() { + long expiresAt = prefs.getLong("expiresTime", 0); + return System.currentTimeMillis() < expiresAt - 300000; // 提前5分钟判定过期 + } + + /** + * 刷新Token + * 同步刷新Token(带锁) + * + * @return + */ + public synchronized boolean refreshToken(MemberService memberService) { + // 如果已经在刷新中,则等待刷新完成 + if (isRefreshing.get()) { + waitForRefresh(); + return isTokenValid(); // 返回当前token是否有效 + } + + // 检查是否有refreshToken可用 + if (!hasRefreshToken()) { + return false; + } + isRefreshing.set(true); + + + try { + // 创建同步锁对象 + final Object lock = new Object(); + final AtomicBoolean refreshSuccess = new AtomicBoolean(false); + + // 调用刷新接口 + memberService.refreshToken( + prefs.getString("refreshToken", ""), + new ApiCallback() { + @Override + public void onSuccess(MemberLoginResp response) { + // 保存新的token信息 + saveTokens( + response.getAccessToken(), + response.getRefreshToken(), + response.getExpiresTime(), + response.getUserId() + ); + refreshSuccess.set(true); + synchronized (lock) { + lock.notifyAll(); + } + } + + @Override + public void onFailure(int errorCode, String errorMessage) { + refreshSuccess.set(false); + synchronized (lock) { + lock.notifyAll(); + } + // 可以添加日志记录 + Log.e("TokenManager", "Refresh token failed: " + errorMessage); + } + }); + + // 等待刷新完成(最多等待30秒) + synchronized (lock) { + lock.wait(30_000); + } + + return refreshSuccess.get(); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + return false; + } finally { + isRefreshing.set(false); + // 通知所有等待的线程 + synchronized (TokenRefreshLock.INSTANCE) { + TokenRefreshLock.INSTANCE.notifyAll(); + } + } + } + + +// public void refreshTokenAsync(ApiCallback callback) { +// new Thread(() -> { +// boolean success = refreshToken(); +// if (callback != null) { +// if (success) { +// callback.onSuccess(true); +// } else { +// callback.onFailure(-1, "Refresh token failed"); +// } +// } +// }).start(); +// } + + /** + * 异步刷新Token + * + * @param callback 刷新结果回调 + */ + public void refreshTokenAsync(MemberService service, ApiCallback callback) { + new Thread(() -> { + boolean result = refreshToken(service); + if (result) { + callback.onSuccess(true); + } else { + callback.onFailure(500, "Refresh failed"); + } + }).start(); + } + + + public String getAccessToken() { + return prefs.getString("accessToken", ""); + } + + public long getUserId() { + return prefs.getLong("userId", -1); + } + + public boolean hasRefreshToken() { + return prefs.contains("refreshToken"); + } + + /** + * 清除Token + */ + public void clearTokens() { + prefs.edit() + .remove("accessToken") + .remove("refreshToken") + .remove("expiresTime") + .remove("userId") + .apply(); + } + + + /** + * 判断Token是否有效 + * + * @return + */ + public boolean isTokenValid() { + return prefs.contains("accessToken") && isAccessTokenValid(); + } + + + /** + * 等待Token刷新完成 + */ + private void waitForRefresh() { + synchronized (TokenRefreshLock.INSTANCE) { + try { + TokenRefreshLock.INSTANCE.wait(30_000); // 超时30秒 + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + } + } + } +} diff --git a/app/src/main/java/com/tfq/finances/network/interceptor/AuthInterceptor.java b/app/src/main/java/com/tfq/finances/network/interceptor/AuthInterceptor.java new file mode 100644 index 0000000..5940ea3 --- /dev/null +++ b/app/src/main/java/com/tfq/finances/network/interceptor/AuthInterceptor.java @@ -0,0 +1,145 @@ +package com.tfq.finances.network.interceptor; + +import android.content.Context; +import android.content.Intent; +import android.os.Handler; +import android.os.Looper; + +import com.google.gson.Gson; +import com.google.gson.reflect.TypeToken; +import com.tfq.finances.core.enums.TokenRefreshLock; +import com.tfq.finances.finances.activity.Activity_Login; +import com.tfq.finances.network.api.member.MemberService; +import com.tfq.finances.network.model.response.base.TfqBaseResult; +import com.tfq.finances.network.config.token.TokenManager; + +import java.io.IOException; +import java.lang.reflect.Type; + +import okhttp3.Interceptor; +import okhttp3.Request; +import okhttp3.Response; +import okhttp3.ResponseBody; + +/** + * 鉴权拦截器 + */ +public class AuthInterceptor implements Interceptor { + private final TokenManager tokenManager; + private final Context context; + + public AuthInterceptor(Context context) { + this.context = context; + this.tokenManager = TokenManager.getInstance(context); + } + + + /** + * 拦截器 + * + * @param chain + * @return + * @throws IOException + */ + @Override + public Response intercept(Chain chain) throws IOException { + Request request = chain.request(); + // 跳过刷新token请求 + if (request.header("X-Refresh-Request") != null) { + return chain.proceed(request); + } + // 首次请求 + Response response = chain.proceed(addTokenIfNeeded(request)); + // 仅处理成功响应 + if (response.isSuccessful()) { + try { + ResponseBody responseBody = response.peekBody(Long.MAX_VALUE); + String json = responseBody.string(); + // 解析 + Type type = new TypeToken>() { + }.getType(); + Gson gson = new Gson(); + TfqBaseResult baseResp = gson.fromJson(json, type); + // 解析业务状态码, + if (isTokenError(baseResp.getCode())) { + //如果token失效 + return handleTokenError(chain, request, response); + } + } catch (Exception e) { + // JSON解析错误处理 + throw new IOException(e); + } + } + return response; + } + + private Response handleTokenError(Chain chain, Request originalReq, Response originalResp) throws IOException { + // 加入全局刷新队列 + synchronized (TokenRefreshLock.INSTANCE) { + // 双重检查 + if (!tokenManager.isTokenValid()) { + try { + // 同步刷新Token + boolean success = tokenManager.refreshToken(new MemberService(context)); + + if (!success) { + //刷新token失败 + //TODO 跳转登录,或者游客模式 + startLoginActivity(); + throw new IOException("Refresh failed"); + } + } catch (Exception e) { + // TODO 全局处理 or 跳转登录 + TokenRefreshLock.INSTANCE.notifyAll(); + startLoginActivity(); + } + } + } + originalResp.close(); + return chain.proceed(addTokenIfNeeded(originalReq)); + } + + /** + * 判断是否Token相关错误 + * + * @param code + * @return + */ + private boolean isTokenError(int code) { + return code == 400 || code == 401; + } + + /** + * 添加Token头 + * + * @param original + * @return + */ + private Request addTokenIfNeeded(Request original) { + //if (requiresAuth(original)) { + return original.newBuilder() + .header("Authorization", "Bearer " + tokenManager.getAccessToken()) + .build(); + // } + //return original; + } + + private Response proceedWithToken(Chain chain, Request request) throws IOException { + String token = tokenManager.getAccessToken(); + return chain.proceed(request.newBuilder() + .header("Authorization", "Bearer " + token) + .build()); + } + + /** + * 启动登录Activity + */ + private void startLoginActivity() { + //TODO 待测试 + new Handler(Looper.getMainLooper()).post(() -> { + Intent intent = new Intent(context, Activity_Login.class); + intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + context.startActivity(intent); + }); + } +} diff --git a/app/src/main/java/com/tfq/finances/network/model/request/base/TfqBaseRequest.kt b/app/src/main/java/com/tfq/finances/network/model/request/base/TfqBaseRequest.kt new file mode 100644 index 0000000..1a41620 --- /dev/null +++ b/app/src/main/java/com/tfq/finances/network/model/request/base/TfqBaseRequest.kt @@ -0,0 +1,21 @@ +// 文件名:TfqBaseRequest.kt +package com.tfq.finances.network.model.request.base + + +/** + * 公共请求参数(Kotlin 数据类版) + * 保持与原始Java类100%兼容的改造方案 + */ +open class TfqBaseRequest( + open var appId: String = "", // 使用var保持可变性 + open var channel: String = "", // 默认空字符串避免NPE + open var appVersion: String = "", + open var appPacketname: String = "" +) { + + /** + * Java兼容构造方法(无参构造) + * 解决Java反射创建实例的需要 + */ + constructor() : this("", "", "", "") +} \ No newline at end of file diff --git a/app/src/main/java/com/tfq/finances/network/model/request/feedback/FeedbackAddRequest.kt b/app/src/main/java/com/tfq/finances/network/model/request/feedback/FeedbackAddRequest.kt new file mode 100644 index 0000000..f1d0710 --- /dev/null +++ b/app/src/main/java/com/tfq/finances/network/model/request/feedback/FeedbackAddRequest.kt @@ -0,0 +1,16 @@ +package com.tfq.finances.network.model.request.feedback + +import com.tfq.finances.network.model.request.base.TfqBaseRequest + +data class FeedbackAddRequest( + // 继承父类字段(通过组合而非继承) +// override var appId: String = "", +// override var channel: String = "", +// override var appVersion: String = "", +// override var appPacketname: String = "", + + var content: String = "", + val phone: String = "", + val appName: String = "" + +): TfqBaseRequest() // 父类属性通过其他方式初始化 \ No newline at end of file diff --git a/app/src/main/java/com/tfq/finances/network/model/request/finances/budgets/BudgetsRequest.kt b/app/src/main/java/com/tfq/finances/network/model/request/finances/budgets/BudgetsRequest.kt new file mode 100644 index 0000000..2dd29a4 --- /dev/null +++ b/app/src/main/java/com/tfq/finances/network/model/request/finances/budgets/BudgetsRequest.kt @@ -0,0 +1,26 @@ +package com.tfq.finances.network.model.request.finances.budgets + +import java.math.BigDecimal + +data class BudgetsRequest( + /** + * 应用id + */ + var appinfoId: String = "", + /** + * 用户id + */ + var userId: Long = -1, + /** + * 金额 + */ + var amount: BigDecimal = BigDecimal.ZERO, + /** + * 月份 yyyy-MM + */ + var month: String = "", + /** + * 预算类型(默认1总2分类预算) + */ + var type: Int = 1, +) \ No newline at end of file diff --git a/app/src/main/java/com/tfq/finances/network/model/request/finances/categories/CategoriesAllRequest.kt b/app/src/main/java/com/tfq/finances/network/model/request/finances/categories/CategoriesAllRequest.kt new file mode 100644 index 0000000..e658929 --- /dev/null +++ b/app/src/main/java/com/tfq/finances/network/model/request/finances/categories/CategoriesAllRequest.kt @@ -0,0 +1,20 @@ +package com.tfq.finances.network.model.request.finances.categories + +data class CategoriesAllRequest( + + /** + * 应用id + */ + var appinfoId: String? = null, + + /** + * userId + */ + var userId: Long? = null, + + /** + * 类型 + */ + var type: Int? = null + +) \ No newline at end of file diff --git a/app/src/main/java/com/tfq/finances/network/model/request/finances/categories/CategoriesRequest.kt b/app/src/main/java/com/tfq/finances/network/model/request/finances/categories/CategoriesRequest.kt new file mode 100644 index 0000000..6a52998 --- /dev/null +++ b/app/src/main/java/com/tfq/finances/network/model/request/finances/categories/CategoriesRequest.kt @@ -0,0 +1,34 @@ +package com.tfq.finances.network.model.request.finances.categories + +import java.math.BigDecimal + +data class CategoriesRequest( + + /** + * id + */ + var id: Int? = null, + + /** + * 应用id + */ + var appinfoId: String = "", + + /** + * 图标 + */ + var iconUrl: String = "", + + /** + * 名称 + */ + var name: String = "", + + /** + * 类型 + */ + var type: Int? = null + + + +) \ No newline at end of file diff --git a/app/src/main/java/com/tfq/finances/network/model/request/finances/transactions/TransactionsExportRequest.kt b/app/src/main/java/com/tfq/finances/network/model/request/finances/transactions/TransactionsExportRequest.kt new file mode 100644 index 0000000..ee2ba72 --- /dev/null +++ b/app/src/main/java/com/tfq/finances/network/model/request/finances/transactions/TransactionsExportRequest.kt @@ -0,0 +1,33 @@ +package com.tfq.finances.network.model.request.finances.transactions + +import java.math.BigDecimal +import java.time.LocalDate + +/** + * 账目 + */ +data class TransactionsExportRequest( + + /** + * 应用id + */ + var appinfoId: String = "", + /** + * 邮箱 + */ + var email: String = "", + + + /** + * 发生日期 起始时间 + * yyyy-MM-dd + */ + var startDate: String? = null, + + /** + * 发生日期 结束时间 + * yyyy-MM-dd + */ + var endDate: String? = null, + +) \ No newline at end of file diff --git a/app/src/main/java/com/tfq/finances/network/model/request/finances/transactions/TransactionsPageRequest.kt b/app/src/main/java/com/tfq/finances/network/model/request/finances/transactions/TransactionsPageRequest.kt new file mode 100644 index 0000000..459dd65 --- /dev/null +++ b/app/src/main/java/com/tfq/finances/network/model/request/finances/transactions/TransactionsPageRequest.kt @@ -0,0 +1,45 @@ +package com.tfq.finances.network.model.request.finances.transactions + +import java.math.BigDecimal +import java.time.LocalDate + +/** + * 账目 + */ +data class TransactionsPageRequest( + + /** + * 分页参数 + */ + var pageNo: Int? = 1, + /** + * 分页参数 + */ + var pageSize: Int? = 10, + + /** + * 应用id + */ + var appinfoId: String = "", + + /** + * 类型 + */ + var type: Int? = null, + + /** + * categoryId + */ + var categoryId: Int? = null, + + /** + * 发生日期 + */ + var transactionDateStart: LocalDate? = null, + + /** + * 发生日期 + */ + var transactionDateEnd: LocalDate? = null + +) \ No newline at end of file diff --git a/app/src/main/java/com/tfq/finances/network/model/request/finances/transactions/TransactionsRequest.kt b/app/src/main/java/com/tfq/finances/network/model/request/finances/transactions/TransactionsRequest.kt new file mode 100644 index 0000000..69b2955 --- /dev/null +++ b/app/src/main/java/com/tfq/finances/network/model/request/finances/transactions/TransactionsRequest.kt @@ -0,0 +1,46 @@ +package com.tfq.finances.network.model.request.finances.transactions + +import java.math.BigDecimal +import java.time.LocalDate + +/** + * 账目 + */ +data class TransactionsRequest( + + /** + * id + */ + var id: Int? = null, + + /** + * 应用id + */ + var appinfoId: String = "", + + /** + * 类型 + */ + var type: Int? = null, + + /** + * categoryId + */ + var categoryId: Int? = null, + + /** + * 金额 + */ + var amount: BigDecimal = BigDecimal.ZERO, + + /** + * 备注 + */ + var userDesc: String = "", + + /** + * 发生日期 + */ + var transactionDate: String? = null + +) \ No newline at end of file diff --git a/app/src/main/java/com/tfq/finances/network/model/request/member/LoginRequest.kt b/app/src/main/java/com/tfq/finances/network/model/request/member/LoginRequest.kt new file mode 100644 index 0000000..21da135 --- /dev/null +++ b/app/src/main/java/com/tfq/finances/network/model/request/member/LoginRequest.kt @@ -0,0 +1,30 @@ +package com.tfq.finances.network.model.request.member + +data class LoginRequest( + /** + * appId + */ + var appinfoId: String = "", + + /** + * clientId + */ + var clientId: String = "", + /** + * appType + */ + var appType: String = "", + /** + * 登录场景 + */ + var scence: Int = 101, + + /** + * 手机号 + */ + var mobile: String = "", + /** + * 验证码 + */ + var code: String = "", +) \ No newline at end of file diff --git a/app/src/main/java/com/tfq/finances/network/model/request/member/SendSmsCodeRequest.kt b/app/src/main/java/com/tfq/finances/network/model/request/member/SendSmsCodeRequest.kt new file mode 100644 index 0000000..b7e5529 --- /dev/null +++ b/app/src/main/java/com/tfq/finances/network/model/request/member/SendSmsCodeRequest.kt @@ -0,0 +1,20 @@ +package com.tfq.finances.network.model.request.member + +data class SendSmsCodeRequest( + /** + * appId + */ + var appinfoId: String = "", + /** + * clientId + */ + var clientId: String = "", + /** + * 手机号 + */ + var mobile: String = "", + /** + * 验证场景的编号 + */ + var scene: Int = 1, +) \ No newline at end of file diff --git a/app/src/main/java/com/tfq/finances/network/model/request/userinfo/UserInfoRequest.kt b/app/src/main/java/com/tfq/finances/network/model/request/userinfo/UserInfoRequest.kt new file mode 100644 index 0000000..a8e066f --- /dev/null +++ b/app/src/main/java/com/tfq/finances/network/model/request/userinfo/UserInfoRequest.kt @@ -0,0 +1,18 @@ +package com.tfq.finances.network.model.request.userinfo + +import com.tfq.finances.network.model.request.base.TfqBaseRequest + +data class UserInfoRequest( + /** + * appinfoId + */ + var appinfoId: String = "", + /** + * 昵称 + */ + var nickname: String = "", + /** + * 头像 + */ + var avatar: String = "", +) \ No newline at end of file diff --git a/app/src/main/java/com/tfq/finances/network/model/response/adv/AdvCacheConfig.kt b/app/src/main/java/com/tfq/finances/network/model/response/adv/AdvCacheConfig.kt new file mode 100644 index 0000000..f5da6b5 --- /dev/null +++ b/app/src/main/java/com/tfq/finances/network/model/response/adv/AdvCacheConfig.kt @@ -0,0 +1,33 @@ +package com.tfq.finances.network.model.response.adv + +data class AdvCacheConfig( + /** + * 开屏广告缓存开关 + */ + val adv1CacheFlag: String = "0", + + /** + * 激励视频缓存开关 + */ + val adv2CacheFlag: String = "0", + + /** + * 信息流缓存开关 + */ + val adv3CacheFlag: String = "0", + + /** + * 插全屏缓存开关 + */ + val adv4CacheFlag: String = "0", + + /** + * Banner缓存开关 + */ + val adv5CacheFlag: String = "0", + + /** + * Draw信息流缓存开关 + */ + val adv6CacheFlag: String = "0" +) \ No newline at end of file diff --git a/app/src/main/java/com/tfq/finances/network/model/response/adv/AdvCacheSwitchData.kt b/app/src/main/java/com/tfq/finances/network/model/response/adv/AdvCacheSwitchData.kt new file mode 100644 index 0000000..bfa2699 --- /dev/null +++ b/app/src/main/java/com/tfq/finances/network/model/response/adv/AdvCacheSwitchData.kt @@ -0,0 +1,40 @@ +package com.tfq.finances.network.model.response.adv + +import com.google.gson.annotations.SerializedName +import com.tfq.finances.core.enums.CommonFlagEnum + +/** + * 各个广告类型是否开启预加载 + */ +data class AdvCacheSwitchData( + + @SerializedName("adv1CacheFlag") + var adv1CacheFlag: String = "1", + + @SerializedName("adv2CacheFlag") + var adv2CacheFlag: String = "1", + + @SerializedName("adv3CacheFlag") + var adv3CacheFlag: String = "1", + + @SerializedName("adv4CacheFlag") + var adv4CacheFlag: String = "1", + + @SerializedName("adv5CacheFlag") + var adv5CacheFlag: String = "1", + + @SerializedName("adv6CacheFlag") + var adv6CacheFlag: String = "1", +) { + + /** + * 业务状态判断 + */ + fun isAdv1CacheFlag(): Boolean = adv1CacheFlag == CommonFlagEnum.YES.value + fun isAdv2CacheFlag(): Boolean = adv2CacheFlag == CommonFlagEnum.YES.value + fun isAdv3CacheFlag(): Boolean = adv3CacheFlag == CommonFlagEnum.YES.value + fun isAdv4CacheFlag(): Boolean = adv4CacheFlag == CommonFlagEnum.YES.value + fun isAdv5CacheFlag(): Boolean = adv5CacheFlag == CommonFlagEnum.YES.value + fun isAdv6CacheFlag(): Boolean = adv6CacheFlag == CommonFlagEnum.YES.value + +} \ No newline at end of file diff --git a/app/src/main/java/com/tfq/finances/network/model/response/adv/AdvConfigResp.kt b/app/src/main/java/com/tfq/finances/network/model/response/adv/AdvConfigResp.kt new file mode 100644 index 0000000..33314f4 --- /dev/null +++ b/app/src/main/java/com/tfq/finances/network/model/response/adv/AdvConfigResp.kt @@ -0,0 +1,16 @@ +package com.tfq.finances.network.model.response.adv + +/** + * 广告设置(缓存和页面刷新广告间隔时间) + */ +data class AdvConfigResp( + /** + * 缓存配置 + */ + val cacheConfig: AdvCacheConfig, + + /** + * 刷新配置 + */ + val refreshConfig: AdvRefreshConfig +) \ No newline at end of file diff --git a/app/src/main/java/com/tfq/finances/network/model/response/adv/AdvFlagResp.kt b/app/src/main/java/com/tfq/finances/network/model/response/adv/AdvFlagResp.kt new file mode 100644 index 0000000..e67181a --- /dev/null +++ b/app/src/main/java/com/tfq/finances/network/model/response/adv/AdvFlagResp.kt @@ -0,0 +1,43 @@ +package com.tfq.finances.network.model.response.adv + +data class AdvFlagResp( + + /** + * 开屏,0关闭1开启 + */ + var adv1Flag: String = "0", + + /** + * 激励视频 + */ + var adv2Flag: String = "0", + + /** + * 信息流 + */ + var adv3Flag: String = "0", + /** + *插全屏 + */ + var adv4Flag: String = "0", + /** + *banner + */ + var adv5Flag: String = "0", + /** + *draw信息流 + */ + var adv6Flag: String = "0", + /** + *插全屏间隔时间 + */ + var adv4Wait: Int = 60, + /** + *广告设置 + */ + var advSettings: AdvConfigResp? = null, + /** + *广告位 + */ + var advSpace: AdvSpaceResp? = null +) \ No newline at end of file diff --git a/app/src/main/java/com/tfq/finances/network/model/response/adv/AdvRefreshConfig.kt b/app/src/main/java/com/tfq/finances/network/model/response/adv/AdvRefreshConfig.kt new file mode 100644 index 0000000..037eaad --- /dev/null +++ b/app/src/main/java/com/tfq/finances/network/model/response/adv/AdvRefreshConfig.kt @@ -0,0 +1,21 @@ +package com.tfq.finances.network.model.response.adv + +/** + * 控制页面广告刷新间隔时间 + */ +data class AdvRefreshConfig( + /** + * 页面1广告位刷新间隔 + */ + val page1: Int = 2, + + /** + * 页面2广告位刷新间隔 + */ + val page2: Int = 2, + + /** + * 页面3广告位刷新间隔 + */ + val page3: Int = 2 +) \ No newline at end of file diff --git a/app/src/main/java/com/tfq/finances/network/model/response/adv/AdvSettingsResp.kt b/app/src/main/java/com/tfq/finances/network/model/response/adv/AdvSettingsResp.kt new file mode 100644 index 0000000..7ecd94a --- /dev/null +++ b/app/src/main/java/com/tfq/finances/network/model/response/adv/AdvSettingsResp.kt @@ -0,0 +1,15 @@ +package com.tfq.finances.network.model.response.adv + + +import com.google.gson.annotations.SerializedName + +/** + * 广告设置响应(组合嵌套结构) + */ +data class AdvSettingsResp( + @SerializedName("advSlotRefreshInterval") + val refreshInterval: AdvSlotRefreshIntervalData = AdvSlotRefreshIntervalData(), + + @SerializedName("advCacheSwitch") + val cacheSwitch: AdvCacheSwitchData = AdvCacheSwitchData() +) diff --git a/app/src/main/java/com/tfq/finances/network/model/response/adv/AdvSlotRefreshIntervalData.kt b/app/src/main/java/com/tfq/finances/network/model/response/adv/AdvSlotRefreshIntervalData.kt new file mode 100644 index 0000000..3af5fa7 --- /dev/null +++ b/app/src/main/java/com/tfq/finances/network/model/response/adv/AdvSlotRefreshIntervalData.kt @@ -0,0 +1,21 @@ +package com.tfq.finances.network.model.response.adv + +import com.google.gson.annotations.SerializedName + +data class AdvSlotRefreshIntervalData( + /** + * 页面1的刷新间隔(秒) + */ + @SerializedName("page1") + val page1: Int = 5, + /** + * 页面2的刷新间隔(秒) + */ + @SerializedName("page2") + val page2: Int = 5, + /** + * 页面3的刷新间隔(秒) + */ + @SerializedName("page3") + val page3: Int = 5, +) \ No newline at end of file diff --git a/app/src/main/java/com/tfq/finances/network/model/response/adv/AdvSpaceData.kt b/app/src/main/java/com/tfq/finances/network/model/response/adv/AdvSpaceData.kt new file mode 100644 index 0000000..38d2ccb --- /dev/null +++ b/app/src/main/java/com/tfq/finances/network/model/response/adv/AdvSpaceData.kt @@ -0,0 +1,22 @@ +package com.tfq.finances.network.model.response.adv + +import com.google.gson.annotations.SerializedName + +/** + * 广告位信息 + */ +data class AdvSpaceData( + + /** + * 广告位序号 + */ + @SerializedName("seqNo") + var seqNo: Int = 0, + + /** + * 广告位ID + */ + @SerializedName("spaceId") + var spaceId: String = "" + +) \ No newline at end of file diff --git a/app/src/main/java/com/tfq/finances/network/model/response/adv/AdvSpaceResp.kt b/app/src/main/java/com/tfq/finances/network/model/response/adv/AdvSpaceResp.kt new file mode 100644 index 0000000..0a34914 --- /dev/null +++ b/app/src/main/java/com/tfq/finances/network/model/response/adv/AdvSpaceResp.kt @@ -0,0 +1,28 @@ +package com.tfq.finances.network.model.response.adv + +import com.google.gson.annotations.SerializedName + +/** + * 广告数据 + */ +data class AdvSpaceResp( + + @SerializedName("adv1Space") + var adv1Space: List? = null, + + @SerializedName("adv2Space") + var adv2Space: List? = null, + + @SerializedName("adv3Space") + var adv3Space: List? = null, + + @SerializedName("adv4Space") + var adv4Space: List? = null, + + @SerializedName("adv5Space") + var adv5Space: List? = null, + + @SerializedName("adv6Space") + var adv6Space: List? = null + +) \ No newline at end of file diff --git a/app/src/main/java/com/tfq/finances/network/model/response/base/TfqBaseResult.kt b/app/src/main/java/com/tfq/finances/network/model/response/base/TfqBaseResult.kt new file mode 100644 index 0000000..90c74aa --- /dev/null +++ b/app/src/main/java/com/tfq/finances/network/model/response/base/TfqBaseResult.kt @@ -0,0 +1,36 @@ +// 文件名:TfqBaseResult.kt +package com.tfq.finances.network.model.response.base + +import com.google.gson.annotations.SerializedName +import com.tfq.finances.network.config.TfqConstants + +/** + * 统一响应结构(组合式设计) + * @param T 实际数据类型 + */ +data class TfqBaseResult( + @SerializedName("code") + var code: Int = -1, // 默认-1表示未初始化 + + @SerializedName("data") + var data: T? = null, // 使用可空类型 + + @SerializedName("msg") + var message: String = "" +) { + /** + * Java兼容性方法 + */ + // 兼容旧版getMsg() + fun getMsg(): String = message + + // 兼容旧版setMsg() + fun setMsg(msg: String) { + this.message = msg + } + + /** + * 业务状态判断 + */ + fun isSuccess(): Boolean = code == TfqConstants.CODE_SUCCESS +} \ No newline at end of file diff --git a/app/src/main/java/com/tfq/finances/network/model/response/finances/budgets/BudgetsMonthResp.kt b/app/src/main/java/com/tfq/finances/network/model/response/finances/budgets/BudgetsMonthResp.kt new file mode 100644 index 0000000..b6d6a7f --- /dev/null +++ b/app/src/main/java/com/tfq/finances/network/model/response/finances/budgets/BudgetsMonthResp.kt @@ -0,0 +1,18 @@ +package com.tfq.finances.network.model.response.finances.budgets + +import com.google.gson.annotations.SerializedName +import java.math.BigDecimal + +data class BudgetsMonthResp( + @SerializedName("amount") + var amount: BigDecimal = BigDecimal.ZERO, + + @SerializedName("totalExpenses") + var totalExpenses: BigDecimal = BigDecimal.ZERO, + + @SerializedName("remainingAmount") + var remainingAmount: BigDecimal = BigDecimal.ZERO, + + @SerializedName("month") + var month: String = "", +) diff --git a/app/src/main/java/com/tfq/finances/network/model/response/finances/budgets/BudgetsResp.kt b/app/src/main/java/com/tfq/finances/network/model/response/finances/budgets/BudgetsResp.kt new file mode 100644 index 0000000..e520f57 --- /dev/null +++ b/app/src/main/java/com/tfq/finances/network/model/response/finances/budgets/BudgetsResp.kt @@ -0,0 +1,23 @@ +package com.tfq.finances.network.model.response.finances.budgets + +import com.google.gson.annotations.SerializedName +import java.math.BigDecimal + +data class BudgetsResp( + + @SerializedName("userId") + var userId: Long = 0, + + @SerializedName("categoriesId") + var categoriesId: Long = 0, + + @SerializedName("granularity") + var granularity: String = "", + + @SerializedName("amount") + var amount: BigDecimal = BigDecimal.ZERO, + + @SerializedName("type") + var type: Long = 0, + +) diff --git a/app/src/main/java/com/tfq/finances/network/model/response/finances/categories/CategoriesAllResp.kt b/app/src/main/java/com/tfq/finances/network/model/response/finances/categories/CategoriesAllResp.kt new file mode 100644 index 0000000..48b890d --- /dev/null +++ b/app/src/main/java/com/tfq/finances/network/model/response/finances/categories/CategoriesAllResp.kt @@ -0,0 +1,19 @@ +package com.tfq.finances.network.model.response.finances.categories + +import com.google.gson.annotations.SerializedName + +data class CategoriesAllResp( + + /** + * 系统类别 + */ + @SerializedName("system") + var system: List? = listOf(), + + /** + * 用户自定义类别 + */ + @SerializedName("user") + var user: List? = listOf() + +) \ No newline at end of file diff --git a/app/src/main/java/com/tfq/finances/network/model/response/finances/categories/CategoriesData.kt b/app/src/main/java/com/tfq/finances/network/model/response/finances/categories/CategoriesData.kt new file mode 100644 index 0000000..ba449b6 --- /dev/null +++ b/app/src/main/java/com/tfq/finances/network/model/response/finances/categories/CategoriesData.kt @@ -0,0 +1,29 @@ +package com.tfq.finances.network.model.response.finances.categories + +import com.google.gson.annotations.SerializedName +import com.tfq.finances.core.enums.CommonTypeOriginEnum +import java.math.BigDecimal + +/** + * 类别数据 + */ +data class CategoriesData( + @SerializedName("id") + var id: Int = 0, + + @SerializedName("typeOrigin") + var typeOrigin: String = CommonTypeOriginEnum.SYSTEM.getName(), + + @SerializedName("sort") + var sort: Int = 0, + + @SerializedName("type") + var type: Int = 0, + + @SerializedName("name") + var name: String = "", + + @SerializedName("iconUrl") + var iconUrl: String = "" + +) \ No newline at end of file diff --git a/app/src/main/java/com/tfq/finances/network/model/response/finances/index/IndexResp.kt b/app/src/main/java/com/tfq/finances/network/model/response/finances/index/IndexResp.kt new file mode 100644 index 0000000..f6a06a3 --- /dev/null +++ b/app/src/main/java/com/tfq/finances/network/model/response/finances/index/IndexResp.kt @@ -0,0 +1,26 @@ +package com.tfq.finances.network.model.response.finances.index + +import com.google.gson.annotations.SerializedName +import com.tfq.finances.network.model.response.finances.transactions.TransactionsResp +import java.math.BigDecimal + +data class IndexResp( + /** + * 支出 + */ + @SerializedName("expenditure") + var expenditure: BigDecimal = BigDecimal.ZERO, + + /** + * 收入 + */ + @SerializedName("income") + var income: BigDecimal = BigDecimal.ZERO, + + /** + * 明细 + */ + @SerializedName("details") + var details: Map>? = null + +) diff --git a/app/src/main/java/com/tfq/finances/network/model/response/finances/transactions/BillsData.kt b/app/src/main/java/com/tfq/finances/network/model/response/finances/transactions/BillsData.kt new file mode 100644 index 0000000..5fee795 --- /dev/null +++ b/app/src/main/java/com/tfq/finances/network/model/response/finances/transactions/BillsData.kt @@ -0,0 +1,13 @@ +package com.tfq.finances.network.model.response.finances.transactions + + +import com.google.gson.annotations.SerializedName +import com.tfq.finances.network.model.response.adv.AdvSpaceData + +/** + * 账单(月度/年度) + */ +data class BillsData( + @SerializedName("billsResp") + val billsResp: List? = null, +) diff --git a/app/src/main/java/com/tfq/finances/network/model/response/finances/transactions/BillsResp.kt b/app/src/main/java/com/tfq/finances/network/model/response/finances/transactions/BillsResp.kt new file mode 100644 index 0000000..193f6b9 --- /dev/null +++ b/app/src/main/java/com/tfq/finances/network/model/response/finances/transactions/BillsResp.kt @@ -0,0 +1,34 @@ +package com.tfq.finances.network.model.response.finances.transactions + +import com.google.gson.annotations.SerializedName +import java.math.BigDecimal + +data class BillsResp( + + + /** + * 时间key 月份/年份 + */ + @SerializedName("timeKey") + val timeKey: String = "", + + + /** + * 支出 + */ + @SerializedName("expenditure") + var expenditure: BigDecimal = BigDecimal.ZERO, + + /** + * 收入 + */ + @SerializedName("income") + var income: BigDecimal = BigDecimal.ZERO, + + /** + * 账户余额 + */ + @SerializedName("balance") + var balance: BigDecimal = BigDecimal.ZERO, + +) \ No newline at end of file diff --git a/app/src/main/java/com/tfq/finances/network/model/response/finances/transactions/TransactionsResp.kt b/app/src/main/java/com/tfq/finances/network/model/response/finances/transactions/TransactionsResp.kt new file mode 100644 index 0000000..570d00e --- /dev/null +++ b/app/src/main/java/com/tfq/finances/network/model/response/finances/transactions/TransactionsResp.kt @@ -0,0 +1,65 @@ +package com.tfq.finances.network.model.response.finances.transactions + +import com.google.gson.annotations.SerializedName +import java.math.BigDecimal + +data class TransactionsResp( + + @SerializedName("id") + var id: Int? = null, + + @SerializedName("appinfoId") + var appinfoId: Int? = null, + + @SerializedName("userId") + var userId: Int? = null, + + /** + * 分类id + */ + @SerializedName("categoryId") + var categoryId: Int? = null, + + /** + * 分类名称 + */ + @SerializedName("categoryName") + var categoryName: String? = null, + + /** + * 金额 + */ + @SerializedName("amount") + var amount: BigDecimal = BigDecimal.ZERO, + + /** + * 描述 + */ + @SerializedName("userDesc") + var userDesc: String? = null, + + /** + * 交易时间 + */ + @SerializedName("transactionDate") + val transactionDate: String? = null, + + /** + * 创建时间 + */ + @SerializedName("createTime") + var createTime: String? = null, + + /** + * 交易类型 + * 1支出 2收入 + */ + @SerializedName("type") + var type: Int? = null, + + /** + * 明细 + */ + @SerializedName("isShowTitle") + var isShowTitle: Boolean? = false +) diff --git a/app/src/main/java/com/tfq/finances/network/model/response/member/MemberLoginResp.kt b/app/src/main/java/com/tfq/finances/network/model/response/member/MemberLoginResp.kt new file mode 100644 index 0000000..b5bd82d --- /dev/null +++ b/app/src/main/java/com/tfq/finances/network/model/response/member/MemberLoginResp.kt @@ -0,0 +1,40 @@ +package com.tfq.finances.network.model.response.member + + +import com.google.gson.annotations.SerializedName + +/** + * 登录返回 + */ +data class MemberLoginResp( + + /** + * 用户id + */ + @SerializedName("userId") + val userId: Long = -1, + + /** + * 登录token + */ + @SerializedName("accessToken") + val accessToken: String = "", + + /** + * 刷新token + */ + @SerializedName("refreshToken") + val refreshToken: String = "", + + /** + * 过期时间 + */ + @SerializedName("expiresTime") + val expiresTime: Long = -1, + + /** + * openid + */ + @SerializedName("openid") + val openid: String = "", +) diff --git a/app/src/main/java/com/tfq/finances/network/model/response/userinfo/UserInfoData.kt b/app/src/main/java/com/tfq/finances/network/model/response/userinfo/UserInfoData.kt new file mode 100644 index 0000000..4e4fe33 --- /dev/null +++ b/app/src/main/java/com/tfq/finances/network/model/response/userinfo/UserInfoData.kt @@ -0,0 +1,48 @@ +package com.tfq.finances.network.model.response.userinfo + +import com.google.gson.annotations.SerializedName + +data class UserInfoData( + + /** + * 昵称 + */ + @SerializedName("nickname") + var nickname: String = "", + + /** + * 状态 + */ + @SerializedName("status") + val status: String = "", + + /** + * 头像 + */ + @SerializedName("avatar") + var avatar: String = "", + + /** + * 手机号 + */ + @SerializedName("mobile") + val mobile: String = "", + + /** + * 创建日期 + */ + @SerializedName("createTime") + val createTime: Long = -1, + + /** + * openid + */ + @SerializedName("levelId") + val levelId: Long = -1, + + /** + * point + */ + @SerializedName("point") + val point: Long = -1, +) \ No newline at end of file diff --git a/app/src/main/java/com/tfq/finances/network/model/response/userinfo/UserInfoResp.kt b/app/src/main/java/com/tfq/finances/network/model/response/userinfo/UserInfoResp.kt new file mode 100644 index 0000000..7b97115 --- /dev/null +++ b/app/src/main/java/com/tfq/finances/network/model/response/userinfo/UserInfoResp.kt @@ -0,0 +1,15 @@ +package com.tfq.finances.network.model.response.userinfo + + +import com.google.gson.annotations.SerializedName + +/** + * 广告设置响应(组合嵌套结构) + */ +data class UserInfoResp( + @SerializedName("memberUser") + val memberUser: UserInfoData = UserInfoData(), + + @SerializedName("userSettings") + val userSettings: UserSettingsData = UserSettingsData(), +) diff --git a/app/src/main/java/com/tfq/finances/network/model/response/userinfo/UserSettingsData.kt b/app/src/main/java/com/tfq/finances/network/model/response/userinfo/UserSettingsData.kt new file mode 100644 index 0000000..9c174b2 --- /dev/null +++ b/app/src/main/java/com/tfq/finances/network/model/response/userinfo/UserSettingsData.kt @@ -0,0 +1,52 @@ +package com.tfq.finances.network.model.response.userinfo + +import com.google.gson.annotations.SerializedName + +/** + * 用户设置信息 + */ +data class UserSettingsData( + /** + *币种 + */ + @SerializedName("currency") + val currency: String = "", + + /** + *皮肤装扮 + */ + @SerializedName("theme") + val theme: String = "", + + /** + *提醒规则 + */ + @SerializedName("reminderRules") + val reminderRules: String = "", + + /** + *手势密码 + */ + @SerializedName("gesturePassword") + val gesturePassword: String = "", + + /** + *语言 + */ + @SerializedName("language") + val language: String = "", + + /** + *是否隐藏金额 + */ + @SerializedName("hideTotalAmount") + val hideTotalAmount: Int = 0, + + /** + *是否开启生物识别(指纹、人脸) + */ + @SerializedName("enableBiometric") + val enableBiometric: Int = 0 + + +) \ No newline at end of file diff --git a/app/src/main/java/com/tfq/finances/utils/BigDecimalUtils.java b/app/src/main/java/com/tfq/finances/utils/BigDecimalUtils.java new file mode 100644 index 0000000..a2d433f --- /dev/null +++ b/app/src/main/java/com/tfq/finances/utils/BigDecimalUtils.java @@ -0,0 +1,43 @@ +package com.tfq.finances.utils; + +import android.os.Build; + +import java.math.BigDecimal; +import java.util.Arrays; +import java.util.List; + +public class BigDecimalUtils { + /** + * 对多个BigDecimal值求和 + * + * @param values 可变参数形式的BigDecimal值 + * @return 求和结果 + */ + public static BigDecimal sum(BigDecimal... values) { + if (values == null || values.length == 0) { + return BigDecimal.ZERO; + } + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { + return Arrays.stream(values) + .reduce(BigDecimal.ZERO, BigDecimal::add); + } + return new BigDecimal(0); + } + + /** + * 对BigDecimal列表求和 + * + * @param list BigDecimal列表 + * @return 求和结果 + */ + public static BigDecimal sum(List list) { + if (list == null || list.isEmpty()) { + return BigDecimal.ZERO; + } + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { + return list.stream() + .reduce(BigDecimal.ZERO, BigDecimal::add); + } + return new BigDecimal(0); + } +} diff --git a/app/src/main/java/com/tfq/finances/utils/ChooseHeadDialog.java b/app/src/main/java/com/tfq/finances/utils/ChooseHeadDialog.java new file mode 100644 index 0000000..9134237 --- /dev/null +++ b/app/src/main/java/com/tfq/finances/utils/ChooseHeadDialog.java @@ -0,0 +1,190 @@ +package com.tfq.finances.utils; + +import android.annotation.SuppressLint; +import android.app.Dialog; +import android.content.Context; +import android.os.Bundle; +import android.util.DisplayMetrics; +import android.view.LayoutInflater; +import android.view.View; +import android.view.Window; +import android.view.WindowManager; +import android.widget.TextView; + +import com.chad.library.adapter.base.BaseQuickAdapter; +import com.tfq.finances.core.enums.AvatarEnum; +import com.tfq.finances.finances.adapter.HeadAdapter; +import com.tfq.finances.finances.model.HeadConfig; +import com.tfq.finances.jzrcj.R; +import com.tfq.finances.utils.animation.AnimationClick; +import com.tfq.library.utils.RecyclerViewHelper; + +import java.util.ArrayList; +import java.util.List; + +import androidx.annotation.NonNull; +import androidx.recyclerview.widget.GridLayoutManager; +import androidx.recyclerview.widget.RecyclerView; + +public class ChooseHeadDialog extends Dialog { + private final Context mContext; + private final LayoutInflater inflater; + private Listener listener; + private String type; + private String content; + private String title; + private RecyclerView recycler_view; + private int old_checked_item = 0; + private HeadAdapter mAdapter; + + public ChooseHeadDialog(Context context, Listener listener) { + super(context, com.tfq.finances.jzrcj.R.style.CustomDialog); + this.listener = listener; + this.mContext = context; + inflater = LayoutInflater.from(context); + } + + public ChooseHeadDialog(Context context, String type) { + super(context, R.style.CustomDialog); + this.mContext = context; + this.type = type; + this.title = title; + this.content = content; + inflater = LayoutInflater.from(context); + } + + public ChooseHeadDialog(Context context, String title, String content, Listener listener) { + super(context, R.style.CustomDialog); + this.mContext = context; + this.title = title; + this.content = content; + this.listener = listener; + inflater = LayoutInflater.from(context); + } + + public ChooseHeadDialog(Context context, String type, String title, String content) { + super(context, R.style.CustomDialog); + this.mContext = context; + this.type = type; + this.title = title; + this.content = content; + inflater = LayoutInflater.from(context); + } + + public ChooseHeadDialog(Context context, String type, String title, String content, Listener listener) { + super(context, R.style.CustomDialog); + this.listener = listener; + this.mContext = context; + this.type = type; + this.title = title; + this.content = content; + inflater = LayoutInflater.from(context); + } + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setViews(); + } + + private void setViews() { + View contentView = inflater.inflate(com.tfq.finances.jzrcj.R.layout.layout_dialog_choose_head, null); + setContentView(contentView); + TextView tv_title = contentView.findViewById(R.id.tv_title); + TextView tv_content = contentView.findViewById(R.id.tv_content); + TextView tv_left = contentView.findViewById(R.id.tv_left); + TextView tv_right = contentView.findViewById(R.id.tv_right); + + recycler_view = contentView.findViewById(R.id.recycler_view); + + initRecyclerView(); + + Window dialogWindow = getWindow(); + WindowManager.LayoutParams lp = dialogWindow.getAttributes(); + DisplayMetrics d = mContext.getResources().getDisplayMetrics(); // 获取屏幕宽、高用 + lp.width = (int) (d.widthPixels * 0.93); // 宽度设置为屏幕的0..8 0.9 + dialogWindow.setAttributes(lp); + + if ("choose_head".equals(type)) { + tv_title.setText(title); + } + + tv_left.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + dismiss(); + } + }); + + tv_right.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + if (listener != null) { + if ("choose_head".equals(type)) { + int avatarId = mAdapter.getData().get(getOldCheckedItem()).getHeadId(); + listener.success(avatarId); + dismiss(); + } + } + } + }); + } + + private void initRecyclerView() { + mAdapter = new HeadAdapter(R.layout.item_head_layout); + recycler_view.setAdapter(mAdapter); + GridLayoutManager manager = new GridLayoutManager(mContext, 4, RecyclerView.VERTICAL, false); + RecyclerViewHelper.initRecyclerViewV(mContext, recycler_view, mAdapter); + recycler_view.setLayoutManager(manager); + List typeOriginConfigs = getHeadConfigs(); + mAdapter.setNewData(typeOriginConfigs); + mAdapter.setOnItemClickListener(new BaseQuickAdapter.OnItemClickListener() { + @SuppressLint("NotifyDataSetChanged") + @Override + public void onItemClick(BaseQuickAdapter adapter, View view, int position) { + if (getOldCheckedItem() != position) { + mAdapter.getData().get(position).setItemCheck(true); + mAdapter.getData().get(getOldCheckedItem()).setItemCheck(false); + setOldCheckedItem(position); + mAdapter.notifyDataSetChanged(); + } + } + }); + AnimationClick.startScaleAnimation(recycler_view); + + } + + @NonNull + private List getHeadConfigs() { + List headConfigs = new ArrayList<>(); + + List mAvatarEnum = AvatarEnum.getAll(); + for (int i = 0; i < mAvatarEnum.size(); i++) { + AvatarEnum avatarEnum = mAvatarEnum.get(i); + HeadConfig config = new HeadConfig(); + config.setHeadId(avatarEnum.getId()); + config.setHeadName(avatarEnum.getName()); + config.setHeadResource(avatarEnum.getAvatar()); + config.setHeadCheck(avatarEnum.getIcUnOnChecked()); + config.setHeadChecked(avatarEnum.getIcOnChecked()); + if (i == getOldCheckedItem()) { + config.setItemCheck(true); + } + headConfigs.add(config); + } + return headConfigs; + } + + public int getOldCheckedItem() { + return old_checked_item; + } + + public void setOldCheckedItem(int old_checked_item) { + this.old_checked_item = old_checked_item; + } + + public interface Listener { + void success(int avaterId); + } + +} diff --git a/app/src/main/java/com/tfq/finances/utils/DateCalculator.java b/app/src/main/java/com/tfq/finances/utils/DateCalculator.java new file mode 100644 index 0000000..c01ae22 --- /dev/null +++ b/app/src/main/java/com/tfq/finances/utils/DateCalculator.java @@ -0,0 +1,93 @@ +package com.tfq.finances.utils; + + +import android.os.Build; + +import java.text.SimpleDateFormat; +import java.time.LocalDate; +import java.time.format.DateTimeFormatter; +import java.time.format.DateTimeParseException; +import java.time.temporal.TemporalAdjusters; +import java.util.Date; +import java.util.Locale; + +public class DateCalculator { + + public static String getDefDate() { + // 方式1:使用SimpleDateFormat(兼容所有API版本) + SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd", Locale.getDefault()); + return sdf.format(new Date()); + } + + public static class DatePair { + public final String previousMonth; + public final String nextMonth; + + public DatePair(String previousMonth, String nextMonth) { + this.previousMonth = previousMonth; + this.nextMonth = nextMonth; + } + } + + public static DatePair calculateMonthDates(String baseDate) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd"); + LocalDate current = LocalDate.parse(baseDate, formatter); + return new DatePair( + getAdjustedDate(current, -1).format(formatter), + getAdjustedDate(current, 1).format(formatter) + ); + } + + return new DatePair(getDefDate(), getDefDate()); + } + + private static LocalDate getAdjustedDate(LocalDate date, int monthOffset) { + // 特殊处理2月28日 + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + if (date.getDayOfMonth() == 28 && date.getMonthValue() == 2) { + LocalDate target = date.plusMonths(monthOffset); + return monthOffset < 0 ? + target.withDayOfMonth(29) : + target.withDayOfMonth(27); + } + // 处理31日 + else if (date.getDayOfMonth() == 31) { + return date.plusMonths(monthOffset) + .with(TemporalAdjusters.lastDayOfMonth()); + } + // 常规处理 + else { + LocalDate target = date.plusMonths(monthOffset); + int dayToSet = Math.min( + date.getDayOfMonth(), + target.getMonth().maxLength() + ); + return target.withDayOfMonth(dayToSet); + } + } + return null; + } + + /** + * 判断输入时间是否大于今天 + * @param targetDate + * @return + */ + public static boolean isAfterToday(String targetDate) { + // 固定当前日期为 2025-06-01 + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + LocalDate today = LocalDate.of(2025, 6, 1); + DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd"); + + try { + LocalDate dateToCheck = LocalDate.parse(targetDate, formatter); + return dateToCheck.isAfter(today); + } catch (DateTimeParseException e) { + return false; // 日期格式错误时返回 false + } + }else { + return false; + } + } +} diff --git a/app/src/main/java/com/tfq/finances/utils/LottieManager.java b/app/src/main/java/com/tfq/finances/utils/LottieManager.java new file mode 100644 index 0000000..4061e9d --- /dev/null +++ b/app/src/main/java/com/tfq/finances/utils/LottieManager.java @@ -0,0 +1,36 @@ +package com.tfq.finances.utils; + +import android.content.Context; +import android.util.LruCache; + +import com.airbnb.lottie.LottieAnimationView; +import com.airbnb.lottie.LottieComposition; +import com.airbnb.lottie.LottieCompositionFactory; +import com.airbnb.lottie.LottieTask; + +/** + * 基本的缓存功能,可扩展支持更多优化特性 + */ + +public class LottieManager { + private static LruCache cache; + + public static void init() { + cache = new LruCache<>(10); // 缓存10个动画 + } + + public static void loadAnimation(Context ctx, String assetName, + LottieAnimationView target) { + LottieComposition cached = cache.get(assetName); + if (cached != null) { + target.setComposition(cached); + return; + } + LottieTask task = + LottieCompositionFactory.fromAsset(ctx, assetName); + task.addListener(result -> { + cache.put(assetName, result); + target.setComposition(result); + }); + } +} diff --git a/app/src/main/java/com/tfq/finances/utils/PAGAnimationLoader.java b/app/src/main/java/com/tfq/finances/utils/PAGAnimationLoader.java new file mode 100644 index 0000000..aacd679 --- /dev/null +++ b/app/src/main/java/com/tfq/finances/utils/PAGAnimationLoader.java @@ -0,0 +1,24 @@ +package com.tfq.finances.utils; + +import android.content.Context; +import android.content.res.AssetManager; + +import org.libpag.PAGFile; +import org.libpag.PAGView; + +/** + * Description: PAG动画 + * Created by JiangKe . + */ +public class PAGAnimationLoader { + public static void loadAndPlayAnimation(Context context, PAGView pagView, String fileName) { + try { + AssetManager assets = context.getAssets(); + PAGFile pagFile = PAGFile.Load(assets, fileName); + pagView.setComposition(pagFile); + pagView.play(); + } catch (Exception e) { + e.printStackTrace(); + } + } +} diff --git a/app/src/main/java/com/tfq/finances/utils/androidpicker/CalculateUtils.java b/app/src/main/java/com/tfq/finances/utils/androidpicker/CalculateUtils.java new file mode 100644 index 0000000..c54d013 --- /dev/null +++ b/app/src/main/java/com/tfq/finances/utils/androidpicker/CalculateUtils.java @@ -0,0 +1,626 @@ +package com.tfq.finances.utils.androidpicker; + +import android.annotation.SuppressLint; +import android.app.Activity; +import android.graphics.Color; +import android.graphics.Typeface; +import android.os.Build; +import android.os.Handler; +import android.text.Editable; +import android.text.TextUtils; +import android.view.View; +import android.widget.EditText; +import android.widget.ImageView; +import android.widget.TextView; + +import com.github.gzuliyujiang.wheelpicker.DatePicker; +import com.github.gzuliyujiang.wheelpicker.annotation.DateMode; +import com.github.gzuliyujiang.wheelpicker.contract.OnDatePickedListener; +import com.github.gzuliyujiang.wheelpicker.entity.DateEntity; +import com.github.gzuliyujiang.wheelpicker.widget.DateWheelLayout; +import com.tfq.finances.finances.activity.Activity_Detail_Add; +import com.tfq.finances.main.model.TypeOriginConfig; +import com.tfq.finances.network.api.finances.TransactionsService; +import com.tfq.finances.network.config.ApiCallback; +import com.tfq.finances.utils.animation.AnimationClick; +import com.tfq.library.utils.LogK; +import com.tfq.library.utils.PermissionDialog; +import com.tfq.library.utils.ToasterUtil; + +import java.math.BigDecimal; +import java.math.RoundingMode; +import java.text.SimpleDateFormat; +import java.time.DayOfWeek; +import java.time.LocalDate; +import java.time.format.DateTimeFormatter; +import java.time.format.TextStyle; +import java.util.Date; +import java.util.Locale; + +/** + * 计算 + */ +public class CalculateUtils { + + //计算结果 + private BigDecimal bigDecimal; + private Activity mActivity; + + private TextView tv_complete, tv_num1, tv_num2, tv_symbol1, tv_result, tv_today; + private EditText et_remark; + private int type; + /** + * 用于不频繁请求创建记账项目 + */ + private boolean isReqServer; + /** + * 仅用于是否添加成功的判断,用于刷新recyclerview + */ + private boolean isReqSuccessRefreshRv; + private TypeOriginConfig typeOriginConfig; + private int yearMonthDay = com.tfq.finances.utils.androidpicker.DateMode.YEAR_MONTH; + private Listener listener; + + /** + * 构造方法 + */ + public CalculateUtils(Activity mActivity) { + this.mActivity = mActivity; + } + + /** + * 获取目标子字符串后的剩余字符数 + * + * @param source 原始字符串 + * @param target 查找的子字符串 + * @return 剩余字符数(未找到返回 -1) + */ + public static int getRemainingCharCount(String source, String target) { + int targetIndex = source.indexOf(target); + if (targetIndex == -1) return -1; + + int targetEndIndex = targetIndex + target.length(); + return source.length() - targetEndIndex; + } + + public static String getWeekDay(String dateStr) { + try { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + // 解析日期(ISO格式可直接解析) + LocalDate date = LocalDate.parse(dateStr); + + // 获取星期并本地化 + DayOfWeek dayOfWeek = date.getDayOfWeek(); + + // 中文显示(示例输出:星期六) + return dayOfWeek.getDisplayName( + TextStyle.FULL, // 完整格式 + Locale.CHINESE // 中文环境 + ); + } + return ""; + } catch (Exception e) { + return ""; + } + } + + public static String convertToChineseFormat(String dateStr) { + if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.O) { + DateTimeFormatter inputFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd"); + DateTimeFormatter outputFormatter = DateTimeFormatter.ofPattern("yyyy年MM月dd日"); + LocalDate date = LocalDate.parse(dateStr, inputFormatter); + return date.format(outputFormatter); + } + return dateStr; + } + + public void yearMonthDay(int YEAR_MONTH_DAY) { + this.yearMonthDay = YEAR_MONTH_DAY; + } + + public void initialize(int type, TextView tv_complete, TextView tv_num1, TextView tv_num2, TextView tv_symbol1, TextView tv_result, TextView tv_today + , EditText et_remark) { + this.type = type; + this.tv_complete = tv_complete; + this.tv_num1 = tv_num1; + this.tv_num2 = tv_num2; + this.tv_symbol1 = tv_symbol1; + this.tv_result = tv_result; + this.et_remark = et_remark; + this.tv_today = tv_today; + } + + /** + * 点击完成按钮 + */ + @SuppressLint("SetTextI18n") + public void onClickFinish() { + String symbol1 = tv_symbol1.getText().toString().trim(); + String num1 = tv_num1.getText().toString().trim(); + String num2 = tv_num2.getText().toString().trim(); + if (tv_complete.getText().toString().trim().equals("=")) {//是等于号 + if (!TextUtils.isEmpty(num2)) {//第二位不为空 + if (!TextUtils.isEmpty(num1) && !TextUtils.isEmpty(symbol1)) { + bigDecimal = calculateResult(num1, num2, symbol1); + tv_num1.setText(""); + tv_symbol1.setText(""); + tv_num2.setText(""); + tv_num1.setText(bigDecimal + ""); + } + } else { + tv_num1.setText(""); + tv_symbol1.setText(""); + tv_num2.setText(""); + bigDecimal = calculateResult(num1, "0", "+"); + tv_num1.setText(num1); + } + } else { + bigDecimal = calculateResult(num1, "0", "+"); + upLoadData(); + } + tv_complete.setText("完成"); + } + + /** + * 点击回退按钮 + */ + public void onClickDelete() { + String symbol1 = tv_symbol1.getText().toString().trim(); + String num1 = tv_num1.getText().toString().trim(); + String num2 = tv_num2.getText().toString().trim(); + if (!TextUtils.isEmpty(num2) && num2.length() > 0) { + tv_num2.setText(num2.substring(0, num2.length() - 1)); + } else if (!TextUtils.isEmpty(symbol1)) { + tv_symbol1.setText(""); + } else if (!TextUtils.isEmpty(num1) && num1.length() > 0) { + tv_num1.setText(num1.substring(0, num1.length() - 1)); + } +// tv_complete.setText("="); + setFinish(); + } + + private void setFinish() { + String symbol1 = tv_symbol1.getText().toString().trim(); + if (!TextUtils.isEmpty(symbol1)) { + tv_complete.setText("="); + } else { + tv_complete.setText("完成"); + } + } + + /** + * 数字按键 + * + * @param press + */ + public void press(String press, TextView tv) { +// tv_complete.setText("="); + AnimationClick.startScaleAnimation(tv); + String symbol1 = tv_symbol1.getText().toString().trim(); + String num1 = tv_num1.getText().toString().trim(); + String num2 = tv_num2.getText().toString().trim(); + if (TextUtils.isEmpty(symbol1)) {//第一个符号为空说明当前的double为第一个textview + if (TextUtils.isEmpty(num1)) { + if (press.equals(".")) { + tv_num1.setText("0."); + } else { + tv_num1.setText(press);////第一个textview为空 + } + } else { + StringBuffer sb = new StringBuffer(); + if (press.equals(".")) { + if (!num1.contains(".")) { + sb.append(num1.trim()); + sb.append(press); + } else { + sb.append(num1.trim()); + } + } else { + sb.append(num1.trim()); + sb.append(press); + } + String string = getString(sb.toString()); + tv_num1.setText(string); + } + } else { + if (TextUtils.isEmpty(num2)) { + if (press.equals(".")) { + tv_num2.setText("0."); + } else { + tv_num2.setText(press);////第二个textview为空 + } + } else { + StringBuffer sb = new StringBuffer(); + if (press.equals(".")) { + if (!num2.contains(".")) { + sb.append(num2.trim()); + sb.append(press); + } else { + sb.append(num2.trim()); + } + } else { + sb.append(num2.trim()); + sb.append(press); + } + String string = getString(sb.toString()); + tv_num2.setText(string); + } + setFinish(); + } + tv_result.setText(""); + } + + /** + * 符号按键 + * + * @param x + */ + public void symbol(String x) { + String num1 = tv_num1.getText().toString().trim(); + String num2 = tv_num2.getText().toString().trim(); + String symbol1 = tv_symbol1.getText().toString().trim(); + if (!TextUtils.isEmpty(num1)) {//第一位不为空 + if (num1.endsWith(".")) { + tv_num1.setText(num1.substring(0, num1.length() - 1)); + } + if (TextUtils.isEmpty(symbol1)) { + tv_symbol1.setText(x); + } else { + if (!TextUtils.isEmpty(num2)) {//第二位不为空 + if (!TextUtils.isEmpty(num1) && !TextUtils.isEmpty(symbol1)) {//第一次想加 num1不为空,并且第一个符号不为空 + bigDecimal = calculateResult(num1, num2, symbol1); + tv_num1.setText(""); + tv_symbol1.setText(""); + tv_num2.setText(""); + tv_num1.setText(bigDecimal + ""); + tv_symbol1.setText(x); + } else { + + } + } else { + tv_symbol1.setText(x); + } + /*else { + tv_num1.setText(""); + tv_symbol1.setText(""); + tv_num2.setText(""); + tv_result.setText(num1); + }*/ + } + } + + tv_complete.setText("="); + } + + private String getString(String string) { + String format; + int remainingCharCount = getRemainingCharCount(string, "."); + if (remainingCharCount >= 2) { + BigDecimal num2 = new BigDecimal(string); + format = num2.setScale(2, RoundingMode.DOWN).toString(); + LogK.e("format=" + format); + } else { + format = string; + } + + return format; + } + + /** + * 计算 + * + * @param currentOperator 符号 + * @return + */ + private BigDecimal calculateResult(String number1, String number2, String currentOperator) { + try { + BigDecimal num1 = new BigDecimal(number1); + BigDecimal num2 = new BigDecimal(number2); + switch (currentOperator) { + case "+": + return num1.add(num2).setScale(2, RoundingMode.HALF_UP); + case "-": + return num1.subtract(num2).setScale(2, RoundingMode.HALF_UP); + case "×": + return num1.multiply(num2).setScale(2, RoundingMode.HALF_UP); + case "÷": + return num1.divide(num2, 2, RoundingMode.HALF_UP); + } + } catch (Exception e) { + e.printStackTrace(); + } + return BigDecimal.ZERO; + } + + /** + * 选择日期 + */ + public void choose_date(TextView tv_today, ImageView iv_today) { + DatePicker picker = new DatePicker(mActivity); + picker.setBodyWidth(240); + picker.setBodyHeight(180); + DateWheelLayout wheelLayout = picker.getWheelLayout(); + wheelLayout.setDateMode(DateMode.YEAR_MONTH_DAY); + wheelLayout.setDateLabel(" ", " ", ""); + DateEntity today = DateEntity.today(); + wheelLayout.setRange(DateEntity.target(2000, 1, 1), DateEntity.target(today.getYear(), today.getMonth(), today.getDay()), DateEntity.today()); + wheelLayout.setCyclicEnabled(false); + wheelLayout.setTextSize(12 * mActivity.getResources().getDisplayMetrics().scaledDensity); + picker.setOnDatePickedListener(new OnDatePickedListener() { + @Override + public void onDatePicked(int year, int month, int day) { + // 处理日期选择回调 + String selectedDate = year + "-" + (month < 10 ? "0" + month : month) + "-" + (day < 10 ? "0" + day : day); + LogK.e("selectedDate=" + selectedDate); + iv_today.setVisibility(View.GONE); + tv_today.setText(selectedDate); + } + }); + TextView cancelView = picker.getCancelView(); + cancelView.setTextColor(Color.BLACK); + cancelView.setTextSize(12); + cancelView.setText(" 取消"); + cancelView.setTypeface(Typeface.DEFAULT_BOLD); + TextView okView = picker.getOkView(); + okView.setTextColor(Color.BLACK); + okView.setTextSize(12); + okView.setText("确定 "); + okView.setTypeface(Typeface.DEFAULT_BOLD); + TextView titleView = picker.getTitleView(); + titleView.setTextColor(Color.BLACK); + titleView.setTextSize(13); + titleView.setText("选择日期"); + titleView.setTypeface(Typeface.DEFAULT_BOLD); + + picker.getCancelView().setTextColor(Color.BLACK); + picker.show(); + } + + /** + * 选择日期2 + */ + public void choose_date(TextView tv1, TextView tv2,Listener listener) { + DatePicker picker = new DatePicker(mActivity); + picker.setBodyWidth(240); + picker.setBodyHeight(180); + DateWheelLayout wheelLayout = picker.getWheelLayout(); + wheelLayout.setDateMode(yearMonthDay); + wheelLayout.setDateLabel(" ", " ", ""); + DateEntity today = DateEntity.today(); + wheelLayout.setRange(DateEntity.target(2000, 1, 1), DateEntity.target(today.getYear(), today.getMonth(), today.getDay()), DateEntity.today()); + wheelLayout.setCyclicEnabled(false); + wheelLayout.setTextSize(12 * mActivity.getResources().getDisplayMetrics().scaledDensity); + picker.setOnDatePickedListener(new OnDatePickedListener() { + @Override + public void onDatePicked(int year, int month, int day) { + // 处理日期选择回调 + String selectedDate = year + "-" + month; + LogK.e("selectedDate=" + selectedDate); + tv1.setText(String.valueOf(year)); + if (tv2 != null) { + tv2.setText(month <= 9 ? "0" + month : String.valueOf(month)); + } + listener.success(year + "-" +(month <= 9 ? "0" + month : String.valueOf(month))); + } + }); + TextView cancelView = picker.getCancelView(); + cancelView.setTextColor(Color.BLACK); + cancelView.setTextSize(12); + cancelView.setText(" 取消"); + cancelView.setTypeface(Typeface.DEFAULT_BOLD); + TextView okView = picker.getOkView(); + okView.setTextColor(Color.BLACK); + okView.setTextSize(12); + okView.setText("确定 "); + okView.setTypeface(Typeface.DEFAULT_BOLD); + TextView titleView = picker.getTitleView(); + titleView.setTextColor(Color.BLACK); + titleView.setTextSize(13); + titleView.setText("选择日期"); + titleView.setTypeface(Typeface.DEFAULT_BOLD); + + picker.getCancelView().setTextColor(Color.BLACK); + picker.show(); + } + + public void setListener(Listener listener) { + this.listener = listener; + } + + /** + * 选择日期3 + */ + public void choose_date(TextView tv_today) { + com.tfq.finances.utils.androidpicker.DatePicker picker = new com.tfq.finances.utils.androidpicker.DatePicker(mActivity); + picker.setBodyWidth(240); + picker.setBodyHeight(180); + com.tfq.finances.utils.androidpicker.DateWheelLayout wheelLayout = picker.getWheelLayout(); + wheelLayout.setDateMode(yearMonthDay); + wheelLayout.setDateLabel(" ", " ", ""); + DateEntity today = DateEntity.today(); + wheelLayout.setRange(DateEntity.target(2000, 1, 1), DateEntity.target(today.getYear(), today.getMonth(), today.getDay()), DateEntity.today()); + wheelLayout.setCyclicEnabled(false); + wheelLayout.setTextSize(12 * mActivity.getResources().getDisplayMetrics().scaledDensity); + picker.setOnDatePickedListener(new OnDatePickedListener() { + @SuppressLint("SetTextI18n") + @Override + public void onDatePicked(int year, int month, int day) { + + // 处理日期选择回调 + String selectedDate = year + "-" + (month < 10 ? "0" + month : month) + "-" + (day < 10 ? "0" + day : day); + LogK.e("selectedDate=" + selectedDate); + tv_today.setText(year + "年"); + if (listener != null) { + listener.success(String.valueOf(year)); + listener = null; + } + } + }); + TextView cancelView = picker.getCancelView(); + cancelView.setTextColor(Color.BLACK); + cancelView.setTextSize(12); + cancelView.setText(" 取消"); + cancelView.setTypeface(Typeface.DEFAULT_BOLD); + TextView okView = picker.getOkView(); + okView.setTextColor(Color.BLACK); + okView.setTextSize(12); + okView.setText("确定 "); + okView.setTypeface(Typeface.DEFAULT_BOLD); + TextView titleView = picker.getTitleView(); + titleView.setTextColor(Color.BLACK); + titleView.setTextSize(13); + titleView.setText("选择日期"); + titleView.setTypeface(Typeface.DEFAULT_BOLD); + + picker.getCancelView().setTextColor(Color.BLACK); + picker.show(); + } + + /** + * 选择日期4 + */ + public void choose_date(TextView tv_today,String type) { + com.tfq.finances.utils.androidpicker.DatePicker picker = new com.tfq.finances.utils.androidpicker.DatePicker(mActivity); + picker.setBodyWidth(240); + picker.setBodyHeight(180); + com.tfq.finances.utils.androidpicker.DateWheelLayout wheelLayout = picker.getWheelLayout(); + wheelLayout.setDateMode(yearMonthDay); + wheelLayout.setDateLabel(" ", " ", ""); + DateEntity today = DateEntity.today(); + wheelLayout.setRange(DateEntity.target(2000, 1, 1), DateEntity.target(today.getYear(), today.getMonth(), today.getDay()), DateEntity.today()); + wheelLayout.setCyclicEnabled(false); + wheelLayout.setTextSize(12 * mActivity.getResources().getDisplayMetrics().scaledDensity); + picker.setOnDatePickedListener(new OnDatePickedListener() { + @SuppressLint("SetTextI18n") + @Override + public void onDatePicked(int year, int month, int day) { + // 处理日期选择回调 + String selectedDate = year + "-" + (month < 10 ? "0" + month : month) + "-" + (day < 10 ? "0" + day : day); + LogK.e("selectedDate=" + selectedDate); + if (!TextUtils.isEmpty(type)){ + + }else { + tv_today.setText(selectedDate); + if (listener != null) { + listener.success(selectedDate); + listener = null; + } + } + } + }); + TextView cancelView = picker.getCancelView(); + cancelView.setTextColor(Color.BLACK); + cancelView.setTextSize(12); + cancelView.setText(" 取消"); + cancelView.setTypeface(Typeface.DEFAULT_BOLD); + TextView okView = picker.getOkView(); + okView.setTextColor(Color.BLACK); + okView.setTextSize(12); + okView.setText("确定 "); + okView.setTypeface(Typeface.DEFAULT_BOLD); + TextView titleView = picker.getTitleView(); + titleView.setTextColor(Color.BLACK); + titleView.setTextSize(13); + titleView.setText("选择日期"); + titleView.setTypeface(Typeface.DEFAULT_BOLD); + + picker.getCancelView().setTextColor(Color.BLACK); + picker.show(); + } + + public void setOriginBean(TypeOriginConfig typeOriginConfig) { + this.typeOriginConfig = typeOriginConfig; + } + + /** + * 将计算结果上传到服务器 + */ + private void upLoadData() { + LogK.e("bigDecimal=" + bigDecimal); + + if (bigDecimal == null) { + ToasterUtil.show("请先输入金额"); + return; + } + BigDecimal bigDecimal1 = new BigDecimal("0"); + LogK.e("bigDecimal.compareTo(bigDecimal1) ==0=" + (bigDecimal.compareTo(bigDecimal1) == 0)); + if (bigDecimal.compareTo(bigDecimal1) == 0) { + ToasterUtil.show("金额需要大于0"); + return; + } + String today = tv_today.getText().toString().trim(); + if (TextUtils.isEmpty(today) || today.equals("今天")) { + // 方式1:使用SimpleDateFormat(兼容所有API版本) + SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd", Locale.getDefault()); + today = sdf.format(new Date()); + } + + Editable text = et_remark.getText(); + String remark = text == null ? "" : text.toString(); + //String userDesc = typeOriginConfig.getTypeOriginName(); + Integer categoryId = typeOriginConfig.getTypeOriginId(); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + if (!isReqServer) { + isReqServer = true; + new Handler().postDelayed(new Runnable() { + @Override + public void run() { + isReqServer = false; + } + }, 1000); + String finalToday = today; + if (false) { + new PermissionDialog(mActivity, "添加记账", "点击确认将添加" + (type == 1 ? "支出" : "收入") + remark + bigDecimal + "元", new PermissionDialog.Listener() { + @Override + public void success() { + addTransactions(categoryId, remark, finalToday); + } + }).show(); + } else { + addTransactions(categoryId, remark, finalToday); + } + } + } + + } + + private void addTransactions(Integer categoryId, String remark, String finalToday) { + new TransactionsService(mActivity).addTransactions(type, categoryId, bigDecimal, remark, finalToday, new ApiCallback() { + @Override + public void onSuccess(String response) { + isReqServer = false; + isReqSuccessRefreshRv = true; + ToasterUtil.show("记账项目创建成功"); + reset(); + + Activity_Detail_Add activityDetailAdd = (Activity_Detail_Add) mActivity; + activityDetailAdd.setReqSuccessRefreshRv(isReqSuccessRefreshRv); + } + + @Override + public void onFailure(int errorCode, String errorMessage) { + isReqServer = false; + ToasterUtil.show(errorMessage); + } + }); + } + + private void reset() { + if (mActivity != null) mActivity.runOnUiThread(new Runnable() { + @Override + public void run() { + et_remark.setText(""); + tv_num1.setText(""); + tv_symbol1.setText(""); + tv_num2.setText(""); + tv_result.setText(""); + bigDecimal = new BigDecimal("0"); + } + }); + } + + public interface Listener { + void success(String date); + } + +} diff --git a/app/src/main/java/com/tfq/finances/utils/androidpicker/DateMode.java b/app/src/main/java/com/tfq/finances/utils/androidpicker/DateMode.java new file mode 100644 index 0000000..6479da7 --- /dev/null +++ b/app/src/main/java/com/tfq/finances/utils/androidpicker/DateMode.java @@ -0,0 +1,47 @@ +/* + * Copyright (c) 2016-present 贵州纳雍穿青人李裕江<1032694760@qq.com> + * + * The software is licensed under the Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR + * PURPOSE. + * See the Mulan PSL v2 for more details. + */ + +package com.tfq.finances.utils.androidpicker; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +/** + * 日期模式 + * + * @author 贵州山野羡民(1032694760@qq.com) + * @since 2019/5/14 17:10 + */ +@Retention(RetentionPolicy.SOURCE) +public @interface DateMode { + /** + * 不显示 + */ + int NONE = -1; + /** + * 年月日 + */ + int YEAR_MONTH_DAY = 0; + /** + * 年月 + */ + int YEAR_MONTH = 1; + /** + * 月日 + */ + int MONTH_DAY = 2; + /** + * 年 + */ + int YEAR = 3; +} diff --git a/app/src/main/java/com/tfq/finances/utils/androidpicker/DatePicker.java b/app/src/main/java/com/tfq/finances/utils/androidpicker/DatePicker.java new file mode 100644 index 0000000..1d47271 --- /dev/null +++ b/app/src/main/java/com/tfq/finances/utils/androidpicker/DatePicker.java @@ -0,0 +1,74 @@ +/* + * Copyright (c) 2016-present 贵州纳雍穿青人李裕江<1032694760@qq.com> + * + * The software is licensed under the Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR + * PURPOSE. + * See the Mulan PSL v2 for more details. + */ + +package com.tfq.finances.utils.androidpicker; + +import android.app.Activity; +import android.view.View; + +import com.github.gzuliyujiang.dialog.ModalDialog; +import com.github.gzuliyujiang.wheelpicker.contract.OnDatePickedListener; + +import androidx.annotation.NonNull; +import androidx.annotation.StyleRes; + +/** + * 日期选择器 + * + * @author 贵州山野羡民(1032694760@qq.com) + * @since 2021/6/5 18:17 + */ +@SuppressWarnings("unused") +public class DatePicker extends ModalDialog { + protected DateWheelLayout wheelLayout; + private OnDatePickedListener onDatePickedListener; + + public DatePicker(@NonNull Activity activity) { + super(activity); + } + + public DatePicker(@NonNull Activity activity, @StyleRes int themeResId) { + super(activity, themeResId); + } + + @NonNull + @Override + protected View createBodyView() { + wheelLayout = new DateWheelLayout(activity); + return wheelLayout; + } + + @Override + protected void onCancel() { + + } + + @Override + protected void onOk() { + if (onDatePickedListener != null) { + int year = wheelLayout.getSelectedYear(); + int month = wheelLayout.getSelectedMonth(); + int day = wheelLayout.getSelectedDay(); + onDatePickedListener.onDatePicked(year, month, day); + } + } + + public void setOnDatePickedListener(OnDatePickedListener onDatePickedListener) { + this.onDatePickedListener = onDatePickedListener; + } + + public final DateWheelLayout getWheelLayout() { + return wheelLayout; + } + +} diff --git a/app/src/main/java/com/tfq/finances/utils/androidpicker/DateWheelLayout.java b/app/src/main/java/com/tfq/finances/utils/androidpicker/DateWheelLayout.java new file mode 100644 index 0000000..f749214 --- /dev/null +++ b/app/src/main/java/com/tfq/finances/utils/androidpicker/DateWheelLayout.java @@ -0,0 +1,445 @@ +/* + * Copyright (c) 2016-present 贵州纳雍穿青人李裕江<1032694760@qq.com> + * + * The software is licensed under the Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR + * PURPOSE. + * See the Mulan PSL v2 for more details. + */ + +package com.tfq.finances.utils.androidpicker; + +import android.content.Context; +import android.content.res.TypedArray; +import android.util.AttributeSet; +import android.view.View; +import android.widget.TextView; + +import com.github.gzuliyujiang.wheelpicker.annotation.DateMode; +import com.github.gzuliyujiang.wheelpicker.contract.DateFormatter; +import com.github.gzuliyujiang.wheelpicker.contract.OnDateSelectedListener; +import com.github.gzuliyujiang.wheelpicker.entity.DateEntity; +import com.github.gzuliyujiang.wheelpicker.impl.SimpleDateFormatter; +import com.github.gzuliyujiang.wheelpicker.widget.BaseWheelLayout; +import com.github.gzuliyujiang.wheelview.annotation.ScrollState; +import com.github.gzuliyujiang.wheelview.contract.WheelFormatter; +import com.github.gzuliyujiang.wheelview.widget.NumberWheelView; +import com.github.gzuliyujiang.wheelview.widget.WheelView; +import com.tfq.finances.jzrcj.R; + +import java.util.Arrays; +import java.util.List; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + +/** + * 日期滚轮控件 + * + * @author 贵州山野羡民(1032694760@qq.com) + * @since 2021/6/5 16:12 + */ +@SuppressWarnings("unused") +public class DateWheelLayout extends BaseWheelLayout { + private NumberWheelView yearWheelView; + private NumberWheelView monthWheelView; + private NumberWheelView dayWheelView; + private TextView yearLabelView; + private TextView monthLabelView; + private TextView dayLabelView; + private DateEntity startValue; + private DateEntity endValue; + private Integer selectedYear; + private Integer selectedMonth; + private Integer selectedDay; + private OnDateSelectedListener onDateSelectedListener; + private boolean resetWhenLinkage = true; + + public DateWheelLayout(Context context) { + super(context); + } + + public DateWheelLayout(Context context, AttributeSet attrs) { + super(context, attrs); + } + + public DateWheelLayout(Context context, AttributeSet attrs, int defStyleAttr) { + super(context, attrs, defStyleAttr); + } + + public DateWheelLayout(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { + super(context, attrs, defStyleAttr, defStyleRes); + } + + @Override + protected int provideLayoutRes() { + return R.layout.wheel_picker_date; + } + + @Override + protected List provideWheelViews() { + return Arrays.asList(yearWheelView, monthWheelView, dayWheelView); + } + + @Override + protected void onInit(@NonNull Context context) { + yearWheelView = findViewById(R.id.wheel_picker_date_year_wheel); + monthWheelView = findViewById(R.id.wheel_picker_date_month_wheel); + dayWheelView = findViewById(R.id.wheel_picker_date_day_wheel); + yearLabelView = findViewById(R.id.wheel_picker_date_year_label); + monthLabelView = findViewById(R.id.wheel_picker_date_month_label); + dayLabelView = findViewById(R.id.wheel_picker_date_day_label); + } + + @Override + protected void onAttributeSet(@NonNull Context context, @Nullable AttributeSet attrs) { + TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.DateWheelLayout); + setDateMode(typedArray.getInt(R.styleable.DateWheelLayout_wheel_dateMode, DateMode.YEAR_MONTH_DAY)); + String yearLabel = typedArray.getString(R.styleable.DateWheelLayout_wheel_yearLabel); + String monthLabel = typedArray.getString(R.styleable.DateWheelLayout_wheel_monthLabel); + String dayLabel = typedArray.getString(R.styleable.DateWheelLayout_wheel_dayLabel); + typedArray.recycle(); + setDateLabel(yearLabel, monthLabel, dayLabel); + setDateFormatter(new SimpleDateFormatter()); + } + + @Override + protected void onVisibilityChanged(@NonNull View changedView, int visibility) { + super.onVisibilityChanged(changedView, visibility); + if (visibility == VISIBLE && startValue == null && endValue == null) { + setRange(DateEntity.today(), DateEntity.yearOnFuture(30), DateEntity.today()); + } + } + + @Override + public void onWheelSelected(WheelView view, int position) { + int id = view.getId(); + if (id == R.id.wheel_picker_date_year_wheel) { + selectedYear = yearWheelView.getItem(position); + if (resetWhenLinkage) { + selectedMonth = null; + selectedDay = null; + } + changeMonth(selectedYear); + dateSelectedCallback(); + return; + } + if (id == R.id.wheel_picker_date_month_wheel) { + selectedMonth = monthWheelView.getItem(position); + if (resetWhenLinkage) { + selectedDay = null; + } + changeDay(selectedYear, selectedMonth); + dateSelectedCallback(); + return; + } + if (id == R.id.wheel_picker_date_day_wheel) { + selectedDay = dayWheelView.getItem(position); + dateSelectedCallback(); + } + } + + @Override + public void onWheelScrollStateChanged(WheelView view, @ScrollState int state) { + int id = view.getId(); + if (id == R.id.wheel_picker_date_year_wheel) { + monthWheelView.setEnabled(state == ScrollState.IDLE); + dayWheelView.setEnabled(state == ScrollState.IDLE); + return; + } + if (id == R.id.wheel_picker_date_month_wheel) { + yearWheelView.setEnabled(state == ScrollState.IDLE); + dayWheelView.setEnabled(state == ScrollState.IDLE); + return; + } + if (id == R.id.wheel_picker_date_day_wheel) { + yearWheelView.setEnabled(state == ScrollState.IDLE); + monthWheelView.setEnabled(state == ScrollState.IDLE); + } + } + + private void dateSelectedCallback() { + if (onDateSelectedListener == null) { + return; + } + dayWheelView.post(new Runnable() { + @Override + public void run() { + onDateSelectedListener.onDateSelected(selectedYear, selectedMonth, selectedDay); + } + }); + } + + public void setDateMode(@DateMode int dateMode) { + yearWheelView.setVisibility(View.VISIBLE); + yearLabelView.setVisibility(View.VISIBLE); + monthWheelView.setVisibility(View.VISIBLE); + monthLabelView.setVisibility(View.VISIBLE); + dayWheelView.setVisibility(View.VISIBLE); + dayLabelView.setVisibility(View.VISIBLE); + if (dateMode == DateMode.NONE) { + yearWheelView.setVisibility(View.GONE); + yearLabelView.setVisibility(View.GONE); + monthWheelView.setVisibility(View.GONE); + monthLabelView.setVisibility(View.GONE); + dayWheelView.setVisibility(View.GONE); + dayLabelView.setVisibility(View.GONE); + return; + } + if (dateMode == DateMode.MONTH_DAY) { + yearWheelView.setVisibility(View.GONE); + yearLabelView.setVisibility(View.GONE); + return; + } + if (dateMode == DateMode.YEAR_MONTH) { + dayWheelView.setVisibility(View.GONE); + dayLabelView.setVisibility(View.GONE); + } + if (dateMode == com.tfq.finances.utils.androidpicker.DateMode.YEAR) { + monthWheelView.setVisibility(View.GONE); + monthLabelView.setVisibility(View.GONE); + dayWheelView.setVisibility(View.GONE); + dayLabelView.setVisibility(View.GONE); + } + } + + /** + * 设置日期时间范围 + */ + public void setRange(DateEntity startValue, DateEntity endValue) { + setRange(startValue, endValue, null); + } + + /** + * 设置日期时间范围 + */ + public void setRange(DateEntity startValue, DateEntity endValue, DateEntity defaultValue) { + if (startValue == null) { + startValue = DateEntity.today(); + } + if (endValue == null) { + endValue = DateEntity.yearOnFuture(30); + } + if (endValue.toTimeInMillis() < startValue.toTimeInMillis()) { + throw new IllegalArgumentException("Ensure the start date is less than the end date"); + } + this.startValue = startValue; + this.endValue = endValue; + if (defaultValue != null) { + selectedYear = defaultValue.getYear(); + selectedMonth = defaultValue.getMonth(); + selectedDay = defaultValue.getDay(); + } else { + selectedYear = null; + selectedMonth = null; + selectedDay = null; + } + changeYear(); + } + + public void setDefaultValue(DateEntity defaultValue) { + setRange(startValue, endValue, defaultValue); + } + + public void setDateFormatter(final DateFormatter dateFormatter) { + if (dateFormatter == null) { + return; + } + yearWheelView.setFormatter(new WheelFormatter() { + @Override + public String formatItem(@NonNull Object value) { + return dateFormatter.formatYear((Integer) value); + } + }); + monthWheelView.setFormatter(new WheelFormatter() { + @Override + public String formatItem(@NonNull Object value) { + return dateFormatter.formatMonth((Integer) value); + } + }); + dayWheelView.setFormatter(new WheelFormatter() { + @Override + public String formatItem(@NonNull Object value) { + return dateFormatter.formatDay((Integer) value); + } + }); + } + + public void setDateLabel(CharSequence year, CharSequence month, CharSequence day) { + yearLabelView.setText(year); + monthLabelView.setText(month); + dayLabelView.setText(day); + } + + public void setOnDateSelectedListener(OnDateSelectedListener onDateSelectedListener) { + this.onDateSelectedListener = onDateSelectedListener; + } + + public void setResetWhenLinkage(boolean resetWhenLinkage) { + this.resetWhenLinkage = resetWhenLinkage; + } + + public final DateEntity getStartValue() { + return startValue; + } + + public final DateEntity getEndValue() { + return endValue; + } + + public final NumberWheelView getYearWheelView() { + return yearWheelView; + } + + public final NumberWheelView getMonthWheelView() { + return monthWheelView; + } + + public final NumberWheelView getDayWheelView() { + return dayWheelView; + } + + public final TextView getYearLabelView() { + return yearLabelView; + } + + public final TextView getMonthLabelView() { + return monthLabelView; + } + + public final TextView getDayLabelView() { + return dayLabelView; + } + + public final int getSelectedYear() { + return yearWheelView.getCurrentItem(); + } + + public final int getSelectedMonth() { + return monthWheelView.getCurrentItem(); + } + + public final int getSelectedDay() { + return dayWheelView.getCurrentItem(); + } + + private void changeYear() { + final int min = Math.min(startValue.getYear(), endValue.getYear()); + final int max = Math.max(startValue.getYear(), endValue.getYear()); + if (selectedYear == null) { + selectedYear = min; + } else { + selectedYear = Math.max(selectedYear, min); + selectedYear = Math.min(selectedYear, max); + } + yearWheelView.setRange(min, max, 1); + yearWheelView.setDefaultValue(selectedYear); + changeMonth(selectedYear); + } + + private void changeMonth(int year) { + final int min, max; + //开始年份和结束年份相同(即只有一个年份,这种情况建议使用月日模式) + if (startValue.getYear() == endValue.getYear()) { + min = Math.min(startValue.getMonth(), endValue.getMonth()); + max = Math.max(startValue.getMonth(), endValue.getMonth()); + } + //当前所选年份和开始年份相同 + else if (year == startValue.getYear()) { + min = startValue.getMonth(); + max = 12; + } + //当前所选年份和结束年份相同 + else if (year == endValue.getYear()) { + min = 1; + max = endValue.getMonth(); + } + //当前所选年份在开始年份和结束年份之间 + else { + min = 1; + max = 12; + } + if (selectedMonth == null) { + selectedMonth = min; + } else { + selectedMonth = Math.max(selectedMonth, min); + selectedMonth = Math.min(selectedMonth, max); + } + monthWheelView.setRange(min, max, 1); + monthWheelView.setDefaultValue(selectedMonth); + changeDay(year, selectedMonth); + } + + private void changeDay(int year, int month) { + final int min, max; + //开始年月及结束年月相同情况 + if (year == startValue.getYear() && month == startValue.getMonth() + && year == endValue.getYear() && month == endValue.getMonth()) { + min = startValue.getDay(); + max = endValue.getDay(); + } + //开始年月相同情况 + else if (year == startValue.getYear() && month == startValue.getMonth()) { + min = startValue.getDay(); + max = getTotalDaysInMonth(year, month); + } + //结束年月相同情况 + else if (year == endValue.getYear() && month == endValue.getMonth()) { + min = 1; + max = endValue.getDay(); + } else { + min = 1; + max = getTotalDaysInMonth(year, month); + } + if (selectedDay == null) { + selectedDay = min; + } else { + selectedDay = Math.max(selectedDay, min); + selectedDay = Math.min(selectedDay, max); + } + dayWheelView.setRange(min, max, 1); + dayWheelView.setDefaultValue(selectedDay); + } + + /** + * 根据年份及月份获取每月的天数,类似于{@link java.util.Calendar#getActualMaximum(int)} + */ + private int getTotalDaysInMonth(int year, int month) { + switch (month) { + case 1: + case 3: + case 5: + case 7: + case 8: + case 10: + case 12: + // 大月月份为31天 + return 31; + case 4: + case 6: + case 9: + case 11: + // 小月月份为30天 + return 30; + case 2: + // 二月需要判断是否闰年 + if (year <= 0) { + return 29; + } + // 是否闰年:能被4整除但不能被100整除;能被400整除; + boolean isLeap = (year % 4 == 0 && year % 100 != 0) || year % 400 == 0; + if (isLeap) { + return 29; + } else { + return 28; + } + default: + return 30; + } + } + +} diff --git a/app/src/main/java/com/tfq/finances/utils/animation/AnimationClick.java b/app/src/main/java/com/tfq/finances/utils/animation/AnimationClick.java new file mode 100644 index 0000000..52bce1c --- /dev/null +++ b/app/src/main/java/com/tfq/finances/utils/animation/AnimationClick.java @@ -0,0 +1,44 @@ +package com.tfq.finances.utils.animation; + +import android.animation.AnimatorSet; +import android.animation.ObjectAnimator; +import android.view.View; + +public class AnimationClick { + + public static void startScaleAnimation(View view) { + long duration = 300; + // 创建放大的动画 使用传入的 View 对象进行 X 轴的缩放动画 + ObjectAnimator scaleUpX = ObjectAnimator.ofFloat(view, "scaleX", 0.7f, 1f); + scaleUpX.setDuration(duration); + // 使用传入的 View 对象进行 Y 轴的缩放动画 + ObjectAnimator scaleUpY = ObjectAnimator.ofFloat(view, "scaleY", 0.7f, 1f); + scaleUpY.setDuration(duration); + + // 创建动画集合 + AnimatorSet scaleUpSet = new AnimatorSet(); + // 让两个缩放动画同时播放 + scaleUpSet.playTogether(scaleUpX, scaleUpY); + + // 启动动画集合 + scaleUpSet.start(); + } + + public static void startScaleAnimation(View view, long duration) { + // 创建放大的动画 使用传入的 View 对象进行 X 轴的缩放动画 + ObjectAnimator scaleUpX = ObjectAnimator.ofFloat(view, "scaleX", 0.7f, 1f); + scaleUpX.setDuration(duration); + // 使用传入的 View 对象进行 Y 轴的缩放动画 + ObjectAnimator scaleUpY = ObjectAnimator.ofFloat(view, "scaleY", 0.7f, 1f); + scaleUpY.setDuration(duration); + + // 创建动画集合 + AnimatorSet scaleUpSet = new AnimatorSet(); + // 让两个缩放动画同时播放 + scaleUpSet.playTogether(scaleUpX, scaleUpY); + + // 启动动画集合 + scaleUpSet.start(); + } + +} diff --git a/app/src/main/res/anim/actionsheet_dialog_in.xml b/app/src/main/res/anim/actionsheet_dialog_in.xml new file mode 100644 index 0000000..cfd58a9 --- /dev/null +++ b/app/src/main/res/anim/actionsheet_dialog_in.xml @@ -0,0 +1,5 @@ + + diff --git a/app/src/main/res/anim/actionsheet_dialog_out.xml b/app/src/main/res/anim/actionsheet_dialog_out.xml new file mode 100644 index 0000000..5439a7a --- /dev/null +++ b/app/src/main/res/anim/actionsheet_dialog_out.xml @@ -0,0 +1,5 @@ + + diff --git a/app/src/main/res/anim/activity_in_animation.xml b/app/src/main/res/anim/activity_in_animation.xml new file mode 100644 index 0000000..5f704aa --- /dev/null +++ b/app/src/main/res/anim/activity_in_animation.xml @@ -0,0 +1,7 @@ + + + + \ No newline at end of file diff --git a/app/src/main/res/anim/activity_out_animation.xml b/app/src/main/res/anim/activity_out_animation.xml new file mode 100644 index 0000000..3eb5162 --- /dev/null +++ b/app/src/main/res/anim/activity_out_animation.xml @@ -0,0 +1,7 @@ + + + + \ No newline at end of file diff --git a/app/src/main/res/anim/anim_float_window_enter.xml b/app/src/main/res/anim/anim_float_window_enter.xml new file mode 100644 index 0000000..5790fe7 --- /dev/null +++ b/app/src/main/res/anim/anim_float_window_enter.xml @@ -0,0 +1,13 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/anim/anim_float_window_exit.xml b/app/src/main/res/anim/anim_float_window_exit.xml new file mode 100644 index 0000000..04818af --- /dev/null +++ b/app/src/main/res/anim/anim_float_window_exit.xml @@ -0,0 +1,13 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/anim/bottom_menu_enter.xml b/app/src/main/res/anim/bottom_menu_enter.xml new file mode 100644 index 0000000..01c4795 --- /dev/null +++ b/app/src/main/res/anim/bottom_menu_enter.xml @@ -0,0 +1,5 @@ + + \ No newline at end of file diff --git a/app/src/main/res/anim/bottom_menu_exit.xml b/app/src/main/res/anim/bottom_menu_exit.xml new file mode 100644 index 0000000..e197528 --- /dev/null +++ b/app/src/main/res/anim/bottom_menu_exit.xml @@ -0,0 +1,5 @@ + + diff --git a/app/src/main/res/anim/cycle_7.xml b/app/src/main/res/anim/cycle_7.xml new file mode 100644 index 0000000..c396f74 --- /dev/null +++ b/app/src/main/res/anim/cycle_7.xml @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/app/src/main/res/anim/dialog_show_enter.xml b/app/src/main/res/anim/dialog_show_enter.xml new file mode 100644 index 0000000..b4b0204 --- /dev/null +++ b/app/src/main/res/anim/dialog_show_enter.xml @@ -0,0 +1,6 @@ + + + + \ No newline at end of file diff --git a/app/src/main/res/anim/dialog_show_enter_from_top.xml b/app/src/main/res/anim/dialog_show_enter_from_top.xml new file mode 100644 index 0000000..05e8a1a --- /dev/null +++ b/app/src/main/res/anim/dialog_show_enter_from_top.xml @@ -0,0 +1,6 @@ + + + + \ No newline at end of file diff --git a/app/src/main/res/anim/dialog_show_exis.xml b/app/src/main/res/anim/dialog_show_exis.xml new file mode 100644 index 0000000..bcc4d03 --- /dev/null +++ b/app/src/main/res/anim/dialog_show_exis.xml @@ -0,0 +1,6 @@ + + + + \ No newline at end of file diff --git a/app/src/main/res/anim/dialog_show_exis_from_top.xml b/app/src/main/res/anim/dialog_show_exis_from_top.xml new file mode 100644 index 0000000..70acad9 --- /dev/null +++ b/app/src/main/res/anim/dialog_show_exis_from_top.xml @@ -0,0 +1,6 @@ + + + + \ No newline at end of file diff --git a/app/src/main/res/anim/shake.xml b/app/src/main/res/anim/shake.xml new file mode 100644 index 0000000..6986867 --- /dev/null +++ b/app/src/main/res/anim/shake.xml @@ -0,0 +1,6 @@ + + \ No newline at end of file diff --git a/app/src/main/res/anim/slide_down.xml b/app/src/main/res/anim/slide_down.xml new file mode 100644 index 0000000..0ec33ee --- /dev/null +++ b/app/src/main/res/anim/slide_down.xml @@ -0,0 +1,7 @@ + + + + \ No newline at end of file diff --git a/app/src/main/res/anim/slide_up.xml b/app/src/main/res/anim/slide_up.xml new file mode 100644 index 0000000..57b5325 --- /dev/null +++ b/app/src/main/res/anim/slide_up.xml @@ -0,0 +1,7 @@ + + + + \ No newline at end of file diff --git a/app/src/main/res/color/menu_live_text_selector.xml b/app/src/main/res/color/menu_live_text_selector.xml new file mode 100644 index 0000000..2147b70 --- /dev/null +++ b/app/src/main/res/color/menu_live_text_selector.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/color/menu_text_selector.xml b/app/src/main/res/color/menu_text_selector.xml new file mode 100644 index 0000000..94886f5 --- /dev/null +++ b/app/src/main/res/color/menu_text_selector.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable-xhdpi/app_logo.png b/app/src/main/res/drawable-xhdpi/app_logo.png new file mode 100644 index 0000000..258adc5 Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/app_logo.png differ diff --git a/app/src/main/res/drawable-xhdpi/app_splash.png b/app/src/main/res/drawable-xhdpi/app_splash.png new file mode 100644 index 0000000..7613a46 Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/app_splash.png differ diff --git a/app/src/main/res/drawable-xxhdpi/back.png b/app/src/main/res/drawable-xxhdpi/back.png new file mode 100644 index 0000000..5a564e3 Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/back.png differ diff --git a/app/src/main/res/drawable-xxhdpi/bg_head.9.png b/app/src/main/res/drawable-xxhdpi/bg_head.9.png new file mode 100644 index 0000000..ae007fc Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/bg_head.9.png differ diff --git a/app/src/main/res/drawable-xxhdpi/bluedot.png b/app/src/main/res/drawable-xxhdpi/bluedot.png new file mode 100644 index 0000000..285f171 Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/bluedot.png differ diff --git a/app/src/main/res/drawable-xxhdpi/bluedown.png b/app/src/main/res/drawable-xxhdpi/bluedown.png new file mode 100644 index 0000000..515d78b Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/bluedown.png differ diff --git a/app/src/main/res/drawable-xxhdpi/blueleft.png b/app/src/main/res/drawable-xxhdpi/blueleft.png new file mode 100644 index 0000000..bddfb78 Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/blueleft.png differ diff --git a/app/src/main/res/drawable-xxhdpi/blueleftdown.png b/app/src/main/res/drawable-xxhdpi/blueleftdown.png new file mode 100644 index 0000000..b77aff7 Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/blueleftdown.png differ diff --git a/app/src/main/res/drawable-xxhdpi/blueleftup.png b/app/src/main/res/drawable-xxhdpi/blueleftup.png new file mode 100644 index 0000000..6c47f58 Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/blueleftup.png differ diff --git a/app/src/main/res/drawable-xxhdpi/blueright.png b/app/src/main/res/drawable-xxhdpi/blueright.png new file mode 100644 index 0000000..3e863fb Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/blueright.png differ diff --git a/app/src/main/res/drawable-xxhdpi/bluerightdown.png b/app/src/main/res/drawable-xxhdpi/bluerightdown.png new file mode 100644 index 0000000..0c3dc45 Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/bluerightdown.png differ diff --git a/app/src/main/res/drawable-xxhdpi/bluerightup.png b/app/src/main/res/drawable-xxhdpi/bluerightup.png new file mode 100644 index 0000000..4e6537b Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/bluerightup.png differ diff --git a/app/src/main/res/drawable-xxhdpi/blueup.png b/app/src/main/res/drawable-xxhdpi/blueup.png new file mode 100644 index 0000000..a443ee5 Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/blueup.png differ diff --git a/app/src/main/res/drawable-xxhdpi/dislike_icon.png b/app/src/main/res/drawable-xxhdpi/dislike_icon.png new file mode 100644 index 0000000..03ec72f Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/dislike_icon.png differ diff --git a/app/src/main/res/drawable-xxhdpi/gesturewhite.png b/app/src/main/res/drawable-xxhdpi/gesturewhite.png new file mode 100644 index 0000000..9870eca Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/gesturewhite.png differ diff --git a/app/src/main/res/drawable-xxhdpi/ic_desp_down.png b/app/src/main/res/drawable-xxhdpi/ic_desp_down.png new file mode 100644 index 0000000..2478ad4 Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/ic_desp_down.png differ diff --git a/app/src/main/res/drawable-xxhdpi/ic_desp_up.png b/app/src/main/res/drawable-xxhdpi/ic_desp_up.png new file mode 100644 index 0000000..2d05308 Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/ic_desp_up.png differ diff --git a/app/src/main/res/drawable-xxhdpi/ic_fileinfos_all.png b/app/src/main/res/drawable-xxhdpi/ic_fileinfos_all.png new file mode 100644 index 0000000..c40e20c Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/ic_fileinfos_all.png differ diff --git a/app/src/main/res/drawable-xxhdpi/ic_fileinfos_apk.png b/app/src/main/res/drawable-xxhdpi/ic_fileinfos_apk.png new file mode 100644 index 0000000..83abe97 Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/ic_fileinfos_apk.png differ diff --git a/app/src/main/res/drawable-xxhdpi/ic_fileinfos_excel.png b/app/src/main/res/drawable-xxhdpi/ic_fileinfos_excel.png new file mode 100644 index 0000000..83abe97 Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/ic_fileinfos_excel.png differ diff --git a/app/src/main/res/drawable-xxhdpi/ic_fileinfos_pdf.png b/app/src/main/res/drawable-xxhdpi/ic_fileinfos_pdf.png new file mode 100644 index 0000000..eb9808e Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/ic_fileinfos_pdf.png differ diff --git a/app/src/main/res/drawable-xxhdpi/ic_fileinfos_ppt.png b/app/src/main/res/drawable-xxhdpi/ic_fileinfos_ppt.png new file mode 100644 index 0000000..630aa68 Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/ic_fileinfos_ppt.png differ diff --git a/app/src/main/res/drawable-xxhdpi/ic_fileinfos_txt.png b/app/src/main/res/drawable-xxhdpi/ic_fileinfos_txt.png new file mode 100644 index 0000000..ed7ab9f Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/ic_fileinfos_txt.png differ diff --git a/app/src/main/res/drawable-xxhdpi/ic_fileinfos_word.png b/app/src/main/res/drawable-xxhdpi/ic_fileinfos_word.png new file mode 100644 index 0000000..6acadfc Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/ic_fileinfos_word.png differ diff --git a/app/src/main/res/drawable-xxhdpi/ic_fileinfos_zip.png b/app/src/main/res/drawable-xxhdpi/ic_fileinfos_zip.png new file mode 100644 index 0000000..151631b Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/ic_fileinfos_zip.png differ diff --git a/app/src/main/res/drawable-xxhdpi/ic_kusic_current_play.png b/app/src/main/res/drawable-xxhdpi/ic_kusic_current_play.png new file mode 100644 index 0000000..71a3d66 Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/ic_kusic_current_play.png differ diff --git a/app/src/main/res/drawable-xxhdpi/ic_music_audio.png b/app/src/main/res/drawable-xxhdpi/ic_music_audio.png new file mode 100644 index 0000000..7f888e8 Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/ic_music_audio.png differ diff --git a/app/src/main/res/drawable-xxhdpi/ic_music_collect_noimal_white.png b/app/src/main/res/drawable-xxhdpi/ic_music_collect_noimal_white.png new file mode 100644 index 0000000..de120a8 Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/ic_music_collect_noimal_white.png differ diff --git a/app/src/main/res/drawable-xxhdpi/ic_music_details_album.png b/app/src/main/res/drawable-xxhdpi/ic_music_details_album.png new file mode 100644 index 0000000..1e90158 Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/ic_music_details_album.png differ diff --git a/app/src/main/res/drawable-xxhdpi/ic_music_details_anchor.png b/app/src/main/res/drawable-xxhdpi/ic_music_details_anchor.png new file mode 100644 index 0000000..d2ce721 Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/ic_music_details_anchor.png differ diff --git a/app/src/main/res/drawable-xxhdpi/ic_music_details_collect.png b/app/src/main/res/drawable-xxhdpi/ic_music_details_collect.png new file mode 100644 index 0000000..e132fab Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/ic_music_details_collect.png differ diff --git a/app/src/main/res/drawable-xxhdpi/ic_music_details_detele.png b/app/src/main/res/drawable-xxhdpi/ic_music_details_detele.png new file mode 100644 index 0000000..f7e866f Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/ic_music_details_detele.png differ diff --git a/app/src/main/res/drawable-xxhdpi/ic_music_details_durtion.png b/app/src/main/res/drawable-xxhdpi/ic_music_details_durtion.png new file mode 100644 index 0000000..b7278d1 Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/ic_music_details_durtion.png differ diff --git a/app/src/main/res/drawable-xxhdpi/ic_music_details_next.png b/app/src/main/res/drawable-xxhdpi/ic_music_details_next.png new file mode 100644 index 0000000..9aab256 Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/ic_music_details_next.png differ diff --git a/app/src/main/res/drawable-xxhdpi/ic_music_details_share.png b/app/src/main/res/drawable-xxhdpi/ic_music_details_share.png new file mode 100644 index 0000000..5cf92c3 Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/ic_music_details_share.png differ diff --git a/app/src/main/res/drawable-xxhdpi/ic_music_empty_default_img.png b/app/src/main/res/drawable-xxhdpi/ic_music_empty_default_img.png new file mode 100644 index 0000000..c14eb78 Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/ic_music_empty_default_img.png differ diff --git a/app/src/main/res/drawable-xxhdpi/ic_music_empty_error.png b/app/src/main/res/drawable-xxhdpi/ic_music_empty_error.png new file mode 100644 index 0000000..cb8e75d Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/ic_music_empty_error.png differ diff --git a/app/src/main/res/drawable-xxhdpi/ic_music_index_collect.png b/app/src/main/res/drawable-xxhdpi/ic_music_index_collect.png new file mode 100644 index 0000000..07fc844 Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/ic_music_index_collect.png differ diff --git a/app/src/main/res/drawable-xxhdpi/ic_music_index_last_play.png b/app/src/main/res/drawable-xxhdpi/ic_music_index_last_play.png new file mode 100644 index 0000000..6efc881 Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/ic_music_index_last_play.png differ diff --git a/app/src/main/res/drawable-xxhdpi/ic_music_index_music.png b/app/src/main/res/drawable-xxhdpi/ic_music_index_music.png new file mode 100644 index 0000000..a7d50bd Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/ic_music_index_music.png differ diff --git a/app/src/main/res/drawable-xxhdpi/ic_music_input_clean.png b/app/src/main/res/drawable-xxhdpi/ic_music_input_clean.png new file mode 100644 index 0000000..c9a125f Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/ic_music_input_clean.png differ diff --git a/app/src/main/res/drawable-xxhdpi/ic_music_item_menu.png b/app/src/main/res/drawable-xxhdpi/ic_music_item_menu.png new file mode 100644 index 0000000..5b240b2 Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/ic_music_item_menu.png differ diff --git a/app/src/main/res/drawable-xxhdpi/ic_music_last_noimal.png b/app/src/main/res/drawable-xxhdpi/ic_music_last_noimal.png new file mode 100644 index 0000000..9985b87 Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/ic_music_last_noimal.png differ diff --git a/app/src/main/res/drawable-xxhdpi/ic_music_last_pre.png b/app/src/main/res/drawable-xxhdpi/ic_music_last_pre.png new file mode 100644 index 0000000..9985b87 Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/ic_music_last_pre.png differ diff --git a/app/src/main/res/drawable-xxhdpi/ic_music_launcher.png b/app/src/main/res/drawable-xxhdpi/ic_music_launcher.png new file mode 100644 index 0000000..6826266 Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/ic_music_launcher.png differ diff --git a/app/src/main/res/drawable-xxhdpi/ic_music_lock_model_random_noimal.png b/app/src/main/res/drawable-xxhdpi/ic_music_lock_model_random_noimal.png new file mode 100644 index 0000000..3215105 Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/ic_music_lock_model_random_noimal.png differ diff --git a/app/src/main/res/drawable-xxhdpi/ic_music_lock_model_random_pre.png b/app/src/main/res/drawable-xxhdpi/ic_music_lock_model_random_pre.png new file mode 100644 index 0000000..2960a5c Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/ic_music_lock_model_random_pre.png differ diff --git a/app/src/main/res/drawable-xxhdpi/ic_music_model_loop_noimal.png b/app/src/main/res/drawable-xxhdpi/ic_music_model_loop_noimal.png new file mode 100644 index 0000000..db6b316 Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/ic_music_model_loop_noimal.png differ diff --git a/app/src/main/res/drawable-xxhdpi/ic_music_model_loop_pre.png b/app/src/main/res/drawable-xxhdpi/ic_music_model_loop_pre.png new file mode 100644 index 0000000..10968b4 Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/ic_music_model_loop_pre.png differ diff --git a/app/src/main/res/drawable-xxhdpi/ic_music_model_signle_noimal.png b/app/src/main/res/drawable-xxhdpi/ic_music_model_signle_noimal.png new file mode 100644 index 0000000..aeef31f Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/ic_music_model_signle_noimal.png differ diff --git a/app/src/main/res/drawable-xxhdpi/ic_music_model_signle_pre.png b/app/src/main/res/drawable-xxhdpi/ic_music_model_signle_pre.png new file mode 100644 index 0000000..0401946 Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/ic_music_model_signle_pre.png differ diff --git a/app/src/main/res/drawable-xxhdpi/ic_music_next_noimal.png b/app/src/main/res/drawable-xxhdpi/ic_music_next_noimal.png new file mode 100644 index 0000000..50a0c1a Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/ic_music_next_noimal.png differ diff --git a/app/src/main/res/drawable-xxhdpi/ic_music_next_pre.png b/app/src/main/res/drawable-xxhdpi/ic_music_next_pre.png new file mode 100644 index 0000000..50a0c1a Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/ic_music_next_pre.png differ diff --git a/app/src/main/res/drawable-xxhdpi/ic_music_pause_noimal.png b/app/src/main/res/drawable-xxhdpi/ic_music_pause_noimal.png new file mode 100644 index 0000000..e118910 Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/ic_music_pause_noimal.png differ diff --git a/app/src/main/res/drawable-xxhdpi/ic_music_pause_pre.png b/app/src/main/res/drawable-xxhdpi/ic_music_pause_pre.png new file mode 100644 index 0000000..e118910 Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/ic_music_pause_pre.png differ diff --git a/app/src/main/res/drawable-xxhdpi/ic_music_play_alll.png b/app/src/main/res/drawable-xxhdpi/ic_music_play_alll.png new file mode 100644 index 0000000..c661dc6 Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/ic_music_play_alll.png differ diff --git a/app/src/main/res/drawable-xxhdpi/ic_music_play_noimal.png b/app/src/main/res/drawable-xxhdpi/ic_music_play_noimal.png new file mode 100644 index 0000000..cd0c372 Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/ic_music_play_noimal.png differ diff --git a/app/src/main/res/drawable-xxhdpi/ic_music_play_pre.png b/app/src/main/res/drawable-xxhdpi/ic_music_play_pre.png new file mode 100644 index 0000000..cd0c372 Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/ic_music_play_pre.png differ diff --git a/app/src/main/res/drawable-xxhdpi/ic_music_private.png b/app/src/main/res/drawable-xxhdpi/ic_music_private.png new file mode 100644 index 0000000..3f3778b Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/ic_music_private.png differ diff --git a/app/src/main/res/drawable-xxhdpi/ic_music_search.png b/app/src/main/res/drawable-xxhdpi/ic_music_search.png new file mode 100644 index 0000000..edca09e Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/ic_music_search.png differ diff --git a/app/src/main/res/drawable-xxhdpi/ic_music_search_mini.png b/app/src/main/res/drawable-xxhdpi/ic_music_search_mini.png new file mode 100644 index 0000000..6dd61ca Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/ic_music_search_mini.png differ diff --git a/app/src/main/res/drawable-xxhdpi/ic_music_search_remove.png b/app/src/main/res/drawable-xxhdpi/ic_music_search_remove.png new file mode 100644 index 0000000..317cfa2 Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/ic_music_search_remove.png differ diff --git a/app/src/main/res/drawable-xxhdpi/ic_music_support.png b/app/src/main/res/drawable-xxhdpi/ic_music_support.png new file mode 100644 index 0000000..98196b4 Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/ic_music_support.png differ diff --git a/app/src/main/res/drawable-xxhdpi/ic_music_title_bar_back_noimal.png b/app/src/main/res/drawable-xxhdpi/ic_music_title_bar_back_noimal.png new file mode 100644 index 0000000..8e4b3a2 Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/ic_music_title_bar_back_noimal.png differ diff --git a/app/src/main/res/drawable-xxhdpi/ic_music_title_bar_back_pre.png b/app/src/main/res/drawable-xxhdpi/ic_music_title_bar_back_pre.png new file mode 100644 index 0000000..af2ba6d Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/ic_music_title_bar_back_pre.png differ diff --git a/app/src/main/res/drawable-xxhdpi/ic_music_update_top_bg.webp b/app/src/main/res/drawable-xxhdpi/ic_music_update_top_bg.webp new file mode 100644 index 0000000..a653745 Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/ic_music_update_top_bg.webp differ diff --git a/app/src/main/res/drawable-xxhdpi/ic_setting_tips1.png b/app/src/main/res/drawable-xxhdpi/ic_setting_tips1.png new file mode 100644 index 0000000..b0e61f6 Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/ic_setting_tips1.png differ diff --git a/app/src/main/res/drawable-xxhdpi/ic_setting_tips2.png b/app/src/main/res/drawable-xxhdpi/ic_setting_tips2.png new file mode 100644 index 0000000..f033eaf Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/ic_setting_tips2.png differ diff --git a/app/src/main/res/drawable-xxhdpi/ic_setting_tips3.png b/app/src/main/res/drawable-xxhdpi/ic_setting_tips3.png new file mode 100644 index 0000000..20ef664 Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/ic_setting_tips3.png differ diff --git a/app/src/main/res/drawable-xxhdpi/ic_video_pause.png b/app/src/main/res/drawable-xxhdpi/ic_video_pause.png new file mode 100644 index 0000000..d316e4a Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/ic_video_pause.png differ diff --git a/app/src/main/res/drawable-xxhdpi/ic_video_play.png b/app/src/main/res/drawable-xxhdpi/ic_video_play.png new file mode 100644 index 0000000..bf09c3d Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/ic_video_play.png differ diff --git a/app/src/main/res/drawable-xxhdpi/index_mine_shadow_bg.9.png b/app/src/main/res/drawable-xxhdpi/index_mine_shadow_bg.9.png new file mode 100644 index 0000000..f4258aa Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/index_mine_shadow_bg.9.png differ diff --git a/app/src/main/res/drawable-xxhdpi/loading_1.png b/app/src/main/res/drawable-xxhdpi/loading_1.png new file mode 100644 index 0000000..9613dda Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/loading_1.png differ diff --git a/app/src/main/res/drawable-xxhdpi/loading_10.png b/app/src/main/res/drawable-xxhdpi/loading_10.png new file mode 100644 index 0000000..caffae9 Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/loading_10.png differ diff --git a/app/src/main/res/drawable-xxhdpi/loading_11.png b/app/src/main/res/drawable-xxhdpi/loading_11.png new file mode 100644 index 0000000..221088d Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/loading_11.png differ diff --git a/app/src/main/res/drawable-xxhdpi/loading_12.png b/app/src/main/res/drawable-xxhdpi/loading_12.png new file mode 100644 index 0000000..12b9d2a Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/loading_12.png differ diff --git a/app/src/main/res/drawable-xxhdpi/loading_2.png b/app/src/main/res/drawable-xxhdpi/loading_2.png new file mode 100644 index 0000000..41da98b Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/loading_2.png differ diff --git a/app/src/main/res/drawable-xxhdpi/loading_3.png b/app/src/main/res/drawable-xxhdpi/loading_3.png new file mode 100644 index 0000000..9027a9e Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/loading_3.png differ diff --git a/app/src/main/res/drawable-xxhdpi/loading_4.png b/app/src/main/res/drawable-xxhdpi/loading_4.png new file mode 100644 index 0000000..9236147 Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/loading_4.png differ diff --git a/app/src/main/res/drawable-xxhdpi/loading_5.png b/app/src/main/res/drawable-xxhdpi/loading_5.png new file mode 100644 index 0000000..9518c5c Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/loading_5.png differ diff --git a/app/src/main/res/drawable-xxhdpi/loading_6.png b/app/src/main/res/drawable-xxhdpi/loading_6.png new file mode 100644 index 0000000..76a42c0 Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/loading_6.png differ diff --git a/app/src/main/res/drawable-xxhdpi/loading_7.png b/app/src/main/res/drawable-xxhdpi/loading_7.png new file mode 100644 index 0000000..c752853 Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/loading_7.png differ diff --git a/app/src/main/res/drawable-xxhdpi/loading_8.png b/app/src/main/res/drawable-xxhdpi/loading_8.png new file mode 100644 index 0000000..ce9a390 Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/loading_8.png differ diff --git a/app/src/main/res/drawable-xxhdpi/loading_9.png b/app/src/main/res/drawable-xxhdpi/loading_9.png new file mode 100644 index 0000000..c1946eb Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/loading_9.png differ diff --git a/app/src/main/res/drawable-xxhdpi/music_ic_dialog_close.png b/app/src/main/res/drawable-xxhdpi/music_ic_dialog_close.png new file mode 100644 index 0000000..224b818 Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/music_ic_dialog_close.png differ diff --git a/app/src/main/res/drawable-xxhdpi/reddot.png b/app/src/main/res/drawable-xxhdpi/reddot.png new file mode 100644 index 0000000..20ac05c Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/reddot.png differ diff --git a/app/src/main/res/drawable-xxhdpi/reddown.png b/app/src/main/res/drawable-xxhdpi/reddown.png new file mode 100644 index 0000000..9b60da8 Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/reddown.png differ diff --git a/app/src/main/res/drawable-xxhdpi/redleft.png b/app/src/main/res/drawable-xxhdpi/redleft.png new file mode 100644 index 0000000..f5d805c Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/redleft.png differ diff --git a/app/src/main/res/drawable-xxhdpi/redleftdown.png b/app/src/main/res/drawable-xxhdpi/redleftdown.png new file mode 100644 index 0000000..7acbd28 Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/redleftdown.png differ diff --git a/app/src/main/res/drawable-xxhdpi/redleftup.png b/app/src/main/res/drawable-xxhdpi/redleftup.png new file mode 100644 index 0000000..0f46898 Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/redleftup.png differ diff --git a/app/src/main/res/drawable-xxhdpi/redright.png b/app/src/main/res/drawable-xxhdpi/redright.png new file mode 100644 index 0000000..2886fe0 Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/redright.png differ diff --git a/app/src/main/res/drawable-xxhdpi/redrightdown.png b/app/src/main/res/drawable-xxhdpi/redrightdown.png new file mode 100644 index 0000000..c84ae0d Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/redrightdown.png differ diff --git a/app/src/main/res/drawable-xxhdpi/redrightup.png b/app/src/main/res/drawable-xxhdpi/redrightup.png new file mode 100644 index 0000000..4d84940 Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/redrightup.png differ diff --git a/app/src/main/res/drawable-xxhdpi/redup.png b/app/src/main/res/drawable-xxhdpi/redup.png new file mode 100644 index 0000000..c531c5e Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/redup.png differ diff --git a/app/src/main/res/drawable/bg_buy.xml b/app/src/main/res/drawable/bg_buy.xml new file mode 100644 index 0000000..baa911b --- /dev/null +++ b/app/src/main/res/drawable/bg_buy.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/bg_dim.xml b/app/src/main/res/drawable/bg_dim.xml new file mode 100644 index 0000000..e3b10e8 --- /dev/null +++ b/app/src/main/res/drawable/bg_dim.xml @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/bg_font_center.xml b/app/src/main/res/drawable/bg_font_center.xml new file mode 100644 index 0000000..6568124 --- /dev/null +++ b/app/src/main/res/drawable/bg_font_center.xml @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/bg_font_left.xml b/app/src/main/res/drawable/bg_font_left.xml new file mode 100644 index 0000000..3988f37 --- /dev/null +++ b/app/src/main/res/drawable/bg_font_left.xml @@ -0,0 +1,17 @@ + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/bg_font_right.xml b/app/src/main/res/drawable/bg_font_right.xml new file mode 100644 index 0000000..ee409b9 --- /dev/null +++ b/app/src/main/res/drawable/bg_font_right.xml @@ -0,0 +1,17 @@ + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/bg_gray_radio.xml b/app/src/main/res/drawable/bg_gray_radio.xml new file mode 100644 index 0000000..9930398 --- /dev/null +++ b/app/src/main/res/drawable/bg_gray_radio.xml @@ -0,0 +1,8 @@ + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/bg_item_avatar.xml b/app/src/main/res/drawable/bg_item_avatar.xml new file mode 100644 index 0000000..87f64bb --- /dev/null +++ b/app/src/main/res/drawable/bg_item_avatar.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/bg_item_video_player_bottom.xml b/app/src/main/res/drawable/bg_item_video_player_bottom.xml new file mode 100644 index 0000000..523980f --- /dev/null +++ b/app/src/main/res/drawable/bg_item_video_player_bottom.xml @@ -0,0 +1,8 @@ + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/bg_menu_center.xml b/app/src/main/res/drawable/bg_menu_center.xml new file mode 100644 index 0000000..c995c0d --- /dev/null +++ b/app/src/main/res/drawable/bg_menu_center.xml @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/bg_menu_left.xml b/app/src/main/res/drawable/bg_menu_left.xml new file mode 100644 index 0000000..78b923d --- /dev/null +++ b/app/src/main/res/drawable/bg_menu_left.xml @@ -0,0 +1,17 @@ + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/bg_menu_right.xml b/app/src/main/res/drawable/bg_menu_right.xml new file mode 100644 index 0000000..630099e --- /dev/null +++ b/app/src/main/res/drawable/bg_menu_right.xml @@ -0,0 +1,17 @@ + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/bg_no_agree.xml b/app/src/main/res/drawable/bg_no_agree.xml new file mode 100644 index 0000000..30d741c --- /dev/null +++ b/app/src/main/res/drawable/bg_no_agree.xml @@ -0,0 +1,8 @@ + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/bg_photo_del.xml b/app/src/main/res/drawable/bg_photo_del.xml new file mode 100644 index 0000000..6ff1036 --- /dev/null +++ b/app/src/main/res/drawable/bg_photo_del.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/bg_play.xml b/app/src/main/res/drawable/bg_play.xml new file mode 100644 index 0000000..5c6fbb1 --- /dev/null +++ b/app/src/main/res/drawable/bg_play.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/bg_popup.xml b/app/src/main/res/drawable/bg_popup.xml new file mode 100644 index 0000000..af3fab2 --- /dev/null +++ b/app/src/main/res/drawable/bg_popup.xml @@ -0,0 +1,6 @@ + + + + + + diff --git a/app/src/main/res/drawable/bg_sercurity.xml b/app/src/main/res/drawable/bg_sercurity.xml new file mode 100644 index 0000000..b89f4b2 --- /dev/null +++ b/app/src/main/res/drawable/bg_sercurity.xml @@ -0,0 +1,8 @@ + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/bg_set_page.png b/app/src/main/res/drawable/bg_set_page.png new file mode 100644 index 0000000..c857b51 Binary files /dev/null and b/app/src/main/res/drawable/bg_set_page.png differ diff --git a/app/src/main/res/drawable/bg_set_up_pwd.xml b/app/src/main/res/drawable/bg_set_up_pwd.xml new file mode 100644 index 0000000..0e1949f --- /dev/null +++ b/app/src/main/res/drawable/bg_set_up_pwd.xml @@ -0,0 +1,9 @@ + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/bg_transactions_left.xml b/app/src/main/res/drawable/bg_transactions_left.xml new file mode 100644 index 0000000..fd1093b --- /dev/null +++ b/app/src/main/res/drawable/bg_transactions_left.xml @@ -0,0 +1,17 @@ + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/bg_transactions_right.xml b/app/src/main/res/drawable/bg_transactions_right.xml new file mode 100644 index 0000000..796719d --- /dev/null +++ b/app/src/main/res/drawable/bg_transactions_right.xml @@ -0,0 +1,17 @@ + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/big_tv_tint_transparent.xml b/app/src/main/res/drawable/big_tv_tint_transparent.xml new file mode 100644 index 0000000..82d4f3f --- /dev/null +++ b/app/src/main/res/drawable/big_tv_tint_transparent.xml @@ -0,0 +1,8 @@ + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/boder_line_01.xml b/app/src/main/res/drawable/boder_line_01.xml new file mode 100644 index 0000000..f998a3a --- /dev/null +++ b/app/src/main/res/drawable/boder_line_01.xml @@ -0,0 +1,8 @@ + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/bottom_menu_btn_selector.xml b/app/src/main/res/drawable/bottom_menu_btn_selector.xml new file mode 100644 index 0000000..bdfb740 --- /dev/null +++ b/app/src/main/res/drawable/bottom_menu_btn_selector.xml @@ -0,0 +1,8 @@ + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/btn_bg_blue.xml b/app/src/main/res/drawable/btn_bg_blue.xml new file mode 100644 index 0000000..bda9084 --- /dev/null +++ b/app/src/main/res/drawable/btn_bg_blue.xml @@ -0,0 +1,8 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/corners_bg.xml b/app/src/main/res/drawable/corners_bg.xml new file mode 100644 index 0000000..53f3a82 --- /dev/null +++ b/app/src/main/res/drawable/corners_bg.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/corners_bg_press.xml b/app/src/main/res/drawable/corners_bg_press.xml new file mode 100644 index 0000000..7ad1bc5 --- /dev/null +++ b/app/src/main/res/drawable/corners_bg_press.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/input_bg.xml b/app/src/main/res/drawable/input_bg.xml new file mode 100644 index 0000000..5da8ed7 --- /dev/null +++ b/app/src/main/res/drawable/input_bg.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/jb_blue.xml b/app/src/main/res/drawable/jb_blue.xml new file mode 100644 index 0000000..38fbe47 --- /dev/null +++ b/app/src/main/res/drawable/jb_blue.xml @@ -0,0 +1,8 @@ + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/jb_blue_dark.xml b/app/src/main/res/drawable/jb_blue_dark.xml new file mode 100644 index 0000000..0a72ece --- /dev/null +++ b/app/src/main/res/drawable/jb_blue_dark.xml @@ -0,0 +1,8 @@ + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/jb_blue_dark_radius.xml b/app/src/main/res/drawable/jb_blue_dark_radius.xml new file mode 100644 index 0000000..3bea670 --- /dev/null +++ b/app/src/main/res/drawable/jb_blue_dark_radius.xml @@ -0,0 +1,9 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/jb_blue_radius.xml b/app/src/main/res/drawable/jb_blue_radius.xml new file mode 100644 index 0000000..8af6a87 --- /dev/null +++ b/app/src/main/res/drawable/jb_blue_radius.xml @@ -0,0 +1,9 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/jb_green.xml b/app/src/main/res/drawable/jb_green.xml new file mode 100644 index 0000000..dde07f0 --- /dev/null +++ b/app/src/main/res/drawable/jb_green.xml @@ -0,0 +1,8 @@ + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/jb_green_radius.xml b/app/src/main/res/drawable/jb_green_radius.xml new file mode 100644 index 0000000..08ac51f --- /dev/null +++ b/app/src/main/res/drawable/jb_green_radius.xml @@ -0,0 +1,9 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/jb_purple.xml b/app/src/main/res/drawable/jb_purple.xml new file mode 100644 index 0000000..7409083 --- /dev/null +++ b/app/src/main/res/drawable/jb_purple.xml @@ -0,0 +1,8 @@ + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/jb_purple_dark.xml b/app/src/main/res/drawable/jb_purple_dark.xml new file mode 100644 index 0000000..1787f6a --- /dev/null +++ b/app/src/main/res/drawable/jb_purple_dark.xml @@ -0,0 +1,8 @@ + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/jb_purple_dark_radius.xml b/app/src/main/res/drawable/jb_purple_dark_radius.xml new file mode 100644 index 0000000..e5321d9 --- /dev/null +++ b/app/src/main/res/drawable/jb_purple_dark_radius.xml @@ -0,0 +1,9 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/jb_purple_radius.xml b/app/src/main/res/drawable/jb_purple_radius.xml new file mode 100644 index 0000000..a1813be --- /dev/null +++ b/app/src/main/res/drawable/jb_purple_radius.xml @@ -0,0 +1,9 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/jb_red.xml b/app/src/main/res/drawable/jb_red.xml new file mode 100644 index 0000000..d6a9cc8 --- /dev/null +++ b/app/src/main/res/drawable/jb_red.xml @@ -0,0 +1,8 @@ + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/jb_red_radius.xml b/app/src/main/res/drawable/jb_red_radius.xml new file mode 100644 index 0000000..43cc178 --- /dev/null +++ b/app/src/main/res/drawable/jb_red_radius.xml @@ -0,0 +1,9 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/jb_red_shallow.xml b/app/src/main/res/drawable/jb_red_shallow.xml new file mode 100644 index 0000000..d9d29f6 --- /dev/null +++ b/app/src/main/res/drawable/jb_red_shallow.xml @@ -0,0 +1,8 @@ + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/lib_item_foreground.xml b/app/src/main/res/drawable/lib_item_foreground.xml new file mode 100644 index 0000000..30dc467 --- /dev/null +++ b/app/src/main/res/drawable/lib_item_foreground.xml @@ -0,0 +1,10 @@ + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/ll_bg_feedback.xml b/app/src/main/res/drawable/ll_bg_feedback.xml new file mode 100644 index 0000000..8b4b7fa --- /dev/null +++ b/app/src/main/res/drawable/ll_bg_feedback.xml @@ -0,0 +1,8 @@ + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/ll_big_boder_line_blue.xml b/app/src/main/res/drawable/ll_big_boder_line_blue.xml new file mode 100644 index 0000000..f998a3a --- /dev/null +++ b/app/src/main/res/drawable/ll_big_boder_line_blue.xml @@ -0,0 +1,8 @@ + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/ll_big_radio_3.xml b/app/src/main/res/drawable/ll_big_radio_3.xml new file mode 100644 index 0000000..49d0ee6 --- /dev/null +++ b/app/src/main/res/drawable/ll_big_radio_3.xml @@ -0,0 +1,8 @@ + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/ll_big_radio_content_wathet.xml b/app/src/main/res/drawable/ll_big_radio_content_wathet.xml new file mode 100644 index 0000000..11bc16f --- /dev/null +++ b/app/src/main/res/drawable/ll_big_radio_content_wathet.xml @@ -0,0 +1,8 @@ + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/ll_boder_head.xml b/app/src/main/res/drawable/ll_boder_head.xml new file mode 100644 index 0000000..772c5b4 --- /dev/null +++ b/app/src/main/res/drawable/ll_boder_head.xml @@ -0,0 +1,8 @@ + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/ll_boder_head_null.xml b/app/src/main/res/drawable/ll_boder_head_null.xml new file mode 100644 index 0000000..6b8fb81 --- /dev/null +++ b/app/src/main/res/drawable/ll_boder_head_null.xml @@ -0,0 +1,8 @@ + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/ll_radio_flowerblue_bottom.xml b/app/src/main/res/drawable/ll_radio_flowerblue_bottom.xml new file mode 100644 index 0000000..02576be --- /dev/null +++ b/app/src/main/res/drawable/ll_radio_flowerblue_bottom.xml @@ -0,0 +1,14 @@ + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/ll_radio_flowerblue_center.xml b/app/src/main/res/drawable/ll_radio_flowerblue_center.xml new file mode 100644 index 0000000..438144c --- /dev/null +++ b/app/src/main/res/drawable/ll_radio_flowerblue_center.xml @@ -0,0 +1,10 @@ + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/ll_radio_flowerblue_top.xml b/app/src/main/res/drawable/ll_radio_flowerblue_top.xml new file mode 100644 index 0000000..b6d4471 --- /dev/null +++ b/app/src/main/res/drawable/ll_radio_flowerblue_top.xml @@ -0,0 +1,12 @@ + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/ll_radio_gray.xml b/app/src/main/res/drawable/ll_radio_gray.xml new file mode 100644 index 0000000..46b0a28 --- /dev/null +++ b/app/src/main/res/drawable/ll_radio_gray.xml @@ -0,0 +1,11 @@ + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/ll_radio_rectangle.xml b/app/src/main/res/drawable/ll_radio_rectangle.xml new file mode 100644 index 0000000..01cd8ac --- /dev/null +++ b/app/src/main/res/drawable/ll_radio_rectangle.xml @@ -0,0 +1,8 @@ + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/ll_radio_rectangle10.xml b/app/src/main/res/drawable/ll_radio_rectangle10.xml new file mode 100644 index 0000000..95a9160 --- /dev/null +++ b/app/src/main/res/drawable/ll_radio_rectangle10.xml @@ -0,0 +1,8 @@ + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/ll_radio_rectangle11.xml b/app/src/main/res/drawable/ll_radio_rectangle11.xml new file mode 100644 index 0000000..bf250b8 --- /dev/null +++ b/app/src/main/res/drawable/ll_radio_rectangle11.xml @@ -0,0 +1,8 @@ + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/ll_radio_rectangle12.xml b/app/src/main/res/drawable/ll_radio_rectangle12.xml new file mode 100644 index 0000000..1380ff5 --- /dev/null +++ b/app/src/main/res/drawable/ll_radio_rectangle12.xml @@ -0,0 +1,8 @@ + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/ll_radio_rectangle13.xml b/app/src/main/res/drawable/ll_radio_rectangle13.xml new file mode 100644 index 0000000..2775505 --- /dev/null +++ b/app/src/main/res/drawable/ll_radio_rectangle13.xml @@ -0,0 +1,8 @@ + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/ll_radio_rectangle14.xml b/app/src/main/res/drawable/ll_radio_rectangle14.xml new file mode 100644 index 0000000..415cb86 --- /dev/null +++ b/app/src/main/res/drawable/ll_radio_rectangle14.xml @@ -0,0 +1,8 @@ + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/ll_radio_rectangle15.xml b/app/src/main/res/drawable/ll_radio_rectangle15.xml new file mode 100644 index 0000000..d7da59e --- /dev/null +++ b/app/src/main/res/drawable/ll_radio_rectangle15.xml @@ -0,0 +1,8 @@ + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/ll_radio_rectangle16.xml b/app/src/main/res/drawable/ll_radio_rectangle16.xml new file mode 100644 index 0000000..6d7a809 --- /dev/null +++ b/app/src/main/res/drawable/ll_radio_rectangle16.xml @@ -0,0 +1,8 @@ + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/ll_radio_rectangle17.xml b/app/src/main/res/drawable/ll_radio_rectangle17.xml new file mode 100644 index 0000000..3dfdaf1 --- /dev/null +++ b/app/src/main/res/drawable/ll_radio_rectangle17.xml @@ -0,0 +1,8 @@ + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/ll_radio_rectangle2.xml b/app/src/main/res/drawable/ll_radio_rectangle2.xml new file mode 100644 index 0000000..20b8869 --- /dev/null +++ b/app/src/main/res/drawable/ll_radio_rectangle2.xml @@ -0,0 +1,8 @@ + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/ll_radio_rectangle3.xml b/app/src/main/res/drawable/ll_radio_rectangle3.xml new file mode 100644 index 0000000..23b80b4 --- /dev/null +++ b/app/src/main/res/drawable/ll_radio_rectangle3.xml @@ -0,0 +1,8 @@ + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/ll_radio_rectangle4.xml b/app/src/main/res/drawable/ll_radio_rectangle4.xml new file mode 100644 index 0000000..b44c0b4 --- /dev/null +++ b/app/src/main/res/drawable/ll_radio_rectangle4.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/ll_radio_rectangle5.xml b/app/src/main/res/drawable/ll_radio_rectangle5.xml new file mode 100644 index 0000000..f173d3a --- /dev/null +++ b/app/src/main/res/drawable/ll_radio_rectangle5.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/ll_radio_rectangle6.xml b/app/src/main/res/drawable/ll_radio_rectangle6.xml new file mode 100644 index 0000000..e9d70f7 --- /dev/null +++ b/app/src/main/res/drawable/ll_radio_rectangle6.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/ll_radio_rectangle7.xml b/app/src/main/res/drawable/ll_radio_rectangle7.xml new file mode 100644 index 0000000..f44ffcf --- /dev/null +++ b/app/src/main/res/drawable/ll_radio_rectangle7.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/ll_radio_rectangle8.xml b/app/src/main/res/drawable/ll_radio_rectangle8.xml new file mode 100644 index 0000000..3dbeb21 --- /dev/null +++ b/app/src/main/res/drawable/ll_radio_rectangle8.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/ll_radio_rectangle9.xml b/app/src/main/res/drawable/ll_radio_rectangle9.xml new file mode 100644 index 0000000..6639b26 --- /dev/null +++ b/app/src/main/res/drawable/ll_radio_rectangle9.xml @@ -0,0 +1,8 @@ + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/ll_radio_transparent.xml b/app/src/main/res/drawable/ll_radio_transparent.xml new file mode 100644 index 0000000..da83971 --- /dev/null +++ b/app/src/main/res/drawable/ll_radio_transparent.xml @@ -0,0 +1,13 @@ + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/music_bg_white_radius_8.xml b/app/src/main/res/drawable/music_bg_white_radius_8.xml new file mode 100644 index 0000000..4dfeb4d --- /dev/null +++ b/app/src/main/res/drawable/music_bg_white_radius_8.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/music_bottom_seek_progress.xml b/app/src/main/res/drawable/music_bottom_seek_progress.xml new file mode 100644 index 0000000..626a8e6 --- /dev/null +++ b/app/src/main/res/drawable/music_bottom_seek_progress.xml @@ -0,0 +1,28 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/drawable/music_bottom_seek_thumb.xml b/app/src/main/res/drawable/music_bottom_seek_thumb.xml new file mode 100644 index 0000000..c1a9f2f --- /dev/null +++ b/app/src/main/res/drawable/music_bottom_seek_thumb.xml @@ -0,0 +1,21 @@ + + + + + + + + + + + + + + + diff --git a/app/src/main/res/drawable/music_diallog_bottom_bg_white_style.xml b/app/src/main/res/drawable/music_diallog_bottom_bg_white_style.xml new file mode 100644 index 0000000..a0801e7 --- /dev/null +++ b/app/src/main/res/drawable/music_diallog_bottom_bg_white_style.xml @@ -0,0 +1,9 @@ + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/music_dialog_loading_bg.xml b/app/src/main/res/drawable/music_dialog_loading_bg.xml new file mode 100644 index 0000000..6cd5f7d --- /dev/null +++ b/app/src/main/res/drawable/music_dialog_loading_bg.xml @@ -0,0 +1,12 @@ + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/music_download_progressbar.xml b/app/src/main/res/drawable/music_download_progressbar.xml new file mode 100644 index 0000000..e3ab47a --- /dev/null +++ b/app/src/main/res/drawable/music_download_progressbar.xml @@ -0,0 +1,26 @@ + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/music_index_item_list_bottom_bg.xml b/app/src/main/res/drawable/music_index_item_list_bottom_bg.xml new file mode 100644 index 0000000..f8f91a5 --- /dev/null +++ b/app/src/main/res/drawable/music_index_item_list_bottom_bg.xml @@ -0,0 +1,9 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/music_index_item_list_top_bg.xml b/app/src/main/res/drawable/music_index_item_list_top_bg.xml new file mode 100644 index 0000000..4f96ecd --- /dev/null +++ b/app/src/main/res/drawable/music_index_item_list_top_bg.xml @@ -0,0 +1,9 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/music_index_title_line.xml b/app/src/main/res/drawable/music_index_title_line.xml new file mode 100644 index 0000000..046afce --- /dev/null +++ b/app/src/main/res/drawable/music_index_title_line.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/music_list_scroll_bar.xml b/app/src/main/res/drawable/music_list_scroll_bar.xml new file mode 100644 index 0000000..89ca49e --- /dev/null +++ b/app/src/main/res/drawable/music_list_scroll_bar.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/music_loading_anim.xml b/app/src/main/res/drawable/music_loading_anim.xml new file mode 100644 index 0000000..ff4486c --- /dev/null +++ b/app/src/main/res/drawable/music_loading_anim.xml @@ -0,0 +1,16 @@ + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/music_player_last_selector.xml b/app/src/main/res/drawable/music_player_last_selector.xml new file mode 100644 index 0000000..b105a99 --- /dev/null +++ b/app/src/main/res/drawable/music_player_last_selector.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/music_player_next_selector.xml b/app/src/main/res/drawable/music_player_next_selector.xml new file mode 100644 index 0000000..f39a69a --- /dev/null +++ b/app/src/main/res/drawable/music_player_next_selector.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/music_player_pause_selector.xml b/app/src/main/res/drawable/music_player_pause_selector.xml new file mode 100644 index 0000000..ebb24a2 --- /dev/null +++ b/app/src/main/res/drawable/music_player_pause_selector.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/music_player_play_selector.xml b/app/src/main/res/drawable/music_player_play_selector.xml new file mode 100644 index 0000000..e3f983a --- /dev/null +++ b/app/src/main/res/drawable/music_player_play_selector.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/music_search_input_bg.xml b/app/src/main/res/drawable/music_search_input_bg.xml new file mode 100644 index 0000000..e210432 --- /dev/null +++ b/app/src/main/res/drawable/music_search_input_bg.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/music_search_tag_bg.xml b/app/src/main/res/drawable/music_search_tag_bg.xml new file mode 100644 index 0000000..c629e57 --- /dev/null +++ b/app/src/main/res/drawable/music_search_tag_bg.xml @@ -0,0 +1,7 @@ + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/music_song_play_bg.xml b/app/src/main/res/drawable/music_song_play_bg.xml new file mode 100644 index 0000000..4aec855 --- /dev/null +++ b/app/src/main/res/drawable/music_song_play_bg.xml @@ -0,0 +1,6 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/music_tags_driver_trans.xml b/app/src/main/res/drawable/music_tags_driver_trans.xml new file mode 100644 index 0000000..0c5bb98 --- /dev/null +++ b/app/src/main/res/drawable/music_tags_driver_trans.xml @@ -0,0 +1,9 @@ + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/rectangle_round.xml b/app/src/main/res/drawable/rectangle_round.xml new file mode 100644 index 0000000..09b506b --- /dev/null +++ b/app/src/main/res/drawable/rectangle_round.xml @@ -0,0 +1,12 @@ + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/rectangle_round_red_button.xml b/app/src/main/res/drawable/rectangle_round_red_button.xml new file mode 100644 index 0000000..8dddb06 --- /dev/null +++ b/app/src/main/res/drawable/rectangle_round_red_button.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/rectangle_round_transparent.xml b/app/src/main/res/drawable/rectangle_round_transparent.xml new file mode 100644 index 0000000..90587d6 --- /dev/null +++ b/app/src/main/res/drawable/rectangle_round_transparent.xml @@ -0,0 +1,14 @@ + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/round_font_scale.xml b/app/src/main/res/drawable/round_font_scale.xml new file mode 100644 index 0000000..70a9fd9 --- /dev/null +++ b/app/src/main/res/drawable/round_font_scale.xml @@ -0,0 +1,10 @@ + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/round_font_scale_get.xml b/app/src/main/res/drawable/round_font_scale_get.xml new file mode 100644 index 0000000..4d0d709 --- /dev/null +++ b/app/src/main/res/drawable/round_font_scale_get.xml @@ -0,0 +1,6 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/round_font_scale_update.xml b/app/src/main/res/drawable/round_font_scale_update.xml new file mode 100644 index 0000000..2f29a29 --- /dev/null +++ b/app/src/main/res/drawable/round_font_scale_update.xml @@ -0,0 +1,6 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/selector_head_back.xml b/app/src/main/res/drawable/selector_head_back.xml new file mode 100644 index 0000000..27c57f2 --- /dev/null +++ b/app/src/main/res/drawable/selector_head_back.xml @@ -0,0 +1,6 @@ + + + + + + diff --git a/app/src/main/res/drawable/tt_ad_cover_btn_begin_bg.xml b/app/src/main/res/drawable/tt_ad_cover_btn_begin_bg.xml new file mode 100644 index 0000000..864e27b --- /dev/null +++ b/app/src/main/res/drawable/tt_ad_cover_btn_begin_bg.xml @@ -0,0 +1,17 @@ + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/layout/activity_about.xml b/app/src/main/res/layout/activity_about.xml new file mode 100644 index 0000000..bfec89f --- /dev/null +++ b/app/src/main/res/layout/activity_about.xml @@ -0,0 +1,131 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/layout/activity_about_us_layout.xml b/app/src/main/res/layout/activity_about_us_layout.xml new file mode 100644 index 0000000..478f6e9 --- /dev/null +++ b/app/src/main/res/layout/activity_about_us_layout.xml @@ -0,0 +1,316 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/activity_app_layout.xml b/app/src/main/res/layout/activity_app_layout.xml new file mode 100644 index 0000000..b663dc4 --- /dev/null +++ b/app/src/main/res/layout/activity_app_layout.xml @@ -0,0 +1,15 @@ + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/activity_budgets_layout.xml b/app/src/main/res/layout/activity_budgets_layout.xml new file mode 100644 index 0000000..cb9a5d3 --- /dev/null +++ b/app/src/main/res/layout/activity_budgets_layout.xml @@ -0,0 +1,193 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/activity_detail_add.xml b/app/src/main/res/layout/activity_detail_add.xml new file mode 100644 index 0000000..474cced --- /dev/null +++ b/app/src/main/res/layout/activity_detail_add.xml @@ -0,0 +1,99 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/activity_encrypt_home_layout.xml b/app/src/main/res/layout/activity_encrypt_home_layout.xml new file mode 100644 index 0000000..c50b2a1 --- /dev/null +++ b/app/src/main/res/layout/activity_encrypt_home_layout.xml @@ -0,0 +1,104 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/activity_export_layout.xml b/app/src/main/res/layout/activity_export_layout.xml new file mode 100644 index 0000000..1508b0d --- /dev/null +++ b/app/src/main/res/layout/activity_export_layout.xml @@ -0,0 +1,154 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/layout/activity_favorite_layout.xml b/app/src/main/res/layout/activity_favorite_layout.xml new file mode 100644 index 0000000..155fc2d --- /dev/null +++ b/app/src/main/res/layout/activity_favorite_layout.xml @@ -0,0 +1,30 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/activity_files_home_layout.xml b/app/src/main/res/layout/activity_files_home_layout.xml new file mode 100644 index 0000000..5151c39 --- /dev/null +++ b/app/src/main/res/layout/activity_files_home_layout.xml @@ -0,0 +1,189 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/activity_files_zip_layout.xml b/app/src/main/res/layout/activity_files_zip_layout.xml new file mode 100644 index 0000000..ebda2f9 --- /dev/null +++ b/app/src/main/res/layout/activity_files_zip_layout.xml @@ -0,0 +1,28 @@ + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/activity_fm_sd.xml b/app/src/main/res/layout/activity_fm_sd.xml new file mode 100644 index 0000000..ad22e31 --- /dev/null +++ b/app/src/main/res/layout/activity_fm_sd.xml @@ -0,0 +1,50 @@ + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/layout/activity_font.xml b/app/src/main/res/layout/activity_font.xml new file mode 100644 index 0000000..bc792dd --- /dev/null +++ b/app/src/main/res/layout/activity_font.xml @@ -0,0 +1,221 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/layout/activity_login.xml b/app/src/main/res/layout/activity_login.xml new file mode 100644 index 0000000..2648bb8 --- /dev/null +++ b/app/src/main/res/layout/activity_login.xml @@ -0,0 +1,165 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml new file mode 100644 index 0000000..9955cee --- /dev/null +++ b/app/src/main/res/layout/activity_main.xml @@ -0,0 +1,60 @@ + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/activity_photo_list.xml b/app/src/main/res/layout/activity_photo_list.xml new file mode 100644 index 0000000..233fcf9 --- /dev/null +++ b/app/src/main/res/layout/activity_photo_list.xml @@ -0,0 +1,29 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/activity_photo_scan.xml b/app/src/main/res/layout/activity_photo_scan.xml new file mode 100644 index 0000000..8d34c61 --- /dev/null +++ b/app/src/main/res/layout/activity_photo_scan.xml @@ -0,0 +1,27 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/activity_player_list.xml b/app/src/main/res/layout/activity_player_list.xml new file mode 100644 index 0000000..cf73ae8 --- /dev/null +++ b/app/src/main/res/layout/activity_player_list.xml @@ -0,0 +1,16 @@ + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/activity_pwd_edit_layout.xml b/app/src/main/res/layout/activity_pwd_edit_layout.xml new file mode 100644 index 0000000..ddc4cbc --- /dev/null +++ b/app/src/main/res/layout/activity_pwd_edit_layout.xml @@ -0,0 +1,74 @@ + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/activity_pwd_verify_layout.xml b/app/src/main/res/layout/activity_pwd_verify_layout.xml new file mode 100644 index 0000000..4320a0b --- /dev/null +++ b/app/src/main/res/layout/activity_pwd_verify_layout.xml @@ -0,0 +1,88 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/activity_setting_more_layout.xml b/app/src/main/res/layout/activity_setting_more_layout.xml new file mode 100644 index 0000000..5608b66 --- /dev/null +++ b/app/src/main/res/layout/activity_setting_more_layout.xml @@ -0,0 +1,258 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/layout/activity_splash.xml b/app/src/main/res/layout/activity_splash.xml new file mode 100644 index 0000000..147a30c --- /dev/null +++ b/app/src/main/res/layout/activity_splash.xml @@ -0,0 +1,16 @@ + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/activity_suggest_layout.xml b/app/src/main/res/layout/activity_suggest_layout.xml new file mode 100644 index 0000000..02de016 --- /dev/null +++ b/app/src/main/res/layout/activity_suggest_layout.xml @@ -0,0 +1,130 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/layout/activity_teamwork_layout.xml b/app/src/main/res/layout/activity_teamwork_layout.xml new file mode 100644 index 0000000..2323ca0 --- /dev/null +++ b/app/src/main/res/layout/activity_teamwork_layout.xml @@ -0,0 +1,77 @@ + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/activity_transactions_layout.xml b/app/src/main/res/layout/activity_transactions_layout.xml new file mode 100644 index 0000000..219075c --- /dev/null +++ b/app/src/main/res/layout/activity_transactions_layout.xml @@ -0,0 +1,280 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/activity_version_update.xml b/app/src/main/res/layout/activity_version_update.xml new file mode 100644 index 0000000..4c26f05 --- /dev/null +++ b/app/src/main/res/layout/activity_version_update.xml @@ -0,0 +1,144 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/activity_video_list_layout.xml b/app/src/main/res/layout/activity_video_list_layout.xml new file mode 100644 index 0000000..f384edd --- /dev/null +++ b/app/src/main/res/layout/activity_video_list_layout.xml @@ -0,0 +1,116 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/layout/activity_video_player.xml b/app/src/main/res/layout/activity_video_player.xml new file mode 100644 index 0000000..daceaa7 --- /dev/null +++ b/app/src/main/res/layout/activity_video_player.xml @@ -0,0 +1,95 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + +